blob: 49cdf13b2aa3695cc601c6d8183fe67b290af183 [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;
import java.io.File;
import java.util.*;
import org.apache.felix.scrplugin.om.*;
import org.apache.felix.scrplugin.om.metatype.*;
import org.apache.felix.scrplugin.tags.*;
import org.apache.felix.scrplugin.xml.ComponentDescriptorIO;
import org.apache.felix.scrplugin.xml.MetaTypeIO;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.*;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.StringUtils;
import org.osgi.service.cm.ConfigurationAdmin;
/**
* The <code>SCRDescriptorMojo</code>
* generates a service descriptor file based on annotations found in the sources.
*
* @goal scr
* @phase process-classes
* @description Build Service Descriptors from Java Source
* @requiresDependencyResolution compile
*/
public class SCRDescriptorMojo extends AbstractMojo {
/**
* @parameter expression="${project.build.directory}/scr-plugin-generated"
* @required
* @readonly
*/
private File outputDirectory;
/**
* The Maven project.
*
* @parameter expression="${project}"
* @required
* @readonly
*/
private MavenProject project;
/**
* Name of the generated descriptor.
*
* @parameter expression="${scr.descriptor.name}" default-value="serviceComponents.xml"
*/
private String finalName;
/**
* Name of the generated meta type file.
*
* @parameter default-value="metatype.xml"
*/
private String metaTypeName;
/**
* This flag controls the generation of the bind/unbind methods.
* @parameter default-value="true"
*/
private boolean generateAccessors;
/**
* @see org.apache.maven.plugin.AbstractMojo#execute()
*/
public void execute() throws MojoExecutionException, MojoFailureException {
this.getLog().debug("Starting SCRDescriptorMojo....");
this.getLog().debug("..generating accessors: " + this.generateAccessors);
boolean hasFailures = false;
JavaClassDescriptorManager jManager = new JavaClassDescriptorManager(this.getLog(),
this.project);
// iterate through all source classes and check for component tag
final JavaClassDescription[] javaSources = jManager.getSourceDescriptions();
final Components components = new Components();
final Components abstractComponents = new Components();
final MetaData metaData = new MetaData();
metaData.setLocalization("metatype");
for (int i = 0; i < javaSources.length; i++) {
this.getLog().debug("Testing source " + javaSources[i].getName());
final JavaTag tag = javaSources[i].getTagByName(Constants.COMPONENT);
if (tag != null) {
this.getLog().debug("Processing service class " + javaSources[i].getName());
final Component comp = this.createComponent(javaSources[i], tag, metaData);
if (comp != null) {
if ( !comp.isDs() ) {
getLog().debug("Not adding descriptor " + comp);
} else if ( comp.isAbstract() ) {
this.getLog().debug("Adding abstract descriptor " + comp);
abstractComponents.addComponent(comp);
} else {
this.getLog().debug("Adding descriptor " + comp);
components.addComponent(comp);
}
} else {
hasFailures = true;
}
}
}
// after checking all classes, throw if there were any failures
if (hasFailures) {
throw new MojoFailureException("SCR Descriptor parsing had failures (see log)");
}
// write meta type info if there is a file
if (!StringUtils.isEmpty(this.metaTypeName)) {
File mtFile = new File(this.outputDirectory, "OSGI-INF" + File.separator + "metatype" + File.separator + this.metaTypeName);
if ( metaData.getDescriptors().size() > 0 ) {
this.getLog().info("Generating "
+ metaData.getDescriptors().size()
+ " MetaType Descriptors to " + mtFile);
mtFile.getParentFile().mkdirs();
MetaTypeIO.write(metaData, mtFile);
} else {
if ( mtFile.exists() ) {
mtFile.delete();
}
}
} else {
this.getLog().info("Have no meta type file name, not writing metatype info");
}
// if we have abstract descriptors, write them
final File adFile = new File(this.outputDirectory, Constants.ABSTRACT_DESCRIPTOR_RELATIVE_PATH);
if ( !abstractComponents.getComponents().isEmpty() ) {
this.getLog().info("Writing abstract service descriptor " + adFile + " with " + abstractComponents.getComponents().size() + " entries.");
adFile.getParentFile().mkdirs();
ComponentDescriptorIO.write(abstractComponents, adFile);
} else {
this.getLog().debug("No abstract SCR Descriptors found in project.");
// remove file
if ( adFile.exists() ) {
this.getLog().debug("Removing obsolete abstract service descriptor " + adFile);
adFile.delete();
}
}
// terminate if there is nothing else to write
if (components.getComponents().isEmpty()) {
this.getLog().debug("No SCR Descriptors found in project.");
return;
}
// check file name
if (StringUtils.isEmpty(this.finalName)) {
this.getLog().error("Descriptor file name must not be empty.");
return;
}
// finally the descriptors have to be written ....
File descriptorFile = new File(new File(this.outputDirectory, "OSGI-INF"), this.finalName);
descriptorFile.getParentFile().mkdirs(); // ensure parent dir
this.getLog().info("Generating " + components.getComponents().size()
+ " Service Component Descriptors to " + descriptorFile);
ComponentDescriptorIO.write(components, descriptorFile);
// now add the descriptor file to the maven resources
final String ourRsrcPath = this.outputDirectory.getAbsolutePath();
boolean found = false;
final Iterator rsrcIterator = this.project.getResources().iterator();
while ( !found && rsrcIterator.hasNext() ) {
final Resource rsrc = (Resource)rsrcIterator.next();
found = rsrc.getDirectory().equals(ourRsrcPath);
}
if ( !found ) {
final Resource resource = new Resource();
resource.setDirectory(this.outputDirectory.getAbsolutePath());
this.project.addResource(resource);
}
// and set include accordingly
String svcComp = project.getProperties().getProperty("Service-Component");
svcComp= (svcComp == null) ? "OSGI-INF/" + finalName : svcComp + ", " + "OSGI-INF/" + finalName;
project.getProperties().setProperty("Service-Component", svcComp);
}
/**
* Create a component for the java class description.
* @param description
* @return The generated component descriptor or null if any error occurs.
* @throws MojoExecutionException
*/
protected Component createComponent(JavaClassDescription description, JavaTag componentTag, MetaData metaData)
throws MojoExecutionException {
// create a new component
final Component component = new Component(componentTag);
// set implementation
component.setImplementation(new Implementation(description.getName()));
final OCD ocd = this.doComponent(componentTag, component, metaData);
boolean inherited = this.getBoolean(componentTag, Constants.COMPONENT_INHERIT, true);
this.doServices(description.getTagsByName(Constants.SERVICE, inherited), component, description);
// collect properties and references from class tags and fields
final Map properties = new HashMap();
final Map references = new HashMap();
JavaClassDescription currentDescription = description;
do {
// properties
final JavaTag[] props = currentDescription.getTagsByName(Constants.PROPERTY, false);
for (int i=0; i < props.length; i++) {
this.testProperty(properties, props[i], null, description == currentDescription);
}
// references
final JavaTag[] refs = currentDescription.getTagsByName(Constants.REFERENCE, inherited);
for (int i=0; i < refs.length; i++) {
this.testReference(references, refs[i], null, description == currentDescription);
}
// fields
final JavaField[] fields = currentDescription.getFields();
for (int i=0; i < fields.length; i++) {
JavaTag tag = fields[i].getTagByName(Constants.REFERENCE);
if (tag != null) {
this.testReference(references, tag, fields[i].getName(), description == currentDescription);
}
tag = fields[i].getTagByName(Constants.PROPERTY);
if (tag != null) {
String defaultName = null;
if ( "java.lang.String".equals(fields[i].getType()) ) {
defaultName = fields[i].getInitializationExpression().trim();
int pos = defaultName.indexOf("\"");
if ( pos != -1 ) {
defaultName = defaultName.substring(pos + 1);
defaultName = defaultName.substring(0, defaultName.lastIndexOf("\""));
}
}
this.testProperty(properties, tag, defaultName, description == currentDescription);
}
}
currentDescription = currentDescription.getSuperClass();
} while (inherited && currentDescription != null);
// process properties
final Iterator propIter = properties.entrySet().iterator();
while ( propIter.hasNext() ) {
final Map.Entry entry = (Map.Entry)propIter.next();
final String propName = entry.getKey().toString();
final JavaTag tag = (JavaTag)entry.getValue();
this.doProperty(tag, propName, component, ocd);
}
// process references
final Iterator refIter = references.entrySet().iterator();
while ( refIter.hasNext() ) {
final Map.Entry entry = (Map.Entry)refIter.next();
final String refName = entry.getKey().toString();
final JavaTag tag = (JavaTag)entry.getValue();
this.doReference(tag, refName, component);
}
// pid handling
final boolean createPid = this.getBoolean(componentTag, Constants.COMPONENT_CREATE_PID, true);
if ( createPid ) {
// check for an existing pid first
boolean found = false;
final Iterator iter = component.getProperties().iterator();
while ( !found && iter.hasNext() ) {
final Property prop = (Property)iter.next();
found = org.osgi.framework.Constants.SERVICE_PID.equals( prop.getName() );
}
if ( !found ) {
final Property pid = new Property();
component.addProperty(pid);
pid.setName(org.osgi.framework.Constants.SERVICE_PID);
pid.setValue(component.getName());
}
}
final List issues = new ArrayList();
final List warnings = new ArrayList();
component.validate(issues, warnings);
// now log warnings and errors (warnings first)
Iterator i = warnings.iterator();
while ( i.hasNext() ) {
this.getLog().warn((String)i.next());
}
i = issues.iterator();
while ( i.hasNext() ) {
this.getLog().error((String)i.next());
}
// return nothing if validation fails
return issues.size() == 0 ? component : null;
}
/**
* Fill the component object with the information from the tag.
* @param tag
* @param component
*/
protected OCD doComponent(JavaTag tag, Component component, MetaData metaData) {
// check if this is an abstract definition
final String abstractType = tag.getNamedParameter(Constants.COMPONENT_ABSTRACT);
if (abstractType != null) {
component.setAbstract("yes".equalsIgnoreCase(abstractType) || "true".equalsIgnoreCase(abstractType));
} else {
// default true for abstract classes, false otherwise
component.setAbstract(tag.getJavaClassDescription().isAbstract());
}
// check if this is a definition to ignore
final String ds = tag.getNamedParameter(Constants.COMPONENT_DS);
component.setDs((ds == null) ? true : ("yes".equalsIgnoreCase(ds) || "true".equalsIgnoreCase(ds)));
String name = tag.getNamedParameter(Constants.COMPONENT_NAME);
component.setName(StringUtils.isEmpty(name) ? component.getImplementation().getClassame() : name);
component.setEnabled(Boolean.valueOf(this.getBoolean(tag, Constants.COMPONENT_ENABLED, true)));
component.setFactory(tag.getNamedParameter(Constants.COMPONENT_FACTORY));
component.setImmediate(Boolean.valueOf(this.getBoolean(tag, Constants.COMPONENT_IMMEDIATE, true)));
// whether metatype information is to generated for the component
final String metaType = tag.getNamedParameter(Constants.COMPONENT_METATYPE);
final boolean hasMetaType = metaType == null || "yes".equalsIgnoreCase(metaType)
|| "true".equalsIgnoreCase(metaType);
if ( hasMetaType ) {
// ocd
final OCD ocd = new OCD();
metaData.addOCD(ocd);
ocd.setId(component.getName());
String ocdName = tag.getNamedParameter(Constants.COMPONENT_LABEL);
if ( ocdName == null ) {
ocdName = "%" + component.getName() + ".name";
}
ocd.setName(ocdName);
String ocdDescription = tag.getNamedParameter(Constants.COMPONENT_DESCRIPTION);
if ( ocdDescription == null ) {
ocdDescription = "%" + component.getName() + ".description";
}
ocd.setDescription(ocdDescription);
// designate
final Designate designate = new Designate();
metaData.addDesignate(designate);
designate.setPid(component.getName());
// designate.object
final MTObject mtobject = new MTObject();
designate.setObject(mtobject);
mtobject.setOcdref(component.getName());
return ocd;
}
return null;
}
/**
* Process the service annotations
* @param services
* @param component
* @param description
* @throws MojoExecutionException
*/
protected void doServices(JavaTag[] services, Component component, JavaClassDescription description)
throws MojoExecutionException {
// no services, hence certainly no service factory
if (services == null || services.length == 0) {
return;
}
final Service service = new Service();
component.setService(service);
boolean serviceFactory = false;
for (int i=0; i < services.length; i++) {
String name = services[i].getNamedParameter(Constants.SERVICE_INTERFACE);
if (StringUtils.isEmpty(name)) {
this.addInterfaces(service, services[i], description);
} else {
final Interface interf = new Interface(services[i]);
interf.setInterfacename(name);
service.addInterface(interf);
}
serviceFactory |= this.getBoolean(services[i], Constants.SERVICE_FACTORY, false);
}
service.setServicefactory(serviceFactory);
}
/**
* Recursively add interfaces to the service.
*/
protected void addInterfaces(final Service service, final JavaTag serviceTag, final JavaClassDescription description)
throws MojoExecutionException {
if ( description != null ) {
JavaClassDescription[] interfaces = description.getImplementedInterfaces();
for (int j=0; j < interfaces.length; j++) {
final Interface interf = new Interface(serviceTag);
interf.setInterfacename(interfaces[j].getName());
service.addInterface(interf);
// recursivly add interfaces implemented by this interface
this.addInterfaces(service, serviceTag, interfaces[j]);
}
// try super class
this.addInterfaces(service, serviceTag, description.getSuperClass());
}
}
/**
* @param property
* @param defaultName
* @param component
*/
protected void doProperty(JavaTag property, String name, Component component, OCD ocd) {
final Property prop = new Property(property);
prop.setName(name);
prop.setType(property.getNamedParameter(Constants.PROPERTY_TYPE));
final String value = property.getNamedParameter(Constants.PROPERTY_VALUE);
if ( value != null ) {
prop.setValue(value);
} else {
// check for multivalue
final List values = new ArrayList();
final Map valueMap = property.getNamedParameterMap();
for (Iterator vi = valueMap.entrySet().iterator(); vi.hasNext();) {
final Map.Entry entry = (Map.Entry) vi.next();
final String key = (String) entry.getKey();
if (key.startsWith("values")) {
values.add(entry.getValue());
}
}
if ( values.size() > 0 ) {
prop.setMultiValue((String[])values.toArray(new String[values.size()]));
}
}
// property is private if explicitly marked or a well known
// service property such as service.pid
final boolean isPrivate = getBoolean(property,
Constants.PROPERTY_PRIVATE, false)
|| name.equals(org.osgi.framework.Constants.SERVICE_PID)
|| name.equals(org.osgi.framework.Constants.SERVICE_DESCRIPTION)
|| name.equals(org.osgi.framework.Constants.SERVICE_ID)
|| name.equals(org.osgi.framework.Constants.SERVICE_RANKING)
|| name.equals(org.osgi.framework.Constants.SERVICE_VENDOR)
|| name.equals(ConfigurationAdmin.SERVICE_BUNDLELOCATION)
|| name.equals(ConfigurationAdmin.SERVICE_FACTORYPID);
// if this is a public property and the component is generating metatype info
// store the information!
if ( !isPrivate && ocd != null ) {
final AttributeDefinition ad = new AttributeDefinition();
ocd.getProperties().add(ad);
ad.setId(prop.getName());
ad.setType(prop.getType());
String adName = property.getNamedParameter(Constants.PROPERTY_LABEL);
if ( adName == null ) {
adName = "%" + prop.getName() + ".name";
}
ad.setName(adName);
String adDesc = property.getNamedParameter(Constants.PROPERTY_DESCRIPTION);
if ( adDesc == null ) {
adDesc = "%" + prop.getName() + ".description";
}
ad.setDescription(adDesc);
// set optional multivalues, cardinality might be overwritten by setValues !!
final String cValue = property.getNamedParameter(Constants.PROPERTY_CARDINALITY);
if (cValue != null) {
if ("-".equals(cValue)) {
// unlimited vector
ad.setCardinality(new Integer(Integer.MIN_VALUE));
} else if ("+".equals(cValue)) {
// unlimited array
ad.setCardinality(new Integer(Integer.MAX_VALUE));
} else {
try {
ad.setCardinality(Integer.valueOf(cValue));
} catch (NumberFormatException nfe) {
// default to scalar in case of conversion problem
}
}
}
ad.setDefaultValue(prop.getValue());
ad.setDefaultMultiValue(prop.getMultiValue());
// check options
String[] parameters = property.getParameters();
Map options = null;
for (int j=0; j < parameters.length; j++) {
if (Constants.PROPERTY_OPTIONS.equals(parameters[j])) {
options = new LinkedHashMap();
} else if (options != null) {
String optionLabel = parameters[j];
String optionValue = (j < parameters.length-2) ? parameters[j+2] : null;
if (optionValue != null) {
options.put(optionLabel, optionValue);
}
j += 2;
}
}
ad.setOptions(options);
}
component.addProperty(prop);
}
protected String getPropertyName(JavaTag property, String defaultName) {
String name = property.getNamedParameter(Constants.PROPERTY_NAME);
if (StringUtils.isEmpty(name) && defaultName != null) {
name = defaultName;
}
if (!StringUtils.isEmpty(name)) {
return name;
}
return null;
}
protected void testProperty(Map properties, JavaTag property, String defaultName, boolean isInspectedClass)
throws MojoExecutionException {
final String propName = this.getPropertyName(property, defaultName);
if ( propName != null ) {
if ( properties.containsKey(propName) ) {
// if the current class is the class we are currently inspecting, we
// have found a duplicate definition
if ( isInspectedClass ) {
throw new MojoExecutionException("Duplicate definition for property " + propName + " in class " + property.getJavaClassDescription().getName());
}
} else {
properties.put(propName, property);
}
}
}
protected void testReference(Map references, JavaTag reference, String defaultName, boolean isInspectedClass)
throws MojoExecutionException {
final String refName = this.getReferenceName(reference, defaultName);
if ( refName != null ) {
if ( references.containsKey(refName) ) {
// if the current class is the class we are currently inspecting, we
// have found a duplicate definition
if ( isInspectedClass ) {
throw new MojoExecutionException("Duplicate definition for reference " + refName + " in class " + reference.getJavaClassDescription().getName());
}
} else {
references.put(refName, reference);
}
}
}
protected String getReferenceName(JavaTag reference, String defaultName) {
String name = reference.getNamedParameter(Constants.REFERENCE_NAME);
if (StringUtils.isEmpty(name)) {
name = defaultName;
}
if (!StringUtils.isEmpty(name)) {
return name;
}
return null;
}
/**
* @param reference
* @param defaultName
* @param component
*/
protected void doReference(JavaTag reference, String name, Component component)
throws MojoExecutionException {
// ensure interface
String type = reference.getNamedParameter(Constants.REFERENCE_INTERFACE);
if (StringUtils.isEmpty(type)) {
if ( reference.getField() != null ) {
type = reference.getField().getType();
}
}
final Reference ref = new Reference(reference, component.getJavaClassDescription());
ref.setName(name);
ref.setInterfacename(type);
ref.setCardinality(reference.getNamedParameter(Constants.REFERENCE_CARDINALITY));
if ( ref.getCardinality() == null ) {
ref.setCardinality("1..1");
}
ref.setPolicy(reference.getNamedParameter(Constants.REFERENCE_POLICY));
if ( ref.getPolicy() == null ) {
ref.setPolicy("static");
}
ref.setTarget(reference.getNamedParameter(Constants.REFERENCE_TARGET));
final String bindValue = reference.getNamedParameter(Constants.REFERENCE_BIND);
if ( bindValue != null ) {
ref.setBind(bindValue);
}
final String unbindValue = reference.getNamedParameter(Constants.REFERENCE_UNDBIND);
if ( unbindValue != null ) {
ref.setUnbind(unbindValue);
}
// if this is a field with a single cardinality,
// we look for the bind/unbind methods
// and create them if they are not availabe
if ( this.generateAccessors ) {
if ( reference.getField() != null && component.getJavaClassDescription() instanceof ModifiableJavaClassDescription ) {
if ( ref.getCardinality().equals("0..1") || ref.getCardinality().equals("1..1") ) {
boolean createBind = false;
boolean createUnbind = false;
// Only create method if no bind name has been specified
if ( bindValue == null && ref.findMethod(ref.getBind()) == null ) {
// create bind method
createBind = true;
}
if ( unbindValue == null && ref.findMethod(ref.getUnbind()) == null ) {
// create unbind method
createUnbind = true;
}
if ( createBind || createUnbind ) {
((ModifiableJavaClassDescription)component.getJavaClassDescription()).addMethods(name, type, createBind, createUnbind);
}
}
}
}
component.addReference(ref);
}
protected boolean getBoolean(JavaTag tag, String name, boolean defaultValue) {
String value = tag.getNamedParameter(name);
return (value == null) ? defaultValue : Boolean.valueOf(value).booleanValue();
}
}