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