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
.SlcNames
;
47 import org
.argeo
.slc
.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
{
235 // TODO remove this ?
236 // Compulsory for the time being, because bundle artifact extends
238 if (!fileNode
.isNodeType(SlcTypes
.SLC_ARTIFACT
)) {
239 ArtifactIndexer indexer
= new ArtifactIndexer();
240 indexer
.index(fileNode
);
243 fileNode
.addMixin(SlcTypes
.SLC_BUNDLE_ARTIFACT
);
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
);
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
);
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
)
276 if (attrs
.containsKey(new Name(Constants
.BUNDLE_CLASSPATH
)))
277 fileNode
.setProperty(SlcNames
.SLC_
+ Constants
.BUNDLE_CLASSPATH
,
278 attrs
.getValue(Constants
.BUNDLE_CLASSPATH
).split(","));
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
);
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
]));
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
]));
320 .startsWith(Constants
.RESOLUTION_DIRECTIVE
)) {
322 SlcNames
.SLC_OPTIONAL
,
323 directiveValue(tokens
[i
]).equals(
324 Constants
.RESOLUTION_OPTIONAL
));
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
]));
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='" +
378 Node usesNode
= node
.addNode(SlcNames
.SLC_USES
,
379 SlcTypes
.SLC_JAVA_PACKAGE
);
380 usesNode
.setProperty(SlcNames
.SLC_NAME
, usedPackage
);
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
++) {
400 .startsWith(Constants
.BUNDLE_VERSION_ATTRIBUTE
)) {
401 node
.setProperty(SlcNames
.SLC_BUNDLE_VERSION
,
402 attributeValue(tokens
[i
]));
404 .startsWith(Constants
.RESOLUTION_DIRECTIVE
)) {
406 SlcNames
.SLC_OPTIONAL
,
407 directiveValue(tokens
[i
]).equals(
408 Constants
.RESOLUTION_OPTIONAL
));
416 private void addAttr(String key
, Node node
, Attributes attrs
)
417 throws RepositoryException
{
418 addAttr(new Name(key
), node
, attrs
);
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
);
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();
438 private String
attributeValue(String str
) {
439 return extractValue(str
, "=");
442 private String
directiveValue(String str
) {
443 return extractValue(str
, ":=");
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();
451 if (value
.startsWith("\""))
452 value
= value
.substring(1);
453 if (value
.endsWith("\""))
454 value
= value
.substring(0, value
.length() - 1);
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("");
463 for (char c
: str
.toCharArray()) {
465 if (!in
) {// new package
466 res
.add(curr
.toString());
467 curr
= new StringBuffer("");
468 } else {// a ',' within " "
471 } else if (c
== '\"') {
478 res
.add(curr
.toString());
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());
494 public void setForce(Boolean force
) {