/**
* Copyright 2011-2012 Universite Joseph Fourier, LIG, ADELE team
* Licensed 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 fr.imag.adele.apam.manager.conflict;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.felix.ipojo.annotations.Instantiate;
import org.apache.felix.ipojo.annotations.Invalidate;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Requires;
import org.apache.felix.ipojo.annotations.Validate;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fr.imag.adele.apam.Apam;
import fr.imag.adele.apam.ApamManagers;
import fr.imag.adele.apam.CST;
import fr.imag.adele.apam.Component;
import fr.imag.adele.apam.Composite;
import fr.imag.adele.apam.CompositeType;
import fr.imag.adele.apam.ContextualManager;
import fr.imag.adele.apam.DynamicManager;
import fr.imag.adele.apam.Instance;
import fr.imag.adele.apam.Link;
import fr.imag.adele.apam.PropertyManager;
import fr.imag.adele.apam.RelToResolve;
import fr.imag.adele.apam.RelationManager;
import fr.imag.adele.apam.Resolved;
import fr.imag.adele.apam.declarations.ComponentKind;
import fr.imag.adele.apam.declarations.OwnedComponentDeclaration;
import fr.imag.adele.apam.impl.APAMImpl;
import fr.imag.adele.apam.impl.ComponentImpl.InvalidConfiguration;
import fr.imag.adele.apam.impl.CompositeImpl;
import fr.imag.adele.apam.impl.FailedResolutionManager;
/**
* This class is the entry point of the dynamic manager implementation.
*
*
* @author vega
*
*/
@Instantiate(name = "ConflictManager-Instance")
@org.apache.felix.ipojo.annotations.Component(name = "ConflictManager", immediate = true)
@Provides
public class ConflictManager implements ContextualManager, RelationManager, DynamicManager, PropertyManager {
private final static Logger logger = LoggerFactory.getLogger(ConflictManager.class);
/**
* A reference to the APAM machine
*/
@Requires(proxy = false)
private Apam apam;
/**
* The content managers of all composites in APAM
*/
private Map<Composite, ContentManager> contentManagers;
/**
* The content manager associated with the root composite
*/
private ContentManager rootManager;
public ConflictManager(BundleContext context) {
}
@Override
public String getName() {
return "ConflictManager";
}
/**
* Conflict Manager is a contextual manager, but it does not have its own declared model,
* all the information is in the component declaration.
*
*/
@Override
public void initializeContext(CompositeType composite) {
}
/**
* For owned instances that could match a resolution request, we must be
* sure that the grants are respected.
*/
@Override
public boolean beginResolving(RelToResolve relation) {
if (!relation.getTargetKind().equals(ComponentKind.INSTANCE)) {
return false;
}
/**
* Iterate over all owned instances that could satisfy this request, and verify if access is granted.
* In case access is not allowed, add the corresponding constraints to the relation
*
* WARNING Notice that this is a global validation, irrespective of composites. We verify all visible
* instances that could satisfy the request.
*/
for (ContentManager container : getManagers()) {
container.addGrantConstraints(relation);
}
return false;
}
/**
* This manager only handles conflicts, it doesn't resolve relations. It
* only participates in resolution to add constraints to enforce conflict
* management rules.
*/
@Override
public Resolved<?> resolve(RelToResolve relation) {
throw new UnsupportedOperationException("Conflict Manager doesn't resolve relations");
}
@Override
public void addedComponent(Component component) {
/*
* Get the list of currently existing managers
*/
Collection<ContentManager> managers = getManagers();
/*
* Create a content manager associated to newly created composites
*/
if (component instanceof Composite) {
Composite composite = (Composite) component;
if (getManager(composite) != null) {
logger.error("Composite already added in APAM "+ composite.getName());
return;
}
try {
ContentManager manager = new ContentManager(this, composite);
/*
* Validate there is no conflict in ownership declarations with
* existing composites
*/
for (ContentManager existingManager : managers) {
Set<OwnedComponentDeclaration> conflicts = manager.getConflictingDeclarations(existingManager);
if (!conflicts.isEmpty()) {
throw new InvalidConfiguration("Invalid owned declaration, conflicts with "+
existingManager.getComposite().getName() + ":" + conflicts);
}
}
/*
* register manager
*/
synchronized (this) {
contentManagers.put(composite, manager);
}
manager.start();
/*
* For all the existing instances we consider the impact of the
* newly created composite in ownership
*/
for (Instance instance : CST.componentBroker.getInsts()) {
verifyOwnership(instance);
}
} catch (InvalidConfiguration error) {
/*
* TODO We should not add the composite in APAM if the content
* manager could not be created, but currently there is no way
* for a manager to signal an error in creation.
*/
logger.error("Error creating content manager for composite "+ component.getName(), error);
}
}
/*
* Verify ownership of newly created instances
*/
if (component instanceof Instance) {
verifyOwnership((Instance) component);
}
}
@Override
public void addedLink(Link link) {
}
@Override
public void attributeAdded(Component component, String attr, String newValue) {
propertyChanged(component, attr);
}
@Override
public void attributeChanged(Component component, String attr, String newValue, String oldValue) {
propertyChanged(component, attr);
}
@Override
public void attributeRemoved(Component component, String attr, String oldValue) {
propertyChanged(component, attr);
}
/**
* Give access to the APAM reference
*/
public Apam getApam() {
return apam;
}
/**
* Give access to the failure manager
*/
public FailedResolutionManager getFailureManager() {
return ((APAMImpl)apam).getFailedResolutionManager();
}
/**
* Get the manager associated to a composite
*/
private synchronized ContentManager getManager(Composite composite) {
return composite != null ? contentManagers.get(composite) : contentManagers.get(CompositeImpl.getRootAllComposites());
}
/**
* Get a thread safe (stack contained) copy of the current list of managers
*/
private synchronized Collection<ContentManager> getManagers() {
return new ArrayList<ContentManager>(contentManagers.values());
}
private void propertyChanged(Component component, String property) {
/*
* If an instance attribute is modified, this may change ownership
* and/or the state of its container
*/
if (component instanceof Instance) {
verifyOwnership((Instance) component);
ContentManager container = getManager(((Instance) component).getComposite());
if (container != null) {
container.propertyChanged((Instance) component, property);
}
}
}
@Override
public void removedComponent(Component component) {
/*
* Remove destroyed instance from the content manager of its container
*/
if (component instanceof Instance) {
ContentManager container = getManager(((Instance) component).getComposite());
if (container != null) {
container.removedInstance((Instance) component);
}
}
/*
* Remove a content manager when its composite is removed
*/
if (component instanceof Composite) {
ContentManager manager = getManager((Composite) component);
synchronized (this) {
contentManagers.remove(component);
}
manager.dispose();
}
}
@Override
public void removedLink(Link link) {
}
/**
* This method is automatically invoked when the manager is validated, so we
* can safely assume that APAM is available
*
*/
@Validate
private synchronized void start() {
/*
* Create the default content manager to be associated with the root
* composite
*/
try {
contentManagers = new HashMap<Composite, ContentManager>();
Composite root = CompositeImpl.getRootAllComposites();
rootManager = new ContentManager(this, root);
contentManagers.put(root, rootManager);
rootManager.start();
} catch (InvalidConfiguration ignored) {
}
/*
* Register with APAM
*/
ApamManagers.addRelationManager(this, Priority.HIGHEST);
ApamManagers.addDynamicManager(this);
ApamManagers.addPropertyManager(this);
/*
* TODO if conflict manager is started or restarted after APAM, should
* we verify if there are already created composites?
*/
}
/**
* This method is automatically invoked when the manager is invalidated, so
* APAM is no longer available
*/
@Invalidate
private synchronized void stop() {
ApamManagers.removeRelationManager(this);
ApamManagers.removeDynamicManager(this);
ApamManagers.removePropertyManager(this);
}
/**
* Set the ownership of an instance to one of the requesting composites,
* signal any detected conflict
*/
private void verifyOwnership(Instance instance) {
/*
* Verify that the current container is registered in dynaman, otherwise
* postpone handling of the event
*/
ContentManager container = getManager(instance.getComposite());
if (container == null) {
return;
}
Collection<ContentManager> managers = getManagers();
ContentManager owner = container.owns(instance) ? container : null;
/*
* Get the list of composites requesting ownership.
*/
List<ContentManager> requesters = new ArrayList<ContentManager>();
StringBuffer requestersNames = new StringBuffer();
for (ContentManager manager : managers) {
if (manager.shouldOwn(instance)) {
requesters.add(manager);
requestersNames.append(" ");
requestersNames.append(manager.getComposite().getName());
}
}
/*
* If there is conflicting requests signal an error
*
* TODO In some cases we could do more than simply logging the error.
* Perhaps avoiding creating composites that will produce conflicts.
*/
if (requesters.size() > 1) {
logger.error("Conflict in ownership : composites ("+ requestersNames + ") request ownership of instance "+ instance.getName());
}
/*
* If there is no ownership request continue processing event
*/
if (requesters.isEmpty()) {
return;
}
/*
* Choose an owner arbitrarily among requesters (try to keep the current
* owner if exists)
*/
ContentManager newOwner = requesters.isEmpty() ? null : requesters.contains(container) ? container : requesters.get(0);
/*
* Revoke ownership to previous owner (if it has changed)
*/
if (owner != null && (newOwner == null || !newOwner.equals(owner))) {
owner.revokeOwnership(instance);
}
/*
* Accord ownership to new owner (if it has changed)
*/
if (newOwner != null && (owner == null || !owner.equals(newOwner))) {
newOwner.accordOwnership(instance);
}
}
}