Pierre De Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 1 | /* |
| 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 | */ |
| 19 | package org.apache.felix.scrplugin.bnd; |
| 20 | |
| 21 | import java.io.BufferedInputStream; |
| 22 | import java.io.ByteArrayOutputStream; |
| 23 | import java.io.File; |
| 24 | import java.io.FileInputStream; |
| 25 | import java.io.IOException; |
| 26 | import java.io.InputStream; |
| 27 | import java.net.URL; |
| 28 | import java.net.URLClassLoader; |
| 29 | import java.util.ArrayList; |
| 30 | import java.util.Collection; |
| 31 | import java.util.HashMap; |
Pierre De Rop | 6399177 | 2015-03-26 14:15:06 +0000 | [diff] [blame] | 32 | import java.util.HashSet; |
| 33 | import java.util.Iterator; |
Pierre De Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 34 | import java.util.List; |
| 35 | import java.util.Map; |
Pierre De Rop | 6399177 | 2015-03-26 14:15:06 +0000 | [diff] [blame] | 36 | import java.util.Set; |
Pierre De Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 37 | |
| 38 | import org.apache.felix.scrplugin.Options; |
| 39 | import org.apache.felix.scrplugin.Result; |
| 40 | import org.apache.felix.scrplugin.SCRDescriptorGenerator; |
| 41 | import org.apache.felix.scrplugin.Source; |
| 42 | import org.apache.felix.scrplugin.SpecVersion; |
| 43 | |
| 44 | import aQute.bnd.osgi.Analyzer; |
| 45 | import aQute.bnd.osgi.Clazz; |
| 46 | import aQute.bnd.osgi.Clazz.QUERY; |
| 47 | import aQute.bnd.osgi.EmbeddedResource; |
| 48 | import aQute.bnd.osgi.Jar; |
| 49 | import aQute.bnd.service.AnalyzerPlugin; |
| 50 | import aQute.bnd.service.Plugin; |
| 51 | import 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 Ziegeler | 158cc82 | 2013-07-17 13:50:17 +0000 | [diff] [blame] | 57 | * |
Pierre De Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 58 | * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> |
| 59 | */ |
| 60 | public 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 Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 67 | * "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 Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 83 | * "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 Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 98 | * 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 Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 114 | * 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 Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 160 | options.setGenerateAccessors(generateAccessor); |
| 161 | options.setStrictMode(strictMode); |
| 162 | options.setProperties(new HashMap<String, String>()); |
| 163 | options.setSpecVersion(specVersion); |
Pierre De Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 164 | |
| 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 Rop | a0f5944 | 2013-08-10 14:23:23 +0000 | [diff] [blame] | 185 | addServiceComponentHeader(analyzer, sb.toString()); |
Pierre De Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 186 | } |
| 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 Rop | a0f5944 | 2013-08-10 14:23:23 +0000 | [diff] [blame] | 202 | 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 Rop | 6399177 | 2015-03-26 14:15:06 +0000 | [diff] [blame] | 206 | 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 Rop | a0f5944 | 2013-08-10 14:23:23 +0000 | [diff] [blame] | 218 | } |
Pierre De Rop | 6399177 | 2015-03-26 14:15:06 +0000 | [diff] [blame] | 219 | 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 Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 233 | } |
| 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 Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 246 | 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 Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 255 | |
| 256 | if (log.isInfoEnabled()) { |
| 257 | log.info("Initialized Bnd ScrPlugin: destDir=" + destDir |
Carsten Ziegeler | 158cc82 | 2013-07-17 13:50:17 +0000 | [diff] [blame] | 258 | + ", strictMode=" + strictMode |
| 259 | + ", specVersion=" + specVersion); |
Pierre De Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 260 | } |
| 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 | } |