diff --git a/scm-core/src/main/java/sonia/scm/io/ZipUnArchiver.java b/scm-core/src/main/java/sonia/scm/io/ZipUnArchiver.java index 99fbd970a6..e26ff2f670 100644 --- a/scm-core/src/main/java/sonia/scm/io/ZipUnArchiver.java +++ b/scm-core/src/main/java/sonia/scm/io/ZipUnArchiver.java @@ -44,8 +44,10 @@ import sonia.scm.util.IOUtil; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -66,6 +68,30 @@ public class ZipUnArchiver extends AbstractUnArchiver //~--- methods -------------------------------------------------------------- + /** + * Method description + * + * + * @param inputStream + * @param outputDirectory + * + * @throws IOException + * @since 1.21 + */ + public void extractArchive(InputStream inputStream, File outputDirectory) + throws IOException + { + ZipInputStream input = new ZipInputStream(inputStream); + + ZipEntry entry = input.getNextEntry(); + + while (entry != null) + { + extractEntry(outputDirectory, input, entry); + entry = input.getNextEntry(); + } + } + /** * Method description * @@ -77,27 +103,20 @@ public class ZipUnArchiver extends AbstractUnArchiver */ @Override protected void extractArchive(File archive, File outputDirectory) - throws IOException + throws IOException { if (logger.isDebugEnabled()) { logger.debug("extract zip \"{}\" to \"{}\"", archive.getPath(), - outputDirectory.getAbsolutePath()); + outputDirectory.getAbsolutePath()); } - ZipInputStream input = null; + InputStream input = null; try { - input = new ZipInputStream(new FileInputStream(archive)); - - ZipEntry entry = input.getNextEntry(); - - while (entry != null) - { - extractEntry(outputDirectory, input, entry); - entry = input.getNextEntry(); - } + input = new FileInputStream(archive); + extractArchive(input, outputDirectory); } finally { @@ -135,8 +154,8 @@ public class ZipUnArchiver extends AbstractUnArchiver * @throws IOException */ private void extractEntry(File outputDirectory, ZipInputStream input, - ZipEntry entry) - throws IOException + ZipEntry entry) + throws IOException { try { @@ -177,7 +196,7 @@ public class ZipUnArchiver extends AbstractUnArchiver * @throws IOException */ private void extractFile(ZipInputStream input, File outputFile) - throws IOException + throws IOException { FileOutputStream output = null; diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java b/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java index 3c2156e623..d1bf4c62bf 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java @@ -35,6 +35,9 @@ package sonia.scm.plugin; //~--- JDK imports ------------------------------------------------------------ +import java.io.IOException; +import java.io.InputStream; + import java.util.Collection; /** @@ -58,6 +61,17 @@ public interface PluginManager */ public void install(String id); + /** + * Installs a plugin package from a inputstream. + * + * + * @param packageStream package input stream + * + * @throws IOException + * @since 1.21 + */ + public void installPackage(InputStream packageStream) throws IOException; + /** * Method description * diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 3dc30e3dd4..c184b36687 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -107,6 +107,12 @@ + + com.sun.jersey.contribs + jersey-multipart + ${jersey.version} + + @@ -203,6 +209,12 @@ ${aether.version} + + org.sonatype.aether + aether-connector-file + ${aether.version} + + diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java index a54e45e50c..4996c8fe98 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java @@ -47,12 +47,18 @@ import sonia.scm.plugin.PluginInformationComparator; //~--- JDK imports ------------------------------------------------------------ +import com.sun.jersey.multipart.FormDataParam; + +import java.io.IOException; +import java.io.InputStream; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; +import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; @@ -85,6 +91,33 @@ public class PluginResource //~--- methods -------------------------------------------------------------- + /** + * Installs a plugin from a package.
+ *
+ * + * + * + * + * @param uploadedInputStream + * @return + * + * @throws IOException + */ + @POST + @Path("install-package") + @Consumes(MediaType.MULTIPART_FORM_DATA) + public Response install( + @FormDataParam("package") InputStream uploadedInputStream) + throws IOException + { + pluginManager.installPackage(uploadedInputStream); + + return Response.ok().build(); + } + /** * Installs a plugin.
*
diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/AetherPluginHandler.java b/scm-webapp/src/main/java/sonia/scm/plugin/AetherPluginHandler.java index 11ed9e7aca..367861e7bd 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/AetherPluginHandler.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/AetherPluginHandler.java @@ -46,6 +46,7 @@ import org.slf4j.LoggerFactory; import org.sonatype.aether.RepositorySystem; import org.sonatype.aether.collection.CollectRequest; import org.sonatype.aether.connector.async.AsyncRepositoryConnectorFactory; +import org.sonatype.aether.connector.file.FileRepositoryConnectorFactory; import org.sonatype.aether.graph.Dependency; import org.sonatype.aether.graph.DependencyFilter; import org.sonatype.aether.graph.DependencyNode; @@ -343,6 +344,8 @@ public class AetherPluginHandler DefaultArtifactDescriptorReader.class); locator.addService(RepositoryConnectorFactory.class, AsyncRepositoryConnectorFactory.class); + locator.addService(RepositoryConnectorFactory.class, + FileRepositoryConnectorFactory.class); return locator.getService(RepositorySystem.class); } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java index 76519f70c0..7d7205cf87 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java @@ -35,6 +35,8 @@ package sonia.scm.plugin; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.collect.Sets; +import com.google.common.io.Files; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; @@ -49,6 +51,7 @@ import sonia.scm.SCMContextProvider; import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; import sonia.scm.config.ScmConfiguration; +import sonia.scm.io.ZipUnArchiver; import sonia.scm.net.HttpClient; import sonia.scm.security.SecurityContext; import sonia.scm.util.AssertUtil; @@ -59,6 +62,8 @@ import sonia.scm.util.Util; //~--- JDK imports ------------------------------------------------------------ +import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; @@ -72,6 +77,7 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import javax.xml.bind.JAXB; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; @@ -82,7 +88,7 @@ import javax.xml.bind.Unmarshaller; */ @Singleton public class DefaultPluginManager - implements PluginManager, ConfigChangedListener + implements PluginManager, ConfigChangedListener { /** Field description */ @@ -116,17 +122,16 @@ public class DefaultPluginManager * @param clientProvider */ @Inject - public DefaultPluginManager( - SCMContextProvider context, - Provider securityContextProvicer, - ScmConfiguration configuration, PluginLoader pluginLoader, - CacheManager cacheManager, Provider clientProvider) + public DefaultPluginManager(SCMContextProvider context, + Provider securityContextProvicer, + ScmConfiguration configuration, PluginLoader pluginLoader, + CacheManager cacheManager, Provider clientProvider) { this.context = context; this.securityContextProvicer = securityContextProvicer; this.configuration = configuration; this.cache = cacheManager.getCache(String.class, PluginCenter.class, - CACHE_NAME); + CACHE_NAME); this.clientProvider = clientProvider; installedPlugins = new HashMap(); @@ -214,6 +219,50 @@ public class DefaultPluginManager } } + /** + * Method description + * + * + * @param packageStream + * + * @throws IOException + */ + @Override + public void installPackage(InputStream packageStream) throws IOException + { + SecurityUtil.assertIsAdmin(securityContextProvicer); + + File tempDirectory = Files.createTempDir(); + + try + { + new ZipUnArchiver().extractArchive(packageStream, tempDirectory); + + Plugin plugin = JAXB.unmarshal(new File(tempDirectory, "plugin.xml"), + Plugin.class); + + // TODO check conditions + + + AetherPluginHandler aph = new AetherPluginHandler(this, context, + configuration); + Collection repositories = + Sets.newHashSet(new PluginRepository("package-repository", + "file://".concat(tempDirectory.getAbsolutePath()))); + + aph.setPluginRepositories(repositories); + + aph.install(plugin.getInformation().getId()); + plugin.getInformation().setState(PluginState.INSTALLED); + installedPlugins.put(plugin.getInformation().getId(), plugin); + + } + finally + { + IOUtil.delete(tempDirectory); + } + } + /** * Method description * @@ -277,7 +326,7 @@ public class DefaultPluginManager for (PluginInformation info : getInstalled()) { if (groupId.equals(info.getGroupId()) - && artefactId.equals(info.getArtifactId())) + && artefactId.equals(info.getArtifactId())) { installed = info; @@ -453,7 +502,7 @@ public class DefaultPluginManager } return url.replace("{version}", context.getVersion()).replace("{os}", - os).replace("{arch}", arch); + os).replace("{arch}", arch); } /** @@ -465,7 +514,7 @@ public class DefaultPluginManager * @param filter */ private void filter(Set target, - Collection source, PluginFilter filter) + Collection source, PluginFilter filter) { for (PluginInformation info : source) { @@ -588,7 +637,7 @@ public class DefaultPluginManager if (pluginHandler == null) { pluginHandler = new AetherPluginHandler(this, - SCMContext.getContext(), configuration); + SCMContext.getContext(), configuration); } pluginHandler.setPluginRepositories(center.getRepositories()); @@ -643,7 +692,7 @@ public class DefaultPluginManager PluginInformation installed = installedPlugin.getInformation(); if (isSamePlugin(available, installed) - && (installed.getState() == PluginState.CORE)) + && (installed.getState() == PluginState.CORE)) { core = true; @@ -664,7 +713,7 @@ public class DefaultPluginManager * @return */ private boolean isNewer(PluginInformation available, - PluginInformation installed) + PluginInformation installed) { boolean result = false; PluginVersion version = PluginVersion.createVersion(available.getVersion()); @@ -689,7 +738,7 @@ public class DefaultPluginManager private boolean isSamePlugin(PluginInformation p1, PluginInformation p2) { return p1.getGroupId().equals(p2.getGroupId()) - && p1.getArtifactId().equals(p2.getArtifactId()); + && p1.getArtifactId().equals(p2.getArtifactId()); } //~--- fields ---------------------------------------------------------------