Package fr.imag.adele.apam.apform.impl.handlers

Source Code of fr.imag.adele.apam.apform.impl.handlers.InterfaceInjectionManager

/**
* 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;

    }

}
TOP

Related Classes of fr.imag.adele.apam.apform.impl.handlers.InterfaceInjectionManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.