X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=org.argeo.slc.repo%2Fsrc%2Forg%2Fargeo%2Fslc%2Frepo%2FJarFileIndexer.java;fp=org.argeo.slc.repo%2Fsrc%2Forg%2Fargeo%2Fslc%2Frepo%2FJarFileIndexer.java;h=5d4bf5bc0cd275d781e95a98a168e452e3845dd2;hb=825d60c5348dbe3f5be25b0bccf7bdebfe694219;hp=0000000000000000000000000000000000000000;hpb=5e991fff5cba01858dcc5747a27e637325bc5c8e;p=gpl%2Fargeo-jcr.git diff --git a/org.argeo.slc.repo/src/org/argeo/slc/repo/JarFileIndexer.java b/org.argeo.slc.repo/src/org/argeo/slc/repo/JarFileIndexer.java new file mode 100644 index 0000000..5d4bf5b --- /dev/null +++ b/org.argeo.slc.repo/src/org/argeo/slc/repo/JarFileIndexer.java @@ -0,0 +1,443 @@ +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; + } + +}