mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-08 22:45:45 +01:00
Merged in feature/install_plugins (pull request #299)
Feature/install plugins
This commit is contained in:
32
scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java
Normal file
32
scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
public class AvailablePlugin implements Plugin {
|
||||
|
||||
private final AvailablePluginDescriptor pluginDescriptor;
|
||||
private final boolean pending;
|
||||
|
||||
public AvailablePlugin(AvailablePluginDescriptor pluginDescriptor) {
|
||||
this(pluginDescriptor, false);
|
||||
}
|
||||
|
||||
private AvailablePlugin(AvailablePluginDescriptor pluginDescriptor, boolean pending) {
|
||||
this.pluginDescriptor = pluginDescriptor;
|
||||
this.pending = pending;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AvailablePluginDescriptor getDescriptor() {
|
||||
return pluginDescriptor;
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
return pending;
|
||||
}
|
||||
|
||||
public AvailablePlugin install() {
|
||||
Preconditions.checkState(!pending, "installation is already pending");
|
||||
return new AvailablePlugin(pluginDescriptor, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class AvailablePluginDescriptor implements PluginDescriptor {
|
||||
|
||||
private final PluginInformation information;
|
||||
private final PluginCondition condition;
|
||||
private final Set<String> dependencies;
|
||||
private final String url;
|
||||
private final String checksum;
|
||||
|
||||
public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set<String> dependencies, String url, String checksum) {
|
||||
this.information = information;
|
||||
this.condition = condition;
|
||||
this.dependencies = dependencies;
|
||||
this.url = url;
|
||||
this.checksum = checksum;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public Optional<String> getChecksum() {
|
||||
return Optional.ofNullable(checksum);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginInformation getInformation() {
|
||||
return information;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginCondition getCondition() {
|
||||
return condition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
}
|
||||
@@ -36,27 +36,27 @@ package sonia.scm.plugin;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Wrapper for a {@link Plugin}. The wrapper holds the directory,
|
||||
* Wrapper for a {@link InstalledPluginDescriptor}. The wrapper holds the directory,
|
||||
* {@link ClassLoader} and {@link WebResourceLoader} of a plugin.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class PluginWrapper
|
||||
public final class InstalledPlugin implements Plugin
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs a new plugin wrapper.
|
||||
*
|
||||
* @param plugin wrapped plugin
|
||||
* @param descriptor wrapped plugin
|
||||
* @param classLoader plugin class loader
|
||||
* @param webResourceLoader web resource loader
|
||||
* @param directory plugin directory
|
||||
*/
|
||||
public PluginWrapper(Plugin plugin, ClassLoader classLoader,
|
||||
public InstalledPlugin(InstalledPluginDescriptor descriptor, ClassLoader classLoader,
|
||||
WebResourceLoader webResourceLoader, Path directory)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
this.descriptor = descriptor;
|
||||
this.classLoader = classLoader;
|
||||
this.webResourceLoader = webResourceLoader;
|
||||
this.directory = directory;
|
||||
@@ -94,18 +94,19 @@ public final class PluginWrapper
|
||||
*/
|
||||
public String getId()
|
||||
{
|
||||
return plugin.getInformation().getId();
|
||||
return descriptor.getInformation().getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin.
|
||||
* Returns the plugin descriptor.
|
||||
*
|
||||
*
|
||||
* @return plugin
|
||||
* @return plugin descriptor
|
||||
*/
|
||||
public Plugin getPlugin()
|
||||
@Override
|
||||
public InstalledPluginDescriptor getDescriptor()
|
||||
{
|
||||
return plugin;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,7 +129,7 @@ public final class PluginWrapper
|
||||
private final Path directory;
|
||||
|
||||
/** plugin */
|
||||
private final Plugin plugin;
|
||||
private final InstalledPluginDescriptor descriptor;
|
||||
|
||||
/** plugin web resource loader */
|
||||
private final WebResourceLoader webResourceLoader;
|
||||
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* 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.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
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.util.Set;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@XmlRootElement(name = "plugin")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public final class InstalledPluginDescriptor extends ScmModule implements PluginDescriptor
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*/
|
||||
InstalledPluginDescriptor() {}
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param scmVersion
|
||||
* @param information
|
||||
* @param resources
|
||||
* @param condition
|
||||
* @param childFirstClassLoader
|
||||
* @param dependencies
|
||||
*/
|
||||
public InstalledPluginDescriptor(int scmVersion, PluginInformation information,
|
||||
PluginResources resources, PluginCondition condition,
|
||||
boolean childFirstClassLoader, Set<String> dependencies)
|
||||
{
|
||||
this.scmVersion = scmVersion;
|
||||
this.information = information;
|
||||
this.resources = resources;
|
||||
this.condition = condition;
|
||||
this.childFirstClassLoader = childFirstClassLoader;
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param obj
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getClass() != obj.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final InstalledPluginDescriptor other = (InstalledPluginDescriptor) obj;
|
||||
|
||||
return Objects.equal(scmVersion, other.scmVersion)
|
||||
&& Objects.equal(condition, other.condition)
|
||||
&& Objects.equal(information, other.information)
|
||||
&& Objects.equal(resources, other.resources)
|
||||
&& Objects.equal(childFirstClassLoader, other.childFirstClassLoader)
|
||||
&& Objects.equal(dependencies, other.dependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hashCode(scmVersion, condition, information, resources,
|
||||
childFirstClassLoader, dependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
//J-
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("scmVersion", scmVersion)
|
||||
.add("condition", condition)
|
||||
.add("information", information)
|
||||
.add("resources", resources)
|
||||
.add("childFirstClassLoader", childFirstClassLoader)
|
||||
.add("dependencies", dependencies)
|
||||
.toString();
|
||||
//J+
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public PluginCondition getCondition()
|
||||
{
|
||||
return condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Override
|
||||
public Set<String> getDependencies()
|
||||
{
|
||||
if (dependencies == null)
|
||||
{
|
||||
dependencies = ImmutableSet.of();
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public PluginInformation getInformation()
|
||||
{
|
||||
return information;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public PluginResources getResources()
|
||||
{
|
||||
return resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getScmVersion()
|
||||
{
|
||||
return scmVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isChildFirstClassLoader()
|
||||
{
|
||||
return childFirstClassLoader;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
@XmlElement(name = "child-first-classloader")
|
||||
private boolean childFirstClassLoader;
|
||||
|
||||
/** Field description */
|
||||
@XmlElement(name = "conditions")
|
||||
private PluginCondition condition;
|
||||
|
||||
/** Field description */
|
||||
@XmlElement(name = "dependency")
|
||||
@XmlElementWrapper(name = "dependencies")
|
||||
private Set<String> dependencies;
|
||||
|
||||
/** Field description */
|
||||
@XmlElement(name = "information")
|
||||
private PluginInformation information;
|
||||
|
||||
/** Field description */
|
||||
private PluginResources resources;
|
||||
|
||||
/** Field description */
|
||||
@XmlElement(name = "scm-version")
|
||||
private int scmVersion = 1;
|
||||
}
|
||||
@@ -1,255 +1,6 @@
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* 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 --------------------------------------------------------
|
||||
public interface Plugin {
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
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.util.Set;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@XmlRootElement
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public final class Plugin extends ScmModule
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*/
|
||||
Plugin() {}
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param scmVersion
|
||||
* @param information
|
||||
* @param resources
|
||||
* @param condition
|
||||
* @param childFirstClassLoader
|
||||
* @param dependencies
|
||||
*/
|
||||
public Plugin(int scmVersion, PluginInformation information,
|
||||
PluginResources resources, PluginCondition condition,
|
||||
boolean childFirstClassLoader, Set<String> dependencies)
|
||||
{
|
||||
this.scmVersion = scmVersion;
|
||||
this.information = information;
|
||||
this.resources = resources;
|
||||
this.condition = condition;
|
||||
this.childFirstClassLoader = childFirstClassLoader;
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param obj
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getClass() != obj.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final Plugin other = (Plugin) obj;
|
||||
|
||||
return Objects.equal(scmVersion, other.scmVersion)
|
||||
&& Objects.equal(condition, other.condition)
|
||||
&& Objects.equal(information, other.information)
|
||||
&& Objects.equal(resources, other.resources)
|
||||
&& Objects.equal(childFirstClassLoader, other.childFirstClassLoader)
|
||||
&& Objects.equal(dependencies, other.dependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hashCode(scmVersion, condition, information, resources,
|
||||
childFirstClassLoader, dependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
//J-
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("scmVersion", scmVersion)
|
||||
.add("condition", condition)
|
||||
.add("information", information)
|
||||
.add("resources", resources)
|
||||
.add("childFirstClassLoader", childFirstClassLoader)
|
||||
.add("dependencies", dependencies)
|
||||
.toString();
|
||||
//J+
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public PluginCondition getCondition()
|
||||
{
|
||||
return condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public Set<String> getDependencies()
|
||||
{
|
||||
if (dependencies == null)
|
||||
{
|
||||
dependencies = ImmutableSet.of();
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public PluginInformation getInformation()
|
||||
{
|
||||
return information;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public PluginResources getResources()
|
||||
{
|
||||
return resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getScmVersion()
|
||||
{
|
||||
return scmVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isChildFirstClassLoader()
|
||||
{
|
||||
return childFirstClassLoader;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
@XmlElement(name = "child-first-classloader")
|
||||
private boolean childFirstClassLoader;
|
||||
|
||||
/** Field description */
|
||||
@XmlElement(name = "conditions")
|
||||
private PluginCondition condition;
|
||||
|
||||
/** Field description */
|
||||
@XmlElement(name = "dependency")
|
||||
@XmlElementWrapper(name = "dependencies")
|
||||
private Set<String> dependencies;
|
||||
|
||||
/** Field description */
|
||||
private PluginInformation information;
|
||||
|
||||
/** Field description */
|
||||
private PluginResources resources;
|
||||
|
||||
/** Field description */
|
||||
@XmlElement(name = "scm-version")
|
||||
private int scmVersion = 1;
|
||||
PluginDescriptor getDescriptor();
|
||||
}
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* 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;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@XmlRootElement(name = "plugin-center")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class PluginCenter implements Serializable
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
private static final long serialVersionUID = -6414175308610267397L;
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<PluginInformation> getPlugins()
|
||||
{
|
||||
return plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<PluginRepository> getRepositories()
|
||||
{
|
||||
return repositories;
|
||||
}
|
||||
|
||||
//~--- set methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param plugins
|
||||
*/
|
||||
public void setPlugins(Set<PluginInformation> plugins)
|
||||
{
|
||||
this.plugins = plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param repositories
|
||||
*/
|
||||
public void setRepositories(Set<PluginRepository> repositories)
|
||||
{
|
||||
this.repositories = repositories;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
@XmlElement(name = "plugin")
|
||||
@XmlElementWrapper(name = "plugins")
|
||||
private Set<PluginInformation> plugins = new HashSet<PluginInformation>();
|
||||
|
||||
/** Field description */
|
||||
@XmlElement(name = "repository")
|
||||
@XmlElementWrapper(name = "repositories")
|
||||
private Set<PluginRepository> repositories = new HashSet<PluginRepository>();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface PluginDescriptor {
|
||||
|
||||
PluginInformation getInformation();
|
||||
|
||||
PluginCondition getCondition();
|
||||
|
||||
Set<String> getDependencies();
|
||||
|
||||
}
|
||||
@@ -70,8 +70,6 @@ public class PluginInformation implements PermissionObject, Validateable, Clonea
|
||||
private String author;
|
||||
private String category;
|
||||
private String avatarUrl;
|
||||
private PluginCondition condition;
|
||||
private PluginState state;
|
||||
|
||||
@Override
|
||||
public PluginInformation clone() {
|
||||
@@ -83,10 +81,6 @@ public class PluginInformation implements PermissionObject, Validateable, Clonea
|
||||
clone.setAuthor(author);
|
||||
clone.setCategory(category);
|
||||
clone.setAvatarUrl(avatarUrl);
|
||||
clone.setState(state);
|
||||
if (condition != null) {
|
||||
clone.setCondition(condition.clone());
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* 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 sonia.scm.util.Util;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 1.6
|
||||
*/
|
||||
public class PluginInformationComparator
|
||||
implements Comparator<PluginInformation>, Serializable
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
public static final PluginInformationComparator INSTANCE =
|
||||
new PluginInformationComparator();
|
||||
|
||||
/** Field description */
|
||||
private static final long serialVersionUID = -8339752498853225668L;
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param plugin
|
||||
* @param other
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public int compare(PluginInformation plugin, PluginInformation other)
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
result = Util.compare(plugin.getName(), other.getName());
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ public interface PluginLoader
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Collection<PluginWrapper> getInstalledPlugins();
|
||||
public Collection<InstalledPlugin> getInstalledPlugins();
|
||||
|
||||
/**
|
||||
* Returns a {@link ClassLoader} which is able to load classes and resources
|
||||
|
||||
@@ -33,113 +33,56 @@
|
||||
|
||||
package sonia.scm.plugin;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* The plugin manager is responsible for plugin related tasks, such as install, uninstall or updating.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public interface PluginManager
|
||||
{
|
||||
public interface PluginManager {
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
* Returns the available plugin with the given name.
|
||||
* @param name of plugin
|
||||
* @return optional available plugin.
|
||||
*/
|
||||
public void clearCache();
|
||||
Optional<AvailablePlugin> getAvailable(String name);
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
* Returns the installed plugin with the given name.
|
||||
* @param name of plugin
|
||||
* @return optional installed plugin.
|
||||
*/
|
||||
public void install(String id);
|
||||
Optional<InstalledPlugin> getInstalled(String name);
|
||||
|
||||
|
||||
/**
|
||||
* Installs a plugin package from a inputstream.
|
||||
* Returns all installed plugins.
|
||||
*
|
||||
*
|
||||
* @param packageStream package input stream
|
||||
*
|
||||
* @throws IOException
|
||||
* @since 1.21
|
||||
* @return a list of installed plugins.
|
||||
*/
|
||||
public void installPackage(InputStream packageStream) throws IOException;
|
||||
List<InstalledPlugin> getInstalled();
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Returns all available plugins. The list contains the plugins which are loaded from the plugin center, but without
|
||||
* the installed plugins.
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
* @return a list of available plugins.
|
||||
*/
|
||||
public void uninstall(String id);
|
||||
List<AvailablePlugin> getAvailable();
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Installs the plugin with the given name from the list of available plugins.
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
* @param name plugin name
|
||||
* @param restartAfterInstallation restart context after plugin installation
|
||||
*/
|
||||
public void update(String id);
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
void install(String name, boolean restartAfterInstallation);
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
*
|
||||
* @return
|
||||
* Install all pending plugins and restart the scm context.
|
||||
*/
|
||||
public PluginInformation get(String id);
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param filter
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Collection<PluginInformation> get(Predicate<PluginInformation> filter);
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Collection<PluginInformation> getAll();
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Collection<PluginInformation> getAvailable();
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Collection<PluginInformation> getAvailableUpdates();
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Collection<PluginInformation> getInstalled();
|
||||
void installPendingAndRestart();
|
||||
}
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* 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.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class PluginRepository implements Serializable
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
private static final long serialVersionUID = -9504354306304731L;
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*/
|
||||
PluginRepository() {}
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
* @param url
|
||||
*/
|
||||
public PluginRepository(String id, String url)
|
||||
{
|
||||
this.id = id;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param obj
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getClass() != obj.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final PluginRepository other = (PluginRepository) obj;
|
||||
|
||||
return Objects.equal(id, other.id) && Objects.equal(url, other.url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hashCode(id, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return MoreObjects.toStringHelper(this).add("id", id).add("url",
|
||||
url).toString();
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getUrl()
|
||||
{
|
||||
return url;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private String id;
|
||||
|
||||
/** Field description */
|
||||
private String url;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* 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;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public enum PluginState
|
||||
{
|
||||
CORE(100), AVAILABLE(60), INSTALLED(80), NEWER_VERSION_INSTALLED(20),
|
||||
UPDATE_AVAILABLE(40);
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param compareValue
|
||||
*/
|
||||
private PluginState(int compareValue)
|
||||
{
|
||||
this.compareValue = compareValue;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @since 1.6
|
||||
* @return
|
||||
*/
|
||||
public int getCompareValue()
|
||||
{
|
||||
return compareValue;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private final int compareValue;
|
||||
}
|
||||
@@ -65,7 +65,7 @@ public final class Plugins
|
||||
{
|
||||
try
|
||||
{
|
||||
context = JAXBContext.newInstance(Plugin.class, ScmModule.class);
|
||||
context = JAXBContext.newInstance(InstalledPluginDescriptor.class, ScmModule.class);
|
||||
}
|
||||
catch (JAXBException ex)
|
||||
{
|
||||
@@ -91,7 +91,7 @@ public final class Plugins
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Plugin parsePluginDescriptor(Path path)
|
||||
public static InstalledPluginDescriptor parsePluginDescriptor(Path path)
|
||||
{
|
||||
return parsePluginDescriptor(Files.asByteSource(path.toFile()));
|
||||
}
|
||||
@@ -104,15 +104,15 @@ public final class Plugins
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Plugin parsePluginDescriptor(ByteSource data)
|
||||
public static InstalledPluginDescriptor parsePluginDescriptor(ByteSource data)
|
||||
{
|
||||
Preconditions.checkNotNull(data, "data parameter is required");
|
||||
|
||||
Plugin plugin;
|
||||
InstalledPluginDescriptor plugin;
|
||||
|
||||
try (InputStream stream = data.openStream())
|
||||
{
|
||||
plugin = (Plugin) context.createUnmarshaller().unmarshal(stream);
|
||||
plugin = (InstalledPluginDescriptor) context.createUnmarshaller().unmarshal(stream);
|
||||
}
|
||||
catch (JAXBException ex)
|
||||
{
|
||||
|
||||
@@ -206,7 +206,7 @@ public final class SmpArchive
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public Plugin getPlugin() throws IOException
|
||||
public InstalledPluginDescriptor getPlugin() throws IOException
|
||||
{
|
||||
if (plugin == null)
|
||||
{
|
||||
@@ -245,9 +245,9 @@ public final class SmpArchive
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private Plugin createPlugin() throws IOException
|
||||
private InstalledPluginDescriptor createPlugin() throws IOException
|
||||
{
|
||||
Plugin p = null;
|
||||
InstalledPluginDescriptor p = null;
|
||||
NonClosingZipInputStream zis = null;
|
||||
|
||||
try
|
||||
@@ -412,5 +412,5 @@ public final class SmpArchive
|
||||
private final ByteSource archive;
|
||||
|
||||
/** Field description */
|
||||
private Plugin plugin;
|
||||
private InstalledPluginDescriptor plugin;
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* 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.google.common.base.Predicate;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class StatePluginPredicate implements Predicate<PluginInformation>
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param state
|
||||
*/
|
||||
public StatePluginPredicate(PluginState state)
|
||||
{
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param plugin
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean apply(PluginInformation plugin)
|
||||
{
|
||||
return state == plugin.getState();
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private final PluginState state;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AvailablePluginTest {
|
||||
|
||||
@Mock
|
||||
private AvailablePluginDescriptor descriptor;
|
||||
|
||||
@Test
|
||||
void shouldReturnNewPendingPluginOnInstall() {
|
||||
AvailablePlugin plugin = new AvailablePlugin(descriptor);
|
||||
assertThat(plugin.isPending()).isFalse();
|
||||
|
||||
AvailablePlugin installed = plugin.install();
|
||||
assertThat(installed.isPending()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalStateExceptionIfAlreadyPending() {
|
||||
AvailablePlugin plugin = new AvailablePlugin(descriptor).install();
|
||||
assertThrows(IllegalStateException.class, () -> plugin.install());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -113,7 +113,7 @@ public class SmpArchiveTest
|
||||
public void testGetPlugin() throws IOException
|
||||
{
|
||||
File archive = createArchive("sonia.sample", "1.0");
|
||||
Plugin plugin = SmpArchive.create(archive).getPlugin();
|
||||
InstalledPluginDescriptor plugin = SmpArchive.create(archive).getPlugin();
|
||||
|
||||
assertNotNull(plugin);
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ type Props = {
|
||||
contentRight?: React.Node,
|
||||
footerLeft: React.Node,
|
||||
footerRight: React.Node,
|
||||
link: string,
|
||||
link?: string,
|
||||
action?: () => void,
|
||||
|
||||
// context props
|
||||
classes: any
|
||||
@@ -54,9 +55,11 @@ type Props = {
|
||||
|
||||
class CardColumn extends React.Component<Props> {
|
||||
createLink = () => {
|
||||
const { link } = this.props;
|
||||
const { link, action } = this.props;
|
||||
if (link) {
|
||||
return <Link className="overlay-column" to={link} />;
|
||||
} else if (action) {
|
||||
return <a className="overlay-column" onClick={e => {e.preventDefault(); action();}} href="#" />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ class ButtonGroup extends React.Component<Props> {
|
||||
const childWrapper = [];
|
||||
React.Children.forEach(children, child => {
|
||||
if (child) {
|
||||
childWrapper.push(<p className="control">{child}</p>);
|
||||
childWrapper.push(<p className="control" key={childWrapper.length}>{child}</p>);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//@flow
|
||||
import type {Collection, Links} from "./hal";
|
||||
|
||||
|
||||
export type Plugin = {
|
||||
name: string,
|
||||
version: string,
|
||||
@@ -10,6 +9,8 @@ export type Plugin = {
|
||||
author: string,
|
||||
category: string,
|
||||
avatarUrl: string,
|
||||
pending: boolean,
|
||||
dependencies: string[],
|
||||
_links: Links
|
||||
};
|
||||
|
||||
|
||||
@@ -29,7 +29,23 @@
|
||||
"installedNavLink": "Installiert",
|
||||
"availableNavLink": "Verfügbar"
|
||||
},
|
||||
"noPlugins": "Keine Plugins gefunden."
|
||||
"installPending": "Austehende Plugins installieren",
|
||||
"noPlugins": "Keine Plugins gefunden.",
|
||||
"modal": {
|
||||
"title": "{{name}} Plugin installieren",
|
||||
"restart": "Neustarten um Plugin zu aktivieren",
|
||||
"install": "Installieren",
|
||||
"installAndRestart": "Installieren und Neustarten",
|
||||
"abort": "Abbrechen",
|
||||
"author": "Autor",
|
||||
"version": "Version",
|
||||
"dependencyNotification": "Mit diesem Plugin werden folgende Abhängigkeiten mit installieren wenn sie noch nicht vorhanden sind!",
|
||||
"dependencies": "Abhängigkeiten",
|
||||
"successNotification": "Das Plugin wurde erfolgreich installiert. Um Änderungen an der UI zu sehen, muss die Seite neu geladen werden:",
|
||||
"reload": "jetzt new laden",
|
||||
"restartNotification": "Der SCM-Manager Kontext sollte nur neu gestartet werden, wenn aktuell niemand damit arbeitet.",
|
||||
"installPending": "Die folgenden Plugins werden installiert und anschließend wir der SCM-Manager Kontext neu gestartet."
|
||||
}
|
||||
},
|
||||
"repositoryRole": {
|
||||
"navLink": "Berechtigungsrollen",
|
||||
|
||||
@@ -29,7 +29,23 @@
|
||||
"installedNavLink": "Installed",
|
||||
"availableNavLink": "Available"
|
||||
},
|
||||
"noPlugins": "No plugins found."
|
||||
"installPending": "Install pending plugins",
|
||||
"noPlugins": "No plugins found.",
|
||||
"modal": {
|
||||
"title": "Install {{name}} Plugin",
|
||||
"restart": "Restart to activate",
|
||||
"install": "Install",
|
||||
"installAndRestart": "Install and Restart",
|
||||
"abort": "Abort",
|
||||
"author": "Author",
|
||||
"version": "Version",
|
||||
"dependencyNotification": "With this plugin, the following dependencies are installed if they are not available yet!",
|
||||
"dependencies": "Dependencies",
|
||||
"successNotification": "Successful installed plugin. You have to reload the page, to see ui changes:",
|
||||
"reload": "reload now",
|
||||
"restartNotification": "Restarting the scm-manager context, should only be done if no one else is currently working with it.",
|
||||
"installPending": "The following plugins will be installed and after installation the scm-manager context will be restarted."
|
||||
}
|
||||
},
|
||||
"repositoryRole": {
|
||||
"navLink": "Permission Roles",
|
||||
|
||||
68
scm-ui/src/admin/plugins/components/InstallPendingAction.js
Normal file
68
scm-ui/src/admin/plugins/components/InstallPendingAction.js
Normal file
@@ -0,0 +1,68 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { Button } from "@scm-manager/ui-components";
|
||||
import type { PluginCollection } from "@scm-manager/ui-types";
|
||||
import { translate } from "react-i18next";
|
||||
import InstallPendingModal from "./InstallPendingModal";
|
||||
|
||||
type Props = {
|
||||
collection: PluginCollection,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
type State = {
|
||||
showModal: boolean
|
||||
};
|
||||
|
||||
class InstallPendingAction extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showModal: false
|
||||
};
|
||||
}
|
||||
|
||||
openModal = () => {
|
||||
this.setState({
|
||||
showModal: true
|
||||
});
|
||||
};
|
||||
|
||||
closeModal = () => {
|
||||
this.setState({
|
||||
showModal: false
|
||||
});
|
||||
};
|
||||
|
||||
renderModal = () => {
|
||||
const { showModal } = this.state;
|
||||
const { collection } = this.props;
|
||||
if (showModal) {
|
||||
return (
|
||||
<InstallPendingModal
|
||||
collection={collection}
|
||||
onClose={this.closeModal}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<>
|
||||
{this.renderModal()}
|
||||
<Button
|
||||
color="primary"
|
||||
label={t("plugins.installPending")}
|
||||
action={this.openModal}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("admin")(InstallPendingAction);
|
||||
134
scm-ui/src/admin/plugins/components/InstallPendingModal.js
Normal file
134
scm-ui/src/admin/plugins/components/InstallPendingModal.js
Normal file
@@ -0,0 +1,134 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import {
|
||||
apiClient,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
ErrorNotification,
|
||||
Modal,
|
||||
Notification
|
||||
} from "@scm-manager/ui-components";
|
||||
import type { PluginCollection } from "@scm-manager/ui-types";
|
||||
import { translate } from "react-i18next";
|
||||
import waitForRestart from "./waitForRestart";
|
||||
import InstallSuccessNotification from "./InstallSuccessNotification";
|
||||
|
||||
type Props = {
|
||||
onClose: () => void,
|
||||
collection: PluginCollection,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
type State = {
|
||||
loading: boolean,
|
||||
success: boolean,
|
||||
error?: Error
|
||||
};
|
||||
|
||||
class InstallPendingModal extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: false,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
|
||||
renderNotifications = () => {
|
||||
const { t } = this.props;
|
||||
const { error, success } = this.state;
|
||||
if (error) {
|
||||
return <ErrorNotification error={error} />;
|
||||
} else if (success) {
|
||||
return <InstallSuccessNotification />;
|
||||
} else {
|
||||
return (
|
||||
<Notification type="warning">
|
||||
{t("plugins.modal.restartNotification")}
|
||||
</Notification>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
installAndRestart = () => {
|
||||
const { collection } = this.props;
|
||||
this.setState({
|
||||
loading: true
|
||||
});
|
||||
|
||||
apiClient
|
||||
.post(collection._links.installPending.href)
|
||||
.then(waitForRestart)
|
||||
.then(() => {
|
||||
this.setState({
|
||||
success: true,
|
||||
loading: false,
|
||||
error: undefined
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this.setState({
|
||||
success: false,
|
||||
loading: false,
|
||||
error: error
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
renderBody = () => {
|
||||
const { collection, t } = this.props;
|
||||
return (
|
||||
<>
|
||||
<div className="media">
|
||||
<div className="content">
|
||||
<p>{t("plugins.modal.installPending")}</p>
|
||||
<ul>
|
||||
{collection._embedded.plugins
|
||||
.filter(plugin => plugin.pending)
|
||||
.map(plugin => (
|
||||
<li key={plugin.name} className="has-text-weight-bold">
|
||||
{plugin.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="media">{this.renderNotifications()}</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderFooter = () => {
|
||||
const { onClose, t } = this.props;
|
||||
const { loading, error, success } = this.state;
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
color="warning"
|
||||
label={t("plugins.modal.installAndRestart")}
|
||||
loading={loading}
|
||||
action={this.installAndRestart}
|
||||
disabled={error || success}
|
||||
/>
|
||||
<Button label={t("plugins.modal.abort")} action={onClose} />
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { onClose, t } = this.props;
|
||||
return (
|
||||
<Modal
|
||||
title={t("plugins.modal.installAndRestart")}
|
||||
closeFunction={onClose}
|
||||
body={this.renderBody()}
|
||||
footer={this.renderFooter()}
|
||||
active={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("admin")(InstallPendingModal);
|
||||
@@ -0,0 +1,25 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { Notification } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
// context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class InstallSuccessNotification extends React.Component<Props> {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<Notification type="success">
|
||||
{t("plugins.modal.successNotification")}{" "}
|
||||
<a onClick={e => window.location.reload(true)}>
|
||||
{t("plugins.modal.reload")}
|
||||
</a>
|
||||
</Notification>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("admin")(InstallSuccessNotification);
|
||||
30
scm-ui/src/admin/plugins/components/PluginBottomActions.js
Normal file
30
scm-ui/src/admin/plugins/components/PluginBottomActions.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import classNames from "classnames";
|
||||
import injectSheet from "react-jss";
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
border: "2px solid #e9f7fd",
|
||||
padding: "1em 1em",
|
||||
marginTop: "2em",
|
||||
display: "flex",
|
||||
justifyContent: "center"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
children?: React.Node,
|
||||
|
||||
// context props
|
||||
classes: any
|
||||
};
|
||||
|
||||
class PluginBottomActions extends React.Component<Props> {
|
||||
render() {
|
||||
const { children, classes } = this.props;
|
||||
return <div className={classNames(classes.container)}>{children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default injectSheet(styles)(PluginBottomActions);
|
||||
@@ -4,62 +4,115 @@ import injectSheet from "react-jss";
|
||||
import type { Plugin } from "@scm-manager/ui-types";
|
||||
import { CardColumn } from "@scm-manager/ui-components";
|
||||
import PluginAvatar from "./PluginAvatar";
|
||||
import PluginModal from "./PluginModal";
|
||||
import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
plugin: Plugin,
|
||||
refresh: () => void,
|
||||
|
||||
// context props
|
||||
classes: any
|
||||
};
|
||||
|
||||
type State = {
|
||||
showModal: boolean
|
||||
};
|
||||
|
||||
const styles = {
|
||||
link: {
|
||||
pointerEvents: "cursor"
|
||||
cursor: "pointer",
|
||||
pointerEvents: "all"
|
||||
},
|
||||
spinner: {
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0
|
||||
}
|
||||
};
|
||||
|
||||
class PluginEntry extends React.Component<Props> {
|
||||
class PluginEntry extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showModal: false
|
||||
};
|
||||
}
|
||||
|
||||
createAvatar = (plugin: Plugin) => {
|
||||
return <PluginAvatar plugin={plugin} />;
|
||||
};
|
||||
|
||||
createContentRight = (plugin: Plugin) => {
|
||||
toggleModal = () => {
|
||||
this.setState(prevState => ({
|
||||
showModal: !prevState.showModal
|
||||
}));
|
||||
};
|
||||
|
||||
createFooterRight = (plugin: Plugin) => {
|
||||
return <small className="level-item">{plugin.author}</small>;
|
||||
};
|
||||
|
||||
isInstallable = () => {
|
||||
const { plugin } = this.props;
|
||||
return plugin._links && plugin._links.install && plugin._links.install.href;
|
||||
};
|
||||
|
||||
createFooterLeft = () => {
|
||||
const { classes } = this.props;
|
||||
if (plugin._links && plugin._links.install && plugin._links.install.href) {
|
||||
if (this.isInstallable()) {
|
||||
return (
|
||||
<div className={classes.link} onClick={() => console.log(plugin._links.install.href) /*TODO trigger plugin installation*/}>
|
||||
<i className="fas fa-cloud-download-alt fa-2x has-text-info" />
|
||||
</div>
|
||||
<span
|
||||
className={classNames(classes.link, "level-item")}
|
||||
onClick={this.toggleModal}
|
||||
>
|
||||
<i className="fas fa-download has-text-info" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
createFooterLeft = (plugin: Plugin) => {
|
||||
return <small className="level-item">{plugin.author}</small>;
|
||||
};
|
||||
|
||||
createFooterRight = (plugin: Plugin) => {
|
||||
return <p className="level-item">{plugin.version}</p>;
|
||||
createPendingSpinner = () => {
|
||||
const { plugin, classes } = this.props;
|
||||
if (plugin.pending) {
|
||||
return (
|
||||
<span className={classes.spinner}>
|
||||
<i className="fas fa-spinner fa-spin has-text-info" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { plugin } = this.props;
|
||||
const { plugin, refresh } = this.props;
|
||||
const { showModal } = this.state;
|
||||
const avatar = this.createAvatar(plugin);
|
||||
const contentRight = this.createContentRight(plugin);
|
||||
const footerLeft = this.createFooterLeft(plugin);
|
||||
const footerLeft = this.createFooterLeft();
|
||||
const footerRight = this.createFooterRight(plugin);
|
||||
|
||||
// TODO: Add link to plugin page below
|
||||
const modal = showModal ? (
|
||||
<PluginModal
|
||||
plugin={plugin}
|
||||
refresh={refresh}
|
||||
onClose={this.toggleModal}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<CardColumn
|
||||
link="#"
|
||||
action={this.isInstallable() ? this.toggleModal : null}
|
||||
avatar={avatar}
|
||||
title={plugin.displayName ? plugin.displayName : plugin.name}
|
||||
description={plugin.description}
|
||||
contentRight={contentRight}
|
||||
contentRight={this.createPendingSpinner()}
|
||||
footerLeft={footerLeft}
|
||||
footerRight={footerRight}
|
||||
/>
|
||||
{modal}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,15 @@ import type { PluginGroup } from "@scm-manager/ui-types";
|
||||
import PluginEntry from "./PluginEntry";
|
||||
|
||||
type Props = {
|
||||
group: PluginGroup
|
||||
group: PluginGroup,
|
||||
refresh: () => void
|
||||
};
|
||||
|
||||
class PluginGroupEntry extends React.Component<Props> {
|
||||
render() {
|
||||
const { group } = this.props;
|
||||
const entries = group.plugins.map((plugin, index) => {
|
||||
return <PluginEntry plugin={plugin} key={index} />;
|
||||
const { group, refresh } = this.props;
|
||||
const entries = group.plugins.map(plugin => {
|
||||
return <PluginEntry plugin={plugin} key={plugin.name} refresh={refresh} />;
|
||||
});
|
||||
return <CardColumnGroup name={group.name} elements={entries} />;
|
||||
}
|
||||
|
||||
@@ -5,18 +5,19 @@ import PluginGroupEntry from "../components/PluginGroupEntry";
|
||||
import groupByCategory from "./groupByCategory";
|
||||
|
||||
type Props = {
|
||||
plugins: Plugin[]
|
||||
plugins: Plugin[],
|
||||
refresh: () => void
|
||||
};
|
||||
|
||||
class PluginList extends React.Component<Props> {
|
||||
render() {
|
||||
const { plugins } = this.props;
|
||||
const { plugins, refresh } = this.props;
|
||||
|
||||
const groups = groupByCategory(plugins);
|
||||
return (
|
||||
<div className="content is-plugin-page">
|
||||
{groups.map(group => {
|
||||
return <PluginGroupEntry group={group} key={group.name} />;
|
||||
return <PluginGroupEntry group={group} key={group.name} refresh={refresh} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
270
scm-ui/src/admin/plugins/components/PluginModal.js
Normal file
270
scm-ui/src/admin/plugins/components/PluginModal.js
Normal file
@@ -0,0 +1,270 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { compose } from "redux";
|
||||
import { translate } from "react-i18next";
|
||||
import injectSheet from "react-jss";
|
||||
import type { Plugin } from "@scm-manager/ui-types";
|
||||
import {
|
||||
apiClient,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Checkbox,
|
||||
ErrorNotification,
|
||||
Modal,
|
||||
Notification
|
||||
} from "@scm-manager/ui-components";
|
||||
import classNames from "classnames";
|
||||
import waitForRestart from "./waitForRestart";
|
||||
import InstallSuccessNotification from "./InstallSuccessNotification";
|
||||
|
||||
type Props = {
|
||||
plugin: Plugin,
|
||||
refresh: () => void,
|
||||
onClose: () => void,
|
||||
|
||||
// context props
|
||||
classes: any,
|
||||
t: (key: string, params?: Object) => string
|
||||
};
|
||||
|
||||
type State = {
|
||||
success: boolean,
|
||||
restart: boolean,
|
||||
loading: boolean,
|
||||
error?: Error
|
||||
};
|
||||
|
||||
const styles = {
|
||||
userLabelAlignment: {
|
||||
textAlign: "left",
|
||||
marginRight: 0,
|
||||
minWidth: "5.5em"
|
||||
},
|
||||
userFieldFlex: {
|
||||
flexGrow: 4
|
||||
}
|
||||
};
|
||||
|
||||
class PluginModal extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: false,
|
||||
restart: false,
|
||||
success: false
|
||||
};
|
||||
}
|
||||
|
||||
onInstallSuccess = () => {
|
||||
const { restart } = this.state;
|
||||
const { refresh, onClose } = this.props;
|
||||
|
||||
const newState = {
|
||||
loading: false,
|
||||
error: undefined
|
||||
};
|
||||
|
||||
if (restart) {
|
||||
waitForRestart()
|
||||
.then(() => {
|
||||
this.setState({
|
||||
...newState,
|
||||
success: true
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
success: false,
|
||||
error
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.setState(newState, () => {
|
||||
refresh();
|
||||
onClose();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
install = (e: Event) => {
|
||||
const { restart } = this.state;
|
||||
const { plugin } = this.props;
|
||||
this.setState({
|
||||
loading: true
|
||||
});
|
||||
e.preventDefault();
|
||||
apiClient
|
||||
.post(plugin._links.install.href + "?restart=" + restart.toString())
|
||||
.then(this.onInstallSuccess)
|
||||
.catch(error => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
error: error
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
footer = () => {
|
||||
const { onClose, t } = this.props;
|
||||
const { loading, error, restart, success } = this.state;
|
||||
|
||||
let color = "primary";
|
||||
let label = "plugins.modal.install";
|
||||
if (restart) {
|
||||
color = "warning";
|
||||
label = "plugins.modal.installAndRestart";
|
||||
}
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
label={t(label)}
|
||||
color={color}
|
||||
action={this.install}
|
||||
loading={loading}
|
||||
disabled={!!error || success}
|
||||
/>
|
||||
<Button label={t("plugins.modal.abort")} action={onClose} />
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
|
||||
renderDependencies() {
|
||||
const { plugin, classes, t } = this.props;
|
||||
|
||||
let dependencies = null;
|
||||
if (plugin.dependencies && plugin.dependencies.length > 0) {
|
||||
dependencies = (
|
||||
<div className="media">
|
||||
<Notification type="warning">
|
||||
<strong>{t("plugins.modal.dependencyNotification")}</strong>
|
||||
<ul className={classes.listSpacing}>
|
||||
{plugin.dependencies.map((dependency, index) => {
|
||||
return <li key={index}>{dependency}</li>;
|
||||
})}
|
||||
</ul>
|
||||
</Notification>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
renderNotifications = () => {
|
||||
const { t } = this.props;
|
||||
const { restart, error, success } = this.state;
|
||||
if (error) {
|
||||
return (
|
||||
<div className="media">
|
||||
<ErrorNotification error={error} />
|
||||
</div>
|
||||
);
|
||||
} else if (success) {
|
||||
return (
|
||||
<div className="media">
|
||||
<InstallSuccessNotification />
|
||||
</div>
|
||||
);
|
||||
} else if (restart) {
|
||||
return (
|
||||
<div className="media">
|
||||
<Notification type="warning">
|
||||
{t("plugins.modal.restartNotification")}
|
||||
</Notification>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
handleRestartChange = (value: boolean) => {
|
||||
this.setState({
|
||||
restart: value
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { restart } = this.state;
|
||||
const { plugin, onClose, classes, t } = this.props;
|
||||
|
||||
const body = (
|
||||
<>
|
||||
<div className="media">
|
||||
<div className="media-content">
|
||||
<p>{plugin.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="media">
|
||||
<div className="media-content">
|
||||
<div className="field is-horizontal">
|
||||
<div
|
||||
className={classNames(
|
||||
classes.userLabelAlignment,
|
||||
"field-label is-inline-flex"
|
||||
)}
|
||||
>
|
||||
{t("plugins.modal.author")}:
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
classes.userFieldFlex,
|
||||
"field-body is-inline-flex"
|
||||
)}
|
||||
>
|
||||
{plugin.author}
|
||||
</div>
|
||||
</div>
|
||||
<div className="field is-horizontal">
|
||||
<div
|
||||
className={classNames(
|
||||
classes.userLabelAlignment,
|
||||
"field-label is-inline-flex"
|
||||
)}
|
||||
>
|
||||
{t("plugins.modal.version")}:
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
classes.userFieldFlex,
|
||||
"field-body is-inline-flex"
|
||||
)}
|
||||
>
|
||||
{plugin.version}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.renderDependencies()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="media">
|
||||
<div className="media-content">
|
||||
<Checkbox
|
||||
checked={restart}
|
||||
label={t("plugins.modal.restart")}
|
||||
onChange={this.handleRestartChange}
|
||||
disabled={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{this.renderNotifications()}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t("plugins.modal.title", {
|
||||
name: plugin.displayName ? plugin.displayName : plugin.name
|
||||
})}
|
||||
closeFunction={() => onClose()}
|
||||
body={body}
|
||||
footer={this.footer()}
|
||||
active={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
injectSheet(styles),
|
||||
translate("admin")
|
||||
)(PluginModal);
|
||||
32
scm-ui/src/admin/plugins/components/PluginTopActions.js
Normal file
32
scm-ui/src/admin/plugins/components/PluginTopActions.js
Normal file
@@ -0,0 +1,32 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import classNames from "classnames";
|
||||
import injectSheet from "react-jss";
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
children?: React.Node,
|
||||
|
||||
// context props
|
||||
classes: any
|
||||
};
|
||||
|
||||
class PluginTopActions extends React.Component<Props> {
|
||||
render() {
|
||||
const { children, classes } = this.props;
|
||||
return (
|
||||
<div className={classNames(classes.container, "column", "is-one-fifths", "is-mobile-action-spacing")}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectSheet(styles)(PluginTopActions);
|
||||
30
scm-ui/src/admin/plugins/components/waitForRestart.js
Normal file
30
scm-ui/src/admin/plugins/components/waitForRestart.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// @flow
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
|
||||
const waitForRestart = () => {
|
||||
const endTime = Number(new Date()) + 10000;
|
||||
let started = false;
|
||||
|
||||
const executor = (resolve, reject) => {
|
||||
// we need some initial delay
|
||||
if (!started) {
|
||||
started = true;
|
||||
setTimeout(executor, 100, resolve, reject);
|
||||
} else {
|
||||
apiClient
|
||||
.get("")
|
||||
.then(resolve)
|
||||
.catch(() => {
|
||||
if (Number(new Date()) < endTime) {
|
||||
setTimeout(executor, 500, resolve, reject);
|
||||
} else {
|
||||
reject(new Error("timeout reached"));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise<void>(executor);
|
||||
};
|
||||
|
||||
export default waitForRestart;
|
||||
@@ -1,5 +1,5 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { translate } from "react-i18next";
|
||||
import { compose } from "redux";
|
||||
@@ -17,11 +17,14 @@ import {
|
||||
getPluginCollection,
|
||||
isFetchPluginsPending
|
||||
} from "../modules/plugins";
|
||||
import PluginsList from "../components/PluginsList";
|
||||
import PluginsList from "../components/PluginList";
|
||||
import {
|
||||
getAvailablePluginsLink,
|
||||
getInstalledPluginsLink
|
||||
} from "../../../modules/indexResource";
|
||||
import PluginTopActions from "../components/PluginTopActions";
|
||||
import PluginBottomActions from "../components/PluginBottomActions";
|
||||
import InstallPendingAction from "../components/InstallPendingAction";
|
||||
|
||||
type Props = {
|
||||
loading: boolean,
|
||||
@@ -51,21 +54,62 @@ class PluginsOverview extends React.Component<Props> {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
installed,
|
||||
} = this.props;
|
||||
if (prevProps.installed !== installed) {
|
||||
this.fetchPlugins();
|
||||
}
|
||||
}
|
||||
|
||||
fetchPlugins = () => {
|
||||
const {
|
||||
installed,
|
||||
fetchPluginsByLink,
|
||||
availablePluginsLink,
|
||||
installedPluginsLink
|
||||
} = this.props;
|
||||
if (prevProps.installed !== installed) {
|
||||
fetchPluginsByLink(
|
||||
installed ? installedPluginsLink : availablePluginsLink
|
||||
);
|
||||
};
|
||||
|
||||
renderHeader = (actions: React.Node) => {
|
||||
const { installed, t } = this.props;
|
||||
return (
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<Title title={t("plugins.title")} />
|
||||
<Subtitle
|
||||
subtitle={
|
||||
installed
|
||||
? t("plugins.installedSubtitle")
|
||||
: t("plugins.availableSubtitle")
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<PluginTopActions>{actions}</PluginTopActions>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
renderFooter = (actions: React.Node) => {
|
||||
if (actions) {
|
||||
return <PluginBottomActions>{actions}</PluginBottomActions>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
createActions = () => {
|
||||
const { collection } = this.props;
|
||||
if (collection._links.installPending) {
|
||||
return <InstallPendingAction collection={collection} />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loading, error, collection, installed, t } = this.props;
|
||||
const { loading, error, collection } = this.props;
|
||||
|
||||
if (error) {
|
||||
return <ErrorNotification error={error} />;
|
||||
@@ -75,17 +119,13 @@ class PluginsOverview extends React.Component<Props> {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
const actions = this.createActions();
|
||||
return (
|
||||
<>
|
||||
<Title title={t("plugins.title")} />
|
||||
<Subtitle
|
||||
subtitle={
|
||||
installed
|
||||
? t("plugins.installedSubtitle")
|
||||
: t("plugins.availableSubtitle")
|
||||
}
|
||||
/>
|
||||
{this.renderHeader(actions)}
|
||||
<hr className="header-with-actions" />
|
||||
{this.renderPluginsList()}
|
||||
{this.renderFooter(actions)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -94,7 +134,7 @@ class PluginsOverview extends React.Component<Props> {
|
||||
const { collection, t } = this.props;
|
||||
|
||||
if (collection._embedded && collection._embedded.plugins.length > 0) {
|
||||
return <PluginsList plugins={collection._embedded.plugins} />;
|
||||
return <PluginsList plugins={collection._embedded.plugins} refresh={this.fetchPlugins} />;
|
||||
}
|
||||
return <Notification type="info">{t("plugins.noPlugins")}</Notification>;
|
||||
}
|
||||
|
||||
@@ -3,24 +3,22 @@ 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.AvailablePlugin;
|
||||
import sonia.scm.plugin.InstalledPluginDescriptor;
|
||||
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.QueryParam;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
@@ -53,11 +51,8 @@ public class AvailablePluginResource {
|
||||
@Produces(VndMediaType.PLUGIN_COLLECTION)
|
||||
public Response getAvailablePlugins() {
|
||||
PluginPermissions.read().check();
|
||||
Collection<PluginInformation> plugins = pluginManager.getAvailable()
|
||||
.stream()
|
||||
.filter(plugin -> plugin.getState().equals(PluginState.AVAILABLE))
|
||||
.collect(Collectors.toList());
|
||||
return Response.ok(collectionMapper.map(plugins)).build();
|
||||
List<AvailablePlugin> available = pluginManager.getAvailable();
|
||||
return Response.ok(collectionMapper.mapAvailable(available)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +61,7 @@ public class AvailablePluginResource {
|
||||
* @return available plugin.
|
||||
*/
|
||||
@GET
|
||||
@Path("/{name}/{version}")
|
||||
@Path("/{name}")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 404, condition = "not found"),
|
||||
@@ -74,35 +69,42 @@ public class AvailablePluginResource {
|
||||
})
|
||||
@TypeHint(PluginDto.class)
|
||||
@Produces(VndMediaType.PLUGIN)
|
||||
public Response getAvailablePlugin(@PathParam("name") String name, @PathParam("version") String version) {
|
||||
public Response getAvailablePlugin(@PathParam("name") String name) {
|
||||
PluginPermissions.read().check();
|
||||
Optional<PluginInformation> plugin = pluginManager.getAvailable()
|
||||
.stream()
|
||||
.filter(p -> p.getId().equals(name + ":" + version))
|
||||
.findFirst();
|
||||
Optional<AvailablePlugin> plugin = pluginManager.getAvailable(name);
|
||||
if (plugin.isPresent()) {
|
||||
return Response.ok(mapper.map(plugin.get())).build();
|
||||
return Response.ok(mapper.mapAvailable(plugin.get())).build();
|
||||
} else {
|
||||
throw notFound(entity(Plugin.class, name));
|
||||
throw notFound(entity(InstalledPluginDescriptor.class, name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers plugin installation.
|
||||
* @param name plugin artefact name
|
||||
* @param version plugin version
|
||||
* @param name plugin name
|
||||
* @return HTTP Status.
|
||||
*/
|
||||
@POST
|
||||
@Path("/{name}/{version}/install")
|
||||
@Consumes(VndMediaType.PLUGIN)
|
||||
@Path("/{name}/install")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
public Response installPlugin(@PathParam("name") String name, @PathParam("version") String version) {
|
||||
public Response installPlugin(@PathParam("name") String name, @QueryParam("restart") boolean restartAfterInstallation) {
|
||||
PluginPermissions.manage().check();
|
||||
pluginManager.install(name + ":" + version);
|
||||
pluginManager.install(name, restartAfterInstallation);
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/install-pending")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
public Response installPending() {
|
||||
PluginPermissions.manage().check();
|
||||
pluginManager.installPendingAndRestart();
|
||||
return Response.ok().build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@ 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.PluginLoader;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
import sonia.scm.plugin.InstalledPluginDescriptor;
|
||||
import sonia.scm.plugin.PluginManager;
|
||||
import sonia.scm.plugin.PluginPermissions;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -16,7 +15,6 @@ import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -25,17 +23,15 @@ import static sonia.scm.NotFoundException.notFound;
|
||||
|
||||
public class InstalledPluginResource {
|
||||
|
||||
private final PluginLoader pluginLoader;
|
||||
private final PluginDtoCollectionMapper collectionMapper;
|
||||
private final PluginDtoMapper mapper;
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
@Inject
|
||||
public InstalledPluginResource(PluginLoader pluginLoader, PluginDtoCollectionMapper collectionMapper, PluginDtoMapper mapper, PluginManager pluginManager) {
|
||||
this.pluginLoader = pluginLoader;
|
||||
public InstalledPluginResource(PluginManager pluginManager, PluginDtoCollectionMapper collectionMapper, PluginDtoMapper mapper) {
|
||||
this.pluginManager = pluginManager;
|
||||
this.collectionMapper = collectionMapper;
|
||||
this.mapper = mapper;
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,8 +49,8 @@ public class InstalledPluginResource {
|
||||
@Produces(VndMediaType.PLUGIN_COLLECTION)
|
||||
public Response getInstalledPlugins() {
|
||||
PluginPermissions.read().check();
|
||||
List<PluginWrapper> plugins = new ArrayList<>(pluginLoader.getInstalledPlugins());
|
||||
return Response.ok(collectionMapper.map(plugins)).build();
|
||||
List<InstalledPlugin> plugins = pluginManager.getInstalled();
|
||||
return Response.ok(collectionMapper.mapInstalled(plugins)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,15 +71,11 @@ public class InstalledPluginResource {
|
||||
@Produces(VndMediaType.PLUGIN)
|
||||
public Response getInstalledPlugin(@PathParam("name") String name) {
|
||||
PluginPermissions.read().check();
|
||||
Optional<PluginDto> pluginDto = pluginLoader.getInstalledPlugins()
|
||||
.stream()
|
||||
.filter(plugin -> name.equals(plugin.getPlugin().getInformation().getName()))
|
||||
.map(mapper::map)
|
||||
.findFirst();
|
||||
Optional<InstalledPlugin> pluginDto = pluginManager.getInstalled(name);
|
||||
if (pluginDto.isPresent()) {
|
||||
return Response.ok(pluginDto.get()).build();
|
||||
return Response.ok(mapper.mapInstalled(pluginDto.get())).build();
|
||||
} else {
|
||||
throw notFound(entity(Plugin.class, name));
|
||||
throw notFound(entity(InstalledPluginDescriptor.class, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,12 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@SuppressWarnings("squid:S2160") // we do not need equals for dto
|
||||
public class PluginDto extends HalRepresentation {
|
||||
|
||||
private String name;
|
||||
@@ -18,6 +21,8 @@ public class PluginDto extends HalRepresentation {
|
||||
private String author;
|
||||
private String category;
|
||||
private String avatarUrl;
|
||||
private boolean pending;
|
||||
private Set<String> dependencies;
|
||||
|
||||
public PluginDto(Links links) {
|
||||
add(links);
|
||||
|
||||
@@ -3,11 +3,12 @@ package sonia.scm.api.v2.resources;
|
||||
import com.google.inject.Inject;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Link;
|
||||
import de.otto.edison.hal.Links;
|
||||
import sonia.scm.plugin.PluginInformation;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.plugin.AvailablePlugin;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
import sonia.scm.plugin.PluginPermissions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
@@ -25,14 +26,14 @@ public class PluginDtoCollectionMapper {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public HalRepresentation map(List<PluginWrapper> plugins) {
|
||||
List<PluginDto> dtos = plugins.stream().map(mapper::map).collect(toList());
|
||||
public HalRepresentation mapInstalled(List<InstalledPlugin> plugins) {
|
||||
List<PluginDto> dtos = plugins.stream().map(mapper::mapInstalled).collect(toList());
|
||||
return new HalRepresentation(createInstalledPluginsLinks(), embedDtos(dtos));
|
||||
}
|
||||
|
||||
public HalRepresentation map(Collection<PluginInformation> plugins) {
|
||||
List<PluginDto> dtos = plugins.stream().map(mapper::map).collect(toList());
|
||||
return new HalRepresentation(createAvailablePluginsLinks(), embedDtos(dtos));
|
||||
public HalRepresentation mapAvailable(List<AvailablePlugin> plugins) {
|
||||
List<PluginDto> dtos = plugins.stream().map(mapper::mapAvailable).collect(toList());
|
||||
return new HalRepresentation(createAvailablePluginsLinks(plugins), embedDtos(dtos));
|
||||
}
|
||||
|
||||
private Links createInstalledPluginsLinks() {
|
||||
@@ -43,14 +44,23 @@ public class PluginDtoCollectionMapper {
|
||||
return linksBuilder.build();
|
||||
}
|
||||
|
||||
private Links createAvailablePluginsLinks() {
|
||||
private Links createAvailablePluginsLinks(List<AvailablePlugin> plugins) {
|
||||
String baseUrl = resourceLinks.availablePluginCollection().self();
|
||||
|
||||
Links.Builder linksBuilder = linkingTo()
|
||||
.with(Links.linkingTo().self(baseUrl).build());
|
||||
|
||||
if (PluginPermissions.manage().isPermitted() && containsPending(plugins)) {
|
||||
linksBuilder.single(Link.link("installPending", resourceLinks.availablePluginCollection().installPending()));
|
||||
}
|
||||
|
||||
return linksBuilder.build();
|
||||
}
|
||||
|
||||
private boolean containsPending(List<AvailablePlugin> plugins) {
|
||||
return plugins.stream().anyMatch(AvailablePlugin::isPending);
|
||||
}
|
||||
|
||||
private Embedded embedDtos(List<PluginDto> dtos) {
|
||||
return embeddedBuilder()
|
||||
.with("plugins", dtos)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
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.AvailablePlugin;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
import sonia.scm.plugin.Plugin;
|
||||
import sonia.scm.plugin.PluginInformation;
|
||||
import sonia.scm.plugin.PluginState;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.plugin.PluginPermissions;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -20,35 +20,50 @@ public abstract class PluginDtoMapper {
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
|
||||
public PluginDto map(PluginWrapper plugin) {
|
||||
return map(plugin.getPlugin().getInformation());
|
||||
public abstract void map(PluginInformation plugin, @MappingTarget PluginDto dto);
|
||||
|
||||
public PluginDto mapInstalled(InstalledPlugin plugin) {
|
||||
PluginDto dto = createDtoForInstalled(plugin);
|
||||
map(dto, plugin);
|
||||
return dto;
|
||||
}
|
||||
|
||||
public abstract PluginDto map(PluginInformation plugin);
|
||||
public PluginDto mapAvailable(AvailablePlugin plugin) {
|
||||
PluginDto dto = createDtoForAvailable(plugin);
|
||||
map(dto, plugin);
|
||||
dto.setPending(plugin.isPending());
|
||||
return dto;
|
||||
}
|
||||
|
||||
@AfterMapping
|
||||
protected void appendCategory(@MappingTarget PluginDto dto) {
|
||||
private void map(PluginDto dto, Plugin plugin) {
|
||||
dto.setDependencies(plugin.getDescriptor().getDependencies());
|
||||
map(plugin.getDescriptor().getInformation(), 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()
|
||||
private PluginDto createDtoForAvailable(AvailablePlugin plugin) {
|
||||
PluginInformation information = plugin.getDescriptor().getInformation();
|
||||
|
||||
Links.Builder links = linkingTo()
|
||||
.self(resourceLinks.availablePlugin()
|
||||
.self(pluginInformation.getName(), pluginInformation.getVersion()));
|
||||
.self(information.getName()));
|
||||
|
||||
linksBuilder.single(link("install", resourceLinks.availablePlugin().install(pluginInformation.getName(), pluginInformation.getVersion())));
|
||||
if (!plugin.isPending() && PluginPermissions.manage().isPermitted()) {
|
||||
links.single(link("install", resourceLinks.availablePlugin().install(information.getName())));
|
||||
}
|
||||
else {
|
||||
linksBuilder = linkingTo()
|
||||
|
||||
return new PluginDto(links.build());
|
||||
}
|
||||
|
||||
private PluginDto createDtoForInstalled(InstalledPlugin plugin) {
|
||||
PluginInformation information = plugin.getDescriptor().getInformation();
|
||||
|
||||
Links.Builder links = linkingTo()
|
||||
.self(resourceLinks.installedPlugin()
|
||||
.self(pluginInformation.getName()));
|
||||
}
|
||||
.self(information.getName()));
|
||||
|
||||
return new PluginDto(linksBuilder.build());
|
||||
return new PluginDto(links.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import javax.inject.Inject;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
@SuppressWarnings("squid:S1192") // string literals should not be duplicated
|
||||
class ResourceLinks {
|
||||
|
||||
private final ScmPathInfoStore scmPathInfoStore;
|
||||
@@ -694,12 +695,12 @@ class ResourceLinks {
|
||||
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 self(String name) {
|
||||
return availablePluginLinkBuilder.method("availablePlugins").parameters().method("getAvailablePlugin").parameters(name).href();
|
||||
}
|
||||
|
||||
String install(String name, String version) {
|
||||
return availablePluginLinkBuilder.method("availablePlugins").parameters().method("installPlugin").parameters(name, version).href();
|
||||
String install(String name) {
|
||||
return availablePluginLinkBuilder.method("availablePlugins").parameters().method("installPlugin").parameters(name).href();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,6 +715,10 @@ class ResourceLinks {
|
||||
availablePluginCollectionLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, AvailablePluginResource.class);
|
||||
}
|
||||
|
||||
String installPending() {
|
||||
return availablePluginCollectionLinkBuilder.method("availablePlugins").parameters().method("installPending").parameters().href();
|
||||
}
|
||||
|
||||
String self() {
|
||||
return availablePluginCollectionLinkBuilder.method("availablePlugins").parameters().method("getAvailablePlugins").parameters().href();
|
||||
}
|
||||
|
||||
@@ -4,7 +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.PluginWrapper;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -24,7 +24,7 @@ public class UIPluginDtoCollectionMapper {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public HalRepresentation map(Collection<PluginWrapper> plugins) {
|
||||
public HalRepresentation map(Collection<InstalledPlugin> plugins) {
|
||||
List<UIPluginDto> dtos = plugins.stream().map(mapper::map).collect(toList());
|
||||
return new HalRepresentation(createLinks(), embedDtos(dtos));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import de.otto.edison.hal.Links;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -25,9 +25,9 @@ public class UIPluginDtoMapper {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public UIPluginDto map(PluginWrapper plugin) {
|
||||
public UIPluginDto map(InstalledPlugin plugin) {
|
||||
UIPluginDto dto = new UIPluginDto(
|
||||
plugin.getPlugin().getInformation().getName(),
|
||||
plugin.getDescriptor().getInformation().getName(),
|
||||
getScriptResources(plugin)
|
||||
);
|
||||
|
||||
@@ -40,8 +40,8 @@ public class UIPluginDtoMapper {
|
||||
return dto;
|
||||
}
|
||||
|
||||
private Set<String> getScriptResources(PluginWrapper wrapper) {
|
||||
Set<String> scriptResources = wrapper.getPlugin().getResources().getScriptResources();
|
||||
private Set<String> getScriptResources(InstalledPlugin wrapper) {
|
||||
Set<String> scriptResources = wrapper.getDescriptor().getResources().getScriptResources();
|
||||
if (scriptResources != null) {
|
||||
return scriptResources.stream()
|
||||
.map(this::addContextPath)
|
||||
|
||||
@@ -4,7 +4,7 @@ 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.PluginLoader;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
import sonia.scm.security.AllowAnonymousAccess;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
@@ -46,7 +46,7 @@ public class UIPluginResource {
|
||||
@TypeHint(CollectionDto.class)
|
||||
@Produces(VndMediaType.UI_PLUGIN_COLLECTION)
|
||||
public Response getInstalledPlugins() {
|
||||
List<PluginWrapper> plugins = pluginLoader.getInstalledPlugins()
|
||||
List<InstalledPlugin> plugins = pluginLoader.getInstalledPlugins()
|
||||
.stream()
|
||||
.filter(this::filter)
|
||||
.collect(Collectors.toList());
|
||||
@@ -85,8 +85,8 @@ public class UIPluginResource {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean filter(PluginWrapper plugin) {
|
||||
return plugin.getPlugin().getResources() != null;
|
||||
private boolean filter(InstalledPlugin plugin) {
|
||||
return plugin.getDescriptor().getResources() != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ import sonia.scm.SCMContext;
|
||||
import sonia.scm.lifecycle.classloading.ClassLoaderLifeCycle;
|
||||
import sonia.scm.migration.UpdateException;
|
||||
import sonia.scm.plugin.DefaultPluginLoader;
|
||||
import sonia.scm.plugin.Plugin;
|
||||
import sonia.scm.plugin.InstalledPluginDescriptor;
|
||||
import sonia.scm.plugin.PluginException;
|
||||
import sonia.scm.plugin.PluginLoadException;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
import sonia.scm.plugin.PluginsInternal;
|
||||
import sonia.scm.plugin.SmpArchive;
|
||||
import sonia.scm.util.IOUtil;
|
||||
@@ -43,7 +43,7 @@ public final class PluginBootstrap {
|
||||
|
||||
private final ClassLoaderLifeCycle classLoaderLifeCycle;
|
||||
private final ServletContext servletContext;
|
||||
private final Set<PluginWrapper> plugins;
|
||||
private final Set<InstalledPlugin> plugins;
|
||||
private final PluginLoader pluginLoader;
|
||||
|
||||
PluginBootstrap(ServletContext servletContext, ClassLoaderLifeCycle classLoaderLifeCycle) {
|
||||
@@ -58,7 +58,7 @@ public final class PluginBootstrap {
|
||||
return pluginLoader;
|
||||
}
|
||||
|
||||
public Set<PluginWrapper> getPlugins() {
|
||||
public Set<InstalledPlugin> getPlugins() {
|
||||
return plugins;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public final class PluginBootstrap {
|
||||
return new DefaultPluginLoader(servletContext, classLoaderLifeCycle.getBootstrapClassLoader(), plugins);
|
||||
}
|
||||
|
||||
private Set<PluginWrapper> collectPlugins() {
|
||||
private Set<InstalledPlugin> collectPlugins() {
|
||||
try {
|
||||
File pluginDirectory = getPluginDirectory();
|
||||
|
||||
@@ -105,7 +105,7 @@ public final class PluginBootstrap {
|
||||
PluginIndexEntry entry) throws IOException {
|
||||
URL url = context.getResource(PLUGIN_DIRECTORY.concat(entry.getName()));
|
||||
SmpArchive archive = SmpArchive.create(url);
|
||||
Plugin plugin = archive.getPlugin();
|
||||
InstalledPluginDescriptor plugin = archive.getPlugin();
|
||||
|
||||
File directory = PluginsInternal.createPluginDirectory(pluginDirectory, plugin);
|
||||
File checksumFile = PluginsInternal.getChecksumFile(directory);
|
||||
|
||||
@@ -85,7 +85,7 @@ public class DefaultPluginLoader implements PluginLoader
|
||||
* @param installedPlugins
|
||||
*/
|
||||
public DefaultPluginLoader(ServletContext servletContext, ClassLoader parent,
|
||||
Set<PluginWrapper> installedPlugins)
|
||||
Set<InstalledPlugin> installedPlugins)
|
||||
{
|
||||
this.installedPlugins = installedPlugins;
|
||||
this.uberClassLoader = new UberClassLoader(parent, installedPlugins);
|
||||
@@ -95,7 +95,7 @@ public class DefaultPluginLoader implements PluginLoader
|
||||
try
|
||||
{
|
||||
JAXBContext context = JAXBContext.newInstance(ScmModule.class,
|
||||
Plugin.class);
|
||||
InstalledPluginDescriptor.class);
|
||||
|
||||
modules = getInstalled(parent, context, PATH_MODULECONFIG);
|
||||
|
||||
@@ -141,7 +141,7 @@ public class DefaultPluginLoader implements PluginLoader
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Collection<PluginWrapper> getInstalledPlugins()
|
||||
public Collection<InstalledPlugin> getInstalledPlugins()
|
||||
{
|
||||
return installedPlugins;
|
||||
}
|
||||
@@ -178,7 +178,7 @@ public class DefaultPluginLoader implements PluginLoader
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private Iterable<Plugin> unwrap()
|
||||
private Iterable<InstalledPluginDescriptor> unwrap()
|
||||
{
|
||||
return PluginsInternal.unwrap(installedPlugins);
|
||||
}
|
||||
@@ -227,7 +227,7 @@ public class DefaultPluginLoader implements PluginLoader
|
||||
private final ExtensionProcessor extensionProcessor;
|
||||
|
||||
/** Field description */
|
||||
private final Set<PluginWrapper> installedPlugins;
|
||||
private final Set<InstalledPlugin> installedPlugins;
|
||||
|
||||
/** Field description */
|
||||
private final Set<ScmModule> modules;
|
||||
|
||||
@@ -35,685 +35,164 @@ package sonia.scm.plugin;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.github.legman.Subscribe;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.cache.Cache;
|
||||
import sonia.scm.cache.CacheManager;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.config.ScmConfigurationChangedEvent;
|
||||
import sonia.scm.io.ZipUnArchiver;
|
||||
import sonia.scm.util.AssertUtil;
|
||||
import sonia.scm.util.IOUtil;
|
||||
import sonia.scm.util.SystemUtil;
|
||||
import sonia.scm.util.Util;
|
||||
import sonia.scm.version.Version;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.lifecycle.RestartEvent;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.bind.JAXB;
|
||||
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
|
||||
import static sonia.scm.plugin.PluginCenterDtoMapper.*;
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
|
||||
/**
|
||||
* TODO replace aether stuff.
|
||||
* TODO check AdvancedPluginConfiguration from 1.x
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Singleton
|
||||
public class DefaultPluginManager implements PluginManager
|
||||
{
|
||||
public class DefaultPluginManager implements PluginManager {
|
||||
|
||||
/** Field description */
|
||||
public static final String CACHE_NAME = "sonia.cache.plugins";
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultPluginManager.class);
|
||||
|
||||
/** Field description */
|
||||
public static final String ENCODING = "UTF-8";
|
||||
private final ScmEventBus eventBus;
|
||||
private final PluginLoader loader;
|
||||
private final PluginCenter center;
|
||||
private final PluginInstaller installer;
|
||||
private final List<PendingPluginInstallation> pendingQueue = new ArrayList<>();
|
||||
|
||||
/** the logger for DefaultPluginManager */
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(DefaultPluginManager.class);
|
||||
|
||||
/** enable or disable remote plugins */
|
||||
private static final boolean REMOTE_PLUGINS_ENABLED = true;
|
||||
|
||||
/** Field description */
|
||||
public static final Predicate<PluginInformation> FILTER_UPDATES =
|
||||
new StatePluginPredicate(PluginState.UPDATE_AVAILABLE);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
* @param context
|
||||
* @param configuration
|
||||
* @param pluginLoader
|
||||
* @param cacheManager
|
||||
* @param httpClient
|
||||
*/
|
||||
@Inject
|
||||
public DefaultPluginManager(SCMContextProvider context,
|
||||
ScmConfiguration configuration, PluginLoader pluginLoader,
|
||||
CacheManager cacheManager, AdvancedHttpClient httpClient)
|
||||
{
|
||||
this.context = context;
|
||||
this.configuration = configuration;
|
||||
this.cache = cacheManager.getCache(CACHE_NAME);
|
||||
this.httpClient = httpClient;
|
||||
installedPlugins = new HashMap<>();
|
||||
|
||||
for (PluginWrapper wrapper : pluginLoader.getInstalledPlugins())
|
||||
{
|
||||
Plugin plugin = wrapper.getPlugin();
|
||||
PluginInformation info = plugin.getInformation();
|
||||
|
||||
if ((info != null) && info.isValid())
|
||||
{
|
||||
installedPlugins.put(info.getId(), plugin);
|
||||
}
|
||||
}
|
||||
public DefaultPluginManager(ScmEventBus eventBus, PluginLoader loader, PluginCenter center, PluginInstaller installer) {
|
||||
this.eventBus = eventBus;
|
||||
this.loader = loader;
|
||||
this.center = center;
|
||||
this.installer = installer;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void clearCache()
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("clear plugin cache");
|
||||
public Optional<AvailablePlugin> getAvailable(String name) {
|
||||
PluginPermissions.read().check();
|
||||
return center.getAvailable()
|
||||
.stream()
|
||||
.filter(filterByName(name))
|
||||
.filter(this::isNotInstalled)
|
||||
.map(p -> getPending(name).orElse(p))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
cache.clear();
|
||||
private Optional<AvailablePlugin> getPending(String name) {
|
||||
return pendingQueue
|
||||
.stream()
|
||||
.map(PendingPluginInstallation::getPlugin)
|
||||
.filter(filterByName(name))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param config
|
||||
*/
|
||||
@Subscribe
|
||||
public void configChanged(ScmConfigurationChangedEvent config)
|
||||
{
|
||||
clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
@Override
|
||||
public void install(String id)
|
||||
{
|
||||
public Optional<InstalledPlugin> getInstalled(String name) {
|
||||
PluginPermissions.read().check();
|
||||
return loader.getInstalledPlugins()
|
||||
.stream()
|
||||
.filter(filterByName(name))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InstalledPlugin> getInstalled() {
|
||||
PluginPermissions.read().check();
|
||||
return ImmutableList.copyOf(loader.getInstalledPlugins());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AvailablePlugin> getAvailable() {
|
||||
PluginPermissions.read().check();
|
||||
return center.getAvailable()
|
||||
.stream()
|
||||
.filter(this::isNotInstalled)
|
||||
.map(p -> getPending(p.getDescriptor().getInformation().getName()).orElse(p))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private <T extends Plugin> Predicate<T> filterByName(String name) {
|
||||
return plugin -> name.equals(plugin.getDescriptor().getInformation().getName());
|
||||
}
|
||||
|
||||
private boolean isNotInstalled(AvailablePlugin availablePlugin) {
|
||||
return !getInstalled(availablePlugin.getDescriptor().getInformation().getName()).isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void install(String name, boolean restartAfterInstallation) {
|
||||
PluginPermissions.manage().check();
|
||||
List<AvailablePlugin> plugins = collectPluginsToInstall(name);
|
||||
List<PendingPluginInstallation> pendingInstallations = new ArrayList<>();
|
||||
for (AvailablePlugin plugin : plugins) {
|
||||
try {
|
||||
PendingPluginInstallation pending = installer.install(plugin);
|
||||
pendingInstallations.add(pending);
|
||||
} catch (PluginInstallException ex) {
|
||||
cancelPending(pendingInstallations);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
PluginCenter center = getPluginCenter();
|
||||
|
||||
for (PluginInformation plugin : center.getPlugins())
|
||||
{
|
||||
String pluginId = plugin.getId();
|
||||
|
||||
if (Util.isNotEmpty(pluginId) && pluginId.equals(id))
|
||||
{
|
||||
plugin.setState(PluginState.INSTALLED);
|
||||
|
||||
// ugly workaround
|
||||
Plugin newPlugin = new Plugin();
|
||||
|
||||
// TODO check
|
||||
// newPlugin.setInformation(plugin);
|
||||
installedPlugins.put(id, newPlugin);
|
||||
if (!pendingInstallations.isEmpty()) {
|
||||
if (restartAfterInstallation) {
|
||||
restart("plugin installation");
|
||||
} else {
|
||||
pendingQueue.addAll(pendingInstallations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param packageStream
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public void installPackage(InputStream packageStream) throws IOException
|
||||
{
|
||||
public void installPendingAndRestart() {
|
||||
PluginPermissions.manage().check();
|
||||
|
||||
File tempDirectory = Files.createTempDir();
|
||||
|
||||
try
|
||||
{
|
||||
new ZipUnArchiver().extractArchive(packageStream, tempDirectory);
|
||||
|
||||
Plugin plugin = JAXB.unmarshal(new File(tempDirectory, "plugin.xml"),
|
||||
Plugin.class);
|
||||
|
||||
PluginCondition condition = plugin.getCondition();
|
||||
|
||||
if ((condition != null) &&!condition.isSupported())
|
||||
{
|
||||
throw new PluginConditionFailedException(condition);
|
||||
}
|
||||
|
||||
/*
|
||||
* AetherPluginHandler aph = new AetherPluginHandler(this, context,
|
||||
* configuration);
|
||||
* Collection<PluginRepository> repositories =
|
||||
* Sets.newHashSet(new PluginRepository("package-repository",
|
||||
* "file://".concat(tempDirectory.getAbsolutePath())));
|
||||
*
|
||||
* aph.setPluginRepositories(repositories);
|
||||
*
|
||||
* aph.install(plugin.getInformation().getId());
|
||||
*/
|
||||
plugin.getInformation().setState(PluginState.INSTALLED);
|
||||
installedPlugins.put(plugin.getInformation().getId(), plugin);
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
IOUtil.delete(tempDirectory);
|
||||
if (!pendingQueue.isEmpty()) {
|
||||
restart("install pending plugins");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
@Override
|
||||
public void uninstall(String id)
|
||||
{
|
||||
PluginPermissions.manage().check();
|
||||
|
||||
Plugin plugin = installedPlugins.get(id);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
String pluginPrefix = getPluginIdPrefix(id);
|
||||
|
||||
for (String nid : installedPlugins.keySet())
|
||||
{
|
||||
if (nid.startsWith(pluginPrefix))
|
||||
{
|
||||
id = nid;
|
||||
plugin = installedPlugins.get(nid);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void restart(String cause) {
|
||||
eventBus.post(new RestartEvent(PluginManager.class, cause));
|
||||
}
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
throw new PluginNotInstalledException(id.concat(" is not install"));
|
||||
private void cancelPending(List<PendingPluginInstallation> pendingInstallations) {
|
||||
pendingInstallations.forEach(PendingPluginInstallation::cancel);
|
||||
}
|
||||
|
||||
/*
|
||||
* if (pluginHandler == null)
|
||||
* {
|
||||
* getPluginCenter();
|
||||
* }
|
||||
*
|
||||
* pluginHandler.uninstall(id);
|
||||
*/
|
||||
installedPlugins.remove(id);
|
||||
preparePlugins(getPluginCenter());
|
||||
private List<AvailablePlugin> collectPluginsToInstall(String name) {
|
||||
List<AvailablePlugin> plugins = new ArrayList<>();
|
||||
collectPluginsToInstall(plugins, name);
|
||||
return plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
@Override
|
||||
public void update(String id)
|
||||
{
|
||||
PluginPermissions.manage().check();
|
||||
private boolean isInstalledOrPending(String name) {
|
||||
return getInstalled(name).isPresent() || getPending(name).isPresent();
|
||||
}
|
||||
|
||||
String[] idParts = id.split(":");
|
||||
String name = idParts[0];
|
||||
PluginInformation installed = null;
|
||||
private void collectPluginsToInstall(List<AvailablePlugin> plugins, String name) {
|
||||
if (!isInstalledOrPending(name)) {
|
||||
AvailablePlugin plugin = getAvailable(name).orElseThrow(() -> NotFoundException.notFound(entity(AvailablePlugin.class, name)));
|
||||
|
||||
for (PluginInformation info : getInstalled())
|
||||
{
|
||||
if (name.equals(info.getName()))
|
||||
{
|
||||
installed = info;
|
||||
|
||||
break;
|
||||
Set<String> dependencies = plugin.getDescriptor().getDependencies();
|
||||
if (dependencies != null) {
|
||||
for (String dependency: dependencies){
|
||||
collectPluginsToInstall(plugins, dependency);
|
||||
}
|
||||
}
|
||||
|
||||
if (installed == null)
|
||||
{
|
||||
StringBuilder msg = new StringBuilder(name);
|
||||
|
||||
msg.append(" is not install");
|
||||
|
||||
throw new PluginNotInstalledException(msg.toString());
|
||||
}
|
||||
|
||||
uninstall(installed.getId());
|
||||
install(id);
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public PluginInformation get(String id)
|
||||
{
|
||||
PluginPermissions.read().check();
|
||||
|
||||
PluginInformation result = null;
|
||||
|
||||
for (PluginInformation info : getPluginCenter().getPlugins())
|
||||
{
|
||||
if (id.equals(info.getId()))
|
||||
{
|
||||
result = info;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param predicate
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Set<PluginInformation> get(Predicate<PluginInformation> predicate)
|
||||
{
|
||||
AssertUtil.assertIsNotNull(predicate);
|
||||
PluginPermissions.read().check();
|
||||
|
||||
Set<PluginInformation> infoSet = new HashSet<>();
|
||||
|
||||
filter(infoSet, getInstalled(), predicate);
|
||||
filter(infoSet, getPluginCenter().getPlugins(), predicate);
|
||||
|
||||
return infoSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Collection<PluginInformation> getAll()
|
||||
{
|
||||
PluginPermissions.read().check();
|
||||
|
||||
Set<PluginInformation> infoSet = getInstalled();
|
||||
|
||||
infoSet.addAll(getPluginCenter().getPlugins());
|
||||
|
||||
return infoSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Collection<PluginInformation> getAvailable()
|
||||
{
|
||||
PluginPermissions.read().check();
|
||||
|
||||
Set<PluginInformation> availablePlugins = new HashSet<>();
|
||||
Set<PluginInformation> centerPlugins = getPluginCenter().getPlugins();
|
||||
|
||||
for (PluginInformation info : centerPlugins)
|
||||
{
|
||||
if (!installedPlugins.containsKey(info.getName()))
|
||||
{
|
||||
availablePlugins.add(info);
|
||||
}
|
||||
}
|
||||
|
||||
return availablePlugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Set<PluginInformation> getAvailableUpdates()
|
||||
{
|
||||
PluginPermissions.read().check();
|
||||
|
||||
return get(FILTER_UPDATES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Set<PluginInformation> getInstalled()
|
||||
{
|
||||
PluginPermissions.read().check();
|
||||
|
||||
Set<PluginInformation> infoSet = new LinkedHashSet<>();
|
||||
|
||||
for (Plugin plugin : installedPlugins.values())
|
||||
{
|
||||
infoSet.add(plugin.getInformation());
|
||||
}
|
||||
|
||||
return infoSet;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
private String buildPluginUrl(String url)
|
||||
{
|
||||
String os = SystemUtil.getOS();
|
||||
String arch = SystemUtil.getArch();
|
||||
|
||||
try
|
||||
{
|
||||
os = URLEncoder.encode(os, ENCODING);
|
||||
}
|
||||
catch (UnsupportedEncodingException ex)
|
||||
{
|
||||
logger.error(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
return url.replace("{version}", context.getVersion()).replace("{os}",
|
||||
os).replace("{arch}", arch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param target
|
||||
* @param source
|
||||
* @param predicate
|
||||
*/
|
||||
private void filter(Set<PluginInformation> target,
|
||||
Collection<PluginInformation> source,
|
||||
Predicate<PluginInformation> predicate)
|
||||
{
|
||||
for (PluginInformation info : source)
|
||||
{
|
||||
if (predicate.apply(info))
|
||||
{
|
||||
target.add(info);
|
||||
plugins.add(plugin);
|
||||
} else {
|
||||
LOG.info("plugin {} is already installed or installation is pending, skipping installation", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param available
|
||||
*/
|
||||
private void preparePlugin(PluginInformation available)
|
||||
{
|
||||
PluginState state = PluginState.AVAILABLE;
|
||||
|
||||
for (PluginInformation installed : getInstalled())
|
||||
{
|
||||
if (isSamePlugin(available, installed))
|
||||
{
|
||||
if (installed.getVersion().equals(available.getVersion()))
|
||||
{
|
||||
state = PluginState.INSTALLED;
|
||||
}
|
||||
else if (isNewer(available, installed))
|
||||
{
|
||||
state = PluginState.UPDATE_AVAILABLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = PluginState.NEWER_VERSION_INSTALLED;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
available.setState(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param pc
|
||||
*/
|
||||
private void preparePlugins(PluginCenter pc)
|
||||
{
|
||||
Set<PluginInformation> infoSet = pc.getPlugins();
|
||||
|
||||
if (infoSet != null)
|
||||
{
|
||||
Iterator<PluginInformation> pit = infoSet.iterator();
|
||||
|
||||
while (pit.hasNext())
|
||||
{
|
||||
PluginInformation available = pit.next();
|
||||
|
||||
if (isCorePluging(available))
|
||||
{
|
||||
pit.remove();
|
||||
}
|
||||
else
|
||||
{
|
||||
preparePlugin(available);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private PluginCenter getPluginCenter()
|
||||
{
|
||||
PluginCenter center = cache.get(PluginCenter.class.getName());
|
||||
|
||||
if (center == null)
|
||||
{
|
||||
synchronized (DefaultPluginManager.class)
|
||||
{
|
||||
String pluginUrl = buildPluginUrl(configuration.getPluginUrl());
|
||||
logger.info("fetch plugin information from {}", pluginUrl);
|
||||
|
||||
if (REMOTE_PLUGINS_ENABLED && Util.isNotEmpty(pluginUrl))
|
||||
{
|
||||
try
|
||||
{
|
||||
center = new PluginCenter();
|
||||
PluginCenterDto pluginCenterDto = httpClient.get(pluginUrl).request().contentFromJson(PluginCenterDto.class);
|
||||
Set<PluginInformation> pluginInformationSet = map(pluginCenterDto.getEmbedded().getPlugins());
|
||||
center.setPlugins(pluginInformationSet);
|
||||
preparePlugins(center);
|
||||
cache.put(PluginCenter.class.getName(), center);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
logger.error("could not load plugins from plugin center", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(center == null) {
|
||||
center = new PluginCenter();
|
||||
}
|
||||
}
|
||||
|
||||
return center;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param pluginId
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String getPluginIdPrefix(String pluginId)
|
||||
{
|
||||
return pluginId.substring(0, pluginId.lastIndexOf(':'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param available
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean isCorePluging(PluginInformation available)
|
||||
{
|
||||
boolean core = false;
|
||||
|
||||
for (Plugin installedPlugin : installedPlugins.values())
|
||||
{
|
||||
PluginInformation installed = installedPlugin.getInformation();
|
||||
|
||||
if (isSamePlugin(available, installed)
|
||||
&& (installed.getState() == PluginState.CORE))
|
||||
{
|
||||
core = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return core;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param available
|
||||
* @param installed
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean isNewer(PluginInformation available,
|
||||
PluginInformation installed)
|
||||
{
|
||||
boolean result = false;
|
||||
Version version = Version.parse(available.getVersion());
|
||||
|
||||
if (version != null)
|
||||
{
|
||||
result = version.isNewer(installed.getVersion());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param p1
|
||||
* @param p2
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean isSamePlugin(PluginInformation p1, PluginInformation p2)
|
||||
{
|
||||
return p1.getName().equals(p2.getName());
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private final Cache<String, PluginCenter> cache;
|
||||
|
||||
/** Field description */
|
||||
private final AdvancedHttpClient httpClient;
|
||||
|
||||
/** Field description */
|
||||
private final ScmConfiguration configuration;
|
||||
|
||||
/** Field description */
|
||||
private final SCMContextProvider context;
|
||||
|
||||
/** Field description */
|
||||
private final Map<String, Plugin> installedPlugins;
|
||||
}
|
||||
|
||||
@@ -71,11 +71,11 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
public DefaultUberWebResourceLoader(ServletContext servletContext, Iterable<PluginWrapper> plugins) {
|
||||
public DefaultUberWebResourceLoader(ServletContext servletContext, Iterable<InstalledPlugin> plugins) {
|
||||
this(servletContext, plugins, SCMContext.getContext().getStage());
|
||||
}
|
||||
|
||||
public DefaultUberWebResourceLoader(ServletContext servletContext, Iterable<PluginWrapper> plugins, Stage stage) {
|
||||
public DefaultUberWebResourceLoader(ServletContext servletContext, Iterable<InstalledPlugin> plugins, Stage stage) {
|
||||
this.servletContext = servletContext;
|
||||
this.plugins = plugins;
|
||||
this.cache = createCache(stage);
|
||||
@@ -153,7 +153,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
|
||||
resources.add(ctxResource);
|
||||
}
|
||||
|
||||
for (PluginWrapper wrapper : plugins)
|
||||
for (InstalledPlugin wrapper : plugins)
|
||||
{
|
||||
URL resource = nonDirectory(wrapper.getWebResourceLoader().getResource(path));
|
||||
|
||||
@@ -205,7 +205,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
|
||||
|
||||
if (resource == null)
|
||||
{
|
||||
for (PluginWrapper wrapper : plugins)
|
||||
for (InstalledPlugin wrapper : plugins)
|
||||
{
|
||||
resource = nonDirectory(wrapper.getWebResourceLoader().getResource(path));
|
||||
|
||||
@@ -259,7 +259,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
|
||||
private final Cache<String, URL> cache;
|
||||
|
||||
/** Field description */
|
||||
private final Iterable<PluginWrapper> plugins;
|
||||
private final Iterable<InstalledPlugin> plugins;
|
||||
|
||||
/** Field description */
|
||||
private final ServletContext servletContext;
|
||||
|
||||
@@ -63,7 +63,7 @@ public final class ExplodedSmp implements Comparable<ExplodedSmp>
|
||||
* @param path
|
||||
* @param plugin
|
||||
*/
|
||||
ExplodedSmp(Path path, Plugin plugin)
|
||||
ExplodedSmp(Path path, InstalledPluginDescriptor plugin)
|
||||
{
|
||||
logger.trace("create exploded scm for plugin {} and dependencies {}", plugin.getInformation().getName(), plugin.getDependencies());
|
||||
this.path = path;
|
||||
@@ -163,7 +163,7 @@ public final class ExplodedSmp implements Comparable<ExplodedSmp>
|
||||
*
|
||||
* @return plugin descriptor
|
||||
*/
|
||||
public Plugin getPlugin()
|
||||
public InstalledPluginDescriptor getPlugin()
|
||||
{
|
||||
return plugin;
|
||||
}
|
||||
@@ -202,5 +202,5 @@ public final class ExplodedSmp implements Comparable<ExplodedSmp>
|
||||
private final Path path;
|
||||
|
||||
/** plugin object */
|
||||
private final Plugin plugin;
|
||||
private final InstalledPluginDescriptor plugin;
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* 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;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class OverviewPluginPredicate implements Predicate<PluginInformation>
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
public static final OverviewPluginPredicate INSTANCE =
|
||||
new OverviewPluginPredicate();
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param plugin
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean apply(PluginInformation plugin)
|
||||
{
|
||||
return plugin.getState() != PluginState.NEWER_VERSION_INSTALLED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class PendingPluginInstallation {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PendingPluginInstallation.class);
|
||||
|
||||
private final AvailablePlugin plugin;
|
||||
private final Path file;
|
||||
|
||||
PendingPluginInstallation(AvailablePlugin plugin, Path file) {
|
||||
this.plugin = plugin;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public AvailablePlugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
String name = plugin.getDescriptor().getInformation().getName();
|
||||
LOG.info("cancel installation of plugin {}", name);
|
||||
try {
|
||||
Files.delete(file);
|
||||
} catch (IOException ex) {
|
||||
throw new PluginFailedToCancelInstallationException("failed to cancel installation of plugin " + name, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
55
scm-webapp/src/main/java/sonia/scm/plugin/PluginCenter.java
Normal file
55
scm-webapp/src/main/java/sonia/scm/plugin/PluginCenter.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.cache.Cache;
|
||||
import sonia.scm.cache.CacheManager;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.util.SystemUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Set;
|
||||
|
||||
public class PluginCenter {
|
||||
|
||||
private static final String CACHE_NAME = "sonia.cache.plugins";
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PluginCenter.class);
|
||||
|
||||
private final SCMContextProvider context;
|
||||
private final ScmConfiguration configuration;
|
||||
private final PluginCenterLoader loader;
|
||||
private final Cache<String, Set<AvailablePlugin>> cache;
|
||||
|
||||
@Inject
|
||||
public PluginCenter(SCMContextProvider context, CacheManager cacheManager, ScmConfiguration configuration, PluginCenterLoader loader) {
|
||||
this.context = context;
|
||||
this.configuration = configuration;
|
||||
this.loader = loader;
|
||||
this.cache = cacheManager.getCache(CACHE_NAME);
|
||||
}
|
||||
|
||||
synchronized Set<AvailablePlugin> getAvailable() {
|
||||
String url = buildPluginUrl(configuration.getPluginUrl());
|
||||
Set<AvailablePlugin> plugins = cache.get(url);
|
||||
if (plugins == null) {
|
||||
LOG.debug("no cached available plugins found, start fetching");
|
||||
plugins = loader.load(url);
|
||||
cache.put(url, plugins);
|
||||
} else {
|
||||
LOG.debug("return available plugins from cache");
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
|
||||
private String buildPluginUrl(String url) {
|
||||
String os = HttpUtil.encode(SystemUtil.getOS());
|
||||
String arch = SystemUtil.getArch();
|
||||
return url.replace("{version}", context.getVersion())
|
||||
.replace("{os}", os)
|
||||
.replace("{arch}", arch);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package sonia.scm.plugin;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
@@ -11,6 +12,7 @@ import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@XmlRootElement
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@@ -56,8 +58,8 @@ public final class PluginCenterDto implements Serializable {
|
||||
@XmlElement(name = "conditions")
|
||||
private Condition conditions;
|
||||
|
||||
@XmlElement(name = "dependecies")
|
||||
private Dependency dependencies;
|
||||
@XmlElement(name = "dependencies")
|
||||
private Set<String> dependencies;
|
||||
|
||||
@XmlElement(name = "_links")
|
||||
private Map<String, Link> links;
|
||||
@@ -75,15 +77,9 @@ public final class PluginCenterDto implements Serializable {
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "dependencies")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
static class Dependency {
|
||||
private String name;
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@Getter
|
||||
static class Link {
|
||||
private String href;
|
||||
}
|
||||
|
||||
@@ -1,26 +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 {
|
||||
public abstract class PluginCenterDtoMapper {
|
||||
|
||||
@Mapping(source = "conditions", target = "condition")
|
||||
PluginInformation map(PluginCenterDto.Plugin plugin);
|
||||
static final PluginCenterDtoMapper INSTANCE = Mappers.getMapper(PluginCenterDtoMapper.class);
|
||||
|
||||
PluginCondition map(PluginCenterDto.Condition condition);
|
||||
abstract PluginInformation map(PluginCenterDto.Plugin plugin);
|
||||
abstract PluginCondition map(PluginCenterDto.Condition condition);
|
||||
|
||||
static Set<PluginInformation> map(List<PluginCenterDto.Plugin> dtos) {
|
||||
PluginCenterDtoMapper mapper = Mappers.getMapper(PluginCenterDtoMapper.class);
|
||||
Set<PluginInformation> plugins = new HashSet<>();
|
||||
for (PluginCenterDto.Plugin plugin : dtos) {
|
||||
plugins.add(mapper.map(plugin));
|
||||
Set<AvailablePlugin> map(PluginCenterDto pluginCenterDto) {
|
||||
Set<AvailablePlugin> plugins = new HashSet<>();
|
||||
for (PluginCenterDto.Plugin plugin : pluginCenterDto.getEmbedded().getPlugins()) {
|
||||
String url = plugin.getLinks().get("download").getHref();
|
||||
AvailablePluginDescriptor descriptor = new AvailablePluginDescriptor(
|
||||
map(plugin), map(plugin.getConditions()), plugin.getDependencies(), url, plugin.getSha256()
|
||||
);
|
||||
plugins.add(new AvailablePlugin(descriptor));
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
class PluginCenterLoader {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PluginCenterLoader.class);
|
||||
|
||||
private final AdvancedHttpClient client;
|
||||
private final PluginCenterDtoMapper mapper;
|
||||
|
||||
@Inject
|
||||
public PluginCenterLoader(AdvancedHttpClient client) {
|
||||
this(client, PluginCenterDtoMapper.INSTANCE);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
PluginCenterLoader(AdvancedHttpClient client, PluginCenterDtoMapper mapper) {
|
||||
this.client = client;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
Set<AvailablePlugin> load(String url) {
|
||||
try {
|
||||
LOG.info("fetch plugins from {}", url);
|
||||
PluginCenterDto pluginCenterDto = client.get(url).request().contentFromJson(PluginCenterDto.class);
|
||||
return mapper.map(pluginCenterDto);
|
||||
} catch (IOException ex) {
|
||||
LOG.error("failed to load plugins from plugin center, returning empty list");
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
public class PluginChecksumMismatchException extends PluginInstallException {
|
||||
public PluginChecksumMismatchException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
public class PluginDownloadException extends PluginInstallException {
|
||||
public PluginDownloadException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
public class PluginFailedToCancelInstallationException extends RuntimeException {
|
||||
public PluginFailedToCancelInstallationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
public class PluginInstallException extends RuntimeException {
|
||||
|
||||
public PluginInstallException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PluginInstallException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.hash.HashingInputStream;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage") // guava hash is marked as unstable
|
||||
class PluginInstaller {
|
||||
|
||||
private final SCMContextProvider context;
|
||||
private final AdvancedHttpClient client;
|
||||
|
||||
@Inject
|
||||
public PluginInstaller(SCMContextProvider context, AdvancedHttpClient client) {
|
||||
this.context = context;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@SuppressWarnings("squid:S4790") // hashing should be safe
|
||||
public PendingPluginInstallation install(AvailablePlugin plugin) {
|
||||
Path file = null;
|
||||
try (HashingInputStream input = new HashingInputStream(Hashing.sha256(), download(plugin))) {
|
||||
file = createFile(plugin);
|
||||
Files.copy(input, file);
|
||||
|
||||
verifyChecksum(plugin, input.hash(), file);
|
||||
return new PendingPluginInstallation(plugin.install(), file);
|
||||
} catch (IOException ex) {
|
||||
cleanup(file);
|
||||
throw new PluginDownloadException("failed to download plugin", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanup(Path file) {
|
||||
try {
|
||||
if (file != null) {
|
||||
Files.deleteIfExists(file);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new PluginInstallException("failed to cleanup, after broken installation");
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyChecksum(AvailablePlugin plugin, HashCode hash, Path file) {
|
||||
Optional<String> checksum = plugin.getDescriptor().getChecksum();
|
||||
if (checksum.isPresent()) {
|
||||
String calculatedChecksum = hash.toString();
|
||||
if (!checksum.get().equalsIgnoreCase(calculatedChecksum)) {
|
||||
cleanup(file);
|
||||
throw new PluginChecksumMismatchException(
|
||||
String.format("downloaded plugin checksum %s does not match expected %s", calculatedChecksum, checksum.get())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream download(AvailablePlugin plugin) throws IOException {
|
||||
return client.get(plugin.getDescriptor().getUrl()).request().contentAsStream();
|
||||
}
|
||||
|
||||
private Path createFile(AvailablePlugin plugin) throws IOException {
|
||||
Path directory = context.resolve(Paths.get("plugins"));
|
||||
Files.createDirectories(directory);
|
||||
return directory.resolve(plugin.getDescriptor().getInformation().getName() + ".smp");
|
||||
}
|
||||
}
|
||||
@@ -157,7 +157,7 @@ public final class PluginNode
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public PluginWrapper getWrapper()
|
||||
public InstalledPlugin getWrapper()
|
||||
{
|
||||
return wrapper;
|
||||
}
|
||||
@@ -170,7 +170,7 @@ public final class PluginNode
|
||||
*
|
||||
* @param wrapper
|
||||
*/
|
||||
public void setWrapper(PluginWrapper wrapper)
|
||||
public void setWrapper(InstalledPlugin wrapper)
|
||||
{
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
@@ -192,5 +192,5 @@ public final class PluginNode
|
||||
private final ExplodedSmp plugin;
|
||||
|
||||
/** Field description */
|
||||
private PluginWrapper wrapper;
|
||||
private InstalledPlugin wrapper;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ public final class PluginProcessor
|
||||
|
||||
try
|
||||
{
|
||||
this.context = JAXBContext.newInstance(Plugin.class);
|
||||
this.context = JAXBContext.newInstance(InstalledPluginDescriptor.class);
|
||||
}
|
||||
catch (JAXBException ex)
|
||||
{
|
||||
@@ -160,7 +160,7 @@ public final class PluginProcessor
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public Set<PluginWrapper> collectPlugins(ClassLoader classLoader)
|
||||
public Set<InstalledPlugin> collectPlugins(ClassLoader classLoader)
|
||||
throws IOException
|
||||
{
|
||||
logger.info("collect plugins");
|
||||
@@ -187,7 +187,7 @@ public final class PluginProcessor
|
||||
|
||||
logger.trace("create plugin wrappers and build classloaders");
|
||||
|
||||
Set<PluginWrapper> wrappers = createPluginWrappers(classLoader, rootNodes);
|
||||
Set<InstalledPlugin> wrappers = createPluginWrappers(classLoader, rootNodes);
|
||||
|
||||
logger.debug("collected {} plugins", wrappers.size());
|
||||
|
||||
@@ -204,7 +204,7 @@ public final class PluginProcessor
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void appendPluginWrapper(Set<PluginWrapper> plugins,
|
||||
private void appendPluginWrapper(Set<InstalledPlugin> plugins,
|
||||
ClassLoader classLoader, PluginNode node)
|
||||
throws IOException
|
||||
{
|
||||
@@ -217,7 +217,7 @@ public final class PluginProcessor
|
||||
|
||||
for (PluginNode parent : node.getParents())
|
||||
{
|
||||
PluginWrapper wrapper = parent.getWrapper();
|
||||
InstalledPlugin wrapper = parent.getWrapper();
|
||||
|
||||
if (wrapper != null)
|
||||
{
|
||||
@@ -236,8 +236,8 @@ public final class PluginProcessor
|
||||
|
||||
}
|
||||
|
||||
PluginWrapper plugin =
|
||||
createPluginWrapper(createParentPluginClassLoader(classLoader, parents),
|
||||
InstalledPlugin plugin =
|
||||
createPlugin(createParentPluginClassLoader(classLoader, parents),
|
||||
smp);
|
||||
|
||||
if (plugin != null)
|
||||
@@ -257,7 +257,7 @@ public final class PluginProcessor
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void appendPluginWrappers(Set<PluginWrapper> plugins,
|
||||
private void appendPluginWrappers(Set<InstalledPlugin> plugins,
|
||||
ClassLoader classLoader, List<PluginNode> nodes)
|
||||
throws IOException
|
||||
{
|
||||
@@ -371,7 +371,7 @@ public final class PluginProcessor
|
||||
|
||||
ClassLoader classLoader;
|
||||
URL[] urlArray = urls.toArray(new URL[urls.size()]);
|
||||
Plugin plugin = smp.getPlugin();
|
||||
InstalledPluginDescriptor plugin = smp.getPlugin();
|
||||
|
||||
String id = plugin.getInformation().getName(false);
|
||||
|
||||
@@ -431,73 +431,36 @@ public final class PluginProcessor
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param classLoader
|
||||
* @param descriptor
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private Plugin createPlugin(ClassLoader classLoader, Path descriptor)
|
||||
{
|
||||
private InstalledPluginDescriptor createDescriptor(ClassLoader classLoader, Path descriptor) {
|
||||
ClassLoader ctxcl = Thread.currentThread().getContextClassLoader();
|
||||
|
||||
Thread.currentThread().setContextClassLoader(classLoader);
|
||||
|
||||
try
|
||||
{
|
||||
return (Plugin) context.createUnmarshaller().unmarshal(
|
||||
descriptor.toFile());
|
||||
}
|
||||
catch (JAXBException ex)
|
||||
{
|
||||
throw new PluginLoadException(
|
||||
"could not load plugin desriptor ".concat(descriptor.toString()), ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try {
|
||||
return (InstalledPluginDescriptor) context.createUnmarshaller().unmarshal(descriptor.toFile());
|
||||
} catch (JAXBException ex) {
|
||||
throw new PluginLoadException("could not load plugin desriptor ".concat(descriptor.toString()), ex);
|
||||
} finally {
|
||||
Thread.currentThread().setContextClassLoader(ctxcl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param classLoader
|
||||
* @param smp
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private PluginWrapper createPluginWrapper(ClassLoader classLoader,
|
||||
ExplodedSmp smp)
|
||||
throws IOException
|
||||
{
|
||||
PluginWrapper wrapper = null;
|
||||
private InstalledPlugin createPlugin(ClassLoader classLoader, ExplodedSmp smp) throws IOException {
|
||||
InstalledPlugin plugin = null;
|
||||
Path directory = smp.getPath();
|
||||
Path descriptor = directory.resolve(PluginConstants.FILE_DESCRIPTOR);
|
||||
Path descriptorPath = directory.resolve(PluginConstants.FILE_DESCRIPTOR);
|
||||
|
||||
if (Files.exists(descriptor))
|
||||
{
|
||||
if (Files.exists(descriptorPath)) {
|
||||
ClassLoader cl = createClassLoader(classLoader, smp);
|
||||
|
||||
Plugin plugin = createPlugin(cl, descriptor);
|
||||
InstalledPluginDescriptor descriptor = createDescriptor(cl, descriptorPath);
|
||||
|
||||
WebResourceLoader resourceLoader = createWebResourceLoader(directory);
|
||||
|
||||
wrapper = new PluginWrapper(plugin, cl, resourceLoader, directory);
|
||||
}
|
||||
else
|
||||
{
|
||||
plugin = new InstalledPlugin(descriptor, cl, resourceLoader, directory);
|
||||
} else {
|
||||
logger.warn("found plugin directory without plugin descriptor");
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -512,11 +475,11 @@ public final class PluginProcessor
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private Set<PluginWrapper> createPluginWrappers(ClassLoader classLoader,
|
||||
private Set<InstalledPlugin> createPluginWrappers(ClassLoader classLoader,
|
||||
List<PluginNode> rootNodes)
|
||||
throws IOException
|
||||
{
|
||||
Set<PluginWrapper> plugins = Sets.newHashSet();
|
||||
Set<InstalledPlugin> plugins = Sets.newHashSet();
|
||||
|
||||
appendPluginWrappers(plugins, classLoader, rootNodes);
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ public final class PluginTree
|
||||
|
||||
for (ExplodedSmp smp : smpOrdered)
|
||||
{
|
||||
Plugin plugin = smp.getPlugin();
|
||||
InstalledPluginDescriptor plugin = smp.getPlugin();
|
||||
|
||||
if (plugin.getScmVersion() != SCM_VERSION)
|
||||
{
|
||||
|
||||
@@ -87,7 +87,7 @@ public final class PluginsInternal
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public static Set<PluginWrapper> collectPlugins(ClassLoaderLifeCycle classLoaderLifeCycle,
|
||||
public static Set<InstalledPlugin> collectPlugins(ClassLoaderLifeCycle classLoaderLifeCycle,
|
||||
Path directory)
|
||||
throws IOException
|
||||
{
|
||||
@@ -105,7 +105,7 @@ public final class PluginsInternal
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static File createPluginDirectory(File parent, Plugin plugin)
|
||||
public static File createPluginDirectory(File parent, InstalledPluginDescriptor plugin)
|
||||
{
|
||||
PluginInformation info = plugin.getInformation();
|
||||
|
||||
@@ -159,7 +159,7 @@ public final class PluginsInternal
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Iterable<Plugin> unwrap(Iterable<PluginWrapper> wrapped)
|
||||
public static Iterable<InstalledPluginDescriptor> unwrap(Iterable<InstalledPlugin> wrapped)
|
||||
{
|
||||
return Iterables.transform(wrapped, new Unwrap());
|
||||
}
|
||||
@@ -188,7 +188,7 @@ public final class PluginsInternal
|
||||
* @version Enter version here..., 14/06/05
|
||||
* @author Enter your name here...
|
||||
*/
|
||||
private static class Unwrap implements Function<PluginWrapper, Plugin>
|
||||
private static class Unwrap implements Function<InstalledPlugin, InstalledPluginDescriptor>
|
||||
{
|
||||
|
||||
/**
|
||||
@@ -200,9 +200,9 @@ public final class PluginsInternal
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Plugin apply(PluginWrapper wrapper)
|
||||
public InstalledPluginDescriptor apply(InstalledPlugin wrapper)
|
||||
{
|
||||
return wrapper.getPlugin();
|
||||
return wrapper.getDescriptor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public final class UberClassLoader extends ClassLoader
|
||||
* @param parent
|
||||
* @param plugins
|
||||
*/
|
||||
public UberClassLoader(ClassLoader parent, Iterable<PluginWrapper> plugins)
|
||||
public UberClassLoader(ClassLoader parent, Iterable<InstalledPlugin> plugins)
|
||||
{
|
||||
super(parent);
|
||||
this.plugins = plugins;
|
||||
@@ -87,7 +87,7 @@ public final class UberClassLoader extends ClassLoader
|
||||
}
|
||||
|
||||
private Class<?> findClassInPlugins(String name) throws ClassNotFoundException {
|
||||
for (PluginWrapper plugin : plugins) {
|
||||
for (InstalledPlugin plugin : plugins) {
|
||||
Class<?> clazz = findClass(plugin.getClassLoader(), name);
|
||||
if (clazz != null) {
|
||||
return clazz;
|
||||
@@ -119,7 +119,7 @@ public final class UberClassLoader extends ClassLoader
|
||||
{
|
||||
URL url = null;
|
||||
|
||||
for (PluginWrapper plugin : plugins)
|
||||
for (InstalledPlugin plugin : plugins)
|
||||
{
|
||||
ClassLoader cl = plugin.getClassLoader();
|
||||
|
||||
@@ -149,7 +149,7 @@ public final class UberClassLoader extends ClassLoader
|
||||
{
|
||||
List<URL> urls = Lists.newArrayList();
|
||||
|
||||
for (PluginWrapper plugin : plugins)
|
||||
for (InstalledPlugin plugin : plugins)
|
||||
{
|
||||
ClassLoader cl = plugin.getClassLoader();
|
||||
|
||||
@@ -194,5 +194,5 @@ public final class UberClassLoader extends ClassLoader
|
||||
Maps.newConcurrentMap();
|
||||
|
||||
/** Field description */
|
||||
private final Iterable<PluginWrapper> plugins;
|
||||
private final Iterable<InstalledPlugin> plugins;
|
||||
}
|
||||
|
||||
@@ -16,9 +16,11 @@ 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.AvailablePlugin;
|
||||
import sonia.scm.plugin.AvailablePluginDescriptor;
|
||||
import sonia.scm.plugin.PluginCondition;
|
||||
import sonia.scm.plugin.PluginInformation;
|
||||
import sonia.scm.plugin.PluginManager;
|
||||
import sonia.scm.plugin.PluginState;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Provider;
|
||||
@@ -27,6 +29,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@@ -87,10 +90,10 @@ class AvailablePluginResourceTest {
|
||||
|
||||
@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());
|
||||
AvailablePlugin plugin = createPlugin();
|
||||
|
||||
when(pluginManager.getAvailable()).thenReturn(Collections.singletonList(plugin));
|
||||
when(collectionMapper.mapAvailable(Collections.singletonList(plugin))).thenReturn(new MockedResultDto());
|
||||
|
||||
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available");
|
||||
request.accept(VndMediaType.PLUGIN_COLLECTION);
|
||||
@@ -105,16 +108,18 @@ class AvailablePluginResourceTest {
|
||||
@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));
|
||||
|
||||
AvailablePlugin plugin = createPlugin(pluginInformation);
|
||||
|
||||
when(pluginManager.getAvailable("pluginName")).thenReturn(Optional.of(plugin));
|
||||
|
||||
PluginDto pluginDto = new PluginDto();
|
||||
pluginDto.setName("pluginName");
|
||||
when(mapper.map(pluginInformation)).thenReturn(pluginDto);
|
||||
when(mapper.mapAvailable(plugin)).thenReturn(pluginDto);
|
||||
|
||||
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available/pluginName/2.0.0");
|
||||
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available/pluginName");
|
||||
request.accept(VndMediaType.PLUGIN);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
@@ -126,15 +131,36 @@ class AvailablePluginResourceTest {
|
||||
|
||||
@Test
|
||||
void installPlugin() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.post("/v2/plugins/available/pluginName/2.0.0/install");
|
||||
request.accept(VndMediaType.PLUGIN);
|
||||
MockHttpRequest request = MockHttpRequest.post("/v2/plugins/available/pluginName/install");
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
verify(pluginManager).install("pluginName:2.0.0");
|
||||
verify(pluginManager).install("pluginName", false);
|
||||
assertThat(HttpServletResponse.SC_OK).isEqualTo(response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void installPendingPlugin() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.post("/v2/plugins/available/install-pending");
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
verify(pluginManager).installPendingAndRestart();
|
||||
assertThat(HttpServletResponse.SC_OK).isEqualTo(response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private AvailablePlugin createPlugin() {
|
||||
return createPlugin(new PluginInformation());
|
||||
}
|
||||
|
||||
private AvailablePlugin createPlugin(PluginInformation pluginInformation) {
|
||||
AvailablePluginDescriptor descriptor = new AvailablePluginDescriptor(
|
||||
pluginInformation, new PluginCondition(), Collections.emptySet(), "https://download.hitchhiker.com", null
|
||||
);
|
||||
return new AvailablePlugin(descriptor);
|
||||
}
|
||||
|
||||
@Nested
|
||||
@@ -156,7 +182,7 @@ class AvailablePluginResourceTest {
|
||||
|
||||
@Test
|
||||
void shouldNotGetAvailablePluginIfMissingPermission() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available/pluginName/2.0.0");
|
||||
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available/pluginName");
|
||||
request.accept(VndMediaType.PLUGIN);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
@@ -166,7 +192,7 @@ class AvailablePluginResourceTest {
|
||||
@Test
|
||||
void shouldNotInstallPluginIfMissingPermission() throws URISyntaxException {
|
||||
ThreadContext.unbindSubject();
|
||||
MockHttpRequest request = MockHttpRequest.post("/v2/plugins/available/pluginName/2.0.0/install");
|
||||
MockHttpRequest request = MockHttpRequest.post("/v2/plugins/available/pluginName/install");
|
||||
request.accept(VndMediaType.PLUGIN);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
|
||||
@@ -16,11 +16,10 @@ 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.InstalledPlugin;
|
||||
import sonia.scm.plugin.InstalledPluginDescriptor;
|
||||
import sonia.scm.plugin.PluginInformation;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.plugin.PluginState;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.plugin.PluginManager;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Provider;
|
||||
@@ -28,12 +27,12 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
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;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class InstalledPluginResourceTest {
|
||||
@@ -46,15 +45,15 @@ class InstalledPluginResourceTest {
|
||||
@Mock
|
||||
Provider<AvailablePluginResource> availablePluginResourceProvider;
|
||||
|
||||
@Mock
|
||||
private PluginLoader pluginLoader;
|
||||
|
||||
@Mock
|
||||
private PluginDtoCollectionMapper collectionMapper;
|
||||
|
||||
@Mock
|
||||
private PluginDtoMapper mapper;
|
||||
|
||||
@Mock
|
||||
private PluginManager pluginManager;
|
||||
|
||||
@InjectMocks
|
||||
InstalledPluginResource installedPluginResource;
|
||||
|
||||
@@ -86,9 +85,9 @@ class InstalledPluginResourceTest {
|
||||
|
||||
@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());
|
||||
InstalledPlugin installedPlugin = createPlugin();
|
||||
when(pluginManager.getInstalled()).thenReturn(Collections.singletonList(installedPlugin));
|
||||
when(collectionMapper.mapInstalled(Collections.singletonList(installedPlugin))).thenReturn(new MockedResultDto());
|
||||
|
||||
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/installed");
|
||||
request.accept(VndMediaType.PLUGIN_COLLECTION);
|
||||
@@ -105,14 +104,13 @@ class InstalledPluginResourceTest {
|
||||
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));
|
||||
InstalledPlugin installedPlugin = createPlugin(pluginInformation);
|
||||
|
||||
when(pluginManager.getInstalled("pluginName")).thenReturn(Optional.of(installedPlugin));
|
||||
|
||||
PluginDto pluginDto = new PluginDto();
|
||||
pluginDto.setName("pluginName");
|
||||
when(mapper.map(pluginWrapper)).thenReturn(pluginDto);
|
||||
when(mapper.mapInstalled(installedPlugin)).thenReturn(pluginDto);
|
||||
|
||||
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/installed/pluginName");
|
||||
request.accept(VndMediaType.PLUGIN);
|
||||
@@ -125,6 +123,18 @@ class InstalledPluginResourceTest {
|
||||
}
|
||||
}
|
||||
|
||||
private InstalledPlugin createPlugin() {
|
||||
return createPlugin(new PluginInformation());
|
||||
}
|
||||
|
||||
private InstalledPlugin createPlugin(PluginInformation information) {
|
||||
InstalledPlugin plugin = mock(InstalledPlugin.class);
|
||||
InstalledPluginDescriptor descriptor = mock(InstalledPluginDescriptor.class);
|
||||
lenient().when(descriptor.getInformation()).thenReturn(information);
|
||||
lenient().when(plugin.getDescriptor()).thenReturn(descriptor);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithoutAuthorization {
|
||||
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.util.ThreadContext;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.plugin.AvailablePlugin;
|
||||
import sonia.scm.plugin.AvailablePluginDescriptor;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
import sonia.scm.plugin.PluginInformation;
|
||||
import sonia.scm.plugin.PluginState;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PluginDtoMapperTest {
|
||||
@@ -21,11 +31,25 @@ class PluginDtoMapperTest {
|
||||
@InjectMocks
|
||||
private PluginDtoMapperImpl mapper;
|
||||
|
||||
@Mock
|
||||
private Subject subject;
|
||||
|
||||
@BeforeEach
|
||||
void bindSubject() {
|
||||
ThreadContext.bind(subject);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void unbindSubject() {
|
||||
ThreadContext.unbindSubject();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMapInformation() {
|
||||
PluginInformation information = createPluginInformation();
|
||||
|
||||
PluginDto dto = mapper.map(information);
|
||||
PluginDto dto = new PluginDto();
|
||||
mapper.map(information, dto);
|
||||
|
||||
assertThat(dto.getName()).isEqualTo("scm-cas-plugin");
|
||||
assertThat(dto.getVersion()).isEqualTo("1.0.0");
|
||||
@@ -48,41 +72,76 @@ class PluginDtoMapperTest {
|
||||
|
||||
@Test
|
||||
void shouldAppendInstalledSelfLink() {
|
||||
PluginInformation information = createPluginInformation();
|
||||
information.setState(PluginState.INSTALLED);
|
||||
InstalledPlugin plugin = createInstalled();
|
||||
|
||||
PluginDto dto = mapper.map(information);
|
||||
PluginDto dto = mapper.mapInstalled(plugin);
|
||||
assertThat(dto.getLinks().getLinkBy("self").get().getHref())
|
||||
.isEqualTo("https://hitchhiker.com/v2/plugins/installed/scm-cas-plugin");
|
||||
}
|
||||
|
||||
private InstalledPlugin createInstalled(PluginInformation information) {
|
||||
InstalledPlugin plugin = mock(InstalledPlugin.class, Answers.RETURNS_DEEP_STUBS);
|
||||
when(plugin.getDescriptor().getInformation()).thenReturn(information);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendAvailableSelfLink() {
|
||||
PluginInformation information = createPluginInformation();
|
||||
information.setState(PluginState.AVAILABLE);
|
||||
AvailablePlugin plugin = createAvailable();
|
||||
|
||||
PluginDto dto = mapper.map(information);
|
||||
PluginDto dto = mapper.mapAvailable(plugin);
|
||||
assertThat(dto.getLinks().getLinkBy("self").get().getHref())
|
||||
.isEqualTo("https://hitchhiker.com/v2/plugins/available/scm-cas-plugin/1.0.0");
|
||||
.isEqualTo("https://hitchhiker.com/v2/plugins/available/scm-cas-plugin");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotAppendInstallLinkWithoutPermissions() {
|
||||
AvailablePlugin plugin = createAvailable();
|
||||
|
||||
PluginDto dto = mapper.mapAvailable(plugin);
|
||||
assertThat(dto.getLinks().getLinkBy("install")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendInstallLink() {
|
||||
PluginInformation information = createPluginInformation();
|
||||
information.setState(PluginState.AVAILABLE);
|
||||
when(subject.isPermitted("plugin:manage")).thenReturn(true);
|
||||
AvailablePlugin plugin = createAvailable();
|
||||
|
||||
PluginDto dto = mapper.map(information);
|
||||
PluginDto dto = mapper.mapAvailable(plugin);
|
||||
assertThat(dto.getLinks().getLinkBy("install").get().getHref())
|
||||
.isEqualTo("https://hitchhiker.com/v2/plugins/available/scm-cas-plugin/1.0.0/install");
|
||||
.isEqualTo("https://hitchhiker.com/v2/plugins/available/scm-cas-plugin/install");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnMiscellaneousIfCategoryIsNull() {
|
||||
PluginInformation information = createPluginInformation();
|
||||
information.setCategory(null);
|
||||
|
||||
PluginDto dto = mapper.map(information);
|
||||
AvailablePlugin plugin = createAvailable(information);
|
||||
PluginDto dto = mapper.mapAvailable(plugin);
|
||||
assertThat(dto.getCategory()).isEqualTo("Miscellaneous");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendDependencies() {
|
||||
AvailablePlugin plugin = createAvailable();
|
||||
when(plugin.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("one", "two"));
|
||||
|
||||
PluginDto dto = mapper.mapAvailable(plugin);
|
||||
assertThat(dto.getDependencies()).containsOnly("one", "two");
|
||||
}
|
||||
|
||||
private InstalledPlugin createInstalled() {
|
||||
return createInstalled(createPluginInformation());
|
||||
}
|
||||
|
||||
private AvailablePlugin createAvailable() {
|
||||
return createAvailable(createPluginInformation());
|
||||
}
|
||||
|
||||
private AvailablePlugin createAvailable(PluginInformation information) {
|
||||
AvailablePluginDescriptor descriptor = mock(AvailablePluginDescriptor.class);
|
||||
when(descriptor.getInformation()).thenReturn(information);
|
||||
return new AvailablePlugin(descriptor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ public class UIRootResourceTest {
|
||||
assertTrue(response.getContentAsString().contains("/scm/my/bundle.js"));
|
||||
}
|
||||
|
||||
private void mockPlugins(PluginWrapper... plugins) {
|
||||
private void mockPlugins(InstalledPlugin... plugins) {
|
||||
when(pluginLoader.getInstalledPlugins()).thenReturn(Lists.newArrayList(plugins));
|
||||
}
|
||||
|
||||
@@ -180,16 +180,16 @@ public class UIRootResourceTest {
|
||||
return new PluginResources(scripts, styles);
|
||||
}
|
||||
|
||||
private PluginWrapper mockPlugin(String id) {
|
||||
private InstalledPlugin mockPlugin(String id) {
|
||||
return mockPlugin(id, id, null);
|
||||
}
|
||||
|
||||
private PluginWrapper mockPlugin(String id, String name, PluginResources pluginResources) {
|
||||
PluginWrapper wrapper = mock(PluginWrapper.class);
|
||||
private InstalledPlugin mockPlugin(String id, String name, PluginResources pluginResources) {
|
||||
InstalledPlugin wrapper = mock(InstalledPlugin.class);
|
||||
when(wrapper.getId()).thenReturn(id);
|
||||
|
||||
Plugin plugin = mock(Plugin.class);
|
||||
when(wrapper.getPlugin()).thenReturn(plugin);
|
||||
InstalledPluginDescriptor plugin = mock(InstalledPluginDescriptor.class);
|
||||
when(wrapper.getDescriptor()).thenReturn(plugin);
|
||||
when(plugin.getResources()).thenReturn(pluginResources);
|
||||
|
||||
PluginInformation information = mock(PluginInformation.class);
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.util.ThreadContext;
|
||||
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.Answers;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.lifecycle.RestartEvent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.in;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DefaultPluginManagerTest {
|
||||
|
||||
@Mock
|
||||
private ScmEventBus eventBus;
|
||||
|
||||
@Mock
|
||||
private PluginLoader loader;
|
||||
|
||||
@Mock
|
||||
private PluginCenter center;
|
||||
|
||||
@Mock
|
||||
private PluginInstaller installer;
|
||||
|
||||
@InjectMocks
|
||||
private DefaultPluginManager manager;
|
||||
|
||||
@Mock
|
||||
private Subject subject;
|
||||
|
||||
@BeforeEach
|
||||
void mockInstaller() {
|
||||
lenient().when(installer.install(any())).then(ic -> {
|
||||
AvailablePlugin plugin = ic.getArgument(0);
|
||||
return new PendingPluginInstallation(plugin.install(), null);
|
||||
});
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithAdminPermissions {
|
||||
|
||||
@BeforeEach
|
||||
void setUpSubject() {
|
||||
ThreadContext.bind(subject);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void clearThreadContext() {
|
||||
ThreadContext.unbindSubject();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnInstalledPlugins() {
|
||||
InstalledPlugin review = createInstalled("scm-review-plugin");
|
||||
InstalledPlugin git = createInstalled("scm-git-plugin");
|
||||
|
||||
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(review, git));
|
||||
|
||||
List<InstalledPlugin> installed = manager.getInstalled();
|
||||
assertThat(installed).containsOnly(review, git);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnReviewPlugin() {
|
||||
InstalledPlugin review = createInstalled("scm-review-plugin");
|
||||
InstalledPlugin git = createInstalled("scm-git-plugin");
|
||||
|
||||
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(review, git));
|
||||
|
||||
Optional<InstalledPlugin> plugin = manager.getInstalled("scm-review-plugin");
|
||||
assertThat(plugin).contains(review);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyForNonInstalledPlugin() {
|
||||
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of());
|
||||
|
||||
Optional<InstalledPlugin> plugin = manager.getInstalled("scm-review-plugin");
|
||||
assertThat(plugin).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnAvailablePlugins() {
|
||||
AvailablePlugin review = createAvailable("scm-review-plugin");
|
||||
AvailablePlugin git = createAvailable("scm-git-plugin");
|
||||
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, git));
|
||||
|
||||
List<AvailablePlugin> available = manager.getAvailable();
|
||||
assertThat(available).containsOnly(review, git);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFilterOutAllInstalled() {
|
||||
InstalledPlugin installedGit = createInstalled("scm-git-plugin");
|
||||
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedGit));
|
||||
|
||||
AvailablePlugin review = createAvailable("scm-review-plugin");
|
||||
AvailablePlugin git = createAvailable("scm-git-plugin");
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, git));
|
||||
|
||||
List<AvailablePlugin> available = manager.getAvailable();
|
||||
assertThat(available).containsOnly(review);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnAvailable() {
|
||||
AvailablePlugin review = createAvailable("scm-review-plugin");
|
||||
AvailablePlugin git = createAvailable("scm-git-plugin");
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, git));
|
||||
|
||||
Optional<AvailablePlugin> available = manager.getAvailable("scm-git-plugin");
|
||||
assertThat(available).contains(git);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyForNonExistingAvailable() {
|
||||
AvailablePlugin review = createAvailable("scm-review-plugin");
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(review));
|
||||
|
||||
Optional<AvailablePlugin> available = manager.getAvailable("scm-git-plugin");
|
||||
assertThat(available).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyForInstalledPlugin() {
|
||||
InstalledPlugin installedGit = createInstalled("scm-git-plugin");
|
||||
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedGit));
|
||||
|
||||
AvailablePlugin git = createAvailable("scm-git-plugin");
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(git));
|
||||
|
||||
Optional<AvailablePlugin> available = manager.getAvailable("scm-git-plugin");
|
||||
assertThat(available).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstallThePlugin() {
|
||||
AvailablePlugin git = createAvailable("scm-git-plugin");
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(git));
|
||||
|
||||
manager.install("scm-git-plugin", false);
|
||||
|
||||
verify(installer).install(git);
|
||||
verify(eventBus, never()).post(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstallDependingPlugins() {
|
||||
AvailablePlugin review = createAvailable("scm-review-plugin");
|
||||
when(review.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin"));
|
||||
AvailablePlugin mail = createAvailable("scm-mail-plugin");
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail));
|
||||
|
||||
manager.install("scm-review-plugin", false);
|
||||
|
||||
verify(installer).install(mail);
|
||||
verify(installer).install(review);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotInstallAlreadyInstalledDependencies() {
|
||||
AvailablePlugin review = createAvailable("scm-review-plugin");
|
||||
when(review.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin"));
|
||||
AvailablePlugin mail = createAvailable("scm-mail-plugin");
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail));
|
||||
|
||||
InstalledPlugin installedMail = createInstalled("scm-mail-plugin");
|
||||
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedMail));
|
||||
|
||||
manager.install("scm-review-plugin", false);
|
||||
|
||||
ArgumentCaptor<AvailablePlugin> captor = ArgumentCaptor.forClass(AvailablePlugin.class);
|
||||
verify(installer).install(captor.capture());
|
||||
|
||||
assertThat(captor.getValue().getDescriptor().getInformation().getName()).isEqualTo("scm-review-plugin");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRollbackOnFailedInstallation() {
|
||||
AvailablePlugin review = createAvailable("scm-review-plugin");
|
||||
when(review.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin"));
|
||||
AvailablePlugin mail = createAvailable("scm-mail-plugin");
|
||||
when(mail.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-notification-plugin"));
|
||||
AvailablePlugin notification = createAvailable("scm-notification-plugin");
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail, notification));
|
||||
|
||||
PendingPluginInstallation pendingNotification = mock(PendingPluginInstallation.class);
|
||||
doReturn(pendingNotification).when(installer).install(notification);
|
||||
|
||||
PendingPluginInstallation pendingMail = mock(PendingPluginInstallation.class);
|
||||
doReturn(pendingMail).when(installer).install(mail);
|
||||
|
||||
doThrow(new PluginChecksumMismatchException("checksum does not match")).when(installer).install(review);
|
||||
|
||||
assertThrows(PluginInstallException.class, () -> manager.install("scm-review-plugin", false));
|
||||
|
||||
verify(pendingNotification).cancel();
|
||||
verify(pendingMail).cancel();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstallNothingIfOneOfTheDependenciesIsNotAvailable() {
|
||||
AvailablePlugin review = createAvailable("scm-review-plugin");
|
||||
when(review.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin"));
|
||||
AvailablePlugin mail = createAvailable("scm-mail-plugin");
|
||||
when(mail.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-notification-plugin"));
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail));
|
||||
|
||||
assertThrows(NotFoundException.class, () -> manager.install("scm-review-plugin", false));
|
||||
|
||||
verify(installer, never()).install(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSendRestartEventAfterInstallation() {
|
||||
AvailablePlugin git = createAvailable("scm-git-plugin");
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(git));
|
||||
|
||||
manager.install("scm-git-plugin", true);
|
||||
|
||||
verify(installer).install(git);
|
||||
verify(eventBus).post(any(RestartEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotSendRestartEventIfNoPluginWasInstalled() {
|
||||
InstalledPlugin gitInstalled = createInstalled("scm-git-plugin");
|
||||
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(gitInstalled));
|
||||
|
||||
manager.install("scm-git-plugin", true);
|
||||
verify(eventBus, never()).post(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotInstallAlreadyPendingPlugins() {
|
||||
AvailablePlugin review = createAvailable("scm-review-plugin");
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(review));
|
||||
|
||||
manager.install("scm-review-plugin", false);
|
||||
manager.install("scm-review-plugin", false);
|
||||
// only one interaction
|
||||
verify(installer).install(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSendRestartEvent() {
|
||||
AvailablePlugin review = createAvailable("scm-review-plugin");
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(review));
|
||||
|
||||
manager.install("scm-review-plugin", false);
|
||||
manager.installPendingAndRestart();
|
||||
|
||||
verify(eventBus).post(any(RestartEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotSendRestartEventWithoutPendingPlugins() {
|
||||
manager.installPendingAndRestart();
|
||||
|
||||
verify(eventBus, never()).post(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnSingleAvailableAsPending() {
|
||||
AvailablePlugin review = createAvailable("scm-review-plugin");
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(review));
|
||||
|
||||
manager.install("scm-review-plugin", false);
|
||||
|
||||
Optional<AvailablePlugin> available = manager.getAvailable("scm-review-plugin");
|
||||
assertThat(available.get().isPending()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnAvailableAsPending() {
|
||||
AvailablePlugin review = createAvailable("scm-review-plugin");
|
||||
when(center.getAvailable()).thenReturn(ImmutableSet.of(review));
|
||||
|
||||
manager.install("scm-review-plugin", false);
|
||||
|
||||
List<AvailablePlugin> available = manager.getAvailable();
|
||||
assertThat(available.get(0).isPending()).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithoutReadPermissions {
|
||||
|
||||
@BeforeEach
|
||||
void setUpSubject() {
|
||||
ThreadContext.bind(subject);
|
||||
doThrow(AuthorizationException.class).when(subject).checkPermission("plugin:read");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void clearThreadContext() {
|
||||
ThreadContext.unbindSubject();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowAuthorizationExceptionsForReadMethods() {
|
||||
assertThrows(AuthorizationException.class, () -> manager.getInstalled());
|
||||
assertThrows(AuthorizationException.class, () -> manager.getInstalled("test"));
|
||||
assertThrows(AuthorizationException.class, () -> manager.getAvailable());
|
||||
assertThrows(AuthorizationException.class, () -> manager.getAvailable("test"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithoutManagePermissions {
|
||||
|
||||
@BeforeEach
|
||||
void setUpSubject() {
|
||||
ThreadContext.bind(subject);
|
||||
doThrow(AuthorizationException.class).when(subject).checkPermission("plugin:manage");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void clearThreadContext() {
|
||||
ThreadContext.unbindSubject();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowAuthorizationExceptionsForInstallMethod() {
|
||||
assertThrows(AuthorizationException.class, () -> manager.install("test", false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowAuthorizationExceptionsForInstallPendingAndRestart() {
|
||||
assertThrows(AuthorizationException.class, () -> manager.installPendingAndRestart());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private AvailablePlugin createAvailable(String name) {
|
||||
PluginInformation information = new PluginInformation();
|
||||
information.setName(name);
|
||||
return createAvailable(information);
|
||||
}
|
||||
|
||||
private InstalledPlugin createInstalled(String name) {
|
||||
PluginInformation information = new PluginInformation();
|
||||
information.setName(name);
|
||||
return createInstalled(information);
|
||||
}
|
||||
|
||||
private InstalledPlugin createInstalled(PluginInformation information) {
|
||||
InstalledPlugin plugin = mock(InstalledPlugin.class, Answers.RETURNS_DEEP_STUBS);
|
||||
returnInformation(plugin, information);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private AvailablePlugin createAvailable(PluginInformation information) {
|
||||
AvailablePluginDescriptor descriptor = mock(AvailablePluginDescriptor.class);
|
||||
lenient().when(descriptor.getInformation()).thenReturn(information);
|
||||
return new AvailablePlugin(descriptor);
|
||||
}
|
||||
|
||||
private void returnInformation(Plugin mockedPlugin, PluginInformation information) {
|
||||
when(mockedPlugin.getDescriptor().getInformation()).thenReturn(information);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -102,7 +102,7 @@ public class DefaultUberWebResourceLoaderTest extends WebResourceLoaderTestBase
|
||||
public void testGetResourceFromCache() {
|
||||
DefaultUberWebResourceLoader resourceLoader =
|
||||
new DefaultUberWebResourceLoader(servletContext,
|
||||
new ArrayList<PluginWrapper>(), Stage.PRODUCTION);
|
||||
new ArrayList<InstalledPlugin>(), Stage.PRODUCTION);
|
||||
|
||||
resourceLoader.getCache().put("/myresource", GITHUB);
|
||||
|
||||
@@ -131,8 +131,8 @@ public class DefaultUberWebResourceLoaderTest extends WebResourceLoaderTestBase
|
||||
{
|
||||
File directory = temp.newFolder();
|
||||
File file = file(directory, "myresource");
|
||||
PluginWrapper wrapper = createPluginWrapper(directory);
|
||||
List<PluginWrapper> plugins = Lists.newArrayList(wrapper);
|
||||
InstalledPlugin wrapper = createPluginWrapper(directory);
|
||||
List<InstalledPlugin> plugins = Lists.newArrayList(wrapper);
|
||||
WebResourceLoader resourceLoader =
|
||||
new DefaultUberWebResourceLoader(servletContext, plugins);
|
||||
|
||||
@@ -170,8 +170,8 @@ public class DefaultUberWebResourceLoaderTest extends WebResourceLoaderTestBase
|
||||
|
||||
File directory = temp.newFolder();
|
||||
File file = file(directory, "myresource");
|
||||
PluginWrapper wrapper = createPluginWrapper(directory);
|
||||
List<PluginWrapper> plugins = Lists.newArrayList(wrapper);
|
||||
InstalledPlugin wrapper = createPluginWrapper(directory);
|
||||
List<InstalledPlugin> plugins = Lists.newArrayList(wrapper);
|
||||
|
||||
UberWebResourceLoader resourceLoader =
|
||||
new DefaultUberWebResourceLoader(servletContext, plugins);
|
||||
@@ -197,11 +197,11 @@ public class DefaultUberWebResourceLoaderTest extends WebResourceLoaderTestBase
|
||||
WebResourceLoader loader = mock(WebResourceLoader.class);
|
||||
when(loader.getResource("/myresource")).thenReturn(url);
|
||||
|
||||
PluginWrapper pluginWrapper = mock(PluginWrapper.class);
|
||||
when(pluginWrapper.getWebResourceLoader()).thenReturn(loader);
|
||||
InstalledPlugin installedPlugin = mock(InstalledPlugin.class);
|
||||
when(installedPlugin.getWebResourceLoader()).thenReturn(loader);
|
||||
|
||||
WebResourceLoader resourceLoader =
|
||||
new DefaultUberWebResourceLoader(servletContext, Lists.newArrayList(pluginWrapper));
|
||||
new DefaultUberWebResourceLoader(servletContext, Lists.newArrayList(installedPlugin));
|
||||
|
||||
assertNull(resourceLoader.getResource("/myresource"));
|
||||
}
|
||||
@@ -214,11 +214,11 @@ public class DefaultUberWebResourceLoaderTest extends WebResourceLoaderTestBase
|
||||
WebResourceLoader loader = mock(WebResourceLoader.class);
|
||||
when(loader.getResource("/myresource")).thenReturn(url);
|
||||
|
||||
PluginWrapper pluginWrapper = mock(PluginWrapper.class);
|
||||
when(pluginWrapper.getWebResourceLoader()).thenReturn(loader);
|
||||
InstalledPlugin installedPlugin = mock(InstalledPlugin.class);
|
||||
when(installedPlugin.getWebResourceLoader()).thenReturn(loader);
|
||||
|
||||
UberWebResourceLoader resourceLoader =
|
||||
new DefaultUberWebResourceLoader(servletContext, Lists.newArrayList(pluginWrapper));
|
||||
new DefaultUberWebResourceLoader(servletContext, Lists.newArrayList(installedPlugin));
|
||||
|
||||
List<URL> resources = resourceLoader.getResources("/myresource");
|
||||
Assertions.assertThat(resources).isEmpty();
|
||||
@@ -232,7 +232,7 @@ public class DefaultUberWebResourceLoaderTest extends WebResourceLoaderTestBase
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private PluginWrapper createPluginWrapper(File directory)
|
||||
private InstalledPlugin createPluginWrapper(File directory)
|
||||
{
|
||||
return createPluginWrapper(directory.toPath());
|
||||
}
|
||||
@@ -245,9 +245,9 @@ public class DefaultUberWebResourceLoaderTest extends WebResourceLoaderTestBase
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private PluginWrapper createPluginWrapper(Path directory)
|
||||
private InstalledPlugin createPluginWrapper(Path directory)
|
||||
{
|
||||
return new PluginWrapper(null, null, new PathWebResourceLoader(directory),
|
||||
return new InstalledPlugin(null, null, new PathWebResourceLoader(directory),
|
||||
directory);
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ public class ExplodedSmpTest
|
||||
info.setName(name);
|
||||
info.setVersion(version);
|
||||
|
||||
Plugin plugin = new Plugin(2, info, null, null, false,
|
||||
InstalledPluginDescriptor plugin = new InstalledPluginDescriptor(2, info, null, null, false,
|
||||
Sets.newSet(dependencies));
|
||||
|
||||
return new ExplodedSmp(null, plugin);
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junitpioneer.jupiter.TempDirectory;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith({MockitoExtension.class, TempDirectory.class})
|
||||
class PendingPluginInstallationTest {
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private AvailablePlugin plugin;
|
||||
|
||||
@Test
|
||||
void shouldDeleteFileOnCancel(@TempDirectory.TempDir Path directory) throws IOException {
|
||||
Path file = directory.resolve("file");
|
||||
Files.write(file, "42".getBytes());
|
||||
|
||||
when(plugin.getDescriptor().getInformation().getName()).thenReturn("scm-awesome-plugin");
|
||||
|
||||
PendingPluginInstallation installation = new PendingPluginInstallation(plugin, file);
|
||||
installation.cancel();
|
||||
|
||||
assertThat(file).doesNotExist();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowExceptionIfCancelFailed(@TempDirectory.TempDir Path directory) {
|
||||
Path file = directory.resolve("file");
|
||||
when(plugin.getDescriptor().getInformation().getName()).thenReturn("scm-awesome-plugin");
|
||||
|
||||
PendingPluginInstallation installation = new PendingPluginInstallation(plugin, file);
|
||||
assertThrows(PluginFailedToCancelInstallationException.class, installation::cancel);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,13 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -10,11 +17,19 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.plugin.PluginCenterDto.Plugin;
|
||||
import static sonia.scm.plugin.PluginCenterDto.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PluginCenterDtoMapperTest {
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private PluginCenterDto dto;
|
||||
|
||||
@InjectMocks
|
||||
private PluginCenterDtoMapperImpl mapper;
|
||||
|
||||
@Test
|
||||
void shouldMapSinglePlugin() {
|
||||
Plugin plugin = new Plugin(
|
||||
@@ -27,19 +42,26 @@ class PluginCenterDtoMapperTest {
|
||||
"http://avatar.url",
|
||||
"555000444",
|
||||
new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
|
||||
new Dependency("scm-review-plugin"),
|
||||
new HashMap<>());
|
||||
ImmutableSet.of("scm-review-plugin"),
|
||||
ImmutableMap.of("download", new Link("http://download.hitchhiker.com"))
|
||||
);
|
||||
|
||||
PluginInformation result = PluginCenterDtoMapper.map(Collections.singletonList(plugin)).iterator().next();
|
||||
when(dto.getEmbedded().getPlugins()).thenReturn(Collections.singletonList(plugin));
|
||||
AvailablePluginDescriptor descriptor = mapper.map(dto).iterator().next().getDescriptor();
|
||||
PluginInformation information = descriptor.getInformation();
|
||||
PluginCondition condition = descriptor.getCondition();
|
||||
|
||||
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());
|
||||
assertThat(descriptor.getUrl()).isEqualTo("http://download.hitchhiker.com");
|
||||
assertThat(descriptor.getChecksum()).contains("555000444");
|
||||
|
||||
assertThat(information.getAuthor()).isEqualTo(plugin.getAuthor());
|
||||
assertThat(information.getCategory()).isEqualTo(plugin.getCategory());
|
||||
assertThat(information.getVersion()).isEqualTo(plugin.getVersion());
|
||||
assertThat(condition.getArch()).isEqualTo(plugin.getConditions().getArch());
|
||||
assertThat(condition.getMinVersion()).isEqualTo(plugin.getConditions().getMinVersion());
|
||||
assertThat(condition.getOs().iterator().next()).isEqualTo(plugin.getConditions().getOs().iterator().next());
|
||||
assertThat(information.getDescription()).isEqualTo(plugin.getDescription());
|
||||
assertThat(information.getName()).isEqualTo(plugin.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -54,8 +76,9 @@ class PluginCenterDtoMapperTest {
|
||||
"https://avatar.url",
|
||||
"12345678aa",
|
||||
new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
|
||||
new Dependency("scm-review-plugin"),
|
||||
new HashMap<>());
|
||||
ImmutableSet.of("scm-review-plugin"),
|
||||
ImmutableMap.of("download", new Link("http://download.hitchhiker.com/review"))
|
||||
);
|
||||
|
||||
Plugin plugin2 = new Plugin(
|
||||
"scm-hitchhiker-plugin",
|
||||
@@ -67,15 +90,16 @@ class PluginCenterDtoMapperTest {
|
||||
"http://avatar.url",
|
||||
"555000444",
|
||||
new Condition(Collections.singletonList("linux"), "amd64","2.0.0"),
|
||||
new Dependency("scm-review-plugin"),
|
||||
new HashMap<>());
|
||||
ImmutableSet.of("scm-review-plugin"),
|
||||
ImmutableMap.of("download", new Link("http://download.hitchhiker.com/hitchhiker"))
|
||||
);
|
||||
|
||||
Set<PluginInformation> resultSet = PluginCenterDtoMapper.map(Arrays.asList(plugin1, plugin2));
|
||||
when(dto.getEmbedded().getPlugins()).thenReturn(Arrays.asList(plugin1, plugin2));
|
||||
|
||||
List<PluginInformation> pluginsList = new ArrayList<>(resultSet);
|
||||
Set<AvailablePlugin> resultSet = mapper.map(dto);
|
||||
|
||||
PluginInformation pluginInformation1 = pluginsList.get(1);
|
||||
PluginInformation pluginInformation2 = pluginsList.get(0);
|
||||
PluginInformation pluginInformation1 = findPlugin(resultSet, plugin1.getName());
|
||||
PluginInformation pluginInformation2 = findPlugin(resultSet, plugin2.getName());
|
||||
|
||||
assertThat(pluginInformation1.getAuthor()).isEqualTo(plugin1.getAuthor());
|
||||
assertThat(pluginInformation1.getVersion()).isEqualTo(plugin1.getVersion());
|
||||
@@ -83,4 +107,14 @@ class PluginCenterDtoMapperTest {
|
||||
assertThat(pluginInformation2.getVersion()).isEqualTo(plugin2.getVersion());
|
||||
assertThat(resultSet.size()).isEqualTo(2);
|
||||
}
|
||||
|
||||
private PluginInformation findPlugin(Set<AvailablePlugin> resultSet, String name) {
|
||||
return resultSet
|
||||
.stream()
|
||||
.filter(p -> name.equals(p.getDescriptor().getInformation().getName()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("could not find plugin " + name))
|
||||
.getDescriptor()
|
||||
.getInformation();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PluginCenterLoaderTest {
|
||||
|
||||
private static final String PLUGIN_URL = "https://plugins.hitchhiker.com";
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private AdvancedHttpClient client;
|
||||
|
||||
@Mock
|
||||
private PluginCenterDtoMapper mapper;
|
||||
|
||||
@InjectMocks
|
||||
private PluginCenterLoader loader;
|
||||
|
||||
@Test
|
||||
void shouldFetch() throws IOException {
|
||||
Set<AvailablePlugin> plugins = Collections.emptySet();
|
||||
PluginCenterDto dto = new PluginCenterDto();
|
||||
when(client.get(PLUGIN_URL).request().contentFromJson(PluginCenterDto.class)).thenReturn(dto);
|
||||
when(mapper.map(dto)).thenReturn(plugins);
|
||||
|
||||
Set<AvailablePlugin> fetched = loader.load(PLUGIN_URL);
|
||||
assertThat(fetched).isSameAs(plugins);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptySetIfPluginCenterNotBeReached() throws IOException {
|
||||
when(client.get(PLUGIN_URL).request()).thenThrow(new IOException("failed to fetch"));
|
||||
|
||||
Set<AvailablePlugin> fetch = loader.load(PLUGIN_URL);
|
||||
assertThat(fetch).isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.cache.CacheManager;
|
||||
import sonia.scm.cache.MapCacheManager;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
import sonia.scm.util.SystemUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PluginCenterTest {
|
||||
|
||||
private static final String PLUGIN_URL_BASE = "https://plugins.hitchhiker.com/";
|
||||
private static final String PLUGIN_URL = PLUGIN_URL_BASE + "{version}";
|
||||
|
||||
@Mock
|
||||
private PluginCenterLoader loader;
|
||||
|
||||
@Mock
|
||||
private SCMContextProvider contextProvider;
|
||||
|
||||
private ScmConfiguration configuration;
|
||||
|
||||
private CacheManager cacheManager;
|
||||
|
||||
private PluginCenter pluginCenter;
|
||||
|
||||
@BeforeEach
|
||||
void setUpPluginCenter() {
|
||||
when(contextProvider.getVersion()).thenReturn("2.0.0");
|
||||
|
||||
cacheManager = new MapCacheManager();
|
||||
|
||||
configuration = new ScmConfiguration();
|
||||
configuration.setPluginUrl(PLUGIN_URL);
|
||||
|
||||
pluginCenter = new PluginCenter(contextProvider, cacheManager, configuration, loader);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFetchPlugins() {
|
||||
Set<AvailablePlugin> plugins = new HashSet<>();
|
||||
when(loader.load(PLUGIN_URL_BASE + "2.0.0")).thenReturn(plugins);
|
||||
|
||||
assertThat(pluginCenter.getAvailable()).isSameAs(plugins);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCache() {
|
||||
Set<AvailablePlugin> first = new HashSet<>();
|
||||
when(loader.load(anyString())).thenReturn(first, new HashSet<>());
|
||||
|
||||
assertThat(pluginCenter.getAvailable()).isSameAs(first);
|
||||
assertThat(pluginCenter.getAvailable()).isSameAs(first);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junitpioneer.jupiter.TempDirectory;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
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.*;
|
||||
|
||||
@ExtendWith({MockitoExtension.class, TempDirectory.class})
|
||||
class PluginInstallerTest {
|
||||
|
||||
@Mock
|
||||
private SCMContextProvider context;
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private AdvancedHttpClient client;
|
||||
|
||||
@InjectMocks
|
||||
private PluginInstaller installer;
|
||||
|
||||
private Path directory;
|
||||
|
||||
@BeforeEach
|
||||
void setUpContext(@TempDirectory.TempDir Path directory) {
|
||||
this.directory = directory;
|
||||
lenient().when(context.resolve(any())).then(ic -> {
|
||||
Path arg = ic.getArgument(0);
|
||||
return directory.resolve(arg);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDownloadPlugin() throws IOException {
|
||||
mockContent("42");
|
||||
|
||||
installer.install(createGitPlugin());
|
||||
|
||||
assertThat(directory.resolve("plugins").resolve("scm-git-plugin.smp")).hasContent("42");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnPendingPluginInstallation() throws IOException {
|
||||
mockContent("42");
|
||||
AvailablePlugin gitPlugin = createGitPlugin();
|
||||
|
||||
PendingPluginInstallation pending = installer.install(gitPlugin);
|
||||
|
||||
assertThat(pending).isNotNull();
|
||||
assertThat(pending.getPlugin().getDescriptor()).isEqualTo(gitPlugin.getDescriptor());
|
||||
assertThat(pending.getPlugin().isPending()).isTrue();
|
||||
}
|
||||
|
||||
private void mockContent(String content) throws IOException {
|
||||
when(client.get("https://download.hitchhiker.com").request().contentAsStream())
|
||||
.thenReturn(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
private AvailablePlugin createGitPlugin() {
|
||||
return createPlugin(
|
||||
"scm-git-plugin",
|
||||
"https://download.hitchhiker.com",
|
||||
"73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049" // 42
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowPluginDownloadException() throws IOException {
|
||||
when(client.get("https://download.hitchhiker.com").request()).thenThrow(new IOException("failed to download"));
|
||||
|
||||
assertThrows(PluginDownloadException.class, () -> installer.install(createGitPlugin()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowPluginChecksumMismatchException() throws IOException {
|
||||
mockContent("21");
|
||||
|
||||
assertThrows(PluginChecksumMismatchException.class, () -> installer.install(createGitPlugin()));
|
||||
assertThat(directory.resolve("plugins").resolve("scm-git-plugin.smp")).doesNotExist();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowPluginDownloadExceptionAndCleanup() throws IOException {
|
||||
InputStream stream = mock(InputStream.class);
|
||||
when(stream.read(any(), anyInt(), anyInt())).thenThrow(new IOException("failed to read"));
|
||||
when(client.get("https://download.hitchhiker.com").request().contentAsStream()).thenReturn(stream);
|
||||
|
||||
assertThrows(PluginDownloadException.class, () -> installer.install(createGitPlugin()));
|
||||
assertThat(directory.resolve("plugins").resolve("scm-git-plugin.smp")).doesNotExist();
|
||||
}
|
||||
|
||||
|
||||
private AvailablePlugin createPlugin(String name, String url, String checksum) {
|
||||
PluginInformation information = new PluginInformation();
|
||||
information.setName(name);
|
||||
AvailablePluginDescriptor descriptor = new AvailablePluginDescriptor(
|
||||
information, null, Collections.emptySet(), url, checksum
|
||||
);
|
||||
return new AvailablePlugin(descriptor);
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@ public class PluginProcessorTest
|
||||
{
|
||||
copySmp(PLUGIN_A);
|
||||
|
||||
PluginWrapper plugin = collectAndGetFirst();
|
||||
InstalledPlugin plugin = collectAndGetFirst();
|
||||
|
||||
assertThat(plugin.getId(), is(PLUGIN_A.id));
|
||||
}
|
||||
@@ -145,15 +145,15 @@ public class PluginProcessorTest
|
||||
{
|
||||
copySmps(PLUGIN_A, PLUGIN_B);
|
||||
|
||||
Set<PluginWrapper> plugins = collectPlugins();
|
||||
Set<InstalledPlugin> plugins = collectPlugins();
|
||||
|
||||
assertThat(plugins, hasSize(2));
|
||||
|
||||
PluginWrapper a = findPlugin(plugins, PLUGIN_A.id);
|
||||
InstalledPlugin a = findPlugin(plugins, PLUGIN_A.id);
|
||||
|
||||
assertNotNull(a);
|
||||
|
||||
PluginWrapper b = findPlugin(plugins, PLUGIN_B.id);
|
||||
InstalledPlugin b = findPlugin(plugins, PLUGIN_B.id);
|
||||
|
||||
assertNotNull(b);
|
||||
}
|
||||
@@ -178,7 +178,7 @@ public class PluginProcessorTest
|
||||
{
|
||||
copySmp(PLUGIN_A);
|
||||
|
||||
PluginWrapper plugin = collectAndGetFirst();
|
||||
InstalledPlugin plugin = collectAndGetFirst();
|
||||
ClassLoader cl = plugin.getClassLoader();
|
||||
|
||||
// load parent class
|
||||
@@ -216,9 +216,9 @@ public class PluginProcessorTest
|
||||
{
|
||||
copySmps(PLUGIN_A, PLUGIN_B);
|
||||
|
||||
Set<PluginWrapper> plugins = collectPlugins();
|
||||
Set<InstalledPlugin> plugins = collectPlugins();
|
||||
|
||||
PluginWrapper plugin = findPlugin(plugins, PLUGIN_B.id);
|
||||
InstalledPlugin plugin = findPlugin(plugins, PLUGIN_B.id);
|
||||
ClassLoader cl = plugin.getClassLoader();
|
||||
|
||||
// load parent class
|
||||
@@ -247,7 +247,7 @@ public class PluginProcessorTest
|
||||
{
|
||||
copySmp(PLUGIN_A);
|
||||
|
||||
PluginWrapper plugin = collectAndGetFirst();
|
||||
InstalledPlugin plugin = collectAndGetFirst();
|
||||
WebResourceLoader wrl = plugin.getWebResourceLoader();
|
||||
|
||||
assertNotNull(wrl);
|
||||
@@ -269,7 +269,7 @@ public class PluginProcessorTest
|
||||
{
|
||||
copySmp(PLUGIN_F_1_0_0);
|
||||
|
||||
PluginWrapper plugin = collectAndGetFirst();
|
||||
InstalledPlugin plugin = collectAndGetFirst();
|
||||
|
||||
assertThat(plugin.getId(), is(PLUGIN_F_1_0_0.id));
|
||||
copySmp(PLUGIN_F_1_0_1);
|
||||
@@ -302,9 +302,9 @@ public class PluginProcessorTest
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private PluginWrapper collectAndGetFirst() throws IOException
|
||||
private InstalledPlugin collectAndGetFirst() throws IOException
|
||||
{
|
||||
Set<PluginWrapper> plugins = collectPlugins();
|
||||
Set<InstalledPlugin> plugins = collectPlugins();
|
||||
|
||||
assertThat(plugins, hasSize(1));
|
||||
|
||||
@@ -319,7 +319,7 @@ public class PluginProcessorTest
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private Set<PluginWrapper> collectPlugins() throws IOException
|
||||
private Set<InstalledPlugin> collectPlugins() throws IOException
|
||||
{
|
||||
return processor.collectPlugins(PluginProcessorTest.class.getClassLoader());
|
||||
}
|
||||
@@ -368,14 +368,14 @@ public class PluginProcessorTest
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private PluginWrapper findPlugin(Iterable<PluginWrapper> plugin,
|
||||
private InstalledPlugin findPlugin(Iterable<InstalledPlugin> plugin,
|
||||
final String id)
|
||||
{
|
||||
return Iterables.find(plugin, new Predicate<PluginWrapper>()
|
||||
return Iterables.find(plugin, new Predicate<InstalledPlugin>()
|
||||
{
|
||||
|
||||
@Override
|
||||
public boolean apply(PluginWrapper input)
|
||||
public boolean apply(InstalledPlugin input)
|
||||
{
|
||||
return id.equals(input.getId());
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public class PluginTreeTest
|
||||
{
|
||||
PluginCondition condition = new PluginCondition("999",
|
||||
new ArrayList<String>(), "hit");
|
||||
Plugin plugin = new Plugin(2, createInfo("a", "1"), null, condition,
|
||||
InstalledPluginDescriptor plugin = new InstalledPluginDescriptor(2, createInfo("a", "1"), null, condition,
|
||||
false, null);
|
||||
ExplodedSmp smp = createSmp(plugin);
|
||||
|
||||
@@ -114,7 +114,7 @@ public class PluginTreeTest
|
||||
@Test(expected = PluginException.class)
|
||||
public void testScmVersion() throws IOException
|
||||
{
|
||||
Plugin plugin = new Plugin(1, createInfo("a", "1"), null, null, false,
|
||||
InstalledPluginDescriptor plugin = new InstalledPluginDescriptor(1, createInfo("a", "1"), null, null, false,
|
||||
null);
|
||||
ExplodedSmp smp = createSmp(plugin);
|
||||
|
||||
@@ -182,7 +182,7 @@ public class PluginTreeTest
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private ExplodedSmp createSmp(Plugin plugin) throws IOException
|
||||
private ExplodedSmp createSmp(InstalledPluginDescriptor plugin) throws IOException
|
||||
{
|
||||
return new ExplodedSmp(tempFolder.newFile().toPath(), plugin);
|
||||
}
|
||||
@@ -199,7 +199,7 @@ public class PluginTreeTest
|
||||
*/
|
||||
private ExplodedSmp createSmp(String name) throws IOException
|
||||
{
|
||||
return createSmp(new Plugin(2, createInfo(name, "1.0.0"), null, null,
|
||||
return createSmp(new InstalledPluginDescriptor(2, createInfo(name, "1.0.0"), null, null,
|
||||
false, null));
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ public class PluginTreeTest
|
||||
dependencySet.add(d);
|
||||
}
|
||||
|
||||
Plugin plugin = new Plugin(2, createInfo(name, "1"), null, null,
|
||||
InstalledPluginDescriptor plugin = new InstalledPluginDescriptor(2, createInfo(name, "1"), null, null,
|
||||
false, dependencySet);
|
||||
|
||||
return createSmp(plugin);
|
||||
|
||||
Reference in New Issue
Block a user