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 | |
Pierre De Rop | 8052ff9 | 2016-01-26 00:06:23 +0000 | [diff] [blame^] | 87 | /** |
| 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 Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 92 | /** |
| 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 Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 103 | * 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 Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 119 | * 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 Rop | 8052ff9 | 2016-01-26 00:06:23 +0000 | [diff] [blame^] | 149 | this.log = new BndLog(reporter, analyzer, |
| 150 | parseOption(properties, LOGTOFILE, false)); |
Pierre De Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 151 | |
| 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 Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 166 | options.setGenerateAccessors(generateAccessor); |
| 167 | options.setStrictMode(strictMode); |
| 168 | options.setProperties(new HashMap<String, String>()); |
| 169 | options.setSpecVersion(specVersion); |
Pierre De Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 170 | |
| 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 Rop | a0f5944 | 2013-08-10 14:23:23 +0000 | [diff] [blame] | 191 | addServiceComponentHeader(analyzer, sb.toString()); |
Pierre De Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 192 | } |
| 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 Rop | a0f5944 | 2013-08-10 14:23:23 +0000 | [diff] [blame] | 208 | 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 Rop | 6399177 | 2015-03-26 14:15:06 +0000 | [diff] [blame] | 212 | 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 Rop | a0f5944 | 2013-08-10 14:23:23 +0000 | [diff] [blame] | 224 | } |
Pierre De Rop | 6399177 | 2015-03-26 14:15:06 +0000 | [diff] [blame] | 225 | 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 Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 239 | } |
| 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 Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 252 | 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 Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 261 | |
| 262 | if (log.isInfoEnabled()) { |
| 263 | log.info("Initialized Bnd ScrPlugin: destDir=" + destDir |
Carsten Ziegeler | 158cc82 | 2013-07-17 13:50:17 +0000 | [diff] [blame] | 264 | + ", strictMode=" + strictMode |
| 265 | + ", specVersion=" + specVersion); |
Pierre De Rop | 96c881f | 2013-07-06 08:35:29 +0000 | [diff] [blame] | 266 | } |
| 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 | } |