]> git.argeo.org Git - gpl/argeo-slc.git/blob - runtime/org.argeo.slc.repo/src/main/java/org/argeo/slc/repo/JarFileIndexer.java
Fix unclosed jar
[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 fileNode.addMixin(SlcTypes.SLC_BUNDLE_ARTIFACT);
235
236 // symbolic name
237 String symbolicName = attrs.getValue(Constants.BUNDLE_SYMBOLICNAME);
238 // make sure there is no directive
239 symbolicName = symbolicName.split(";")[0];
240 fileNode.setProperty(SlcNames.SLC_SYMBOLIC_NAME, symbolicName);
241
242 // direct mapping
243 addAttr(Constants.BUNDLE_SYMBOLICNAME, fileNode, attrs);
244 addAttr(Constants.BUNDLE_NAME, fileNode, attrs);
245 addAttr(Constants.BUNDLE_DESCRIPTION, fileNode, attrs);
246 addAttr(Constants.BUNDLE_MANIFESTVERSION, fileNode, attrs);
247 addAttr(Constants.BUNDLE_CATEGORY, fileNode, attrs);
248 addAttr(Constants.BUNDLE_ACTIVATIONPOLICY, fileNode, attrs);
249 addAttr(Constants.BUNDLE_COPYRIGHT, fileNode, attrs);
250 addAttr(Constants.BUNDLE_VENDOR, fileNode, attrs);
251 addAttr("Bundle-License", fileNode, attrs);
252 addAttr(Constants.BUNDLE_DOCURL, fileNode, attrs);
253 addAttr(Constants.BUNDLE_CONTACTADDRESS, fileNode, attrs);
254 addAttr(Constants.BUNDLE_ACTIVATOR, fileNode, attrs);
255 addAttr(Constants.BUNDLE_UPDATELOCATION, fileNode, attrs);
256 addAttr(Constants.BUNDLE_LOCALIZATION, fileNode, attrs);
257
258 // required execution environment
259 if (attrs.containsKey(new Name(
260 Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT)))
261 fileNode.setProperty(SlcNames.SLC_
262 + Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT, attrs
263 .getValue(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT)
264 .split(","));
265
266 // bundle classpath
267 if (attrs.containsKey(new Name(Constants.BUNDLE_CLASSPATH)))
268 fileNode.setProperty(SlcNames.SLC_ + Constants.BUNDLE_CLASSPATH,
269 attrs.getValue(Constants.BUNDLE_CLASSPATH).split(","));
270
271 // version
272 Version version = new Version(attrs.getValue(Constants.BUNDLE_VERSION));
273 fileNode.setProperty(SlcNames.SLC_BUNDLE_VERSION, version.toString());
274 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.BUNDLE_VERSION);
275 Node bundleVersionNode = fileNode.addNode(SlcNames.SLC_
276 + Constants.BUNDLE_VERSION, SlcTypes.SLC_OSGI_VERSION);
277 mapOsgiVersion(version, bundleVersionNode);
278
279 // fragment
280 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.FRAGMENT_HOST);
281 if (attrs.containsKey(new Name(Constants.FRAGMENT_HOST))) {
282 String fragmentHost = attrs.getValue(Constants.FRAGMENT_HOST);
283 String[] tokens = fragmentHost.split(";");
284 Node node = fileNode.addNode(SlcNames.SLC_
285 + Constants.FRAGMENT_HOST, SlcTypes.SLC_FRAGMENT_HOST);
286 node.setProperty(SlcNames.SLC_SYMBOLIC_NAME, tokens[0]);
287 for (int i = 1; i < tokens.length; i++) {
288 if (tokens[i].startsWith(Constants.BUNDLE_VERSION_ATTRIBUTE)) {
289 node.setProperty(SlcNames.SLC_BUNDLE_VERSION,
290 attributeValue(tokens[i]));
291 }
292 }
293 }
294
295 // imported packages
296 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.IMPORT_PACKAGE);
297 if (attrs.containsKey(new Name(Constants.IMPORT_PACKAGE))) {
298 String importPackages = attrs.getValue(Constants.IMPORT_PACKAGE);
299 List<String> packages = parseCommaSeparated(importPackages);
300 for (String pkg : packages) {
301 String[] tokens = pkg.split(";");
302 Node node = fileNode.addNode(SlcNames.SLC_
303 + Constants.IMPORT_PACKAGE,
304 SlcTypes.SLC_IMPORTED_PACKAGE);
305 node.setProperty(SlcNames.SLC_NAME, tokens[0]);
306 for (int i = 1; i < tokens.length; i++) {
307 if (tokens[i].startsWith(Constants.VERSION_ATTRIBUTE)) {
308 node.setProperty(SlcNames.SLC_VERSION,
309 attributeValue(tokens[i]));
310 } else if (tokens[i]
311 .startsWith(Constants.RESOLUTION_DIRECTIVE)) {
312 node.setProperty(
313 SlcNames.SLC_OPTIONAL,
314 directiveValue(tokens[i]).equals(
315 Constants.RESOLUTION_OPTIONAL));
316 }
317 }
318 }
319 }
320
321 // dynamic import package
322 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.DYNAMICIMPORT_PACKAGE);
323 if (attrs.containsKey(new Name(Constants.DYNAMICIMPORT_PACKAGE))) {
324 String importPackages = attrs
325 .getValue(Constants.DYNAMICIMPORT_PACKAGE);
326 List<String> packages = parseCommaSeparated(importPackages);
327 for (String pkg : packages) {
328 String[] tokens = pkg.split(";");
329 Node node = fileNode.addNode(SlcNames.SLC_
330 + Constants.DYNAMICIMPORT_PACKAGE,
331 SlcTypes.SLC_DYNAMIC_IMPORTED_PACKAGE);
332 node.setProperty(SlcNames.SLC_NAME, tokens[0]);
333 for (int i = 1; i < tokens.length; i++) {
334 if (tokens[i].startsWith(Constants.VERSION_ATTRIBUTE)) {
335 node.setProperty(SlcNames.SLC_VERSION,
336 attributeValue(tokens[i]));
337 }
338 }
339 }
340 }
341
342 // exported packages
343 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.EXPORT_PACKAGE);
344 if (attrs.containsKey(new Name(Constants.EXPORT_PACKAGE))) {
345 String exportPackages = attrs.getValue(Constants.EXPORT_PACKAGE);
346 List<String> packages = parseCommaSeparated(exportPackages);
347 for (String pkg : packages) {
348 String[] tokens = pkg.split(";");
349 Node node = fileNode.addNode(SlcNames.SLC_
350 + Constants.EXPORT_PACKAGE,
351 SlcTypes.SLC_EXPORTED_PACKAGE);
352 node.setProperty(SlcNames.SLC_NAME, tokens[0]);
353 // TODO: are these cleans really necessary?
354 cleanSubNodes(node, SlcNames.SLC_USES);
355 cleanSubNodes(node, SlcNames.SLC_VERSION);
356 for (int i = 1; i < tokens.length; i++) {
357 if (tokens[i].startsWith(Constants.VERSION_ATTRIBUTE)) {
358 String versionStr = attributeValue(tokens[i]);
359 Node versionNode = node.addNode(SlcNames.SLC_VERSION,
360 SlcTypes.SLC_OSGI_VERSION);
361 mapOsgiVersion(new Version(versionStr), versionNode);
362 } else if (tokens[i].startsWith(Constants.USES_DIRECTIVE)) {
363 String usedPackages = directiveValue(tokens[i]);
364 // log.debug("uses='" + usedPackages + "'");
365 for (String usedPackage : usedPackages.split(",")) {
366 // log.debug("usedPackage='" +
367 // usedPackage +
368 // "'");
369 Node usesNode = node.addNode(SlcNames.SLC_USES,
370 SlcTypes.SLC_JAVA_PACKAGE);
371 usesNode.setProperty(SlcNames.SLC_NAME, usedPackage);
372 }
373 }
374 }
375 }
376 }
377
378 // required bundle
379 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.REQUIRE_BUNDLE);
380 if (attrs.containsKey(new Name(Constants.REQUIRE_BUNDLE))) {
381 String requireBundle = attrs.getValue(Constants.REQUIRE_BUNDLE);
382 List<String> bundles = parseCommaSeparated(requireBundle);
383 for (String bundle : bundles) {
384 String[] tokens = bundle.split(";");
385 Node node = fileNode.addNode(SlcNames.SLC_
386 + Constants.REQUIRE_BUNDLE,
387 SlcTypes.SLC_REQUIRED_BUNDLE);
388 node.setProperty(SlcNames.SLC_SYMBOLIC_NAME, tokens[0]);
389 for (int i = 1; i < tokens.length; i++) {
390 if (tokens[i]
391 .startsWith(Constants.BUNDLE_VERSION_ATTRIBUTE)) {
392 node.setProperty(SlcNames.SLC_BUNDLE_VERSION,
393 attributeValue(tokens[i]));
394 } else if (tokens[i]
395 .startsWith(Constants.RESOLUTION_DIRECTIVE)) {
396 node.setProperty(
397 SlcNames.SLC_OPTIONAL,
398 directiveValue(tokens[i]).equals(
399 Constants.RESOLUTION_OPTIONAL));
400 }
401 }
402 }
403 }
404
405 }
406
407 private void addAttr(String key, Node node, Attributes attrs)
408 throws RepositoryException {
409 addAttr(new Name(key), node, attrs);
410 }
411
412 private void addAttr(Name key, Node node, Attributes attrs)
413 throws RepositoryException {
414 if (attrs.containsKey(key)) {
415 String value = attrs.getValue(key);
416 node.setProperty(SlcNames.SLC_ + key, value);
417 }
418 }
419
420 private void cleanSubNodes(Node node, String name)
421 throws RepositoryException {
422 if (node.hasNode(name)) {
423 NodeIterator nit = node.getNodes(name);
424 while (nit.hasNext())
425 nit.nextNode().remove();
426 }
427 }
428
429 private String attributeValue(String str) {
430 return extractValue(str, "=");
431 }
432
433 private String directiveValue(String str) {
434 return extractValue(str, ":=");
435 }
436
437 private String extractValue(String str, String eq) {
438 String[] tokens = str.split(eq);
439 // String key = tokens[0];
440 String value = tokens[1].trim();
441 // TODO: optimize?
442 if (value.startsWith("\""))
443 value = value.substring(1);
444 if (value.endsWith("\""))
445 value = value.substring(0, value.length() - 1);
446 return value;
447 }
448
449 /** Parse package list with nested directive with ',' */
450 private List<String> parseCommaSeparated(String str) {
451 List<String> res = new ArrayList<String>();
452 StringBuffer curr = new StringBuffer("");
453 boolean in = false;
454 for (char c : str.toCharArray()) {
455 if (c == ',') {
456 if (!in) {// new package
457 res.add(curr.toString());
458 curr = new StringBuffer("");
459 } else {// a ',' within " "
460 curr.append(c);
461 }
462 } else if (c == '\"') {
463 in = !in;
464 curr.append(c);
465 } else {
466 curr.append(c);
467 }
468 }
469 res.add(curr.toString());
470 // log.debug(res);
471 return res;
472 }
473
474 protected void mapOsgiVersion(Version version, Node versionNode)
475 throws RepositoryException {
476 versionNode.setProperty(SlcNames.SLC_AS_STRING, version.toString());
477 versionNode.setProperty(SlcNames.SLC_MAJOR, version.getMajor());
478 versionNode.setProperty(SlcNames.SLC_MINOR, version.getMinor());
479 versionNode.setProperty(SlcNames.SLC_MICRO, version.getMicro());
480 if (!version.getQualifier().equals(""))
481 versionNode.setProperty(SlcNames.SLC_QUALIFIER,
482 version.getQualifier());
483 }
484
485 public void setForce(Boolean force) {
486 this.force = force;
487 }
488
489 }