]> git.argeo.org Git - gpl/argeo-slc.git/blob - JarFileIndexer.java
d3abf9bad0c52434c9753e8a2fbc00c0914c1bc3
[gpl/argeo-slc.git] / JarFileIndexer.java
1 /*
2 * Copyright (C) 2007-2012 Argeo GmbH
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16 package org.argeo.slc.repo;
17
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;
27
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;
35
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;
46
47 /**
48 * Indexes jar file, currently supports standard J2SE and OSGi metadata (both
49 * from MANIFEST)
50 */
51 public class JarFileIndexer implements NodeIndexer, SlcNames {
52 private final static Log log = LogFactory.getLog(JarFileIndexer.class);
53
54 public Boolean support(String path) {
55 return FilenameUtils.getExtension(path).equals("jar");
56 }
57
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;
64 try {
65 if(!support(fileNode.getPath()))
66 return;
67
68 if (!fileNode.isNodeType(NodeType.NT_FILE))
69 return;
70
71 Session jcrSession = fileNode.getSession();
72 Node contentNode = fileNode.getNode(Node.JCR_CONTENT);
73 fileBinary = contentNode.getProperty(Property.JCR_DATA).getBinary();
74 // jar file
75 // if (!FilenameUtils.isExtension(fileNode.getName(), "jar")) {
76 // return;
77 // }
78
79 jarIn = new JarInputStream(fileBinary.getStream());
80 Manifest manifest = jarIn.getManifest();
81 if (manifest == null) {
82 log.error(fileNode + " has no MANIFEST");
83 return;
84 }
85
86 bo = new ByteArrayOutputStream();
87 manifest.write(bo);
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 "
95 + fileNode);
96 return;
97 }
98 }
99
100 bi = new ByteArrayInputStream(newManifest);
101 manifestBinary = jcrSession.getValueFactory().createBinary(bi);
102
103 // standard jar file
104 fileNode.addMixin(SlcTypes.SLC_JAR_FILE);
105
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()));
111
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);
125
126 // OSGi
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);
132 } else {
133 JcrUtils.updateLastModified(fileNode);
134 if (log.isTraceEnabled())
135 log.trace("Indexed JAR file " + fileNode);
136 }
137
138 } catch (Exception e) {
139 throw new SlcException("Cannot index jar " + fileNode, e);
140 } finally {
141 IOUtils.closeQuietly(bi);
142 IOUtils.closeQuietly(bo);
143 IOUtils.closeQuietly(jarIn);
144 JcrUtils.closeQuietly(manifestBinary);
145 JcrUtils.closeQuietly(fileBinary);
146 }
147
148 }
149
150 protected void addOsgiMetadata(Node fileNode, Attributes attrs)
151 throws RepositoryException {
152 fileNode.addMixin(SlcTypes.SLC_BUNDLE_ARTIFACT);
153
154 // symbolic name
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);
159
160 // direct mapping
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);
175
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)
182 .split(","));
183
184 // bundle classpath
185 if (attrs.containsKey(new Name(Constants.BUNDLE_CLASSPATH)))
186 fileNode.setProperty(SlcNames.SLC_ + Constants.BUNDLE_CLASSPATH,
187 attrs.getValue(Constants.BUNDLE_CLASSPATH).split(","));
188
189 // version
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);
196
197 // fragment
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]));
209 }
210 }
211 }
212
213 // imported packages
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]));
228 } else if (tokens[i]
229 .startsWith(Constants.RESOLUTION_DIRECTIVE)) {
230 node.setProperty(
231 SlcNames.SLC_OPTIONAL,
232 directiveValue(tokens[i]).equals(
233 Constants.RESOLUTION_OPTIONAL));
234 }
235 }
236 }
237 }
238
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]));
255 }
256 }
257 }
258 }
259
260 // exported packages
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='" +
285 // usedPackage +
286 // "'");
287 Node usesNode = node.addNode(SlcNames.SLC_USES,
288 SlcTypes.SLC_JAVA_PACKAGE);
289 usesNode.setProperty(SlcNames.SLC_NAME, usedPackage);
290 }
291 }
292 }
293 }
294 }
295
296 // required bundle
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++) {
308 if (tokens[i]
309 .startsWith(Constants.BUNDLE_VERSION_ATTRIBUTE)) {
310 node.setProperty(SlcNames.SLC_BUNDLE_VERSION,
311 attributeValue(tokens[i]));
312 } else if (tokens[i]
313 .startsWith(Constants.RESOLUTION_DIRECTIVE)) {
314 node.setProperty(
315 SlcNames.SLC_OPTIONAL,
316 directiveValue(tokens[i]).equals(
317 Constants.RESOLUTION_OPTIONAL));
318 }
319 }
320 }
321 }
322
323 }
324
325 private void addAttr(String key, Node node, Attributes attrs)
326 throws RepositoryException {
327 addAttr(new Name(key), node, attrs);
328 }
329
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);
335 }
336 }
337
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();
344 }
345 }
346
347 private String attributeValue(String str) {
348 return extractValue(str, "=");
349 }
350
351 private String directiveValue(String str) {
352 return extractValue(str, ":=");
353 }
354
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();
359 // TODO: optimize?
360 if (value.startsWith("\""))
361 value = value.substring(1);
362 if (value.endsWith("\""))
363 value = value.substring(0, value.length() - 1);
364 return value;
365 }
366
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("");
371 boolean in = false;
372 for (char c : str.toCharArray()) {
373 if (c == ',') {
374 if (!in) {// new package
375 res.add(curr.toString());
376 curr = new StringBuffer("");
377 } else {// a ',' within " "
378 curr.append(c);
379 }
380 } else if (c == '\"') {
381 in = !in;
382 curr.append(c);
383 } else {
384 curr.append(c);
385 }
386 }
387 res.add(curr.toString());
388 // log.debug(res);
389 return res;
390 }
391
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());
401 }
402
403 }