
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