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
.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(!support(fileNode
.getPath()))
68 if (!fileNode
.isNodeType(NodeType
.NT_FILE
))
71 Session jcrSession
= fileNode
.getSession();
72 Node contentNode
= fileNode
.getNode(Node
.JCR_CONTENT
);
73 fileBinary
= contentNode
.getProperty(Property
.JCR_DATA
).getBinary();
75 // if (!FilenameUtils.isExtension(fileNode.getName(), "jar")) {
79 jarIn
= new JarInputStream(fileBinary
.getStream());
80 Manifest manifest
= jarIn
.getManifest();
81 if (manifest
== null) {
82 log
.error(fileNode
+ " has no MANIFEST");
86 bo
= new ByteArrayOutputStream();
88 byte[] newManifest
= bo
.toByteArray();
89 if (fileNode
.hasProperty(SLC_MANIFEST
)) {
90 byte[] storedManifest
= JcrUtils
.getBinaryAsBytes(fileNode
91 .getProperty(SLC_MANIFEST
));
92 if (Arrays
.equals(newManifest
, storedManifest
)) {
93 if (log
.isTraceEnabled())
94 log
.trace("Manifest not changed, doing nothing "
100 bi
= new ByteArrayInputStream(newManifest
);
101 manifestBinary
= jcrSession
.getValueFactory().createBinary(bi
);
104 fileNode
.addMixin(SlcTypes
.SLC_JAR_FILE
);
106 fileNode
.setProperty(SlcNames
.SLC_MANIFEST
, manifestBinary
);
107 Attributes attrs
= manifest
.getMainAttributes();
108 if (log
.isTraceEnabled())
109 for (Object key
: attrs
.keySet())
110 log
.trace(key
+ ": " + attrs
.getValue(key
.toString()));
112 // standard J2SE MANIFEST attributes
113 addAttr(Attributes
.Name
.MANIFEST_VERSION
, fileNode
, attrs
);
114 addAttr(Attributes
.Name
.SIGNATURE_VERSION
, fileNode
, attrs
);
115 addAttr(Attributes
.Name
.CLASS_PATH
, fileNode
, attrs
);
116 addAttr(Attributes
.Name
.MAIN_CLASS
, fileNode
, attrs
);
117 addAttr(Attributes
.Name
.EXTENSION_NAME
, fileNode
, attrs
);
118 addAttr(Attributes
.Name
.IMPLEMENTATION_VERSION
, fileNode
, attrs
);
119 addAttr(Attributes
.Name
.IMPLEMENTATION_VENDOR
, fileNode
, attrs
);
120 addAttr(Attributes
.Name
.IMPLEMENTATION_VENDOR_ID
, fileNode
, attrs
);
121 addAttr(Attributes
.Name
.SPECIFICATION_TITLE
, fileNode
, attrs
);
122 addAttr(Attributes
.Name
.SPECIFICATION_VERSION
, fileNode
, attrs
);
123 addAttr(Attributes
.Name
.SPECIFICATION_VENDOR
, fileNode
, attrs
);
124 addAttr(Attributes
.Name
.SEALED
, fileNode
, attrs
);
127 if (attrs
.containsKey(new Name(Constants
.BUNDLE_SYMBOLICNAME
))) {
128 addOsgiMetadata(fileNode
, attrs
);
129 JcrUtils
.updateLastModified(fileNode
);
130 if (log
.isTraceEnabled())
131 log
.trace("Indexed OSGi bundle " + fileNode
);
133 JcrUtils
.updateLastModified(fileNode
);
134 if (log
.isTraceEnabled())
135 log
.trace("Indexed JAR file " + fileNode
);
138 } catch (Exception e
) {
139 throw new SlcException("Cannot index jar " + fileNode
, e
);
141 IOUtils
.closeQuietly(bi
);
142 IOUtils
.closeQuietly(bo
);
143 IOUtils
.closeQuietly(jarIn
);
144 JcrUtils
.closeQuietly(manifestBinary
);
145 JcrUtils
.closeQuietly(fileBinary
);
150 protected void addOsgiMetadata(Node fileNode
, Attributes attrs
)
151 throws RepositoryException
{
152 fileNode
.addMixin(SlcTypes
.SLC_BUNDLE_ARTIFACT
);
155 String symbolicName
= attrs
.getValue(Constants
.BUNDLE_SYMBOLICNAME
);
156 // make sure there is no directive
157 symbolicName
= symbolicName
.split(";")[0];
158 fileNode
.setProperty(SlcNames
.SLC_SYMBOLIC_NAME
, symbolicName
);
161 addAttr(Constants
.BUNDLE_SYMBOLICNAME
, fileNode
, attrs
);
162 addAttr(Constants
.BUNDLE_NAME
, fileNode
, attrs
);
163 addAttr(Constants
.BUNDLE_DESCRIPTION
, fileNode
, attrs
);
164 addAttr(Constants
.BUNDLE_MANIFESTVERSION
, fileNode
, attrs
);
165 addAttr(Constants
.BUNDLE_CATEGORY
, fileNode
, attrs
);
166 addAttr(Constants
.BUNDLE_ACTIVATIONPOLICY
, fileNode
, attrs
);
167 addAttr(Constants
.BUNDLE_COPYRIGHT
, fileNode
, attrs
);
168 addAttr(Constants
.BUNDLE_VENDOR
, fileNode
, attrs
);
169 addAttr("Bundle-License", fileNode
, attrs
);
170 addAttr(Constants
.BUNDLE_DOCURL
, fileNode
, attrs
);
171 addAttr(Constants
.BUNDLE_CONTACTADDRESS
, fileNode
, attrs
);
172 addAttr(Constants
.BUNDLE_ACTIVATOR
, fileNode
, attrs
);
173 addAttr(Constants
.BUNDLE_UPDATELOCATION
, fileNode
, attrs
);
174 addAttr(Constants
.BUNDLE_LOCALIZATION
, fileNode
, attrs
);
176 // required execution environment
177 if (attrs
.containsKey(new Name(
178 Constants
.BUNDLE_REQUIREDEXECUTIONENVIRONMENT
)))
179 fileNode
.setProperty(SlcNames
.SLC_
180 + Constants
.BUNDLE_REQUIREDEXECUTIONENVIRONMENT
, attrs
181 .getValue(Constants
.BUNDLE_REQUIREDEXECUTIONENVIRONMENT
)
185 if (attrs
.containsKey(new Name(Constants
.BUNDLE_CLASSPATH
)))
186 fileNode
.setProperty(SlcNames
.SLC_
+ Constants
.BUNDLE_CLASSPATH
,
187 attrs
.getValue(Constants
.BUNDLE_CLASSPATH
).split(","));
190 Version version
= new Version(attrs
.getValue(Constants
.BUNDLE_VERSION
));
191 fileNode
.setProperty(SlcNames
.SLC_BUNDLE_VERSION
, version
.toString());
192 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.BUNDLE_VERSION
);
193 Node bundleVersionNode
= fileNode
.addNode(SlcNames
.SLC_
194 + Constants
.BUNDLE_VERSION
, SlcTypes
.SLC_OSGI_VERSION
);
195 mapOsgiVersion(version
, bundleVersionNode
);
198 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.FRAGMENT_HOST
);
199 if (attrs
.containsKey(new Name(Constants
.FRAGMENT_HOST
))) {
200 String fragmentHost
= attrs
.getValue(Constants
.FRAGMENT_HOST
);
201 String
[] tokens
= fragmentHost
.split(";");
202 Node node
= fileNode
.addNode(SlcNames
.SLC_
203 + Constants
.FRAGMENT_HOST
, SlcTypes
.SLC_FRAGMENT_HOST
);
204 node
.setProperty(SlcNames
.SLC_SYMBOLIC_NAME
, tokens
[0]);
205 for (int i
= 1; i
< tokens
.length
; i
++) {
206 if (tokens
[i
].startsWith(Constants
.BUNDLE_VERSION_ATTRIBUTE
)) {
207 node
.setProperty(SlcNames
.SLC_BUNDLE_VERSION
,
208 attributeValue(tokens
[i
]));
214 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.IMPORT_PACKAGE
);
215 if (attrs
.containsKey(new Name(Constants
.IMPORT_PACKAGE
))) {
216 String importPackages
= attrs
.getValue(Constants
.IMPORT_PACKAGE
);
217 List
<String
> packages
= parseCommaSeparated(importPackages
);
218 for (String pkg
: packages
) {
219 String
[] tokens
= pkg
.split(";");
220 Node node
= fileNode
.addNode(SlcNames
.SLC_
221 + Constants
.IMPORT_PACKAGE
,
222 SlcTypes
.SLC_IMPORTED_PACKAGE
);
223 node
.setProperty(SlcNames
.SLC_NAME
, tokens
[0]);
224 for (int i
= 1; i
< tokens
.length
; i
++) {
225 if (tokens
[i
].startsWith(Constants
.VERSION_ATTRIBUTE
)) {
226 node
.setProperty(SlcNames
.SLC_VERSION
,
227 attributeValue(tokens
[i
]));
229 .startsWith(Constants
.RESOLUTION_DIRECTIVE
)) {
231 SlcNames
.SLC_OPTIONAL
,
232 directiveValue(tokens
[i
]).equals(
233 Constants
.RESOLUTION_OPTIONAL
));
239 // dynamic import package
240 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.DYNAMICIMPORT_PACKAGE
);
241 if (attrs
.containsKey(new Name(Constants
.DYNAMICIMPORT_PACKAGE
))) {
242 String importPackages
= attrs
243 .getValue(Constants
.DYNAMICIMPORT_PACKAGE
);
244 List
<String
> packages
= parseCommaSeparated(importPackages
);
245 for (String pkg
: packages
) {
246 String
[] tokens
= pkg
.split(";");
247 Node node
= fileNode
.addNode(SlcNames
.SLC_
248 + Constants
.DYNAMICIMPORT_PACKAGE
,
249 SlcTypes
.SLC_DYNAMIC_IMPORTED_PACKAGE
);
250 node
.setProperty(SlcNames
.SLC_NAME
, tokens
[0]);
251 for (int i
= 1; i
< tokens
.length
; i
++) {
252 if (tokens
[i
].startsWith(Constants
.VERSION_ATTRIBUTE
)) {
253 node
.setProperty(SlcNames
.SLC_VERSION
,
254 attributeValue(tokens
[i
]));
261 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.EXPORT_PACKAGE
);
262 if (attrs
.containsKey(new Name(Constants
.EXPORT_PACKAGE
))) {
263 String exportPackages
= attrs
.getValue(Constants
.EXPORT_PACKAGE
);
264 List
<String
> packages
= parseCommaSeparated(exportPackages
);
265 for (String pkg
: packages
) {
266 String
[] tokens
= pkg
.split(";");
267 Node node
= fileNode
.addNode(SlcNames
.SLC_
268 + Constants
.EXPORT_PACKAGE
,
269 SlcTypes
.SLC_EXPORTED_PACKAGE
);
270 node
.setProperty(SlcNames
.SLC_NAME
, tokens
[0]);
271 // TODO: are these cleans really necessary?
272 cleanSubNodes(node
, SlcNames
.SLC_USES
);
273 cleanSubNodes(node
, SlcNames
.SLC_VERSION
);
274 for (int i
= 1; i
< tokens
.length
; i
++) {
275 if (tokens
[i
].startsWith(Constants
.VERSION_ATTRIBUTE
)) {
276 String versionStr
= attributeValue(tokens
[i
]);
277 Node versionNode
= node
.addNode(SlcNames
.SLC_VERSION
,
278 SlcTypes
.SLC_OSGI_VERSION
);
279 mapOsgiVersion(new Version(versionStr
), versionNode
);
280 } else if (tokens
[i
].startsWith(Constants
.USES_DIRECTIVE
)) {
281 String usedPackages
= directiveValue(tokens
[i
]);
282 // log.debug("uses='" + usedPackages + "'");
283 for (String usedPackage
: usedPackages
.split(",")) {
284 // log.debug("usedPackage='" +
287 Node usesNode
= node
.addNode(SlcNames
.SLC_USES
,
288 SlcTypes
.SLC_JAVA_PACKAGE
);
289 usesNode
.setProperty(SlcNames
.SLC_NAME
, usedPackage
);
297 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.REQUIRE_BUNDLE
);
298 if (attrs
.containsKey(new Name(Constants
.REQUIRE_BUNDLE
))) {
299 String requireBundle
= attrs
.getValue(Constants
.REQUIRE_BUNDLE
);
300 List
<String
> bundles
= parseCommaSeparated(requireBundle
);
301 for (String bundle
: bundles
) {
302 String
[] tokens
= bundle
.split(";");
303 Node node
= fileNode
.addNode(SlcNames
.SLC_
304 + Constants
.REQUIRE_BUNDLE
,
305 SlcTypes
.SLC_REQUIRED_BUNDLE
);
306 node
.setProperty(SlcNames
.SLC_SYMBOLIC_NAME
, tokens
[0]);
307 for (int i
= 1; i
< tokens
.length
; i
++) {
309 .startsWith(Constants
.BUNDLE_VERSION_ATTRIBUTE
)) {
310 node
.setProperty(SlcNames
.SLC_BUNDLE_VERSION
,
311 attributeValue(tokens
[i
]));
313 .startsWith(Constants
.RESOLUTION_DIRECTIVE
)) {
315 SlcNames
.SLC_OPTIONAL
,
316 directiveValue(tokens
[i
]).equals(
317 Constants
.RESOLUTION_OPTIONAL
));
325 private void addAttr(String key
, Node node
, Attributes attrs
)
326 throws RepositoryException
{
327 addAttr(new Name(key
), node
, attrs
);
330 private void addAttr(Name key
, Node node
, Attributes attrs
)
331 throws RepositoryException
{
332 if (attrs
.containsKey(key
)) {
333 String value
= attrs
.getValue(key
);
334 node
.setProperty(SlcNames
.SLC_
+ key
, value
);
338 private void cleanSubNodes(Node node
, String name
)
339 throws RepositoryException
{
340 if (node
.hasNode(name
)) {
341 NodeIterator nit
= node
.getNodes(name
);
342 while (nit
.hasNext())
343 nit
.nextNode().remove();
347 private String
attributeValue(String str
) {
348 return extractValue(str
, "=");
351 private String
directiveValue(String str
) {
352 return extractValue(str
, ":=");
355 private String
extractValue(String str
, String eq
) {
356 String
[] tokens
= str
.split(eq
);
357 // String key = tokens[0];
358 String value
= tokens
[1].trim();
360 if (value
.startsWith("\""))
361 value
= value
.substring(1);
362 if (value
.endsWith("\""))
363 value
= value
.substring(0, value
.length() - 1);
367 /** Parse package list with nested directive with ',' */
368 private List
<String
> parseCommaSeparated(String str
) {
369 List
<String
> res
= new ArrayList
<String
>();
370 StringBuffer curr
= new StringBuffer("");
372 for (char c
: str
.toCharArray()) {
374 if (!in
) {// new package
375 res
.add(curr
.toString());
376 curr
= new StringBuffer("");
377 } else {// a ',' within " "
380 } else if (c
== '\"') {
387 res
.add(curr
.toString());
392 protected void mapOsgiVersion(Version version
, Node versionNode
)
393 throws RepositoryException
{
394 versionNode
.setProperty(SlcNames
.SLC_AS_STRING
, version
.toString());
395 versionNode
.setProperty(SlcNames
.SLC_MAJOR
, version
.getMajor());
396 versionNode
.setProperty(SlcNames
.SLC_MINOR
, version
.getMinor());
397 versionNode
.setProperty(SlcNames
.SLC_MICRO
, version
.getMicro());
398 if (!version
.getQualifier().equals(""))
399 versionNode
.setProperty(SlcNames
.SLC_QUALIFIER
,
400 version
.getQualifier());