]> git.argeo.org Git - gpl/argeo-slc.git/blob - runtime/org.argeo.slc.repo/src/main/java/org/argeo/slc/repo/JarFileIndexer.java
Improve licenses and sources
[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.io.IOException;
21 import java.io.InputStream;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.List;
25 import java.util.Properties;
26 import java.util.jar.Attributes;
27 import java.util.jar.Attributes.Name;
28 import java.util.jar.JarEntry;
29 import java.util.jar.JarInputStream;
30 import java.util.jar.Manifest;
31
32 import javax.jcr.Binary;
33 import javax.jcr.Node;
34 import javax.jcr.NodeIterator;
35 import javax.jcr.Property;
36 import javax.jcr.RepositoryException;
37 import javax.jcr.Session;
38 import javax.jcr.nodetype.NodeType;
39
40 import org.apache.commons.io.FilenameUtils;
41 import org.apache.commons.io.IOUtils;
42 import org.apache.commons.logging.Log;
43 import org.apache.commons.logging.LogFactory;
44 import org.argeo.jcr.JcrUtils;
45 import org.argeo.slc.SlcException;
46 import org.argeo.slc.jcr.SlcNames;
47 import org.argeo.slc.jcr.SlcTypes;
48 import org.osgi.framework.Constants;
49 import org.osgi.framework.Version;
50
51 /**
52 * Indexes jar file, currently supports standard J2SE and OSGi metadata (both
53 * from MANIFEST)
54 */
55 public class JarFileIndexer implements NodeIndexer, SlcNames {
56 private final static Log log = LogFactory.getLog(JarFileIndexer.class);
57 private Boolean force = false;
58
59 public Boolean support(String path) {
60 return FilenameUtils.getExtension(path).equals("jar");
61 }
62
63 public void index(Node fileNode) {
64 Binary fileBinary = null;
65 JarInputStream jarIn = null;
66 ByteArrayOutputStream bo = null;
67 ByteArrayInputStream bi = null;
68 Binary manifestBinary = null;
69 try {
70 if (!support(fileNode.getPath()))
71 return;
72
73 // Already indexed
74 if (!force && fileNode.isNodeType(SlcTypes.SLC_JAR_FILE))
75 return;
76
77 if (!fileNode.isNodeType(NodeType.NT_FILE))
78 return;
79
80 Session jcrSession = fileNode.getSession();
81 Node contentNode = fileNode.getNode(Node.JCR_CONTENT);
82 fileBinary = contentNode.getProperty(Property.JCR_DATA).getBinary();
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 getI18nValues(fileBinary, attrs);
115
116 // standard J2SE MANIFEST attributes
117 addAttr(Attributes.Name.MANIFEST_VERSION, fileNode, attrs);
118 addAttr(Attributes.Name.SIGNATURE_VERSION, fileNode, attrs);
119 addAttr(Attributes.Name.CLASS_PATH, fileNode, attrs);
120 addAttr(Attributes.Name.MAIN_CLASS, fileNode, attrs);
121 addAttr(Attributes.Name.EXTENSION_NAME, fileNode, attrs);
122 addAttr(Attributes.Name.IMPLEMENTATION_VERSION, fileNode, attrs);
123 addAttr(Attributes.Name.IMPLEMENTATION_VENDOR, fileNode, attrs);
124 addAttr(Attributes.Name.IMPLEMENTATION_VENDOR_ID, fileNode, attrs);
125 addAttr(Attributes.Name.SPECIFICATION_TITLE, fileNode, attrs);
126 addAttr(Attributes.Name.SPECIFICATION_VERSION, fileNode, attrs);
127 addAttr(Attributes.Name.SPECIFICATION_VENDOR, fileNode, attrs);
128 addAttr(Attributes.Name.SEALED, fileNode, attrs);
129
130 // OSGi
131 if (attrs.containsKey(new Name(Constants.BUNDLE_SYMBOLICNAME))) {
132 addOsgiMetadata(fileNode, attrs);
133 if (log.isTraceEnabled())
134 log.trace("Indexed OSGi bundle " + fileNode);
135 } else {
136 if (log.isTraceEnabled())
137 log.trace("Indexed JAR file " + fileNode);
138 }
139
140 JcrUtils.updateLastModified(fileNode);
141
142 } catch (Exception e) {
143 throw new SlcException("Cannot index jar " + fileNode, e);
144 } finally {
145 IOUtils.closeQuietly(bi);
146 IOUtils.closeQuietly(bo);
147 IOUtils.closeQuietly(jarIn);
148 JcrUtils.closeQuietly(manifestBinary);
149 JcrUtils.closeQuietly(fileBinary);
150 }
151
152 }
153
154 private void getI18nValues(Binary fileBinary, Attributes attrs)
155 throws IOException {
156 JarInputStream jarIn = null;
157 try {
158 jarIn = new JarInputStream(fileBinary.getStream());
159 String bundleLocalization = null;
160
161 String blKey = Constants.BUNDLE_LOCALIZATION; // "Bundle-Localization";
162 Name blkName = new Name(blKey);
163
164 browse: for (Object obj : attrs.keySet()) {
165 String value = attrs.getValue((Attributes.Name) obj);
166 if (value.startsWith("%")) {
167 if (attrs.containsKey(blkName)) {
168 bundleLocalization = attrs.getValue(blkName);
169 break browse;
170 }
171 }
172 }
173
174 JarEntry jarEntry = null;
175 byte[] propBytes = null;
176 ByteArrayOutputStream baos = null;
177 browse: if (bundleLocalization != null) {
178 JarEntry entry = jarIn.getNextJarEntry();
179 while (entry != null) {
180 if (entry.getName().equals(
181 bundleLocalization + ".properties")) {
182 jarEntry = entry;
183
184 // if(je.getSize() != -1){
185 // propBytes = new byte[(int)je.getSize()];
186 // int len = (int) je.getSize();
187 // int offset = 0;
188 // while (offset != len)
189 // offset += jarIn.read(propBytes, offset, len -
190 // offset);
191 // } else {
192 baos = new ByteArrayOutputStream();
193 while (true) {
194 int qwe = jarIn.read();
195 if (qwe == -1)
196 break;
197 baos.write(qwe);
198 }
199 propBytes = baos.toByteArray();
200 break browse;
201 }
202 entry = jarIn.getNextJarEntry();
203 }
204 }
205
206 if (jarEntry != null) {
207 Properties prop = new Properties();
208 InputStream is = new ByteArrayInputStream(propBytes);
209 prop.load(is);
210
211 for (Object obj : attrs.keySet()) {
212 String value = attrs.getValue((Attributes.Name) obj);
213 if (value.startsWith("%")) {
214 String newVal = prop.getProperty(value.substring(1));
215 if (newVal != null)
216 attrs.put(obj, newVal);
217 }
218 }
219 }
220 } catch (RepositoryException e) {
221 throw new SlcException(
222 "Error while reading the jar binary content " + fileBinary,
223 e);
224 } catch (IOException ioe) {
225 throw new SlcException("unable to get internationalized values",
226 ioe);
227 } finally {
228 IOUtils.closeQuietly(jarIn);
229 }
230 }
231
232 protected void addOsgiMetadata(Node fileNode, Attributes attrs)
233 throws RepositoryException {
234
235 // TODO remove this ?
236 // Compulsory for the time being, because bundle artifact extends
237 // artifact
238 if (!fileNode.isNodeType(SlcTypes.SLC_ARTIFACT)) {
239 ArtifactIndexer indexer = new ArtifactIndexer();
240 indexer.index(fileNode);
241 }
242
243 fileNode.addMixin(SlcTypes.SLC_BUNDLE_ARTIFACT);
244
245 // symbolic name
246 String symbolicName = attrs.getValue(Constants.BUNDLE_SYMBOLICNAME);
247 // make sure there is no directive
248 symbolicName = symbolicName.split(";")[0];
249 fileNode.setProperty(SlcNames.SLC_SYMBOLIC_NAME, symbolicName);
250
251 // direct mapping
252 addAttr(Constants.BUNDLE_SYMBOLICNAME, fileNode, attrs);
253 addAttr(Constants.BUNDLE_NAME, fileNode, attrs);
254 addAttr(Constants.BUNDLE_DESCRIPTION, fileNode, attrs);
255 addAttr(Constants.BUNDLE_MANIFESTVERSION, fileNode, attrs);
256 addAttr(Constants.BUNDLE_CATEGORY, fileNode, attrs);
257 addAttr(Constants.BUNDLE_ACTIVATIONPOLICY, fileNode, attrs);
258 addAttr(Constants.BUNDLE_COPYRIGHT, fileNode, attrs);
259 addAttr(Constants.BUNDLE_VENDOR, fileNode, attrs);
260 addAttr("Bundle-License", fileNode, attrs);
261 addAttr(Constants.BUNDLE_DOCURL, fileNode, attrs);
262 addAttr(Constants.BUNDLE_CONTACTADDRESS, fileNode, attrs);
263 addAttr(Constants.BUNDLE_ACTIVATOR, fileNode, attrs);
264 addAttr(Constants.BUNDLE_UPDATELOCATION, fileNode, attrs);
265 addAttr(Constants.BUNDLE_LOCALIZATION, fileNode, attrs);
266
267 // required execution environment
268 if (attrs.containsKey(new Name(
269 Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT)))
270 fileNode.setProperty(SlcNames.SLC_
271 + Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT, attrs
272 .getValue(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT)
273 .split(","));
274
275 // bundle classpath
276 if (attrs.containsKey(new Name(Constants.BUNDLE_CLASSPATH)))
277 fileNode.setProperty(SlcNames.SLC_ + Constants.BUNDLE_CLASSPATH,
278 attrs.getValue(Constants.BUNDLE_CLASSPATH).split(","));
279
280 // version
281 Version version = new Version(attrs.getValue(Constants.BUNDLE_VERSION));
282 fileNode.setProperty(SlcNames.SLC_BUNDLE_VERSION, version.toString());
283 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.BUNDLE_VERSION);
284 Node bundleVersionNode = fileNode.addNode(SlcNames.SLC_
285 + Constants.BUNDLE_VERSION, SlcTypes.SLC_OSGI_VERSION);
286 mapOsgiVersion(version, bundleVersionNode);
287
288 // fragment
289 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.FRAGMENT_HOST);
290 if (attrs.containsKey(new Name(Constants.FRAGMENT_HOST))) {
291 String fragmentHost = attrs.getValue(Constants.FRAGMENT_HOST);
292 String[] tokens = fragmentHost.split(";");
293 Node node = fileNode.addNode(SlcNames.SLC_
294 + Constants.FRAGMENT_HOST, SlcTypes.SLC_FRAGMENT_HOST);
295 node.setProperty(SlcNames.SLC_SYMBOLIC_NAME, tokens[0]);
296 for (int i = 1; i < tokens.length; i++) {
297 if (tokens[i].startsWith(Constants.BUNDLE_VERSION_ATTRIBUTE)) {
298 node.setProperty(SlcNames.SLC_BUNDLE_VERSION,
299 attributeValue(tokens[i]));
300 }
301 }
302 }
303
304 // imported packages
305 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.IMPORT_PACKAGE);
306 if (attrs.containsKey(new Name(Constants.IMPORT_PACKAGE))) {
307 String importPackages = attrs.getValue(Constants.IMPORT_PACKAGE);
308 List<String> packages = parseCommaSeparated(importPackages);
309 for (String pkg : packages) {
310 String[] tokens = pkg.split(";");
311 Node node = fileNode.addNode(SlcNames.SLC_
312 + Constants.IMPORT_PACKAGE,
313 SlcTypes.SLC_IMPORTED_PACKAGE);
314 node.setProperty(SlcNames.SLC_NAME, tokens[0]);
315 for (int i = 1; i < tokens.length; i++) {
316 if (tokens[i].startsWith(Constants.VERSION_ATTRIBUTE)) {
317 node.setProperty(SlcNames.SLC_VERSION,
318 attributeValue(tokens[i]));
319 } else if (tokens[i]
320 .startsWith(Constants.RESOLUTION_DIRECTIVE)) {
321 node.setProperty(
322 SlcNames.SLC_OPTIONAL,
323 directiveValue(tokens[i]).equals(
324 Constants.RESOLUTION_OPTIONAL));
325 }
326 }
327 }
328 }
329
330 // dynamic import package
331 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.DYNAMICIMPORT_PACKAGE);
332 if (attrs.containsKey(new Name(Constants.DYNAMICIMPORT_PACKAGE))) {
333 String importPackages = attrs
334 .getValue(Constants.DYNAMICIMPORT_PACKAGE);
335 List<String> packages = parseCommaSeparated(importPackages);
336 for (String pkg : packages) {
337 String[] tokens = pkg.split(";");
338 Node node = fileNode.addNode(SlcNames.SLC_
339 + Constants.DYNAMICIMPORT_PACKAGE,
340 SlcTypes.SLC_DYNAMIC_IMPORTED_PACKAGE);
341 node.setProperty(SlcNames.SLC_NAME, tokens[0]);
342 for (int i = 1; i < tokens.length; i++) {
343 if (tokens[i].startsWith(Constants.VERSION_ATTRIBUTE)) {
344 node.setProperty(SlcNames.SLC_VERSION,
345 attributeValue(tokens[i]));
346 }
347 }
348 }
349 }
350
351 // exported packages
352 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.EXPORT_PACKAGE);
353 if (attrs.containsKey(new Name(Constants.EXPORT_PACKAGE))) {
354 String exportPackages = attrs.getValue(Constants.EXPORT_PACKAGE);
355 List<String> packages = parseCommaSeparated(exportPackages);
356 for (String pkg : packages) {
357 String[] tokens = pkg.split(";");
358 Node node = fileNode.addNode(SlcNames.SLC_
359 + Constants.EXPORT_PACKAGE,
360 SlcTypes.SLC_EXPORTED_PACKAGE);
361 node.setProperty(SlcNames.SLC_NAME, tokens[0]);
362 // TODO: are these cleans really necessary?
363 cleanSubNodes(node, SlcNames.SLC_USES);
364 cleanSubNodes(node, SlcNames.SLC_VERSION);
365 for (int i = 1; i < tokens.length; i++) {
366 if (tokens[i].startsWith(Constants.VERSION_ATTRIBUTE)) {
367 String versionStr = attributeValue(tokens[i]);
368 Node versionNode = node.addNode(SlcNames.SLC_VERSION,
369 SlcTypes.SLC_OSGI_VERSION);
370 mapOsgiVersion(new Version(versionStr), versionNode);
371 } else if (tokens[i].startsWith(Constants.USES_DIRECTIVE)) {
372 String usedPackages = directiveValue(tokens[i]);
373 // log.debug("uses='" + usedPackages + "'");
374 for (String usedPackage : usedPackages.split(",")) {
375 // log.debug("usedPackage='" +
376 // usedPackage +
377 // "'");
378 Node usesNode = node.addNode(SlcNames.SLC_USES,
379 SlcTypes.SLC_JAVA_PACKAGE);
380 usesNode.setProperty(SlcNames.SLC_NAME, usedPackage);
381 }
382 }
383 }
384 }
385 }
386
387 // required bundle
388 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.REQUIRE_BUNDLE);
389 if (attrs.containsKey(new Name(Constants.REQUIRE_BUNDLE))) {
390 String requireBundle = attrs.getValue(Constants.REQUIRE_BUNDLE);
391 List<String> bundles = parseCommaSeparated(requireBundle);
392 for (String bundle : bundles) {
393 String[] tokens = bundle.split(";");
394 Node node = fileNode.addNode(SlcNames.SLC_
395 + Constants.REQUIRE_BUNDLE,
396 SlcTypes.SLC_REQUIRED_BUNDLE);
397 node.setProperty(SlcNames.SLC_SYMBOLIC_NAME, tokens[0]);
398 for (int i = 1; i < tokens.length; i++) {
399 if (tokens[i]
400 .startsWith(Constants.BUNDLE_VERSION_ATTRIBUTE)) {
401 node.setProperty(SlcNames.SLC_BUNDLE_VERSION,
402 attributeValue(tokens[i]));
403 } else if (tokens[i]
404 .startsWith(Constants.RESOLUTION_DIRECTIVE)) {
405 node.setProperty(
406 SlcNames.SLC_OPTIONAL,
407 directiveValue(tokens[i]).equals(
408 Constants.RESOLUTION_OPTIONAL));
409 }
410 }
411 }
412 }
413
414 }
415
416 private void addAttr(String key, Node node, Attributes attrs)
417 throws RepositoryException {
418 addAttr(new Name(key), node, attrs);
419 }
420
421 private void addAttr(Name key, Node node, Attributes attrs)
422 throws RepositoryException {
423 if (attrs.containsKey(key)) {
424 String value = attrs.getValue(key);
425 node.setProperty(SlcNames.SLC_ + key, value);
426 }
427 }
428
429 private void cleanSubNodes(Node node, String name)
430 throws RepositoryException {
431 if (node.hasNode(name)) {
432 NodeIterator nit = node.getNodes(name);
433 while (nit.hasNext())
434 nit.nextNode().remove();
435 }
436 }
437
438 private String attributeValue(String str) {
439 return extractValue(str, "=");
440 }
441
442 private String directiveValue(String str) {
443 return extractValue(str, ":=");
444 }
445
446 private String extractValue(String str, String eq) {
447 String[] tokens = str.split(eq);
448 // String key = tokens[0];
449 String value = tokens[1].trim();
450 // TODO: optimize?
451 if (value.startsWith("\""))
452 value = value.substring(1);
453 if (value.endsWith("\""))
454 value = value.substring(0, value.length() - 1);
455 return value;
456 }
457
458 /** Parse package list with nested directive with ',' */
459 private List<String> parseCommaSeparated(String str) {
460 List<String> res = new ArrayList<String>();
461 StringBuffer curr = new StringBuffer("");
462 boolean in = false;
463 for (char c : str.toCharArray()) {
464 if (c == ',') {
465 if (!in) {// new package
466 res.add(curr.toString());
467 curr = new StringBuffer("");
468 } else {// a ',' within " "
469 curr.append(c);
470 }
471 } else if (c == '\"') {
472 in = !in;
473 curr.append(c);
474 } else {
475 curr.append(c);
476 }
477 }
478 res.add(curr.toString());
479 // log.debug(res);
480 return res;
481 }
482
483 protected void mapOsgiVersion(Version version, Node versionNode)
484 throws RepositoryException {
485 versionNode.setProperty(SlcNames.SLC_AS_STRING, version.toString());
486 versionNode.setProperty(SlcNames.SLC_MAJOR, version.getMajor());
487 versionNode.setProperty(SlcNames.SLC_MINOR, version.getMinor());
488 versionNode.setProperty(SlcNames.SLC_MICRO, version.getMicro());
489 if (!version.getQualifier().equals(""))
490 versionNode.setProperty(SlcNames.SLC_QUALIFIER,
491 version.getQualifier());
492 }
493
494 public void setForce(Boolean force) {
495 this.force = force;
496 }
497
498 }