X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=org.argeo.slc.repo%2Fsrc%2Forg%2Fargeo%2Fslc%2Frepo%2Fmaven%2FGenerateBinaries.java;fp=org.argeo.slc.repo%2Fsrc%2Forg%2Fargeo%2Fslc%2Frepo%2Fmaven%2FGenerateBinaries.java;h=095b371767e0cebafbb6110415fbe09cb2aa7cb2;hb=825d60c5348dbe3f5be25b0bccf7bdebfe694219;hp=0000000000000000000000000000000000000000;hpb=5e991fff5cba01858dcc5747a27e637325bc5c8e;p=gpl%2Fargeo-jcr.git diff --git a/org.argeo.slc.repo/src/org/argeo/slc/repo/maven/GenerateBinaries.java b/org.argeo.slc.repo/src/org/argeo/slc/repo/maven/GenerateBinaries.java new file mode 100644 index 0000000..095b371 --- /dev/null +++ b/org.argeo.slc.repo/src/org/argeo/slc/repo/maven/GenerateBinaries.java @@ -0,0 +1,537 @@ +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; + } +} \ No newline at end of file