| /* |
| * 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"; |
| |
| /** |
| * 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.getBsn()); |
| |
| 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 ArrayList<URL> path = new ArrayList<URL>(); |
| for (final Jar j : a.getClasspath()) { |
| path.add(j.getSource().toURI().toURL()); |
| } |
| log.info("Using claspath: " + 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; |
| } |
| } |