blob: 844eec0a0937257bb7984f954795010439f32ee5 [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.scrplugin.ds;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import org.apache.felix.scrplugin.SCRDescriptorException;
import org.apache.felix.scrplugin.SCRDescriptorFailureException;
import org.apache.felix.scrplugin.SpecVersion;
import org.apache.felix.scrplugin.annotations.AnnotationProcessor;
import org.apache.felix.scrplugin.annotations.ClassAnnotation;
import org.apache.felix.scrplugin.annotations.MethodAnnotation;
import org.apache.felix.scrplugin.annotations.ScannedClass;
import org.apache.felix.scrplugin.description.ClassDescription;
import org.apache.felix.scrplugin.description.ComponentConfigurationPolicy;
import org.apache.felix.scrplugin.description.ComponentDescription;
import org.apache.felix.scrplugin.description.PropertyDescription;
import org.apache.felix.scrplugin.description.PropertyType;
import org.apache.felix.scrplugin.description.PropertyUnbounded;
import org.apache.felix.scrplugin.description.ReferenceCardinality;
import org.apache.felix.scrplugin.description.ReferenceDescription;
import org.apache.felix.scrplugin.description.ReferencePolicy;
import org.apache.felix.scrplugin.description.ReferencePolicyOption;
import org.apache.felix.scrplugin.description.ReferenceStrategy;
import org.apache.felix.scrplugin.description.ServiceDescription;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
/**
* This is the processor for the DS annotations.
*/
public class DSAnnotationProcessor implements AnnotationProcessor {
/**
* @see org.apache.felix.scrplugin.annotations.AnnotationProcessor#getName()
*/
@Override
public String getName() {
return "DS Annotation Processor";
}
/**
* @see org.apache.felix.scrplugin.annotations.AnnotationProcessor#process(org.apache.felix.scrplugin.annotations.ScannedClass, org.apache.felix.scrplugin.description.ClassDescription)
*/
@Override
public void process(final ScannedClass scannedClass,
final ClassDescription describedClass)
throws SCRDescriptorFailureException, SCRDescriptorException {
final List<ClassAnnotation> componentTags = scannedClass.getClassAnnotations(Component.class.getName());
scannedClass.processed(componentTags);
for (final ClassAnnotation cad : componentTags) {
this.createComponent(cad, describedClass, scannedClass);
}
// search for the component descriptions and use the first one
final List<ComponentDescription> componentDescs = describedClass.getDescriptions(ComponentDescription.class);
ComponentDescription found = null;
if (!componentDescs.isEmpty()) {
found = componentDescs.get(0);
}
if (found != null) {
final ComponentDescription cd = found;
// search for methods
final List<MethodAnnotation> methodTags = scannedClass.getMethodAnnotations(null);
for (final MethodAnnotation m : methodTags) {
if (m.getName().equals(Activate.class.getName())) {
cd.setActivate(m.getAnnotatedMethod().getName());
scannedClass.processed(m);
} else if (m.getName().equals(Deactivate.class.getName())) {
cd.setDeactivate(m.getAnnotatedMethod().getName());
scannedClass.processed(m);
} else if (m.getName().equals(Modified.class.getName())) {
cd.setModified(m.getAnnotatedMethod().getName());
scannedClass.processed(m);
} else if (m.getName().equals(Reference.class.getName()) ) {
this.processReference(describedClass, m);
scannedClass.processed(m);
}
}
}
}
/**
* @see org.apache.felix.scrplugin.annotations.AnnotationProcessor#getRanking()
*/
@Override
public int getRanking() {
return 300;
}
/**
* Create a component description.
*
* @param cad The component annotation for the class.
* @param scannedClass The scanned class.
*/
private ComponentDescription createComponent(final ClassAnnotation cad,
final ClassDescription describedClass,
final ScannedClass scannedClass)
throws SCRDescriptorException {
final ComponentDescription component = new ComponentDescription(cad);
describedClass.add(component);
// Although not defined in the spec, we support abstract classes.
final boolean classIsAbstract = Modifier.isAbstract(scannedClass.getClass().getModifiers());
component.setAbstract(classIsAbstract);
// name
component.setName(cad.getStringValue("name", scannedClass.getScannedClass().getName()));
// services
final List<String> listedInterfaces = new ArrayList<String>();
if (cad.hasValue("service") ) {
final String[] interfaces = (String[]) cad.getValue("service");
if ( interfaces != null ) {
for (final String t : interfaces) {
listedInterfaces.add(t);
}
}
} else {
// scan directly implemented interfaces
this.searchInterfaces(listedInterfaces, scannedClass.getScannedClass());
}
if ( listedInterfaces.size() > 0 ) {
final ServiceDescription serviceDesc = new ServiceDescription(cad);
describedClass.add(serviceDesc);
for(final String name : listedInterfaces) {
serviceDesc.addInterface(name);
}
serviceDesc.setServiceFactory(cad.getBooleanValue("servicefactory", false));
}
// factory
component.setFactory(cad.getStringValue("factory", null));
// enabled
if (cad.getValue("enabled") != null) {
component.setEnabled(cad.getBooleanValue("enabled", true));
}
// immediate
if (cad.getValue("immediate") != null) {
component.setImmediate(cad.getBooleanValue("immediate", false));
}
// property
final String[] property = (String[])cad.getValue("property");
if ( property != null ) {
// TODO - what do we do if the value is invalid?
for(final String propDef : property) {
final int pos = propDef.indexOf('=');
if ( pos != -1 ) {
final String prefix = propDef.substring(0, pos);
final String value = propDef.substring(pos + 1);
final int typeSep = prefix.indexOf(':');
final String key = (typeSep == -1 ? prefix : prefix.substring(0, typeSep));
final String type = (typeSep == -1 ? PropertyType.String.name() : prefix.substring(typeSep + 1));
final PropertyType propType = PropertyType.valueOf(type);
// FELIX-4159 : check if this is a multi value prop
final List<PropertyDescription> existingProps = describedClass.getDescriptions(PropertyDescription.class);
PropertyDescription found = null;
for(final PropertyDescription current : existingProps) {
if ( current.getName().equals(key) ) {
found = current;
break;
}
}
if ( found == null ) {
final PropertyDescription pd = new PropertyDescription(cad);
describedClass.add(pd);
pd.setName(key);
pd.setValue(value);
pd.setType(propType);
pd.setUnbounded(PropertyUnbounded.DEFAULT);
} else {
if ( propType != found.getType() ) {
throw new SCRDescriptorException("Multi value property '" + key + "' has different types: "
+ found.getType() + " & " + propType,
describedClass.getSource());
}
if ( found.getValue() != null ) {
final String[] values = new String[2];
values[0] = found.getValue();
values[1] = value;
found.setMultiValue(values);
} else {
final String[] oldValues = found.getMultiValue();
final String[] newValues = new String[oldValues.length + 1];
System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
newValues[oldValues.length] = value;
found.setMultiValue(newValues);
}
}
}
}
}
// TODO: properties
// xmlns
if (cad.getValue("xmlns") != null) {
final SpecVersion spec = SpecVersion.fromNamespaceUrl(cad.getValue("xmlns").toString());
if ( spec == null ) {
throw new SCRDescriptorException("Unknown xmlns attribute value: " + cad.getValue("xmlns"),
describedClass.getSource());
}
component.setSpecVersion(spec);
}
// configuration policy
component.setConfigurationPolicy(ComponentConfigurationPolicy.valueOf(cad.getEnumValue("configurationPolicy",
ComponentConfigurationPolicy.OPTIONAL.name())));
// configuration pid
Object configPid = cad.getValue("configurationPid");
if ( configPid instanceof String ) {
component.setConfigurationPid((String)configPid);
} else if ( configPid instanceof String[] && ((String[])configPid).length == 1 ) {
component.setConfigurationPid(((String[])configPid)[0]);
} else {
component.setConfigurationPid(null);
}
component.setCreatePid(false);
// no inheritance
component.setInherit(false);
return component;
}
/**
* Get all directly implemented interfaces
*/
private void searchInterfaces(final List<String> interfaceList, final Class<?> javaClass) {
final Class<?>[] interfaces = javaClass.getInterfaces();
for (final Class<?> i : interfaces) {
interfaceList.add(i.getName());
}
}
/**
* Process a reference
*/
private void processReference(final ClassDescription describedClass, final MethodAnnotation ad) {
final ReferenceDescription ref = new ReferenceDescription(ad);
describedClass.add(ref);
ref.setStrategy(ReferenceStrategy.EVENT);
final String methodName = ad.getAnnotatedMethod().getName();
final String defaultUnbindMethodName;
final String refNameByMethod;
if ( methodName.startsWith("add") ) {
refNameByMethod = methodName.substring(3);
defaultUnbindMethodName = "remove" + refNameByMethod;
} else if ( methodName.startsWith("set") ) {
refNameByMethod = methodName.substring(3);
defaultUnbindMethodName = "unset" + refNameByMethod;
} else if ( methodName.startsWith("bind") ) {
refNameByMethod = methodName.substring(4);
defaultUnbindMethodName = "unbind" + refNameByMethod;
} else {
refNameByMethod = methodName;
defaultUnbindMethodName = "un" + refNameByMethod;
}
final String defaultUpdateMethodName = "updated" + refNameByMethod;
// bind method
ref.setBind(ad.getAnnotatedMethod().getName());
// unbind method
final String unbind = ad.getStringValue("unbind",
this.hasMethod(describedClass, defaultUnbindMethodName) ? defaultUnbindMethodName : "-");
if ( !unbind.equals("-") ) {
ref.setUnbind(unbind);
}
// update method
final String updated = ad.getStringValue("updated",
this.hasMethod(describedClass, defaultUpdateMethodName) ? defaultUpdateMethodName : "-");
if ( !updated.equals("-") ) {
ref.setUpdated(updated);
}
// name
ref.setName(ad.getStringValue("name", refNameByMethod));
// service
final String serviceName = ad.getStringValue("service", null);
if ( serviceName != null ) {
ref.setInterfaceName(serviceName);
} else {
final Class<?>[] params = ad.getAnnotatedMethod().getParameterTypes();
if ( params != null && params.length > 0 ) {
ref.setInterfaceName(params[0].getName());
}
}
// cardinality
final String cardinality = ad.getEnumValue("cardinality", "MANDATORY");
if ( cardinality.equals("OPTIONAL") ) {
ref.setCardinality(ReferenceCardinality.OPTIONAL_UNARY);
} else if ( cardinality.equals("MULTIPLE") ) {
ref.setCardinality(ReferenceCardinality.OPTIONAL_MULTIPLE);
} else if ( cardinality.equals("AT_LEAST_ONE") ) {
ref.setCardinality(ReferenceCardinality.MANDATORY_MULTIPLE);
} else {
ref.setCardinality(ReferenceCardinality.MANDATORY_UNARY);
}
// policy
ref.setPolicy(ReferencePolicy.valueOf(ad.getEnumValue("policy", ReferencePolicy.STATIC.name())));
// policy option
ref.setPolicyOption(ReferencePolicyOption.valueOf(ad.getEnumValue("policyOption", ReferencePolicyOption.RELUCTANT.name())));
// target
ref.setTarget(ad.getStringValue("target", null));
}
private boolean hasMethod(final ClassDescription classDescription, final String name) {
final Method[] allMethods = classDescription.getDescribedClass().getDeclaredMethods();
for(final Method m : allMethods) {
if ( m.getName().equals(name) ) {
return true;
}
}
return false;
}
}