/* * 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.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 HashSet<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; } }