package org.argeo.slc.repo.maven; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.util.Comparator; import java.util.HashSet; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import javax.jcr.Binary; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.Session; import javax.jcr.nodetype.NodeType; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.jcr.JcrUtils; import org.argeo.slc.SlcException; import org.argeo.slc.aether.AetherTemplate; import org.sonatype.aether.artifact.Artifact; import org.sonatype.aether.graph.DependencyNode; import org.sonatype.aether.util.artifact.DefaultArtifact; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; public class ImportMavenDependencies implements Runnable { private final static Log log = LogFactory .getLog(ImportMavenDependencies.class); private AetherTemplate aetherTemplate; private String rootCoordinates; private Set excludedArtifacts = new HashSet(); private Session jcrSession; private String artifactBasePath = "/slc/repo/artifacts"; public void run() { Set artifacts = resolveDistribution(); syncDistribution(artifacts); } public Set resolveDistribution() { try { Artifact pomArtifact = new DefaultArtifact(rootCoordinates); Comparator artifactComparator = new Comparator() { public int compare(Artifact o1, Artifact o2) { return o1.getArtifactId().compareTo(o2.getArtifactId()); } }; Set registeredArtifacts = new TreeSet( artifactComparator); parsePom(aetherTemplate, registeredArtifacts, pomArtifact); if (log.isDebugEnabled()) log.debug("Gathered " + registeredArtifacts.size() + " artifacts"); // Resolve and add non-optional dependencies Set artifacts = new TreeSet(artifactComparator); for (Artifact artifact : registeredArtifacts) { try { addArtifact(artifacts, artifact); DependencyNode node = aetherTemplate .resolveDependencies(artifact); addDependencies(artifacts, node); } catch (Exception e) { log.error("Could not resolve dependencies of " + artifact + ": " + e.getCause().getMessage()); } } if (log.isDebugEnabled()) log.debug("Resolved " + artifacts.size() + " artifacts"); Properties distributionDescriptor = new Properties(); for (Artifact artifact : artifacts) { log.debug(artifact.getArtifactId() + " [" + artifact.getVersion() + "]\t(" + artifact + ")"); distributionDescriptor.setProperty(artifact.getArtifactId() + ":" + artifact.getVersion(), artifact.toString()); } ByteArrayOutputStream out = new ByteArrayOutputStream(); distributionDescriptor.store(out, ""); log.debug(new String(out.toByteArray())); out.close(); return artifacts; } catch (Exception e) { throw new SlcException("Cannot resolve distribution", e); } } protected void syncDistribution(Set artifacts) { Long begin = System.currentTimeMillis(); try { JcrUtils.mkdirs(jcrSession, artifactBasePath); for (Artifact artifact : artifacts) { String parentPath = artifactBasePath + '/' + artifactParentPath(artifact); Node parentNode; if (!jcrSession.itemExists(parentPath)) { parentNode = JcrUtils.mkdirs(jcrSession, parentPath, NodeType.NT_FOLDER, false); } else { parentNode = jcrSession.getNode(parentPath); } File file = artifact.getFile(); Node fileNode; if (!parentNode.hasNode(file.getName())) { fileNode = createFileNode(parentNode, file); } else { fileNode = parentNode.getNode(file.getName()); } } Long duration = (System.currentTimeMillis() - begin) / 1000; if (log.isDebugEnabled()) log.debug("Synchronized distribution in " + duration + "s"); } catch (Exception e) { throw new SlcException("Cannot synchronize distribution", e); } } protected String artifactParentPath(Artifact artifact) { return artifact.getGroupId().replace('.', '/') + '/' + artifact.getArtifactId() + '/' + artifact.getVersion(); } protected Node createFileNode(Node parentNode, File file) { try { Node fileNode = parentNode .addNode(file.getName(), NodeType.NT_FILE); Node contentNode = fileNode.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE); Binary binary = jcrSession.getValueFactory().createBinary( new FileInputStream(file)); contentNode.setProperty(Property.JCR_DATA, binary); binary.dispose(); return fileNode; } catch (Exception e) { throw new SlcException("Cannot create file node based on " + file + " under " + parentNode, e); } } /** Recursively adds non optional dependencies */ private void addDependencies(Set artifacts, DependencyNode node) { for (DependencyNode child : node.getChildren()) { if (!child.getDependency().isOptional()) { addArtifact(artifacts, child.getDependency().getArtifact()); addDependencies(artifacts, child); } } } private void addArtifact(Set artifacts, Artifact artifact) { if (!excludedArtifacts.contains(artifact.getGroupId() + ":" + artifact.getArtifactId())) artifacts.add(artifact); } /** * Directly parses Maven POM XML format in order to find all artifacts * references under the dependency and dependencyManagement tags. This is * meant to migrate existing pom registering a lot of artifacts, not to * replace Maven resolving. */ protected void parsePom(AetherTemplate aetherTemplate, Set artifacts, Artifact pomArtifact) { if (log.isDebugEnabled()) log.debug("Gather dependencies for " + pomArtifact); try { File file = aetherTemplate.getResolvedFile(pomArtifact); DocumentBuilder documentBuilder = DocumentBuilderFactory .newInstance().newDocumentBuilder(); Document doc = documentBuilder.parse(file); // 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()); } } } // dependencies (direct and dependencyManagement) NodeList dependencies = doc.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 parsePom(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); } } } catch (Exception e) { throw new SlcException("Cannot process " + pomArtifact, e); } } public void setAetherTemplate(AetherTemplate aetherTemplate) { this.aetherTemplate = aetherTemplate; } public void setExcludedArtifacts(Set excludedArtifactIds) { this.excludedArtifacts = excludedArtifactIds; } public void setRootCoordinates(String rootCoordinates) { this.rootCoordinates = rootCoordinates; } public void setJcrSession(Session jcrSession) { this.jcrSession = jcrSession; } }