package org.argeo.slc.repo; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Properties; import java.util.jar.Attributes; import java.util.jar.Attributes.Name; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import java.util.jar.Manifest; import javax.jcr.Binary; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.nodetype.NodeType; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.argeo.api.cms.CmsLog; import org.argeo.jcr.JcrUtils; import org.argeo.slc.SlcException; import org.argeo.slc.SlcNames; import org.argeo.slc.SlcTypes; import org.osgi.framework.Constants; import org.osgi.framework.Version; /** * Indexes jar file, currently supports standard J2SE and OSGi metadata (both * from MANIFEST) */ public class JarFileIndexer implements NodeIndexer, SlcNames { private final static CmsLog log = CmsLog.getLog(JarFileIndexer.class); private Boolean force = false; public Boolean support(String path) { return FilenameUtils.getExtension(path).equals("jar"); } public void index(Node fileNode) { Binary fileBinary = null; JarInputStream jarIn = null; ByteArrayOutputStream bo = null; ByteArrayInputStream bi = null; Binary manifestBinary = null; try { if (!support(fileNode.getPath())) return; // Already indexed if (!force && fileNode.isNodeType(SlcTypes.SLC_JAR_FILE)) return; if (!fileNode.isNodeType(NodeType.NT_FILE)) return; Session jcrSession = fileNode.getSession(); Node contentNode = fileNode.getNode(Node.JCR_CONTENT); fileBinary = contentNode.getProperty(Property.JCR_DATA).getBinary(); jarIn = new JarInputStream(fileBinary.getStream()); Manifest manifest = jarIn.getManifest(); if (manifest == null) { log.error(fileNode + " has no MANIFEST"); return; } bo = new ByteArrayOutputStream(); manifest.write(bo); byte[] newManifest = bo.toByteArray(); if (fileNode.hasProperty(SLC_MANIFEST)) { byte[] storedManifest = JcrUtils.getBinaryAsBytes(fileNode.getProperty(SLC_MANIFEST)); if (Arrays.equals(newManifest, storedManifest)) { if (log.isTraceEnabled()) log.trace("Manifest not changed, doing nothing " + fileNode); return; } } bi = new ByteArrayInputStream(newManifest); manifestBinary = jcrSession.getValueFactory().createBinary(bi); // standard jar file fileNode.addMixin(SlcTypes.SLC_JAR_FILE); fileNode.setProperty(SlcNames.SLC_MANIFEST, manifestBinary); Attributes attrs = manifest.getMainAttributes(); getI18nValues(fileBinary, attrs); // standard J2SE MANIFEST attributes addAttr(Attributes.Name.MANIFEST_VERSION, fileNode, attrs); addAttr(Attributes.Name.SIGNATURE_VERSION, fileNode, attrs); addAttr(Attributes.Name.CLASS_PATH, fileNode, attrs); addAttr(Attributes.Name.MAIN_CLASS, fileNode, attrs); addAttr(Attributes.Name.EXTENSION_NAME, fileNode, attrs); addAttr(Attributes.Name.IMPLEMENTATION_VERSION, fileNode, attrs); addAttr(Attributes.Name.IMPLEMENTATION_VENDOR, fileNode, attrs); addAttr(Attributes.Name.IMPLEMENTATION_VENDOR_ID, fileNode, attrs); addAttr(Attributes.Name.SPECIFICATION_TITLE, fileNode, attrs); addAttr(Attributes.Name.SPECIFICATION_VERSION, fileNode, attrs); addAttr(Attributes.Name.SPECIFICATION_VENDOR, fileNode, attrs); addAttr(Attributes.Name.SEALED, fileNode, attrs); // OSGi if (attrs.containsKey(new Name(Constants.BUNDLE_SYMBOLICNAME))) { addOsgiMetadata(fileNode, attrs); if (log.isTraceEnabled()) log.trace("Indexed OSGi bundle " + fileNode); } else { if (log.isTraceEnabled()) log.trace("Indexed JAR file " + fileNode); } JcrUtils.updateLastModified(fileNode); } catch (Exception e) { throw new SlcException("Cannot index jar " + fileNode, e); } finally { IOUtils.closeQuietly(bi); IOUtils.closeQuietly(bo); IOUtils.closeQuietly(jarIn); JcrUtils.closeQuietly(manifestBinary); JcrUtils.closeQuietly(fileBinary); } } private void getI18nValues(Binary fileBinary, Attributes attrs) throws IOException { JarInputStream jarIn = null; try { jarIn = new JarInputStream(fileBinary.getStream()); String bundleLocalization = null; String blKey = Constants.BUNDLE_LOCALIZATION; // "Bundle-Localization"; Name blkName = new Name(blKey); browse: for (Object obj : attrs.keySet()) { String value = attrs.getValue((Attributes.Name) obj); if (value.startsWith("%")) { if (attrs.containsKey(blkName)) { bundleLocalization = attrs.getValue(blkName); break browse; } } } JarEntry jarEntry = null; byte[] propBytes = null; ByteArrayOutputStream baos = null; browse: if (bundleLocalization != null) { JarEntry entry = jarIn.getNextJarEntry(); while (entry != null) { if (entry.getName().equals(bundleLocalization + ".properties")) { jarEntry = entry; // if(je.getSize() != -1){ // propBytes = new byte[(int)je.getSize()]; // int len = (int) je.getSize(); // int offset = 0; // while (offset != len) // offset += jarIn.read(propBytes, offset, len - // offset); // } else { baos = new ByteArrayOutputStream(); while (true) { int qwe = jarIn.read(); if (qwe == -1) break; baos.write(qwe); } propBytes = baos.toByteArray(); break browse; } entry = jarIn.getNextJarEntry(); } } if (jarEntry != null) { Properties prop = new Properties(); InputStream is = new ByteArrayInputStream(propBytes); prop.load(is); for (Object obj : attrs.keySet()) { String value = attrs.getValue((Attributes.Name) obj); if (value.startsWith("%")) { String newVal = prop.getProperty(value.substring(1)); if (newVal != null) attrs.put(obj, newVal); } } } } catch (RepositoryException e) { throw new SlcException("Error while reading the jar binary content " + fileBinary, e); } catch (IOException ioe) { throw new SlcException("unable to get internationalized values", ioe); } finally { IOUtils.closeQuietly(jarIn); } } protected void addOsgiMetadata(Node fileNode, Attributes attrs) throws RepositoryException { // TODO remove this ? // Compulsory for the time being, because bundle artifact extends // artifact if (!fileNode.isNodeType(SlcTypes.SLC_ARTIFACT)) { ArtifactIndexer indexer = new ArtifactIndexer(); indexer.index(fileNode); } fileNode.addMixin(SlcTypes.SLC_BUNDLE_ARTIFACT); // symbolic name String symbolicName = attrs.getValue(Constants.BUNDLE_SYMBOLICNAME); // make sure there is no directive symbolicName = symbolicName.split(";")[0]; fileNode.setProperty(SlcNames.SLC_SYMBOLIC_NAME, symbolicName); // direct mapping addAttr(Constants.BUNDLE_SYMBOLICNAME, fileNode, attrs); addAttr(Constants.BUNDLE_NAME, fileNode, attrs); addAttr(Constants.BUNDLE_DESCRIPTION, fileNode, attrs); addAttr(Constants.BUNDLE_MANIFESTVERSION, fileNode, attrs); addAttr(Constants.BUNDLE_CATEGORY, fileNode, attrs); addAttr(Constants.BUNDLE_ACTIVATIONPOLICY, fileNode, attrs); addAttr(Constants.BUNDLE_COPYRIGHT, fileNode, attrs); addAttr(Constants.BUNDLE_VENDOR, fileNode, attrs); addAttr("Bundle-License", fileNode, attrs); addAttr(Constants.BUNDLE_DOCURL, fileNode, attrs); addAttr(Constants.BUNDLE_CONTACTADDRESS, fileNode, attrs); addAttr(Constants.BUNDLE_ACTIVATOR, fileNode, attrs); addAttr(Constants.BUNDLE_UPDATELOCATION, fileNode, attrs); addAttr(Constants.BUNDLE_LOCALIZATION, fileNode, attrs); // required execution environment if (attrs.containsKey(new Name(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT))) fileNode.setProperty(SlcNames.SLC_ + Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT, attrs.getValue(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT).split(",")); // bundle classpath if (attrs.containsKey(new Name(Constants.BUNDLE_CLASSPATH))) fileNode.setProperty(SlcNames.SLC_ + Constants.BUNDLE_CLASSPATH, attrs.getValue(Constants.BUNDLE_CLASSPATH).split(",")); // version Version version = new Version(attrs.getValue(Constants.BUNDLE_VERSION)); fileNode.setProperty(SlcNames.SLC_BUNDLE_VERSION, version.toString()); cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.BUNDLE_VERSION); Node bundleVersionNode = fileNode.addNode(SlcNames.SLC_ + Constants.BUNDLE_VERSION, SlcTypes.SLC_OSGI_VERSION); mapOsgiVersion(version, bundleVersionNode); // fragment cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.FRAGMENT_HOST); if (attrs.containsKey(new Name(Constants.FRAGMENT_HOST))) { String fragmentHost = attrs.getValue(Constants.FRAGMENT_HOST); String[] tokens = fragmentHost.split(";"); Node node = fileNode.addNode(SlcNames.SLC_ + Constants.FRAGMENT_HOST, SlcTypes.SLC_FRAGMENT_HOST); node.setProperty(SlcNames.SLC_SYMBOLIC_NAME, tokens[0]); for (int i = 1; i < tokens.length; i++) { if (tokens[i].startsWith(Constants.BUNDLE_VERSION_ATTRIBUTE)) { node.setProperty(SlcNames.SLC_BUNDLE_VERSION, attributeValue(tokens[i])); } } } // imported packages cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.IMPORT_PACKAGE); if (attrs.containsKey(new Name(Constants.IMPORT_PACKAGE))) { String importPackages = attrs.getValue(Constants.IMPORT_PACKAGE); List packages = parseCommaSeparated(importPackages); for (String pkg : packages) { String[] tokens = pkg.split(";"); Node node = fileNode.addNode(SlcNames.SLC_ + Constants.IMPORT_PACKAGE, SlcTypes.SLC_IMPORTED_PACKAGE); node.setProperty(SlcNames.SLC_NAME, tokens[0]); for (int i = 1; i < tokens.length; i++) { if (tokens[i].startsWith(Constants.VERSION_ATTRIBUTE)) { node.setProperty(SlcNames.SLC_VERSION, attributeValue(tokens[i])); } else if (tokens[i].startsWith(Constants.RESOLUTION_DIRECTIVE)) { node.setProperty(SlcNames.SLC_OPTIONAL, directiveValue(tokens[i]).equals(Constants.RESOLUTION_OPTIONAL)); } } } } // dynamic import package cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.DYNAMICIMPORT_PACKAGE); if (attrs.containsKey(new Name(Constants.DYNAMICIMPORT_PACKAGE))) { String importPackages = attrs.getValue(Constants.DYNAMICIMPORT_PACKAGE); List packages = parseCommaSeparated(importPackages); for (String pkg : packages) { String[] tokens = pkg.split(";"); Node node = fileNode.addNode(SlcNames.SLC_ + Constants.DYNAMICIMPORT_PACKAGE, SlcTypes.SLC_DYNAMIC_IMPORTED_PACKAGE); node.setProperty(SlcNames.SLC_NAME, tokens[0]); for (int i = 1; i < tokens.length; i++) { if (tokens[i].startsWith(Constants.VERSION_ATTRIBUTE)) { node.setProperty(SlcNames.SLC_VERSION, attributeValue(tokens[i])); } } } } // exported packages cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.EXPORT_PACKAGE); if (attrs.containsKey(new Name(Constants.EXPORT_PACKAGE))) { String exportPackages = attrs.getValue(Constants.EXPORT_PACKAGE); List packages = parseCommaSeparated(exportPackages); for (String pkg : packages) { String[] tokens = pkg.split(";"); Node node = fileNode.addNode(SlcNames.SLC_ + Constants.EXPORT_PACKAGE, SlcTypes.SLC_EXPORTED_PACKAGE); node.setProperty(SlcNames.SLC_NAME, tokens[0]); // TODO: are these cleans really necessary? cleanSubNodes(node, SlcNames.SLC_USES); cleanSubNodes(node, SlcNames.SLC_VERSION); for (int i = 1; i < tokens.length; i++) { if (tokens[i].startsWith(Constants.VERSION_ATTRIBUTE)) { String versionStr = attributeValue(tokens[i]); Node versionNode = node.addNode(SlcNames.SLC_VERSION, SlcTypes.SLC_OSGI_VERSION); mapOsgiVersion(new Version(versionStr), versionNode); } else if (tokens[i].startsWith(Constants.USES_DIRECTIVE)) { String usedPackages = directiveValue(tokens[i]); // log.debug("uses='" + usedPackages + "'"); for (String usedPackage : usedPackages.split(",")) { // log.debug("usedPackage='" + // usedPackage + // "'"); Node usesNode = node.addNode(SlcNames.SLC_USES, SlcTypes.SLC_JAVA_PACKAGE); usesNode.setProperty(SlcNames.SLC_NAME, usedPackage); } } } } } // required bundle cleanSubNodes(fileNode, SlcNames.SLC_ + Constants.REQUIRE_BUNDLE); if (attrs.containsKey(new Name(Constants.REQUIRE_BUNDLE))) { String requireBundle = attrs.getValue(Constants.REQUIRE_BUNDLE); List bundles = parseCommaSeparated(requireBundle); for (String bundle : bundles) { String[] tokens = bundle.split(";"); Node node = fileNode.addNode(SlcNames.SLC_ + Constants.REQUIRE_BUNDLE, SlcTypes.SLC_REQUIRED_BUNDLE); node.setProperty(SlcNames.SLC_SYMBOLIC_NAME, tokens[0]); for (int i = 1; i < tokens.length; i++) { if (tokens[i].startsWith(Constants.BUNDLE_VERSION_ATTRIBUTE)) { node.setProperty(SlcNames.SLC_BUNDLE_VERSION, attributeValue(tokens[i])); } else if (tokens[i].startsWith(Constants.RESOLUTION_DIRECTIVE)) { node.setProperty(SlcNames.SLC_OPTIONAL, directiveValue(tokens[i]).equals(Constants.RESOLUTION_OPTIONAL)); } } } } } private void addAttr(String key, Node node, Attributes attrs) throws RepositoryException { addAttr(new Name(key), node, attrs); } private void addAttr(Name key, Node node, Attributes attrs) throws RepositoryException { if (attrs.containsKey(key)) { String value = attrs.getValue(key); node.setProperty(SlcNames.SLC_ + key, value); } } private void cleanSubNodes(Node node, String name) throws RepositoryException { if (node.hasNode(name)) { NodeIterator nit = node.getNodes(name); while (nit.hasNext()) nit.nextNode().remove(); } } private String attributeValue(String str) { return extractValue(str, "="); } private String directiveValue(String str) { return extractValue(str, ":="); } private String extractValue(String str, String eq) { String[] tokens = str.split(eq); // String key = tokens[0]; String value = tokens[1].trim(); // TODO: optimize? if (value.startsWith("\"")) value = value.substring(1); if (value.endsWith("\"")) value = value.substring(0, value.length() - 1); return value; } /** Parse package list with nested directive with ',' */ private List parseCommaSeparated(String str) { List res = new ArrayList(); StringBuffer curr = new StringBuffer(""); boolean in = false; for (char c : str.toCharArray()) { if (c == ',') { if (!in) {// new package res.add(curr.toString()); curr = new StringBuffer(""); } else {// a ',' within " " curr.append(c); } } else if (c == '\"') { in = !in; curr.append(c); } else { curr.append(c); } } res.add(curr.toString()); // log.debug(res); return res; } protected void mapOsgiVersion(Version version, Node versionNode) throws RepositoryException { versionNode.setProperty(SlcNames.SLC_AS_STRING, version.toString()); versionNode.setProperty(SlcNames.SLC_MAJOR, version.getMajor()); versionNode.setProperty(SlcNames.SLC_MINOR, version.getMinor()); versionNode.setProperty(SlcNames.SLC_MICRO, version.getMicro()); if (!version.getQualifier().equals("")) versionNode.setProperty(SlcNames.SLC_QUALIFIER, version.getQualifier()); } public void setForce(Boolean force) { this.force = force; } }