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
.argeo
.api
.cms
.CmsLog
;
28 import org
.argeo
.jcr
.JcrUtils
;
29 import org
.argeo
.slc
.SlcException
;
30 import org
.argeo
.slc
.SlcNames
;
31 import org
.argeo
.slc
.SlcTypes
;
32 import org
.osgi
.framework
.Constants
;
33 import org
.osgi
.framework
.Version
;
36 * Indexes jar file, currently supports standard J2SE and OSGi metadata (both
39 public class JarFileIndexer
implements NodeIndexer
, SlcNames
{
40 private final static CmsLog log
= CmsLog
.getLog(JarFileIndexer
.class);
41 private Boolean force
= false;
43 public Boolean
support(String path
) {
44 return FilenameUtils
.getExtension(path
).equals("jar");
47 public void index(Node fileNode
) {
48 Binary fileBinary
= null;
49 JarInputStream jarIn
= null;
50 ByteArrayOutputStream bo
= null;
51 ByteArrayInputStream bi
= null;
52 Binary manifestBinary
= null;
54 if (!support(fileNode
.getPath()))
58 if (!force
&& fileNode
.isNodeType(SlcTypes
.SLC_JAR_FILE
))
61 if (!fileNode
.isNodeType(NodeType
.NT_FILE
))
64 Session jcrSession
= fileNode
.getSession();
65 Node contentNode
= fileNode
.getNode(Node
.JCR_CONTENT
);
66 fileBinary
= contentNode
.getProperty(Property
.JCR_DATA
).getBinary();
68 jarIn
= new JarInputStream(fileBinary
.getStream());
69 Manifest manifest
= jarIn
.getManifest();
70 if (manifest
== null) {
71 log
.error(fileNode
+ " has no MANIFEST");
75 bo
= new ByteArrayOutputStream();
77 byte[] newManifest
= bo
.toByteArray();
78 if (fileNode
.hasProperty(SLC_MANIFEST
)) {
79 byte[] storedManifest
= JcrUtils
.getBinaryAsBytes(fileNode
.getProperty(SLC_MANIFEST
));
80 if (Arrays
.equals(newManifest
, storedManifest
)) {
81 if (log
.isTraceEnabled())
82 log
.trace("Manifest not changed, doing nothing " + fileNode
);
87 bi
= new ByteArrayInputStream(newManifest
);
88 manifestBinary
= jcrSession
.getValueFactory().createBinary(bi
);
91 fileNode
.addMixin(SlcTypes
.SLC_JAR_FILE
);
93 fileNode
.setProperty(SlcNames
.SLC_MANIFEST
, manifestBinary
);
94 Attributes attrs
= manifest
.getMainAttributes();
96 getI18nValues(fileBinary
, attrs
);
98 // standard J2SE MANIFEST attributes
99 addAttr(Attributes
.Name
.MANIFEST_VERSION
, fileNode
, attrs
);
100 addAttr(Attributes
.Name
.SIGNATURE_VERSION
, fileNode
, attrs
);
101 addAttr(Attributes
.Name
.CLASS_PATH
, fileNode
, attrs
);
102 addAttr(Attributes
.Name
.MAIN_CLASS
, fileNode
, attrs
);
103 addAttr(Attributes
.Name
.EXTENSION_NAME
, fileNode
, attrs
);
104 addAttr(Attributes
.Name
.IMPLEMENTATION_VERSION
, fileNode
, attrs
);
105 addAttr(Attributes
.Name
.IMPLEMENTATION_VENDOR
, fileNode
, attrs
);
106 addAttr(Attributes
.Name
.IMPLEMENTATION_VENDOR_ID
, fileNode
, attrs
);
107 addAttr(Attributes
.Name
.SPECIFICATION_TITLE
, fileNode
, attrs
);
108 addAttr(Attributes
.Name
.SPECIFICATION_VERSION
, fileNode
, attrs
);
109 addAttr(Attributes
.Name
.SPECIFICATION_VENDOR
, fileNode
, attrs
);
110 addAttr(Attributes
.Name
.SEALED
, fileNode
, attrs
);
113 if (attrs
.containsKey(new Name(Constants
.BUNDLE_SYMBOLICNAME
))) {
114 addOsgiMetadata(fileNode
, attrs
);
115 if (log
.isTraceEnabled())
116 log
.trace("Indexed OSGi bundle " + fileNode
);
118 if (log
.isTraceEnabled())
119 log
.trace("Indexed JAR file " + fileNode
);
122 JcrUtils
.updateLastModified(fileNode
);
124 } catch (Exception e
) {
125 throw new SlcException("Cannot index jar " + fileNode
, e
);
127 IOUtils
.closeQuietly(bi
);
128 IOUtils
.closeQuietly(bo
);
129 IOUtils
.closeQuietly(jarIn
);
130 JcrUtils
.closeQuietly(manifestBinary
);
131 JcrUtils
.closeQuietly(fileBinary
);
136 private void getI18nValues(Binary fileBinary
, Attributes attrs
) throws IOException
{
137 JarInputStream jarIn
= null;
139 jarIn
= new JarInputStream(fileBinary
.getStream());
140 String bundleLocalization
= null;
142 String blKey
= Constants
.BUNDLE_LOCALIZATION
; // "Bundle-Localization";
143 Name blkName
= new Name(blKey
);
145 browse
: for (Object obj
: attrs
.keySet()) {
146 String value
= attrs
.getValue((Attributes
.Name
) obj
);
147 if (value
.startsWith("%")) {
148 if (attrs
.containsKey(blkName
)) {
149 bundleLocalization
= attrs
.getValue(blkName
);
155 JarEntry jarEntry
= null;
156 byte[] propBytes
= null;
157 ByteArrayOutputStream baos
= null;
158 browse
: if (bundleLocalization
!= null) {
159 JarEntry entry
= jarIn
.getNextJarEntry();
160 while (entry
!= null) {
161 if (entry
.getName().equals(bundleLocalization
+ ".properties")) {
164 // if(je.getSize() != -1){
165 // propBytes = new byte[(int)je.getSize()];
166 // int len = (int) je.getSize();
168 // while (offset != len)
169 // offset += jarIn.read(propBytes, offset, len -
172 baos
= new ByteArrayOutputStream();
174 int qwe
= jarIn
.read();
179 propBytes
= baos
.toByteArray();
182 entry
= jarIn
.getNextJarEntry();
186 if (jarEntry
!= null) {
187 Properties prop
= new Properties();
188 InputStream is
= new ByteArrayInputStream(propBytes
);
191 for (Object obj
: attrs
.keySet()) {
192 String value
= attrs
.getValue((Attributes
.Name
) obj
);
193 if (value
.startsWith("%")) {
194 String newVal
= prop
.getProperty(value
.substring(1));
196 attrs
.put(obj
, newVal
);
200 } catch (RepositoryException e
) {
201 throw new SlcException("Error while reading the jar binary content " + fileBinary
, e
);
202 } catch (IOException ioe
) {
203 throw new SlcException("unable to get internationalized values", ioe
);
205 IOUtils
.closeQuietly(jarIn
);
209 protected void addOsgiMetadata(Node fileNode
, Attributes attrs
) throws RepositoryException
{
211 // TODO remove this ?
212 // Compulsory for the time being, because bundle artifact extends
214 if (!fileNode
.isNodeType(SlcTypes
.SLC_ARTIFACT
)) {
215 ArtifactIndexer indexer
= new ArtifactIndexer();
216 indexer
.index(fileNode
);
219 fileNode
.addMixin(SlcTypes
.SLC_BUNDLE_ARTIFACT
);
222 String symbolicName
= attrs
.getValue(Constants
.BUNDLE_SYMBOLICNAME
);
223 // make sure there is no directive
224 symbolicName
= symbolicName
.split(";")[0];
225 fileNode
.setProperty(SlcNames
.SLC_SYMBOLIC_NAME
, symbolicName
);
228 addAttr(Constants
.BUNDLE_SYMBOLICNAME
, fileNode
, attrs
);
229 addAttr(Constants
.BUNDLE_NAME
, fileNode
, attrs
);
230 addAttr(Constants
.BUNDLE_DESCRIPTION
, fileNode
, attrs
);
231 addAttr(Constants
.BUNDLE_MANIFESTVERSION
, fileNode
, attrs
);
232 addAttr(Constants
.BUNDLE_CATEGORY
, fileNode
, attrs
);
233 addAttr(Constants
.BUNDLE_ACTIVATIONPOLICY
, fileNode
, attrs
);
234 addAttr(Constants
.BUNDLE_COPYRIGHT
, fileNode
, attrs
);
235 addAttr(Constants
.BUNDLE_VENDOR
, fileNode
, attrs
);
236 addAttr("Bundle-License", fileNode
, attrs
);
237 addAttr(Constants
.BUNDLE_DOCURL
, fileNode
, attrs
);
238 addAttr(Constants
.BUNDLE_CONTACTADDRESS
, fileNode
, attrs
);
239 addAttr(Constants
.BUNDLE_ACTIVATOR
, fileNode
, attrs
);
240 addAttr(Constants
.BUNDLE_UPDATELOCATION
, fileNode
, attrs
);
241 addAttr(Constants
.BUNDLE_LOCALIZATION
, fileNode
, attrs
);
243 // required execution environment
244 if (attrs
.containsKey(new Name(Constants
.BUNDLE_REQUIREDEXECUTIONENVIRONMENT
)))
245 fileNode
.setProperty(SlcNames
.SLC_
+ Constants
.BUNDLE_REQUIREDEXECUTIONENVIRONMENT
,
246 attrs
.getValue(Constants
.BUNDLE_REQUIREDEXECUTIONENVIRONMENT
).split(","));
249 if (attrs
.containsKey(new Name(Constants
.BUNDLE_CLASSPATH
)))
250 fileNode
.setProperty(SlcNames
.SLC_
+ Constants
.BUNDLE_CLASSPATH
,
251 attrs
.getValue(Constants
.BUNDLE_CLASSPATH
).split(","));
254 Version version
= new Version(attrs
.getValue(Constants
.BUNDLE_VERSION
));
255 fileNode
.setProperty(SlcNames
.SLC_BUNDLE_VERSION
, version
.toString());
256 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.BUNDLE_VERSION
);
257 Node bundleVersionNode
= fileNode
.addNode(SlcNames
.SLC_
+ Constants
.BUNDLE_VERSION
, SlcTypes
.SLC_OSGI_VERSION
);
258 mapOsgiVersion(version
, bundleVersionNode
);
261 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.FRAGMENT_HOST
);
262 if (attrs
.containsKey(new Name(Constants
.FRAGMENT_HOST
))) {
263 String fragmentHost
= attrs
.getValue(Constants
.FRAGMENT_HOST
);
264 String
[] tokens
= fragmentHost
.split(";");
265 Node node
= fileNode
.addNode(SlcNames
.SLC_
+ Constants
.FRAGMENT_HOST
, SlcTypes
.SLC_FRAGMENT_HOST
);
266 node
.setProperty(SlcNames
.SLC_SYMBOLIC_NAME
, tokens
[0]);
267 for (int i
= 1; i
< tokens
.length
; i
++) {
268 if (tokens
[i
].startsWith(Constants
.BUNDLE_VERSION_ATTRIBUTE
)) {
269 node
.setProperty(SlcNames
.SLC_BUNDLE_VERSION
, attributeValue(tokens
[i
]));
275 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.IMPORT_PACKAGE
);
276 if (attrs
.containsKey(new Name(Constants
.IMPORT_PACKAGE
))) {
277 String importPackages
= attrs
.getValue(Constants
.IMPORT_PACKAGE
);
278 List
<String
> packages
= parseCommaSeparated(importPackages
);
279 for (String pkg
: packages
) {
280 String
[] tokens
= pkg
.split(";");
281 Node node
= fileNode
.addNode(SlcNames
.SLC_
+ Constants
.IMPORT_PACKAGE
, SlcTypes
.SLC_IMPORTED_PACKAGE
);
282 node
.setProperty(SlcNames
.SLC_NAME
, tokens
[0]);
283 for (int i
= 1; i
< tokens
.length
; i
++) {
284 if (tokens
[i
].startsWith(Constants
.VERSION_ATTRIBUTE
)) {
285 node
.setProperty(SlcNames
.SLC_VERSION
, attributeValue(tokens
[i
]));
286 } else if (tokens
[i
].startsWith(Constants
.RESOLUTION_DIRECTIVE
)) {
287 node
.setProperty(SlcNames
.SLC_OPTIONAL
,
288 directiveValue(tokens
[i
]).equals(Constants
.RESOLUTION_OPTIONAL
));
294 // dynamic import package
295 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.DYNAMICIMPORT_PACKAGE
);
296 if (attrs
.containsKey(new Name(Constants
.DYNAMICIMPORT_PACKAGE
))) {
297 String importPackages
= attrs
.getValue(Constants
.DYNAMICIMPORT_PACKAGE
);
298 List
<String
> packages
= parseCommaSeparated(importPackages
);
299 for (String pkg
: packages
) {
300 String
[] tokens
= pkg
.split(";");
301 Node node
= fileNode
.addNode(SlcNames
.SLC_
+ Constants
.DYNAMICIMPORT_PACKAGE
,
302 SlcTypes
.SLC_DYNAMIC_IMPORTED_PACKAGE
);
303 node
.setProperty(SlcNames
.SLC_NAME
, tokens
[0]);
304 for (int i
= 1; i
< tokens
.length
; i
++) {
305 if (tokens
[i
].startsWith(Constants
.VERSION_ATTRIBUTE
)) {
306 node
.setProperty(SlcNames
.SLC_VERSION
, attributeValue(tokens
[i
]));
313 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.EXPORT_PACKAGE
);
314 if (attrs
.containsKey(new Name(Constants
.EXPORT_PACKAGE
))) {
315 String exportPackages
= attrs
.getValue(Constants
.EXPORT_PACKAGE
);
316 List
<String
> packages
= parseCommaSeparated(exportPackages
);
317 for (String pkg
: packages
) {
318 String
[] tokens
= pkg
.split(";");
319 Node node
= fileNode
.addNode(SlcNames
.SLC_
+ Constants
.EXPORT_PACKAGE
, SlcTypes
.SLC_EXPORTED_PACKAGE
);
320 node
.setProperty(SlcNames
.SLC_NAME
, tokens
[0]);
321 // TODO: are these cleans really necessary?
322 cleanSubNodes(node
, SlcNames
.SLC_USES
);
323 cleanSubNodes(node
, SlcNames
.SLC_VERSION
);
324 for (int i
= 1; i
< tokens
.length
; i
++) {
325 if (tokens
[i
].startsWith(Constants
.VERSION_ATTRIBUTE
)) {
326 String versionStr
= attributeValue(tokens
[i
]);
327 Node versionNode
= node
.addNode(SlcNames
.SLC_VERSION
, SlcTypes
.SLC_OSGI_VERSION
);
328 mapOsgiVersion(new Version(versionStr
), versionNode
);
329 } else if (tokens
[i
].startsWith(Constants
.USES_DIRECTIVE
)) {
330 String usedPackages
= directiveValue(tokens
[i
]);
331 // log.debug("uses='" + usedPackages + "'");
332 for (String usedPackage
: usedPackages
.split(",")) {
333 // log.debug("usedPackage='" +
336 Node usesNode
= node
.addNode(SlcNames
.SLC_USES
, SlcTypes
.SLC_JAVA_PACKAGE
);
337 usesNode
.setProperty(SlcNames
.SLC_NAME
, usedPackage
);
345 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.REQUIRE_BUNDLE
);
346 if (attrs
.containsKey(new Name(Constants
.REQUIRE_BUNDLE
))) {
347 String requireBundle
= attrs
.getValue(Constants
.REQUIRE_BUNDLE
);
348 List
<String
> bundles
= parseCommaSeparated(requireBundle
);
349 for (String bundle
: bundles
) {
350 String
[] tokens
= bundle
.split(";");
351 Node node
= fileNode
.addNode(SlcNames
.SLC_
+ Constants
.REQUIRE_BUNDLE
, SlcTypes
.SLC_REQUIRED_BUNDLE
);
352 node
.setProperty(SlcNames
.SLC_SYMBOLIC_NAME
, tokens
[0]);
353 for (int i
= 1; i
< tokens
.length
; i
++) {
354 if (tokens
[i
].startsWith(Constants
.BUNDLE_VERSION_ATTRIBUTE
)) {
355 node
.setProperty(SlcNames
.SLC_BUNDLE_VERSION
, attributeValue(tokens
[i
]));
356 } else if (tokens
[i
].startsWith(Constants
.RESOLUTION_DIRECTIVE
)) {
357 node
.setProperty(SlcNames
.SLC_OPTIONAL
,
358 directiveValue(tokens
[i
]).equals(Constants
.RESOLUTION_OPTIONAL
));
366 private void addAttr(String key
, Node node
, Attributes attrs
) throws RepositoryException
{
367 addAttr(new Name(key
), node
, attrs
);
370 private void addAttr(Name key
, Node node
, Attributes attrs
) throws RepositoryException
{
371 if (attrs
.containsKey(key
)) {
372 String value
= attrs
.getValue(key
);
373 node
.setProperty(SlcNames
.SLC_
+ key
, value
);
377 private void cleanSubNodes(Node node
, String name
) throws RepositoryException
{
378 if (node
.hasNode(name
)) {
379 NodeIterator nit
= node
.getNodes(name
);
380 while (nit
.hasNext())
381 nit
.nextNode().remove();
385 private String
attributeValue(String str
) {
386 return extractValue(str
, "=");
389 private String
directiveValue(String str
) {
390 return extractValue(str
, ":=");
393 private String
extractValue(String str
, String eq
) {
394 String
[] tokens
= str
.split(eq
);
395 // String key = tokens[0];
396 String value
= tokens
[1].trim();
398 if (value
.startsWith("\""))
399 value
= value
.substring(1);
400 if (value
.endsWith("\""))
401 value
= value
.substring(0, value
.length() - 1);
405 /** Parse package list with nested directive with ',' */
406 private List
<String
> parseCommaSeparated(String str
) {
407 List
<String
> res
= new ArrayList
<String
>();
408 StringBuffer curr
= new StringBuffer("");
410 for (char c
: str
.toCharArray()) {
412 if (!in
) {// new package
413 res
.add(curr
.toString());
414 curr
= new StringBuffer("");
415 } else {// a ',' within " "
418 } else if (c
== '\"') {
425 res
.add(curr
.toString());
430 protected void mapOsgiVersion(Version version
, Node versionNode
) throws RepositoryException
{
431 versionNode
.setProperty(SlcNames
.SLC_AS_STRING
, version
.toString());
432 versionNode
.setProperty(SlcNames
.SLC_MAJOR
, version
.getMajor());
433 versionNode
.setProperty(SlcNames
.SLC_MINOR
, version
.getMinor());
434 versionNode
.setProperty(SlcNames
.SLC_MICRO
, version
.getMicro());
435 if (!version
.getQualifier().equals(""))
436 versionNode
.setProperty(SlcNames
.SLC_QUALIFIER
, version
.getQualifier());
439 public void setForce(Boolean force
) {