package org.argeo.slc.repo.maven; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeSet; import javax.jcr.Credentials; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import org.argeo.api.cms.CmsLog; import org.argeo.jcr.JcrMonitor; import org.argeo.jcr.JcrUtils; import org.argeo.slc.SlcException; import org.argeo.slc.SlcNames; import org.argeo.slc.SlcTypes; import org.argeo.slc.repo.ArtifactIndexer; import org.argeo.slc.repo.RepoConstants; import org.argeo.slc.repo.RepoUtils; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; import org.osgi.framework.Version; /** * Generates binaries-, sources- and sdk-version.pom artifacts for a given * group. */ public class GenerateBinaries implements Runnable, SlcNames { private final static CmsLog log = CmsLog.getLog(GenerateBinaries.class); // Connection info private Repository repository; private Credentials credentials; private String workspace; // Business info private String groupId; private String parentPomCoordinates; private String version = null; // Constants private String artifactBasePath = RepoConstants.DEFAULT_ARTIFACTS_BASE_PATH; private List excludedSuffixes = new ArrayList(); // Indexes private Set binaries = new TreeSet(new ArtifactIdComparator()); private Set sources = new TreeSet(new ArtifactIdComparator()); // local cache private ArtifactIndexer artifactIndexer = new ArtifactIndexer(); private Node allArtifactsHighestVersion; public void run() { Session session = null; try { session = repository.login(credentials, workspace); Node groupNode = session.getNode(MavenConventionsUtils.groupPath(artifactBasePath, groupId)); internalPreProcessing(groupNode, null); internalProcessing(groupNode, null); } catch (Exception e) { throw new SlcException("Cannot normalize group " + groupId + " in " + workspace, e); } finally { JcrUtils.logoutQuietly(session); } } /** * Generates binaries-, sources- and sdk-version.pom artifacts for the given * version (or the highest of all children version if none is precised). * * By default, it includes each latest version of all artifact of this group. * * The 3 generated artifacts are then marked as modular distributions and * indexed. */ public static void processGroupNode(Node groupNode, String version, JcrMonitor monitor) throws RepositoryException { // TODO set artifactsBase based on group node GenerateBinaries gb = new GenerateBinaries(); String groupId = groupNode.getProperty(SlcNames.SLC_GROUP_BASE_ID).getString(); gb.setGroupId(groupId); gb.setVersion(version); // TODO use already done pre-processing gb.internalPreProcessing(groupNode, monitor); gb.internalProcessing(groupNode, monitor); } /** Only builds local indexes. Does not change anything in the local Session */ public static GenerateBinaries preProcessGroupNode(Node groupNode, JcrMonitor monitor) throws RepositoryException { // TODO set artifactsBase based on group node GenerateBinaries gb = new GenerateBinaries(); String groupId = groupNode.getProperty(SlcNames.SLC_GROUP_BASE_ID).getString(); gb.setGroupId(groupId); // gb.setVersion(version); // gb.setOverridePoms(overridePoms); gb.internalPreProcessing(groupNode, monitor); return gb; } // exposes indexes. to display results of the pre-processing phase. public Set getBinaries() { return binaries; } public Artifact getHighestArtifactVersion() throws RepositoryException { return allArtifactsHighestVersion == null ? null : RepoUtils.asArtifact(allArtifactsHighestVersion); } // ////////////////////////////////////// // INTERNAL METHODS /** * Browse all children of a Node considered as a folder that follows Aether * conventions i.e that has Aether's artifact base as children. * * Each of such child contains a set of Aether artifact versions. This methods * build the binaries {@code Set} and other indexes. It does not * impact the */ protected void internalPreProcessing(Node groupNode, JcrMonitor monitor) throws RepositoryException { if (monitor != null) monitor.subTask("Pre processing group " + groupId); // Process all direct children nodes, // gathering latest versions of each artifact allArtifactsHighestVersion = null; aBases: for (NodeIterator aBases = groupNode.getNodes(); aBases.hasNext();) { Node aBase = aBases.nextNode(); if (aBase.isNodeType(SlcTypes.SLC_ARTIFACT_BASE)) { Node highestAVersion = getArtifactLatestVersion(aBase); if (highestAVersion == null) continue aBases; else { // retrieve relevant child node // Information is stored on the NT_FILE child node. for (NodeIterator files = highestAVersion.getNodes(); files.hasNext();) { Node file = files.nextNode(); if (file.isNodeType(SlcTypes.SLC_BUNDLE_ARTIFACT)) { if (log.isDebugEnabled()) log.debug("Pre-Processing " + file.getName()); preProcessBundleArtifact(file); } } } } } // if (log.isDebugEnabled()) { // int bundleCount = symbolicNamesToNodes.size(); // log.debug("" + bundleCount + " bundles have been indexed for " // + groupId); // } } /** Does the real job : writes JCR META-DATA and generates binaries */ protected void internalProcessing(Node groupNode, JcrMonitor monitor) throws RepositoryException { if (monitor != null) monitor.subTask("Processing group " + groupId); Session session = groupNode.getSession(); // if version not set or empty, use the highest version // useful when indexing a product maven repository where // all artifacts have the same version for a given release // => the version can then be left empty if (version == null || version.trim().equals("")) if (allArtifactsHighestVersion != null) version = allArtifactsHighestVersion.getProperty(SLC_ARTIFACT_VERSION).getString(); else throw new SlcException("Group version " + version + " is empty."); // int bundleCount = symbolicNamesToNodes.size(); // int count = 1; // for (Node bundleNode : symbolicNamesToNodes.values()) { // if (log.isDebugEnabled()) // log.debug("Processing " + bundleNode.getName() + " ( " + count // + "/" + bundleCount + " )"); // // // processBundleArtifact(bundleNode); // // bundleNode.getSession().save(); // count++; // } // indexes Set indexes = new TreeSet(new ArtifactIdComparator()); Artifact indexArtifact; indexArtifact = writeIndex(session, RepoConstants.BINARIES_ARTIFACT_ID, binaries); indexes.add(indexArtifact); indexArtifact = writeIndex(session, RepoConstants.SOURCES_ARTIFACT_ID, sources); indexes.add(indexArtifact); // sdk writeIndex(session, RepoConstants.SDK_ARTIFACT_ID, indexes); if (monitor != null) monitor.worked(1); } protected void preProcessBundleArtifact(Node bundleNode) throws RepositoryException { String symbolicName = JcrUtils.get(bundleNode, SLC_SYMBOLIC_NAME); // Sanity check. if (symbolicName == null) log.warn("Symbolic name is null for bundle " + bundleNode); // Manage source bundles if (symbolicName.endsWith(".source")) { // TODO make a shared node with classifier 'sources'? String bundleName = RepoUtils.extractBundleNameFromSourceName(symbolicName); for (String excludedSuffix : excludedSuffixes) { if (bundleName.endsWith(excludedSuffix)) return;// skip adding to sources } sources.add(RepoUtils.asArtifact(bundleNode)); return; } // // Build indexes // NodeIterator exportPackages = bundleNode.getNodes(SLC_ // + Constants.EXPORT_PACKAGE); // while (exportPackages.hasNext()) { // Node exportPackage = exportPackages.nextNode(); // String pkg = JcrUtils.get(exportPackage, SLC_NAME); // packagesToSymbolicNames.put(pkg, symbolicName); // } // // symbolicNamesToNodes.put(symbolicName, bundleNode); // for (String excludedSuffix : excludedSuffixes) { // if (symbolicName.endsWith(excludedSuffix)) // return;// skip adding to binaries // } binaries.add(RepoUtils.asArtifact(bundleNode)); // Extra check. to remove if (bundleNode.getSession().hasPendingChanges()) throw new SlcException("Pending changes in the session, " + "this should not be true here."); } // protected void processBundleArtifact(Node bundleNode) // throws RepositoryException { // Node artifactFolder = bundleNode.getParent(); // String baseName = FilenameUtils.getBaseName(bundleNode.getName()); // // // pom // String pomName = baseName + ".pom"; // if (artifactFolder.hasNode(pomName) && !overridePoms) // return;// skip // // String pom = generatePomForBundle(bundleNode); // Node pomNode = JcrUtils.copyBytesAsFile(artifactFolder, pomName, // pom.getBytes()); // // checksum // String bundleSha = JcrUtils.checksumFile(bundleNode, "SHA-1"); // JcrUtils.copyBytesAsFile(artifactFolder, // bundleNode.getName() + ".sha1", bundleSha.getBytes()); // String pomSha = JcrUtils.checksumFile(pomNode, "SHA-1"); // JcrUtils.copyBytesAsFile(artifactFolder, pomNode.getName() + ".sha1", // pomSha.getBytes()); // } // //////////////////// // LOCAL WRITERS // private Artifact writeIndex(Session session, String artifactId, Set artifacts) throws RepositoryException { Artifact artifact = new DefaultArtifact(groupId, artifactId, "pom", version); Artifact parentArtifact = parentPomCoordinates != null ? new DefaultArtifact(parentPomCoordinates) : null; String pom = MavenConventionsUtils.artifactsAsDependencyPom(artifact, artifacts, parentArtifact); Node node = RepoUtils.copyBytesAsArtifact(session.getNode(artifactBasePath), artifact, pom.getBytes()); artifactIndexer.index(node); // TODO factorize String pomSha = JcrUtils.checksumFile(node, "SHA-1"); JcrUtils.copyBytesAsFile(node.getParent(), node.getName() + ".sha1", pomSha.getBytes()); String pomMd5 = JcrUtils.checksumFile(node, "MD5"); JcrUtils.copyBytesAsFile(node.getParent(), node.getName() + ".md5", pomMd5.getBytes()); session.save(); return artifact; } // Helpers private Node getArtifactLatestVersion(Node artifactBase) { try { Node highestAVersion = null; for (NodeIterator aVersions = artifactBase.getNodes(); aVersions.hasNext();) { Node aVersion = aVersions.nextNode(); if (aVersion.isNodeType(SlcTypes.SLC_ARTIFACT_VERSION_BASE)) { if (highestAVersion == null) { highestAVersion = aVersion; if (allArtifactsHighestVersion == null) allArtifactsHighestVersion = aVersion; // Correctly handle following arrival order: // Name1 - V1, name2 - V3 else { Version cachedHighestVersion = extractOsgiVersion(allArtifactsHighestVersion); Version currVersion = extractOsgiVersion(aVersion); if (currVersion.compareTo(cachedHighestVersion) > 0) allArtifactsHighestVersion = aVersion; } } else { Version currVersion = extractOsgiVersion(aVersion); Version currentHighestVersion = extractOsgiVersion(highestAVersion); if (currVersion.compareTo(currentHighestVersion) > 0) { highestAVersion = aVersion; } if (currVersion.compareTo(extractOsgiVersion(allArtifactsHighestVersion)) > 0) { allArtifactsHighestVersion = aVersion; } } } } return highestAVersion; } catch (RepositoryException re) { throw new SlcException("Unable to get latest version for node " + artifactBase, re); } } private Version extractOsgiVersion(Node artifactVersion) throws RepositoryException { String rawVersion = artifactVersion.getProperty(SLC_ARTIFACT_VERSION).getString(); String cleanVersion = rawVersion.replace("-SNAPSHOT", ".SNAPSHOT"); Version osgiVersion = null; // log invalid version value to enable tracking them try { osgiVersion = new Version(cleanVersion); } catch (IllegalArgumentException e) { log.error("Version string " + cleanVersion + " is invalid "); String twickedVersion = twickInvalidVersion(cleanVersion); osgiVersion = new Version(twickedVersion); log.error("Using " + twickedVersion + " instead"); // throw e; } return osgiVersion; } private String twickInvalidVersion(String tmpVersion) { String[] tokens = tmpVersion.split("\\."); if (tokens.length == 3 && tokens[2].lastIndexOf("-") > 0) { String newSuffix = tokens[2].replaceFirst("-", "."); tmpVersion = tmpVersion.replaceFirst(tokens[2], newSuffix); } else if (tokens.length > 4) { // FIXME manually remove other "." StringTokenizer st = new StringTokenizer(tmpVersion, ".", true); StringBuilder builder = new StringBuilder(); // Major builder.append(st.nextToken()).append(st.nextToken()); // Minor builder.append(st.nextToken()).append(st.nextToken()); // Micro builder.append(st.nextToken()).append(st.nextToken()); // Qualifier builder.append(st.nextToken()); while (st.hasMoreTokens()) { // consume delimiter st.nextToken(); if (st.hasMoreTokens()) builder.append("-").append(st.nextToken()); } tmpVersion = builder.toString(); } return tmpVersion; } // private String generatePomForBundle(Node n) throws RepositoryException { // String ownSymbolicName = JcrUtils.get(n, SLC_SYMBOLIC_NAME); // // StringBuffer p = new StringBuffer(); // // // XML header // p.append("\n"); // p.append("\n"); // p.append("4.0.0"); // // // Artifact // p.append("").append(JcrUtils.get(n, SLC_GROUP_ID)) // .append("\n"); // p.append("").append(JcrUtils.get(n, SLC_ARTIFACT_ID)) // .append("\n"); // p.append("").append(JcrUtils.get(n, SLC_ARTIFACT_VERSION)) // .append("\n"); // p.append("pom\n"); // if (n.hasProperty(SLC_ + Constants.BUNDLE_NAME)) // p.append("") // .append(JcrUtils.get(n, SLC_ + Constants.BUNDLE_NAME)) // .append("\n"); // if (n.hasProperty(SLC_ + Constants.BUNDLE_DESCRIPTION)) // p.append("") // .append(JcrUtils // .get(n, SLC_ + Constants.BUNDLE_DESCRIPTION)) // .append("\n"); // // // Dependencies // Set dependenciesSymbolicNames = new TreeSet(); // Set optionalSymbolicNames = new TreeSet(); // NodeIterator importPackages = n.getNodes(SLC_ // + Constants.IMPORT_PACKAGE); // while (importPackages.hasNext()) { // Node importPackage = importPackages.nextNode(); // String pkg = JcrUtils.get(importPackage, SLC_NAME); // if (packagesToSymbolicNames.containsKey(pkg)) { // String dependencySymbolicName = packagesToSymbolicNames // .get(pkg); // if (JcrUtils.check(importPackage, SLC_OPTIONAL)) // optionalSymbolicNames.add(dependencySymbolicName); // else // dependenciesSymbolicNames.add(dependencySymbolicName); // } else { // if (!JcrUtils.check(importPackage, SLC_OPTIONAL) // && !systemPackages.contains(pkg)) // log.warn("No bundle found for pkg " + pkg); // } // } // // if (n.hasNode(SLC_ + Constants.FRAGMENT_HOST)) { // String fragmentHost = JcrUtils.get( // n.getNode(SLC_ + Constants.FRAGMENT_HOST), // SLC_SYMBOLIC_NAME); // dependenciesSymbolicNames.add(fragmentHost); // } // // // TODO require bundles // // List dependencyNodes = new ArrayList(); // for (String depSymbName : dependenciesSymbolicNames) { // if (depSymbName.equals(ownSymbolicName)) // continue;// skip self // // if (symbolicNamesToNodes.containsKey(depSymbName)) // dependencyNodes.add(symbolicNamesToNodes.get(depSymbName)); // else // log.warn("Could not find node for " + depSymbName); // } // List optionalDependencyNodes = new ArrayList(); // for (String depSymbName : optionalSymbolicNames) { // if (symbolicNamesToNodes.containsKey(depSymbName)) // optionalDependencyNodes.add(symbolicNamesToNodes // .get(depSymbName)); // else // log.warn("Could not find node for " + depSymbName); // } // // p.append("\n"); // for (Node dependencyNode : dependencyNodes) { // p.append("\n"); // p.append("\t") // .append(JcrUtils.get(dependencyNode, SLC_GROUP_ID)) // .append("\n"); // p.append("\t") // .append(JcrUtils.get(dependencyNode, SLC_ARTIFACT_ID)) // .append("\n"); // p.append("\n"); // } // // if (optionalDependencyNodes.size() > 0) // p.append("\n"); // for (Node dependencyNode : optionalDependencyNodes) { // p.append("\n"); // p.append("\t") // .append(JcrUtils.get(dependencyNode, SLC_GROUP_ID)) // .append("\n"); // p.append("\t") // .append(JcrUtils.get(dependencyNode, SLC_ARTIFACT_ID)) // .append("\n"); // p.append("\ttrue\n"); // p.append("\n"); // } // p.append("\n"); // // // Dependency management // p.append("\n"); // p.append("\n"); // p.append("\n"); // p.append("\t").append(groupId).append("\n"); // p.append("\t") // .append(ownSymbolicName.endsWith(".source") ? // RepoConstants.SOURCES_ARTIFACT_ID // : RepoConstants.BINARIES_ARTIFACT_ID) // .append("\n"); // p.append("\t").append(version).append("\n"); // p.append("\tpom\n"); // p.append("\timport\n"); // p.append("\n"); // p.append("\n"); // p.append("\n"); // // p.append("\n"); // return p.toString(); // } /* SETTERS */ public void setRepository(Repository repository) { this.repository = repository; } public void setCredentials(Credentials credentials) { this.credentials = credentials; } public void setWorkspace(String workspace) { this.workspace = workspace; } public void setGroupId(String groupId) { this.groupId = groupId; } public void setParentPomCoordinates(String parentPomCoordinates) { this.parentPomCoordinates = parentPomCoordinates; } public void setArtifactBasePath(String artifactBasePath) { this.artifactBasePath = artifactBasePath; } public void setVersion(String version) { this.version = version; } public void setExcludedSuffixes(List excludedSuffixes) { this.excludedSuffixes = excludedSuffixes; } public void setArtifactIndexer(ArtifactIndexer artifactIndexer) { this.artifactIndexer = artifactIndexer; } }