--- /dev/null
+package org.argeo.slc.repo.osgi;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.jar.JarInputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.slc.CategoryNameVersion;
+import org.argeo.slc.DefaultNameVersion;
+import org.argeo.slc.ModuleSet;
+import org.argeo.slc.NameVersion;
+import org.argeo.slc.SlcException;
+import org.argeo.slc.build.Distribution;
+import org.argeo.slc.build.License;
+import org.argeo.slc.repo.OsgiFactory;
+import org.argeo.slc.repo.RepoUtils;
+import org.argeo.slc.repo.internal.springutil.AntPathMatcher;
+import org.argeo.slc.repo.internal.springutil.PathMatcher;
+import org.argeo.slc.repo.maven.ArtifactIdComparator;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+
+import aQute.bnd.osgi.Jar;
+
+/**
+ * Download a software distribution and generates the related OSGi bundles from
+ * the jars, or import them directly if they are already OSGi bundles and don't
+ * need further modification.
+ */
+public class ArchiveWrapper implements Runnable, ModuleSet, Distribution {
+ private final static CmsLog log = CmsLog.getLog(ArchiveWrapper.class);
+
+ private OsgiFactory osgiFactory;
+ private String version;
+ private License license;
+
+ private String uri;
+
+ /** Jars to wrap as OSGi bundles */
+ private Map<String, BndWrapper> wrappers = new HashMap<String, BndWrapper>();
+
+ private SourcesProvider sourcesProvider;
+
+ // pattern of OSGi bundles to import
+ private PathMatcher pathMatcher = new AntPathMatcher();
+ private Map<String, String> includes = new HashMap<String, String>();
+ private List<String> excludes = new ArrayList<String>();
+
+ private Boolean mavenGroupIndexes = false;
+
+ public void init() {
+ for (BndWrapper wrapper : wrappers.values()) {
+ wrapper.setFactory(this);
+ if (version != null && wrapper.getVersion() == null)
+ wrapper.setVersion(version);
+ if (license != null && wrapper.getLicense() == null)
+ wrapper.setLicense(license);
+ }
+ }
+
+ public void destroy() {
+
+ }
+
+ public String getDistributionId() {
+ return uri;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public License getLicense() {
+ return license;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public Iterator<? extends NameVersion> nameVersions() {
+ if (wrappers.size() > 0)
+ return wrappers.values().iterator();
+ else
+ return osgiNameVersions();
+ }
+
+ @SuppressWarnings("resource")
+ protected Iterator<? extends NameVersion> osgiNameVersions() {
+ List<CategoryNameVersion> nvs = new ArrayList<CategoryNameVersion>();
+
+ Session distSession = null;
+ ZipInputStream zin = null;
+ try {
+ distSession = osgiFactory.openDistSession();
+
+ Node distNode = osgiFactory.getDist(distSession, uri);
+ zin = new ZipInputStream(
+ distNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary().getStream());
+
+ ZipEntry zentry = null;
+ entries: while ((zentry = zin.getNextEntry()) != null) {
+ String name = zentry.getName();
+ if (log.isTraceEnabled())
+ log.trace("Zip entry " + name);
+ for (String exclude : excludes)
+ if (pathMatcher.match(exclude, name))
+ continue entries;
+
+ for (String include : includes.keySet()) {
+ if (pathMatcher.match(include, name)) {
+ String groupId = includes.get(include);
+ JarInputStream jis = new JarInputStream(zin);
+ if (jis.getManifest() == null) {
+ log.warn("No MANIFEST in entry " + name + ", skipping...");
+ continue entries;
+ }
+ NameVersion nv = RepoUtils.readNameVersion(jis.getManifest());
+ if (nv != null) {
+ if (nv.getName().endsWith(".source"))
+ continue entries;
+ CategoryNameVersion cnv = new ArchiveWrapperCNV(groupId, nv.getName(), nv.getVersion(),
+ this);
+ nvs.add(cnv);
+ // no need to process further includes
+ continue entries;
+ }
+ }
+ }
+ }
+ return nvs.iterator();
+ } catch (Exception e) {
+ throw new SlcException("Cannot wrap distribution " + uri, e);
+ } finally {
+ IOUtils.closeQuietly(zin);
+ JcrUtils.logoutQuietly(distSession);
+ }
+ }
+
+ public void run() {
+ if (mavenGroupIndexes && (version == null))
+ throw new SlcException("'mavenGroupIndexes' requires 'version' to be set");
+
+ Map<String, Set<Artifact>> binaries = new HashMap<String, Set<Artifact>>();
+ Map<String, Set<Artifact>> sources = new HashMap<String, Set<Artifact>>();
+
+ Session distSession = null;
+ Session javaSession = null;
+ ZipInputStream zin = null;
+ try {
+ javaSession = osgiFactory.openJavaSession();
+ distSession = osgiFactory.openDistSession();
+
+ if (log.isDebugEnabled())
+ log.debug("Wrapping " + uri);
+ boolean nothingWasDone = true;
+
+ Node distNode = osgiFactory.getDist(distSession, uri);
+ zin = new ZipInputStream(
+ distNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary().getStream());
+
+ ZipEntry zentry = null;
+ entries: while ((zentry = zin.getNextEntry()) != null) {
+ String name = zentry.getName();
+
+ // sources autodetect
+ String baseName = FilenameUtils.getBaseName(name);
+ if (baseName.endsWith("-sources")) {
+ String bundle = baseName.substring(0, baseName.length() - "-sources".length());
+ // log.debug(name + "," + baseName + ", " + bundle);
+ String bundlePath = FilenameUtils.getPath(name) + bundle + ".jar";
+ if (wrappers.containsKey(bundlePath)) {
+ BndWrapper wrapper = wrappers.get(bundlePath);
+ NameVersion bundleNv = new DefaultNameVersion(wrapper.getName(), wrapper.getVersion());
+ byte[] pdeSource = RepoUtils.packageAsPdeSource(zin, bundleNv);
+ Artifact sourcesArtifact = new DefaultArtifact(wrapper.getCategory(),
+ wrapper.getName() + ".source", "jar", wrapper.getVersion());
+ Node pdeSourceNode = RepoUtils.copyBytesAsArtifact(javaSession.getRootNode(), sourcesArtifact,
+ pdeSource);
+ osgiFactory.indexNode(pdeSourceNode);
+ pdeSourceNode.getSession().save();
+ if (log.isDebugEnabled())
+ log.debug("Added sources " + sourcesArtifact + " for bundle " + wrapper.getArtifact()
+ + "from " + name + " in binary archive.");
+ }
+
+ }
+ // else if (baseName.endsWith(".source")) {
+ // }
+
+ // binaries
+ if (wrappers.containsKey(name)) {
+ BndWrapper wrapper = (BndWrapper) wrappers.get(name);
+ // we must copy since the stream is closed by BND
+ byte[] origJarBytes = IOUtils.toByteArray(zin);
+ Artifact artifact = wrapZipEntry(javaSession, zentry, origJarBytes, wrapper);
+ nothingWasDone = false;
+ addArtifactToIndex(binaries, wrapper.getGroupId(), artifact);
+ } else {
+ for (String wrapperKey : wrappers.keySet())
+ if (pathMatcher.match(wrapperKey, name)) {
+ // first matched is taken
+ BndWrapper wrapper = (BndWrapper) wrappers.get(wrapperKey);
+ // we must copy since the stream is closed by BND
+ byte[] origJarBytes = IOUtils.toByteArray(zin);
+ Artifact artifact = wrapZipEntry(javaSession, zentry, origJarBytes, wrapper);
+ nothingWasDone = false;
+ addArtifactToIndex(binaries, wrapper.getGroupId(), artifact);
+ continue entries;
+ } else {
+ if (log.isTraceEnabled())
+ log.trace(name + " not matched by " + wrapperKey);
+ }
+
+ for (String exclude : excludes)
+ if (pathMatcher.match(exclude, name))
+ continue entries;
+
+ for (String include : includes.keySet()) {
+ if (pathMatcher.match(include, name)) {
+ String groupId = includes.get(include);
+ byte[] origJarBytes = IOUtils.toByteArray(zin);
+ Artifact artifact = importZipEntry(javaSession, zentry, origJarBytes, groupId);
+ if (artifact == null) {
+ log.warn("Skipped non identified " + zentry);
+ continue entries;
+ }
+ nothingWasDone = false;
+ if (artifact.getArtifactId().endsWith(".source"))
+ addArtifactToIndex(sources, groupId, artifact);
+ else
+ addArtifactToIndex(binaries, groupId, artifact);
+ // no need to process this entry further
+ continue entries;
+ }
+ }
+ }
+ }
+
+ // indexes
+ if (mavenGroupIndexes && version != null) {
+ for (String groupId : binaries.keySet()) {
+ RepoUtils.writeGroupIndexes(javaSession, "/", groupId, version, binaries.get(groupId),
+ sources.containsKey(groupId) ? sources.get(groupId) : null);
+ }
+ }
+
+ if (nothingWasDone) {
+ log.error("Nothing was done when wrapping " + uri + ". THE DISTRIBUTION IS INCONSISTENT.");
+ // throw new SlcException("Nothing was done");
+ // TODO Fail if not all wrappers matched
+ }
+
+ } catch (Exception e) {
+ throw new SlcException("Cannot wrap distribution " + uri, e);
+ } finally {
+ IOUtils.closeQuietly(zin);
+ JcrUtils.logoutQuietly(distSession);
+ JcrUtils.logoutQuietly(javaSession);
+ }
+ }
+
+ protected Artifact wrapZipEntry(Session javaSession, ZipEntry zentry, byte[] origJarBytes, BndWrapper wrapper)
+ throws RepositoryException {
+ ByteArrayOutputStream out = null;
+ ByteArrayInputStream in = null;
+ Node newJarNode;
+ Jar jar = null;
+ try {
+ out = new ByteArrayOutputStream((int) zentry.getSize());
+ in = new ByteArrayInputStream(origJarBytes);
+ wrapper.wrapJar(in, out);
+
+ Artifact artifact = wrapper.getArtifact();
+ newJarNode = RepoUtils.copyBytesAsArtifact(javaSession.getRootNode(), artifact, out.toByteArray());
+ osgiFactory.indexNode(newJarNode);
+ newJarNode.getSession().save();
+ if (log.isDebugEnabled())
+ log.debug("Wrapped jar " + zentry.getName() + " to " + newJarNode.getPath());
+
+ if (sourcesProvider != null)
+ addSource(javaSession, artifact, out.toByteArray());
+
+ return artifact;
+ } finally {
+ IOUtils.closeQuietly(in);
+ IOUtils.closeQuietly(out);
+ if (jar != null)
+ jar.close();
+ }
+ }
+
+ protected void addSource(Session javaSession, Artifact artifact, byte[] binaryJarBytes) {
+ InputStream in = null;
+ ByteArrayOutputStream out = null;
+ Jar jar = null;
+ try {
+ in = new ByteArrayInputStream(binaryJarBytes);
+ jar = new Jar(null, in);
+ List<String> packages = jar.getPackages();
+
+ out = new ByteArrayOutputStream();
+ sourcesProvider.writeSources(packages, new ZipOutputStream(out));
+
+ IOUtils.closeQuietly(in);
+ in = new ByteArrayInputStream(out.toByteArray());
+ byte[] sourcesJar = RepoUtils.packageAsPdeSource(in,
+ new DefaultNameVersion(artifact.getArtifactId(), artifact.getVersion()));
+ Artifact sourcesArtifact = new DefaultArtifact(artifact.getGroupId(), artifact.getArtifactId() + ".source",
+ "jar", artifact.getVersion());
+ Node sourcesJarNode = RepoUtils.copyBytesAsArtifact(javaSession.getRootNode(), sourcesArtifact, sourcesJar);
+ sourcesJarNode.getSession().save();
+
+ if (log.isDebugEnabled())
+ log.debug("Added sources " + sourcesArtifact + " for bundle " + artifact + "from source provider "
+ + sourcesProvider);
+ } catch (Exception e) {
+ throw new SlcException("Cannot get sources for " + artifact, e);
+ } finally {
+ IOUtils.closeQuietly(in);
+ IOUtils.closeQuietly(out);
+ if (jar != null)
+ jar.close();
+ }
+ }
+
+ protected Artifact importZipEntry(Session javaSession, ZipEntry zentry, byte[] binaryJarBytes, String groupId)
+ throws RepositoryException {
+ ByteArrayInputStream in = null;
+ Node newJarNode;
+ try {
+ in = new ByteArrayInputStream(binaryJarBytes);
+ NameVersion nameVersion = RepoUtils.readNameVersion(in);
+ if (nameVersion == null) {
+ log.warn("Cannot identify " + zentry.getName());
+ return null;
+ }
+ Artifact artifact = new DefaultArtifact(groupId, nameVersion.getName(), "jar", nameVersion.getVersion());
+ newJarNode = RepoUtils.copyBytesAsArtifact(javaSession.getRootNode(), artifact, binaryJarBytes);
+ osgiFactory.indexNode(newJarNode);
+ newJarNode.getSession().save();
+ if (log.isDebugEnabled()) {
+ log.debug(zentry.getName() + " => " + artifact);
+ }
+
+ if (sourcesProvider != null)
+ addSource(javaSession, artifact, binaryJarBytes);
+
+ return artifact;
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+ }
+
+ private void addArtifactToIndex(Map<String, Set<Artifact>> index, String groupId, Artifact artifact) {
+ if (!index.containsKey(groupId))
+ index.put(groupId, new TreeSet<Artifact>(new ArtifactIdComparator()));
+ index.get(groupId).add(artifact);
+ }
+
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ public void setWrappers(Map<String, BndWrapper> wrappers) {
+ this.wrappers = wrappers;
+ }
+
+ public void setOsgiFactory(OsgiFactory osgiFactory) {
+ this.osgiFactory = osgiFactory;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public void setLicense(License license) {
+ this.license = license;
+ }
+
+ public void setPathMatcher(PathMatcher pathMatcher) {
+ this.pathMatcher = pathMatcher;
+ }
+
+ public void setIncludes(Map<String, String> includes) {
+ this.includes = includes;
+ }
+
+ public void setExcludes(List<String> excludes) {
+ this.excludes = excludes;
+ }
+
+ public void setMavenGroupIndexes(Boolean mavenGroupIndexes) {
+ this.mavenGroupIndexes = mavenGroupIndexes;
+ }
+
+ public void setSourcesProvider(SourcesProvider sourcesProvider) {
+ this.sourcesProvider = sourcesProvider;
+ }
+
+ public Map<String, BndWrapper> getWrappers() {
+ return wrappers;
+ }
+
+ public Map<String, String> getIncludes() {
+ return includes;
+ }
+
+ public List<String> getExcludes() {
+ return excludes;
+ }
+
+}