]> git.argeo.org Git - gpl/argeo-slc.git/blob - org.argeo.slc.repo/src/org/argeo/slc/repo/JarFileIndexer.java
Merge remote-tracking branch 'origin/unstable' into testing
[gpl/argeo-slc.git] / org.argeo.slc.repo / src / org / argeo / slc / repo / JarFileIndexer.java
1 package org.argeo.slc.repo;
2
3 import java.io.ByteArrayInputStream;
4 import java.io.ByteArrayOutputStream;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.util.ArrayList;
8 import java.util.Arrays;
9 import java.util.List;
10 import java.util.Properties;
11 import java.util.jar.Attributes;
12 import java.util.jar.Attributes.Name;
13 import java.util.jar.JarEntry;
14 import java.util.jar.JarInputStream;
15 import java.util.jar.Manifest;
16
17 import javax.jcr.Binary;
18 import javax.jcr.Node;
19 import javax.jcr.NodeIterator;
20 import javax.jcr.Property;
21 import javax.jcr.RepositoryException;
22 import javax.jcr.Session;
23 import javax.jcr.nodetype.NodeType;
24
25 import org.apache.commons.io.FilenameUtils;
26 import org.apache.commons.io.IOUtils;
27 import org.argeo.api.cms.CmsLog;
28 import org.argeo.jcr.JcrUtils;
29 import org.argeo.slc.SlcException;
30 import org.argeo.slc.SlcNames;
31 import org.argeo.slc.SlcTypes;
32 import org.osgi.framework.Constants;
33 import org.osgi.framework.Version;
34
35 /**
36 * Indexes jar file, currently supports standard J2SE and OSGi metadata (both
37 * from MANIFEST)
38 */
39 public class JarFileIndexer implements NodeIndexer, SlcNames {
40 private final static CmsLog log = CmsLog.getLog(JarFileIndexer.class);
41 private Boolean force = false;
42
43 public Boolean support(String path) {
44 return FilenameUtils.getExtension(path).equals("jar");
45 }
46
47 public void index(Node fileNode) {
48 Binary fileBinary = null;
49 JarInputStream jarIn = null;
50 ByteArrayOutputStream bo = null;
51 ByteArrayInputStream bi = null;
52 Binary manifestBinary = null;
53 try {
54 if (!support(fileNode.getPath()))
55 return;
56
57 // Already indexed
58 if (!force && fileNode.isNodeType(SlcTypes.SLC_JAR_FILE))
59 return;
60
61 if (!fileNode.isNodeType(NodeType.NT_FILE))
62 return;
63
64 Session jcrSession = fileNode.getSession();
65 Node contentNode = fileNode.getNode(Node.JCR_CONTENT);
66 fileBinary = contentNode.getProperty(Property.JCR_DATA).getBinary();
67
68 jarIn = new JarInputStream(fileBinary.getStream());
69 Manifest manifest = jarIn.getManifest();
70 if (manifest == null) {
71 log.error(fileNode + " has no MANIFEST");
72 return;
73 }
74
75 bo = new ByteArrayOutputStream();
76 manifest.write(bo);
77 byte[] newManifest = bo.toByteArray();
78 if (fileNode.hasProperty(SLC_MANIFEST)) {
79 byte[] storedManifest = JcrUtils.getBinaryAsBytes(fileNode.getProperty(SLC_MANIFEST));
80 if (Arrays.equals(newManifest, storedManifest)) {
81 if (log.isTraceEnabled())
82 log.trace("Manifest not changed, doing nothing " + fileNode);
83 return;
84 }
85 }
86
87 bi = new ByteArrayInputStream(newManifest);
88 manifestBinary = jcrSession.getValueFactory().createBinary(bi);
89
90 // standard jar file
91 fileNode.addMixin(SlcTypes.SLC_JAR_FILE);
92
93 fileNode.setProperty(SlcNames.SLC_MANIFEST, manifestBinary);
94 Attributes attrs = manifest.getMainAttributes();
95
96 getI18nValues(fileBinary, attrs);
97
98 // standard J2SE MANIFEST attributes
99 addAttr(Attributes.Name.MANIFEST_VERSION, fileNode, attrs);
100 addAttr(Attributes.Name.SIGNATURE_VERSION, fileNode, attrs);
101 addAttr(Attributes.Name.CLASS_PATH, fileNode, attrs);
102 addAttr(Attributes.Name.MAIN_CLASS, fileNode, attrs);
103 addAttr(Attributes.Name.EXTENSION_NAME, fileNode, attrs);
104 addAttr(Attributes.Name.IMPLEMENTATION_VERSION, fileNode, attrs);
105 addAttr(Attributes.Name.IMPLEMENTATION_VENDOR, fileNode, attrs);
106 addAttr(Attributes.Name.IMPLEMENTATION_VENDOR_ID, fileNode, attrs);
107 addAttr(Attributes.Name.SPECIFICATION_TITLE, fileNode, attrs);
108 addAttr(Attributes.Name.SPECIFICATION_VERSION, fileNode, attrs);
109 addAttr(Attributes.Name.SPECIFICATION_VENDOR, fileNode, attrs);
110 addAttr(Attributes.Name.SEALED, fileNode, attrs);
111
112 // OSGi
113 if (attrs.containsKey(new Name(Constants.BUNDLE_SYMBOLICNAME))) {
114 addOsgiMetadata(fileNode, attrs);
115 if (log.isTraceEnabled())
116 log.trace("Indexed OSGi bundle " + fileNode);
117 } else {
118 if (log.isTraceEnabled())
119 log.trace("Indexed JAR file " + fileNode);
120 }
121
122 JcrUtils.updateLastModified(fileNode);
123
124 } catch (Exception e) {
125 throw new SlcException("Cannot index jar " + fileNode, e);
126 } finally {
127 IOUtils.closeQuietly(bi);
128 IOUtils.closeQuietly(bo);
129 IOUtils.closeQuietly(jarIn);
130 JcrUtils.closeQuietly(manifestBinary);
131 JcrUtils.closeQuietly(fileBinary);
132 }
133
134 }
135
136 private void getI18nValues(Binary fileBinary, Attributes attrs) throws IOException {
137 JarInputStream jarIn = null;
138 try {
139 jarIn = new JarInputStream(fileBinary.getStream());
140 String bundleLocalization = null;
141
142 String blKey = Constants.BUNDLE_LOCALIZATION; // "Bundle-Localization";
143 Name blkName = new Name(blKey);
144
145 browse: for (Object obj : attrs.keySet()) {
146 String value = attrs.getValue((Attributes.Name) obj);
147 if (value.startsWith("%")) {
148 if (attrs.containsKey(blkName)) {
149 bundleLocalization = attrs.getValue(blkName);
150 break browse;
151 }
152 }
153 }
154
155 JarEntry jarEntry = null;
156 byte[] propBytes = null;
157 ByteArrayOutputStream baos = null;
158 browse: if (bundleLocalization != null) {
159 JarEntry entry = jarIn.getNextJarEntry();
160 while (entry != null) {
161 if (entry.getName().equals(bundleLocalization + ".properties")) {
162 jarEntry = entry;
163
164 // if(je.getSize() != -1){
165 // propBytes = new byte[(int)je.getSize()];
166 // int len = (int) je.getSize();
167 // int offset = 0;
168 // while (offset != len)
169 // offset += jarIn.read(propBytes, offset, len -
170 // offset);
171 // } else {
172 baos = new ByteArrayOutputStream();
173 while (true) {
174 int qwe = jarIn.read();
175 if (qwe == -1)
176 break;
177 baos.write(qwe);
178 }
179 propBytes = baos.toByteArray();
180 break browse;
181 }
182 entry = jarIn.getNextJarEntry();
183 }
184 }
185
186 if (jarEntry != null) {
187 Properties prop = new Properties();
188 InputStream is = new ByteArrayInputStream(propBytes);
189 prop.load(is);
190
191 for (Object obj : attrs.keySet()) {
192 String value = attrs.getValue((Attributes.Name) obj);
193 if (value.startsWith("%")) {
194 String newVal = prop.getProperty(value.substring(1));
195 if (newVal != null)
196 attrs.put(obj, newVal);
197 }
198 }
199 }
200 } catch (RepositoryException e) {
201 throw new SlcException("Error while reading the jar binary content " + fileBinary, e);
202 } catch (IOException ioe) {
203 throw new SlcException("unable to get internationalized values", ioe);
204 } finally {
205 IOUtils.closeQuietly(jarIn);
206 }
207 }
208
209 protected void addOsgiMetadata(Node fileNode, Attributes attrs) throws RepositoryException {
210
211 // TODO remove this ?
212 // Compulsory for the time being, because bundle artifact extends
213 // artifact
214 if (!fileNode.isNodeType(SlcTypes.SLC_ARTIFACT)) {
215 ArtifactIndexer indexer = new ArtifactIndexer();
216 indexer.index(fileNode);
217 }
218
219 fileNode.addMixin(SlcTypes.SLC_BUNDLE_ARTIFACT);
220
221 // symbolic name
222 String symbolicName = attrs.getValue(Constants.BUNDLE_SYMBOLICNAME);
223 // make sure there is no directive
224 symbolicName = symbolicName.split(";")[0];
225 fileNode.setProperty(SlcNames.SLC_SYMBOLIC_NAME, symbolicName);
226
227 // direct mapping
228 addAttr(Constants.BUNDLE_SYMBOLICNAME, fileNode, attrs);
229 addAttr(Constants.BUNDLE_NAME, fileNode, attrs);
230 addAttr(Constants.BUNDLE_DESCRIPTION, fileNode, attrs);
231 addAttr(Constants.BUNDLE_MANIFESTVERSION, fileNode, attrs);
232 addAttr(Constants.BUNDLE_CATEGORY, fileNode, attrs);
233 addAttr(Constants.BUNDLE_ACTIVATIONPOLICY, fileNode, attrs);
234 addAttr(Constants.BUNDLE_COPYRIGHT, fileNode, attrs);
235 addAttr(Constants.BUNDLE_VENDOR, fileNode, attrs);
236 addAttr("Bundle-License", fileNode, attrs);
237 addAttr(Constants.BUNDLE_DOCURL, fileNode, attrs);
238 addAttr(Constants.BUNDLE_CONTACTADDRESS, fileNode, attrs);
239 addAttr(Constants.BUNDLE_ACTIVATOR, fileNode, attrs);
240 addAttr(Constants.BUNDLE_UPDATELOCATION, fileNode, attrs);
241 addAttr(Constants.BUNDLE_LOCALIZATION, fileNode, attrs);
242
243 // required execution environment
244 if (attrs.containsKey(new Name(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT)))
245 fileNode.setProperty(SlcNames.SLC_ + Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT,
246 attrs.getValue(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT).split(","));
247
248 // bundle classpath
249 if (attrs.containsKey(new Name(Constants.BUNDLE_CLASSPATH)))
250 fileNode.setProperty(SlcNames.SLC_ + Constants.BUNDLE_CLASSPATH,
251 attrs.getValue(Constants.BUNDLE_CLASSPATH).split(","));
252
253 // version
254 Version version = new Version(attrs.getValue(Constants.BUNDLE_VERSION));
255 fileNode.setProperty(SlcNames.SLC_BUNDLE_VERSION, version.toString());
256 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.BUNDLE_VERSION);
257 Node bundleVersionNode = fileNode.addNode(SlcNames.SLC_ + Constants.BUNDLE_VERSION, SlcTypes.SLC_OSGI_VERSION);
258 mapOsgiVersion(version, bundleVersionNode);
259
260 // fragment
261 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.FRAGMENT_HOST);
262 if (attrs.containsKey(new Name(Constants.FRAGMENT_HOST))) {
263 String fragmentHost = attrs.getValue(Constants.FRAGMENT_HOST);
264 String[] tokens = fragmentHost.split(";");
265 Node node = fileNode.addNode(SlcNames.SLC_ + Constants.FRAGMENT_HOST, SlcTypes.SLC_FRAGMENT_HOST);
266 node.setProperty(SlcNames.SLC_SYMBOLIC_NAME, tokens[0]);
267 for (int i = 1; i < tokens.length; i++) {
268 if (tokens[i].startsWith(Constants.BUNDLE_VERSION_ATTRIBUTE)) {
269 node.setProperty(SlcNames.SLC_BUNDLE_VERSION, attributeValue(tokens[i]));
270 }
271 }
272 }
273
274 // imported packages
275 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.IMPORT_PACKAGE);
276 if (attrs.containsKey(new Name(Constants.IMPORT_PACKAGE))) {
277 String importPackages = attrs.getValue(Constants.IMPORT_PACKAGE);
278 List<String> packages = parseCommaSeparated(importPackages);
279 for (String pkg : packages) {
280 String[] tokens = pkg.split(";");
281 Node node = fileNode.addNode(SlcNames.SLC_ + Constants.IMPORT_PACKAGE, SlcTypes.SLC_IMPORTED_PACKAGE);
282 node.setProperty(SlcNames.SLC_NAME, tokens[0]);
283 for (int i = 1; i < tokens.length; i++) {
284 if (tokens[i].startsWith(Constants.VERSION_ATTRIBUTE)) {
285 node.setProperty(SlcNames.SLC_VERSION, attributeValue(tokens[i]));
286 } else if (tokens[i].startsWith(Constants.RESOLUTION_DIRECTIVE)) {
287 node.setProperty(SlcNames.SLC_OPTIONAL,
288 directiveValue(tokens[i]).equals(Constants.RESOLUTION_OPTIONAL));
289 }
290 }
291 }
292 }
293
294 // dynamic import package
295 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.DYNAMICIMPORT_PACKAGE);
296 if (attrs.containsKey(new Name(Constants.DYNAMICIMPORT_PACKAGE))) {
297 String importPackages = attrs.getValue(Constants.DYNAMICIMPORT_PACKAGE);
298 List<String> packages = parseCommaSeparated(importPackages);
299 for (String pkg : packages) {
300 String[] tokens = pkg.split(";");
301 Node node = fileNode.addNode(SlcNames.SLC_ + Constants.DYNAMICIMPORT_PACKAGE,
302 SlcTypes.SLC_DYNAMIC_IMPORTED_PACKAGE);
303 node.setProperty(SlcNames.SLC_NAME, tokens[0]);
304 for (int i = 1; i < tokens.length; i++) {
305 if (tokens[i].startsWith(Constants.VERSION_ATTRIBUTE)) {
306 node.setProperty(SlcNames.SLC_VERSION, attributeValue(tokens[i]));
307 }
308 }
309 }
310 }
311
312 // exported packages
313 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.EXPORT_PACKAGE);
314 if (attrs.containsKey(new Name(Constants.EXPORT_PACKAGE))) {
315 String exportPackages = attrs.getValue(Constants.EXPORT_PACKAGE);
316 List<String> packages = parseCommaSeparated(exportPackages);
317 for (String pkg : packages) {
318 String[] tokens = pkg.split(";");
319 Node node = fileNode.addNode(SlcNames.SLC_ + Constants.EXPORT_PACKAGE, SlcTypes.SLC_EXPORTED_PACKAGE);
320 node.setProperty(SlcNames.SLC_NAME, tokens[0]);
321 // TODO: are these cleans really necessary?
322 cleanSubNodes(node, SlcNames.SLC_USES);
323 cleanSubNodes(node, SlcNames.SLC_VERSION);
324 for (int i = 1; i < tokens.length; i++) {
325 if (tokens[i].startsWith(Constants.VERSION_ATTRIBUTE)) {
326 String versionStr = attributeValue(tokens[i]);
327 Node versionNode = node.addNode(SlcNames.SLC_VERSION, SlcTypes.SLC_OSGI_VERSION);
328 mapOsgiVersion(new Version(versionStr), versionNode);
329 } else if (tokens[i].startsWith(Constants.USES_DIRECTIVE)) {
330 String usedPackages = directiveValue(tokens[i]);
331 // log.debug("uses='" + usedPackages + "'");
332 for (String usedPackage : usedPackages.split(",")) {
333 // log.debug("usedPackage='" +
334 // usedPackage +
335 // "'");
336 Node usesNode = node.addNode(SlcNames.SLC_USES, SlcTypes.SLC_JAVA_PACKAGE);
337 usesNode.setProperty(SlcNames.SLC_NAME, usedPackage);
338 }
339 }
340 }
341 }
342 }
343
344 // required bundle
345 cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.REQUIRE_BUNDLE);
346 if (attrs.containsKey(new Name(Constants.REQUIRE_BUNDLE))) {
347 String requireBundle = attrs.getValue(Constants.REQUIRE_BUNDLE);
348 List<String> bundles = parseCommaSeparated(requireBundle);
349 for (String bundle : bundles) {
350 String[] tokens = bundle.split(";");
351 Node node = fileNode.addNode(SlcNames.SLC_ + Constants.REQUIRE_BUNDLE, SlcTypes.SLC_REQUIRED_BUNDLE);
352 node.setProperty(SlcNames.SLC_SYMBOLIC_NAME, tokens[0]);
353 for (int i = 1; i < tokens.length; i++) {
354 if (tokens[i].startsWith(Constants.BUNDLE_VERSION_ATTRIBUTE)) {
355 node.setProperty(SlcNames.SLC_BUNDLE_VERSION, attributeValue(tokens[i]));
356 } else if (tokens[i].startsWith(Constants.RESOLUTION_DIRECTIVE)) {
357 node.setProperty(SlcNames.SLC_OPTIONAL,
358 directiveValue(tokens[i]).equals(Constants.RESOLUTION_OPTIONAL));
359 }
360 }
361 }
362 }
363
364 }
365
366 private void addAttr(String key, Node node, Attributes attrs) throws RepositoryException {
367 addAttr(new Name(key), node, attrs);
368 }
369
370 private void addAttr(Name key, Node node, Attributes attrs) throws RepositoryException {
371 if (attrs.containsKey(key)) {
372 String value = attrs.getValue(key);
373 node.setProperty(SlcNames.SLC_ + key, value);
374 }
375 }
376
377 private void cleanSubNodes(Node node, String name) throws RepositoryException {
378 if (node.hasNode(name)) {
379 NodeIterator nit = node.getNodes(name);
380 while (nit.hasNext())
381 nit.nextNode().remove();
382 }
383 }
384
385 private String attributeValue(String str) {
386 return extractValue(str, "=");
387 }
388
389 private String directiveValue(String str) {
390 return extractValue(str, ":=");
391 }
392
393 private String extractValue(String str, String eq) {
394 String[] tokens = str.split(eq);
395 // String key = tokens[0];
396 String value = tokens[1].trim();
397 // TODO: optimize?
398 if (value.startsWith("\""))
399 value = value.substring(1);
400 if (value.endsWith("\""))
401 value = value.substring(0, value.length() - 1);
402 return value;
403 }
404
405 /** Parse package list with nested directive with ',' */
406 private List<String> parseCommaSeparated(String str) {
407 List<String> res = new ArrayList<String>();
408 StringBuffer curr = new StringBuffer("");
409 boolean in = false;
410 for (char c : str.toCharArray()) {
411 if (c == ',') {
412 if (!in) {// new package
413 res.add(curr.toString());
414 curr = new StringBuffer("");
415 } else {// a ',' within " "
416 curr.append(c);
417 }
418 } else if (c == '\"') {
419 in = !in;
420 curr.append(c);
421 } else {
422 curr.append(c);
423 }
424 }
425 res.add(curr.toString());
426 // log.debug(res);
427 return res;
428 }
429
430 protected void mapOsgiVersion(Version version, Node versionNode) throws RepositoryException {
431 versionNode.setProperty(SlcNames.SLC_AS_STRING, version.toString());
432 versionNode.setProperty(SlcNames.SLC_MAJOR, version.getMajor());
433 versionNode.setProperty(SlcNames.SLC_MINOR, version.getMinor());
434 versionNode.setProperty(SlcNames.SLC_MICRO, version.getMicro());
435 if (!version.getQualifier().equals(""))
436 versionNode.setProperty(SlcNames.SLC_QUALIFIER, version.getQualifier());
437 }
438
439 public void setForce(Boolean force) {
440 this.force = force;
441 }
442
443 }