diff --git a/docs/dtd/plugin/2.0.0-01.dtd b/docs/dtd/plugin/2.0.0-01.dtd
index eec149f3e8..954a2c2219 100644
--- a/docs/dtd/plugin/2.0.0-01.dtd
+++ b/docs/dtd/plugin/2.0.0-01.dtd
@@ -29,46 +29,28 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
@@ -121,4 +103,4 @@
-
\ No newline at end of file
+
diff --git a/pom.xml b/pom.xml
index ba06282720..3166c6ec75 100644
--- a/pom.xml
+++ b/pom.xml
@@ -437,7 +437,7 @@
sonia.scm.maven
smp-maven-plugin
- 1.0.0-alpha-4
+ 1.0.0-alpha-6
diff --git a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java
index d23bbaf07d..95deebfd60 100644
--- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java
+++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java
@@ -73,7 +73,7 @@ public class ScmConfiguration implements Configuration {
* Default plugin url
*/
public static final String DEFAULT_PLUGINURL =
- "http://plugins.scm-manager.org/scm-plugin-backend/api/{version}/plugins?os={os}&arch={arch}&snapshot=false";
+ "http://download.scm-manager.org/api/v2/plugins.json?os={os}&arch={arch}&snapshot=false&version={version}";
/**
* Default url for login information (plugin and feature tips on the login page).
diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java b/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java
index 6de52c3cca..22911041d4 100644
--- a/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java
+++ b/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java
@@ -1,19 +1,19 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
- *
+ *
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
- *
+ *
* 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
+ * this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
- * contributors may be used to endorse or promote products derived from this
- * software without specific prior written permission.
- *
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@@ -24,558 +24,88 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
+ *
* http://bitbucket.org/sdorra/scm-manager
- *
*/
-
package sonia.scm.plugin;
//~--- non-JDK imports --------------------------------------------------------
import com.github.sdorra.ssp.PermissionObject;
import com.github.sdorra.ssp.StaticPermissions;
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Objects;
+import lombok.Data;
import sonia.scm.Validateable;
import sonia.scm.util.Util;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
//~--- JDK imports ------------------------------------------------------------
/**
- *
* @author Sebastian Sdorra
*/
+@Data
@StaticPermissions(
- value = "plugin",
- generatedClass = "PluginPermissions",
+ value = "plugin",
+ generatedClass = "PluginPermissions",
permissions = {},
- globalPermissions = { "read", "manage" },
+ globalPermissions = {"read", "manage"},
custom = true, customGlobal = true
)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "plugin-information")
-public class PluginInformation
- implements PermissionObject, Validateable, Cloneable, Serializable
-{
+public class PluginInformation implements PermissionObject, Validateable, Cloneable, Serializable {
- /** Field description */
private static final long serialVersionUID = 461382048865977206L;
- //~--- methods --------------------------------------------------------------
+ private String name;
+ private String version;
+ private String displayName;
+ private String description;
+ private String author;
+ private String category;
+ private String avatarUrl;
+ private PluginCondition condition;
+ private PluginState state;
- /**
- * Method description
- *
- *
- * @return
- *
- * @since 1.11
- */
@Override
- public PluginInformation clone()
- {
+ public PluginInformation clone() {
PluginInformation clone = new PluginInformation();
-
- clone.setArtifactId(artifactId);
+ clone.setName(name);
+ clone.setVersion(version);
+ clone.setDisplayName(displayName);
+ clone.setDescription(description);
clone.setAuthor(author);
clone.setCategory(category);
- clone.setTags(tags);
-
- if (condition != null)
- {
+ clone.setAvatarUrl(avatarUrl);
+ clone.setState(state);
+ if (condition != null) {
clone.setCondition(condition.clone());
}
-
- clone.setDescription(description);
- clone.setGroupId(groupId);
- clone.setName(name);
-
- if (Util.isNotEmpty(screenshots))
- {
- clone.setScreenshots(new ArrayList(screenshots));
- }
-
- clone.setState(state);
- clone.setUrl(url);
- clone.setVersion(version);
- clone.setWiki(wiki);
-
return clone;
}
- /**
- * Method description
- *
- *
- * @param obj
- *
- * @return
- */
@Override
- public boolean equals(Object obj)
- {
- if (obj == null)
- {
- return false;
- }
-
- if (getClass() != obj.getClass())
- {
- return false;
- }
-
- final PluginInformation other = (PluginInformation) obj;
-
- //J-
- return Objects.equal(artifactId, other.artifactId)
- && Objects.equal(author, other.author)
- && Objects.equal(category, other.category)
- && Objects.equal(tags, other.tags)
- && Objects.equal(condition, other.condition)
- && Objects.equal(description, other.description)
- && Objects.equal(groupId, other.groupId)
- && Objects.equal(name, other.name)
- && Objects.equal(screenshots, other.screenshots)
- && Objects.equal(state, other.state)
- && Objects.equal(url, other.url)
- && Objects.equal(version, other.version)
- && Objects.equal(wiki, other.wiki);
- //J+
+ public String getId() {
+ return getName(true);
}
- /**
- * Method description
- *
- *
- * @return
- */
- @Override
- public int hashCode()
- {
- return Objects.hashCode(artifactId, author, category, tags, condition,
- description, groupId, name, screenshots, state, url, version, wiki);
- }
+ public String getName(boolean withVersion) {
+ StringBuilder id = new StringBuilder(name);
- /**
- * Method description
- *
- *
- * @return
- */
- @Override
- public String toString()
- {
- //J-
- return MoreObjects.toStringHelper(this)
- .add("artifactId", artifactId)
- .add("author", author)
- .add("category", category)
- .add("tags", tags)
- .add("condition", condition)
- .add("description", description)
- .add("groupId", groupId)
- .add("name", name)
- .add("screenshots", screenshots)
- .add("state", state)
- .add("url", url)
- .add("version", version)
- .add("wiki", wiki)
- .toString();
- //J+
- }
-
- //~--- get methods ----------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @return
- */
- public String getArtifactId()
- {
- return artifactId;
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- public String getAuthor()
- {
- return author;
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- public String getCategory()
- {
- return category;
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- public PluginCondition getCondition()
- {
- return condition;
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- public String getDescription()
- {
- return description;
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- public String getGroupId()
- {
- return groupId;
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- @Override
- public String getId()
- {
- return getId(true);
- }
-
- /**
- * Method description
- *
- *
- * @param withVersion
- *
- * @return
- * @since 1.21
- */
- public String getId(boolean withVersion)
- {
- StringBuilder id = new StringBuilder(groupId);
-
- id.append(":").append(artifactId);
-
- if (withVersion)
- {
+ if (withVersion) {
id.append(":").append(version);
}
-
return id.toString();
}
- /**
- * Method description
- *
- *
- * @return
- */
- public String getName()
- {
- return name;
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- public List getScreenshots()
- {
- return screenshots;
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- public PluginState getState()
- {
- return state;
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- public List getTags()
- {
- return tags;
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- public String getUrl()
- {
- return url;
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- public String getVersion()
- {
- return version;
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
- public String getWiki()
- {
- return wiki;
- }
-
- /**
- * Method description
- *
- *
- * @return
- */
@Override
- public boolean isValid()
- {
- return Util.isNotEmpty(groupId) && Util.isNotEmpty(artifactId)
- && Util.isNotEmpty(name) && Util.isNotEmpty(version);
+ public boolean isValid() {
+ return Util.isNotEmpty(name) && Util.isNotEmpty(version);
}
-
- //~--- set methods ----------------------------------------------------------
-
- /**
- * Method description
- *
- *
- * @param artifactId
- */
- public void setArtifactId(String artifactId)
- {
- this.artifactId = artifactId;
- }
-
- /**
- * Method description
- *
- *
- * @param author
- */
- public void setAuthor(String author)
- {
- this.author = author;
- }
-
- /**
- * Method description
- *
- *
- * @param category
- */
- public void setCategory(String category)
- {
- this.category = category;
- }
-
- /**
- * Method description
- *
- *
- * @param condition
- */
- public void setCondition(PluginCondition condition)
- {
- this.condition = condition;
- }
-
- /**
- * Method description
- *
- *
- * @param description
- */
- public void setDescription(String description)
- {
- this.description = description;
- }
-
- /**
- * Method description
- *
- *
- * @param groupId
- */
- public void setGroupId(String groupId)
- {
- this.groupId = groupId;
- }
-
- /**
- * Method description
- *
- *
- * @param name
- */
- public void setName(String name)
- {
- this.name = name;
- }
-
- /**
- * Method description
- *
- *
- * @param screenshots
- */
- public void setScreenshots(List screenshots)
- {
- this.screenshots = screenshots;
- }
-
- /**
- * Method description
- *
- *
- * @param state
- */
- public void setState(PluginState state)
- {
- this.state = state;
- }
-
- /**
- * Method description
- *
- *
- * @param tags
- */
- public void setTags(List tags)
- {
- this.tags = tags;
- }
-
- /**
- * Method description
- *
- *
- * @param url
- */
- public void setUrl(String url)
- {
- this.url = url;
- }
-
- /**
- * Method description
- *
- *
- * @param version
- */
- public void setVersion(String version)
- {
- this.version = version;
- }
-
- /**
- * Method description
- *
- *
- * @param wiki
- */
- public void setWiki(String wiki)
- {
- this.wiki = wiki;
- }
-
- //~--- fields ---------------------------------------------------------------
-
- /** Field description */
- private String artifactId;
-
- /** Field description */
- private String author;
-
- /** Field description */
- private String category;
-
- /** Field description */
- private PluginCondition condition;
-
- /** Field description */
- private String description;
-
- /** Field description */
- private String groupId;
-
- /** Field description */
- private String name;
-
- /** Field description */
- @XmlElement(name = "screenshot")
- @XmlElementWrapper(name = "screenshots")
- private List screenshots;
-
- /** Field description */
- private PluginState state;
-
- /** Field description */
- @XmlElement(name = "tag")
- @XmlElementWrapper(name = "tags")
- private List tags;
-
- /** Field description */
- private String url;
-
- /** Field description */
- private String version;
-
- /** Field description */
- private String wiki;
}
diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginInformationComparator.java b/scm-core/src/main/java/sonia/scm/plugin/PluginInformationComparator.java
index f44de35e8a..5443b2328d 100644
--- a/scm-core/src/main/java/sonia/scm/plugin/PluginInformationComparator.java
+++ b/scm-core/src/main/java/sonia/scm/plugin/PluginInformationComparator.java
@@ -75,29 +75,24 @@ public class PluginInformationComparator
{
int result = 0;
- result = Util.compare(plugin.getGroupId(), other.getGroupId());
+ result = Util.compare(plugin.getName(), other.getName());
if (result == 0)
{
- result = Util.compare(plugin.getArtifactId(), other.getArtifactId());
+ PluginState state = plugin.getState();
+ PluginState otherState = other.getState();
- if (result == 0)
+ if ((state != null) && (otherState != null))
{
- PluginState state = plugin.getState();
- PluginState otherState = other.getState();
-
- if ((state != null) && (otherState != null))
- {
- result = state.getCompareValue() - otherState.getCompareValue();
- }
- else if ((state == null) && (otherState != null))
- {
- result = 1;
- }
- else if ((state != null) && (otherState == null))
- {
- result = -1;
- }
+ result = state.getCompareValue() - otherState.getCompareValue();
+ }
+ else if ((state == null) && (otherState != null))
+ {
+ result = 1;
+ }
+ else if ((state != null) && (otherState == null))
+ {
+ result = -1;
}
}
diff --git a/scm-core/src/main/java/sonia/scm/plugin/SmpArchive.java b/scm-core/src/main/java/sonia/scm/plugin/SmpArchive.java
index 63d5e8fb8f..f674bdd2ba 100644
--- a/scm-core/src/main/java/sonia/scm/plugin/SmpArchive.java
+++ b/scm-core/src/main/java/sonia/scm/plugin/SmpArchive.java
@@ -219,16 +219,10 @@ public final class SmpArchive
throw new PluginException("could not find information section");
}
- if (Strings.isNullOrEmpty(info.getGroupId()))
+ if (Strings.isNullOrEmpty(info.getName()))
{
throw new PluginException(
- "could not find groupId in plugin descriptor");
- }
-
- if (Strings.isNullOrEmpty(info.getArtifactId()))
- {
- throw new PluginException(
- "could not find artifactId in plugin descriptor");
+ "could not find name in plugin descriptor");
}
if (Strings.isNullOrEmpty(info.getVersion()))
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java
index c06edcd918..5807ffa998 100644
--- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java
+++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java
@@ -31,7 +31,6 @@
package sonia.scm.repository.api;
-import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.cache.CacheManager;
diff --git a/scm-core/src/main/java/sonia/scm/util/IOUtil.java b/scm-core/src/main/java/sonia/scm/util/IOUtil.java
index 16f84d1031..4058c089f4 100644
--- a/scm-core/src/main/java/sonia/scm/util/IOUtil.java
+++ b/scm-core/src/main/java/sonia/scm/util/IOUtil.java
@@ -37,14 +37,11 @@ package sonia.scm.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-
import sonia.scm.io.Command;
import sonia.scm.io.CommandResult;
import sonia.scm.io.SimpleCommand;
import sonia.scm.io.ZipUnArchiver;
-//~--- JDK imports ------------------------------------------------------------
-
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
@@ -55,12 +52,13 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
+//~--- JDK imports ------------------------------------------------------------
+
/**
*
* @author Sebastian Sdorra
diff --git a/scm-core/src/test/java/sonia/scm/plugin/SmpArchiveTest.java b/scm-core/src/test/java/sonia/scm/plugin/SmpArchiveTest.java
index 95addf388f..d7f4ecf515 100644
--- a/scm-core/src/test/java/sonia/scm/plugin/SmpArchiveTest.java
+++ b/scm-core/src/test/java/sonia/scm/plugin/SmpArchiveTest.java
@@ -85,7 +85,7 @@ public class SmpArchiveTest
public void testExtract()
throws IOException, ParserConfigurationException, SAXException
{
- File archive = createArchive("sonia.sample", "sample", "1.0");
+ File archive = createArchive("sonia.sample", "1.0");
File target = tempFolder.newFolder();
IOUtil.mkdirs(target);
@@ -112,7 +112,7 @@ public class SmpArchiveTest
@Test
public void testGetPlugin() throws IOException
{
- File archive = createArchive("sonia.sample", "sample", "1.0");
+ File archive = createArchive("sonia.sample", "1.0");
Plugin plugin = SmpArchive.create(archive).getPlugin();
assertNotNull(plugin);
@@ -121,8 +121,7 @@ public class SmpArchiveTest
assertNotNull(info);
- assertEquals("sonia.sample", info.getGroupId());
- assertEquals("sample", info.getArtifactId());
+ assertEquals("sonia.sample", info.getName());
assertEquals("1.0", info.getVersion());
}
@@ -132,22 +131,9 @@ public class SmpArchiveTest
* @throws IOException
*/
@Test(expected = PluginException.class)
- public void testWithMissingArtifactId() throws IOException
+ public void testWithMissingName() throws IOException
{
- File archive = createArchive("sonia.sample", null, "1.0");
-
- SmpArchive.create(archive).getPlugin();
- }
-
- /**
- * Method description
- *
- * @throws IOException
- */
- @Test(expected = PluginException.class)
- public void testWithMissingGroupId() throws IOException
- {
- File archive = createArchive(null, "sample", "1.0");
+ File archive = createArchive( null, "1.0");
SmpArchive.create(archive).getPlugin();
}
@@ -160,7 +146,7 @@ public class SmpArchiveTest
@Test(expected = PluginException.class)
public void testWithMissingVersion() throws IOException
{
- File archive = createArchive("sonia.sample", "sample", null);
+ File archive = createArchive("sonia.sample", null);
SmpArchive.create(archive).getPlugin();
}
@@ -169,13 +155,12 @@ public class SmpArchiveTest
* Method description
*
*
- * @param groupId
- * @param artifactId
+ * @param name
* @param version
*
* @return
*/
- private File createArchive(String groupId, String artifactId, String version)
+ private File createArchive(String name, String version)
{
File archiveFile;
@@ -183,7 +168,7 @@ public class SmpArchiveTest
{
File descriptor = tempFolder.newFile();
- writeDescriptor(descriptor, groupId, artifactId, version);
+ writeDescriptor(descriptor, name, version);
archiveFile = tempFolder.newFile();
try (ZipOutputStream zos =
@@ -229,14 +214,13 @@ public class SmpArchiveTest
*
*
* @param descriptor
- * @param groupId
- * @param artifactId
+ * @param name
* @param version
*
* @throws IOException
*/
- private void writeDescriptor(File descriptor, String groupId,
- String artifactId, String version)
+ private void writeDescriptor(File descriptor, String name,
+ String version)
throws IOException
{
try
@@ -252,8 +236,7 @@ public class SmpArchiveTest
writer.writeStartDocument();
writer.writeStartElement("plugin");
writer.writeStartElement("information");
- writeElement(writer, "groupId", groupId);
- writeElement(writer, "artifactId", artifactId);
+ writeElement(writer, "name", name);
writeElement(writer, "version", version);
writer.writeEndElement();
diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml
index a838e2f146..ac1f007394 100644
--- a/scm-plugins/scm-git-plugin/pom.xml
+++ b/scm-plugins/scm-git-plugin/pom.xml
@@ -10,7 +10,6 @@
scm-git-plugin
- scm-git-plugin
smp
https://bitbucket.org/sdorra/scm-manager
Plugin for the version control system Git
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java
index a3f63b8f5a..e7f26d6885 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java
@@ -1,6 +1,5 @@
package sonia.scm.repository.spi;
-import com.google.common.base.Throwables;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import sonia.scm.repository.GitUtil;
diff --git a/scm-plugins/scm-git-plugin/src/main/resources/META-INF/scm/plugin.xml b/scm-plugins/scm-git-plugin/src/main/resources/META-INF/scm/plugin.xml
index ff699441a8..ba1d625fb4 100644
--- a/scm-plugins/scm-git-plugin/src/main/resources/META-INF/scm/plugin.xml
+++ b/scm-plugins/scm-git-plugin/src/main/resources/META-INF/scm/plugin.xml
@@ -46,14 +46,10 @@
2
- Sebastian Sdorra
- Git
-
- git
- scm
- vcs
- dvcs
-
+ Git
+ Cloudogu GmbH
+ Source Code Management
+ /images/git-logo.png
diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml
index 025f79add3..e5decb0567 100644
--- a/scm-plugins/scm-hg-plugin/pom.xml
+++ b/scm-plugins/scm-hg-plugin/pom.xml
@@ -10,7 +10,6 @@
scm-hg-plugin
- scm-hg-plugin
smp
https://bitbucket.org/sdorra/scm-manager
Plugin for the version control system Mercurial
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java
index b8883a0d92..1d227fb54e 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgEnvironment.java
@@ -40,11 +40,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.web.HgUtil;
-//~--- JDK imports ------------------------------------------------------------
-
+import javax.servlet.http.HttpServletRequest;
import java.util.Map;
-import javax.servlet.http.HttpServletRequest;
+//~--- JDK imports ------------------------------------------------------------
/**
*
diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/META-INF/scm/plugin.xml b/scm-plugins/scm-hg-plugin/src/main/resources/META-INF/scm/plugin.xml
index 1d0b05c4a8..352192121f 100644
--- a/scm-plugins/scm-hg-plugin/src/main/resources/META-INF/scm/plugin.xml
+++ b/scm-plugins/scm-hg-plugin/src/main/resources/META-INF/scm/plugin.xml
@@ -46,15 +46,10 @@ jo
2
- Sebastian Sdorra
- Mercurial
-
- mercurial
- hg
- scm
- vcs
- dvcs
-
+ Mercurial
+ Cloudogu GmbH
+ Source Code Management
+ /images/hg-logo.png
diff --git a/scm-plugins/scm-legacy-plugin/pom.xml b/scm-plugins/scm-legacy-plugin/pom.xml
index 6cfa74ea61..1a12234014 100644
--- a/scm-plugins/scm-legacy-plugin/pom.xml
+++ b/scm-plugins/scm-legacy-plugin/pom.xml
@@ -6,8 +6,9 @@
scm-plugins
2.0.0-SNAPSHOT
- sonia.scm.plugins
+
scm-legacy-plugin
+ Support migrated repository urls and v1 passwords
2.0.0-SNAPSHOT
smp
@@ -21,6 +22,7 @@
${servlet.version}
provided
+
javax.ws.rs
jsr311-api
diff --git a/scm-plugins/scm-legacy-plugin/src/main/js/DummyComponent.js b/scm-plugins/scm-legacy-plugin/src/main/js/DummyComponent.js
index 396558f852..6728a93c78 100644
--- a/scm-plugins/scm-legacy-plugin/src/main/js/DummyComponent.js
+++ b/scm-plugins/scm-legacy-plugin/src/main/js/DummyComponent.js
@@ -1,6 +1,6 @@
//@flow
import React from "react";
-import { withRouter } from "react-router-dom";
+import {withRouter} from "react-router-dom";
class DummyComponent extends React.Component {
render() {
diff --git a/scm-plugins/scm-legacy-plugin/src/main/js/index.js b/scm-plugins/scm-legacy-plugin/src/main/js/index.js
index 97c3eb7e32..7be0386359 100644
--- a/scm-plugins/scm-legacy-plugin/src/main/js/index.js
+++ b/scm-plugins/scm-legacy-plugin/src/main/js/index.js
@@ -1,14 +1,9 @@
// @flow
import React from "react";
-import { withRouter } from "react-router-dom";
-import { binder } from "@scm-manager/ui-extensions";
-import {
- ProtectedRoute,
- apiClient,
- ErrorNotification,
- ErrorBoundary
-} from "@scm-manager/ui-components";
+import {withRouter} from "react-router-dom";
+import {binder} from "@scm-manager/ui-extensions";
+import {apiClient, ErrorBoundary, ErrorNotification, ProtectedRoute} from "@scm-manager/ui-components";
import DummyComponent from "./DummyComponent";
import type {Links} from "@scm-manager/ui-types";
diff --git a/scm-plugins/scm-legacy-plugin/src/main/resources/META-INF/scm/plugin.xml b/scm-plugins/scm-legacy-plugin/src/main/resources/META-INF/scm/plugin.xml
index f8a3c8c7b4..2a6b553cdf 100644
--- a/scm-plugins/scm-legacy-plugin/src/main/resources/META-INF/scm/plugin.xml
+++ b/scm-plugins/scm-legacy-plugin/src/main/resources/META-INF/scm/plugin.xml
@@ -46,7 +46,9 @@
2
- Sebastian Sdorra
+ Legacy
+ Cloudogu GmbH
+ Legacy Support
diff --git a/scm-plugins/scm-legacy-plugin/src/test/java/sonia/scm/legacy/LegacyRepositoryServiceTest.java b/scm-plugins/scm-legacy-plugin/src/test/java/sonia/scm/legacy/LegacyRepositoryServiceTest.java
index 169c80eae2..a28f87dbf8 100644
--- a/scm-plugins/scm-legacy-plugin/src/test/java/sonia/scm/legacy/LegacyRepositoryServiceTest.java
+++ b/scm-plugins/scm-legacy-plugin/src/test/java/sonia/scm/legacy/LegacyRepositoryServiceTest.java
@@ -10,7 +10,7 @@ import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
diff --git a/scm-plugins/scm-svn-plugin/pom.xml b/scm-plugins/scm-svn-plugin/pom.xml
index 4386efde5b..83da627eb9 100644
--- a/scm-plugins/scm-svn-plugin/pom.xml
+++ b/scm-plugins/scm-svn-plugin/pom.xml
@@ -10,7 +10,6 @@
scm-svn-plugin
- scm-svn-plugin
smp
https://bitbucket.org/sdorra/scm-manager
Plugin for the version control system Subversion
diff --git a/scm-plugins/scm-svn-plugin/src/main/resources/META-INF/scm/plugin.xml b/scm-plugins/scm-svn-plugin/src/main/resources/META-INF/scm/plugin.xml
index 302abd2b10..5e941e98e1 100644
--- a/scm-plugins/scm-svn-plugin/src/main/resources/META-INF/scm/plugin.xml
+++ b/scm-plugins/scm-svn-plugin/src/main/resources/META-INF/scm/plugin.xml
@@ -46,14 +46,10 @@
2
- Sebastian Sdorra
- Subversion
-
- subversion
- scm
- vcs
- svn
-
+ Subversion
+ Cloudogu GmbH
+ Source Code Management
+ /images/svn-logo.gif
diff --git a/scm-ui-components/packages/ui-components/src/Autocomplete.js b/scm-ui-components/packages/ui-components/src/Autocomplete.js
index 22107e75b6..116df6562a 100644
--- a/scm-ui-components/packages/ui-components/src/Autocomplete.js
+++ b/scm-ui-components/packages/ui-components/src/Autocomplete.js
@@ -1,7 +1,7 @@
// @flow
import React from "react";
-import { AsyncCreatable, Async } from "react-select";
-import type { AutocompleteObject, SelectValue } from "@scm-manager/ui-types";
+import {Async, AsyncCreatable} from "react-select";
+import type {AutocompleteObject, SelectValue} from "@scm-manager/ui-types";
import LabelWithHelpIcon from "./forms/LabelWithHelpIcon";
type Props = {
diff --git a/scm-ui-components/packages/ui-components/src/CardColumn.js b/scm-ui-components/packages/ui-components/src/CardColumn.js
index e1eb65255a..65710c7be2 100644
--- a/scm-ui-components/packages/ui-components/src/CardColumn.js
+++ b/scm-ui-components/packages/ui-components/src/CardColumn.js
@@ -25,12 +25,17 @@ const styles = {
},
content: {
display: "flex",
- flexGrow: 1
+ flexGrow: 1,
+ alignItems: "center",
+ justifyContent: "space-between"
},
footer: {
display: "flex",
marginTop: "auto",
paddingBottom: "1.5rem"
+ },
+ noBottomMargin: {
+ marginBottom: "0 !important"
}
};
@@ -38,9 +43,11 @@ type Props = {
title: string,
description: string,
avatar: React.Node,
+ contentRight?: React.Node,
footerLeft: React.Node,
footerRight: React.Node,
link: string,
+
// context props
classes: any
};
@@ -55,7 +62,15 @@ class CardColumn extends React.Component {
};
render() {
- const { avatar, title, description, footerLeft, footerRight, classes } = this.props;
+ const {
+ avatar,
+ title,
+ description,
+ contentRight,
+ footerLeft,
+ footerRight,
+ classes
+ } = this.props;
const link = this.createLink();
return (
<>
@@ -64,16 +79,29 @@ class CardColumn extends React.Component {
{avatar}
-
+
-
+
+ {contentRight && contentRight}
-
+
{footerLeft}
{footerRight}
diff --git a/scm-ui-components/packages/ui-components/src/ErrorNotification.js b/scm-ui-components/packages/ui-components/src/ErrorNotification.js
index 72173679dd..0c6367a474 100644
--- a/scm-ui-components/packages/ui-components/src/ErrorNotification.js
+++ b/scm-ui-components/packages/ui-components/src/ErrorNotification.js
@@ -1,7 +1,7 @@
//@flow
import React from "react";
-import { translate } from "react-i18next";
-import { BackendError, ForbiddenError, UnauthorizedError } from "./errors";
+import {translate} from "react-i18next";
+import {BackendError, ForbiddenError, UnauthorizedError} from "./errors";
import Notification from "./Notification";
import BackendErrorNotification from "./BackendErrorNotification";
diff --git a/scm-ui-components/packages/ui-components/src/GroupAutocomplete.js b/scm-ui-components/packages/ui-components/src/GroupAutocomplete.js
index e39130f1d7..8aeeedc3e4 100644
--- a/scm-ui-components/packages/ui-components/src/GroupAutocomplete.js
+++ b/scm-ui-components/packages/ui-components/src/GroupAutocomplete.js
@@ -1,6 +1,6 @@
// @flow
import React from "react";
-import { translate } from "react-i18next";
+import {translate} from "react-i18next";
import type AutocompleteProps from "./UserGroupAutocomplete";
import UserGroupAutocomplete from "./UserGroupAutocomplete";
diff --git a/scm-ui-components/packages/ui-components/src/UserAutocomplete.js b/scm-ui-components/packages/ui-components/src/UserAutocomplete.js
index 9ef7aaa7a7..308835d8db 100644
--- a/scm-ui-components/packages/ui-components/src/UserAutocomplete.js
+++ b/scm-ui-components/packages/ui-components/src/UserAutocomplete.js
@@ -1,6 +1,6 @@
// @flow
import React from "react";
-import { translate } from "react-i18next";
+import {translate} from "react-i18next";
import type AutocompleteProps from "./UserGroupAutocomplete";
import UserGroupAutocomplete from "./UserGroupAutocomplete";
diff --git a/scm-ui-components/packages/ui-components/src/UserGroupAutocomplete.js b/scm-ui-components/packages/ui-components/src/UserGroupAutocomplete.js
index 0d6e3ec46e..d038e21221 100644
--- a/scm-ui-components/packages/ui-components/src/UserGroupAutocomplete.js
+++ b/scm-ui-components/packages/ui-components/src/UserGroupAutocomplete.js
@@ -1,6 +1,6 @@
// @flow
import React from "react";
-import type { SelectValue } from "@scm-manager/ui-types";
+import type {SelectValue} from "@scm-manager/ui-types";
import Autocomplete from "./Autocomplete";
export type AutocompleteProps = {
diff --git a/scm-ui-components/packages/ui-components/src/repos/Diff.js b/scm-ui-components/packages/ui-components/src/repos/Diff.js
index 97692210c2..7a5bcbf4a2 100644
--- a/scm-ui-components/packages/ui-components/src/repos/Diff.js
+++ b/scm-ui-components/packages/ui-components/src/repos/Diff.js
@@ -1,7 +1,7 @@
//@flow
import React from "react";
import DiffFile from "./DiffFile";
-import type { DiffObjectProps, File } from "./DiffTypes";
+import type {DiffObjectProps, File} from "./DiffTypes";
type Props = DiffObjectProps & {
diff: File[]
diff --git a/scm-ui-components/packages/ui-components/src/repos/DiffFile.js b/scm-ui-components/packages/ui-components/src/repos/DiffFile.js
index 3f849dff6b..ab020f723d 100644
--- a/scm-ui-components/packages/ui-components/src/repos/DiffFile.js
+++ b/scm-ui-components/packages/ui-components/src/repos/DiffFile.js
@@ -1,17 +1,10 @@
//@flow
import React from "react";
-import {
- Hunk,
- Diff as DiffComponent,
- getChangeKey,
- Change,
- DiffObjectProps,
- File
-} from "react-diff-view";
+import {Change, Diff as DiffComponent, DiffObjectProps, File, getChangeKey, Hunk} from "react-diff-view";
import injectSheets from "react-jss";
import classNames from "classnames";
-import { translate } from "react-i18next";
-import { ButtonGroup, Button } from "../buttons";
+import {translate} from "react-i18next";
+import {Button, ButtonGroup} from "../buttons";
const styles = {
panel: {
diff --git a/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js b/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js
index c8a5250756..876b757f44 100644
--- a/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js
+++ b/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js
@@ -1,6 +1,6 @@
//@flow
import React from "react";
-import { apiClient } from "../apiclient";
+import {apiClient} from "../apiclient";
import ErrorNotification from "../ErrorNotification";
import parser from "gitdiff-parser";
diff --git a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetButtonGroup.js b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetButtonGroup.js
index 8d46a9f0b4..74a4eb0050 100644
--- a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetButtonGroup.js
+++ b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetButtonGroup.js
@@ -1,9 +1,9 @@
//@flow
import React from "react";
-import type { Changeset, Repository } from "@scm-manager/ui-types";
-import { ButtonAddons, Button } from "../../buttons";
-import { createChangesetLink, createSourcesLink } from "./changesets";
-import { translate } from "react-i18next";
+import type {Changeset, Repository} from "@scm-manager/ui-types";
+import {Button, ButtonAddons} from "../../buttons";
+import {createChangesetLink, createSourcesLink} from "./changesets";
+import {translate} from "react-i18next";
type Props = {
repository: Repository,
diff --git a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetRow.js b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetRow.js
index 0150fcb9c3..6de47f0c87 100644
--- a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetRow.js
+++ b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetRow.js
@@ -1,16 +1,16 @@
//@flow
import React from "react";
-import type { Changeset, Repository, Tag } from "@scm-manager/ui-types";
+import type {Changeset, Repository} from "@scm-manager/ui-types";
import classNames from "classnames";
-import { Interpolate, translate } from "react-i18next";
+import {Interpolate, translate} from "react-i18next";
import ChangesetId from "./ChangesetId";
import injectSheet from "react-jss";
-import { DateFromNow } from "../..";
+import {DateFromNow} from "../..";
import ChangesetAuthor from "./ChangesetAuthor";
-import { parseDescription } from "./changesets";
-import { AvatarWrapper, AvatarImage } from "../../avatar";
-import { ExtensionPoint} from "@scm-manager/ui-extensions";
+import {parseDescription} from "./changesets";
+import {AvatarImage, AvatarWrapper} from "../../avatar";
+import {ExtensionPoint} from "@scm-manager/ui-extensions";
import ChangesetTags from "./ChangesetTags";
import ChangesetButtonGroup from "./ChangesetButtonGroup";
diff --git a/scm-ui-components/packages/ui-components/src/repos/index.js b/scm-ui-components/packages/ui-components/src/repos/index.js
index fd1448fbdd..19f9a160b3 100644
--- a/scm-ui-components/packages/ui-components/src/repos/index.js
+++ b/scm-ui-components/packages/ui-components/src/repos/index.js
@@ -1,5 +1,6 @@
// @flow
import * as diffs from "./diffs";
+
export { diffs };
export * from "./changesets";
diff --git a/scm-ui-components/packages/ui-types/src/Plugin.js b/scm-ui-components/packages/ui-types/src/Plugin.js
index bb9c5e7d88..3f4f9858c1 100644
--- a/scm-ui-components/packages/ui-types/src/Plugin.js
+++ b/scm-ui-components/packages/ui-types/src/Plugin.js
@@ -1,12 +1,15 @@
//@flow
-import type { Collection, Links } from "./hal";
+import type {Collection, Links} from "./hal";
+
export type Plugin = {
name: string,
- type: string,
version: string,
- author: string,
+ displayName: string,
description?: string,
+ author: string,
+ category: string,
+ avatarUrl: string,
_links: Links
};
diff --git a/scm-ui/public/locales/de/config.json b/scm-ui/public/locales/de/config.json
index a94965fa68..0bc220515a 100644
--- a/scm-ui/public/locales/de/config.json
+++ b/scm-ui/public/locales/de/config.json
@@ -41,7 +41,7 @@
"date-format": "Datumsformat",
"anonymous-access-enabled": "Anonyme Zugriffe erlauben",
"skip-failed-authenticators": "Fehlgeschlagene Authentifizierer überspringen",
- "plugin-url": "Plugin URL",
+ "plugin-url": "Plugin Center URL",
"enabled-xsrf-protection": "XSRF Protection aktivieren",
"namespace-strategy": "Namespace Strategie",
"login-info-url": "Login Info URL"
@@ -55,7 +55,7 @@
"help": {
"realmDescriptionHelpText": "Beschreibung des Authentication Realm.",
"dateFormatHelpText": "Moments Datumsformat. Zulässige Formate sind in der MomentJS Dokumentation beschrieben.",
- "pluginRepositoryHelpText": "Die URL des Plugin Repositories. Beschreibung der Platzhalter: version = SCM-Manager Version; os = Betriebssystem; arch = Architektur",
+ "pluginUrlHelpText": "Die URL der Plugin Center API. Beschreibung der Platzhalter: version = SCM-Manager Version; os = Betriebssystem; arch = Architektur",
"enableForwardingHelpText": "mod_proxy Port Weiterleitung aktivieren.",
"enableRepositoryArchiveHelpText": "Repository Archive aktivieren. Nach einer Änderung an dieser Einstellung muss die Seite komplett neu geladen werden.",
"disableGroupingGridHelpText": "Repository Gruppen deaktivieren. Nach einer Änderung an dieser Einstellung muss die Seite komplett neu geladen werden.",
diff --git a/scm-ui/public/locales/en/config.json b/scm-ui/public/locales/en/config.json
index 894d0563ba..ce0f7252df 100644
--- a/scm-ui/public/locales/en/config.json
+++ b/scm-ui/public/locales/en/config.json
@@ -41,7 +41,7 @@
"date-format": "Date Format",
"anonymous-access-enabled": "Anonymous Access Enabled",
"skip-failed-authenticators": "Skip Failed Authenticators",
- "plugin-url": "Plugin URL",
+ "plugin-url": "Plugin Center URL",
"enabled-xsrf-protection": "Enabled XSRF Protection",
"namespace-strategy": "Namespace Strategy",
"login-info-url": "Login Info URL"
@@ -55,7 +55,7 @@
"help": {
"realmDescriptionHelpText": "Enter authentication realm description.",
"dateFormatHelpText": "Moments date format. Please have a look at the MomentJS documentation.",
- "pluginRepositoryHelpText": "The url of the plugin repository. Explanation of the placeholders: version = SCM-Manager Version; os = Operation System; arch = Architecture",
+ "pluginUrlHelpText": "The url of the Plugin Center API. Explanation of the placeholders: version = SCM-Manager Version; os = Operation System; arch = Architecture",
"enableForwardingHelpText": "Enable mod_proxy port forwarding.",
"enableRepositoryArchiveHelpText": "Enable repository archives. A complete page reload is required after a change of this value.",
"disableGroupingGridHelpText": "Disable repository Groups. A complete page reload is required after a change of this value.",
diff --git a/scm-ui/src/admin/components/form/GeneralSettings.js b/scm-ui/src/admin/components/form/GeneralSettings.js
index a2fb1127af..842d0ed7f4 100644
--- a/scm-ui/src/admin/components/form/GeneralSettings.js
+++ b/scm-ui/src/admin/components/form/GeneralSettings.js
@@ -29,6 +29,7 @@ class GeneralSettings extends React.Component
{
t,
realmDescription,
loginInfoUrl,
+ pluginUrl,
enabledXsrfProtection,
namespaceStrategy,
hasUpdatePermission,
@@ -78,6 +79,17 @@ class GeneralSettings extends React.Component {
/>
+
);
}
@@ -85,7 +97,6 @@ class GeneralSettings extends React.Component
{
handleLoginInfoUrlChange = (value: string) => {
this.props.onChange(true, value, "loginInfoUrl");
};
-
handleRealmDescriptionChange = (value: string) => {
this.props.onChange(true, value, "realmDescription");
};
@@ -95,6 +106,9 @@ class GeneralSettings extends React.Component {
handleNamespaceStrategyChange = (value: string) => {
this.props.onChange(true, value, "namespaceStrategy");
};
+ handlePluginCenterUrlChange = (value: string) => {
+ this.props.onChange(true, value, "pluginUrl");
+ };
}
export default translate("config")(GeneralSettings);
diff --git a/scm-ui/src/admin/containers/Admin.js b/scm-ui/src/admin/containers/Admin.js
index 58e37a0a7c..22c6184548 100644
--- a/scm-ui/src/admin/containers/Admin.js
+++ b/scm-ui/src/admin/containers/Admin.js
@@ -1,14 +1,24 @@
// @flow
import React from "react";
import { translate } from "react-i18next";
-import {Redirect, Route, Switch} from "react-router-dom";
+import { Redirect, Route, Switch } from "react-router-dom";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import type { History } from "history";
import { connect } from "react-redux";
import { compose } from "redux";
import type { Links } from "@scm-manager/ui-types";
-import { Page, Navigation, NavLink, Section, SubNavigation } from "@scm-manager/ui-components";
-import { getLinks } from "../../modules/indexResource";
+import {
+ Page,
+ Navigation,
+ NavLink,
+ Section,
+ SubNavigation
+} from "@scm-manager/ui-components";
+import {
+ getLinks,
+ getAvailablePluginsLink,
+ getInstalledPluginsLink
+} from "../../modules/indexResource";
import AdminDetails from "./AdminDetails";
import PluginsOverview from "../plugins/containers/PluginsOverview";
import GlobalConfig from "./GlobalConfig";
@@ -18,6 +28,8 @@ import CreateRepositoryRole from "../roles/containers/CreateRepositoryRole";
type Props = {
links: Links,
+ availablePluginsLink: string,
+ installedPluginsLink: string,
// context objects
t: string => string,
@@ -28,7 +40,7 @@ type Props = {
class Admin extends React.Component {
stripEndingSlash = (url: string) => {
if (url.endsWith("/")) {
- if(url.includes("role")) {
+ if (url.includes("role")) {
return url.substring(0, url.length - 2);
}
return url.substring(0, url.length - 1);
@@ -47,7 +59,7 @@ class Admin extends React.Component {
};
render() {
- const { links, t } = this.props;
+ const { links, availablePluginsLink, installedPluginsLink, t } = this.props;
const url = this.matchedUrl();
const extensionProps = {
@@ -62,34 +74,54 @@ class Admin extends React.Component {
-
-
+
+
(
-
+
)}
/>
(
-
+
)}
/>
(
-
+
)}
/>
(
-
+
)}
/>
{
(
-
+
)}
/>
(
-
- )}
+ render={() => }
/>
{
icon="fas fa-info-circle"
label={t("admin.menu.informationNavLink")}
/>
- {
- links.plugins &&
-
-
- {/* Activate this again after available plugins page is created */}
- {/* */}
-
- }
+ {(availablePluginsLink || installedPluginsLink) && (
+
+ {installedPluginsLink && (
+
+ )}
+ {availablePluginsLink && (
+
+ )}
+
+ )}
{
const mapStateToProps = (state: any) => {
const links = getLinks(state);
+ const availablePluginsLink = getAvailablePluginsLink(state);
+ const installedPluginsLink = getInstalledPluginsLink(state);
return {
- links
+ links,
+ availablePluginsLink,
+ installedPluginsLink
};
};
diff --git a/scm-ui/src/admin/plugins/components/PluginAvatar.js b/scm-ui/src/admin/plugins/components/PluginAvatar.js
index 10408f14bd..42a1fd732b 100644
--- a/scm-ui/src/admin/plugins/components/PluginAvatar.js
+++ b/scm-ui/src/admin/plugins/components/PluginAvatar.js
@@ -1,8 +1,8 @@
//@flow
import React from "react";
-import { ExtensionPoint } from "@scm-manager/ui-extensions";
-import type { Plugin } from "@scm-manager/ui-types";
-import { Image } from "@scm-manager/ui-components";
+import {ExtensionPoint} from "@scm-manager/ui-extensions";
+import type {Plugin} from "@scm-manager/ui-types";
+import {Image} from "@scm-manager/ui-components";
type Props = {
plugin: Plugin
@@ -14,7 +14,7 @@ export default class PluginAvatar extends React.Component {
return (
-
+
);
diff --git a/scm-ui/src/admin/plugins/components/PluginEntry.js b/scm-ui/src/admin/plugins/components/PluginEntry.js
index a8cdaad915..7aaeb3f67f 100644
--- a/scm-ui/src/admin/plugins/components/PluginEntry.js
+++ b/scm-ui/src/admin/plugins/components/PluginEntry.js
@@ -1,11 +1,21 @@
//@flow
import React from "react";
-import type { Plugin } from "@scm-manager/ui-types";
-import { CardColumn } from "@scm-manager/ui-components";
+import injectSheet from "react-jss";
+import type {Plugin} from "@scm-manager/ui-types";
+import {CardColumn} from "@scm-manager/ui-components";
import PluginAvatar from "./PluginAvatar";
type Props = {
- plugin: Plugin
+ plugin: Plugin,
+
+ // context props
+ classes: any
+};
+
+const styles = {
+ link: {
+ pointerEvents: "cursor"
+ }
};
class PluginEntry extends React.Component {
@@ -13,6 +23,17 @@ class PluginEntry extends React.Component {
return ;
};
+ createContentRight = (plugin: Plugin) => {
+ const { classes } = this.props;
+ if (plugin._links && plugin._links.install && plugin._links.install.href) {
+ return (
+ console.log(plugin._links.install.href) /*TODO trigger plugin installation*/}>
+
+
+ );
+ }
+ };
+
createFooterLeft = (plugin: Plugin) => {
return {plugin.author} ;
};
@@ -24,6 +45,7 @@ class PluginEntry extends React.Component {
render() {
const { plugin } = this.props;
const avatar = this.createAvatar(plugin);
+ const contentRight = this.createContentRight(plugin);
const footerLeft = this.createFooterLeft(plugin);
const footerRight = this.createFooterRight(plugin);
@@ -32,8 +54,9 @@ class PluginEntry extends React.Component {
@@ -41,4 +64,4 @@ class PluginEntry extends React.Component {
}
}
-export default PluginEntry;
+export default injectSheet(styles)(PluginEntry);
diff --git a/scm-ui/src/admin/plugins/components/groupByCategory.js b/scm-ui/src/admin/plugins/components/groupByCategory.js
index 1c542d45e3..49b6590d9a 100644
--- a/scm-ui/src/admin/plugins/components/groupByCategory.js
+++ b/scm-ui/src/admin/plugins/components/groupByCategory.js
@@ -6,7 +6,7 @@ export default function groupByCategory(
): PluginGroup[] {
let groups = {};
for (let plugin of plugins) {
- const groupName = plugin.type;
+ const groupName = plugin.category;
let group = groups[groupName];
if (!group) {
diff --git a/scm-ui/src/admin/plugins/containers/PluginsOverview.js b/scm-ui/src/admin/plugins/containers/PluginsOverview.js
index 7a3fc7ec4a..5a8a46f884 100644
--- a/scm-ui/src/admin/plugins/containers/PluginsOverview.js
+++ b/scm-ui/src/admin/plugins/containers/PluginsOverview.js
@@ -18,7 +18,10 @@ import {
isFetchPluginsPending
} from "../modules/plugins";
import PluginsList from "../components/PluginsList";
-import { getPluginsLink } from "../../../modules/indexResource";
+import {
+ getAvailablePluginsLink,
+ getInstalledPluginsLink
+} from "../../../modules/indexResource";
type Props = {
loading: boolean,
@@ -26,7 +29,8 @@ type Props = {
collection: PluginCollection,
baseUrl: string,
installed: boolean,
- pluginsLink: string,
+ availablePluginsLink: string,
+ installedPluginsLink: string,
// context objects
t: string => string,
@@ -37,8 +41,27 @@ type Props = {
class PluginsOverview extends React.Component {
componentDidMount() {
- const { fetchPluginsByLink, pluginsLink } = this.props;
- fetchPluginsByLink(pluginsLink);
+ const {
+ installed,
+ fetchPluginsByLink,
+ availablePluginsLink,
+ installedPluginsLink
+ } = this.props;
+ fetchPluginsByLink(installed ? installedPluginsLink : availablePluginsLink);
+ }
+
+ componentDidUpdate(prevProps) {
+ const {
+ installed,
+ fetchPluginsByLink,
+ availablePluginsLink,
+ installedPluginsLink
+ } = this.props;
+ if (prevProps.installed !== installed) {
+ fetchPluginsByLink(
+ installed ? installedPluginsLink : availablePluginsLink
+ );
+ }
}
render() {
@@ -81,13 +104,15 @@ const mapStateToProps = state => {
const collection = getPluginCollection(state);
const loading = isFetchPluginsPending(state);
const error = getFetchPluginsFailure(state);
- const pluginsLink = getPluginsLink(state);
+ const availablePluginsLink = getAvailablePluginsLink(state);
+ const installedPluginsLink = getInstalledPluginsLink(state);
return {
collection,
loading,
error,
- pluginsLink
+ availablePluginsLink,
+ installedPluginsLink
};
};
diff --git a/scm-ui/src/containers/Main.js b/scm-ui/src/containers/Main.js
index 1d358ecc87..8c05578b6b 100644
--- a/scm-ui/src/containers/Main.js
+++ b/scm-ui/src/containers/Main.js
@@ -1,16 +1,16 @@
//@flow
import React from "react";
-import { Redirect, Route, Switch, withRouter } from "react-router-dom";
-import type { Links } from "@scm-manager/ui-types";
+import {Redirect, Route, Switch, withRouter} from "react-router-dom";
+import type {Links} from "@scm-manager/ui-types";
import Overview from "../repos/containers/Overview";
import Users from "../users/containers/Users";
import Login from "../containers/Login";
import Logout from "../containers/Logout";
-import { ProtectedRoute } from "@scm-manager/ui-components";
-import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
+import {ProtectedRoute} from "@scm-manager/ui-components";
+import {binder, ExtensionPoint} from "@scm-manager/ui-extensions";
import CreateUser from "../users/containers/CreateUser";
import SingleUser from "../users/containers/SingleUser";
diff --git a/scm-ui/src/modules/indexResource.js b/scm-ui/src/modules/indexResource.js
index 9676faffba..d62e6b8b5d 100644
--- a/scm-ui/src/modules/indexResource.js
+++ b/scm-ui/src/modules/indexResource.js
@@ -116,8 +116,12 @@ export function getUiPluginsLink(state: Object) {
return getLink(state, "uiPlugins");
}
-export function getPluginsLink(state: Object) {
- return getLink(state, "plugins");
+export function getAvailablePluginsLink(state: Object) {
+ return getLink(state, "availablePlugins");
+}
+
+export function getInstalledPluginsLink(state: Object) {
+ return getLink(state, "installedPlugins");
}
export function getMeLink(state: Object) {
diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js
index 5cb2078e5a..164ddcbc64 100644
--- a/scm-ui/src/repos/containers/RepositoryRoot.js
+++ b/scm-ui/src/repos/containers/RepositoryRoot.js
@@ -1,33 +1,20 @@
//@flow
import React from "react";
-import {
- fetchRepoByName,
- getFetchRepoFailure,
- getRepository,
- isFetchRepoPending
-} from "../modules/repos";
+import {fetchRepoByName, getFetchRepoFailure, getRepository, isFetchRepoPending} from "../modules/repos";
-import { connect } from "react-redux";
-import { Redirect, Route, Switch } from "react-router-dom";
-import type { Repository } from "@scm-manager/ui-types";
+import {connect} from "react-redux";
+import {Redirect, Route, Switch} from "react-router-dom";
+import type {Repository} from "@scm-manager/ui-types";
-import {
- Loading,
- Navigation,
- SubNavigation,
- NavLink,
- Page,
- Section,
- ErrorPage
-} from "@scm-manager/ui-components";
-import { translate } from "react-i18next";
+import {ErrorPage, Loading, Navigation, NavLink, Page, Section, SubNavigation} from "@scm-manager/ui-components";
+import {translate} from "react-i18next";
import RepositoryDetails from "../components/RepositoryDetails";
import EditRepo from "./EditRepo";
import BranchesOverview from "../branches/containers/BranchesOverview";
import CreateBranch from "../branches/containers/CreateBranch";
import Permissions from "../permissions/containers/Permissions";
-import type { History } from "history";
+import type {History} from "history";
import EditRepoNavLink from "../components/EditRepoNavLink";
import BranchRoot from "../branches/containers/BranchRoot";
import ChangesetsRoot from "./ChangesetsRoot";
@@ -35,8 +22,8 @@ import ChangesetView from "./ChangesetView";
import PermissionsNavLink from "../components/PermissionsNavLink";
import Sources from "../sources/containers/Sources";
import RepositoryNavLink from "../components/RepositoryNavLink";
-import { getLinks, getRepositoriesLink } from "../../modules/indexResource";
-import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
+import {getLinks, getRepositoriesLink} from "../../modules/indexResource";
+import {binder, ExtensionPoint} from "@scm-manager/ui-extensions";
type Props = {
namespace: string,
diff --git a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js
index efd92914d3..33211c76f4 100644
--- a/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js
+++ b/scm-ui/src/repos/permissions/containers/CreatePermissionForm.js
@@ -1,25 +1,20 @@
// @flow
import React from "react";
-import { translate } from "react-i18next";
-import type {
- RepositoryRole,
- PermissionCollection,
- PermissionCreateEntry,
- SelectValue
-} from "@scm-manager/ui-types";
+import {translate} from "react-i18next";
+import type {PermissionCollection, PermissionCreateEntry, RepositoryRole, SelectValue} from "@scm-manager/ui-types";
import {
- Subtitle,
- SubmitButton,
Button,
+ GroupAutocomplete,
LabelWithHelpIcon,
Radio,
- GroupAutocomplete,
+ SubmitButton,
+ Subtitle,
UserAutocomplete
} from "@scm-manager/ui-components";
import * as validator from "../components/permissionValidation";
import RoleSelector from "../components/RoleSelector";
import AdvancedPermissionsDialog from "./AdvancedPermissionsDialog";
-import { findVerbsForRole } from "../modules/permissions";
+import {findVerbsForRole} from "../modules/permissions";
type Props = {
availableRoles: RepositoryRole[],
diff --git a/scm-ui/src/repos/permissions/containers/Permissions.js b/scm-ui/src/repos/permissions/containers/Permissions.js
index ea467a56da..41cc366ea6 100644
--- a/scm-ui/src/repos/permissions/containers/Permissions.js
+++ b/scm-ui/src/repos/permissions/containers/Permissions.js
@@ -1,44 +1,34 @@
//@flow
import React from "react";
-import { connect } from "react-redux";
-import { translate } from "react-i18next";
+import {connect} from "react-redux";
+import {translate} from "react-i18next";
import {
+ createPermission,
+ createPermissionReset,
+ deletePermissionReset,
fetchAvailablePermissionsIfNeeded,
fetchPermissions,
- getFetchAvailablePermissionsFailure,
getAvailablePermissions,
+ getAvailableRepositoryRoles,
+ getAvailableRepositoryVerbs,
+ getCreatePermissionFailure,
+ getDeletePermissionsFailure,
+ getFetchAvailablePermissionsFailure,
getFetchPermissionsFailure,
- isFetchAvailablePermissionsPending,
- isFetchPermissionsPending,
+ getModifyPermissionsFailure,
getPermissionsOfRepo,
hasCreatePermission,
- createPermission,
isCreatePermissionPending,
- getCreatePermissionFailure,
- createPermissionReset,
- getDeletePermissionsFailure,
- getModifyPermissionsFailure,
- modifyPermissionReset,
- deletePermissionReset,
- getAvailableRepositoryRoles,
- getAvailableRepositoryVerbs
+ isFetchAvailablePermissionsPending,
+ isFetchPermissionsPending,
+ modifyPermissionReset
} from "../modules/permissions";
-import {
- Loading,
- ErrorPage,
- Subtitle,
- LabelWithHelpIcon
-} from "@scm-manager/ui-components";
-import type {
- Permission,
- PermissionCollection,
- PermissionCreateEntry,
- RepositoryRole
-} from "@scm-manager/ui-types";
+import {ErrorPage, LabelWithHelpIcon, Loading, Subtitle} from "@scm-manager/ui-components";
+import type {Permission, PermissionCollection, PermissionCreateEntry, RepositoryRole} from "@scm-manager/ui-types";
import SinglePermission from "./SinglePermission";
import CreatePermissionForm from "./CreatePermissionForm";
-import type { History } from "history";
-import { getPermissionsLink } from "../../modules/repos";
+import type {History} from "history";
+import {getPermissionsLink} from "../../modules/repos";
import {
getGroupAutoCompleteLink,
getRepositoryRolesLink,
diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js
index 549c970aaf..11533a0526 100644
--- a/scm-ui/src/repos/sources/containers/Sources.js
+++ b/scm-ui/src/repos/sources/containers/Sources.js
@@ -1,25 +1,20 @@
// @flow
import React from "react";
-import { connect } from "react-redux";
-import { withRouter } from "react-router-dom";
-import type { Branch, Repository } from "@scm-manager/ui-types";
+import {connect} from "react-redux";
+import {withRouter} from "react-router-dom";
+import type {Branch, Repository} from "@scm-manager/ui-types";
import FileTree from "../components/FileTree";
-import {
- ErrorNotification,
- Loading,
- BranchSelector,
- Breadcrumb
-} from "@scm-manager/ui-components";
-import { translate } from "react-i18next";
+import {BranchSelector, Breadcrumb, ErrorNotification, Loading} from "@scm-manager/ui-components";
+import {translate} from "react-i18next";
import {
fetchBranches,
getBranches,
getFetchBranchesFailure,
isFetchBranchesPending
} from "../../branches/modules/branches";
-import { compose } from "redux";
+import {compose} from "redux";
import Content from "./Content";
-import { fetchSources, isDirectory } from "../modules/sources";
+import {fetchSources, isDirectory} from "../modules/sources";
type Props = {
repository: Repository,
diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml
index a74d8dc429..73431b780e 100644
--- a/scm-webapp/pom.xml
+++ b/scm-webapp/pom.xml
@@ -464,32 +464,28 @@
sonia.scm.maven
smp-maven-plugin
-
-
+
+
sonia.scm.plugins
scm-hg-plugin
${project.version}
- smp
-
-
+
+
sonia.scm.plugins
scm-svn-plugin
${project.version}
- smp
-
-
+
+
sonia.scm.plugins
scm-git-plugin
${project.version}
- smp
-
-
+
+
sonia.scm.plugins
scm-legacy-plugin
${project.version}
- smp
-
-
+
+
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java
new file mode 100644
index 0000000000..6d5711133f
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java
@@ -0,0 +1,108 @@
+package sonia.scm.api.v2.resources;
+
+import com.webcohesion.enunciate.metadata.rs.ResponseCode;
+import com.webcohesion.enunciate.metadata.rs.StatusCodes;
+import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import sonia.scm.plugin.Plugin;
+import sonia.scm.plugin.PluginInformation;
+import sonia.scm.plugin.PluginManager;
+import sonia.scm.plugin.PluginPermissions;
+import sonia.scm.plugin.PluginState;
+import sonia.scm.web.VndMediaType;
+
+import javax.inject.Inject;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static sonia.scm.ContextEntry.ContextBuilder.entity;
+import static sonia.scm.NotFoundException.notFound;
+
+public class AvailablePluginResource {
+
+ private final PluginDtoCollectionMapper collectionMapper;
+ private final PluginManager pluginManager;
+ private final PluginDtoMapper mapper;
+
+ @Inject
+ public AvailablePluginResource(PluginDtoCollectionMapper collectionMapper, PluginManager pluginManager, PluginDtoMapper mapper) {
+ this.collectionMapper = collectionMapper;
+ this.pluginManager = pluginManager;
+ this.mapper = mapper;
+ }
+
+ /**
+ * Returns a collection of available plugins.
+ *
+ * @return collection of available plugins.
+ */
+ @GET
+ @Path("")
+ @StatusCodes({
+ @ResponseCode(code = 200, condition = "success"),
+ @ResponseCode(code = 500, condition = "internal server error")
+ })
+ @TypeHint(CollectionDto.class)
+ @Produces(VndMediaType.PLUGIN_COLLECTION)
+ public Response getAvailablePlugins() {
+ PluginPermissions.read().check();
+ Collection plugins = pluginManager.getAvailable()
+ .stream()
+ .filter(plugin -> plugin.getState().equals(PluginState.AVAILABLE))
+ .collect(Collectors.toList());
+ return Response.ok(collectionMapper.map(plugins)).build();
+ }
+
+ /**
+ * Returns available plugin.
+ *
+ * @return available plugin.
+ */
+ @GET
+ @Path("/{name}/{version}")
+ @StatusCodes({
+ @ResponseCode(code = 200, condition = "success"),
+ @ResponseCode(code = 404, condition = "not found"),
+ @ResponseCode(code = 500, condition = "internal server error")
+ })
+ @TypeHint(PluginDto.class)
+ @Produces(VndMediaType.PLUGIN)
+ public Response getAvailablePlugin(@PathParam("name") String name, @PathParam("version") String version) {
+ PluginPermissions.read().check();
+ Optional plugin = pluginManager.getAvailable()
+ .stream()
+ .filter(p -> p.getId().equals(name + ":" + version))
+ .findFirst();
+ if (plugin.isPresent()) {
+ return Response.ok(mapper.map(plugin.get())).build();
+ } else {
+ throw notFound(entity(Plugin.class, name));
+ }
+ }
+
+ /**
+ * Triggers plugin installation.
+ * @param name plugin artefact name
+ * @param version plugin version
+ * @return HTTP Status.
+ */
+ @POST
+ @Path("/{name}/{version}/install")
+ @Consumes(VndMediaType.PLUGIN)
+ @StatusCodes({
+ @ResponseCode(code = 200, condition = "success"),
+ @ResponseCode(code = 500, condition = "internal server error")
+ })
+ public Response installPlugin(@PathParam("name") String name, @PathParam("version") String version) {
+ PluginPermissions.manage().check();
+ pluginManager.install(name + ":" + version);
+ return Response.ok().build();
+ }
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java
index cace57577c..d05596abd5 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java
@@ -51,7 +51,8 @@ public class IndexDtoGenerator extends HalAppenderMapper {
link("logout", resourceLinks.authentication().logout())
);
if (PluginPermissions.read().isPermitted()) {
- builder.single(link("plugins", resourceLinks.pluginCollection().self()));
+ builder.single(link("installedPlugins", resourceLinks.installedPluginCollection().self()));
+ builder.single(link("availablePlugins", resourceLinks.availablePluginCollection().self()));
}
if (UserPermissions.list().isPermitted()) {
builder.single(link("users", resourceLinks.userCollection().self()));
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InstalledPluginResource.java
similarity index 79%
rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginResource.java
rename to scm-webapp/src/main/java/sonia/scm/api/v2/resources/InstalledPluginResource.java
index c3b6ea6020..f10912e5ac 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InstalledPluginResource.java
@@ -5,6 +5,7 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.plugin.Plugin;
import sonia.scm.plugin.PluginLoader;
+import sonia.scm.plugin.PluginManager;
import sonia.scm.plugin.PluginPermissions;
import sonia.scm.plugin.PluginWrapper;
import sonia.scm.web.VndMediaType;
@@ -22,17 +23,19 @@ import java.util.Optional;
import static sonia.scm.ContextEntry.ContextBuilder.entity;
import static sonia.scm.NotFoundException.notFound;
-public class PluginResource {
+public class InstalledPluginResource {
private final PluginLoader pluginLoader;
private final PluginDtoCollectionMapper collectionMapper;
private final PluginDtoMapper mapper;
+ private final PluginManager pluginManager;
@Inject
- public PluginResource(PluginLoader pluginLoader, PluginDtoCollectionMapper collectionMapper, PluginDtoMapper mapper) {
+ public InstalledPluginResource(PluginLoader pluginLoader, PluginDtoCollectionMapper collectionMapper, PluginDtoMapper mapper, PluginManager pluginManager) {
this.pluginLoader = pluginLoader;
this.collectionMapper = collectionMapper;
this.mapper = mapper;
+ this.pluginManager = pluginManager;
}
/**
@@ -57,12 +60,12 @@ public class PluginResource {
/**
* Returns the installed plugin with the given id.
*
- * @param id id of plugin
+ * @param name name of plugin
*
* @return installed plugin with specified id
*/
@GET
- @Path("{id}")
+ @Path("/{name}")
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 404, condition = "not found"),
@@ -70,18 +73,17 @@ public class PluginResource {
})
@TypeHint(PluginDto.class)
@Produces(VndMediaType.PLUGIN)
- public Response getInstalledPlugin(@PathParam("id") String id) {
+ public Response getInstalledPlugin(@PathParam("name") String name) {
PluginPermissions.read().check();
Optional pluginDto = pluginLoader.getInstalledPlugins()
.stream()
- .filter(plugin -> id.equals(plugin.getPlugin().getInformation().getId(false)))
+ .filter(plugin -> name.equals(plugin.getPlugin().getInformation().getName()))
.map(mapper::map)
.findFirst();
if (pluginDto.isPresent()) {
return Response.ok(pluginDto.get()).build();
} else {
- throw notFound(entity(Plugin.class, id));
+ throw notFound(entity(Plugin.class, name));
}
}
-
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java
index cf09eeb128..0b419cf542 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java
@@ -54,5 +54,7 @@ public class MapperModule extends AbstractModule {
bind(UIPluginDtoCollectionMapper.class);
bind(ScmPathInfoStore.class).in(ServletScopes.REQUEST);
+
+ bind(PluginDtoMapper.class).to(Mappers.getMapper(PluginDtoMapper.class).getClass());
}
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java
index d119eca711..b096266537 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java
@@ -3,7 +3,6 @@ package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
-
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -13,10 +12,12 @@ import lombok.Setter;
public class PluginDto extends HalRepresentation {
private String name;
- private String type;
private String version;
- private String author;
+ private String displayName;
private String description;
+ private String author;
+ private String category;
+ private String avatarUrl;
public PluginDto(Links links) {
add(links);
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java
index 72178e94f3..5d8746c211 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java
@@ -4,6 +4,7 @@ import com.google.inject.Inject;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
+import sonia.scm.plugin.PluginInformation;
import sonia.scm.plugin.PluginWrapper;
import java.util.Collection;
@@ -24,13 +25,26 @@ public class PluginDtoCollectionMapper {
this.mapper = mapper;
}
- public HalRepresentation map(Collection plugins) {
+ public HalRepresentation map(List plugins) {
List dtos = plugins.stream().map(mapper::map).collect(toList());
- return new HalRepresentation(createLinks(), embedDtos(dtos));
+ return new HalRepresentation(createInstalledPluginsLinks(), embedDtos(dtos));
}
- private Links createLinks() {
- String baseUrl = resourceLinks.pluginCollection().self();
+ public HalRepresentation map(Collection plugins) {
+ List dtos = plugins.stream().map(mapper::map).collect(toList());
+ return new HalRepresentation(createAvailablePluginsLinks(), embedDtos(dtos));
+ }
+
+ private Links createInstalledPluginsLinks() {
+ String baseUrl = resourceLinks.installedPluginCollection().self();
+
+ Links.Builder linksBuilder = linkingTo()
+ .with(Links.linkingTo().self(baseUrl).build());
+ return linksBuilder.build();
+ }
+
+ private Links createAvailablePluginsLinks() {
+ String baseUrl = resourceLinks.availablePluginCollection().self();
Links.Builder linksBuilder = linkingTo()
.with(Links.linkingTo().self(baseUrl).build());
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java
index d17ecdae70..ca81edd7ff 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java
@@ -1,32 +1,54 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
+import org.mapstruct.AfterMapping;
+import org.mapstruct.Mapper;
+import org.mapstruct.MappingTarget;
+import org.mapstruct.ObjectFactory;
+import sonia.scm.plugin.PluginInformation;
+import sonia.scm.plugin.PluginState;
import sonia.scm.plugin.PluginWrapper;
+
import javax.inject.Inject;
+import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
-public class PluginDtoMapper {
-
- private final ResourceLinks resourceLinks;
+@Mapper
+public abstract class PluginDtoMapper {
@Inject
- public PluginDtoMapper(ResourceLinks resourceLinks) {
- this.resourceLinks = resourceLinks;
- }
+ private ResourceLinks resourceLinks;
public PluginDto map(PluginWrapper plugin) {
- Links.Builder linksBuilder = linkingTo()
- .self(resourceLinks.plugin()
- .self(plugin.getPlugin().getInformation().getId(false)));
+ return map(plugin.getPlugin().getInformation());
+ }
- PluginDto pluginDto = new PluginDto(linksBuilder.build());
- pluginDto.setName(plugin.getPlugin().getInformation().getName());
- pluginDto.setType(plugin.getPlugin().getInformation().getCategory() != null ? plugin.getPlugin().getInformation().getCategory() : "Miscellaneous");
- pluginDto.setVersion(plugin.getPlugin().getInformation().getVersion());
- pluginDto.setAuthor(plugin.getPlugin().getInformation().getAuthor());
- pluginDto.setDescription(plugin.getPlugin().getInformation().getDescription());
+ public abstract PluginDto map(PluginInformation plugin);
- return pluginDto;
+ @AfterMapping
+ protected void appendCategory(@MappingTarget PluginDto dto) {
+ if (dto.getCategory() == null) {
+ dto.setCategory("Miscellaneous");
+ }
+ }
+
+ @ObjectFactory
+ public PluginDto createDto(PluginInformation pluginInformation) {
+ Links.Builder linksBuilder;
+ if (pluginInformation.getState() != null && pluginInformation.getState().equals(PluginState.AVAILABLE)) {
+ linksBuilder = linkingTo()
+ .self(resourceLinks.availablePlugin()
+ .self(pluginInformation.getName(), pluginInformation.getVersion()));
+
+ linksBuilder.single(link("install", resourceLinks.availablePlugin().install(pluginInformation.getName(), pluginInformation.getVersion())));
+ }
+ else {
+ linksBuilder = linkingTo()
+ .self(resourceLinks.installedPlugin()
+ .self(pluginInformation.getName()));
+ }
+
+ return new PluginDto(linksBuilder.build());
}
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java
index e9b0f0a997..79c46369a3 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java
@@ -4,18 +4,23 @@ import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
-@Path("v2/")
+@Path("v2/plugins")
public class PluginRootResource {
- private Provider pluginResourceProvider;
+ private Provider installedPluginResourceProvider;
+ private Provider availablePluginResourceProvider;
@Inject
- public PluginRootResource(Provider pluginResourceProvider) {
- this.pluginResourceProvider = pluginResourceProvider;
+ public PluginRootResource(Provider installedPluginResourceProvider, Provider availablePluginResourceProvider) {
+ this.installedPluginResourceProvider = installedPluginResourceProvider;
+ this.availablePluginResourceProvider = availablePluginResourceProvider;
}
- @Path("plugins")
- public PluginResource plugins() {
- return pluginResourceProvider.get();
+ @Path("/installed")
+ public InstalledPluginResource installedPlugins() {
+ return installedPluginResourceProvider.get();
}
+
+ @Path("/available")
+ public AvailablePluginResource availablePlugins() { return availablePluginResourceProvider.get(); }
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java
index 1d06659649..268f5f8619 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java
@@ -651,35 +651,71 @@ class ResourceLinks {
}
}
- public PluginLinks plugin() {
- return new PluginLinks(scmPathInfoStore.get());
+ public InstalledPluginLinks installedPlugin() {
+ return new InstalledPluginLinks(scmPathInfoStore.get());
}
- static class PluginLinks {
- private final LinkBuilder pluginLinkBuilder;
+ static class InstalledPluginLinks {
+ private final LinkBuilder installedPluginLinkBuilder;
- PluginLinks(ScmPathInfo pathInfo) {
- pluginLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, PluginResource.class);
+ InstalledPluginLinks(ScmPathInfo pathInfo) {
+ installedPluginLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, InstalledPluginResource.class);
}
String self(String id) {
- return pluginLinkBuilder.method("plugins").parameters().method("getInstalledPlugin").parameters(id).href();
+ return installedPluginLinkBuilder.method("installedPlugins").parameters().method("getInstalledPlugin").parameters(id).href();
}
}
- public PluginCollectionLinks pluginCollection() {
- return new PluginCollectionLinks(scmPathInfoStore.get());
+ public InstalledPluginCollectionLinks installedPluginCollection() {
+ return new InstalledPluginCollectionLinks(scmPathInfoStore.get());
}
- static class PluginCollectionLinks {
- private final LinkBuilder pluginCollectionLinkBuilder;
+ static class InstalledPluginCollectionLinks {
+ private final LinkBuilder installedPluginCollectionLinkBuilder;
- PluginCollectionLinks(ScmPathInfo pathInfo) {
- pluginCollectionLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, PluginResource.class);
+ InstalledPluginCollectionLinks(ScmPathInfo pathInfo) {
+ installedPluginCollectionLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, InstalledPluginResource.class);
}
String self() {
- return pluginCollectionLinkBuilder.method("plugins").parameters().method("getInstalledPlugins").parameters().href();
+ return installedPluginCollectionLinkBuilder.method("installedPlugins").parameters().method("getInstalledPlugins").parameters().href();
+ }
+ }
+
+ public AvailablePluginLinks availablePlugin() {
+ return new AvailablePluginLinks(scmPathInfoStore.get());
+ }
+
+ static class AvailablePluginLinks {
+ private final LinkBuilder availablePluginLinkBuilder;
+
+ AvailablePluginLinks(ScmPathInfo pathInfo) {
+ availablePluginLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, AvailablePluginResource.class);
+ }
+
+ String self(String name, String version) {
+ return availablePluginLinkBuilder.method("availablePlugins").parameters().method("getAvailablePlugin").parameters(name, version).href();
+ }
+
+ String install(String name, String version) {
+ return availablePluginLinkBuilder.method("availablePlugins").parameters().method("installPlugin").parameters(name, version).href();
+ }
+ }
+
+ public AvailablePluginCollectionLinks availablePluginCollection() {
+ return new AvailablePluginCollectionLinks(scmPathInfoStore.get());
+ }
+
+ static class AvailablePluginCollectionLinks {
+ private final LinkBuilder availablePluginCollectionLinkBuilder;
+
+ AvailablePluginCollectionLinks(ScmPathInfo pathInfo) {
+ availablePluginCollectionLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, AvailablePluginResource.class);
+ }
+
+ String self() {
+ return availablePluginCollectionLinkBuilder.method("availablePlugins").parameters().method("getAvailablePlugins").parameters().href();
}
}
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 ed1f691988..b718a43a81 100644
--- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java
@@ -78,6 +78,8 @@ import javax.xml.bind.JAXB;
import sonia.scm.net.ahc.AdvancedHttpClient;
+import static sonia.scm.plugin.PluginCenterDtoMapper.*;
+
/**
* TODO replace aether stuff.
* TODO check AdvancedPluginConfiguration from 1.x
@@ -99,7 +101,7 @@ public class DefaultPluginManager implements PluginManager
LoggerFactory.getLogger(DefaultPluginManager.class);
/** enable or disable remote plugins */
- private static final boolean REMOTE_PLUGINS_ENABLED = false;
+ private static final boolean REMOTE_PLUGINS_ENABLED = true;
/** Field description */
public static final Predicate FILTER_UPDATES =
@@ -181,8 +183,6 @@ public class DefaultPluginManager implements PluginManager
PluginCenter center = getPluginCenter();
- // pluginHandler.install(id);
-
for (PluginInformation plugin : center.getPlugins())
{
String pluginId = plugin.getId();
@@ -309,14 +309,12 @@ public class DefaultPluginManager implements PluginManager
PluginPermissions.manage().check();
String[] idParts = id.split(":");
- String groupId = idParts[0];
- String artefactId = idParts[1];
+ String name = idParts[0];
PluginInformation installed = null;
for (PluginInformation info : getInstalled())
{
- if (groupId.equals(info.getGroupId())
- && artefactId.equals(info.getArtifactId()))
+ if (name.equals(info.getName()))
{
installed = info;
@@ -326,9 +324,9 @@ public class DefaultPluginManager implements PluginManager
if (installed == null)
{
- StringBuilder msg = new StringBuilder(groupId);
+ StringBuilder msg = new StringBuilder(name);
- msg.append(":").append(groupId).append(" is not install");
+ msg.append(" is not install");
throw new PluginNotInstalledException(msg.toString());
}
@@ -423,7 +421,7 @@ public class DefaultPluginManager implements PluginManager
for (PluginInformation info : centerPlugins)
{
- if (!installedPlugins.containsKey(info.getId()))
+ if (!installedPlugins.containsKey(info.getName()))
{
availablePlugins.add(info);
}
@@ -596,48 +594,28 @@ public class DefaultPluginManager implements PluginManager
{
synchronized (DefaultPluginManager.class)
{
- String pluginUrl = configuration.getPluginUrl();
+ String pluginUrl = buildPluginUrl(configuration.getPluginUrl());
+ logger.info("fetch plugin information from {}", pluginUrl);
- pluginUrl = buildPluginUrl(pluginUrl);
-
- if (logger.isInfoEnabled())
- {
- logger.info("fetch plugin informations from {}", pluginUrl);
- }
-
- /**
- * remote plugins are disabled for early 2.0.0-SNAPSHOTS
- * TODO enable remote plugins later
- */
if (REMOTE_PLUGINS_ENABLED && Util.isNotEmpty(pluginUrl))
{
try
{
- center = httpClient.get(pluginUrl).request().contentFromXml(PluginCenter.class);
+ center = new PluginCenter();
+ PluginCenterDto pluginCenterDto = httpClient.get(pluginUrl).request().contentFromJson(PluginCenterDto.class);
+ Set pluginInformationSet = map(pluginCenterDto.getEmbedded().getPlugins());
+ center.setPlugins(pluginInformationSet);
preparePlugins(center);
cache.put(PluginCenter.class.getName(), center);
-
- /*
- * if (pluginHandler == null)
- * {
- * pluginHandler = new AetherPluginHandler(this,
- * SCMContext.getContext(), configuration,
- * advancedPluginConfiguration);
- * }
- *
- * pluginHandler.setPluginRepositories(center.getRepositories());
- */
}
catch (IOException ex)
{
logger.error("could not load plugins from plugin center", ex);
}
}
-
- if (center == null)
- {
- center = new PluginCenter();
- }
+ }
+ if(center == null) {
+ center = new PluginCenter();
}
}
@@ -719,8 +697,7 @@ public class DefaultPluginManager implements PluginManager
*/
private boolean isSamePlugin(PluginInformation p1, PluginInformation p2)
{
- return p1.getGroupId().equals(p2.getGroupId())
- && p1.getArtifactId().equals(p2.getArtifactId());
+ return p1.getName().equals(p2.getName());
}
//~--- fields ---------------------------------------------------------------
diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/ExplodedSmp.java b/scm-webapp/src/main/java/sonia/scm/plugin/ExplodedSmp.java
index d1fe214f50..372470df14 100644
--- a/scm-webapp/src/main/java/sonia/scm/plugin/ExplodedSmp.java
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/ExplodedSmp.java
@@ -115,8 +115,8 @@ public final class ExplodedSmp implements Comparable
}
else
{
- String id = plugin.getInformation().getId(false);
- String oid = o.plugin.getInformation().getId(false);
+ String id = plugin.getInformation().getName(false);
+ String oid = o.plugin.getInformation().getName(false);
if (depends.contains(oid) && odepends.contains(id))
{
diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java
new file mode 100644
index 0000000000..8bb48c8ceb
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java
@@ -0,0 +1,90 @@
+package sonia.scm.plugin;
+
+import com.google.common.collect.ImmutableList;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.FIELD)
+public final class PluginCenterDto implements Serializable {
+
+ @XmlElement(name = "_embedded")
+ private Embedded embedded;
+
+ public Embedded getEmbedded() {
+ return embedded;
+ }
+
+ @XmlRootElement(name = "_embedded")
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class Embedded {
+
+ @XmlElement(name = "plugins")
+ private List plugins;
+
+ public List getPlugins() {
+ if (plugins == null) {
+ plugins = ImmutableList.of();
+ }
+ return plugins;
+ }
+ }
+
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlRootElement(name = "plugins")
+ @Getter
+ @AllArgsConstructor
+ public static class Plugin {
+
+ private String name;
+ private String version;
+ private String displayName;
+ private String description;
+ private String category;
+ private String author;
+ private String avatarUrl;
+ private String sha256;
+
+ @XmlElement(name = "conditions")
+ private Condition conditions;
+
+ @XmlElement(name = "dependecies")
+ private Dependency dependencies;
+
+ @XmlElement(name = "_links")
+ private Map links;
+ }
+
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlRootElement(name = "conditions")
+ @Getter
+ @AllArgsConstructor
+ public static class Condition {
+
+ private List os;
+ private String arch;
+ private String minVersion;
+ }
+
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlRootElement(name = "dependencies")
+ @Getter
+ @AllArgsConstructor
+ static class Dependency {
+ private String name;
+ }
+
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @Getter
+ static class Link {
+ private String href;
+ }
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java
new file mode 100644
index 0000000000..ea445b3ede
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java
@@ -0,0 +1,27 @@
+package sonia.scm.plugin;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@Mapper
+public interface PluginCenterDtoMapper {
+
+ @Mapping(source = "conditions", target = "condition")
+ PluginInformation map(PluginCenterDto.Plugin plugin);
+
+ PluginCondition map(PluginCenterDto.Condition condition);
+
+ static Set map(List dtos) {
+ PluginCenterDtoMapper mapper = Mappers.getMapper(PluginCenterDtoMapper.class);
+ Set plugins = new HashSet<>();
+ for (PluginCenterDto.Plugin plugin : dtos) {
+ plugins.add(mapper.map(plugin));
+ }
+ return plugins;
+ }
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginNode.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginNode.java
index e28ccff2ff..8bc4f47658 100644
--- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginNode.java
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginNode.java
@@ -126,7 +126,7 @@ public final class PluginNode
*/
public String getId()
{
- return plugin.getPlugin().getInformation().getId(false);
+ return plugin.getPlugin().getInformation().getName(false);
}
/**
diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java
index c7d669ee63..b91ee9b1ee 100644
--- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java
@@ -318,10 +318,7 @@ public final class PluginProcessor
{
for (Path parent : parentStream)
{
- try (DirectoryStream direcotries = stream(parent, filter))
- {
- paths.addAll(direcotries);
- }
+ paths.add(parent);
}
}
@@ -333,7 +330,6 @@ public final class PluginProcessor
*
*
* @param parentClassLoader
- * @param directory
* @param smp
*
* @return
@@ -377,7 +373,7 @@ public final class PluginProcessor
URL[] urlArray = urls.toArray(new URL[urls.size()]);
Plugin plugin = smp.getPlugin();
- String id = plugin.getInformation().getId(false);
+ String id = plugin.getInformation().getName(false);
if (smp.getPlugin().isChildFirstClassLoader())
{
@@ -472,7 +468,6 @@ public final class PluginProcessor
*
*
* @param classLoader
- * @param directory
* @param smp
*
* @return
@@ -511,7 +506,6 @@ public final class PluginProcessor
*
*
* @param classLoader
- * @param smps
* @param rootNodes
*
* @return
diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginsInternal.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginsInternal.java
index 52d192da32..0354ded11a 100644
--- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginsInternal.java
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginsInternal.java
@@ -109,7 +109,7 @@ public final class PluginsInternal
{
PluginInformation info = plugin.getInformation();
- return new File(new File(parent, info.getGroupId()), info.getArtifactId());
+ return new File(parent, info.getName());
}
/**
@@ -131,14 +131,14 @@ public final class PluginsInternal
if (directory.exists())
{
logger.debug("delete directory {} for plugin extraction",
- archive.getPlugin().getInformation().getId(false));
+ archive.getPlugin().getInformation().getName(false));
IOUtil.delete(directory);
}
IOUtil.mkdirs(directory);
logger.debug("extract plugin {}",
- archive.getPlugin().getInformation().getId(false));
+ archive.getPlugin().getInformation().getName(false));
archive.extract(directory);
Files.write(checksum, checksumFile, Charsets.UTF_8);
diff --git a/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardServlet.java b/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardServlet.java
index e913cead25..173dcb0638 100644
--- a/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardServlet.java
+++ b/scm-webapp/src/main/java/sonia/scm/update/MigrationWizardServlet.java
@@ -7,10 +7,10 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import sonia.scm.lifecycle.RestartEvent;
import sonia.scm.event.ScmEventBus;
-import sonia.scm.update.repository.MigrationStrategy;
+import sonia.scm.lifecycle.RestartEvent;
import sonia.scm.update.repository.DefaultMigrationStrategyDAO;
+import sonia.scm.update.repository.MigrationStrategy;
import sonia.scm.update.repository.V1Repository;
import sonia.scm.update.repository.XmlRepositoryV1UpdateStep;
import sonia.scm.util.ValidationUtil;
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java
new file mode 100644
index 0000000000..57564999ef
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AvailablePluginResourceTest.java
@@ -0,0 +1,182 @@
+package sonia.scm.api.v2.resources;
+
+import de.otto.edison.hal.HalRepresentation;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ThreadContext;
+import org.jboss.resteasy.core.Dispatcher;
+import org.jboss.resteasy.mock.MockDispatcherFactory;
+import org.jboss.resteasy.mock.MockHttpRequest;
+import org.jboss.resteasy.mock.MockHttpResponse;
+import org.jboss.resteasy.spi.UnhandledException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import sonia.scm.plugin.PluginInformation;
+import sonia.scm.plugin.PluginManager;
+import sonia.scm.plugin.PluginState;
+import sonia.scm.web.VndMediaType;
+
+import javax.inject.Provider;
+import javax.servlet.http.HttpServletResponse;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URISyntaxException;
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class AvailablePluginResourceTest {
+
+ private Dispatcher dispatcher;
+
+ @Mock
+ Provider installedPluginResourceProvider;
+
+ @Mock
+ Provider availablePluginResourceProvider;
+
+ @Mock
+ private PluginDtoCollectionMapper collectionMapper;
+
+ @Mock
+ private PluginManager pluginManager;
+
+ @Mock
+ private PluginDtoMapper mapper;
+
+ @InjectMocks
+ AvailablePluginResource availablePluginResource;
+
+ PluginRootResource pluginRootResource;
+
+ private final Subject subject = mock(Subject.class);
+
+
+ @BeforeEach
+ void prepareEnvironment() {
+ dispatcher = MockDispatcherFactory.createDispatcher();
+ pluginRootResource = new PluginRootResource(installedPluginResourceProvider, availablePluginResourceProvider);
+ when(availablePluginResourceProvider.get()).thenReturn(availablePluginResource);
+ dispatcher.getRegistry().addSingletonResource(pluginRootResource);
+ }
+
+ @Nested
+ class withAuthorization {
+
+ @BeforeEach
+ void bindSubject() {
+ ThreadContext.bind(subject);
+ when(subject.isPermitted(any(String.class))).thenReturn(true);
+ }
+
+ @AfterEach
+ public void unbindSubject() {
+ ThreadContext.unbindSubject();
+ }
+
+ @Test
+ void getAvailablePlugins() throws URISyntaxException, UnsupportedEncodingException {
+ PluginInformation pluginInformation = new PluginInformation();
+ pluginInformation.setState(PluginState.AVAILABLE);
+ when(pluginManager.getAvailable()).thenReturn(Collections.singletonList(pluginInformation));
+ when(collectionMapper.map(Collections.singletonList(pluginInformation))).thenReturn(new MockedResultDto());
+
+ MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available");
+ request.accept(VndMediaType.PLUGIN_COLLECTION);
+ MockHttpResponse response = new MockHttpResponse();
+
+ dispatcher.invoke(request, response);
+
+ assertThat(HttpServletResponse.SC_OK).isEqualTo(response.getStatus());
+ assertThat(response.getContentAsString()).contains("\"marker\":\"x\"");
+ }
+
+ @Test
+ void getAvailablePlugin() throws UnsupportedEncodingException, URISyntaxException {
+ PluginInformation pluginInformation = new PluginInformation();
+ pluginInformation.setState(PluginState.AVAILABLE);
+ pluginInformation.setName("pluginName");
+ pluginInformation.setVersion("2.0.0");
+ when(pluginManager.getAvailable()).thenReturn(Collections.singletonList(pluginInformation));
+
+ PluginDto pluginDto = new PluginDto();
+ pluginDto.setName("pluginName");
+ when(mapper.map(pluginInformation)).thenReturn(pluginDto);
+
+ MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available/pluginName/2.0.0");
+ request.accept(VndMediaType.PLUGIN);
+ MockHttpResponse response = new MockHttpResponse();
+
+ dispatcher.invoke(request, response);
+
+ assertThat(HttpServletResponse.SC_OK).isEqualTo(response.getStatus());
+ assertThat(response.getContentAsString()).contains("\"name\":\"pluginName\"");
+ }
+
+ @Test
+ void installPlugin() throws URISyntaxException {
+ MockHttpRequest request = MockHttpRequest.post("/v2/plugins/available/pluginName/2.0.0/install");
+ request.accept(VndMediaType.PLUGIN);
+ MockHttpResponse response = new MockHttpResponse();
+
+ dispatcher.invoke(request, response);
+
+ verify(pluginManager).install("pluginName:2.0.0");
+ assertThat(HttpServletResponse.SC_OK).isEqualTo(response.getStatus());
+ }
+ }
+
+ @Nested
+ class WithoutAuthorization {
+
+ @BeforeEach
+ void unbindSubject() {
+ ThreadContext.unbindSubject();
+ }
+
+ @Test
+ void shouldNotGetAvailablePluginsIfMissingPermission() throws URISyntaxException {
+ MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available");
+ request.accept(VndMediaType.PLUGIN_COLLECTION);
+ MockHttpResponse response = new MockHttpResponse();
+
+ assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response));
+ }
+
+ @Test
+ void shouldNotGetAvailablePluginIfMissingPermission() throws URISyntaxException {
+ MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available/pluginName/2.0.0");
+ request.accept(VndMediaType.PLUGIN);
+ MockHttpResponse response = new MockHttpResponse();
+
+ assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response));
+ }
+
+ @Test
+ void shouldNotInstallPluginIfMissingPermission() throws URISyntaxException {
+ ThreadContext.unbindSubject();
+ MockHttpRequest request = MockHttpRequest.post("/v2/plugins/available/pluginName/2.0.0/install");
+ request.accept(VndMediaType.PLUGIN);
+ MockHttpResponse response = new MockHttpResponse();
+
+ assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response));
+ }
+ }
+
+ public class MockedResultDto extends HalRepresentation {
+ public String getMarker() {
+ return "x";
+ }
+ }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java
new file mode 100644
index 0000000000..a81eadadb8
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InstalledPluginResourceTest.java
@@ -0,0 +1,160 @@
+package sonia.scm.api.v2.resources;
+
+import de.otto.edison.hal.HalRepresentation;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ThreadContext;
+import org.jboss.resteasy.core.Dispatcher;
+import org.jboss.resteasy.mock.MockDispatcherFactory;
+import org.jboss.resteasy.mock.MockHttpRequest;
+import org.jboss.resteasy.mock.MockHttpResponse;
+import org.jboss.resteasy.spi.UnhandledException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import sonia.scm.plugin.Plugin;
+import sonia.scm.plugin.PluginInformation;
+import sonia.scm.plugin.PluginLoader;
+import sonia.scm.plugin.PluginState;
+import sonia.scm.plugin.PluginWrapper;
+import sonia.scm.web.VndMediaType;
+
+import javax.inject.Provider;
+import javax.servlet.http.HttpServletResponse;
+import java.io.UnsupportedEncodingException;
+import java.net.URISyntaxException;
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class InstalledPluginResourceTest {
+
+ private Dispatcher dispatcher;
+
+ @Mock
+ Provider installedPluginResourceProvider;
+
+ @Mock
+ Provider availablePluginResourceProvider;
+
+ @Mock
+ private PluginLoader pluginLoader;
+
+ @Mock
+ private PluginDtoCollectionMapper collectionMapper;
+
+ @Mock
+ private PluginDtoMapper mapper;
+
+ @InjectMocks
+ InstalledPluginResource installedPluginResource;
+
+ PluginRootResource pluginRootResource;
+
+ private final Subject subject = mock(Subject.class);
+
+ @BeforeEach
+ void prepareEnvironment() {
+ dispatcher = MockDispatcherFactory.createDispatcher();
+ pluginRootResource = new PluginRootResource(installedPluginResourceProvider, availablePluginResourceProvider);
+ when(installedPluginResourceProvider.get()).thenReturn(installedPluginResource);
+ dispatcher.getRegistry().addSingletonResource(pluginRootResource);
+ }
+
+ @Nested
+ class withAuthorization {
+
+ @BeforeEach
+ void bindSubject() {
+ ThreadContext.bind(subject);
+ when(subject.isPermitted(any(String.class))).thenReturn(true);
+ }
+
+ @AfterEach
+ public void unbindSubject() {
+ ThreadContext.unbindSubject();
+ }
+
+ @Test
+ void getInstalledPlugins() throws URISyntaxException, UnsupportedEncodingException {
+ PluginWrapper pluginWrapper = new PluginWrapper(null, null, null, null);
+ when(pluginLoader.getInstalledPlugins()).thenReturn(Collections.singletonList(pluginWrapper));
+ when(collectionMapper.map(Collections.singletonList(pluginWrapper))).thenReturn(new MockedResultDto());
+
+ MockHttpRequest request = MockHttpRequest.get("/v2/plugins/installed");
+ request.accept(VndMediaType.PLUGIN_COLLECTION);
+ MockHttpResponse response = new MockHttpResponse();
+
+ dispatcher.invoke(request, response);
+
+ assertThat(HttpServletResponse.SC_OK).isEqualTo(response.getStatus());
+ assertThat(response.getContentAsString()).contains("\"marker\":\"x\"");
+ }
+
+ @Test
+ void getInstalledPlugin() throws UnsupportedEncodingException, URISyntaxException {
+ PluginInformation pluginInformation = new PluginInformation();
+ pluginInformation.setVersion("2.0.0");
+ pluginInformation.setName("pluginName");
+ pluginInformation.setState(PluginState.INSTALLED);
+ Plugin plugin = new Plugin(2, pluginInformation, null, null, false, null);
+ PluginWrapper pluginWrapper = new PluginWrapper(plugin, null, null, null);
+ when(pluginLoader.getInstalledPlugins()).thenReturn(Collections.singletonList(pluginWrapper));
+
+ PluginDto pluginDto = new PluginDto();
+ pluginDto.setName("pluginName");
+ when(mapper.map(pluginWrapper)).thenReturn(pluginDto);
+
+ MockHttpRequest request = MockHttpRequest.get("/v2/plugins/installed/pluginName");
+ request.accept(VndMediaType.PLUGIN);
+ MockHttpResponse response = new MockHttpResponse();
+
+ dispatcher.invoke(request, response);
+
+ assertThat(HttpServletResponse.SC_OK).isEqualTo(response.getStatus());
+ assertThat(response.getContentAsString()).contains("\"name\":\"pluginName\"");
+ }
+ }
+
+ @Nested
+ class WithoutAuthorization {
+
+ @BeforeEach
+ void unbindSubject() {
+ ThreadContext.unbindSubject();
+ }
+
+ @Test
+ void shouldNotGetInstalledPluginsIfMissingPermission() throws URISyntaxException {
+ MockHttpRequest request = MockHttpRequest.get("/v2/plugins/installed");
+ request.accept(VndMediaType.PLUGIN_COLLECTION);
+ MockHttpResponse response = new MockHttpResponse();
+
+ assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response));
+ }
+
+ @Test
+ void shouldNotGetInstalledPluginIfMissingPermission() throws URISyntaxException {
+ MockHttpRequest request = MockHttpRequest.get("/v2/plugins/installed/pluginName");
+ request.accept(VndMediaType.PLUGIN);
+ MockHttpResponse response = new MockHttpResponse();
+
+ assertThrows(UnhandledException.class, () -> dispatcher.invoke(request, response));
+ }
+ }
+
+ public class MockedResultDto extends HalRepresentation {
+ public String getMarker() {
+ return "x";
+ }
+ }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java
new file mode 100644
index 0000000000..97b46603d3
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PluginDtoMapperTest.java
@@ -0,0 +1,88 @@
+package sonia.scm.api.v2.resources;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.junit.jupiter.MockitoExtension;
+import sonia.scm.plugin.PluginInformation;
+import sonia.scm.plugin.PluginState;
+
+import java.net.URI;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ExtendWith(MockitoExtension.class)
+class PluginDtoMapperTest {
+
+ @SuppressWarnings("unused") // Is injected
+ private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("https://hitchhiker.com/"));
+
+ @InjectMocks
+ private PluginDtoMapperImpl mapper;
+
+ @Test
+ void shouldMapInformation() {
+ PluginInformation information = createPluginInformation();
+
+ PluginDto dto = mapper.map(information);
+
+ assertThat(dto.getName()).isEqualTo("scm-cas-plugin");
+ assertThat(dto.getVersion()).isEqualTo("1.0.0");
+ assertThat(dto.getDisplayName()).isEqualTo("CAS");
+ assertThat(dto.getAuthor()).isEqualTo("Sebastian Sdorra");
+ assertThat(dto.getCategory()).isEqualTo("Authentication");
+ assertThat(dto.getAvatarUrl()).isEqualTo("https://avatar.scm-manager.org/plugins/cas.png");
+ }
+
+ private PluginInformation createPluginInformation() {
+ PluginInformation information = new PluginInformation();
+ information.setName("scm-cas-plugin");
+ information.setVersion("1.0.0");
+ information.setDisplayName("CAS");
+ information.setAuthor("Sebastian Sdorra");
+ information.setCategory("Authentication");
+ information.setAvatarUrl("https://avatar.scm-manager.org/plugins/cas.png");
+ return information;
+ }
+
+ @Test
+ void shouldAppendInstalledSelfLink() {
+ PluginInformation information = createPluginInformation();
+ information.setState(PluginState.INSTALLED);
+
+ PluginDto dto = mapper.map(information);
+ assertThat(dto.getLinks().getLinkBy("self").get().getHref())
+ .isEqualTo("https://hitchhiker.com/v2/plugins/installed/scm-cas-plugin");
+ }
+
+ @Test
+ void shouldAppendAvailableSelfLink() {
+ PluginInformation information = createPluginInformation();
+ information.setState(PluginState.AVAILABLE);
+
+ PluginDto dto = mapper.map(information);
+ assertThat(dto.getLinks().getLinkBy("self").get().getHref())
+ .isEqualTo("https://hitchhiker.com/v2/plugins/available/scm-cas-plugin/1.0.0");
+ }
+
+ @Test
+ void shouldAppendInstallLink() {
+ PluginInformation information = createPluginInformation();
+ information.setState(PluginState.AVAILABLE);
+
+ PluginDto dto = mapper.map(information);
+ assertThat(dto.getLinks().getLinkBy("install").get().getHref())
+ .isEqualTo("https://hitchhiker.com/v2/plugins/available/scm-cas-plugin/1.0.0/install");
+ }
+
+ @Test
+ void shouldReturnMiscellaneousIfCategoryIsNull() {
+ PluginInformation information = createPluginInformation();
+ information.setCategory(null);
+
+ PluginDto dto = mapper.map(information);
+ assertThat(dto.getCategory()).isEqualTo("Miscellaneous");
+ }
+
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java
index 83a0f073dd..1aef4e57cb 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java
@@ -36,6 +36,10 @@ public class ResourceLinksMock {
when(resourceLinks.modifications()).thenReturn(new ResourceLinks.ModificationsLinks(uriInfo));
when(resourceLinks.repositoryType()).thenReturn(new ResourceLinks.RepositoryTypeLinks(uriInfo));
when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(uriInfo));
+ when(resourceLinks.installedPluginCollection()).thenReturn(new ResourceLinks.InstalledPluginCollectionLinks(uriInfo));
+ when(resourceLinks.availablePluginCollection()).thenReturn(new ResourceLinks.AvailablePluginCollectionLinks(uriInfo));
+ when(resourceLinks.installedPlugin()).thenReturn(new ResourceLinks.InstalledPluginLinks(uriInfo));
+ when(resourceLinks.availablePlugin()).thenReturn(new ResourceLinks.AvailablePluginLinks(uriInfo));
when(resourceLinks.uiPluginCollection()).thenReturn(new ResourceLinks.UIPluginCollectionLinks(uriInfo));
when(resourceLinks.uiPlugin()).thenReturn(new ResourceLinks.UIPluginLinks(uriInfo));
when(resourceLinks.authentication()).thenReturn(new ResourceLinks.AuthenticationLinks(uriInfo));
@@ -46,7 +50,6 @@ public class ResourceLinksMock {
when(resourceLinks.repositoryRole()).thenReturn(new ResourceLinks.RepositoryRoleLinks(uriInfo));
when(resourceLinks.repositoryRoleCollection()).thenReturn(new ResourceLinks.RepositoryRoleCollectionLinks(uriInfo));
when(resourceLinks.namespaceStrategies()).thenReturn(new ResourceLinks.NamespaceStrategiesLinks(uriInfo));
- when(resourceLinks.pluginCollection()).thenReturn(new ResourceLinks.PluginCollectionLinks(uriInfo));
return resourceLinks;
}
diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/ExplodedSmpTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/ExplodedSmpTest.java
index b7bde65677..601725d938 100644
--- a/scm-webapp/src/test/java/sonia/scm/plugin/ExplodedSmpTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/plugin/ExplodedSmpTest.java
@@ -60,12 +60,12 @@ public class ExplodedSmpTest
@Test
public void testCompareTo()
{
- ExplodedSmp e1 = create("a", "c", "1", "a:a");
+ ExplodedSmp e1 = create("a", "c", "1", "a");
ExplodedSmp e3 = create("a", "a", "1");
ExplodedSmp e2 = create("a", "b", "1");
List es = list(e1, e2, e3);
- is(es, 2, "c");
+ is(es, 2, "a");
}
/**
@@ -75,9 +75,9 @@ public class ExplodedSmpTest
@Test(expected = PluginCircularDependencyException.class)
public void testCompareToCyclicDependency()
{
- ExplodedSmp e1 = create("a", "a", "1", "a:c");
- ExplodedSmp e2 = create("a", "b", "1");
- ExplodedSmp e3 = create("a", "c", "1", "a:a");
+ ExplodedSmp e1 = create("a", "1", "c");
+ ExplodedSmp e2 = create("b", "1");
+ ExplodedSmp e3 = create("c", "1", "a");
list(e1, e2, e3);
}
@@ -89,9 +89,9 @@ public class ExplodedSmpTest
@Test
public void testCompareToTransitiveDependencies()
{
- ExplodedSmp e1 = create("a", "a", "1", "a:b");
- ExplodedSmp e2 = create("a", "b", "1");
- ExplodedSmp e3 = create("a", "c", "1", "a:a");
+ ExplodedSmp e1 = create("a", "1", "b");
+ ExplodedSmp e2 = create("b", "1");
+ ExplodedSmp e3 = create("c", "1", "a");
List es = list(e1, e2, e3);
@@ -107,9 +107,9 @@ public class ExplodedSmpTest
@Test
public void testMultipleDependencies()
{
- ExplodedSmp e1 = create("a", "a", "1", "a:b", "a:c");
- ExplodedSmp e2 = create("a", "b", "1", "a:c");
- ExplodedSmp e3 = create("a", "c", "1");
+ ExplodedSmp e1 = create("a", "1", "b", "c");
+ ExplodedSmp e2 = create("b", "1", "c");
+ ExplodedSmp e3 = create("c", "1");
List es = list(e1, e2, e3);
is(es, 2, "a");
@@ -119,20 +119,18 @@ public class ExplodedSmpTest
* Method description
*
*
- * @param groupId
- * @param artifactId
+ * @param name
* @param version
* @param dependencies
*
* @return
*/
- private ExplodedSmp create(String groupId, String artifactId, String version,
+ private ExplodedSmp create(String name, String version,
String... dependencies)
{
PluginInformation info = new PluginInformation();
- info.setGroupId(groupId);
- info.setArtifactId(artifactId);
+ info.setName(name);
info.setVersion(version);
Plugin plugin = new Plugin(2, info, null, null, false,
@@ -170,6 +168,6 @@ public class ExplodedSmpTest
*/
private void is(List es, int p, String a)
{
- assertEquals(a, es.get(p).getPlugin().getInformation().getArtifactId());
+ assertEquals(a, es.get(p).getPlugin().getInformation().getName());
}
}
diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterDtoMapperTest.java
new file mode 100644
index 0000000000..66a90255b3
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginCenterDtoMapperTest.java
@@ -0,0 +1,86 @@
+package sonia.scm.plugin;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static sonia.scm.plugin.PluginCenterDto.Plugin;
+import static sonia.scm.plugin.PluginCenterDto.*;
+
+class PluginCenterDtoMapperTest {
+
+ @Test
+ void shouldMapSinglePlugin() {
+ Plugin plugin = new Plugin(
+ "scm-hitchhiker-plugin",
+ "SCM Hitchhiker Plugin",
+ "plugin for hitchhikers",
+ "Travel",
+ "2.0.0",
+ "trillian",
+ "http://avatar.url",
+ "555000444",
+ new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
+ new Dependency("scm-review-plugin"),
+ new HashMap<>());
+
+ PluginInformation result = PluginCenterDtoMapper.map(Collections.singletonList(plugin)).iterator().next();
+
+ assertThat(result.getAuthor()).isEqualTo(plugin.getAuthor());
+ assertThat(result.getCategory()).isEqualTo(plugin.getCategory());
+ assertThat(result.getVersion()).isEqualTo(plugin.getVersion());
+ assertThat(result.getCondition().getArch()).isEqualTo(plugin.getConditions().getArch());
+ assertThat(result.getCondition().getMinVersion()).isEqualTo(plugin.getConditions().getMinVersion());
+ assertThat(result.getCondition().getOs().iterator().next()).isEqualTo(plugin.getConditions().getOs().iterator().next());
+ assertThat(result.getDescription()).isEqualTo(plugin.getDescription());
+ assertThat(result.getName()).isEqualTo(plugin.getName());
+ }
+
+ @Test
+ void shouldMapMultiplePlugins() {
+ Plugin plugin1 = new Plugin(
+ "scm-review-plugin",
+ "SCM Hitchhiker Plugin",
+ "plugin for hitchhikers",
+ "Travel",
+ "2.1.0",
+ "trillian",
+ "https://avatar.url",
+ "12345678aa",
+ new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
+ new Dependency("scm-review-plugin"),
+ new HashMap<>());
+
+ Plugin plugin2 = new Plugin(
+ "scm-hitchhiker-plugin",
+ "SCM Hitchhiker Plugin",
+ "plugin for hitchhikers",
+ "Travel",
+ "2.0.0",
+ "dent",
+ "http://avatar.url",
+ "555000444",
+ new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
+ new Dependency("scm-review-plugin"),
+ new HashMap<>());
+
+ Set resultSet = PluginCenterDtoMapper.map(Arrays.asList(plugin1, plugin2));
+
+ List pluginsList = new ArrayList<>(resultSet);
+
+ PluginInformation pluginInformation1 = pluginsList.get(1);
+ PluginInformation pluginInformation2 = pluginsList.get(0);
+
+ assertThat(pluginInformation1.getAuthor()).isEqualTo(plugin1.getAuthor());
+ assertThat(pluginInformation1.getVersion()).isEqualTo(plugin1.getVersion());
+ assertThat(pluginInformation2.getAuthor()).isEqualTo(plugin2.getAuthor());
+ assertThat(pluginInformation2.getVersion()).isEqualTo(plugin2.getVersion());
+ assertThat(resultSet.size()).isEqualTo(2);
+ }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java
index 8b352b8e68..87e9cbf7b7 100644
--- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java
@@ -71,37 +71,37 @@ public class PluginProcessorTest
/** Field description */
private static final PluginResource PLUGIN_A =
new PluginResource("sonia/scm/plugin/scm-a-plugin.smp", "scm-a-plugin.smp",
- "sonia.scm.plugins:scm-a-plugin:1.0.0-SNAPSHOT");
+ "scm-a-plugin:1.0.0-SNAPSHOT");
/** Field description */
private static final PluginResource PLUGIN_B =
new PluginResource("sonia/scm/plugin/scm-b-plugin.smp", "scm-b-plugin.smp",
- "sonia.scm.plugins:scm-b-plugin:1.0.0-SNAPSHOT");
+ "scm-b-plugin:1.0.0-SNAPSHOT");
/** Field description */
private static final PluginResource PLUGIN_C =
new PluginResource("sonia/scm/plugin/scm-c-plugin.smp", "scm-c-plugin.smp",
- "sonia.scm.plugins:scm-c-plugin:1.0.0-SNAPSHOT");
+ "scm-c-plugin:1.0.0-SNAPSHOT");
/** Field description */
private static final PluginResource PLUGIN_D =
new PluginResource("sonia/scm/plugin/scm-d-plugin.smp", "scm-d-plugin.smp",
- "sonia.scm.plugins:scm-d-plugin:1.0.0-SNAPSHOT");
+ "scm-d-plugin:1.0.0-SNAPSHOT");
/** Field description */
private static final PluginResource PLUGIN_E =
new PluginResource("sonia/scm/plugin/scm-e-plugin.smp", "scm-e-plugin.smp",
- "sonia.scm.plugins:scm-e-plugin:1.0.0-SNAPSHOT");
+ "scm-e-plugin:1.0.0-SNAPSHOT");
/** Field description */
private static final PluginResource PLUGIN_F_1_0_0 =
new PluginResource("sonia/scm/plugin/scm-f-plugin-1.0.0.smp",
- "scm-f-plugin.smp", "sonia.scm.plugins:scm-f-plugin:1.0.0");
+ "scm-f-plugin.smp", "scm-f-plugin:1.0.0");
/** Field description */
private static final PluginResource PLUGIN_F_1_0_1 =
new PluginResource("sonia/scm/plugin/scm-f-plugin-1.0.1.smp",
- "scm-f-plugin.smp", "sonia.scm.plugins:scm-f-plugin:1.0.1");
+ "scm-f-plugin.smp", "scm-f-plugin:1.0.1");
//~--- methods --------------------------------------------------------------
diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginTreeTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginTreeTest.java
index 06d6c1732c..0115f4510e 100644
--- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginTreeTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginTreeTest.java
@@ -71,7 +71,7 @@ public class PluginTreeTest
{
PluginCondition condition = new PluginCondition("999",
new ArrayList(), "hit");
- Plugin plugin = new Plugin(2, createInfo("a", "b", "1"), null, condition,
+ Plugin plugin = new Plugin(2, createInfo("a", "1"), null, condition,
false, null);
ExplodedSmp smp = createSmp(plugin);
@@ -102,7 +102,7 @@ public class PluginTreeTest
List smps = createSmps("a", "b", "c");
List nodes = unwrapIds(new PluginTree(smps).getRootNodes());
- assertThat(nodes, containsInAnyOrder("a:a", "b:b", "c:c"));
+ assertThat(nodes, containsInAnyOrder("a", "b", "c"));
}
/**
@@ -114,7 +114,7 @@ public class PluginTreeTest
@Test(expected = PluginException.class)
public void testScmVersion() throws IOException
{
- Plugin plugin = new Plugin(1, createInfo("a", "b", "1"), null, null, false,
+ Plugin plugin = new Plugin(1, createInfo("a", "1"), null, null, false,
null);
ExplodedSmp smp = createSmp(plugin);
@@ -141,34 +141,32 @@ public class PluginTreeTest
PluginTree tree = new PluginTree(smps);
List rootNodes = tree.getRootNodes();
- assertThat(unwrapIds(rootNodes), containsInAnyOrder("a:a"));
+ assertThat(unwrapIds(rootNodes), containsInAnyOrder("a"));
PluginNode a = rootNodes.get(0);
- assertThat(unwrapIds(a.getChildren()), containsInAnyOrder("b:b", "c:c"));
+ assertThat(unwrapIds(a.getChildren()), containsInAnyOrder("b", "c"));
- PluginNode b = a.getChild("b:b");
+ PluginNode b = a.getChild("b");
- assertThat(unwrapIds(b.getChildren()), containsInAnyOrder("c:c"));
+ assertThat(unwrapIds(b.getChildren()), containsInAnyOrder("c"));
}
/**
* Method description
*
*
- * @param groupId
- * @param artifactId
+ * @param name
* @param version
*
* @return
*/
- private PluginInformation createInfo(String groupId, String artifactId,
+ private PluginInformation createInfo(String name,
String version)
{
PluginInformation info = new PluginInformation();
- info.setGroupId(groupId);
- info.setArtifactId(artifactId);
+ info.setName(name);
info.setVersion(version);
return info;
@@ -201,7 +199,7 @@ public class PluginTreeTest
*/
private ExplodedSmp createSmp(String name) throws IOException
{
- return createSmp(new Plugin(2, createInfo(name, name, "1.0.0"), null, null,
+ return createSmp(new Plugin(2, createInfo(name, "1.0.0"), null, null,
false, null));
}
@@ -224,10 +222,10 @@ public class PluginTreeTest
for (String d : dependencies)
{
- dependencySet.add(d.concat(":").concat(d));
+ dependencySet.add(d);
}
- Plugin plugin = new Plugin(2, createInfo(name, name, "1"), null, null,
+ Plugin plugin = new Plugin(2, createInfo(name, "1"), null, null,
false, dependencySet);
return createSmp(plugin);
diff --git a/scm-webapp/src/test/java/sonia/scm/update/MigrationWizardServletTest.java b/scm-webapp/src/test/java/sonia/scm/update/MigrationWizardServletTest.java
index 9dbe00059e..683c446af7 100644
--- a/scm-webapp/src/test/java/sonia/scm/update/MigrationWizardServletTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/update/MigrationWizardServletTest.java
@@ -5,8 +5,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
-import sonia.scm.update.repository.MigrationStrategy;
import sonia.scm.update.repository.DefaultMigrationStrategyDAO;
+import sonia.scm.update.repository.MigrationStrategy;
import sonia.scm.update.repository.V1Repository;
import sonia.scm.update.repository.XmlRepositoryV1UpdateStep;
diff --git a/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java
index 73c7fe6aca..6ab32f3397 100644
--- a/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java
@@ -11,10 +11,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.SCMContextProvider;
import sonia.scm.security.AssignedPermission;
import sonia.scm.store.ConfigurationEntryStore;
-import sonia.scm.store.ConfigurationEntryStoreFactory;
-import sonia.scm.store.InMemoryConfigurationEntryStore;
import sonia.scm.store.InMemoryConfigurationEntryStoreFactory;
-import sonia.scm.update.security.XmlSecurityV1UpdateStep;
import javax.xml.bind.JAXBException;
import java.io.IOException;
diff --git a/scm-webapp/src/test/java/sonia/scm/web/protocol/NamespaceAndNameFromPathExtractorTest.java b/scm-webapp/src/test/java/sonia/scm/web/protocol/NamespaceAndNameFromPathExtractorTest.java
index 5ac8f37c01..5481e2e2a5 100644
--- a/scm-webapp/src/test/java/sonia/scm/web/protocol/NamespaceAndNameFromPathExtractorTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/web/protocol/NamespaceAndNameFromPathExtractorTest.java
@@ -6,14 +6,12 @@ import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryType;
-import javax.inject.Inject;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
diff --git a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-b-plugin.smp b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-b-plugin.smp
index 4106b97945..a70205e3eb 100644
Binary files a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-b-plugin.smp and b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-b-plugin.smp differ
diff --git a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-c-plugin.smp b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-c-plugin.smp
index aee452fac4..b80169b9b5 100644
Binary files a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-c-plugin.smp and b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-c-plugin.smp differ
diff --git a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-d-plugin.smp b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-d-plugin.smp
index ec5c816c12..68509a2ee8 100644
Binary files a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-d-plugin.smp and b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-d-plugin.smp differ
diff --git a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-e-plugin.smp b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-e-plugin.smp
index 68b1facafe..702b5c344f 100644
Binary files a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-e-plugin.smp and b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-e-plugin.smp differ
diff --git a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.0.smp b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.0.smp
index cfcaae8427..8f2758f962 100644
Binary files a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.0.smp and b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.0.smp differ
diff --git a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.1.smp b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.1.smp
index 7132f78277..b5cac4f1e3 100644
Binary files a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.1.smp and b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-f-plugin-1.0.1.smp differ