blob: 10ef4f78ae54979ae45e3720361b9a3ad7f5c64e [file] [log] [blame]
Pierre De Rop96c881f2013-07-06 08:35:29 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package org.apache.felix.scrplugin.bnd;
20
21import java.io.BufferedInputStream;
22import java.io.ByteArrayOutputStream;
23import java.io.File;
24import java.io.FileInputStream;
25import java.io.IOException;
26import java.io.InputStream;
27import java.net.URL;
28import java.net.URLClassLoader;
29import java.util.ArrayList;
30import java.util.Collection;
31import java.util.HashMap;
Pierre De Rop63991772015-03-26 14:15:06 +000032import java.util.HashSet;
33import java.util.Iterator;
Pierre De Rop96c881f2013-07-06 08:35:29 +000034import java.util.List;
35import java.util.Map;
Pierre De Rop63991772015-03-26 14:15:06 +000036import java.util.Set;
Pierre De Rop96c881f2013-07-06 08:35:29 +000037
38import org.apache.felix.scrplugin.Options;
39import org.apache.felix.scrplugin.Result;
40import org.apache.felix.scrplugin.SCRDescriptorGenerator;
41import org.apache.felix.scrplugin.Source;
42import org.apache.felix.scrplugin.SpecVersion;
43
44import aQute.bnd.osgi.Analyzer;
45import aQute.bnd.osgi.Clazz;
46import aQute.bnd.osgi.Clazz.QUERY;
47import aQute.bnd.osgi.EmbeddedResource;
48import aQute.bnd.osgi.Jar;
49import aQute.bnd.service.AnalyzerPlugin;
50import aQute.bnd.service.Plugin;
51import aQute.service.reporter.Reporter;
52
53/**
54 * The <code>SCRDescriptorBndPlugin</code> class is a <code>bnd</code> analyzer
55 * plugin which generates a service descriptor file based on annotations found
56 * in the sources.
Carsten Ziegeler158cc822013-07-17 13:50:17 +000057 *
Pierre De Rop96c881f2013-07-06 08:35:29 +000058 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
59 */
60public class SCRDescriptorBndPlugin implements AnalyzerPlugin, Plugin {
61 /**
62 * "destdir" parameter, optionally provided in the "-plugin" directive.
63 */
64 private static final String DESTDIR = "destdir";
65
66 /**
Pierre De Rop96c881f2013-07-06 08:35:29 +000067 * "generateAccessors" parameter, optionally provided in the "-plugin"
68 * directive.
69 */
70 private static final String GENERATE_ACCESSOR = "generateAccessors";
71
72 /**
73 * "strictMode" parameter, optionally provided in the "-plugin" directive.
74 */
75 private static final String STRICT_MODE = "strictMode";
76
77 /**
78 * "specVersion" parameter, optionally provided in the "-plugin" directive.
79 */
80 private static final String SPECVERSION = "specVersion";
81
82 /**
Pierre De Rop96c881f2013-07-06 08:35:29 +000083 * "log" parameter, which may be provided in the "-plugin" directive.
84 */
85 private static final String LOGLEVEL = "log";
86
Pierre De Rop8052ff92016-01-26 00:06:23 +000087 /**
88 * "logToFile" parameter, which may be set to false to suppress writing log of plugin action additionally to temp dir. Default: true.
89 */
90 private static final String LOGTOFILE = "logToFile";
91
Pierre De Rop96c881f2013-07-06 08:35:29 +000092 /**
93 * The name of the directory where the descriptor files are generated into.
94 */
95 private File destDir;
96
97 /**
98 * Object allowing to log debug messages using bnd reporter object.
99 */
100 private BndLog log;
101
102 /**
Pierre De Rop96c881f2013-07-06 08:35:29 +0000103 * This flag controls the generation of the bind/unbind methods.
104 */
105 private boolean generateAccessor = true;
106
107 /**
108 * In strict mode the plugin even fails on warnings.
109 */
110 private boolean strictMode = false;
111
112 /**
113 * The version of the DS spec this plugin generates a descriptor for. By
114 * default the version is detected by the used tags.
115 */
116 private SpecVersion specVersion;
117
118 /**
Pierre De Rop96c881f2013-07-06 08:35:29 +0000119 * Bnd plugin properties.
120 */
121 private Map<String, String> properties;
122
123 /**
124 * Object used to report logs to bnd.
125 */
126 private Reporter reporter;
127
128 /**
129 * Sets the reporter for logging into the bnd logger.
130 */
131 public void setReporter(Reporter reporter) {
132 this.reporter = reporter;
133 }
134
135 /**
136 * Sets properties which can be specified in the "-plugin" directive. For
137 * example: -plugin
138 * org.apache.felix.scrplugin.bnd.SCRDescriptorBndPlugin;destdir
139 * =target/classes
140 */
141 public void setProperties(Map<String, String> map) {
142 this.properties = map;
143 }
144
145 /**
146 * Scan scr or ds annotation from the target jar.
147 */
148 public boolean analyzeJar(Analyzer analyzer) throws Exception {
Pierre De Rop8052ff92016-01-26 00:06:23 +0000149 this.log = new BndLog(reporter, analyzer,
150 parseOption(properties, LOGTOFILE, false));
Pierre De Rop96c881f2013-07-06 08:35:29 +0000151
152 try {
153 init(analyzer);
154
155 log.info("Analyzing " + analyzer.getBsn());
156 final org.apache.felix.scrplugin.Project project = new org.apache.felix.scrplugin.Project();
157 project.setClassLoader(new URLClassLoader(getClassPath(analyzer),
158 this.getClass().getClassLoader()));
159 project.setDependencies(getDependencies(analyzer));
160 project.setSources(getClassFiles(analyzer));
161 project.setClassesDirectory(destDir.getAbsolutePath());
162
163 // create options
164 final Options options = new Options();
165 options.setOutputDirectory(destDir);
Pierre De Rop96c881f2013-07-06 08:35:29 +0000166 options.setGenerateAccessors(generateAccessor);
167 options.setStrictMode(strictMode);
168 options.setProperties(new HashMap<String, String>());
169 options.setSpecVersion(specVersion);
Pierre De Rop96c881f2013-07-06 08:35:29 +0000170
171 final SCRDescriptorGenerator generator = new SCRDescriptorGenerator(
172 log);
173
174 // setup from plugin configuration
175 generator.setOptions(options);
176 generator.setProject(project);
177
178 Result r = generator.execute();
179
180 // Embed scr descriptors in target jar
181 List<String> scrFiles = r.getScrFiles();
182 if (scrFiles != null) {
183 StringBuilder sb = new StringBuilder();
184 for (String scrFile : scrFiles) {
185 log.info("SCR descriptor result file: " + scrFile);
186 sb.append(scrFile);
187 sb.append(",");
188 putResource(analyzer, scrFile);
189 }
190 sb.setLength(sb.length() - 1);
Pierre De Ropa0f59442013-08-10 14:23:23 +0000191 addServiceComponentHeader(analyzer, sb.toString());
Pierre De Rop96c881f2013-07-06 08:35:29 +0000192 }
193
194 // Embed metatype descriptors in target jar
195 List<String> metaTypeFiles = r.getMetatypeFiles();
196 if (metaTypeFiles != null) {
197 for (String metaTypeFile : metaTypeFiles) {
198 log.info("Meta Type result file: " + metaTypeFile);
199 putResource(analyzer, metaTypeFile);
200 }
201 }
202 } catch (Throwable t) {
203 log.error("Got unexpected exception while analyzing",
204 t);
205 } finally {
206 log.close();
207 }
Pierre De Ropa0f59442013-08-10 14:23:23 +0000208 return false; // do not reanalyze bundle classpath because our plugin has not changed it.
209 }
210
211 private void addServiceComponentHeader(Analyzer analyzer, String components) {
Pierre De Rop63991772015-03-26 14:15:06 +0000212 Set<String> descriptorsSet = new HashSet<String>();
213 String oldComponents = analyzer.getProperty("Service-Component");
214 parseComponents(descriptorsSet, oldComponents);
215 parseComponents(descriptorsSet, components);
216
217 StringBuilder sb = new StringBuilder();
218 Iterator<String> it = descriptorsSet.iterator();
219 while (it.hasNext()) {
220 sb.append(it.next());
221 if (it.hasNext()) {
222 sb.append(",");
223 }
Pierre De Ropa0f59442013-08-10 14:23:23 +0000224 }
Pierre De Rop63991772015-03-26 14:15:06 +0000225 String comps = sb.toString();
226 log.info("Setting Service-Component header: " + comps);
227 analyzer.setProperty("Service-Component", comps);
228 }
229
230 private void parseComponents(Set<String> descriptorsSet, String components) {
231 if (components != null && components.length() > 0) {
232 for (String comp : components.split(",")) {
233 comp = comp.trim();
234 if (comp.length() > 0) {
235 descriptorsSet.add(comp);
236 }
237 }
238 }
Pierre De Rop96c881f2013-07-06 08:35:29 +0000239 }
240
241 private void init(Analyzer analyzer) {
242 this.log.setLevel(parseOption(properties, LOGLEVEL,
243 BndLog.Level.Warn.toString()));
244
245 String param = parseOption(properties, DESTDIR, new File(analyzer.getBase() + File.separator + "bin").getPath());
246 destDir = new File(param);
247 if (!destDir.exists() && !destDir.mkdirs()) {
248 throw new IllegalArgumentException("Could not create " + destDir
249 + " directory.");
250 }
251
Pierre De Rop96c881f2013-07-06 08:35:29 +0000252 generateAccessor = parseOption(properties, GENERATE_ACCESSOR,
253 generateAccessor);
254 strictMode = parseOption(properties, STRICT_MODE, strictMode);
255 String version = parseOption(properties, SPECVERSION, null);
256 specVersion = SpecVersion.fromName(version);
257 if (version != null && specVersion == null) {
258 throw new IllegalArgumentException(
259 "Unknown spec version specified: " + version);
260 }
Pierre De Rop96c881f2013-07-06 08:35:29 +0000261
262 if (log.isInfoEnabled()) {
263 log.info("Initialized Bnd ScrPlugin: destDir=" + destDir
Carsten Ziegeler158cc822013-07-17 13:50:17 +0000264 + ", strictMode=" + strictMode
265 + ", specVersion=" + specVersion);
Pierre De Rop96c881f2013-07-06 08:35:29 +0000266 }
267 }
268
269 private String parseOption(Map<String, String> opts, String name, String def) {
270 String value = opts.get(name);
271 return value == null ? def : value;
272 }
273
274 private boolean parseOption(Map<String, String> opts, String name,
275 boolean def) {
276 String value = opts.get(name);
277 return value == null ? def : Boolean.valueOf(value);
278 }
279
280 private Collection<Source> getClassFiles(Analyzer analyzer)
281 throws Exception {
282 ArrayList<Source> files = new ArrayList<Source>();
283 Collection<Clazz> expanded = analyzer.getClasses("",
284 QUERY.NAMED.toString(), "*");
285 for (final Clazz c : expanded) {
286 files.add(new Source() {
287 public File getFile() {
288 log.debug("Found class "
289 + c.getAbsolutePath());
290 return new File(c.getAbsolutePath());
291 }
292
293 public String getClassName() {
294 return c.getFQN();
295 }
296 });
297 }
298 return files;
299 }
300
301 private URL[] getClassPath(Analyzer a) throws Exception {
302 final ArrayList<URL> path = new ArrayList<URL>();
303 for (final Jar j : a.getClasspath()) {
304 path.add(j.getSource().toURI().toURL());
305 }
306 log.info("Using claspath: " + path);
307 return path.toArray(new URL[path.size()]);
308 }
309
310 private void putResource(Analyzer analyzer, String path) throws IOException {
311 ByteArrayOutputStream out = new ByteArrayOutputStream();
312 File f = new File(destDir, path);
313 InputStream in = new BufferedInputStream(new FileInputStream(f));
314 try {
315 int c;
316 while ((c = in.read()) != -1) {
317 out.write(c);
318 }
319 } finally {
320 in.close();
321 }
322 byte[] data = out.toByteArray();
323 analyzer.getJar().putResource(path, new EmbeddedResource(data, 0));
324 }
325
326 private List<File> getDependencies(Analyzer a) {
327 ArrayList<File> files = new ArrayList<File>();
328 for (final Jar j : a.getClasspath()) {
329 File file = j.getSource();
330 if (file.isFile()) {
331 files.add(file);
332 }
333 }
334 log.info("Using dependencies: " + files);
335 return files;
336 }
337}