]> git.argeo.org Git - gpl/argeo-slc.git/blob - runtime/org.argeo.slc.repo/src/main/java/org/argeo/slc/repo/JarFileIndexer.java
Restructure Repo Dist UI
[gpl/argeo-slc.git] / runtime / org.argeo.slc.repo / src / main / java / org / argeo / slc / repo / JarFileIndexer.java
1 /*
2 * Copyright (C) 2007-2012 Mathieu Baudier
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 (!fileNode.isNodeType(NodeType.NT_FILE))
66 return;
67
68 Session jcrSession = fileNode.getSession();
69 Node contentNode = fileNode.getNode(Node.JCR_CONTENT);
70 fileBinary = contentNode.getProperty(Property.JCR_DATA).getBinary();
71 // jar file
72 // if (!FilenameUtils.isExtension(fileNode.getName(), "jar")) {
73 // return;
74 // }
75
76 jarIn = new JarInputStream(fileBinary.getStream());
77 Manifest manifest = jarIn.getManifest();
78 if (manifest == null) {
79 log.error(fileNode + " has no MANIFEST");
80 return;
81 }
82
83 bo = new ByteArrayOutputStream();
84 manifest.write(bo);
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 "
92 + fileNode);
93 return;
94 }
95 }
96
97 bi = new ByteArrayInputStream(newManifest);
98 manifestBinary = jcrSession.getValueFactory().createBinary(bi);
99
100 // standard jar file
101 fileNode.addMixin(SlcTypes.SLC_JAR_FILE);
102
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()));
108
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);
122
123 // OSGi
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);
129 } else {
130 JcrUtils.updateLastModified(fileNode);
131 if (log.isTraceEnabled())
132 log.trace("Indexed JAR file " + fileNode);
133 }
134
135 } catch (Exception e) {
136 throw new SlcException("Cannot index jar " + fileNode, e);
137 } finally {
138 IOUtils.closeQuietly(bi);
139 IOUtils.closeQuietly(bo);
140 IOUtils.closeQuietly(jarIn);
141 JcrUtils.closeQuietly(manifestBinary);
142 JcrUtils.closeQuietly(fileBinary);
143 }
144
145 }
146
147 protected void addOsgiMetadata(Node fileNode, Attributes attrs)
148 throws RepositoryException {
149 fileNode.addMixin(SlcTypes.SLC_BUNDLE_ARTIFACT);
150
151 // symbolic name
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);
156
157 // direct mapping
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);
172
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)
179 .split(","));
180
181 // bundle classpath
182 if (attrs.containsKey(new Name(Constants.BUNDLE_CLASSPATH)))
183 fileNode.setProperty(SlcNames.SLC_ + Constants.BUNDLE_CLASSPATH,
184 attrs.getValue(Constants.BUNDLE_CLASSPATH).split(","));
185
186 // version
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);
193
194 // fragment
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]));
206 }
207 }
208 }
209
210 // imported packages
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]));
225 } else if (tokens[i]
226 .startsWith(Constants.RESOLUTION_DIRECTIVE)) {
227 node.setProperty(
228 SlcNames.SLC_OPTIONAL,
229 directiveValue(tokens[i]).equals(
230 Constants.RESOLUTION_OPTIONAL));
231 }
232 }
233 }
234 }
235
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]));
252 }
253 }
254 }
255 }
256
257 // exported packages
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='" +
282 // usedPackage +
283 // "'");
284 Node usesNode = node.addNode(SlcNames.SLC_USES,
285 SlcTypes.SLC_JAVA_PACKAGE);
286 usesNode.setProperty(SlcNames.SLC_NAME, usedPackage);
287 }
288 }
289 }
290 }
291 }
292
293 // required bundle
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++) {
305 if (tokens[i]
306 .startsWith(Constants.BUNDLE_VERSION_ATTRIBUTE)) {
307 node.setProperty(SlcNames.SLC_BUNDLE_VERSION,
308 attributeValue(tokens[i]));
309 } else if (tokens[i]
310 .startsWith(Constants.RESOLUTION_DIRECTIVE)) {
311 node.setProperty(
312 SlcNames.SLC_OPTIONAL,
313 directiveValue(tokens[i]).equals(
314 Constants.RESOLUTION_OPTIONAL));
315 }
316 }
317 }
318 }
319
320 }
321
322 private void addAttr(String key, Node node, Attributes attrs)
323 throws RepositoryException {
324 addAttr(new Name(key), node, attrs);
325 }
326
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);
332 }
333 }
334
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();
341 }
342 }
343
344 private String attributeValue(String str) {
345 return extractValue(str, "=");
346 }
347
348 private String directiveValue(String str) {
349 return extractValue(str, ":=");
350 }
351
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();
356 // TODO: optimize?
357 if (value.startsWith("\""))
358 value = value.substring(1);
359 if (value.endsWith("\""))
360 value = value.substring(0, value.length() - 1);
361 return value;
362 }
363
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("");
368 boolean in = false;
369 for (char c : str.toCharArray()) {
370 if (c == ',') {
371 if (!in) {// new package
372 res.add(curr.toString());
373 curr = new StringBuffer("");
374 } else {// a ',' within " "
375 curr.append(c);
376 }
377 } else if (c == '\"') {
378 in = !in;
379 curr.append(c);
380 } else {
381 curr.append(c);
382 }
383 }
384 res.add(curr.toString());
385 // log.debug(res);
386 return res;
387 }
388
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());
398 }
399
400 }