/**
* 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.apform.impl.handlers;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import org.apache.felix.ipojo.ComponentFactory;
import org.apache.felix.ipojo.metadata.Attribute;
import org.apache.felix.ipojo.metadata.Element;
import org.apache.felix.ipojo.parser.FieldMetadata;
import fr.imag.adele.apam.Component;
import fr.imag.adele.apam.Implementation;
import fr.imag.adele.apam.Instance;
import fr.imag.adele.apam.Specification;
import fr.imag.adele.apam.apform.impl.ApamComponentFactory;
import fr.imag.adele.apam.declarations.RelationDeclaration;
import fr.imag.adele.apam.declarations.RequirerInstrumentation;
import fr.imag.adele.apam.declarations.references.resources.InterfaceReference;
/**
* This class keeps track of an APAM interface relation, it handles the
* calculation of the target services based on updates to the application model.
*
* @author vega
*
*/
public class InterfaceInjectionManager implements RelationInjectionManager {
/**
* The associated resolver
*/
private final Resolver resolver;
/**
* The relation that is going to be injected
*/
private final RelationDeclaration relation;
/**
* The relation injection managed by this relation
*/
private final RequirerInstrumentation injection;
/**
* The metadata of the field that must be injected
*/
private final Class<?> fieldClass;
private final boolean isCollection;
/**
* The list of target APAM instances of this relation.
*/
private final Set<Component> targetServices;
/**
* The last injected value.
*
* This is a cached value that must be recalculated in case of update of the
* relation.
*/
private Object injectedValue;
public InterfaceInjectionManager(ComponentFactory factory, Resolver resolver, RelationInjectionHandler handler, RelationDeclaration relation, RequirerInstrumentation injection) throws ClassNotFoundException {
assert injection.getRequiredResource() instanceof InterfaceReference;
this.resolver = resolver;
this.relation = relation;
this.injection = injection;
/*
* Get field metadata
*
* TODO We keep a reference to the class of the field, this may prevent the class loader from being
* garbage collected and the class from being updated. We need to verify what is the behavior of OSGi
* when the class of a field is updated, and adjust this implementation accordingly.
*/
FieldMetadata field = factory.getPojoMetadata().getField(injection.getName());
String fieldType = FieldMetadata.getReflectionType(field.getFieldType());
this.fieldClass = factory.loadClass(fieldType);
this.isCollection = injection.acceptMultipleProviders();
/*
* Initialize target services
*/
targetServices = new HashSet<Component>();
injectedValue = null;
resolver.addInjection(this);
}
/**
* The relation to be injectet
*/
@Override
public RelationDeclaration getRelation() {
return relation;
}
/**
* The relation injection associated to this manager
*/
@Override
public RequirerInstrumentation getRelationInjection() {
return injection;
}
/**
* Get an XML representation of the state of this relation
*/
@Override
public Element getDescription() {
Element relationDescription = new Element("injection", ApamComponentFactory.APAM_NAMESPACE);
relationDescription.addAttribute(new Attribute("relation", relation.getIdentifier()));
relationDescription.addAttribute(new Attribute("target", relation.getTarget().toString()));
relationDescription.addAttribute(new Attribute("name", injection.getName()));
relationDescription.addAttribute(new Attribute("type", injection.getRequiredResource().toString()));
relationDescription.addAttribute(new Attribute("isAggregate", Boolean.toString(injection.acceptMultipleProviders())));
/*
* show the current state of resolution. To avoid unnecessary
* synchronization overhead make a copy of the current target services
* and do not use directly the field that can be concurrently modified
*/
Set<Component> resolutions = new HashSet<Component>();
synchronized (this) {
resolutions.addAll(targetServices);
}
relationDescription.addAttribute(new Attribute("resolved", Boolean.toString(!resolutions.isEmpty())));
for (Component target : resolutions) {
Element bindingDescription = new Element("binding",ApamComponentFactory.APAM_NAMESPACE);
bindingDescription.addAttribute(new Attribute("target", target.getName()));
relationDescription.addElement(bindingDescription);
}
return relationDescription;
}
/**
* Interface injection doesn't require any external service, so it is always
* available
*/
@Override
public boolean isValid() {
return true;
}
/*
* (non-Javadoc)
*
* @see
* fr.imag.adele.apam.apform.impl.handlers.relationInjectionManager#addTarget
* (fr.imag.adele.apam.Instance)
*/
@Override
public void addTarget(Component target) {
/*
* Add this target and invalidate cache
*/
synchronized (this) {
targetServices.add(target);
injectedValue = null;
}
}
/*
* (non-Javadoc)
*
* @see
* fr.imag.adele.apam.apform.impl.handlers.relationInjectionManager#removeTarget
* (fr.imag.adele.apam.Instance)
*/
@Override
public void removeTarget(Component target) {
/*
* Remove this target and invalidate cache
*/
synchronized (this) {
targetServices.remove(target);
injectedValue = null;
}
}
@Override
public void onSet(Object pojo, String fieldName, Object value) {
/*
* If the field is nullified we interpret this as an indication from the
* component to release the currently bound instances and force a
* resolution
*/
if (value == null)
resolver.unresolve(this);
}
@Override
public Object onGet(Object pojo, String fieldName, Object value) {
synchronized (this) {
/*
* First try the fast path, use the cached value if still valid
*/
if (injectedValue != null)
return injectedValue;
/*
* Next handle the case in which we need to update the cached value,
* but the relation is resolved
*/
if (!isCollection && !targetServices.isEmpty()) {
injectedValue = getInjectedValue();
return injectedValue;
}
/*
* The worst case is when we need to resolve the relation.
*
* IMPORTANT notice that resolution is performed outside the
* synchronization block. This is because resolution is a
* side-effect process that can trigger wire notifications for this
* relation. These notifications can originate in other threads (for
* example in the cases when the resolution triggers a deployment)
* and that would lead to deadlocks if we keep this object locked.
*/
}
/*
* Ask APAM to resolve the relation. Depending on the application
* policies this may throw an error, or block the thread until the
* relation is fulfilled, or do nothing.
*
* Resolution has as side-effect a modification of the target services.
*/
resolver.resolve(this);
/*
* update cached values after resolution
*/
synchronized (this) {
injectedValue = !targetServices.isEmpty() ? getInjectedValue() : null;
return injectedValue;
}
}
/**
* Get the value to be injected in the field. The returned object depends on
* the cardinality of the relation.
*
* For scalar dependencies, returns directly the service object associated
* with the target instance. For unresolved dependencies returns null.
*
* For aggregate dependencies, returns a collection of service objects. The
* kind of collection is chosen to match the declared class of the field.
* For unresolved dependencies returns null.
*
* In principle the returned object should not be aliased (by keeping a
* reference to it or passing it in parameters to other classes), as it is
* potentially dynamically recalculated every time the field is accessed.
*
* In the case of aggregate dependencies, the returned collection should be
* immutable, as it is calculated by APAM according to global application
* policies.
*
* We suppose that components are well behaved and follow these
* restrictions, so we do not have a complex machinery to enforce this. This
* method directly return the service object or a non thread-safe mutable
* collection of service objects.
*
* TODO if we want to support a less restrictive programming model (allowing
* aliasing), or to be more robust against bad behaved components, we should
* use smart proxies and collections backed up by the information in this
* relation object.
*
*/
private final Object getInjectedValue() {
/*
* TODO change injection to better handle this new case
*/
String injectionClass = injection.getRequiredResource().as(InterfaceReference.class).getJavaType();
boolean injectComponent = injectionClass.equals(Instance.class.getName()) ||
injectionClass.equals(Implementation.class.getName()) ||
injectionClass.equals(Specification.class.getName()) ||
injectionClass.equals(Component.class.getName());
/*
* For scalar dependencies return any of the target objects wired
*/
if (!isCollection) {
Component target = targetServices.iterator().next();
return injectComponent ? target : ((Instance) target).getServiceObject();
}
/*
* For arrays, we need to reflectively build a type conforming array
* initialized to the list of target objects
*/
if (fieldClass.isArray()) {
int index = 0;
Object array = Array.newInstance(fieldClass.getComponentType(), targetServices.size());
for (Component targetService : targetServices) {
Array.set(array, index++, injectComponent ? targetService : ((Instance) targetService).getServiceObject());
}
return array;
}
/*
* For collections, use an erased Object collection of the target objects, with the type that that better fits the
* class of the field
*/
Collection<Object> serviceObjects = null;
if (Vector.class.isAssignableFrom(fieldClass)) {
serviceObjects = new Vector<Object>(targetServices.size());
} else if (List.class.isAssignableFrom(fieldClass)) {
serviceObjects = new ArrayList<Object>(targetServices.size());
} else if (Set.class.isAssignableFrom(fieldClass)) {
serviceObjects = new HashSet<Object>(targetServices.size());
} else if (Collection.class.isAssignableFrom(fieldClass)) {
serviceObjects = new ArrayList<Object>(targetServices.size());
} else
return null;
/*
* fill the collection with the target objects
*/
for (Component targetService : targetServices) {
serviceObjects.add(injectComponent ? targetService : ((Instance) targetService).getServiceObject());
}
return serviceObjects;
}
}