/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.brooklyn.entity.group; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.entity.Group; import org.apache.brooklyn.core.BrooklynFeatureEnablement; import org.apache.brooklyn.core.entity.AbstractEntity; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.entity.stock.DelegateEntity; import org.apache.brooklyn.util.collections.SetFromLiveMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; 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(AbstractGroupImpl.class); private Set<Entity> members = Sets.newLinkedHashSet(); public AbstractGroupImpl() { } @Deprecated public AbstractGroupImpl(@SuppressWarnings("rawtypes") Map flags, Entity parent) { super(flags, parent); } @Override public void setManagementContext(ManagementContextInternal managementContext) { super.setManagementContext(managementContext); if (BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_USE_BROOKLYN_LIVE_OBJECTS_DATAGRID_STORAGE)) { Set<Entity> oldMembers = members; members = SetFromLiveMap.create(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(); sensors().set(GROUP_SIZE, 0); sensors().set(GROUP_MEMBERS, ImmutableList.<Entity>of()); } @Override protected void initEnrichers() { super.initEnrichers(); // check states and upness separately so they can be individually replaced if desired // problem if any children or members are on fire ServiceStateLogic.newEnricherFromChildrenState().checkChildrenAndMembers().requireRunningChildren(getConfig(RUNNING_QUORUM_CHECK)).addTo(this); // defaults to requiring at least one member or child who is up ServiceStateLogic.newEnricherFromChildrenUp().checkChildrenAndMembers().requireUpChildren(getConfig(UP_QUORUM_CHECK)).addTo(this); } /** * 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) { if (Entities.isNoLongerManaged(member)) { // Don't add dead entities, as they could never be removed (because addMember could be called in // concurrent thread as removeMember triggered by the unmanage). // Not using Entities.isManaged here, as could be called in entity.init() log.debug("Group {} ignoring new member {}, because it is no longer managed", this, member); return false; } // FIXME do not set sensors on members; possibly we don't need FIRST at all, just look at the first in MEMBERS, and take care to guarantee order there Entity first = getAttribute(FIRST); if (first == null) { ((EntityLocal) member).sensors().set(FIRST_MEMBER, true); ((EntityLocal) member).sensors().set(FIRST, member); sensors().set(FIRST, member); } else { if (first.equals(member) || first.equals(member.getAttribute(FIRST))) { // do nothing (rebinding) } else { ((EntityLocal) member).sensors().set(FIRST_MEMBER, false); ((EntityLocal) member).sensors().set(FIRST, first); } } member.addGroup((Group)getProxyIfAvailable()); boolean changed = addMemberInternal(member); if (changed) { log.debug("Group {} got new member {}", this, member); sensors().set(GROUP_SIZE, getCurrentSize()); sensors().set(GROUP_MEMBERS, getMembers()); // emit after the above so listeners can use getMembers() and getCurrentSize() sensors().emit(MEMBER_ADDED, member); if (Boolean.TRUE.equals(getConfig(MEMBER_DELEGATE_CHILDREN))) { log.warn("Use of deprecated ConfigKey {} in {} (as of 0.9.0)", MEMBER_DELEGATE_CHILDREN.getName(), this); Optional<Entity> result = Iterables.tryFind(getChildren(), Predicates.equalTo(member)); if (!result.isPresent()) { String nameFormat = Optional.fromNullable(getConfig(MEMBER_DELEGATE_NAME_FORMAT)).or("%s"); DelegateEntity child = addChild(EntitySpec.create(DelegateEntity.class) .configure(DelegateEntity.DELEGATE_ENTITY, member) .displayName(String.format(nameFormat, member.getDisplayName()))); } } getManagementSupport().getEntityChangeListener().onMembersChanged(); } return changed; } } // visible for rebind public boolean addMemberInternal(Entity member) { synchronized (members) { return members.add(member); } } /** * Returns {@code true} if the group was changed as a result of the call. */ @Override public boolean removeMember(final Entity member) { synchronized (members) { member.removeGroup((Group)getProxyIfAvailable()); boolean changed = (member != null && members.remove(member)); if (changed) { log.debug("Group {} lost member {}", this, member); // TODO ideally the following are all synched sensors().set(GROUP_SIZE, getCurrentSize()); sensors().set(GROUP_MEMBERS, getMembers()); if (member.equals(getAttribute(FIRST))) { // TODO should we elect a new FIRST ? as is the *next* will become first. could we do away with FIRST altogether? sensors().set(FIRST, null); } // emit after the above so listeners can use getMembers() and getCurrentSize() sensors().emit(MEMBER_REMOVED, member); if (Boolean.TRUE.equals(getConfig(MEMBER_DELEGATE_CHILDREN))) { Optional<Entity> result = Iterables.tryFind(getChildren(), new Predicate<Entity>() { @Override public boolean apply(Entity input) { Entity delegate = input.getConfig(DelegateEntity.DELEGATE_ENTITY); if (delegate == null) return false; return delegate.equals(member); } }); if (result.isPresent()) { Entity child = result.get(); removeChild(child); Entities.unmanage(child); } } 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() { // TODO use this instead; see issue and email thread where this comment was introduced // relations().getRelations(EntityRelations.GROUP_CONTAINS); 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(); } } @Override public <T extends Entity> T addMemberChild(EntitySpec<T> spec) { T child = addChild(spec); addMember(child); return child; } @Override public <T extends Entity> T addMemberChild(T child) { child = addChild(child); addMember(child); return child; } }