blob: 4033a75455d5b7cae0b568180312690446a5c6c1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.felix.dm.annotation.plugin.bnd;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import org.apache.felix.dm.annotation.api.AdapterService;
import org.apache.felix.dm.annotation.api.AspectService;
import org.apache.felix.dm.annotation.api.BundleAdapterService;
import org.apache.felix.dm.annotation.api.BundleDependency;
import org.apache.felix.dm.annotation.api.Component;
import org.apache.felix.dm.annotation.api.Composition;
import org.apache.felix.dm.annotation.api.ConfigurationDependency;
import org.apache.felix.dm.annotation.api.Destroy;
import org.apache.felix.dm.annotation.api.FactoryConfigurationAdapterService;
import org.apache.felix.dm.annotation.api.Init;
import org.apache.felix.dm.annotation.api.Inject;
import org.apache.felix.dm.annotation.api.LifecycleController;
import org.apache.felix.dm.annotation.api.Property;
import org.apache.felix.dm.annotation.api.Registered;
import org.apache.felix.dm.annotation.api.RepeatableProperty;
import org.apache.felix.dm.annotation.api.ResourceAdapterService;
import org.apache.felix.dm.annotation.api.ResourceDependency;
import org.apache.felix.dm.annotation.api.ServiceDependency;
import org.apache.felix.dm.annotation.api.Start;
import org.apache.felix.dm.annotation.api.Stop;
import org.apache.felix.dm.annotation.api.Unregistered;
import org.osgi.framework.Bundle;
import aQute.bnd.osgi.Annotation;
import aQute.bnd.osgi.ClassDataCollector;
import aQute.bnd.osgi.Clazz;
import aQute.bnd.osgi.Descriptors.TypeRef;
import aQute.bnd.osgi.Verifier;
/**
* This is the scanner which does all the annotation parsing on a given class.
* To start the parsing, just invoke the parseClassFileWithCollector and finish methods.
* Once parsed, the corresponding component descriptors can be built using the "writeTo" method.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class AnnotationCollector extends ClassDataCollector
{
private final static String A_INIT = Init.class.getName();
private final static String A_START = Start.class.getName();
private final static String A_STOP = Stop.class.getName();
private final static String A_DESTROY = Destroy.class.getName();
private final static String A_COMPOSITION = Composition.class.getName();
private final static String A_LIFCLE_CTRL = LifecycleController.class.getName();
private final static String A_COMPONENT = Component.class.getName();
private final static String A_PROPERTY = Property.class.getName();
private final static String A_REPEATABLE_PROPERTY = RepeatableProperty.class.getName();
private final static String A_SERVICE_DEP = ServiceDependency.class.getName();
private final static String A_CONFIGURATION_DEPENDENCY = ConfigurationDependency.class.getName();
private final static String A_BUNDLE_DEPENDENCY = BundleDependency.class.getName();
private final static String A_RESOURCE_DEPENDENCY = ResourceDependency.class.getName();
private final static String A_ASPECT_SERVICE = AspectService.class.getName();
private final static String A_ADAPTER_SERVICE = AdapterService.class.getName();
private final static String A_BUNDLE_ADAPTER_SERVICE = BundleAdapterService.class.getName();
private final static String A_RESOURCE_ADAPTER_SERVICE = ResourceAdapterService.class.getName();
private final static String A_FACTORYCONFIG_ADAPTER_SERVICE = FactoryConfigurationAdapterService.class.getName();
private final static String A_INJECT = Inject.class.getName();
private final static String A_REGISTERED = Registered.class.getName();
private final static String A_UNREGISTERED = Unregistered.class.getName();
private Logger m_logger;
private String[] m_interfaces;
private boolean m_isField;
private String m_field;
private String m_method;
private String m_descriptor;
private final Set<String> m_dependencyNames = new HashSet<String>();
private final List<EntryWriter> m_writers = new ArrayList<EntryWriter>();
private MetaType m_metaType;
private String m_startMethod;
private String m_stopMethod;
private String m_initMethod;
private String m_destroyMethod;
private String m_compositionMethod;
private String m_starter;
private String m_stopper;
private final Set<String> m_importService = new HashSet<String>();
private final Set<String> m_exportService = new HashSet<String>();
private String m_bundleContextField;
private String m_dependencyManagerField;
private String m_registrationField;
private String m_componentField;
private String m_registeredMethod;
private String m_unregisteredMethod;
private TypeRef m_superClass;
private boolean m_baseClass = true;
/**
* Name of the class annotated with @Component (or other kind of components, like aspect, adapters).
*/
private String m_componentClassName;
/*
* Name of class currently being parsed (the component class name at first, then the inherited classes).
* See DescriptorGenerator class, which first calls parseClassFileWithCollector method with the component class, then it calls
* again the parseClassFileWithCollector method with all inherited component super classes.
*/
private String m_currentClassName;
/**
* Contains all bind methods annotated with a dependency.
* Each entry has the format: "methodName/method signature".
*/
private final Set<String> m_bindMethods = new HashSet<>();
/**
* When more than one @Property annotation are declared on a component type (outside of the @Component annotation), then a @Repeatable
* annotation is used as the container for the @Property annotations. When such annotation is found, it is stored in this attribute, which
* will be parsed in our finish() method.
*/
private Annotation m_repeatableProperty;
/**
* If a Single @Property is declared on the component type (outside of the @Component annotation), then there is no @Repeatable annotation.
* When such single @Property annotation is found, it is stored in this attribute, which will be parsed in our finish() method.
*/
private Annotation m_singleProperty;
/**
* List of all possible DM components.
*/
private final List<EntryType> m_componentTypes = Arrays.asList(EntryType.Component, EntryType.AspectService, EntryType.AdapterService,
EntryType.BundleAdapterService, EntryType.ResourceAdapterService, EntryType.FactoryConfigurationAdapterService);
/**
* Makes a new Collector for parsing a given class.
* @param reporter the object used to report logs.
*/
public AnnotationCollector(Logger reporter, MetaType metaType)
{
m_logger = reporter;
m_metaType = metaType;
}
/**
* Indicates that we are parsing a superclass of a given component class.
*/
public void baseClass(boolean baseClass) {
m_logger.debug("baseClass:%b", baseClass);
m_baseClass = baseClass;
}
/**
* Parses the name of the class.
* @param access the class access
* @param name the class name (package are "/" separated).
*/
@Override
public void classBegin(int access, TypeRef name)
{
if (m_baseClass)
{
m_componentClassName = name.getFQN();
m_logger.debug("Parsing class: %s", m_componentClassName);
}
m_currentClassName = name.getFQN();
}
/**
* Parses the implemented interfaces ("/" separated).
*/
@Override
public void implementsInterfaces(TypeRef[] interfaces)
{
if (m_baseClass)
{
List<String> result = new ArrayList<String>();
for (int i = 0; i < interfaces.length; i++)
{
if (!interfaces[i].getBinary().equals("scala/ScalaObject"))
{
result.add(interfaces[i].getFQN());
}
}
m_interfaces = result.toArray(new String[result.size()]);
m_logger.debug("implements: %s", Arrays.toString(m_interfaces));
}
}
/**
* Parses a method. Always invoked BEFORE eventual method annotation.
*/
@Override
public void method(Clazz.MethodDef method)
{
m_logger.debug("Parsed method %s, descriptor=%s", method.getName(), method.getDescriptor());
m_isField = false;
m_method = method.getName();
m_descriptor = method.getDescriptor().toString();
}
/**
* Parses a field. Always invoked BEFORE eventual field annotation
*/
@Override
public void field(Clazz.FieldDef field)
{
m_logger.debug("Parsed field %s, descriptor=%s", field.getName(), field.getDescriptor());
m_isField = true;
m_field = field.getName();
m_descriptor = field.getDescriptor().toString();
}
@Override
public void extendsClass(TypeRef name) {
m_superClass = name;
}
public TypeRef getSuperClass() {
return m_superClass;
}
/**
* An annotation has been parsed. Always invoked AFTER the "method"/"field"/"classBegin" callbacks.
*/
@Override
public void annotation(Annotation annotation)
{
m_logger.debug("Parsing annotation: %s", annotation.getName());
// if we are parsing a superclass of a given component, then ignore any component annotations.
String name = annotation.getName().getFQN();
if (! m_baseClass) {
String simpleName = name.indexOf(".") != -1 ? name.substring(name.lastIndexOf(".")+1) : name;
Optional<EntryType> type = m_componentTypes.stream().filter(writer -> writer.name().equals(simpleName)).findFirst();
if (type.isPresent()) {
m_logger.debug("Ignoring annotation %s from super class %s of component class %s", name, m_currentClassName, m_componentClassName);
return;
}
}
if (name.equals(A_COMPONENT))
{
parseComponentAnnotation(annotation);
}
else if (name.equals(A_ASPECT_SERVICE))
{
parseAspectService(annotation);
}
else if (name.equals(A_ADAPTER_SERVICE))
{
parseAdapterService(annotation);
}
else if (name.equals(A_BUNDLE_ADAPTER_SERVICE))
{
parseBundleAdapterService(annotation);
}
else if (name.equals(A_RESOURCE_ADAPTER_SERVICE))
{
parseResourceAdapterService(annotation);
}
else if (name.equals(A_FACTORYCONFIG_ADAPTER_SERVICE))
{
parseFactoryConfigurationAdapterService(annotation);
}
else if (name.equals(A_INIT))
{
checkAlreadyDeclaredSingleAnnot(() -> m_initMethod, "@Init");
m_initMethod = m_method;
}
else if (name.equals(A_START))
{
checkAlreadyDeclaredSingleAnnot(() -> m_startMethod, "@Start");
m_startMethod = m_method;
}
else if (name.equals(A_REGISTERED))
{
checkAlreadyDeclaredSingleAnnot(() -> m_registeredMethod, "@Registered");
m_registeredMethod = m_method;
}
else if (name.equals(A_STOP))
{
checkAlreadyDeclaredSingleAnnot(() -> m_stopMethod, "@Stop");
m_stopMethod = m_method;
}
else if (name.equals(A_UNREGISTERED))
{
checkAlreadyDeclaredSingleAnnot(() -> m_unregisteredMethod, "@Unregistered");
m_unregisteredMethod = m_method;
}
else if (name.equals(A_DESTROY))
{
checkAlreadyDeclaredSingleAnnot(() -> m_destroyMethod, "@Destroy");
m_destroyMethod = m_method;
}
else if (name.equals(A_COMPOSITION))
{
checkAlreadyDeclaredSingleAnnot(() -> m_compositionMethod, "@Composition");
Patterns.parseMethod(m_method, m_descriptor, Patterns.COMPOSITION);
m_compositionMethod = m_method;
}
else if (name.equals(A_LIFCLE_CTRL))
{
parseLifecycleAnnotation(annotation);
}
else if (name.equals(A_SERVICE_DEP))
{
parseServiceDependencyAnnotation(annotation);
}
else if (name.equals(A_CONFIGURATION_DEPENDENCY))
{
parseConfigurationDependencyAnnotation(annotation);
}
else if (name.equals(A_BUNDLE_DEPENDENCY))
{
parseBundleDependencyAnnotation(annotation);
}
else if (name.equals(A_RESOURCE_DEPENDENCY))
{
parseRersourceDependencyAnnotation(annotation);
}
else if (name.equals(A_INJECT))
{
parseInject(annotation);
}
else if (name.equals(A_REPEATABLE_PROPERTY))
{
parseRepeatableProperties(annotation);
}
else if (annotation.getName().getFQN().equals(A_PROPERTY))
{
m_singleProperty = annotation;
}
}
/**
* Finishes up the class parsing. This method must be called once the parseClassFileWithCollector method has returned.
* @return true if some annotations have been parsed, false if not.
*/
public boolean finish()
{
m_logger.info("finish %s", m_componentClassName);
// check if we have a component (or adapter) annotation.
Optional<EntryWriter> componentWriter = m_writers.stream()
.filter(writer -> m_componentTypes.indexOf(writer.getEntryType()) != -1)
.findFirst();
if (! componentWriter.isPresent() || m_writers.size() == 0) {
m_logger.info("No components found for class %s", m_componentClassName);
return false;
}
finishComponentAnnotation(componentWriter.get());
// log all meta data for component annotations, dependencies, etc ...
StringBuilder sb = new StringBuilder();
sb.append("Parsed annotation for class ");
sb.append(m_componentClassName);
for (int i = m_writers.size() - 1; i >= 0; i--)
{
sb.append("\n\t").append(m_writers.get(i).toString());
}
m_logger.info(sb.toString());
return true;
}
private void finishComponentAnnotation(EntryWriter componentWriter) {
// Register previously parsed Init/Start/Stop/Destroy/Composition annotations
addCommonServiceParams(componentWriter);
// Add any repeated @Property annotations to the component (or to the aspect, or adapter).
if (m_repeatableProperty != null)
{
Object[] properties = m_repeatableProperty.get("value");
for (Object property : properties)
{
// property is actually a @Property annotation.
parseProperty((Annotation) property, componentWriter);
}
}
// Handle a single Property declared on the component type (in this case, there is no @Repeatable annotation).
if (m_singleProperty != null) {
parseProperty(m_singleProperty, componentWriter);
}
}
/**
* Writes the generated component descriptor in the given print writer.
* The first line must be the component descriptor (@Component or AspectService, etc ..).
* @param pw the writer where the component descriptor will be written.
*/
public void writeTo(PrintWriter pw)
{
// write first the component descriptor (@Component, @AspectService, ...)
EntryWriter componentWriter = m_writers.stream()
.filter(writer -> m_componentTypes.indexOf(writer.getEntryType()) != -1)
.findFirst()
.orElseThrow(() -> new IllegalStateException("Component type not found while scanning class " + m_componentClassName));
pw.println(componentWriter);
// and write other component descriptors (dependencies, and other annotations)
m_writers.stream()
.filter(writer -> m_componentTypes.indexOf(writer.getEntryType()) == -1)
.forEach(dependency -> pw.println(dependency.toString()));
}
/**
* Returns list of all imported services. Imported services are deduced from every
* @ServiceDependency annotations.
* @return the list of imported services, or null
*/
public Set<String> getImportService()
{
return m_importService;
}
/**
* Returns list of all exported services. Imported services are deduced from every
* annotations which provides a service (@Component, etc ...)
* @return the list of exported services, or null
*/
public Set<String> getExportService()
{
return m_exportService;
}
/**
* Parses a Property annotation that is applied on the component class.
* @param property the Property annotation.
*/
private void parseRepeatableProperties(Annotation repeatedProperties)
{
m_repeatableProperty = repeatedProperties;
}
/**
* Checks double declaration of an annotation, which normally must be declared only one time.
* @param field the field supplier (if not null, means the annotation is already declared)
* @param annot the annotation name.
* @throws IllegalStateException if annotation is already declared.
*/
private void checkAlreadyDeclaredSingleAnnot(Supplier<Object> field, String annot) {
if (field.get() != null) {
throw new IllegalStateException("detected multiple " + annot + " annotation from class " + m_currentClassName + " (on from child classes)");
}
}
private void parseComponentAnnotation(Annotation annotation)
{
EntryWriter writer = new EntryWriter(EntryType.Component);
m_writers.add(writer);
// impl attribute
writer.put(EntryParam.impl, m_componentClassName);
// Parse Adapter properties, and other params common to all kind of component
parseCommonComponentAttributes(annotation, writer);
// provides attribute.
if (writer.putClassArray(annotation, EntryParam.provides, m_interfaces, m_exportService) == 0)
{
// no service provided: check if @Registered/@Unregistered annotation are used
// and raise an error if true.
checkRegisteredUnregisteredNotPresent();
}
// factorySet attribute (deprecated, replaced by factoryName)
String factorySetName = writer.putString(annotation, EntryParam.factorySet, null);
if (factorySetName != null)
{
// When a component defines a factorySet, it means that a java.util.Set will
// be provided into the OSGi registry, in order to let anoter component add
// some component instance configurations into it.
// So, we have to indicate that the Set is provided as a service, in the Export-Serviec
// header.
m_exportService.add("java.util.Set");
}
// factoryName attribute
String factoryName = writer.putString(annotation, EntryParam.factoryName, null);
if (factoryName != null)
{
// When a component defines a factoryName, it means that a ComponentFactory will
// be provided into the OSGi registry, in order to let another component create some component instances.
// So, we have to indicate that the ComponentFactory is provided as a service, in the Export-Serviec
// header.
m_exportService.add("org.apache.felix.dependencymanager.runtime.api.ComponentFactory");
}
// factoryConfigure attribute
writer.putString(annotation, EntryParam.factoryConfigure, null);
// factoryMethod attribute
writer.putString(annotation, EntryParam.factoryMethod, null);
}
private void addCommonServiceParams(EntryWriter writer)
{
if (m_initMethod != null)
{
writer.put(EntryParam.init, m_initMethod);
}
if (m_startMethod != null)
{
writer.put(EntryParam.start, m_startMethod);
}
if (m_registeredMethod != null)
{
writer.put(EntryParam.registered, m_registeredMethod);
}
if (m_stopMethod != null)
{
writer.put(EntryParam.stop, m_stopMethod);
}
if (m_unregisteredMethod != null)
{
writer.put(EntryParam.unregistered, m_unregisteredMethod);
}
if (m_destroyMethod != null)
{
writer.put(EntryParam.destroy, m_destroyMethod);
}
if (m_compositionMethod != null)
{
writer.put(EntryParam.composition, m_compositionMethod);
}
if (m_starter != null)
{
writer.put(EntryParam.starter, m_starter);
}
if (m_stopper != null)
{
writer.put(EntryParam.stopper, m_stopper);
if (m_starter == null)
{
throw new IllegalArgumentException("Can't use a @LifecycleController annotation for stopping a service without declaring a " +
"@LifecycleController that starts the component in class " + m_currentClassName);
}
}
if (m_bundleContextField != null)
{
writer.put(EntryParam.bundleContextField, m_bundleContextField);
}
if (m_dependencyManagerField != null)
{
writer.put(EntryParam.dependencyManagerField, m_dependencyManagerField);
}
if (m_componentField != null)
{
writer.put(EntryParam.componentField, m_componentField);
}
if (m_registrationField != null)
{
writer.put(EntryParam.registrationField, m_registrationField);
}
}
/**
* Check if a dependency is already declared in another same bindMethod (or class field) on another child class.
*/
private void checkDependencyAlreadyDeclaredInChild(Annotation annotation, String methodOrField, boolean method) {
if (! m_baseClass && m_bindMethods.contains(methodOrField + "/" + m_descriptor)) {
throw new IllegalStateException("Annotation " + annotation.getName().getShortName()
+ " declared on " + m_currentClassName + "." + methodOrField + (method ? " method" : " field") + " is already declared in child classe(s)");
}
m_bindMethods.add(methodOrField + "/" + m_descriptor);
}
/**
* Parses a ServiceDependency Annotation.
* @param annotation the ServiceDependency Annotation.
*/
private void parseServiceDependencyAnnotation(Annotation annotation)
{
EntryWriter writer = new EntryWriter(EntryType.ServiceDependency);
m_writers.add(writer);
// service attribute
String service = parseClassAttrValue(annotation.get(EntryParam.service.toString()));
if (service == null)
{
if (m_isField)
{
checkDependencyAlreadyDeclaredInChild(annotation, m_field, false);
service = Patterns.parseClass(m_descriptor, Patterns.CLASS, 1);
}
else
{
// if we are parsing some inherited classes, detect if the bind method is already declared in child classes
checkDependencyAlreadyDeclaredInChild(annotation, m_method, true);
// parse "bind(Component, ServiceReference, Service)" signature
service = Patterns.parseClass(m_descriptor, Patterns.BIND_CLASS1, 3, false);
if (service == null) {
// parse "bind(Component, Service)" signature
service = Patterns.parseClass(m_descriptor, Patterns.BIND_CLASS2, 2, false);
}
if (service == null) {
// parse "bind(Component, Map, Service)" signature
service = Patterns.parseClass(m_descriptor, Patterns.BIND_CLASS3, 3, false);
}
if (service == null) {
// parse "bind(ServiceReference, Service)" signature
service = Patterns.parseClass(m_descriptor, Patterns.BIND_CLASS4, 2, false);
}
if (service == null) {
// parse "bind(Service)" signature
service = Patterns.parseClass(m_descriptor, Patterns.BIND_CLASS5, 1, false);
}
if (service == null) {
// parse "bind(Service, Map)" signature
service = Patterns.parseClass(m_descriptor, Patterns.BIND_CLASS6, 1, false);
}
if (service == null) {
// parse "bind(Map, Service)" signature
service = Patterns.parseClass(m_descriptor, Patterns.BIND_CLASS7, 2, false);
}
if (service == null) {
// parse "bind(Service, Dictionary)" signature
service = Patterns.parseClass(m_descriptor, Patterns.BIND_CLASS8, 1, false);
}
if (service == null) {
// parse "bind(Dictionary, Service)" signature
service = Patterns.parseClass(m_descriptor, Patterns.BIND_CLASS9, 2, true);
}
}
}
boolean matchAnyService = service != null && service.equals(ServiceDependency.Any.class.getName());
if (matchAnyService) {
service = null;
}
writer.put(EntryParam.service, service);
// Store this service in list of imported services.
m_importService.add(service);
// autoConfig attribute
if (m_isField)
{
writer.put(EntryParam.autoConfig, m_field);
}
// filter attribute
String filter = annotation.get(EntryParam.filter.toString());
if (filter != null)
{
Verifier.verifyFilter(filter, 0);
if (matchAnyService) {
filter = "(&(objectClass=*)" + filter + ")";
}
writer.put(EntryParam.filter, filter);
} else if (matchAnyService) {
filter = "(objectClass=*)";
writer.put(EntryParam.filter, filter);
}
// defaultImpl attribute
writer.putClass(annotation, EntryParam.defaultImpl);
// added callback
writer.putString(annotation, EntryParam.added, (!m_isField) ? m_method : null);
// timeout parameter
writer.putString(annotation, EntryParam.timeout, null);
Long t = (Long) annotation.get(EntryParam.timeout.toString());
if (t != null && t.longValue() < -1)
{
throw new IllegalArgumentException("Invalid timeout value " + t + " in ServiceDependency annotation from class " + m_currentClassName);
}
// required attribute (not valid if parsing a temporal service dependency)
writer.putString(annotation, EntryParam.required, null);
// changed callback
writer.putString(annotation, EntryParam.changed, null);
// removed callback
writer.putString(annotation, EntryParam.removed, null);
// swap callback
writer.putString(annotation, EntryParam.swap, null);
// name attribute
parseDependencyName(writer, annotation);
// propagate attribute
writer.putString(annotation, EntryParam.propagate, null);
// dereference flag
writer.putString(annotation, EntryParam.dereference, null);
}
/**
* Parse the value of a given annotation attribute (which is of type 'class').
* This method is compatible with bndtools 2.4.1 (where the annotation.get() method returns a String of the form "Lfull/class/name;"),
* and with bndtools 3.x.x (where the annotation.get() method returns a TypeRef).
*
* @param annot the annotation which contains the given attribute
* @param attr the attribute name (of 'class' type).
* @return the annotation class attribute value
*/
public static String parseClassAttrValue(Object value) {
if (value instanceof String)
{
return Patterns.parseClass((String) value, Patterns.CLASS, 1);
}
else if (value instanceof TypeRef)
{
return ((TypeRef) value).getFQN();
}
else if (value == null) {
return null;
}
else {
throw new IllegalStateException("can't parse class attribute value from " + value);
}
}
/**
* Parses a ConfigurationDependency annotation.
* @param annotation the ConfigurationDependency annotation.
*/
private void parseConfigurationDependencyAnnotation(Annotation annotation)
{
checkDependencyAlreadyDeclaredInChild(annotation, m_method, true);
EntryWriter writer = new EntryWriter(EntryType.ConfigurationDependency);
m_writers.add(writer);
// The pid is either:
//
// - the fqdn of the configuration proxy type, if the callback accepts an interface (not a Dictionary).
// - or the fqdn of the class specified by the pidFromClass attribute
// - or the value of the pid attribute
// - or by default the fdqn of the class where the annotation is found
String pidFromClass = parseClassAttrValue(annotation.get(EntryParam.pidClass.toString()));
String pid = pidFromClass != null ? pidFromClass : get(annotation, EntryParam.pid.toString(), null);
// Check if annotation is applied on "updated(ConfigProxyType)"
String confProxyType = Patterns.parseClass(m_descriptor, Patterns.BIND_CLASS5, 1, false);
if (confProxyType != null)
{
if (! Dictionary.class.getName().equals(confProxyType))
{
// It's a conf proxy type.
writer.put(EntryParam.configType, confProxyType);
}
else
{
confProxyType = null;
}
}
else
{
// Check if annotation is applied on "updated(Component, ConfigProxyType)"
confProxyType = Patterns.parseClass(m_descriptor, Patterns.BIND_CLASS2, 2, false);
if (! Dictionary.class.getName().equals(confProxyType))
{
// It's a conf proxy type.
writer.put(EntryParam.configType, confProxyType);
}
else
{
confProxyType = null;
}
}
if (pid == null)
{
if (confProxyType != null)
{
pid = confProxyType;
}
else
{
pid = m_componentClassName;
}
}
writer.put(EntryParam.pid, pid);
// the method on which the annotation is applied
writer.put(EntryParam.updated, m_method);
// propagate attribute
writer.putString(annotation, EntryParam.propagate, null);
// required flag (true by default)
writer.putString(annotation, EntryParam.required, Boolean.TRUE.toString());
// Property Meta Types
parseMetaTypes(annotation, pid, false);
// name attribute
parseDependencyName(writer, annotation);
}
/**
* Parses an AspectService annotation.
* @param annotation
*/
private void parseAspectService(Annotation annotation)
{
EntryWriter writer = new EntryWriter(EntryType.AspectService);
m_writers.add(writer);
// Parse service filter
String filter = annotation.get(EntryParam.filter.toString());
if (filter != null)
{
Verifier.verifyFilter(filter, 0);
writer.put(EntryParam.filter, filter);
}
// Parse service aspect ranking
Integer ranking = annotation.get(EntryParam.ranking.toString());
writer.put(EntryParam.ranking, ranking.toString());
// Generate Aspect Implementation
writer.put(EntryParam.impl, m_componentClassName);
// Parse Adapter properties, and other params common to all kind of component
parseCommonComponentAttributes(annotation, writer);
// Parse field/added/changed/removed attributes
parseAspectOrAdapterCallbackMethods(annotation, writer);
// Parse service interface this aspect is applying to
Object service = annotation.get(EntryParam.service.toString());
if (service == null)
{
if (m_interfaces == null)
{
throw new IllegalStateException("Invalid AspectService annotation: " +
"the service attribute has not been set and the class " + m_componentClassName
+ " does not implement any interfaces");
}
if (m_interfaces.length != 1)
{
throw new IllegalStateException("Invalid AspectService annotation: " +
"the service attribute has not been set and the class " + m_componentClassName
+ " implements more than one interface");
}
writer.put(EntryParam.service, m_interfaces[0]);
}
else
{
writer.putClass(annotation, EntryParam.service);
}
// Parse factoryMethod attribute
writer.putString(annotation, EntryParam.factoryMethod, null);
}
private void parseAspectOrAdapterCallbackMethods(Annotation annotation, EntryWriter writer)
{
String field = annotation.get(EntryParam.field.toString());
String added = annotation.get(EntryParam.added.toString());
String changed = annotation.get(EntryParam.changed.toString());
String removed = annotation.get(EntryParam.removed.toString());
String swap = annotation.get(EntryParam.swap.toString());
// "field" and "added/changed/removed/swap" attributes can't be mixed
if (field != null && (added != null || changed != null || removed != null || swap != null))
{
throw new IllegalStateException("Annotation " + annotation + "can't applied on " + m_componentClassName
+ " can't mix \"field\" attribute with \"added/changed/removed\" attributes");
}
// Parse aspect impl field where to inject the original service.
writer.putString(annotation, EntryParam.field, null);
// Parse aspect impl callback methods.
writer.putString(annotation, EntryParam.added, null);
writer.putString(annotation, EntryParam.changed, null);
writer.putString(annotation, EntryParam.removed, null);
writer.putString(annotation, EntryParam.swap, null);
}
/**
* Parses an AspectService annotation.
* @param annotation
*/
private void parseAdapterService(Annotation annotation)
{
EntryWriter writer = new EntryWriter(EntryType.AdapterService);
m_writers.add(writer);
// Generate Adapter Implementation
writer.put(EntryParam.impl, m_componentClassName);
// Parse adaptee filter
String adapteeFilter = annotation.get(EntryParam.adapteeFilter.toString());
if (adapteeFilter != null)
{
Verifier.verifyFilter(adapteeFilter, 0);
writer.put(EntryParam.adapteeFilter, adapteeFilter);
}
// Parse the mandatory adapted service interface.
writer.putClass(annotation, EntryParam.adapteeService);
// Parse Adapter properties, and other params common to all kind of component
parseCommonComponentAttributes(annotation, writer);
// Parse the provided adapter service (use directly implemented interface by default).
if (writer.putClassArray(annotation, EntryParam.provides, m_interfaces, m_exportService) == 0)
{
checkRegisteredUnregisteredNotPresent();
}
// Parse factoryMethod attribute
writer.putString(annotation, EntryParam.factoryMethod, null);
// Parse propagate flag.
// Parse factoryMethod attribute
writer.putString(annotation, EntryParam.propagate, null);
// Parse field/added/changed/removed attributes
parseAspectOrAdapterCallbackMethods(annotation, writer);
}
/**
* Parses a BundleAdapterService annotation.
* @param annotation
*/
private void parseBundleAdapterService(Annotation annotation)
{
EntryWriter writer = new EntryWriter(EntryType.BundleAdapterService);
m_writers.add(writer);
// Generate Adapter Implementation
writer.put(EntryParam.impl, m_componentClassName);
// Parse bundle filter
String filter = annotation.get(EntryParam.filter.toString());
if (filter != null)
{
Verifier.verifyFilter(filter, 0);
writer.put(EntryParam.filter, filter);
}
// Parse stateMask attribute
writer.putString(annotation, EntryParam.stateMask, Integer.valueOf(
Bundle.INSTALLED | Bundle.RESOLVED | Bundle.ACTIVE).toString());
// Parse Adapter properties, and other params common to all kind of component
parseCommonComponentAttributes(annotation, writer);
// Parse the optional adapter service (use directly implemented interface by default).
if (writer.putClassArray(annotation, EntryParam.provides, m_interfaces, m_exportService) == 0)
{
checkRegisteredUnregisteredNotPresent();
}
// Parse propagate attribute
writer.putString(annotation, EntryParam.propagate, Boolean.FALSE.toString());
// Parse factoryMethod attribute
writer.putString(annotation, EntryParam.factoryMethod, null);
}
/**
* Parses a BundleAdapterService annotation.
* @param annotation
*/
private void parseResourceAdapterService(Annotation annotation)
{
EntryWriter writer = new EntryWriter(EntryType.ResourceAdapterService);
m_writers.add(writer);
// Generate Adapter Implementation
writer.put(EntryParam.impl, m_componentClassName);
// Parse resource filter
String filter = annotation.get(EntryParam.filter.toString());
if (filter != null)
{
Verifier.verifyFilter(filter, 0);
writer.put(EntryParam.filter, filter);
}
// Parse Adapter properties, and other params common to all kind of component
parseCommonComponentAttributes(annotation, writer);
// Parse the provided adapter service (use directly implemented interface by default).
if (writer.putClassArray(annotation, EntryParam.provides, m_interfaces, m_exportService) == 0)
{
checkRegisteredUnregisteredNotPresent();
}
// Parse propagate attribute
writer.putString(annotation, EntryParam.propagate, Boolean.FALSE.toString());
// Parse changed attribute
writer.putString(annotation, EntryParam.changed, null);
}
/**
* Parses a Factory Configuration Adapter annotation.
* @param annotation
*/
private void parseFactoryConfigurationAdapterService(Annotation annotation)
{
EntryWriter writer = new EntryWriter(EntryType.FactoryConfigurationAdapterService);
m_writers.add(writer);
// Generate Adapter Implementation
writer.put(EntryParam.impl, m_componentClassName);
// factory pid attribute (can be specified using the factoryPid attribute, or using the factoryPidClass attribute)
String factoryPidClass = parseClassAttrValue(annotation.get(EntryParam.factoryPidClass.toString()));
// Test if a type safe configuration type is provided.
String configType = parseClassAttrValue(annotation.get(EntryParam.configType.toString()));
if (configType != null) {
writer.put(EntryParam.configType, configType);
}
String factoryPid = null;
factoryPid = get(annotation, EntryParam.factoryPid.toString(), factoryPidClass);
if (factoryPid == null) {
factoryPid = configType != null ? configType : m_componentClassName;
}
writer.put(EntryParam.factoryPid, factoryPid);
// Parse updated callback
writer.putString(annotation, EntryParam.updated, "updated");
// propagate attribute
writer.putString(annotation, EntryParam.propagate, Boolean.FALSE.toString());
// Parse the provided adapter service (use directly implemented interface by default).
if (writer.putClassArray(annotation, EntryParam.provides, m_interfaces, m_exportService) == 0)
{
checkRegisteredUnregisteredNotPresent();
}
// Parse Adapter properties, and other params common to all kind of component
parseCommonComponentAttributes(annotation, writer);
// Parse optional meta types for configuration description.
parseMetaTypes(annotation, factoryPid, true);
// Parse factoryMethod attribute
writer.putString(annotation, EntryParam.factoryMethod, null);
}
private void parseBundleDependencyAnnotation(Annotation annotation)
{
checkDependencyAlreadyDeclaredInChild(annotation, m_method, true);
EntryWriter writer = new EntryWriter(EntryType.BundleDependency);
m_writers.add(writer);
String filter = annotation.get(EntryParam.filter.toString());
if (filter != null)
{
Verifier.verifyFilter(filter, 0);
writer.put(EntryParam.filter, filter);
}
writer.putString(annotation, EntryParam.added, m_method);
writer.putString(annotation, EntryParam.changed, null);
writer.putString(annotation, EntryParam.removed, null);
writer.putString(annotation, EntryParam.required, null);
writer.putString(annotation, EntryParam.stateMask, null);
writer.putString(annotation, EntryParam.propagate, null);
parseDependencyName(writer, annotation);
}
private void parseRersourceDependencyAnnotation(Annotation annotation)
{
checkDependencyAlreadyDeclaredInChild(annotation, ! m_isField ? m_method : m_field, ! m_isField);
EntryWriter writer = new EntryWriter(EntryType.ResourceDependency);
m_writers.add(writer);
String filter = annotation.get(EntryParam.filter.toString());
if (filter != null)
{
Verifier.verifyFilter(filter, 0);
writer.put(EntryParam.filter, filter);
}
if (m_isField)
{
writer.put(EntryParam.autoConfig, m_field);
}
writer.putString(annotation, EntryParam.added, (!m_isField) ? m_method : null);
writer.putString(annotation, EntryParam.changed, null);
writer.putString(annotation, EntryParam.removed, null);
writer.putString(annotation, EntryParam.required, null);
writer.putString(annotation, EntryParam.propagate, null);
writer.putString(annotation, EntryParam.factoryMethod, null);
parseDependencyName(writer, annotation);
}
/**
* Parse the name of a given dependency.
* @param writer The writer where to write the dependency name
* @param annotation the dependency to be parsed
*/
private void parseDependencyName(EntryWriter writer, Annotation annotation)
{
String name = annotation.get(EntryParam.name.toString());
if (name != null)
{
if(! m_dependencyNames.add(name))
{
throw new IllegalArgumentException("Duplicate dependency name " + name + " in Dependency " + annotation + " from class " + m_currentClassName);
}
writer.put(EntryParam.name, name);
}
}
private void parseLifecycleAnnotation(Annotation annotation)
{
Patterns.parseField(m_field, m_descriptor, Patterns.RUNNABLE);
if ("true".equals(get(annotation,EntryParam.start.name(), "true")))
{
if (m_starter != null) {
throw new IllegalStateException("Lifecycle annotation already defined on field " +
m_starter + " in class (or super class of) " + m_componentClassName);
}
m_starter = m_field;
} else {
if (m_stopper != null) {
throw new IllegalStateException("Lifecycle annotation already defined on field " +
m_stopper + " in class (or super class of) " + m_componentClassName);
}
m_stopper = m_field;
}
}
/**
* Parse optional meta types annotation attributes
* @param annotation
*/
private void parseMetaTypes(Annotation annotation, String pid, boolean factory)
{
if (annotation.get("metadata") != null)
{
String propertiesHeading = annotation.get("heading");
String propertiesDesc = annotation.get("description");
MetaType.OCD ocd = new MetaType.OCD(pid, propertiesHeading, propertiesDesc);
for (Object p: (Object[]) annotation.get("metadata"))
{
Annotation property = (Annotation) p;
String heading = property.get("heading");
String id = property.get("id");
String type = parseClassAttrValue(property.get("type"));
Object[] defaults = (Object[]) property.get("defaults");
String description = property.get("description");
Integer cardinality = property.get("cardinality");
Boolean required = property.get("required");
MetaType.AD ad = new MetaType.AD(id, type, defaults, heading, description,
cardinality, required);
Object[] optionLabels = property.get("optionLabels");
Object[] optionValues = property.get("optionValues");
if (optionLabels == null
&& optionValues != null
||
optionLabels != null
&& optionValues == null
||
(optionLabels != null && optionValues != null && optionLabels.length != optionValues.length))
{
throw new IllegalArgumentException("invalid option labels/values specified for property "
+ id +
" in PropertyMetadata annotation from class " + m_currentClassName);
}
if (optionValues != null)
{
for (int i = 0; i < optionValues.length; i++)
{
ad.add(new MetaType.Option(optionValues[i].toString(), optionLabels[i].toString()));
}
}
ocd.add(ad);
}
m_metaType.add(ocd);
MetaType.Designate designate = new MetaType.Designate(pid, factory);
m_metaType.add(designate);
m_logger.info("Parsed MetaType Properties from class " + m_componentClassName);
}
}
/**
* Parses attributes common to all kind of components.
* First, Property annotation is parsed which represents a list of key-value pair.
* The properties are encoded using the following json form:
*
* properties ::= key-value-pair*
* key-value-pair ::= key value
* value ::= String | String[] | value-type
* value-type ::= jsonObject with value-type-info
* value-type-info ::= "type"=primitive java type
* "value"=String|String[]
*
* Exemple:
*
* "properties" : {
* "string-param" : "string-value",
* "string-array-param" : ["str1", "str2],
* "long-param" : {"type":"java.lang.Long", "value":"1"}}
* "long-array-param" : {"type":"java.lang.Long", "value":["1"]}}
* }
* }
*
* @param component the component annotation which contains a "properties" attribute. The component can be either a @Component, or an aspect, or an adapter.
* @param writer the object where the parsed attributes are written.
*/
private void parseCommonComponentAttributes(Annotation component, EntryWriter writer)
{
// Parse properties attribute.
Object[] properties = component.get(EntryParam.properties.toString());
if (properties != null)
{
for (Object property : properties)
{
Annotation propertyAnnotation = (Annotation) property;
parseProperty(propertyAnnotation, writer);
}
}
}
/**
* Parses a Property annotation. The result is added to the associated writer object
* @param annotation the @Property annotation.
* @param writer the writer object where the parsed property will be added to.
*/
private void parseProperty(Annotation property, EntryWriter writer)
{
String name = (String) property.get("name");
String type = parseClassAttrValue(property.get("type"));
Class<?> classType;
try
{
classType = (type == null) ? String.class : Class.forName(type);
}
catch (ClassNotFoundException e)
{
// Theorically impossible
throw new IllegalArgumentException("Invalid Property type " + type
+ " from annotation " + property + " in class " + m_componentClassName);
}
Object[] values;
if ((values = property.get("value")) != null)
{
values = checkPropertyType(name, classType, values);
writer.addProperty(name, values, classType);
}
else if ((values = property.get("values")) != null)
{ // deprecated
values = checkPropertyType(name, classType, values);
writer.addProperty(name, values, classType);
}
else if ((values = property.get("longValue")) != null)
{
writer.addProperty(name, values, Long.class);
}
else if ((values = property.get("doubleValue")) != null)
{
writer.addProperty(name, values, Double.class);
}
else if ((values = property.get("floatValue")) != null)
{
writer.addProperty(name, values, Float.class);
}
else if ((values = property.get("intValue")) != null)
{
writer.addProperty(name, values, Integer.class);
}
else if ((values = property.get("byteValue")) != null)
{
writer.addProperty(name, values, Byte.class);
}
else if ((values = property.get("charValue")) != null)
{
writer.addProperty(name, values, Character.class);
}
else if ((values = property.get("booleanValue")) != null)
{
writer.addProperty(name, values, Boolean.class);
}
else if ((values = property.get("shortValue")) != null)
{
writer.addProperty(name, values, Short.class);
}
else
{
throw new IllegalArgumentException(
"Missing Property value from annotation " + property + " in class " + m_componentClassName);
}
}
/**
* Checks if a property contains values that are compatible with a give primitive type.
*
* @param name the property name
* @param values the values for the property name
* @param type the primitive type.
* @return the same property values, possibly modified if the type is 'Character' (the strings are converted to their character integer values).
*/
private Object[] checkPropertyType(String name, Class<?> type, Object ... values)
{
if (type.equals(String.class))
{
return values;
} else if (type.equals(Long.class)) {
for (Object value : values) {
try {
Long.valueOf(value.toString());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Property \"" + name + "\" in class " + m_componentClassName
+ " does not contain a valid Long value (" + value.toString() + ")");
}
}
} else if (type.equals(Double.class)) {
for (Object value : values) {
try {
Double.valueOf(value.toString());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Property \"" + name + "\" in class " + m_componentClassName
+ " does not contain a valid Double value (" + value.toString() + ")");
}
}
} else if (type.equals(Float.class)) {
for (Object value : values) {
try {
Float.valueOf(value.toString());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Property \"" + name + "\" in class " + m_componentClassName
+ " does not contain a valid Float value (" + value.toString() + ")");
}
}
} else if (type.equals(Integer.class)) {
for (Object value : values) {
try {
Integer.valueOf(value.toString());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Property \"" + name + "\" in class " + m_componentClassName
+ " does not contain a valid Integer value (" + value.toString() + ")");
}
}
} else if (type.equals(Byte.class)) {
for (Object value : values) {
try {
Byte.valueOf(value.toString());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Property \"" + name + "\" in class " + m_componentClassName
+ " does not contain a valid Byte value (" + value.toString() + ")");
}
}
} else if (type.equals(Character.class)) {
for (int i = 0; i < values.length; i++)
{
try
{
// If the string is already an integer, don't modify it
Integer.valueOf(values[i].toString());
}
catch (NumberFormatException e)
{
// Converter the character string to its corresponding integer code.
if (values[i].toString().length() != 1)
{
throw new IllegalArgumentException("Property \"" + name + "\" in class "
+ m_componentClassName + " does not contain a valid Character value (" + values[i] + ")");
}
try
{
values[i] = Integer.valueOf(values[i].toString().charAt(0));
}
catch (NumberFormatException e2)
{
throw new IllegalArgumentException("Property \"" + name + "\" in class "
+ m_componentClassName + " does not contain a valid Character value (" + values[i].toString()
+ ")");
}
}
}
} else if (type.equals(Boolean.class)) {
for (Object value : values) {
if (! value.toString().equalsIgnoreCase("false") && ! value.toString().equalsIgnoreCase("true")) {
throw new IllegalArgumentException("Property \"" + name + "\" in class " + m_componentClassName
+ " does not contain a valid Boolean value (" + value.toString() + ")");
}
}
} else if (type.equals(Short.class)) {
for (Object value : values) {
try {
Short.valueOf(value.toString());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Property \"" + name + "\" in class " + m_componentClassName
+ " does not contain a valid Short value (" + value.toString() + ")");
}
}
}
return values;
}
/**
* Parse Inject annotation, used to inject some special classes in some fields
* (BundleContext/DependencyManager etc ...)
* @param annotation the Inject annotation
*/
private void parseInject(Annotation annotation)
{
if (Patterns.BUNDLE_CONTEXT.matcher(m_descriptor).matches())
{
if (m_bundleContextField != null) {
throw new IllegalStateException("detected multiple @Inject annotation from class " + m_currentClassName + " (on from child classes)");
}
m_bundleContextField = m_field;
}
else if (Patterns.DEPENDENCY_MANAGER.matcher(m_descriptor).matches())
{
if (m_dependencyManagerField != null) {
throw new IllegalStateException("detected multiple @Inject annotation from class " + m_currentClassName + " (on from child classes)");
}
m_dependencyManagerField = m_field;
}
else if (Patterns.COMPONENT.matcher(m_descriptor).matches())
{
if (m_componentField != null) {
throw new IllegalStateException("detected multiple @Inject annotation from class " + m_currentClassName + " (on from child classes)");
}
m_componentField = m_field;
}
else if (Patterns.SERVICE_REGISTRATION.matcher(m_descriptor).matches())
{
if (m_registrationField != null) {
throw new IllegalStateException("detected multiple @Inject annotation from class " + m_currentClassName + " (on from child classes)");
}
m_registrationField = m_field;
}
else
{
throw new IllegalArgumentException("@Inject annotation can't be applied on the field \"" + m_field
+ "\" in class " + m_currentClassName);
}
}
/**
* This method checks if the @Registered and/or @Unregistered annotations have been defined
* while they should not, because the component does not provide a service.
*/
private void checkRegisteredUnregisteredNotPresent()
{
if (m_registeredMethod != null)
{
throw new IllegalStateException("@Registered annotation can't be used on a Component " +
" which does not provide a service (class=" + m_currentClassName + ")");
}
if (m_unregisteredMethod != null)
{
throw new IllegalStateException("@Unregistered annotation can't be used on a Component " +
" which does not provide a service (class=" + m_currentClassName + ")");
}
}
/**
* Get an annotation attribute, and return a default value if its not present.
* @param <T> the type of the variable which is assigned to the return value of this method.
* @param annotation The annotation we are parsing
* @param name the attribute name to get from the annotation
* @param defaultValue the default value to return if the attribute is not found in the annotation
* @return the annotation attribute value, or the defaultValue if not found
*/
@SuppressWarnings("unchecked")
private <T> T get(Annotation annotation, String name, T defaultValue)
{
T value = (T) annotation.get(name);
return value != null ? value : defaultValue;
}
}