1 package org
.argeo
.slc
.repo
;
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
;
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
;
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
;
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
;
37 * Indexes jar file, currently supports standard J2SE and OSGi metadata (both
40 public class JarFileIndexer
implements NodeIndexer
, SlcNames
{
41 private final static Log log
= LogFactory
.getLog(JarFileIndexer
.class);
42 private Boolean force
= false;
44 public Boolean
support(String path
) {
45 return FilenameUtils
.getExtension(path
).equals("jar");
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;
55 if (!support(fileNode
.getPath()))
59 if (!force
&& fileNode
.isNodeType(SlcTypes
.SLC_JAR_FILE
))
62 if (!fileNode
.isNodeType(NodeType
.NT_FILE
))
65 Session jcrSession
= fileNode
.getSession();
66 Node contentNode
= fileNode
.getNode(Node
.JCR_CONTENT
);
67 fileBinary
= contentNode
.getProperty(Property
.JCR_DATA
).getBinary();
69 jarIn
= new JarInputStream(fileBinary
.getStream());
70 Manifest manifest
= jarIn
.getManifest();
71 if (manifest
== null) {
72 log
.error(fileNode
+ " has no MANIFEST");
76 bo
= new ByteArrayOutputStream();
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 "
90 bi
= new ByteArrayInputStream(newManifest
);
91 manifestBinary
= jcrSession
.getValueFactory().createBinary(bi
);
94 fileNode
.addMixin(SlcTypes
.SLC_JAR_FILE
);
96 fileNode
.setProperty(SlcNames
.SLC_MANIFEST
, manifestBinary
);
97 Attributes attrs
= manifest
.getMainAttributes();
99 getI18nValues(fileBinary
, attrs
);
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
);
116 if (attrs
.containsKey(new Name(Constants
.BUNDLE_SYMBOLICNAME
))) {
117 addOsgiMetadata(fileNode
, attrs
);
118 if (log
.isTraceEnabled())
119 log
.trace("Indexed OSGi bundle " + fileNode
);
121 if (log
.isTraceEnabled())
122 log
.trace("Indexed JAR file " + fileNode
);
125 JcrUtils
.updateLastModified(fileNode
);
127 } catch (Exception e
) {
128 throw new SlcException("Cannot index jar " + fileNode
, e
);
130 IOUtils
.closeQuietly(bi
);
131 IOUtils
.closeQuietly(bo
);
132 IOUtils
.closeQuietly(jarIn
);
133 JcrUtils
.closeQuietly(manifestBinary
);
134 JcrUtils
.closeQuietly(fileBinary
);
139 private void getI18nValues(Binary fileBinary
, Attributes attrs
)
141 JarInputStream jarIn
= null;
143 jarIn
= new JarInputStream(fileBinary
.getStream());
144 String bundleLocalization
= null;
146 String blKey
= Constants
.BUNDLE_LOCALIZATION
; // "Bundle-Localization";
147 Name blkName
= new Name(blKey
);
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
);
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")) {
169 // if(je.getSize() != -1){
170 // propBytes = new byte[(int)je.getSize()];
171 // int len = (int) je.getSize();
173 // while (offset != len)
174 // offset += jarIn.read(propBytes, offset, len -
177 baos
= new ByteArrayOutputStream();
179 int qwe
= jarIn
.read();
184 propBytes
= baos
.toByteArray();
187 entry
= jarIn
.getNextJarEntry();
191 if (jarEntry
!= null) {
192 Properties prop
= new Properties();
193 InputStream is
= new ByteArrayInputStream(propBytes
);
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));
201 attrs
.put(obj
, newVal
);
205 } catch (RepositoryException e
) {
206 throw new SlcException(
207 "Error while reading the jar binary content " + fileBinary
,
209 } catch (IOException ioe
) {
210 throw new SlcException("unable to get internationalized values",
213 IOUtils
.closeQuietly(jarIn
);
217 protected void addOsgiMetadata(Node fileNode
, Attributes attrs
)
218 throws RepositoryException
{
220 // TODO remove this ?
221 // Compulsory for the time being, because bundle artifact extends
223 if (!fileNode
.isNodeType(SlcTypes
.SLC_ARTIFACT
)) {
224 ArtifactIndexer indexer
= new ArtifactIndexer();
225 indexer
.index(fileNode
);
228 fileNode
.addMixin(SlcTypes
.SLC_BUNDLE_ARTIFACT
);
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
);
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
);
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
)
261 if (attrs
.containsKey(new Name(Constants
.BUNDLE_CLASSPATH
)))
262 fileNode
.setProperty(SlcNames
.SLC_
+ Constants
.BUNDLE_CLASSPATH
,
263 attrs
.getValue(Constants
.BUNDLE_CLASSPATH
).split(","));
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
);
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
]));
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
]));
305 .startsWith(Constants
.RESOLUTION_DIRECTIVE
)) {
307 SlcNames
.SLC_OPTIONAL
,
308 directiveValue(tokens
[i
]).equals(
309 Constants
.RESOLUTION_OPTIONAL
));
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
]));
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='" +
363 Node usesNode
= node
.addNode(SlcNames
.SLC_USES
,
364 SlcTypes
.SLC_JAVA_PACKAGE
);
365 usesNode
.setProperty(SlcNames
.SLC_NAME
, usedPackage
);
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
++) {
385 .startsWith(Constants
.BUNDLE_VERSION_ATTRIBUTE
)) {
386 node
.setProperty(SlcNames
.SLC_BUNDLE_VERSION
,
387 attributeValue(tokens
[i
]));
389 .startsWith(Constants
.RESOLUTION_DIRECTIVE
)) {
391 SlcNames
.SLC_OPTIONAL
,
392 directiveValue(tokens
[i
]).equals(
393 Constants
.RESOLUTION_OPTIONAL
));
401 private void addAttr(String key
, Node node
, Attributes attrs
)
402 throws RepositoryException
{
403 addAttr(new Name(key
), node
, attrs
);
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
);
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();
423 private String
attributeValue(String str
) {
424 return extractValue(str
, "=");
427 private String
directiveValue(String str
) {
428 return extractValue(str
, ":=");
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();
436 if (value
.startsWith("\""))
437 value
= value
.substring(1);
438 if (value
.endsWith("\""))
439 value
= value
.substring(0, value
.length() - 1);
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("");
448 for (char c
: str
.toCharArray()) {
450 if (!in
) {// new package
451 res
.add(curr
.toString());
452 curr
= new StringBuffer("");
453 } else {// a ',' within " "
456 } else if (c
== '\"') {
463 res
.add(curr
.toString());
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());
479 public void setForce(Boolean force
) {