]> git.argeo.org Git - gpl/argeo-slc.git/blob - runtime/org.argeo.slc.repo/src/main/java/org/argeo/slc/repo/JarFileIndexer.java
06e7129b48adc4f326909b6a130af7b3af98a53f
[gpl/argeo-slc.git] / runtime / org.argeo.slc.repo / src / main / java / org / argeo / slc / repo / JarFileIndexer.java
1 /*
2 * Copyright (C) 2007-2012 Argeo GmbH
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.argeo.slc.repo;
17
18 import java.io.ByteArrayInputStream;
19 import java.io.ByteArrayOutputStream;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.List;
23 import java.util.jar.Attributes;
24 import java.util.jar.Attributes.Name;
25 import java.util.jar.JarInputStream;
26 import java.util.jar.Manifest;
27
28 import javax.jcr.Binary;
29 import javax.jcr.Node;
30 import javax.jcr.NodeIterator;
31 import javax.jcr.Property;
32 import javax.jcr.RepositoryException;
33 import javax.jcr.Session;
34 import javax.jcr.nodetype.NodeType;
35
36 import org.apache.commons.io.FilenameUtils;
37 import org.apache.commons.io.IOUtils;
38 import org.apache.commons.logging.Log;
39 import org.apache.commons.logging.LogFactory;
40 import org.argeo.jcr.JcrUtils;
41 import org.argeo.slc.SlcException;
42 import org.argeo.slc.jcr.SlcNames;
43 import org.argeo.slc.jcr.SlcTypes;
44 import org.osgi.framework.Constants;
45 import org.osgi.framework.Version;
46
47 /**
48 * Indexes jar file, currently supports standard J2SE and OSGi metadata (both
49 * from MANIFEST)
50 */
51 public class JarFileIndexer implements NodeIndexer, SlcNames {
52 private final static Log log = LogFactory.getLog(JarFileIndexer.class);
53 private Boolean force = false;
54
55 public Boolean support(String path) {
56 return FilenameUtils.getExtension(path).equals("jar");
57 }
58
59 public void index(Node fileNode) {
60 Binary fileBinary = null;
61 JarInputStream jarIn = null;
62 ByteArrayOutputStream bo = null;
63 ByteArrayInputStream bi = null;
64 Binary manifestBinary = null;
65 try {
66 if (!support(fileNode.getPath()))
67 return;
68
69 // Already indexed
70 if (!force && fileNode.isNodeType(SlcTypes.SLC_JAR_FILE))
71 return;
72
73 if (!fileNode.isNodeType(NodeType.NT_FILE))
74 return;
75
76 Session jcrSession = fileNode.getSession();
77 Node contentNode = fileNode.getNode(Node.JCR_CONTENT);
78 fileBinary = contentNode.getProperty(Property.JCR_DATA).getBinary();
79 // jar file
80 // if (!FilenameUtils.isExtension(fileNode.getName(), "jar")) {
81 // return;
82 // }
83
84 jarIn = new JarInputStream(fileBinary.getStream());
85 Manifest manifest = jarIn.getManifest();
86 if (manifest == null) {
87 log.error(fileNode + " has no MANIFEST");
88 return;
89 }
90
91 bo = new ByteArrayOutputStream();
92 manifest.write(bo);
93 byte[] newManifest = bo.toByteArray();
94 if (fileNode.hasProperty(SLC_MANIFEST)) {
95 byte[] storedManifest = JcrUtils.getBinaryAsBytes(fileNode
96 .getProperty(SLC_MANIFEST));
97 if (Arrays.equals(newManifest, storedManifest)) {
98 if (log.isTraceEnabled())
99 log.trace("Manifest not changed, doing nothing "
100 + fileNode);
101 return;
102 }
103 }
104
105 bi = new ByteArrayInputStream(newManifest);
106 manifestBinary = jcrSession.getValueFactory().createBinary(bi);
107
108 // standard jar file
109 fileNode.addMixin(SlcTypes.SLC_JAR_FILE);
110
111 fileNode.setProperty(SlcNames.SLC_MANIFEST, manifestBinary);
112 Attributes attrs = manifest.getMainAttributes();
113
114 // if (log.isTraceEnabled())
115 // for (Object key : attrs.keySet())
116 // log.trace(key + ": " + attrs.getValue(key.toString()));
117
118 // standard J2SE MANIFEST attributes
119 addAttr(Attributes.Name.MANIFEST_VERSION, fileNode, attrs);
120 addAttr(Attributes.Name.SIGNATURE_VERSION, fileNode, attrs);
121 addAttr(Attributes.Name.CLASS_PATH, fileNode, attrs);
122 addAttr(Attributes.Name.MAIN_CLASS, fileNode, attrs);
123 addAttr(Attributes.Name.EXTENSION_NAME, fileNode, attrs);
124 addAttr(Attributes.Name.IMPLEMENTATION_VERSION, fileNode, attrs);
125 addAttr(Attributes.Name.IMPLEMENTATION_VENDOR, fileNode, attrs);
126 addAttr(Attributes.Name.IMPLEMENTATION_VENDOR_ID, fileNode, attrs);
127 addAttr(Attributes.Name.SPECIFICATION_TITLE, fileNode, attrs);
128 addAttr(Attributes.Name.SPECIFICATION_VERSION, fileNode, attrs);
129 addAttr(Attributes.Name.SPECIFICATION_VENDOR, fileNode, attrs);
130 addAttr(Attributes.Name.SEALED, fileNode, attrs);
131
132 // OSGi
133 if (attrs.containsKey(new Name(Constants.BUNDLE_SYMBOLICNAME))) {
134 addOsgiMetadata(fileNode, attrs);
135 JcrUtils.updateLastModified(fileNode);
136 if (log.isTraceEnabled())
137 log.trace("Indexed OSGi bundle " + fileNode);
138 } else {
139 JcrUtils.updateLastModified(fileNode);
140 if (log.isTraceEnabled())
141 log.trace("Indexed JAR file " + fileNode);
142 }
143
144 } catch (Exception e) {
145 throw new SlcException("Cannot index jar " + fileNode, e);
146 } finally {
147 IOUtils.closeQuietly(bi);
148 IOUtils.closeQuietly(bo);
149 IOUtils.closeQuietly(jarIn);
150 JcrUtils.closeQuietly(manifestBinary);
151 JcrUtils.closeQuietly(fileBinary);
152 }
153
154 }
155
156 protected void addOsgiMetadata(Node fileNode, Attributes attrs)
157 throws RepositoryException {
158 fileNode.addMixin(SlcTypes.SLC_BUNDLE_ARTIFACT);
159
160 // symbolic name
161 String symbolicName = attrs.getValue(Constants.BUNDLE_SYMBOLICNAME);
162 // make sure there is no directive
163 symbolicName = symbolicName.split(";")[0];
164 fileNode.setProperty(SlcNames.SLC_SYMBOLIC_NAME, symbolicName);
165
166 // direct mapping
167 addAttr(Constants.BUNDLE_SYMBOLICNAME, fileNode, attrs);
168 addAttr(Constants.BUNDLE_NAME, fileNode, attrs);
169 addAttr(Constants.BUNDLE_DESCRIPTION, fileNode, attrs);
170 addAttr(Constants.BUNDLE_MANIFESTVERSION, fileNode, attrs);
171 addAttr(Constants.BUNDLE_CATEGORY, fileNode, attrs);
172 addAttr(Constants.BUNDLE_ACTIVATIONPOLICY, fileNode, attrs);
173 addAttr(Constants.BUNDLE_COPYRIGHT, fileNode, attrs);
174 addAttr(Constants.BUNDLE_VENDOR, fileNode, attrs);
175 addAttr("Bundle-License", fileNode, attrs);
176 addAttr(Constants.BUNDLE_DOCURL, fileNode, attrs);
177 addAttr(Constants.BUNDLE_CONTACTADDRESS, fileNode, attrs);
178 addAttr(Constants.BUNDLE_ACTIVATOR, fileNode, attrs);
179 addAttr(Constants.BUNDLE_UPDATELOCATION, fileNode, attrs);
180 addAttr(Constants.BUNDLE_LOCALIZATION, fileNode, attrs);
181
182 // required execution environment
183 if (attrs.containsKey(new Name(
184 Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT)))
185 fileNode.setProperty(SlcNames.SLC_
186 + Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT, attrs
187 .getValue(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT)
188 .split(","));
189
190 // bundle classpath
191 if (attrs.containsKey(new Name(Constants.BUNDLE_CLASSPATH)))
192 fileNode.setProperty(SlcNames.SLC_ + Constants.BUNDLE_CLASSPATH,
193 attrs.getValue(Constants.BUNDLE_CLASSPATH).split(","));
194
195 // version
196 Version version = new Version(attrs.getValue(Constants.BUNDLE_VERSION));
197 fileNode.setProperty(SlcNames.SLC_BUNDLE_VERSION, version.toString());
198 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.BUNDLE_VERSION);
199 Node bundleVersionNode = fileNode.addNode(SlcNames.SLC_
200 + Constants.BUNDLE_VERSION, SlcTypes.SLC_OSGI_VERSION);
201 mapOsgiVersion(version, bundleVersionNode);
202
203 // fragment
204 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.FRAGMENT_HOST);
205 if (attrs.containsKey(new Name(Constants.FRAGMENT_HOST))) {
206 String fragmentHost = attrs.getValue(Constants.FRAGMENT_HOST);
207 String[] tokens = fragmentHost.split(";");
208 Node node = fileNode.addNode(SlcNames.SLC_
209 + Constants.FRAGMENT_HOST, SlcTypes.SLC_FRAGMENT_HOST);
210 node.setProperty(SlcNames.SLC_SYMBOLIC_NAME, tokens[0]);
211 for (int i = 1; i < tokens.length; i++) {
212 if (tokens[i].startsWith(Constants.BUNDLE_VERSION_ATTRIBUTE)) {
213 node.setProperty(SlcNames.SLC_BUNDLE_VERSION,
214 attributeValue(tokens[i]));
215 }
216 }
217 }
218
219 // imported packages
220 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.IMPORT_PACKAGE);
221 if (attrs.containsKey(new Name(Constants.IMPORT_PACKAGE))) {
222 String importPackages = attrs.getValue(Constants.IMPORT_PACKAGE);
223 List<String> packages = parseCommaSeparated(importPackages);
224 for (String pkg : packages) {
225 String[] tokens = pkg.split(";");
226 Node node = fileNode.addNode(SlcNames.SLC_
227 + Constants.IMPORT_PACKAGE,
228 SlcTypes.SLC_IMPORTED_PACKAGE);
229 node.setProperty(SlcNames.SLC_NAME, tokens[0]);
230 for (int i = 1; i < tokens.length; i++) {
231 if (tokens[i].startsWith(Constants.VERSION_ATTRIBUTE)) {
232 node.setProperty(SlcNames.SLC_VERSION,
233 attributeValue(tokens[i]));
234 } else if (tokens[i]
235 .startsWith(Constants.RESOLUTION_DIRECTIVE)) {
236 node.setProperty(
237 SlcNames.SLC_OPTIONAL,
238 directiveValue(tokens[i]).equals(
239 Constants.RESOLUTION_OPTIONAL));
240 }
241 }
242 }
243 }
244
245 // dynamic import package
246 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.DYNAMICIMPORT_PACKAGE);
247 if (attrs.containsKey(new Name(Constants.DYNAMICIMPORT_PACKAGE))) {
248 String importPackages = attrs
249 .getValue(Constants.DYNAMICIMPORT_PACKAGE);
250 List<String> packages = parseCommaSeparated(importPackages);
251 for (String pkg : packages) {
252 String[] tokens = pkg.split(";");
253 Node node = fileNode.addNode(SlcNames.SLC_
254 + Constants.DYNAMICIMPORT_PACKAGE,
255 SlcTypes.SLC_DYNAMIC_IMPORTED_PACKAGE);
256 node.setProperty(SlcNames.SLC_NAME, tokens[0]);
257 for (int i = 1; i < tokens.length; i++) {
258 if (tokens[i].startsWith(Constants.VERSION_ATTRIBUTE)) {
259 node.setProperty(SlcNames.SLC_VERSION,
260 attributeValue(tokens[i]));
261 }
262 }
263 }
264 }
265
266 // exported packages
267 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.EXPORT_PACKAGE);
268 if (attrs.containsKey(new Name(Constants.EXPORT_PACKAGE))) {
269 String exportPackages = attrs.getValue(Constants.EXPORT_PACKAGE);
270 List<String> packages = parseCommaSeparated(exportPackages);
271 for (String pkg : packages) {
272 String[] tokens = pkg.split(";");
273 Node node = fileNode.addNode(SlcNames.SLC_
274 + Constants.EXPORT_PACKAGE,
275 SlcTypes.SLC_EXPORTED_PACKAGE);
276 node.setProperty(SlcNames.SLC_NAME, tokens[0]);
277 // TODO: are these cleans really necessary?
278 cleanSubNodes(node, SlcNames.SLC_USES);
279 cleanSubNodes(node, SlcNames.SLC_VERSION);
280 for (int i = 1; i < tokens.length; i++) {
281 if (tokens[i].startsWith(Constants.VERSION_ATTRIBUTE)) {
282 String versionStr = attributeValue(tokens[i]);
283 Node versionNode = node.addNode(SlcNames.SLC_VERSION,
284 SlcTypes.SLC_OSGI_VERSION);
285 mapOsgiVersion(new Version(versionStr), versionNode);
286 } else if (tokens[i].startsWith(Constants.USES_DIRECTIVE)) {
287 String usedPackages = directiveValue(tokens[i]);
288 // log.debug("uses='" + usedPackages + "'");
289 for (String usedPackage : usedPackages.split(",")) {
290 // log.debug("usedPackage='" +
291 // usedPackage +
292 // "'");
293 Node usesNode = node.addNode(SlcNames.SLC_USES,
294 SlcTypes.SLC_JAVA_PACKAGE);
295 usesNode.setProperty(SlcNames.SLC_NAME, usedPackage);
296 }
297 }
298 }
299 }
300 }
301
302 // required bundle
303 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.REQUIRE_BUNDLE);
304 if (attrs.containsKey(new Name(Constants.REQUIRE_BUNDLE))) {
305 String requireBundle = attrs.getValue(Constants.REQUIRE_BUNDLE);
306 List<String> bundles = parseCommaSeparated(requireBundle);
307 for (String bundle : bundles) {
308 String[] tokens = bundle.split(";");
309 Node node = fileNode.addNode(SlcNames.SLC_
310 + Constants.REQUIRE_BUNDLE,
311 SlcTypes.SLC_REQUIRED_BUNDLE);
312 node.setProperty(SlcNames.SLC_SYMBOLIC_NAME, tokens[0]);
313 for (int i = 1; i < tokens.length; i++) {
314 if (tokens[i]
315 .startsWith(Constants.BUNDLE_VERSION_ATTRIBUTE)) {
316 node.setProperty(SlcNames.SLC_BUNDLE_VERSION,
317 attributeValue(tokens[i]));
318 } else if (tokens[i]
319 .startsWith(Constants.RESOLUTION_DIRECTIVE)) {
320 node.setProperty(
321 SlcNames.SLC_OPTIONAL,
322 directiveValue(tokens[i]).equals(
323 Constants.RESOLUTION_OPTIONAL));
324 }
325 }
326 }
327 }
328
329 }
330
331 private void addAttr(String key, Node node, Attributes attrs)
332 throws RepositoryException {
333 addAttr(new Name(key), node, attrs);
334 }
335
336 private void addAttr(Name key, Node node, Attributes attrs)
337 throws RepositoryException {
338 if (attrs.containsKey(key)) {
339 String value = attrs.getValue(key);
340 node.setProperty(SlcNames.SLC_ + key, value);
341 }
342 }
343
344 private void cleanSubNodes(Node node, String name)
345 throws RepositoryException {
346 if (node.hasNode(name)) {
347 NodeIterator nit = node.getNodes(name);
348 while (nit.hasNext())
349 nit.nextNode().remove();
350 }
351 }
352
353 private String attributeValue(String str) {
354 return extractValue(str, "=");
355 }
356
357 private String directiveValue(String str) {
358 return extractValue(str, ":=");
359 }
360
361 private String extractValue(String str, String eq) {
362 String[] tokens = str.split(eq);
363 // String key = tokens[0];
364 String value = tokens[1].trim();
365 // TODO: optimize?
366 if (value.startsWith("\""))
367 value = value.substring(1);
368 if (value.endsWith("\""))
369 value = value.substring(0, value.length() - 1);
370 return value;
371 }
372
373 /** Parse package list with nested directive with ',' */
374 private List<String> parseCommaSeparated(String str) {
375 List<String> res = new ArrayList<String>();
376 StringBuffer curr = new StringBuffer("");
377 boolean in = false;
378 for (char c : str.toCharArray()) {
379 if (c == ',') {
380 if (!in) {// new package
381 res.add(curr.toString());
382 curr = new StringBuffer("");
383 } else {// a ',' within " "
384 curr.append(c);
385 }
386 } else if (c == '\"') {
387 in = !in;
388 curr.append(c);
389 } else {
390 curr.append(c);
391 }
392 }
393 res.add(curr.toString());
394 // log.debug(res);
395 return res;
396 }
397
398 protected void mapOsgiVersion(Version version, Node versionNode)
399 throws RepositoryException {
400 versionNode.setProperty(SlcNames.SLC_AS_STRING, version.toString());
401 versionNode.setProperty(SlcNames.SLC_MAJOR, version.getMajor());
402 versionNode.setProperty(SlcNames.SLC_MINOR, version.getMinor());
403 versionNode.setProperty(SlcNames.SLC_MICRO, version.getMicro());
404 if (!version.getQualifier().equals(""))
405 versionNode.setProperty(SlcNames.SLC_QUALIFIER,
406 version.getQualifier());
407 }
408
409 public void setForce(Boolean force) {
410 this.force = force;
411 }
412
413 }