blob: 38e50518c69f3a4f1bade4d1bb32e5b63d0aa324 [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.bnd;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.felix.scrplugin.Options;
import org.apache.felix.scrplugin.Result;
import org.apache.felix.scrplugin.SCRDescriptorGenerator;
import org.apache.felix.scrplugin.Source;
import org.apache.felix.scrplugin.SpecVersion;
import aQute.bnd.osgi.Analyzer;
import aQute.bnd.osgi.Clazz;
import aQute.bnd.osgi.Clazz.QUERY;
import aQute.bnd.osgi.EmbeddedResource;
import aQute.bnd.osgi.Jar;
import aQute.bnd.service.AnalyzerPlugin;
import aQute.bnd.service.Plugin;
import aQute.service.reporter.Reporter;
/**
* The <code>SCRDescriptorBndPlugin</code> class is a <code>bnd</code> analyzer
* plugin which generates a service descriptor file based on annotations found
* in the sources.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class SCRDescriptorBndPlugin implements AnalyzerPlugin, Plugin {
/**
* "destdir" parameter, optionally provided in the "-plugin" directive.
*/
private static final String DESTDIR = "destdir";
/**
* "generateAccessors" parameter, optionally provided in the "-plugin"
* directive.
*/
private static final String GENERATE_ACCESSOR = "generateAccessors";
/**
* "strictMode" parameter, optionally provided in the "-plugin" directive.
*/
private static final String STRICT_MODE = "strictMode";
/**
* "specVersion" parameter, optionally provided in the "-plugin" directive.
*/
private static final String SPECVERSION = "specVersion";
/**
* "log" parameter, which may be provided in the "-plugin" directive.
*/
private static final String LOGLEVEL = "log";
/**
* "logToFile" parameter, which may be set to false to suppress writing log of plugin action additionally to temp dir. Default: true.
*/
private static final String LOGTOFILE = "logToFile";
/**
* The name of the directory where the descriptor files are generated into.
*/
private File destDir;
/**
* Object allowing to log debug messages using bnd reporter object.
*/
private BndLog log;
/**
* This flag controls the generation of the bind/unbind methods.
*/
private boolean generateAccessor = true;
/**
* In strict mode the plugin even fails on warnings.
*/
private boolean strictMode = false;
/**
* The version of the DS spec this plugin generates a descriptor for. By
* default the version is detected by the used tags.
*/
private SpecVersion specVersion;
/**
* Bnd plugin properties.
*/
private Map<String, String> properties;
/**
* Object used to report logs to bnd.
*/
private Reporter reporter;
/**
* Sets the reporter for logging into the bnd logger.
*/
public void setReporter(Reporter reporter) {
this.reporter = reporter;
}
/**
* Sets properties which can be specified in the "-plugin" directive. For
* example: -plugin
* org.apache.felix.scrplugin.bnd.SCRDescriptorBndPlugin;destdir
* =target/classes
*/
public void setProperties(Map<String, String> map) {
this.properties = map;
}
/**
* Scan scr or ds annotation from the target jar.
*/
public boolean analyzeJar(Analyzer analyzer) throws Exception {
this.log = new BndLog(reporter, analyzer,
parseOption(properties, LOGTOFILE, false));
try {
init(analyzer);
log.info("Analyzing " + analyzer.getBsn());
final org.apache.felix.scrplugin.Project project = new org.apache.felix.scrplugin.Project();
project.setClassLoader(new URLClassLoader(getClassPath(analyzer),
this.getClass().getClassLoader()));
project.setDependencies(getDependencies(analyzer));
project.setSources(getClassFiles(analyzer));
project.setClassesDirectory(destDir.getAbsolutePath());
// create options
final Options options = new Options();
options.setOutputDirectory(destDir);
options.setGenerateAccessors(generateAccessor);
options.setStrictMode(strictMode);
options.setProperties(new HashMap<String, String>());
options.setSpecVersion(specVersion);
final SCRDescriptorGenerator generator = new SCRDescriptorGenerator(
log);
// setup from plugin configuration
generator.setOptions(options);
generator.setProject(project);
Result r = generator.execute();
// Embed scr descriptors in target jar
List<String> scrFiles = r.getScrFiles();
if (scrFiles != null) {
StringBuilder sb = new StringBuilder();
for (String scrFile : scrFiles) {
log.info("SCR descriptor result file: " + scrFile);
sb.append(scrFile);
sb.append(",");
putResource(analyzer, scrFile);
}
sb.setLength(sb.length() - 1);
addServiceComponentHeader(analyzer, sb.toString());
}
// Embed metatype descriptors in target jar
List<String> metaTypeFiles = r.getMetatypeFiles();
if (metaTypeFiles != null) {
for (String metaTypeFile : metaTypeFiles) {
log.info("Meta Type result file: " + metaTypeFile);
putResource(analyzer, metaTypeFile);
}
}
} catch (Throwable t) {
log.error("Got unexpected exception while analyzing",
t);
} finally {
log.close();
}
return false; // do not reanalyze bundle classpath because our plugin has not changed it.
}
private void addServiceComponentHeader(Analyzer analyzer, String components) {
Set<String> descriptorsSet = new HashSet<String>();
String oldComponents = analyzer.getProperty("Service-Component");
parseComponents(descriptorsSet, oldComponents);
parseComponents(descriptorsSet, components);
StringBuilder sb = new StringBuilder();
Iterator<String> it = descriptorsSet.iterator();
while (it.hasNext()) {
sb.append(it.next());
if (it.hasNext()) {
sb.append(",");
}
}
String comps = sb.toString();
log.info("Setting Service-Component header: " + comps);
analyzer.setProperty("Service-Component", comps);
}
private void parseComponents(Set<String> descriptorsSet, String components) {
if (components != null && components.length() > 0) {
for (String comp : components.split(",")) {
comp = comp.trim();
if (comp.length() > 0) {
descriptorsSet.add(comp);
}
}
}
}
private void init(Analyzer analyzer) {
this.log.setLevel(parseOption(properties, LOGLEVEL,
BndLog.Level.Warn.toString()));
String param = parseOption(properties, DESTDIR, new File(analyzer.getBase() + File.separator + "bin").getPath());
destDir = new File(param);
if (!destDir.exists() && !destDir.mkdirs()) {
throw new IllegalArgumentException("Could not create " + destDir
+ " directory.");
}
generateAccessor = parseOption(properties, GENERATE_ACCESSOR,
generateAccessor);
strictMode = parseOption(properties, STRICT_MODE, strictMode);
String version = parseOption(properties, SPECVERSION, null);
specVersion = SpecVersion.fromName(version);
if (version != null && specVersion == null) {
throw new IllegalArgumentException(
"Unknown spec version specified: " + version);
}
if (log.isInfoEnabled()) {
log.info("Initialized Bnd ScrPlugin: destDir=" + destDir
+ ", strictMode=" + strictMode
+ ", specVersion=" + specVersion);
}
}
private String parseOption(Map<String, String> opts, String name, String def) {
String value = opts.get(name);
return value == null ? def : value;
}
private boolean parseOption(Map<String, String> opts, String name,
boolean def) {
String value = opts.get(name);
return value == null ? def : Boolean.valueOf(value);
}
private Collection<Source> getClassFiles(Analyzer analyzer)
throws Exception {
ArrayList<Source> files = new ArrayList<Source>();
Collection<Clazz> expanded = analyzer.getClasses("",
QUERY.NAMED.toString(), "*");
for (final Clazz c : expanded) {
files.add(new Source() {
public File getFile() {
log.debug("Found class "
+ c.getAbsolutePath());
return new File(c.getAbsolutePath());
}
public String getClassName() {
return c.getFQN();
}
});
}
return files;
}
private URL[] getClassPath(Analyzer a) throws Exception {
final Set<URL> path = new LinkedHashSet<URL>();
for (final Jar j : a.getClasspath()) {
path.add(j.getSource().toURI().toURL());
}
// always add the target url (containing all compiled classes) because it it not necessarily part of the analyzer classpath
if (a.getTarget() != null && a.getTarget().getSource() != null) {
path.add(a.getTarget().getSource().toURI().toURL());
}
log.info("Using classpath: " + path);
return path.toArray(new URL[path.size()]);
}
private void putResource(Analyzer analyzer, String path) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
File f = new File(destDir, path);
InputStream in = new BufferedInputStream(new FileInputStream(f));
try {
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
} finally {
in.close();
}
byte[] data = out.toByteArray();
analyzer.getJar().putResource(path, new EmbeddedResource(data, 0));
}
private List<File> getDependencies(Analyzer a) {
ArrayList<File> files = new ArrayList<File>();
for (final Jar j : a.getClasspath()) {
File file = j.getSource();
if (file.isFile()) {
files.add(file);
}
}
log.info("Using dependencies: " + files);
return files;
}
}