All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ow2.frascati.tinfi.control.content.ContentClassMetaData Maven / Gradle / Ivy

The newest version!
/***
 * OW2 FraSCAti Tinfi
 * Copyright (C) 2008-2018 Inria, Univ. Lille
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Contact: [email protected]
 *
 * Author: Lionel Seinturier
 * Contributor: Philippe Merle
 */

package org.ow2.frascati.tinfi.control.content;

import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;

import org.oasisopen.sca.ComponentContext;
import org.oasisopen.sca.annotation.Callback;
import org.oasisopen.sca.annotation.ComponentName;
import org.oasisopen.sca.annotation.Context;
import org.oasisopen.sca.annotation.Destroy;
import org.oasisopen.sca.annotation.EagerInit;
import org.oasisopen.sca.annotation.Init;
import org.oasisopen.sca.annotation.Property;
import org.oasisopen.sca.annotation.Reference;
import org.oasisopen.sca.annotation.Scope;
import org.objectweb.fractal.fraclet.annotations.Attribute;
import org.objectweb.fractal.fraclet.annotations.Lifecycle;
import org.objectweb.fractal.fraclet.annotations.Requires;
import org.objectweb.fractal.fraclet.extensions.Controller;
import org.objectweb.fractal.fraclet.types.Step;
import org.objectweb.fractal.juliac.commons.ipf.CompositeInjectionPointHashMap;
import org.objectweb.fractal.juliac.commons.ipf.DuplicationInjectionPointException;
import org.objectweb.fractal.juliac.commons.ipf.InjectionPoint;
import org.objectweb.fractal.juliac.commons.ipf.InjectionPointImpl;
import org.objectweb.fractal.juliac.commons.lang.ClassHelper;
import org.objectweb.fractal.juliac.commons.lang.annotation.AnnotationHelper;
import org.objectweb.fractal.juliac.commons.lang.reflect.AccessibleObjectHelper;
import org.objectweb.fractal.juliac.commons.lang.reflect.AnnotatedElementFilter;
import org.objectweb.fractal.juliac.commons.lang.reflect.AnnotatedElementHelper;
import org.objectweb.fractal.juliac.commons.lang.reflect.MethodHelper;
import org.objectweb.fractal.juliac.commons.util.function.CompositeOrFilter;
import org.objectweb.fractal.juliac.commons.util.function.Filters;
import org.osoa.sca.annotations.ConversationAttributes;
import org.osoa.sca.annotations.ConversationID;
import org.ow2.frascati.tinfi.annotations.Provides;

/**
 * This class stores SCA related metadata for the content class of a component.
 * 
 * @author Lionel Seinturier 
 * @author Philippe Merle 
 */
public class ContentClassMetaData {

    // -----------------------------------------------------------------------
    // Factory
    // -----------------------------------------------------------------------
    
    /**
     * Retrieve the SCA related metadata for the specified component class.
     * 
     * @param c  the component class (so called content class)
     * @return   the corresponding metadata
     * @throws IllegalContentClassMetaData
     *      if an error occurs while retrieving metadata 
     */
    public static ContentClassMetaData get( Class c )
    throws IllegalContentClassMetaData {
        
        ContentClassMetaData ccmd = ccmds.get(c);
        if( ccmd == null ) {
            ccmd = new ContentClassMetaData(c);
            ccmds.put(c,ccmd);
        }
        
        return ccmd;
    }
    
    /**
     * The map of {@link ContentClassMetaData} instances indexed by the content
     * classes which they are associated to. Up to Tinfi 1.1.2, this map was
     * indexed by the name of the content class.
     * 
     * Following a discussion with Philippe and Nicolas D who are envisionning
     * some scenarios for the FP7 SOA4All project where different content
     * classes but with the same name (hence loaded by different classloaders),
     * are used to reconfigure an SCA application, this map is indexed by the
     * classes.
     * 
     * TODO provide a JUnit test
     */
    private static Map,ContentClassMetaData> ccmds =
        new HashMap<>();

    
    // -------------------------------------------------------------------------
    // Public data
    // -------------------------------------------------------------------------
    
    /** The component content class. */
    public Class fcContentClass;
    
    /** true if the @EagerInit annotation is present. */
    public boolean eagerinit;

    /** The value associated with the scope policy, if any. */
    public String scope;
    
    /** true if the @ConversationAttributes annotation is present. */
    public boolean convattr;
    
    /** The maximun age of the conversation in milliseconds. */
    public long convMaxAge = 0;
    
    /** The maximun idle time of the conversation in milliseconds. */
    public long convMaxIdleTime = 0;
    
    /** The constructor annotated with @Constructor, if any. */
    public Constructor constructorAnnotatedElement;
    
    /**
     * The method annotated with @Provides, if any.
     * @since 1.4.2
     */
    public Method providesMethod;
    
    /** The method annotated with @Init, if any. */
    public Method initMethod;
    
    /** The method annotated with @Destroy, if any. */
    public Method destroyMethod;
    
    /** The method annotated with @Lifecycle(step=Step.START), if any. */
    public Method startMethod;
    
    /** The method annotated with @Lifecycle(step=Step.STOP), if any. */
    public Method stopMethod;
    
    /** The fields or setter methods annotated with @Controller. */
    public InjectionPoint[] controllerAnnotatedElements;
    
    /** The field or setter method annotated with @Context, if any. */
    public InjectionPoint contextAnnotatedElement;
    
    /** The field or setter method annotated with @ComponentName, if any. */
    public InjectionPoint componentNameAnnotatedElement;
    
    /** The field or setter method annotated with @ConversationID, if any. */
    public InjectionPoint conversationIDAnnotatedElement;
    
    /** List of references. */
    public CompositeInjectionPointHashMap refs;

    /** List of properties. */
    public CompositeInjectionPointHashMap props;
    
    /** The unannotated setter methods. */
    public Map setters;
    
    /** The fields or setter methods annotated with @Callback. */
    public InjectionPoint[] callbacks;

    
    // -------------------------------------------------------------------------
    // Constructor to initialize data
    // -------------------------------------------------------------------------
    
    /**
     * Retrieve the SCA related metadata for the specified component class name.
     * 
     * @throws IllegalContentClassMetaData
     *      if an error occurs while retrieving metadata 
     */
    private ContentClassMetaData( Class fcContentClass )
    throws IllegalContentClassMetaData {
        
        this.fcContentClass = fcContentClass;
        
        /*
         * Retrieve the scope policy of the content class.
         */
        Annotation scopeAnnot =
            ClassHelper.getAnnotation(
                fcContentClass,
                Scope.class.getName(),
                "org.osoa.sca.annotations.Scope");
        scope = AnnotationHelper.getAnnotationParamValue(scopeAnnot,"value");
        
        /*
         * Retrieve the scope eager init policy of the content class, if any.
         */
        eagerinit =
            ClassHelper.isAnnotationPresent(
                fcContentClass,
                EagerInit.class.getName(),
                "org.osoa.sca.annotations.EagerInit");
        if(eagerinit) {
            if( scope==null || !scope.equals("COMPOSITE") ) {
                final String msg =
                    "Class "+fcContentClass+
                    " annotated with @EagerInit should be composite-scoped";
                throw new IllegalContentClassMetaData(msg);
            }
        }

        /*
         * Retrieve the conversation attributes of the content class.
         */
        Annotation convAttrAnnot =
            ClassHelper.getAnnotation(
                fcContentClass, ConversationAttributes.class.getName() );
        convattr = convAttrAnnot != null;
        if(convattr) {
            String ma = AnnotationHelper.getAnnotationParamValue(convAttrAnnot,"maxAge");
            convMaxAge = convDurationToMilli(ma);
            String mit =
                AnnotationHelper.getAnnotationParamValue(convAttrAnnot,"maxIdleTime");
            convMaxIdleTime = convDurationToMilli(mit);
        }
        
        /*
         * Retrieve the constructor annotated with @Constructor, if any.
         * Check that only one such constructor exists.
         */
        Constructor[] constructors =
            fcContentClass.getDeclaredConstructors();
        Predicate filter =
            new AnnotatedElementFilter(
                org.oasisopen.sca.annotation.Constructor.class.getName(),
                "org.osoa.sca.annotations.Constructor");
        Constructor[] ctors = Filters.filter(constructors,filter);
        if( ctors.length > 1 ) {
            final String msg =
                "Only one constructor should be annotated with @Constructor";
            throw new IllegalContentClassMetaData(msg);
        }
        constructorAnnotatedElement = (ctors.length==1) ? ctors[0] : null;

        /*
         * Retrieve the method annotated with @Provides, if any.
         * Check that only one such method exists.
         */
        Method[] methods = ClassHelper.getAllMethods(fcContentClass);
        filter = new AnnotatedElementFilter(Provides.class.getName());
        Method[] provides = Filters.filter(methods,filter);
        provides = MethodHelper.removeOverridden(provides);
        providesMethod =
            checkUniqueAnnotatedFactoryMethod(
                provides, Provides.class.getName() );
        
        /*
         * Retrieve the method annotated with @Init, if any.
         * Check that only one such method exists.
         */
        filter =
            new CompositeOrFilter(
                new AnnotatedElementFilter(
                    Init.class.getName(),
                    "org.osoa.sca.annotations.Init"),
                new LifecycleAnnotatedElementFilter(Step.CREATE) );
        Method[] inits = Filters.filter(methods,filter);
        inits = MethodHelper.removeOverridden(inits);
        initMethod =
            checkUniqueAnnotatedVoidMethod(
                inits,
                Init.class.getName(),
                "org.osoa.sca.annotations.Init",
                Lifecycle.class.getName()+"(step=Step.CREATE)");

        /*
         * Retrieve the setter or field annotated with @Destroy, if any.
         * Check that only one such method exists.
         */
        filter =
            new CompositeOrFilter(
                new AnnotatedElementFilter(
                    Destroy.class.getName(),
                    "org.osoa.sca.annotations.Destroy"),
                new LifecycleAnnotatedElementFilter(Step.DESTROY) );
        Method[] destroys = Filters.filter(methods,filter);
        destroys = MethodHelper.removeOverridden(destroys);
        destroyMethod =
            checkUniqueAnnotatedVoidMethod(
                destroys,
                Destroy.class.getName(),
                "org.osoa.sca.annotations.Destroy",
                Lifecycle.class.getName()+"(step=Step.DESTROY)");

        /*
         * Retrieve the setter or field annotated with
         * @Lifecycle(step=Step.START), if any.
         * Check that only one such method exists.
         */
        filter = new LifecycleAnnotatedElementFilter(Step.START);
        Method[] starts = Filters.filter(methods,filter);
        starts = MethodHelper.removeOverridden(starts);
        startMethod =
            checkUniqueAnnotatedVoidMethod(
                starts,
                Lifecycle.class.getName()+"(step=Step.START)");

        /*
         * Retrieve the setter or field annotated with
         * @Lifecycle(step=Step.STOP), if any.
         * Check that only one such method exists.
         */
        filter = new LifecycleAnnotatedElementFilter(Step.STOP);
        Method[] stops = Filters.filter(methods,filter);
        stops = MethodHelper.removeOverridden(stops);
        stopMethod =
            checkUniqueAnnotatedVoidMethod(
                stops,
                Lifecycle.class.getName()+"(step=Step.STOP)");

        /*
         * Retrieve the setter or field annotated with @Controller, if any.
         * Check that only one such element exists.
         */
        AccessibleObject[] aos =
            ClassHelper.getAllAnnotatedSettersAndFields(
                fcContentClass, Controller.class.getName() );
        controllerAnnotatedElements = new InjectionPoint[ aos.length ];
        for (int i = 0; i < aos.length; i++) {
            Annotation annot =
                AnnotatedElementHelper.getAnnotation( aos[i], Controller.class.getName() );
            controllerAnnotatedElements[i] =
                InjectionPointImpl.getInjectionPoint(aos[i],annot);
        }

        /*
         * Retrieve the setter or field annotated with @Context, if any.
         * Check that only one such element exists.
         */
        contextAnnotatedElement =
            getSingletonAnnotatedFieldOrSetter(
                ComponentContext.class,
                Context.class.getName(),
                "org.osoa.sca.annotations.Context");
        
        /*
         * Retrieve the setter or field annotated with @ComponentName, if any.
         * Check that only one such element exists.
         */
        componentNameAnnotatedElement =
            getSingletonAnnotatedFieldOrSetter(
                String.class,
                ComponentName.class.getName(),
                "org.osoa.sca.annotations.ComponentName");

        /*
         * Retrieve the element annotated with @ConversationID, if any.
         * Check that only one such element exists.
         */
        conversationIDAnnotatedElement =
            getSingletonAnnotatedFieldOrSetter(
                Object.class, ConversationID.class.getName() );

        /*
         * Retrieve the elements annotated with @Reference.
         */
        refs =
            new CompositeInjectionPointHashMap(
                fcContentClass,
                Reference.class.getName(),
                "org.osoa.sca.annotations.Reference",
                Requires.class.getName());
        try {
            refs.putAll();
        }
        catch (DuplicationInjectionPointException dipe) {
            throw new IllegalContentClassMetaData(dipe);
        }
        
        /*
         * Retrieve the elements annotated with @Property.
         */
        props =
            new CompositeInjectionPointHashMap(
                fcContentClass,
                Property.class.getName(),
                "org.osoa.sca.annotations.Property",
                Attribute.class.getName());
        try {
            props.putAll();
        }
        catch (DuplicationInjectionPointException dipe) {
            throw new IllegalContentClassMetaData(dipe);
        }
        
        /*
         * Retrieve the unannotated setter methods.
         */
        setters = ClassHelper.getAllUnAnnotatedSetterMethods(fcContentClass);
        
        /*
         * Retrieve the callbacks declared in the content class.
         */
        aos =
            ClassHelper.getAllAnnotatedSettersAndFields(
                fcContentClass,
                Callback.class.getName(),
                "org.osoa.sca.annotations.Callback");
        callbacks = new InjectionPoint[ aos.length ];
        for (int i = 0; i < aos.length; i++) {
            Annotation annot =
                AnnotatedElementHelper.getAnnotation(
                    aos[i],
                    Callback.class.getName(),
                    "org.osoa.sca.annotations.Callback");
            callbacks[i] = InjectionPointImpl.getInjectionPoint(aos[i],annot);
        }
    }

    
    // -------------------------------------------------------------------------
    // Implementation specific
    // -------------------------------------------------------------------------

    /**
     * Return the singleton setter method or field defined in {@link
     * #fcContentClass} where the specified type can be injected.
     * 
     * @param type  the type to be injected
     * @param annotClassNames
     *         the potentional annotation type names associated with the setter
     *         method or field
     * @return  the injection point for the singleton element or
     *             null if the array is empty
     * @throws IllegalContentClassMetaData
     *             if the array is neither empty nor a singleton
     * @since 1.4.1
     */
    private InjectionPoint getSingletonAnnotatedFieldOrSetter(
        Class type, String... annotClassNames )
    throws IllegalContentClassMetaData {
        
        AccessibleObject[] aos =
            ClassHelper.getAllAnnotatedSettersAndFields(
                fcContentClass,annotClassNames);
        aos = AccessibleObjectHelper.removeOverridden(aos);

        if( aos.length == 0 ) {
            return null;
        }
        
        if( aos.length > 1 ) {
            String str = Arrays.deepToString(annotClassNames);
            String msg =
                "More than one element annotated with @"+str+" in "+
                fcContentClass.getName();
            throw new IllegalContentClassMetaData(msg);
        }
        
        AccessibleObject ao = aos[0];
        
        Annotation[] annots = ao.getAnnotations();
        for (String annotClassName : annotClassNames) {
            for (Annotation annot : annots) {
                String name = annot.annotationType().getName();
                if( name.equals(annotClassName) ) {
                    InjectionPoint ip =
                        InjectionPointImpl.getInjectionPoint(ao,annot);
                    Class iptype = ip.getType();
                    if( type.isAssignableFrom(iptype) ) {
                        return ip;
                    }
                }
            }
        }
        
        final String msg =
            ao.toString()+" should be injectable with "+type.getName();
        throw new IllegalContentClassMetaData(msg);
    }

    /**
     * Check that the specified array contains at most one void returning, no
     * argument method. Methods in the array are expected to be annotated with
     * one of the specified annotation class names.
     * 
     * @param methods  the methods
     * @param names    the annotation class names
     * @return  the void returning, no argument method contained in
     *             methods or null if the array is empty
     * @throws IllegalContentClassMetaData
     *             if the array is not empty and the method does not fit the
     *             conditions
     */
    private Method checkUniqueAnnotatedVoidMethod(
        Method[] methods, String... names )
    throws IllegalContentClassMetaData {
        
        Method method = checkUnique(methods,names);
        if( method == null ) {
            return null;
        }
        
        if( ! method.getReturnType().equals(void.class) ||
            method.getParameterTypes().length != 0 ) {
            String str = Arrays.deepToString(names);
            final String msg =
                "The signature of the @"+str+" annotated method ("+
                method.getName()+") should be ():void";
            throw new IllegalContentClassMetaData(msg);
        }
        
        return method;
    }
    
    /**
     * Check that the specified array contains at most one no argument, factory
     * method for the current content class. Methods in the array are expected
     * to be annotated with one of the specified annotation class names.
     * 
     * @param methods  the methods
     * @param names    the annotation class names
     * @return  the no argument, factory method for the current content class
     *             contained in methods or null if the
     *             array is empty
     * @throws IllegalContentClassMetaData
     *             if the array is not empty and the method does not fit the
     *             conditions
     * @since 1.4.2
     */
    private Method checkUniqueAnnotatedFactoryMethod(
        Method[] methods, String... names )
    throws IllegalContentClassMetaData {
        
        Method method = checkUnique(methods,names);
        if( method == null ) {
            return null;
        }
        
        Class rtype = method.getReturnType();        
        if( ! fcContentClass.isAssignableFrom(rtype) ||
            method.getParameterTypes().length != 0 ) {
            String str = Arrays.deepToString(names);
            final String msg =
                "The signature of the @"+str+" annotated method ("+
                method.getName()+") should be ():"+fcContentClass.getName();
            throw new IllegalContentClassMetaData(msg);
        }
        
        return method;
    }
    
    /**
     * Check that the specified array contains at most one method. Methods in
     * the array are expected to be annotated with one of the specified
     * annotation class names.
     * 
     * @param methods  the methods
     * @param names    the annotation class names
     * @return  the void returning, no argument method contained in
     *             methods or null if the array is empty
     * @throws IllegalContentClassMetaData   if the array is not empty
     */
    private Method checkUnique( Method[] methods, String... names )
    throws IllegalContentClassMetaData {
        
        if( methods.length == 0 ) {
            return null;
        }
        
        if( methods.length > 1 ) {
            String str = Arrays.deepToString(names);
            final String msg =
                "More than one method annotated with @"+str+" in "+
                fcContentClass.getName();
            throw new IllegalContentClassMetaData(msg);
        }        
        
        // methods.length == 1
        Method method = methods[0];
        return method;
    }
    
    /**
     * Return the duration in milliseconds corresponding to the specified
     * string. The format of the string is defined in the SCA specifications.
     * 
     * @throws IllegalContentClassMetaData  if the string is badly formatted
     */
    private static long convDurationToMilli( String duration )
    throws IllegalContentClassMetaData {
        
        int space = duration.indexOf(' ');
        if( space == -1 ) {
            final String msg = "No space in duration string: "+duration;
            throw new IllegalContentClassMetaData(msg);
        }
        
        String numberStr = duration.substring(0,space);
        int number = Integer.parseInt(numberStr);
        if( number < 0 ) {
            final String msg = "Negative duration: "+duration;
            throw new IllegalContentClassMetaData(msg);
        }
        
        if( space+1 == duration.length() ) {
            final String msg = "No time unit in: "+duration;
            throw new IllegalContentClassMetaData(msg);
        }
        
        String unit = duration.substring(space+1);
        long l = number;
        if( unit.equals("seconds") )      { l *= 1000; }
        else if( unit.equals("minutes") ) { l *= 60*1000; }
        else if( unit.equals("hours") )   { l *= 60*60*1000; }
        else if( unit.equals("days") )    { l *= 24*60*60*1000; }
        else if( unit.equals("years") )   { l *= 365*24*60*60*1000; }
        else {
            final String msg = "Unsupported time unit in: "+duration;
            throw new IllegalContentClassMetaData(msg);
        }
        
        return l;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy