2 * Copyright (C) 2007-2012 Argeo GmbH
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org
.argeo
.slc
.repo
;
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
;
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
;
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
;
52 * Indexes jar file, currently supports standard J2SE and OSGi metadata (both
55 public class JarFileIndexer
implements NodeIndexer
, SlcNames
{
56 private final static Log log
= LogFactory
.getLog(JarFileIndexer
.class);
57 private Boolean force
= false;
59 public Boolean
support(String path
) {
60 return FilenameUtils
.getExtension(path
).equals("jar");
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;
70 if (!support(fileNode
.getPath()))
74 if (!force
&& fileNode
.isNodeType(SlcTypes
.SLC_JAR_FILE
))
77 if (!fileNode
.isNodeType(NodeType
.NT_FILE
))
80 Session jcrSession
= fileNode
.getSession();
81 Node contentNode
= fileNode
.getNode(Node
.JCR_CONTENT
);
82 fileBinary
= contentNode
.getProperty(Property
.JCR_DATA
).getBinary();
84 jarIn
= new JarInputStream(fileBinary
.getStream());
85 Manifest manifest
= jarIn
.getManifest();
86 if (manifest
== null) {
87 log
.error(fileNode
+ " has no MANIFEST");
91 bo
= new ByteArrayOutputStream();
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 "
105 bi
= new ByteArrayInputStream(newManifest
);
106 manifestBinary
= jcrSession
.getValueFactory().createBinary(bi
);
109 fileNode
.addMixin(SlcTypes
.SLC_JAR_FILE
);
111 fileNode
.setProperty(SlcNames
.SLC_MANIFEST
, manifestBinary
);
112 Attributes attrs
= manifest
.getMainAttributes();
114 getI18nValues(fileBinary
, attrs
);
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
);
131 if (attrs
.containsKey(new Name(Constants
.BUNDLE_SYMBOLICNAME
))) {
132 addOsgiMetadata(fileNode
, attrs
);
133 if (log
.isTraceEnabled())
134 log
.trace("Indexed OSGi bundle " + fileNode
);
136 if (log
.isTraceEnabled())
137 log
.trace("Indexed JAR file " + fileNode
);
140 JcrUtils
.updateLastModified(fileNode
);
142 } catch (Exception e
) {
143 throw new SlcException("Cannot index jar " + fileNode
, e
);
145 IOUtils
.closeQuietly(bi
);
146 IOUtils
.closeQuietly(bo
);
147 IOUtils
.closeQuietly(jarIn
);
148 JcrUtils
.closeQuietly(manifestBinary
);
149 JcrUtils
.closeQuietly(fileBinary
);
154 private void getI18nValues(Binary fileBinary
, Attributes attrs
)
156 JarInputStream jarIn
= null;
158 jarIn
= new JarInputStream(fileBinary
.getStream());
159 String bundleLocalization
= null;
161 String blKey
= Constants
.BUNDLE_LOCALIZATION
; //"Bundle-Localization";
162 Name blkName
= new Name(blKey
);
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
);
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")) {
184 // if(je.getSize() != -1){
185 // propBytes = new byte[(int)je.getSize()];
186 // int len = (int) je.getSize();
188 // while (offset != len)
189 // offset += jarIn.read(propBytes, offset, len -
192 baos
= new ByteArrayOutputStream();
194 int qwe
= jarIn
.read();
199 propBytes
= baos
.toByteArray();
202 entry
= jarIn
.getNextJarEntry();
206 if (jarEntry
!= null) {
207 Properties prop
= new Properties();
208 InputStream is
= new ByteArrayInputStream(propBytes
);
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));
216 attrs
.put(obj
, newVal
);
220 } catch (RepositoryException e
) {
221 throw new SlcException(
222 "Error while reading the jar binary content " + fileBinary
,
224 } catch (IOException ioe
) {
225 throw new SlcException("unable to get internationalized values",
228 IOUtils
.closeQuietly(jarIn
);
232 protected void addOsgiMetadata(Node fileNode
, Attributes attrs
)
233 throws RepositoryException
{
234 fileNode
.addMixin(SlcTypes
.SLC_BUNDLE_ARTIFACT
);
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
);
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
);
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
)
267 if (attrs
.containsKey(new Name(Constants
.BUNDLE_CLASSPATH
)))
268 fileNode
.setProperty(SlcNames
.SLC_
+ Constants
.BUNDLE_CLASSPATH
,
269 attrs
.getValue(Constants
.BUNDLE_CLASSPATH
).split(","));
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
);
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
]));
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
]));
311 .startsWith(Constants
.RESOLUTION_DIRECTIVE
)) {
313 SlcNames
.SLC_OPTIONAL
,
314 directiveValue(tokens
[i
]).equals(
315 Constants
.RESOLUTION_OPTIONAL
));
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
]));
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='" +
369 Node usesNode
= node
.addNode(SlcNames
.SLC_USES
,
370 SlcTypes
.SLC_JAVA_PACKAGE
);
371 usesNode
.setProperty(SlcNames
.SLC_NAME
, usedPackage
);
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
++) {
391 .startsWith(Constants
.BUNDLE_VERSION_ATTRIBUTE
)) {
392 node
.setProperty(SlcNames
.SLC_BUNDLE_VERSION
,
393 attributeValue(tokens
[i
]));
395 .startsWith(Constants
.RESOLUTION_DIRECTIVE
)) {
397 SlcNames
.SLC_OPTIONAL
,
398 directiveValue(tokens
[i
]).equals(
399 Constants
.RESOLUTION_OPTIONAL
));
407 private void addAttr(String key
, Node node
, Attributes attrs
)
408 throws RepositoryException
{
409 addAttr(new Name(key
), node
, attrs
);
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
);
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();
429 private String
attributeValue(String str
) {
430 return extractValue(str
, "=");
433 private String
directiveValue(String str
) {
434 return extractValue(str
, ":=");
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();
442 if (value
.startsWith("\""))
443 value
= value
.substring(1);
444 if (value
.endsWith("\""))
445 value
= value
.substring(0, value
.length() - 1);
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("");
454 for (char c
: str
.toCharArray()) {
456 if (!in
) {// new package
457 res
.add(curr
.toString());
458 curr
= new StringBuffer("");
459 } else {// a ',' within " "
462 } else if (c
== '\"') {
469 res
.add(curr
.toString());
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());
485 public void setForce(Boolean force
) {