Merge tag 'v2.3.20' into testing
[gpl/argeo-jcr.git] / org.argeo.slc.repo / src / org / argeo / slc / repo / ModularDistributionFactory.java
diff --git a/org.argeo.slc.repo/src/org/argeo/slc/repo/ModularDistributionFactory.java b/org.argeo.slc.repo/src/org/argeo/slc/repo/ModularDistributionFactory.java
new file mode 100644 (file)
index 0000000..19627d8
--- /dev/null
@@ -0,0 +1,519 @@
+package org.argeo.slc.repo;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.slc.CategoryNameVersion;
+import org.argeo.slc.NameVersion;
+import org.argeo.slc.SlcException;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.osgi.framework.Constants;
+
+/**
+ * Creates a jar bundle from an ArgeoOsgiDistribution. This jar is then
+ * persisted and indexed in a java repository using the OSGI Factory.
+ * 
+ * It does the following <list>
+ * <li>Creates a Manifest</li>
+ * <li>Creates files indexes (csv, feature.xml ...)</li>
+ * <li>Populate the corresponding jar</li>
+ * <li>Save it in the repository</li>
+ * <li>Index the node and creates corresponding sha1 and md5 files</li> </list>
+ * 
+ */
+public class ModularDistributionFactory implements Runnable {
+
+       private OsgiFactory osgiFactory;
+       private Session javaSession;
+       private ArgeoOsgiDistribution osgiDistribution;
+       private String modularDistributionSeparator = ",";
+       private String artifactBasePath = RepoConstants.DEFAULT_ARTIFACTS_BASE_PATH;
+       private String artifactType = "jar";
+
+       // Constants
+       private final static String CSV_FILE_NAME = "modularDistribution.csv";
+       private final DateFormat snapshotTimestamp = new SimpleDateFormat("YYYYMMddhhmm");
+
+       // private final static String FEATURE_FILE_NAME = "feature.xml";
+       // private static int BUFFER_SIZE = 10240;
+
+       /** Convenience constructor with minimal configuration */
+       public ModularDistributionFactory(OsgiFactory osgiFactory, ArgeoOsgiDistribution osgiDistribution) {
+               this.osgiFactory = osgiFactory;
+               this.osgiDistribution = osgiDistribution;
+       }
+
+       @Override
+       public void run() {
+               byte[] distFile = null;
+               try {
+                       javaSession = osgiFactory.openJavaSession();
+
+                       if (artifactType == "jar")
+                               distFile = generateJarFile();
+                       else if (artifactType == "pom")
+                               distFile = generatePomFile();
+                       else
+                               throw new SlcException("Unimplemented distribution artifact type: " + artifactType + " for "
+                                               + osgiDistribution.toString());
+
+                       // Save in java repository
+                       Artifact osgiArtifact = new DefaultArtifact(osgiDistribution.getCategory(), osgiDistribution.getName(),
+                                       artifactType, osgiDistribution.getVersion());
+
+                       Node distNode = RepoUtils.copyBytesAsArtifact(javaSession.getNode(artifactBasePath), osgiArtifact,
+                                       distFile);
+
+                       // index
+                       osgiFactory.indexNode(distNode);
+
+                       // We use a specific session. Save before closing
+                       javaSession.save();
+               } catch (RepositoryException e) {
+                       throw new SlcException(
+                                       "JCR error while persisting modular distribution in JCR " + osgiDistribution.toString(), e);
+               } finally {
+                       JcrUtils.logoutQuietly(javaSession);
+               }
+       }
+
+       private byte[] generateJarFile() {
+               ByteArrayOutputStream byteOut = null;
+               JarOutputStream jarOut = null;
+               try {
+                       byteOut = new ByteArrayOutputStream();
+                       jarOut = new JarOutputStream(byteOut, createManifest());
+                       // Create various indexes
+                       addToJar(createCsvDescriptor(), CSV_FILE_NAME, jarOut);
+                       jarOut.close();
+                       return byteOut.toByteArray();
+               } catch (IOException e) {
+                       throw new SlcException("IO error while generating modular distribution " + osgiDistribution.toString(), e);
+               } finally {
+                       IOUtils.closeQuietly(byteOut);
+                       IOUtils.closeQuietly(jarOut);
+               }
+       }
+
+       // private void indexDistribution(Node distNode) throws RepositoryException
+       // {
+       // distNode.addMixin(SlcTypes.SLC_MODULAR_DISTRIBUTION);
+       // distNode.addMixin(SlcTypes.SLC_CATEGORIZED_NAME_VERSION);
+       // distNode.setProperty(SlcNames.SLC_CATEGORY,
+       // osgiDistribution.getCategory());
+       // distNode.setProperty(SlcNames.SLC_NAME, osgiDistribution.getName());
+       // distNode.setProperty(SlcNames.SLC_VERSION,
+       // osgiDistribution.getVersion());
+       //
+       // if (distNode.hasNode(SlcNames.SLC_MODULES))
+       // distNode.getNode(SlcNames.SLC_MODULES).remove();
+       // Node modules = distNode.addNode(SlcNames.SLC_MODULES,
+       // NodeType.NT_UNSTRUCTURED);
+       //
+       // for (Iterator<? extends NameVersion> it = osgiDistribution
+       // .nameVersions(); it.hasNext();)
+       // addModule(modules, it.next());
+       // }
+
+       private Manifest createManifest() {
+               Manifest manifest = new Manifest();
+
+               // TODO make this configurable
+               manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+//             addManifestAttribute(manifest, Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT, "JavaSE-1.8");
+               addManifestAttribute(manifest, Constants.BUNDLE_VENDOR, "Argeo");
+               addManifestAttribute(manifest, Constants.BUNDLE_MANIFESTVERSION, "2");
+//             addManifestAttribute(manifest, "Bundle-License", "http://www.apache.org/licenses/LICENSE-2.0.txt");
+
+               // TODO define a user friendly name
+               addManifestAttribute(manifest, Constants.BUNDLE_NAME, osgiDistribution.getName());
+
+               // Categorized name version
+               addManifestAttribute(manifest, RepoConstants.SLC_CATEGORY_ID, osgiDistribution.getCategory());
+               addManifestAttribute(manifest, Constants.BUNDLE_SYMBOLICNAME, osgiDistribution.getName());
+               String version = osgiDistribution.getVersion();
+               if (version.endsWith("-SNAPSHOT")) {
+                       version = version.substring(0, version.length() - "-SNAPSHOT".length());
+                       version = version + ".SNAPSHOT-r" + snapshotTimestamp.format(new Date());
+               }
+               addManifestAttribute(manifest, Constants.BUNDLE_VERSION, version);
+
+               return manifest;
+       }
+
+       private void addManifestAttribute(Manifest manifest, String name, String value) {
+               manifest.getMainAttributes().put(new Attributes.Name(name), value);
+       }
+
+       private byte[] createCsvDescriptor() {
+               Writer writer = null;
+               try {
+                       // FIXME remove use of tmp file.
+                       File tmpFile = File.createTempFile("modularDistribution", "csv");
+                       tmpFile.deleteOnExit();
+                       writer = new FileWriter(tmpFile);
+                       // Populate the file
+                       for (Iterator<? extends NameVersion> it = osgiDistribution.nameVersions(); it.hasNext();)
+                               writer.write(getCsvLine(it.next()));
+                       writer.flush();
+                       return FileUtils.readFileToByteArray(tmpFile);
+               } catch (Exception e) {
+                       throw new SlcException("unable to create csv distribution file for " + osgiDistribution.toString(), e);
+               } finally {
+                       IOUtils.closeQuietly(writer);
+               }
+       }
+
+       @SuppressWarnings("unused")
+       private byte[] createFeatureDescriptor() {
+               // Directly retrieved from Argeo maven plugin
+               // Does not work due to the lack of org.codehaus.plexus/plexus-archiver
+               // third party dependency
+
+               throw new SlcException("Unimplemented method");
+
+               // // protected void writeFeatureDescriptor() throws
+               // MojoExecutionException {
+               // File featureDesc = File.createTempFile("feature", "xml");
+               // featureDesc.deleteOnExit();
+               //
+               // Writer writer = null;
+               // try {
+               // writer = new FileWriter(featureDesc);
+               // PrettyPrintXMLWriter xmlWriter = new PrettyPrintXMLWriter(writer);
+               // xmlWriter.startElement("feature");
+               // xmlWriter.addAttribute("id", project.getArtifactId());
+               // xmlWriter.addAttribute("label", project.getName());
+               //
+               // // Version
+               // String projectVersion = project.getVersion();
+               // int indexSnapshot = projectVersion.indexOf("-SNAPSHOT");
+               // if (indexSnapshot > -1)
+               // projectVersion = projectVersion.substring(0, indexSnapshot);
+               // projectVersion = projectVersion + ".qualifier";
+               //
+               // // project.
+               // xmlWriter.addAttribute("version", projectVersion);
+               //
+               // Organization organization = project.getOrganization();
+               // if (organization != null && organization.getName() != null)
+               // xmlWriter.addAttribute("provider-name", organization.getName());
+               //
+               // if (project.getDescription() != null || project.getUrl() != null) {
+               // xmlWriter.startElement("description");
+               // if (project.getUrl() != null)
+               // xmlWriter.addAttribute("url", project.getUrl());
+               // if (project.getDescription() != null)
+               // xmlWriter.writeText(project.getDescription());
+               // xmlWriter.endElement();// description
+               // }
+               //
+               // if (feature != null && feature.getCopyright() != null
+               // || (organization != null && organization.getUrl() != null)) {
+               // xmlWriter.startElement("copyright");
+               // if (organization != null && organization.getUrl() != null)
+               // xmlWriter.addAttribute("url", organization.getUrl());
+               // if (feature.getCopyright() != null)
+               // xmlWriter.writeText(feature.getCopyright());
+               // xmlWriter.endElement();// copyright
+               // }
+               //
+               // if (feature != null && feature.getUpdateSite() != null) {
+               // xmlWriter.startElement("url");
+               // xmlWriter.startElement("update");
+               // xmlWriter.addAttribute("url", feature.getUpdateSite());
+               // xmlWriter.endElement();// update
+               // xmlWriter.endElement();// url
+               // }
+               //
+               // List licenses = project.getLicenses();
+               // if (licenses.size() > 0) {
+               // // take the first one
+               // License license = (License) licenses.get(0);
+               // xmlWriter.startElement("license");
+               //
+               // if (license.getUrl() != null)
+               // xmlWriter.addAttribute("url", license.getUrl());
+               // if (license.getComments() != null)
+               // xmlWriter.writeText(license.getComments());
+               // else if (license.getName() != null)
+               // xmlWriter.writeText(license.getName());
+               // xmlWriter.endElement();// license
+               // }
+               //
+               // // deploymentRepository.pathOf(null);
+               // if (jarDirectory == null) {
+               // Set dependencies = mavenDependencyManager
+               // .getTransitiveProjectDependencies(project, remoteRepos,
+               // local);
+               // // // protected void writeFeatureDescriptor() throws
+               // MojoExecutionException {
+               // File featureDesc = File.createTempFile("feature", "xml");
+               // featureDesc.deleteOnExit();
+               //
+               // Writer writer = null;
+               // try {
+               // writer = new FileWriter(featureDesc);
+               // PrettyPrintXMLWriter xmlWriter = new PrettyPrintXMLWriter(writer);
+               // xmlWriter.startElement("feature");
+               // xmlWriter.addAttribute("id", project.getArtifactId());
+               // xmlWriter.addAttribute("label", project.getName());
+               //
+               // // Version
+               // String projectVersion = project.getVersion();
+               // int indexSnapshot = projectVersion.indexOf("-SNAPSHOT");
+               // if (indexSnapshot > -1)
+               // projectVersion = projectVersion.substring(0, indexSnapshot);
+               // projectVersion = projectVersion + ".qualifier";
+               //
+               // // project.
+               // xmlWriter.addAttribute("version", projectVersion);
+               //
+               // Organization organization = project.getOrganization();
+               // if (organization != null && organization.getName() != null)
+               // xmlWriter.addAttribute("provider-name", organization.getName());
+               //
+               // if (project.getDescription() != null || project.getUrl() != null) {
+               // xmlWriter.startElement("description");
+               // if (project.getUrl() != null)
+               // xmlWriter.addAttribute("url", project.getUrl());
+               // if (project.getDescription() != null)
+               // xmlWriter.writeText(project.getDescription());
+               // xmlWriter.endElement();// description
+               // }
+               //
+               // if (feature != null && feature.getCopyright() != null
+               // || (organization != null && organization.getUrl() != null)) {
+               // xmlWriter.startElement("copyright");
+               // if (organization != null && organization.getUrl() != null)
+               // xmlWriter.addAttribute("url", organization.getUrl());
+               // if (feature.getCopyright() != null)
+               // xmlWriter.writeText(feature.getCopyright());
+               // xmlWriter.endElement();// copyright
+               // }
+               //
+               // if (feature != null && feature.getUpdateSite() != null) {
+               // xmlWriter.startElement("url");
+               // xmlWriter.startElement("update");
+               // xmlWriter.addAttribute("url", feature.getUpdateSite());
+               // xmlWriter.endElement();// update
+               // xmlWriter.endElement();// url
+               // }
+               //
+               // List licenses = project.getLicenses();
+               // if (licenses.size() > 0) {
+               // // take the first one
+               // License license = (License) licenses.get(0);
+               // xmlWriter.startElement("license");
+               //
+               // if (license.getUrl() != null)
+               // xmlWriter.addAttribute("url", license.getUrl());
+               // if (license.getComments() != null)
+               // xmlWriter.writeText(license.getComments());
+               // else if (license.getName() != null)
+               // xmlWriter.writeText(license.getName());
+               // xmlWriter.endElement();// license
+               // }
+               //
+               // // deploymentRepository.pathOf(null);
+               // if (jarDirectory == null) {
+               // Set dependencies = mavenDependencyManager
+               // .getTransitiveProjectDependencies(project, remoteRepos,
+               // local);
+               // for (Iterator it = dependencies.iterator(); it.hasNext();) {
+               // Artifact artifact = (Artifact) it.next();
+               // writeFeaturePlugin(xmlWriter, artifact.getFile());
+               // }
+               // } else {
+               // // TODO: filter jars
+               // File[] jars = jarDirectory.listFiles();
+               // if (jars == null)
+               // throw new MojoExecutionException("No jar found in "
+               // + jarDirectory);
+               // for (int i = 0; i < jars.length; i++) {
+               // writeFeaturePlugin(xmlWriter, jars[i]);
+               // }
+               // }
+               //
+               // xmlWriter.endElement();// feature
+               //
+               // if (getLog().isDebugEnabled())
+               // getLog().debug("Wrote Eclipse feature descriptor.");
+               // } catch (Exception e) {
+               // throw new MojoExecutionException("Cannot write feature descriptor",
+               // e);
+               // } finally {
+               // IOUtil.close(writer);
+               // }for (Iterator it = dependencies.iterator(); it.hasNext();) {
+               // Artifact artifact = (Artifact) it.next();
+               // writeFeaturePlugin(xmlWriter, artifact.getFile());
+               // }
+               // } else {
+               // // TODO: filter jars
+               // File[] jars = jarDirectory.listFiles();
+               // if (jars == null)
+               // throw new MojoExecutionException("No jar found in "
+               // + jarDirectory);
+               // for (int i = 0; i < jars.length; i++) {
+               // writeFeaturePlugin(xmlWriter, jars[i]);
+               // }
+               // }
+               //
+               // xmlWriter.endElement();// feature
+               //
+               // if (getLog().isDebugEnabled())
+               // getLog().debug("Wrote Eclipse feature descriptor.");
+               // } catch (Exception e) {
+               // throw new MojoExecutionException("Cannot write feature descriptor",
+               // e);
+               // } finally {
+               // IOUtil.close(writer);
+               // }
+       }
+
+       /** Create an Aether like distribution artifact */
+       private byte[] generatePomFile() {
+               StringBuilder b = new StringBuilder();
+               // XML header
+               b.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+               b.append(
+                               "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n");
+               b.append("<modelVersion>4.0.0</modelVersion>");
+
+               // Artifact
+               b.append("<groupId>").append(osgiDistribution.getCategory()).append("</groupId>\n");
+               b.append("<artifactId>").append(osgiDistribution.getName()).append("</artifactId>\n");
+               b.append("<version>").append(osgiDistribution.getVersion()).append("</version>\n");
+               b.append("<packaging>pom</packaging>\n");
+               // p.append("<name>").append("Bundle Name").append("</name>\n");
+               // p.append("<description>").append("Bundle
+               // Description").append("</description>\n");
+
+               // Dependencies
+               b.append("<dependencies>\n");
+               for (Iterator<? extends NameVersion> it = osgiDistribution.nameVersions(); it.hasNext();) {
+                       NameVersion nameVersion = it.next();
+                       if (!(nameVersion instanceof CategoryNameVersion))
+                               throw new SlcException("Unsupported type " + nameVersion.getClass());
+                       CategoryNameVersion nv = (CategoryNameVersion) nameVersion;
+                       b.append(getDependencySnippet(nv, false));
+               }
+               b.append("</dependencies>\n");
+
+               // Dependency management
+               b.append("<dependencyManagement>\n");
+               b.append("<dependencies>\n");
+
+               for (Iterator<? extends NameVersion> it = osgiDistribution.nameVersions(); it.hasNext();)
+                       b.append(getDependencySnippet((CategoryNameVersion) it.next(), true));
+               b.append("</dependencies>\n");
+               b.append("</dependencyManagement>\n");
+
+               b.append("</project>\n");
+               return b.toString().getBytes();
+       }
+
+       private String getDependencySnippet(CategoryNameVersion cnv, boolean includeVersion) { // , String type, String
+                                                                                                                                                                                               // scope
+               StringBuilder b = new StringBuilder();
+               b.append("<dependency>\n");
+               b.append("\t<groupId>").append(cnv.getCategory()).append("</groupId>\n");
+               b.append("\t<artifactId>").append(cnv.getName()).append("</artifactId>\n");
+               if (includeVersion)
+                       b.append("\t<version>").append(cnv.getVersion()).append("</version>\n");
+               // if (type!= null)
+               // p.append("\t<type>").append(type).append("</type>\n");
+               // if (type!= null)
+               // p.append("\t<scope>").append(scope).append("</scope>\n");
+               b.append("</dependency>\n");
+               return b.toString();
+       }
+
+       // Helpers
+       private void addToJar(byte[] content, String name, JarOutputStream target) throws IOException {
+               ByteArrayInputStream in = null;
+               try {
+                       target.putNextEntry(new JarEntry(name));
+                       in = new ByteArrayInputStream(content);
+                       byte[] buffer = new byte[1024];
+                       while (true) {
+                               int count = in.read(buffer);
+                               if (count == -1)
+                                       break;
+                               target.write(buffer, 0, count);
+                       }
+                       target.closeEntry();
+               } finally {
+                       IOUtils.closeQuietly(in);
+               }
+       }
+
+       private String getCsvLine(NameVersion nameVersion) throws RepositoryException {
+               if (!(nameVersion instanceof CategoryNameVersion))
+                       throw new SlcException("Unsupported type " + nameVersion.getClass());
+               CategoryNameVersion cnv = (CategoryNameVersion) nameVersion;
+               StringBuilder builder = new StringBuilder();
+
+               builder.append(cnv.getName());
+               builder.append(modularDistributionSeparator);
+               builder.append(nameVersion.getVersion());
+               builder.append(modularDistributionSeparator);
+               builder.append(cnv.getCategory().replace('.', '/'));
+               // MavenConventionsUtils.groupPath("", cnv.getCategory());
+               builder.append('/');
+               builder.append(cnv.getName());
+               builder.append('/');
+               builder.append(cnv.getVersion());
+               builder.append('/');
+               builder.append(cnv.getName());
+               builder.append('-');
+               builder.append(cnv.getVersion());
+               builder.append('.');
+               // TODO make this dynamic
+               builder.append("jar");
+               builder.append("\n");
+
+               return builder.toString();
+       }
+
+       /** Enable dependency injection */
+       public void setOsgiFactory(OsgiFactory osgiFactory) {
+               this.osgiFactory = osgiFactory;
+       }
+
+       public void setOsgiDistribution(ArgeoOsgiDistribution osgiDistribution) {
+               this.osgiDistribution = osgiDistribution;
+       }
+
+       public void setModularDistributionSeparator(String modularDistributionSeparator) {
+               this.modularDistributionSeparator = modularDistributionSeparator;
+       }
+
+       public void setArtifactBasePath(String artifactBasePath) {
+               this.artifactBasePath = artifactBasePath;
+       }
+
+       public void setArtifactType(String artifactType) {
+               this.artifactType = artifactType;
+       }
+}
\ No newline at end of file