blob: aab84d9153d575196ed3561da1a5441bc4f06a9a [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
87 /**
88 * The name of the directory where the descriptor files are generated into.
89 */
90 private File destDir;
91
92 /**
93 * Object allowing to log debug messages using bnd reporter object.
94 */
95 private BndLog log;
96
97 /**
Pierre De Rop96c881f2013-07-06 08:35:29 +000098 * This flag controls the generation of the bind/unbind methods.
99 */
100 private boolean generateAccessor = true;
101
102 /**
103 * In strict mode the plugin even fails on warnings.
104 */
105 private boolean strictMode = false;
106
107 /**
108 * The version of the DS spec this plugin generates a descriptor for. By
109 * default the version is detected by the used tags.
110 */
111 private SpecVersion specVersion;
112
113 /**
Pierre De Rop96c881f2013-07-06 08:35:29 +0000114 * Bnd plugin properties.
115 */
116 private Map<String, String> properties;
117
118 /**
119 * Object used to report logs to bnd.
120 */
121 private Reporter reporter;
122
123 /**
124 * Sets the reporter for logging into the bnd logger.
125 */
126 public void setReporter(Reporter reporter) {
127 this.reporter = reporter;
128 }
129
130 /**
131 * Sets properties which can be specified in the "-plugin" directive. For
132 * example: -plugin
133 * org.apache.felix.scrplugin.bnd.SCRDescriptorBndPlugin;destdir
134 * =target/classes
135 */
136 public void setProperties(Map<String, String> map) {
137 this.properties = map;
138 }
139
140 /**
141 * Scan scr or ds annotation from the target jar.
142 */
143 public boolean analyzeJar(Analyzer analyzer) throws Exception {
144 this.log = new BndLog(reporter, analyzer.getBsn());
145
146 try {
147 init(analyzer);
148
149 log.info("Analyzing " + analyzer.getBsn());
150 final org.apache.felix.scrplugin.Project project = new org.apache.felix.scrplugin.Project();
151 project.setClassLoader(new URLClassLoader(getClassPath(analyzer),
152 this.getClass().getClassLoader()));
153 project.setDependencies(getDependencies(analyzer));
154 project.setSources(getClassFiles(analyzer));
155 project.setClassesDirectory(destDir.getAbsolutePath());
156
157 // create options
158 final Options options = new Options();
159 options.setOutputDirectory(destDir);
Pierre De Rop96c881f2013-07-06 08:35:29 +0000160 options.setGenerateAccessors(generateAccessor);
161 options.setStrictMode(strictMode);
162 options.setProperties(new HashMap<String, String>());
163 options.setSpecVersion(specVersion);
Pierre De Rop96c881f2013-07-06 08:35:29 +0000164
165 final SCRDescriptorGenerator generator = new SCRDescriptorGenerator(
166 log);
167
168 // setup from plugin configuration
169 generator.setOptions(options);
170 generator.setProject(project);
171
172 Result r = generator.execute();
173
174 // Embed scr descriptors in target jar
175 List<String> scrFiles = r.getScrFiles();
176 if (scrFiles != null) {
177 StringBuilder sb = new StringBuilder();
178 for (String scrFile : scrFiles) {
179 log.info("SCR descriptor result file: " + scrFile);
180 sb.append(scrFile);
181 sb.append(",");
182 putResource(analyzer, scrFile);
183 }
184 sb.setLength(sb.length() - 1);
Pierre De Ropa0f59442013-08-10 14:23:23 +0000185 addServiceComponentHeader(analyzer, sb.toString());
Pierre De Rop96c881f2013-07-06 08:35:29 +0000186 }
187
188 // Embed metatype descriptors in target jar
189 List<String> metaTypeFiles = r.getMetatypeFiles();
190 if (metaTypeFiles != null) {
191 for (String metaTypeFile : metaTypeFiles) {
192 log.info("Meta Type result file: " + metaTypeFile);
193 putResource(analyzer, metaTypeFile);
194 }
195 }
196 } catch (Throwable t) {
197 log.error("Got unexpected exception while analyzing",
198 t);
199 } finally {
200 log.close();
201 }
Pierre De Ropa0f59442013-08-10 14:23:23 +0000202 return false; // do not reanalyze bundle classpath because our plugin has not changed it.
203 }
204
205 private void addServiceComponentHeader(Analyzer analyzer, String components) {
Pierre De Rop63991772015-03-26 14:15:06 +0000206 Set<String> descriptorsSet = new HashSet<String>();
207 String oldComponents = analyzer.getProperty("Service-Component");
208 parseComponents(descriptorsSet, oldComponents);
209 parseComponents(descriptorsSet, components);
210
211 StringBuilder sb = new StringBuilder();
212 Iterator<String> it = descriptorsSet.iterator();
213 while (it.hasNext()) {
214 sb.append(it.next());
215 if (it.hasNext()) {
216 sb.append(",");
217 }
Pierre De Ropa0f59442013-08-10 14:23:23 +0000218 }
Pierre De Rop63991772015-03-26 14:15:06 +0000219 String comps = sb.toString();
220 log.info("Setting Service-Component header: " + comps);
221 analyzer.setProperty("Service-Component", comps);
222 }
223
224 private void parseComponents(Set<String> descriptorsSet, String components) {
225 if (components != null && components.length() > 0) {
226 for (String comp : components.split(",")) {
227 comp = comp.trim();
228 if (comp.length() > 0) {
229 descriptorsSet.add(comp);
230 }
231 }
232 }
Pierre De Rop96c881f2013-07-06 08:35:29 +0000233 }
234
235 private void init(Analyzer analyzer) {
236 this.log.setLevel(parseOption(properties, LOGLEVEL,
237 BndLog.Level.Warn.toString()));
238
239 String param = parseOption(properties, DESTDIR, new File(analyzer.getBase() + File.separator + "bin").getPath());
240 destDir = new File(param);
241 if (!destDir.exists() && !destDir.mkdirs()) {
242 throw new IllegalArgumentException("Could not create " + destDir
243 + " directory.");
244 }
245
Pierre De Rop96c881f2013-07-06 08:35:29 +0000246 generateAccessor = parseOption(properties, GENERATE_ACCESSOR,
247 generateAccessor);
248 strictMode = parseOption(properties, STRICT_MODE, strictMode);
249 String version = parseOption(properties, SPECVERSION, null);
250 specVersion = SpecVersion.fromName(version);
251 if (version != null && specVersion == null) {
252 throw new IllegalArgumentException(
253 "Unknown spec version specified: " + version);
254 }
Pierre De Rop96c881f2013-07-06 08:35:29 +0000255
256 if (log.isInfoEnabled()) {
257 log.info("Initialized Bnd ScrPlugin: destDir=" + destDir
Carsten Ziegeler158cc822013-07-17 13:50:17 +0000258 + ", strictMode=" + strictMode
259 + ", specVersion=" + specVersion);
Pierre De Rop96c881f2013-07-06 08:35:29 +0000260 }
261 }
262
263 private String parseOption(Map<String, String> opts, String name, String def) {
264 String value = opts.get(name);
265 return value == null ? def : value;
266 }
267
268 private boolean parseOption(Map<String, String> opts, String name,
269 boolean def) {
270 String value = opts.get(name);
271 return value == null ? def : Boolean.valueOf(value);
272 }
273
274 private Collection<Source> getClassFiles(Analyzer analyzer)
275 throws Exception {
276 ArrayList<Source> files = new ArrayList<Source>();
277 Collection<Clazz> expanded = analyzer.getClasses("",
278 QUERY.NAMED.toString(), "*");
279 for (final Clazz c : expanded) {
280 files.add(new Source() {
281 public File getFile() {
282 log.debug("Found class "
283 + c.getAbsolutePath());
284 return new File(c.getAbsolutePath());
285 }
286
287 public String getClassName() {
288 return c.getFQN();
289 }
290 });
291 }
292 return files;
293 }
294
295 private URL[] getClassPath(Analyzer a) throws Exception {
296 final ArrayList<URL> path = new ArrayList<URL>();
297 for (final Jar j : a.getClasspath()) {
298 path.add(j.getSource().toURI().toURL());
299 }
300 log.info("Using claspath: " + path);
301 return path.toArray(new URL[path.size()]);
302 }
303
304 private void putResource(Analyzer analyzer, String path) throws IOException {
305 ByteArrayOutputStream out = new ByteArrayOutputStream();
306 File f = new File(destDir, path);
307 InputStream in = new BufferedInputStream(new FileInputStream(f));
308 try {
309 int c;
310 while ((c = in.read()) != -1) {
311 out.write(c);
312 }
313 } finally {
314 in.close();
315 }
316 byte[] data = out.toByteArray();
317 analyzer.getJar().putResource(path, new EmbeddedResource(data, 0));
318 }
319
320 private List<File> getDependencies(Analyzer a) {
321 ArrayList<File> files = new ArrayList<File>();
322 for (final Jar j : a.getClasspath()) {
323 File file = j.getSource();
324 if (file.isFile()) {
325 files.add(file);
326 }
327 }
328 log.info("Using dependencies: " + files);
329 return files;
330 }
331}