2 * Copyright (C) 2007-2012 Mathieu Baudier
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
.util
.ArrayList
;
21 import java
.util
.Arrays
;
22 import java
.util
.List
;
23 import java
.util
.jar
.Attributes
;
24 import java
.util
.jar
.Attributes
.Name
;
25 import java
.util
.jar
.JarInputStream
;
26 import java
.util
.jar
.Manifest
;
28 import javax
.jcr
.Binary
;
29 import javax
.jcr
.Node
;
30 import javax
.jcr
.NodeIterator
;
31 import javax
.jcr
.Property
;
32 import javax
.jcr
.RepositoryException
;
33 import javax
.jcr
.Session
;
34 import javax
.jcr
.nodetype
.NodeType
;
36 import org
.apache
.commons
.io
.FilenameUtils
;
37 import org
.apache
.commons
.io
.IOUtils
;
38 import org
.apache
.commons
.logging
.Log
;
39 import org
.apache
.commons
.logging
.LogFactory
;
40 import org
.argeo
.jcr
.JcrUtils
;
41 import org
.argeo
.slc
.SlcException
;
42 import org
.argeo
.slc
.jcr
.SlcNames
;
43 import org
.argeo
.slc
.jcr
.SlcTypes
;
44 import org
.osgi
.framework
.Constants
;
45 import org
.osgi
.framework
.Version
;
48 * Indexes jar file, currently supports standard J2SE and OSGi metadata (both
51 public class JarFileIndexer
implements NodeIndexer
, SlcNames
{
52 private final static Log log
= LogFactory
.getLog(JarFileIndexer
.class);
54 public Boolean
support(String path
) {
55 return FilenameUtils
.getExtension(path
).equals("jar");
58 public void index(Node fileNode
) {
59 Binary fileBinary
= null;
60 JarInputStream jarIn
= null;
61 ByteArrayOutputStream bo
= null;
62 ByteArrayInputStream bi
= null;
63 Binary manifestBinary
= null;
65 if (!fileNode
.isNodeType(NodeType
.NT_FILE
))
68 Session jcrSession
= fileNode
.getSession();
69 Node contentNode
= fileNode
.getNode(Node
.JCR_CONTENT
);
70 fileBinary
= contentNode
.getProperty(Property
.JCR_DATA
).getBinary();
72 // if (!FilenameUtils.isExtension(fileNode.getName(), "jar")) {
76 jarIn
= new JarInputStream(fileBinary
.getStream());
77 Manifest manifest
= jarIn
.getManifest();
78 if (manifest
== null) {
79 log
.error(fileNode
+ " has no MANIFEST");
83 bo
= new ByteArrayOutputStream();
85 byte[] newManifest
= bo
.toByteArray();
86 if (fileNode
.hasProperty(SLC_MANIFEST
)) {
87 byte[] storedManifest
= JcrUtils
.getBinaryAsBytes(fileNode
88 .getProperty(SLC_MANIFEST
));
89 if (Arrays
.equals(newManifest
, storedManifest
)) {
90 if (log
.isTraceEnabled())
91 log
.trace("Manifest not changed, doing nothing "
97 bi
= new ByteArrayInputStream(newManifest
);
98 manifestBinary
= jcrSession
.getValueFactory().createBinary(bi
);
101 fileNode
.addMixin(SlcTypes
.SLC_JAR_FILE
);
103 fileNode
.setProperty(SlcNames
.SLC_MANIFEST
, manifestBinary
);
104 Attributes attrs
= manifest
.getMainAttributes();
105 if (log
.isTraceEnabled())
106 for (Object key
: attrs
.keySet())
107 log
.trace(key
+ ": " + attrs
.getValue(key
.toString()));
109 // standard J2SE MANIFEST attributes
110 addAttr(Attributes
.Name
.MANIFEST_VERSION
, fileNode
, attrs
);
111 addAttr(Attributes
.Name
.SIGNATURE_VERSION
, fileNode
, attrs
);
112 addAttr(Attributes
.Name
.CLASS_PATH
, fileNode
, attrs
);
113 addAttr(Attributes
.Name
.MAIN_CLASS
, fileNode
, attrs
);
114 addAttr(Attributes
.Name
.EXTENSION_NAME
, fileNode
, attrs
);
115 addAttr(Attributes
.Name
.IMPLEMENTATION_VERSION
, fileNode
, attrs
);
116 addAttr(Attributes
.Name
.IMPLEMENTATION_VENDOR
, fileNode
, attrs
);
117 addAttr(Attributes
.Name
.IMPLEMENTATION_VENDOR_ID
, fileNode
, attrs
);
118 addAttr(Attributes
.Name
.SPECIFICATION_TITLE
, fileNode
, attrs
);
119 addAttr(Attributes
.Name
.SPECIFICATION_VERSION
, fileNode
, attrs
);
120 addAttr(Attributes
.Name
.SPECIFICATION_VENDOR
, fileNode
, attrs
);
121 addAttr(Attributes
.Name
.SEALED
, fileNode
, attrs
);
124 if (attrs
.containsKey(new Name(Constants
.BUNDLE_SYMBOLICNAME
))) {
125 addOsgiMetadata(fileNode
, attrs
);
126 JcrUtils
.updateLastModified(fileNode
);
127 if (log
.isTraceEnabled())
128 log
.trace("Indexed OSGi bundle " + fileNode
);
130 JcrUtils
.updateLastModified(fileNode
);
131 if (log
.isTraceEnabled())
132 log
.trace("Indexed JAR file " + fileNode
);
135 } catch (Exception e
) {
136 throw new SlcException("Cannot index jar " + fileNode
, e
);
138 IOUtils
.closeQuietly(bi
);
139 IOUtils
.closeQuietly(bo
);
140 IOUtils
.closeQuietly(jarIn
);
141 JcrUtils
.closeQuietly(manifestBinary
);
142 JcrUtils
.closeQuietly(fileBinary
);
147 protected void addOsgiMetadata(Node fileNode
, Attributes attrs
)
148 throws RepositoryException
{
149 fileNode
.addMixin(SlcTypes
.SLC_BUNDLE_ARTIFACT
);
152 String symbolicName
= attrs
.getValue(Constants
.BUNDLE_SYMBOLICNAME
);
153 // make sure there is no directive
154 symbolicName
= symbolicName
.split(";")[0];
155 fileNode
.setProperty(SlcNames
.SLC_SYMBOLIC_NAME
, symbolicName
);
158 addAttr(Constants
.BUNDLE_SYMBOLICNAME
, fileNode
, attrs
);
159 addAttr(Constants
.BUNDLE_NAME
, fileNode
, attrs
);
160 addAttr(Constants
.BUNDLE_DESCRIPTION
, fileNode
, attrs
);
161 addAttr(Constants
.BUNDLE_MANIFESTVERSION
, fileNode
, attrs
);
162 addAttr(Constants
.BUNDLE_CATEGORY
, fileNode
, attrs
);
163 addAttr(Constants
.BUNDLE_ACTIVATIONPOLICY
, fileNode
, attrs
);
164 addAttr(Constants
.BUNDLE_COPYRIGHT
, fileNode
, attrs
);
165 addAttr(Constants
.BUNDLE_VENDOR
, fileNode
, attrs
);
166 addAttr("Bundle-License", fileNode
, attrs
);
167 addAttr(Constants
.BUNDLE_DOCURL
, fileNode
, attrs
);
168 addAttr(Constants
.BUNDLE_CONTACTADDRESS
, fileNode
, attrs
);
169 addAttr(Constants
.BUNDLE_ACTIVATOR
, fileNode
, attrs
);
170 addAttr(Constants
.BUNDLE_UPDATELOCATION
, fileNode
, attrs
);
171 addAttr(Constants
.BUNDLE_LOCALIZATION
, fileNode
, attrs
);
173 // required execution environment
174 if (attrs
.containsKey(new Name(
175 Constants
.BUNDLE_REQUIREDEXECUTIONENVIRONMENT
)))
176 fileNode
.setProperty(SlcNames
.SLC_
177 + Constants
.BUNDLE_REQUIREDEXECUTIONENVIRONMENT
, attrs
178 .getValue(Constants
.BUNDLE_REQUIREDEXECUTIONENVIRONMENT
)
182 if (attrs
.containsKey(new Name(Constants
.BUNDLE_CLASSPATH
)))
183 fileNode
.setProperty(SlcNames
.SLC_
+ Constants
.BUNDLE_CLASSPATH
,
184 attrs
.getValue(Constants
.BUNDLE_CLASSPATH
).split(","));
187 Version version
= new Version(attrs
.getValue(Constants
.BUNDLE_VERSION
));
188 fileNode
.setProperty(SlcNames
.SLC_BUNDLE_VERSION
, version
.toString());
189 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.BUNDLE_VERSION
);
190 Node bundleVersionNode
= fileNode
.addNode(SlcNames
.SLC_
191 + Constants
.BUNDLE_VERSION
, SlcTypes
.SLC_OSGI_VERSION
);
192 mapOsgiVersion(version
, bundleVersionNode
);
195 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.FRAGMENT_HOST
);
196 if (attrs
.containsKey(new Name(Constants
.FRAGMENT_HOST
))) {
197 String fragmentHost
= attrs
.getValue(Constants
.FRAGMENT_HOST
);
198 String
[] tokens
= fragmentHost
.split(";");
199 Node node
= fileNode
.addNode(SlcNames
.SLC_
200 + Constants
.FRAGMENT_HOST
, SlcTypes
.SLC_FRAGMENT_HOST
);
201 node
.setProperty(SlcNames
.SLC_SYMBOLIC_NAME
, tokens
[0]);
202 for (int i
= 1; i
< tokens
.length
; i
++) {
203 if (tokens
[i
].startsWith(Constants
.BUNDLE_VERSION_ATTRIBUTE
)) {
204 node
.setProperty(SlcNames
.SLC_BUNDLE_VERSION
,
205 attributeValue(tokens
[i
]));
211 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.IMPORT_PACKAGE
);
212 if (attrs
.containsKey(new Name(Constants
.IMPORT_PACKAGE
))) {
213 String importPackages
= attrs
.getValue(Constants
.IMPORT_PACKAGE
);
214 List
<String
> packages
= parseCommaSeparated(importPackages
);
215 for (String pkg
: packages
) {
216 String
[] tokens
= pkg
.split(";");
217 Node node
= fileNode
.addNode(SlcNames
.SLC_
218 + Constants
.IMPORT_PACKAGE
,
219 SlcTypes
.SLC_IMPORTED_PACKAGE
);
220 node
.setProperty(SlcNames
.SLC_NAME
, tokens
[0]);
221 for (int i
= 1; i
< tokens
.length
; i
++) {
222 if (tokens
[i
].startsWith(Constants
.VERSION_ATTRIBUTE
)) {
223 node
.setProperty(SlcNames
.SLC_VERSION
,
224 attributeValue(tokens
[i
]));
226 .startsWith(Constants
.RESOLUTION_DIRECTIVE
)) {
228 SlcNames
.SLC_OPTIONAL
,
229 directiveValue(tokens
[i
]).equals(
230 Constants
.RESOLUTION_OPTIONAL
));
236 // dynamic import package
237 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.DYNAMICIMPORT_PACKAGE
);
238 if (attrs
.containsKey(new Name(Constants
.DYNAMICIMPORT_PACKAGE
))) {
239 String importPackages
= attrs
240 .getValue(Constants
.DYNAMICIMPORT_PACKAGE
);
241 List
<String
> packages
= parseCommaSeparated(importPackages
);
242 for (String pkg
: packages
) {
243 String
[] tokens
= pkg
.split(";");
244 Node node
= fileNode
.addNode(SlcNames
.SLC_
245 + Constants
.DYNAMICIMPORT_PACKAGE
,
246 SlcTypes
.SLC_DYNAMIC_IMPORTED_PACKAGE
);
247 node
.setProperty(SlcNames
.SLC_NAME
, tokens
[0]);
248 for (int i
= 1; i
< tokens
.length
; i
++) {
249 if (tokens
[i
].startsWith(Constants
.VERSION_ATTRIBUTE
)) {
250 node
.setProperty(SlcNames
.SLC_VERSION
,
251 attributeValue(tokens
[i
]));
258 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.EXPORT_PACKAGE
);
259 if (attrs
.containsKey(new Name(Constants
.EXPORT_PACKAGE
))) {
260 String exportPackages
= attrs
.getValue(Constants
.EXPORT_PACKAGE
);
261 List
<String
> packages
= parseCommaSeparated(exportPackages
);
262 for (String pkg
: packages
) {
263 String
[] tokens
= pkg
.split(";");
264 Node node
= fileNode
.addNode(SlcNames
.SLC_
265 + Constants
.EXPORT_PACKAGE
,
266 SlcTypes
.SLC_EXPORTED_PACKAGE
);
267 node
.setProperty(SlcNames
.SLC_NAME
, tokens
[0]);
268 // TODO: are these cleans really necessary?
269 cleanSubNodes(node
, SlcNames
.SLC_USES
);
270 cleanSubNodes(node
, SlcNames
.SLC_VERSION
);
271 for (int i
= 1; i
< tokens
.length
; i
++) {
272 if (tokens
[i
].startsWith(Constants
.VERSION_ATTRIBUTE
)) {
273 String versionStr
= attributeValue(tokens
[i
]);
274 Node versionNode
= node
.addNode(SlcNames
.SLC_VERSION
,
275 SlcTypes
.SLC_OSGI_VERSION
);
276 mapOsgiVersion(new Version(versionStr
), versionNode
);
277 } else if (tokens
[i
].startsWith(Constants
.USES_DIRECTIVE
)) {
278 String usedPackages
= directiveValue(tokens
[i
]);
279 // log.debug("uses='" + usedPackages + "'");
280 for (String usedPackage
: usedPackages
.split(",")) {
281 // log.debug("usedPackage='" +
284 Node usesNode
= node
.addNode(SlcNames
.SLC_USES
,
285 SlcTypes
.SLC_JAVA_PACKAGE
);
286 usesNode
.setProperty(SlcNames
.SLC_NAME
, usedPackage
);
294 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.REQUIRE_BUNDLE
);
295 if (attrs
.containsKey(new Name(Constants
.REQUIRE_BUNDLE
))) {
296 String requireBundle
= attrs
.getValue(Constants
.REQUIRE_BUNDLE
);
297 List
<String
> bundles
= parseCommaSeparated(requireBundle
);
298 for (String bundle
: bundles
) {
299 String
[] tokens
= bundle
.split(";");
300 Node node
= fileNode
.addNode(SlcNames
.SLC_
301 + Constants
.REQUIRE_BUNDLE
,
302 SlcTypes
.SLC_REQUIRED_BUNDLE
);
303 node
.setProperty(SlcNames
.SLC_SYMBOLIC_NAME
, tokens
[0]);
304 for (int i
= 1; i
< tokens
.length
; i
++) {
306 .startsWith(Constants
.BUNDLE_VERSION_ATTRIBUTE
)) {
307 node
.setProperty(SlcNames
.SLC_BUNDLE_VERSION
,
308 attributeValue(tokens
[i
]));
310 .startsWith(Constants
.RESOLUTION_DIRECTIVE
)) {
312 SlcNames
.SLC_OPTIONAL
,
313 directiveValue(tokens
[i
]).equals(
314 Constants
.RESOLUTION_OPTIONAL
));
322 private void addAttr(String key
, Node node
, Attributes attrs
)
323 throws RepositoryException
{
324 addAttr(new Name(key
), node
, attrs
);
327 private void addAttr(Name key
, Node node
, Attributes attrs
)
328 throws RepositoryException
{
329 if (attrs
.containsKey(key
)) {
330 String value
= attrs
.getValue(key
);
331 node
.setProperty(SlcNames
.SLC_
+ key
, value
);
335 private void cleanSubNodes(Node node
, String name
)
336 throws RepositoryException
{
337 if (node
.hasNode(name
)) {
338 NodeIterator nit
= node
.getNodes(name
);
339 while (nit
.hasNext())
340 nit
.nextNode().remove();
344 private String
attributeValue(String str
) {
345 return extractValue(str
, "=");
348 private String
directiveValue(String str
) {
349 return extractValue(str
, ":=");
352 private String
extractValue(String str
, String eq
) {
353 String
[] tokens
= str
.split(eq
);
354 // String key = tokens[0];
355 String value
= tokens
[1].trim();
357 if (value
.startsWith("\""))
358 value
= value
.substring(1);
359 if (value
.endsWith("\""))
360 value
= value
.substring(0, value
.length() - 1);
364 /** Parse package list with nested directive with ',' */
365 private List
<String
> parseCommaSeparated(String str
) {
366 List
<String
> res
= new ArrayList
<String
>();
367 StringBuffer curr
= new StringBuffer("");
369 for (char c
: str
.toCharArray()) {
371 if (!in
) {// new package
372 res
.add(curr
.toString());
373 curr
= new StringBuffer("");
374 } else {// a ',' within " "
377 } else if (c
== '\"') {
384 res
.add(curr
.toString());
389 protected void mapOsgiVersion(Version version
, Node versionNode
)
390 throws RepositoryException
{
391 versionNode
.setProperty(SlcNames
.SLC_AS_STRING
, version
.toString());
392 versionNode
.setProperty(SlcNames
.SLC_MAJOR
, version
.getMajor());
393 versionNode
.setProperty(SlcNames
.SLC_MINOR
, version
.getMinor());
394 versionNode
.setProperty(SlcNames
.SLC_MICRO
, version
.getMicro());
395 if (!version
.getQualifier().equals(""))
396 versionNode
.setProperty(SlcNames
.SLC_QUALIFIER
,
397 version
.getQualifier());