package org.argeo.slc.repo;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
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.Property;
import javax.jcr.nodetype.NodeType;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.argeo.jcr.JcrUtils;
import org.argeo.slc.CategorizedNameVersion;
import org.argeo.slc.DefaultNameVersion;
import org.argeo.slc.SlcException;
import org.argeo.slc.aether.AetherUtils;
import org.argeo.slc.aether.ArtifactIdComparator;
import org.argeo.slc.jcr.SlcNames;
import org.argeo.slc.jcr.SlcTypes;
import org.osgi.framework.Constants;
import org.sonatype.aether.artifact.Artifact;
import org.sonatype.aether.util.artifact.DefaultArtifact;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
* Create or update JCR meta-data for an SLC Modular Distribution
*
* Currently, following types are managed: * .jar: dependency
* artifacts with csv index .pom: artifact (binaries) that indexes a
* group, the .pom file contains a tag "dependencyManagement" that list all
* modules
*/
public class ModularDistributionIndexer implements NodeIndexer, SlcNames {
private final static Log log = LogFactory
.getLog(ModularDistributionIndexer.class);
// Constants for csv indexing
private final static String INDEX_FILE_NAME = "modularDistribution.csv";
private String separator = ",";
// Artifact indexing
private final static List BINARIES_ARTIFACTS_NAME;
static {
List tmpList = new ArrayList();
tmpList.add(RepoConstants.BINARIES_ARTIFACT_ID);
// tmpList.add(RepoConstants.SOURCES_ARTIFACT_ID);
// tmpList.add(RepoConstants.SDK_ARTIFACT_ID);
BINARIES_ARTIFACTS_NAME = Collections.unmodifiableList(tmpList);
}
private Manifest manifest;
// private String symbolicName;
// private String version;
// private List artifacts;
private Comparator artifactComparator = new ArtifactIdComparator();
// private Set artifacts = new
// TreeSet(artifactComparator);
public Boolean support(String path) {
if (FilenameUtils.getExtension(path).equals("jar"))
return true;
if (FilenameUtils.getExtension(path).equals("pom")
&& BINARIES_ARTIFACTS_NAME.contains(FilenameUtils.getName(path)
.split("-")[0]))
return true;
return false;
}
public void index(Node fileNode) {
// JarInputStream jarIn = null;
Binary fileBinary = null;
try {
String fileNodePath = fileNode.getPath();
if (!support(fileNodePath))
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();
Set artifacts = new TreeSet(artifactComparator);
MyCategorizedNameVersion currCatNV = null;
if (FilenameUtils.getExtension(fileNode.getPath()).equals("jar"))
currCatNV = listModulesFromCsvIndex(artifacts, fileNode,
fileBinary);
else if (FilenameUtils.getExtension(fileNode.getPath()).equals(
"pom"))
currCatNV = listModulesFromPomIndex(artifacts, fileNode,
fileBinary);
if (artifacts.isEmpty())
return; // no modules found
else {
Node modules;
if (fileNode.isNodeType(SlcTypes.SLC_MODULAR_DISTRIBUTION)) {
modules = fileNode.getNode(SlcNames.SLC_MODULES);
} else {
fileNode.addMixin(SlcTypes.SLC_MODULAR_DISTRIBUTION);
fileNode.addMixin(SlcTypes.SLC_CATEGORIZED_NAME_VERSION);
if (currCatNV.getCategory() != null)
fileNode.setProperty(SLC_CATEGORY,
currCatNV.getCategory());
fileNode.setProperty(SLC_NAME, currCatNV.getName());
fileNode.setProperty(SLC_VERSION, currCatNV.getVersion());
modules = JcrUtils.mkdirs(fileNode, SlcNames.SLC_MODULES,
NodeType.NT_UNSTRUCTURED);
}
for (Artifact artifact : artifacts) {
// TODO clean this once an overwrite policy has been
// decided.
if (!modules.hasNode(artifact.getArtifactId())) {
Node moduleCoord = modules.addNode(
artifact.getArtifactId(),
SlcTypes.SLC_MODULE_COORDINATES);
moduleCoord.setProperty(SlcNames.SLC_NAME,
artifact.getArtifactId());
moduleCoord.setProperty(SlcNames.SLC_VERSION,
artifact.getVersion());
String groupId = artifact.getGroupId();
if (groupId != null && !"".equals(groupId.trim()))
moduleCoord.setProperty(SlcNames.SLC_CATEGORY,
artifact.getGroupId());
}
}
}
if (log.isTraceEnabled())
log.trace("Indexed " + fileNode + " as modular distribution");
} catch (Exception e) {
throw new SlcException("Cannot list dependencies from " + fileNode,
e);
}
}
protected MyCategorizedNameVersion listModulesFromCsvIndex(
Set artifacts, Node fileNode, Binary fileBinary) {
JarInputStream jarIn = null;
BufferedReader reader = null;
try {
jarIn = new JarInputStream(fileBinary.getStream());
// meta data
manifest = jarIn.getManifest();
if (manifest == null) {
log.error(fileNode + " has no MANIFEST");
return null;
}
String symbolicName = manifest.getMainAttributes().getValue(
Constants.BUNDLE_SYMBOLICNAME);
String version = manifest.getMainAttributes().getValue(
Constants.BUNDLE_VERSION);
String category = manifest.getMainAttributes().getValue(
"SLC-GroupId");
// Retrieve the index file
JarEntry indexEntry;
while ((indexEntry = jarIn.getNextJarEntry()) != null) {
String entryName = indexEntry.getName();
if (entryName.equals(INDEX_FILE_NAME)) {
break;
}
try {
jarIn.closeEntry();
} catch (SecurityException se) {
log.error("Invalid signature file digest "
+ "for Manifest main attributes: " + entryName
+ " while looking for an index in bundle "
+ symbolicName);
}
}
if (indexEntry == null)
return null; // Not a modular definition artifact
// Process the index
reader = new BufferedReader(new InputStreamReader(jarIn));
String line = null;
while ((line = reader.readLine()) != null) {
StringTokenizer st = new StringTokenizer(line, separator);
st.nextToken(); // moduleName
st.nextToken(); // moduleVersion
String relativeUrl = st.nextToken();
artifacts.add(AetherUtils.convertPathToArtifact(relativeUrl,
null));
// if (log.isTraceEnabled())
// log.trace("Processed dependency: " + line);
}
return new MyCategorizedNameVersion(category, symbolicName, version);
} catch (Exception e) {
throw new SlcException("Cannot list artifacts", e);
} finally {
IOUtils.closeQuietly(jarIn);
IOUtils.closeQuietly(reader);
}
}
protected MyCategorizedNameVersion listModulesFromPomIndex(
Set artifacts, Node fileNode, Binary fileBinary) {
InputStream input = null;
try {
input = fileBinary.getStream();
DocumentBuilder documentBuilder = DocumentBuilderFactory
.newInstance().newDocumentBuilder();
Document doc = documentBuilder.parse(input);
// properties
Properties props = new Properties();
// props.setProperty("project.version",
// pomArtifact.getBaseVersion());
NodeList properties = doc.getElementsByTagName("properties");
if (properties.getLength() > 0) {
NodeList propertiesElems = properties.item(0).getChildNodes();
for (int i = 0; i < propertiesElems.getLength(); i++) {
if (propertiesElems.item(i) instanceof Element) {
Element property = (Element) propertiesElems.item(i);
props.put(property.getNodeName(),
property.getTextContent());
}
}
}
// full coordinates are under
NodeList dependencies = ((Element) doc.getElementsByTagName(
"dependencyManagement").item(0))
.getElementsByTagName("dependency");
for (int i = 0; i < dependencies.getLength(); i++) {
Element dependency = (Element) dependencies.item(i);
String groupId = dependency.getElementsByTagName("groupId")
.item(0).getTextContent().trim();
String artifactId = dependency
.getElementsByTagName("artifactId").item(0)
.getTextContent().trim();
String version = dependency.getElementsByTagName("version")
.item(0).getTextContent().trim();
// if (version.startsWith("${")) {
// String versionKey = version.substring(0,
// version.length() - 1).substring(2);
// if (!props.containsKey(versionKey))
// throw new SlcException("Cannot interpret version "
// + version);
// version = props.getProperty(versionKey);
// }
// NodeList scopes = dependency.getElementsByTagName("scope");
// if (scopes.getLength() > 0
// && scopes.item(0).getTextContent().equals("import")) {
// // recurse
// gatherPomDependencies(aetherTemplate, artifacts,
// new DefaultArtifact(groupId, artifactId, "pom",
// version));
// } else {
// TODO: deal with scope?
// TODO: deal with type
String type = "jar";
Artifact artifact = new DefaultArtifact(groupId, artifactId,
type, version);
artifacts.add(artifact);
// }
}
String groupId = doc.getElementsByTagName("groupId").item(0)
.getTextContent().trim();
String artifactId = doc.getElementsByTagName("artifactId").item(0)
.getTextContent().trim();
String version = doc.getElementsByTagName("version").item(0)
.getTextContent().trim();
return new MyCategorizedNameVersion(groupId, artifactId, version);
} catch (Exception e) {
throw new SlcException("Cannot process pom " + fileNode, e);
} finally {
IOUtils.closeQuietly(input);
}
}
/** Separator used to parse the tabular file, default is "," */
public void setSeparator(String modulesUrlSeparator) {
this.separator = modulesUrlSeparator;
}
/** The created modular distribution */
private static class MyCategorizedNameVersion extends DefaultNameVersion
implements CategorizedNameVersion {
private final String category;
public MyCategorizedNameVersion(String category, String name,
String version) {
super(name, version);
this.category = category;
}
public String getCategory() {
return category;
}
}
}