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.
+ *
+ *
+ * - 200 success
+ * - 500 internal server error
+ *
+ *
+ *
+ *
+ * @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 ---------------------------------------------------------------