aQute.bnd.make.component.ServiceComponent Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of biz.aQute.bndlib Show documentation
Show all versions of biz.aQute.bndlib Show documentation
bndlib: A Swiss Army Knife for OSGi
package aQute.bnd.make.component;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import aQute.bnd.component.HeaderReader;
import aQute.bnd.component.TagResource;
import aQute.bnd.header.Attrs;
import aQute.bnd.header.Parameters;
import aQute.bnd.make.metatype.MetaTypeReader;
import aQute.bnd.osgi.Analyzer;
import aQute.bnd.osgi.Clazz;
import aQute.bnd.osgi.Clazz.QUERY;
import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Descriptors.TypeRef;
import aQute.bnd.osgi.Processor;
import aQute.bnd.osgi.Resource;
import aQute.bnd.osgi.Verifier;
import aQute.bnd.service.AnalyzerPlugin;
import aQute.lib.tag.Tag;
/**
* This class is an analyzer plugin. It looks at the properties and tries to
* find out if the Service-Component header contains the bnd shortut syntax. If
* not, the header is copied to the output, if it does, an XML file is created
* and added to the JAR and the header is modified appropriately.
*/
public class ServiceComponent implements AnalyzerPlugin {
public boolean analyzeJar(Analyzer analyzer) throws Exception {
ComponentMaker m = new ComponentMaker(analyzer);
Map> l = m.doServiceComponent();
analyzer.setProperty(Constants.SERVICE_COMPONENT, Processor.printClauses(l));
analyzer.getInfo(m, Constants.SERVICE_COMPONENT + ": ");
m.close();
return false;
}
private static class ComponentMaker extends Processor {
Analyzer analyzer;
ComponentMaker(Analyzer analyzer) {
super(analyzer);
this.analyzer = analyzer;
}
/**
* Iterate over the Service Component entries. There are two cases:
*
* - An XML file reference
* - A FQN/wildcard with a set of attributes
*
* An XML reference is immediately expanded, an FQN/wildcard is more
* complicated and is delegated to
* {@link #componentEntry(Map, String, Map)}.
*
* @throws Exception
*/
Map> doServiceComponent() throws Exception {
Map> serviceComponents = newMap();
String header = getProperty(SERVICE_COMPONENT);
Parameters sc = parseHeader(header);
for (Entry entry : sc.entrySet()) {
String name = entry.getKey();
Map info = entry.getValue();
try {
if (name.indexOf('/') >= 0 || name.endsWith(".xml")) {
// Normal service component, we do not process it
serviceComponents.put(name, EMPTY);
} else {
componentEntry(serviceComponents, name, info);
}
} catch (Exception e) {
e.printStackTrace();
error("Invalid " + Constants.SERVICE_COMPONENT + " header: %s %s, throws %s", name, info, e);
throw e;
}
}
return serviceComponents;
}
/**
* Parse an entry in the Service-Component header. This header supports
* the following types:
*
* - An FQN + attributes describing a component
* - A wildcard expression for finding annotated components.
*
* The problem is the distinction between an FQN and a wildcard because
* an FQN can also be used as a wildcard. If the info specifies
* {@link Constants#NOANNOTATIONS} then wildcards are an error and the
* component must be fully described by the info. Otherwise the
* FQN/wildcard is expanded into a list of classes with annotations. If
* this list is empty, the FQN case is interpreted as a complete
* component definition. For the wildcard case, it is checked if any
* matching classes for the wildcard have been compiled for a class file
* format that does not support annotations, this can be a problem with
* JSR14 who silently ignores annotations. An error is reported in such
* a case.
*
* @param serviceComponents
* @param name
* @param info
* @throws Exception
* @throws IOException
*/
private void componentEntry(Map> serviceComponents, String name,
Map info) throws Exception, IOException {
boolean annotations = !Processor.isTrue(info.get(NOANNOTATIONS));
boolean fqn = Verifier.isFQN(name);
if (annotations) {
// Annotations possible!
Collection annotatedComponents = analyzer.getClasses("", QUERY.ANNOTATED.toString(),
"aQute.bnd.annotation.component.Component", //
QUERY.NAMED.toString(), name //
);
if (fqn) {
if (annotatedComponents.isEmpty()) {
// No annotations, fully specified in header
createComponentResource(serviceComponents, name, info);
} else {
// We had a FQN so expect only one
for (Clazz c : annotatedComponents) {
annotated(serviceComponents, c, info);
}
}
} else {
// We did not have an FQN, so expect the use of wildcards
if (annotatedComponents.isEmpty())
checkAnnotationsFeasible(name);
else
for (Clazz c : annotatedComponents) {
annotated(serviceComponents, c, info);
}
}
} else {
// No annotations
if (fqn)
createComponentResource(serviceComponents, name, info);
else
error("Set to %s but entry %s is not an FQN ", NOANNOTATIONS, name);
}
}
/**
* Check if annotations are actually feasible looking at the class
* format. If the class format does not provide annotations then it is
* no use specifying annotated components.
*
* @param name
* @throws Exception
*/
private Collection checkAnnotationsFeasible(String name) throws Exception {
Collection not = analyzer.getClasses("", QUERY.NAMED.toString(), name //
);
if (not.isEmpty()) {
if ("*".equals(name))
return not;
error("Specified %s but could not find any class matching this pattern", name);
}
for (Clazz c : not) {
if (c.getFormat().hasAnnotations())
return not;
}
warning("Wildcards are used (%s) requiring annotations to decide what is a component. Wildcard maps to classes that are compiled with java.target < 1.5. Annotations were introduced in Java 1.5",
name);
return not;
}
void annotated(Map> components, Clazz c, Map info) throws Exception {
analyzer.warning(
"%s annotation used in class %s. Bnd DS annotations are deprecated as of Bnd 3.2 and support will be removed in Bnd 4.0. Please change to use OSGi DS annotations.",
"aQute.bnd.annotation.component.Component", c);
// Get the component definition
// from the annotations
Map map = ComponentAnnotationReader.getDefinition(c, this);
// Pick the name, the annotation can override
// the name.
String localname = map.get(COMPONENT_NAME);
if (localname == null)
localname = c.getFQN();
// Override the component info without manifest
// entries. We merge the properties though.
String merged = Processor.merge(info.remove(COMPONENT_PROPERTIES), map.remove(COMPONENT_PROPERTIES));
if (merged != null && merged.length() > 0)
map.put(COMPONENT_PROPERTIES, merged);
map.putAll(info);
createComponentResource(components, localname, map);
}
private void createComponentResource(Map> components, String name,
Map info) throws Exception {
// We can override the name in the parameters
if (info.containsKey(COMPONENT_NAME))
name = info.get(COMPONENT_NAME);
// Assume the impl==name, but allow override
String impl = name;
if (info.containsKey(COMPONENT_IMPLEMENTATION))
impl = info.get(COMPONENT_IMPLEMENTATION);
TypeRef implRef = analyzer.getTypeRefFromFQN(impl);
// Check if such a class exists
analyzer.referTo(implRef);
boolean designate = designate(name, info.get(COMPONENT_DESIGNATE), false)
|| designate(name, info.get(COMPONENT_DESIGNATEFACTORY), true);
// If we had a designate, we want a default configuration policy of
// require.
if (designate && info.get(COMPONENT_CONFIGURATION_POLICY) == null)
info.put(COMPONENT_CONFIGURATION_POLICY, "require");
// We have a definition, so make an XML resources
Resource resource = createComponentResource(name, impl, info);
String pathSegment = analyzer.validResourcePath(name, "Invalid component name");
analyzer.getJar().putResource("OSGI-INF/" + pathSegment + ".xml", resource);
components.put("OSGI-INF/" + pathSegment + ".xml", EMPTY);
}
/**
* Create a Metatype and Designate record out of the given
* configurations.
*
* @param name
* @param config
* @throws Exception
*/
private boolean designate(String name, String config, boolean factory) throws Exception {
if (config == null)
return false;
for (String c : Processor.split(config)) {
TypeRef ref = analyzer.getTypeRefFromFQN(c);
Clazz clazz = analyzer.findClass(ref);
if (clazz != null) {
analyzer.referTo(ref);
MetaTypeReader r = new MetaTypeReader(clazz, analyzer);
r.setDesignate(name, factory);
String rname = "OSGI-INF/metatype/" + name + ".xml";
analyzer.getJar().putResource(rname, r);
} else {
analyzer.error("Cannot find designated configuration class %s for component %s", c, name);
}
}
return true;
}
/**
* Create the resource for a DS component.
*
* @param list
* @param name
* @param info
* @throws UnsupportedEncodingException
*/
Resource createComponentResource(String name, String impl, Map info) throws Exception {
HeaderReader hr = new HeaderReader(analyzer);
Tag tag = hr.createComponentTag(name, impl, info);
hr.close();
return new TagResource(tag);
}
}
}