blob: 9d7e0494388267e10cbe376a43aba8df36e50ca0 [file] [log] [blame]
package brooklyn.entity.basic;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import brooklyn.entity.Entity;
import brooklyn.entity.trait.Changeable;
import brooklyn.management.internal.ManagementContextInternal;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
/**
* Represents a group of entities - sub-classes can support dynamically changing membership,
* ad hoc groupings, etc.
* <p>
* Synchronization model. When changing and reading the group membership, this class uses internal
* synchronization to ensure atomic operations and the "happens-before" relationship for reads/updates
* from different threads. Sub-classes should not use this same synchronization mutex when doing
* expensive operations - e.g. if resizing a cluster, don't block everyone else from asking for the
* current number of members.
*/
public abstract class AbstractGroupImpl extends AbstractEntity implements AbstractGroup {
private static final Logger log = LoggerFactory.getLogger(AbstractGroup.class);
private Set<Entity> members = Sets.newLinkedHashSet();
public AbstractGroupImpl() {
}
@Override
public void setManagementContext(ManagementContextInternal managementContext) {
super.setManagementContext(managementContext);
Set<Entity> oldMembers = members;
members = Collections.newSetFromMap(managementContext.getStorage().<Entity,Boolean>getMap(getId()+"-members"));
// Only override stored defaults if we have actual values. We might be in setManagementContext
// because we are reconstituting an existing entity in a new brooklyn management-node (in which
// case believe what is already in the storage), or we might be in the middle of creating a new
// entity. Normally for a new entity (using EntitySpec creation approach), this will get called
// before setting the parent etc. However, for backwards compatibility we still support some
// things calling the entity's constructor directly.
if (oldMembers.size() > 0) members.addAll(oldMembers);
}
@Override
public void init() {
super.init();
setAttribute(Changeable.GROUP_SIZE, 0);
}
/**
* Adds the given entity as a member of this group <em>and</em> this group as one of the groups of the child
*/
@Override
public boolean addMember(Entity member) {
synchronized (members) {
member.addGroup(this);
boolean changed = members.add(member);
if (changed) {
log.debug("Group {} got new member {}", this, member);
emit(MEMBER_ADDED, member);
setAttribute(Changeable.GROUP_SIZE, getCurrentSize());
getManagementSupport().getEntityChangeListener().onMembersChanged();
}
return changed;
}
}
/**
* Returns {@code true} if the group was changed as a result of the call.
*/
@Override
public boolean removeMember(Entity member) {
synchronized (members) {
boolean changed = (member != null && members.remove(member));
if (changed) {
log.debug("Group {} lost member {}", this, member);
emit(MEMBER_REMOVED, member);
setAttribute(Changeable.GROUP_SIZE, getCurrentSize());
getManagementSupport().getEntityChangeListener().onMembersChanged();
}
return changed;
}
}
@Override
public void setMembers(Collection<Entity> m) {
setMembers(m, null);
}
@Override
public void setMembers(Collection<Entity> mm, Predicate<Entity> filter) {
synchronized (members) {
log.debug("Group {} members set explicitly to {} (of which some possibly filtered)", this, members);
List<Entity> mmo = new ArrayList<Entity>(getMembers());
for (Entity m: mmo) {
if (!(mm.contains(m) && (filter==null || filter.apply(m))))
// remove, unless already present, being set, and not filtered out
removeMember(m);
}
for (Entity m: mm) {
if ((!mmo.contains(m)) && (filter==null || filter.apply(m))) {
// add if not alrady contained, and not filtered out
addMember(m);
}
}
getManagementSupport().getEntityChangeListener().onMembersChanged();
}
}
@Override
public Collection<Entity> getMembers() {
synchronized (members) {
return ImmutableSet.<Entity>copyOf(members);
}
}
@Override
public boolean hasMember(Entity e) {
synchronized (members) {
return members.contains(e);
}
}
@Override
public Integer getCurrentSize() {
synchronized (members) {
return members.size();
}
}
}