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);
53 private Boolean force
= false;
55 public Boolean
support(String path
) {
56 return FilenameUtils
.getExtension(path
).equals("jar");
59 public void index(Node fileNode
) {
60 Binary fileBinary
= null;
61 JarInputStream jarIn
= null;
62 ByteArrayOutputStream bo
= null;
63 ByteArrayInputStream bi
= null;
64 Binary manifestBinary
= null;
66 if (!support(fileNode
.getPath()))
70 if (!force
&& fileNode
.isNodeType(SlcTypes
.SLC_JAR_FILE
))
73 if (!fileNode
.isNodeType(NodeType
.NT_FILE
))
76 Session jcrSession
= fileNode
.getSession();
77 Node contentNode
= fileNode
.getNode(Node
.JCR_CONTENT
);
78 fileBinary
= contentNode
.getProperty(Property
.JCR_DATA
).getBinary();
80 // if (!FilenameUtils.isExtension(fileNode.getName(), "jar")) {
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 // if (log.isTraceEnabled())
115 // for (Object key : attrs.keySet())
116 // log.trace(key + ": " + attrs.getValue(key.toString()));
118 // standard J2SE MANIFEST attributes
119 addAttr(Attributes
.Name
.MANIFEST_VERSION
, fileNode
, attrs
);
120 addAttr(Attributes
.Name
.SIGNATURE_VERSION
, fileNode
, attrs
);
121 addAttr(Attributes
.Name
.CLASS_PATH
, fileNode
, attrs
);
122 addAttr(Attributes
.Name
.MAIN_CLASS
, fileNode
, attrs
);
123 addAttr(Attributes
.Name
.EXTENSION_NAME
, fileNode
, attrs
);
124 addAttr(Attributes
.Name
.IMPLEMENTATION_VERSION
, fileNode
, attrs
);
125 addAttr(Attributes
.Name
.IMPLEMENTATION_VENDOR
, fileNode
, attrs
);
126 addAttr(Attributes
.Name
.IMPLEMENTATION_VENDOR_ID
, fileNode
, attrs
);
127 addAttr(Attributes
.Name
.SPECIFICATION_TITLE
, fileNode
, attrs
);
128 addAttr(Attributes
.Name
.SPECIFICATION_VERSION
, fileNode
, attrs
);
129 addAttr(Attributes
.Name
.SPECIFICATION_VENDOR
, fileNode
, attrs
);
130 addAttr(Attributes
.Name
.SEALED
, fileNode
, attrs
);
133 if (attrs
.containsKey(new Name(Constants
.BUNDLE_SYMBOLICNAME
))) {
134 addOsgiMetadata(fileNode
, attrs
);
135 JcrUtils
.updateLastModified(fileNode
);
136 if (log
.isTraceEnabled())
137 log
.trace("Indexed OSGi bundle " + fileNode
);
139 JcrUtils
.updateLastModified(fileNode
);
140 if (log
.isTraceEnabled())
141 log
.trace("Indexed JAR file " + fileNode
);
144 } catch (Exception e
) {
145 throw new SlcException("Cannot index jar " + fileNode
, e
);
147 IOUtils
.closeQuietly(bi
);
148 IOUtils
.closeQuietly(bo
);
149 IOUtils
.closeQuietly(jarIn
);
150 JcrUtils
.closeQuietly(manifestBinary
);
151 JcrUtils
.closeQuietly(fileBinary
);
156 protected void addOsgiMetadata(Node fileNode
, Attributes attrs
)
157 throws RepositoryException
{
158 fileNode
.addMixin(SlcTypes
.SLC_BUNDLE_ARTIFACT
);
161 String symbolicName
= attrs
.getValue(Constants
.BUNDLE_SYMBOLICNAME
);
162 // make sure there is no directive
163 symbolicName
= symbolicName
.split(";")[0];
164 fileNode
.setProperty(SlcNames
.SLC_SYMBOLIC_NAME
, symbolicName
);
167 addAttr(Constants
.BUNDLE_SYMBOLICNAME
, fileNode
, attrs
);
168 addAttr(Constants
.BUNDLE_NAME
, fileNode
, attrs
);
169 addAttr(Constants
.BUNDLE_DESCRIPTION
, fileNode
, attrs
);
170 addAttr(Constants
.BUNDLE_MANIFESTVERSION
, fileNode
, attrs
);
171 addAttr(Constants
.BUNDLE_CATEGORY
, fileNode
, attrs
);
172 addAttr(Constants
.BUNDLE_ACTIVATIONPOLICY
, fileNode
, attrs
);
173 addAttr(Constants
.BUNDLE_COPYRIGHT
, fileNode
, attrs
);
174 addAttr(Constants
.BUNDLE_VENDOR
, fileNode
, attrs
);
175 addAttr("Bundle-License", fileNode
, attrs
);
176 addAttr(Constants
.BUNDLE_DOCURL
, fileNode
, attrs
);
177 addAttr(Constants
.BUNDLE_CONTACTADDRESS
, fileNode
, attrs
);
178 addAttr(Constants
.BUNDLE_ACTIVATOR
, fileNode
, attrs
);
179 addAttr(Constants
.BUNDLE_UPDATELOCATION
, fileNode
, attrs
);
180 addAttr(Constants
.BUNDLE_LOCALIZATION
, fileNode
, attrs
);
182 // required execution environment
183 if (attrs
.containsKey(new Name(
184 Constants
.BUNDLE_REQUIREDEXECUTIONENVIRONMENT
)))
185 fileNode
.setProperty(SlcNames
.SLC_
186 + Constants
.BUNDLE_REQUIREDEXECUTIONENVIRONMENT
, attrs
187 .getValue(Constants
.BUNDLE_REQUIREDEXECUTIONENVIRONMENT
)
191 if (attrs
.containsKey(new Name(Constants
.BUNDLE_CLASSPATH
)))
192 fileNode
.setProperty(SlcNames
.SLC_
+ Constants
.BUNDLE_CLASSPATH
,
193 attrs
.getValue(Constants
.BUNDLE_CLASSPATH
).split(","));
196 Version version
= new Version(attrs
.getValue(Constants
.BUNDLE_VERSION
));
197 fileNode
.setProperty(SlcNames
.SLC_BUNDLE_VERSION
, version
.toString());
198 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.BUNDLE_VERSION
);
199 Node bundleVersionNode
= fileNode
.addNode(SlcNames
.SLC_
200 + Constants
.BUNDLE_VERSION
, SlcTypes
.SLC_OSGI_VERSION
);
201 mapOsgiVersion(version
, bundleVersionNode
);
204 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.FRAGMENT_HOST
);
205 if (attrs
.containsKey(new Name(Constants
.FRAGMENT_HOST
))) {
206 String fragmentHost
= attrs
.getValue(Constants
.FRAGMENT_HOST
);
207 String
[] tokens
= fragmentHost
.split(";");
208 Node node
= fileNode
.addNode(SlcNames
.SLC_
209 + Constants
.FRAGMENT_HOST
, SlcTypes
.SLC_FRAGMENT_HOST
);
210 node
.setProperty(SlcNames
.SLC_SYMBOLIC_NAME
, tokens
[0]);
211 for (int i
= 1; i
< tokens
.length
; i
++) {
212 if (tokens
[i
].startsWith(Constants
.BUNDLE_VERSION_ATTRIBUTE
)) {
213 node
.setProperty(SlcNames
.SLC_BUNDLE_VERSION
,
214 attributeValue(tokens
[i
]));
220 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.IMPORT_PACKAGE
);
221 if (attrs
.containsKey(new Name(Constants
.IMPORT_PACKAGE
))) {
222 String importPackages
= attrs
.getValue(Constants
.IMPORT_PACKAGE
);
223 List
<String
> packages
= parseCommaSeparated(importPackages
);
224 for (String pkg
: packages
) {
225 String
[] tokens
= pkg
.split(";");
226 Node node
= fileNode
.addNode(SlcNames
.SLC_
227 + Constants
.IMPORT_PACKAGE
,
228 SlcTypes
.SLC_IMPORTED_PACKAGE
);
229 node
.setProperty(SlcNames
.SLC_NAME
, tokens
[0]);
230 for (int i
= 1; i
< tokens
.length
; i
++) {
231 if (tokens
[i
].startsWith(Constants
.VERSION_ATTRIBUTE
)) {
232 node
.setProperty(SlcNames
.SLC_VERSION
,
233 attributeValue(tokens
[i
]));
235 .startsWith(Constants
.RESOLUTION_DIRECTIVE
)) {
237 SlcNames
.SLC_OPTIONAL
,
238 directiveValue(tokens
[i
]).equals(
239 Constants
.RESOLUTION_OPTIONAL
));
245 // dynamic import package
246 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.DYNAMICIMPORT_PACKAGE
);
247 if (attrs
.containsKey(new Name(Constants
.DYNAMICIMPORT_PACKAGE
))) {
248 String importPackages
= attrs
249 .getValue(Constants
.DYNAMICIMPORT_PACKAGE
);
250 List
<String
> packages
= parseCommaSeparated(importPackages
);
251 for (String pkg
: packages
) {
252 String
[] tokens
= pkg
.split(";");
253 Node node
= fileNode
.addNode(SlcNames
.SLC_
254 + Constants
.DYNAMICIMPORT_PACKAGE
,
255 SlcTypes
.SLC_DYNAMIC_IMPORTED_PACKAGE
);
256 node
.setProperty(SlcNames
.SLC_NAME
, tokens
[0]);
257 for (int i
= 1; i
< tokens
.length
; i
++) {
258 if (tokens
[i
].startsWith(Constants
.VERSION_ATTRIBUTE
)) {
259 node
.setProperty(SlcNames
.SLC_VERSION
,
260 attributeValue(tokens
[i
]));
267 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.EXPORT_PACKAGE
);
268 if (attrs
.containsKey(new Name(Constants
.EXPORT_PACKAGE
))) {
269 String exportPackages
= attrs
.getValue(Constants
.EXPORT_PACKAGE
);
270 List
<String
> packages
= parseCommaSeparated(exportPackages
);
271 for (String pkg
: packages
) {
272 String
[] tokens
= pkg
.split(";");
273 Node node
= fileNode
.addNode(SlcNames
.SLC_
274 + Constants
.EXPORT_PACKAGE
,
275 SlcTypes
.SLC_EXPORTED_PACKAGE
);
276 node
.setProperty(SlcNames
.SLC_NAME
, tokens
[0]);
277 // TODO: are these cleans really necessary?
278 cleanSubNodes(node
, SlcNames
.SLC_USES
);
279 cleanSubNodes(node
, SlcNames
.SLC_VERSION
);
280 for (int i
= 1; i
< tokens
.length
; i
++) {
281 if (tokens
[i
].startsWith(Constants
.VERSION_ATTRIBUTE
)) {
282 String versionStr
= attributeValue(tokens
[i
]);
283 Node versionNode
= node
.addNode(SlcNames
.SLC_VERSION
,
284 SlcTypes
.SLC_OSGI_VERSION
);
285 mapOsgiVersion(new Version(versionStr
), versionNode
);
286 } else if (tokens
[i
].startsWith(Constants
.USES_DIRECTIVE
)) {
287 String usedPackages
= directiveValue(tokens
[i
]);
288 // log.debug("uses='" + usedPackages + "'");
289 for (String usedPackage
: usedPackages
.split(",")) {
290 // log.debug("usedPackage='" +
293 Node usesNode
= node
.addNode(SlcNames
.SLC_USES
,
294 SlcTypes
.SLC_JAVA_PACKAGE
);
295 usesNode
.setProperty(SlcNames
.SLC_NAME
, usedPackage
);
303 cleanSubNodes(fileNode
, SlcNames
.SLC_
+ Constants
.REQUIRE_BUNDLE
);
304 if (attrs
.containsKey(new Name(Constants
.REQUIRE_BUNDLE
))) {
305 String requireBundle
= attrs
.getValue(Constants
.REQUIRE_BUNDLE
);
306 List
<String
> bundles
= parseCommaSeparated(requireBundle
);
307 for (String bundle
: bundles
) {
308 String
[] tokens
= bundle
.split(";");
309 Node node
= fileNode
.addNode(SlcNames
.SLC_
310 + Constants
.REQUIRE_BUNDLE
,
311 SlcTypes
.SLC_REQUIRED_BUNDLE
);
312 node
.setProperty(SlcNames
.SLC_SYMBOLIC_NAME
, tokens
[0]);
313 for (int i
= 1; i
< tokens
.length
; i
++) {
315 .startsWith(Constants
.BUNDLE_VERSION_ATTRIBUTE
)) {
316 node
.setProperty(SlcNames
.SLC_BUNDLE_VERSION
,
317 attributeValue(tokens
[i
]));
319 .startsWith(Constants
.RESOLUTION_DIRECTIVE
)) {
321 SlcNames
.SLC_OPTIONAL
,
322 directiveValue(tokens
[i
]).equals(
323 Constants
.RESOLUTION_OPTIONAL
));
331 private void addAttr(String key
, Node node
, Attributes attrs
)
332 throws RepositoryException
{
333 addAttr(new Name(key
), node
, attrs
);
336 private void addAttr(Name key
, Node node
, Attributes attrs
)
337 throws RepositoryException
{
338 if (attrs
.containsKey(key
)) {
339 String value
= attrs
.getValue(key
);
340 node
.setProperty(SlcNames
.SLC_
+ key
, value
);
344 private void cleanSubNodes(Node node
, String name
)
345 throws RepositoryException
{
346 if (node
.hasNode(name
)) {
347 NodeIterator nit
= node
.getNodes(name
);
348 while (nit
.hasNext())
349 nit
.nextNode().remove();
353 private String
attributeValue(String str
) {
354 return extractValue(str
, "=");
357 private String
directiveValue(String str
) {
358 return extractValue(str
, ":=");
361 private String
extractValue(String str
, String eq
) {
362 String
[] tokens
= str
.split(eq
);
363 // String key = tokens[0];
364 String value
= tokens
[1].trim();
366 if (value
.startsWith("\""))
367 value
= value
.substring(1);
368 if (value
.endsWith("\""))
369 value
= value
.substring(0, value
.length() - 1);
373 /** Parse package list with nested directive with ',' */
374 private List
<String
> parseCommaSeparated(String str
) {
375 List
<String
> res
= new ArrayList
<String
>();
376 StringBuffer curr
= new StringBuffer("");
378 for (char c
: str
.toCharArray()) {
380 if (!in
) {// new package
381 res
.add(curr
.toString());
382 curr
= new StringBuffer("");
383 } else {// a ',' within " "
386 } else if (c
== '\"') {
393 res
.add(curr
.toString());
398 protected void mapOsgiVersion(Version version
, Node versionNode
)
399 throws RepositoryException
{
400 versionNode
.setProperty(SlcNames
.SLC_AS_STRING
, version
.toString());
401 versionNode
.setProperty(SlcNames
.SLC_MAJOR
, version
.getMajor());
402 versionNode
.setProperty(SlcNames
.SLC_MINOR
, version
.getMinor());
403 versionNode
.setProperty(SlcNames
.SLC_MICRO
, version
.getMicro());
404 if (!version
.getQualifier().equals(""))
405 versionNode
.setProperty(SlcNames
.SLC_QUALIFIER
,
406 version
.getQualifier());
409 public void setForce(Boolean force
) {