create new simplified PluginManager API

This commit is contained in:
Sebastian Sdorra
2019-08-20 10:33:57 +02:00
parent 0aaec1174a
commit 3f1521bcca
19 changed files with 220 additions and 1410 deletions

View File

@@ -0,0 +1,20 @@
package sonia.scm.plugin;
public class AvailablePlugin implements Plugin {
private final AvailablePluginDescriptor pluginDescriptor;
public AvailablePlugin(AvailablePluginDescriptor pluginDescriptor) {
this.pluginDescriptor = pluginDescriptor;
}
@Override
public AvailablePluginDescriptor getDescriptor() {
return pluginDescriptor;
}
@Override
public PluginState getState() {
return PluginState.AVAILABLE;
}
}

View File

@@ -0,0 +1,34 @@
package sonia.scm.plugin;
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;
public AvailablePluginDescriptor(PluginInformation information, PluginCondition condition, Set<String> dependencies) {
this.information = information;
this.condition = condition;
this.dependencies = dependencies;
}
@Override
public PluginInformation getInformation() {
return information;
}
@Override
public PluginCondition getCondition() {
return condition;
}
@Override
public Set<String> getDependencies() {
return dependencies;
}
}

View File

@@ -52,7 +52,7 @@ import java.util.Set;
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@XmlRootElement @XmlRootElement(name = "plugin")
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
public final class InstalledPluginDescriptor extends ScmModule implements PluginDescriptor public final class InstalledPluginDescriptor extends ScmModule implements PluginDescriptor
{ {
@@ -247,6 +247,7 @@ public final class InstalledPluginDescriptor extends ScmModule implements Plugin
private Set<String> dependencies; private Set<String> dependencies;
/** Field description */ /** Field description */
@XmlElement(name = "information")
private PluginInformation information; private PluginInformation information;
/** Field description */ /** Field description */

View File

@@ -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>();
}

View File

@@ -71,7 +71,6 @@ public class PluginInformation implements PermissionObject, Validateable, Clonea
private String category; private String category;
private String avatarUrl; private String avatarUrl;
private PluginCondition condition; private PluginCondition condition;
private PluginState state;
@Override @Override
public PluginInformation clone() { public PluginInformation clone() {
@@ -83,7 +82,6 @@ public class PluginInformation implements PermissionObject, Validateable, Clonea
clone.setAuthor(author); clone.setAuthor(author);
clone.setCategory(category); clone.setCategory(category);
clone.setAvatarUrl(avatarUrl); clone.setAvatarUrl(avatarUrl);
clone.setState(state);
if (condition != null) { if (condition != null) {
clone.setCondition(condition.clone()); clone.setCondition(condition.clone());
} }

View File

@@ -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;
}
}

View File

@@ -33,113 +33,50 @@
package sonia.scm.plugin; package sonia.scm.plugin;
//~--- JDK imports ------------------------------------------------------------ import java.util.List;
import java.util.Optional;
import com.google.common.base.Predicate;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
/** /**
* The plugin manager is responsible for plugin related tasks, such as install, uninstall or updating.
* *
* @author Sebastian Sdorra * @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 * Returns the installed plugin with the given name.
* * @param name of plugin
* * @return optional installed plugin.
* @param id
*/ */
public void install(String id); Optional<InstalledPlugin> getInstalled(String name);
/** /**
* Installs a plugin package from a inputstream. * Returns all installed plugins.
* *
* * @return a list of installed plugins.
* @param packageStream package input stream
*
* @throws IOException
* @since 1.21
*/ */
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.
* *
* * @return a list of available plugins.
* @param id
*/ */
public void uninstall(String id); List<AvailablePlugin> getAvailable();
/** /**
* Method description * Installs the plugin with the given name from the list of available plugins.
* *
* * @param name plugin name
* @param id
*/ */
public void update(String id); void install(String name);
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param id
*
* @return
*/
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();
} }

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -3,11 +3,10 @@ package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint; import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.plugin.AvailablePlugin;
import sonia.scm.plugin.InstalledPluginDescriptor; import sonia.scm.plugin.InstalledPluginDescriptor;
import sonia.scm.plugin.PluginInformation;
import sonia.scm.plugin.PluginManager; import sonia.scm.plugin.PluginManager;
import sonia.scm.plugin.PluginPermissions; import sonia.scm.plugin.PluginPermissions;
import sonia.scm.plugin.PluginState;
import sonia.scm.web.VndMediaType; import sonia.scm.web.VndMediaType;
import javax.inject.Inject; import javax.inject.Inject;
@@ -18,9 +17,8 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.Collection; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.ContextEntry.ContextBuilder.entity;
import static sonia.scm.NotFoundException.notFound; import static sonia.scm.NotFoundException.notFound;
@@ -53,11 +51,8 @@ public class AvailablePluginResource {
@Produces(VndMediaType.PLUGIN_COLLECTION) @Produces(VndMediaType.PLUGIN_COLLECTION)
public Response getAvailablePlugins() { public Response getAvailablePlugins() {
PluginPermissions.read().check(); PluginPermissions.read().check();
Collection<PluginInformation> plugins = pluginManager.getAvailable() List<AvailablePlugin> available = pluginManager.getAvailable();
.stream() return Response.ok(collectionMapper.mapAvailable(available)).build();
.filter(plugin -> plugin.getState().equals(PluginState.AVAILABLE))
.collect(Collectors.toList());
return Response.ok(collectionMapper.map(plugins)).build();
} }
/** /**
@@ -66,7 +61,7 @@ public class AvailablePluginResource {
* @return available plugin. * @return available plugin.
*/ */
@GET @GET
@Path("/{name}/{version}") @Path("/{name}")
@StatusCodes({ @StatusCodes({
@ResponseCode(code = 200, condition = "success"), @ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 404, condition = "not found"), @ResponseCode(code = 404, condition = "not found"),
@@ -74,12 +69,9 @@ public class AvailablePluginResource {
}) })
@TypeHint(PluginDto.class) @TypeHint(PluginDto.class)
@Produces(VndMediaType.PLUGIN) @Produces(VndMediaType.PLUGIN)
public Response getAvailablePlugin(@PathParam("name") String name, @PathParam("version") String version) { public Response getAvailablePlugin(@PathParam("name") String name) {
PluginPermissions.read().check(); PluginPermissions.read().check();
Optional<PluginInformation> plugin = pluginManager.getAvailable() Optional<AvailablePlugin> plugin = pluginManager.getAvailable(name);
.stream()
.filter(p -> p.getId().equals(name + ":" + version))
.findFirst();
if (plugin.isPresent()) { if (plugin.isPresent()) {
return Response.ok(mapper.map(plugin.get())).build(); return Response.ok(mapper.map(plugin.get())).build();
} else { } else {
@@ -90,19 +82,18 @@ public class AvailablePluginResource {
/** /**
* Triggers plugin installation. * Triggers plugin installation.
* @param name plugin artefact name * @param name plugin artefact name
* @param version plugin version
* @return HTTP Status. * @return HTTP Status.
*/ */
@POST @POST
@Path("/{name}/{version}/install") @Path("/{name}/install")
@Consumes(VndMediaType.PLUGIN) @Consumes(VndMediaType.PLUGIN)
@StatusCodes({ @StatusCodes({
@ResponseCode(code = 200, condition = "success"), @ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 500, condition = "internal server error") @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) {
PluginPermissions.manage().check(); PluginPermissions.manage().check();
pluginManager.install(name + ":" + version); pluginManager.install(name);
return Response.ok().build(); return Response.ok().build();
} }
} }

View File

@@ -3,11 +3,10 @@ package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint; import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.plugin.InstalledPluginDescriptor; import sonia.scm.plugin.InstalledPluginDescriptor;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.PluginManager; import sonia.scm.plugin.PluginManager;
import sonia.scm.plugin.PluginPermissions; import sonia.scm.plugin.PluginPermissions;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.web.VndMediaType; import sonia.scm.web.VndMediaType;
import javax.inject.Inject; import javax.inject.Inject;
@@ -16,7 +15,6 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -25,17 +23,15 @@ import static sonia.scm.NotFoundException.notFound;
public class InstalledPluginResource { public class InstalledPluginResource {
private final PluginLoader pluginLoader;
private final PluginDtoCollectionMapper collectionMapper; private final PluginDtoCollectionMapper collectionMapper;
private final PluginDtoMapper mapper; private final PluginDtoMapper mapper;
private final PluginManager pluginManager; private final PluginManager pluginManager;
@Inject @Inject
public InstalledPluginResource(PluginLoader pluginLoader, PluginDtoCollectionMapper collectionMapper, PluginDtoMapper mapper, PluginManager pluginManager) { public InstalledPluginResource(PluginManager pluginManager, PluginDtoCollectionMapper collectionMapper, PluginDtoMapper mapper) {
this.pluginLoader = pluginLoader; this.pluginManager = pluginManager;
this.collectionMapper = collectionMapper; this.collectionMapper = collectionMapper;
this.mapper = mapper; this.mapper = mapper;
this.pluginManager = pluginManager;
} }
/** /**
@@ -53,8 +49,8 @@ public class InstalledPluginResource {
@Produces(VndMediaType.PLUGIN_COLLECTION) @Produces(VndMediaType.PLUGIN_COLLECTION)
public Response getInstalledPlugins() { public Response getInstalledPlugins() {
PluginPermissions.read().check(); PluginPermissions.read().check();
List<InstalledPlugin> plugins = new ArrayList<>(pluginLoader.getInstalledPlugins()); List<InstalledPlugin> plugins = pluginManager.getInstalled();
return Response.ok(collectionMapper.map(plugins)).build(); return Response.ok(collectionMapper.mapInstalled(plugins)).build();
} }
/** /**
@@ -75,13 +71,9 @@ public class InstalledPluginResource {
@Produces(VndMediaType.PLUGIN) @Produces(VndMediaType.PLUGIN)
public Response getInstalledPlugin(@PathParam("name") String name) { public Response getInstalledPlugin(@PathParam("name") String name) {
PluginPermissions.read().check(); PluginPermissions.read().check();
Optional<PluginDto> pluginDto = pluginLoader.getInstalledPlugins() Optional<InstalledPlugin> pluginDto = pluginManager.getInstalled(name);
.stream()
.filter(plugin -> name.equals(plugin.getDescriptor().getInformation().getName()))
.map(mapper::map)
.findFirst();
if (pluginDto.isPresent()) { if (pluginDto.isPresent()) {
return Response.ok(pluginDto.get()).build(); return Response.ok(mapper.map(pluginDto.get())).build();
} else { } else {
throw notFound(entity(InstalledPluginDescriptor.class, name)); throw notFound(entity(InstalledPluginDescriptor.class, name));
} }

View File

@@ -4,6 +4,7 @@ import com.google.inject.Inject;
import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import sonia.scm.plugin.AvailablePlugin;
import sonia.scm.plugin.PluginInformation; import sonia.scm.plugin.PluginInformation;
import sonia.scm.plugin.InstalledPlugin; import sonia.scm.plugin.InstalledPlugin;
@@ -25,12 +26,12 @@ public class PluginDtoCollectionMapper {
this.mapper = mapper; this.mapper = mapper;
} }
public HalRepresentation map(List<InstalledPlugin> plugins) { public HalRepresentation mapInstalled(List<InstalledPlugin> plugins) {
List<PluginDto> dtos = plugins.stream().map(mapper::map).collect(toList()); List<PluginDto> dtos = plugins.stream().map(mapper::map).collect(toList());
return new HalRepresentation(createInstalledPluginsLinks(), embedDtos(dtos)); return new HalRepresentation(createInstalledPluginsLinks(), embedDtos(dtos));
} }
public HalRepresentation map(Collection<PluginInformation> plugins) { public HalRepresentation mapAvailable(List<AvailablePlugin> plugins) {
List<PluginDto> dtos = plugins.stream().map(mapper::map).collect(toList()); List<PluginDto> dtos = plugins.stream().map(mapper::map).collect(toList());
return new HalRepresentation(createAvailablePluginsLinks(), embedDtos(dtos)); return new HalRepresentation(createAvailablePluginsLinks(), embedDtos(dtos));
} }

View File

@@ -1,13 +1,11 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget; import org.mapstruct.MappingTarget;
import org.mapstruct.ObjectFactory; import sonia.scm.plugin.Plugin;
import sonia.scm.plugin.PluginInformation; import sonia.scm.plugin.PluginInformation;
import sonia.scm.plugin.PluginState; import sonia.scm.plugin.PluginState;
import sonia.scm.plugin.InstalledPlugin;
import javax.inject.Inject; import javax.inject.Inject;
@@ -20,23 +18,23 @@ public abstract class PluginDtoMapper {
@Inject @Inject
private ResourceLinks resourceLinks; private ResourceLinks resourceLinks;
public PluginDto map(InstalledPlugin plugin) { public abstract void map(PluginInformation plugin, @MappingTarget PluginDto dto);
return map(plugin.getDescriptor().getInformation());
}
public abstract PluginDto map(PluginInformation plugin); public PluginDto map(Plugin plugin) {
PluginDto dto = createDto(plugin);
@AfterMapping map(plugin.getDescriptor().getInformation(), dto);
protected void appendCategory(@MappingTarget PluginDto dto) {
if (dto.getCategory() == null) { if (dto.getCategory() == null) {
dto.setCategory("Miscellaneous"); dto.setCategory("Miscellaneous");
} }
return dto;
} }
@ObjectFactory private PluginDto createDto(Plugin plugin) {
public PluginDto createDto(PluginInformation pluginInformation) {
Links.Builder linksBuilder; Links.Builder linksBuilder;
if (pluginInformation.getState() != null && pluginInformation.getState().equals(PluginState.AVAILABLE)) {
PluginInformation pluginInformation = plugin.getDescriptor().getInformation();
if (plugin.getState() != null && plugin.getState().equals(PluginState.AVAILABLE)) {
linksBuilder = linkingTo() linksBuilder = linkingTo()
.self(resourceLinks.availablePlugin() .self(resourceLinks.availablePlugin()
.self(pluginInformation.getName(), pluginInformation.getVersion())); .self(pluginInformation.getName(), pluginInformation.getVersion()));

View File

@@ -35,685 +35,41 @@ package sonia.scm.plugin;
//~--- non-JDK imports -------------------------------------------------------- //~--- 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.inject.Singleton; 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;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
import java.util.List;
import java.io.File; import java.util.Optional;
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 java.util.Set;
import javax.xml.bind.JAXB;
import sonia.scm.net.ahc.AdvancedHttpClient;
import static sonia.scm.plugin.PluginCenterDtoMapper.*;
/** /**
* TODO replace aether stuff.
* TODO check AdvancedPluginConfiguration from 1.x
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@Singleton @Singleton
public class DefaultPluginManager implements PluginManager public class DefaultPluginManager implements PluginManager {
{
/** Field description */
public static final String CACHE_NAME = "sonia.cache.plugins";
/** Field description */
public static final String ENCODING = "UTF-8";
/** 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 (InstalledPlugin wrapper : pluginLoader.getInstalledPlugins())
{
InstalledPluginDescriptor plugin = wrapper.getDescriptor();
PluginInformation info = plugin.getInformation();
if ((info != null) && info.isValid())
{
installedPlugins.put(info.getId(), plugin);
}
}
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*/
@Override @Override
public void clearCache() public Optional<AvailablePlugin> getAvailable(String name) {
{ return Optional.empty();
if (logger.isDebugEnabled())
{
logger.debug("clear plugin cache");
} }
cache.clear();
}
/**
* Method description
*
*
* @param config
*/
@Subscribe
public void configChanged(ScmConfigurationChangedEvent config)
{
clearCache();
}
/**
* Method description
*
*
* @param id
*/
@Override @Override
public void install(String id) public Optional<InstalledPlugin> getInstalled(String name) {
{ return Optional.empty();
PluginPermissions.manage().check();
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
InstalledPluginDescriptor newPlugin = new InstalledPluginDescriptor();
// TODO check
// newPlugin.setInformation(plugin);
installedPlugins.put(id, newPlugin);
}
}
} }
/**
* Method description
*
*
* @param packageStream
*
* @throws IOException
*/
@Override @Override
public void installPackage(InputStream packageStream) throws IOException public List<InstalledPlugin> getInstalled() {
{ return null;
PluginPermissions.manage().check();
File tempDirectory = Files.createTempDir();
try
{
new ZipUnArchiver().extractArchive(packageStream, tempDirectory);
InstalledPluginDescriptor plugin = JAXB.unmarshal(new File(tempDirectory, "plugin.xml"),
InstalledPluginDescriptor.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);
}
}
/**
* Method description
*
*
* @param id
*/
@Override @Override
public void uninstall(String id) public List<AvailablePlugin> getAvailable() {
{ return null;
PluginPermissions.manage().check();
InstalledPluginDescriptor 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;
}
}
} }
if (plugin == null)
{
throw new PluginNotInstalledException(id.concat(" is not install"));
}
/*
* if (pluginHandler == null)
* {
* getPluginCenter();
* }
*
* pluginHandler.uninstall(id);
*/
installedPlugins.remove(id);
preparePlugins(getPluginCenter());
}
/**
* Method description
*
*
* @param id
*/
@Override @Override
public void update(String id) public void install(String name) {
{
PluginPermissions.manage().check();
String[] idParts = id.split(":");
String name = idParts[0];
PluginInformation installed = null;
for (PluginInformation info : getInstalled())
{
if (name.equals(info.getName()))
{
installed = info;
break;
} }
}
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 (InstalledPluginDescriptor 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);
}
}
}
/**
* 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 (InstalledPluginDescriptor 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, InstalledPluginDescriptor> installedPlugins;
} }

View File

@@ -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;
}
}

View File

@@ -237,7 +237,7 @@ public final class PluginProcessor
} }
InstalledPlugin plugin = InstalledPlugin plugin =
createPluginWrapper(createParentPluginClassLoader(classLoader, parents), createPlugin(createParentPluginClassLoader(classLoader, parents),
smp); smp);
if (plugin != null) if (plugin != null)
@@ -431,73 +431,36 @@ public final class PluginProcessor
return result; return result;
} }
/** private InstalledPluginDescriptor createDescriptor(ClassLoader classLoader, Path descriptor) {
* Method description
*
*
*
* @param classLoader
* @param descriptor
*
* @return
*/
private InstalledPluginDescriptor createPlugin(ClassLoader classLoader, Path descriptor)
{
ClassLoader ctxcl = Thread.currentThread().getContextClassLoader(); ClassLoader ctxcl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classLoader); Thread.currentThread().setContextClassLoader(classLoader);
try {
try return (InstalledPluginDescriptor) context.createUnmarshaller().unmarshal(descriptor.toFile());
{ } catch (JAXBException ex) {
return (InstalledPluginDescriptor) context.createUnmarshaller().unmarshal( throw new PluginLoadException("could not load plugin desriptor ".concat(descriptor.toString()), ex);
descriptor.toFile()); } finally {
}
catch (JAXBException ex)
{
throw new PluginLoadException(
"could not load plugin desriptor ".concat(descriptor.toString()), ex);
}
finally
{
Thread.currentThread().setContextClassLoader(ctxcl); Thread.currentThread().setContextClassLoader(ctxcl);
} }
} }
/** private InstalledPlugin createPlugin(ClassLoader classLoader, ExplodedSmp smp) throws IOException {
* Method description InstalledPlugin plugin = null;
*
*
* @param classLoader
* @param smp
*
* @return
*
* @throws IOException
*/
private InstalledPlugin createPluginWrapper(ClassLoader classLoader,
ExplodedSmp smp)
throws IOException
{
InstalledPlugin wrapper = null;
Path directory = smp.getPath(); 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); ClassLoader cl = createClassLoader(classLoader, smp);
InstalledPluginDescriptor plugin = createPlugin(cl, descriptor); InstalledPluginDescriptor descriptor = createDescriptor(cl, descriptorPath);
WebResourceLoader resourceLoader = createWebResourceLoader(directory); WebResourceLoader resourceLoader = createWebResourceLoader(directory);
wrapper = new InstalledPlugin(plugin, cl, resourceLoader, directory); plugin = new InstalledPlugin(descriptor, cl, resourceLoader, directory);
} } else {
else
{
logger.warn("found plugin directory without plugin descriptor"); logger.warn("found plugin directory without plugin descriptor");
} }
return wrapper; return plugin;
} }
/** /**

View File

@@ -16,6 +16,9 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; 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.PluginInformation;
import sonia.scm.plugin.PluginManager; import sonia.scm.plugin.PluginManager;
import sonia.scm.plugin.PluginState; import sonia.scm.plugin.PluginState;
@@ -27,6 +30,7 @@ import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Collections; import java.util.Collections;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -87,10 +91,10 @@ class AvailablePluginResourceTest {
@Test @Test
void getAvailablePlugins() throws URISyntaxException, UnsupportedEncodingException { void getAvailablePlugins() throws URISyntaxException, UnsupportedEncodingException {
PluginInformation pluginInformation = new PluginInformation(); AvailablePlugin plugin = createPlugin();
pluginInformation.setState(PluginState.AVAILABLE);
when(pluginManager.getAvailable()).thenReturn(Collections.singletonList(pluginInformation)); when(pluginManager.getAvailable()).thenReturn(Collections.singletonList(plugin));
when(collectionMapper.map(Collections.singletonList(pluginInformation))).thenReturn(new MockedResultDto()); when(collectionMapper.mapAvailable(Collections.singletonList(plugin))).thenReturn(new MockedResultDto());
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available"); MockHttpRequest request = MockHttpRequest.get("/v2/plugins/available");
request.accept(VndMediaType.PLUGIN_COLLECTION); request.accept(VndMediaType.PLUGIN_COLLECTION);
@@ -105,16 +109,18 @@ class AvailablePluginResourceTest {
@Test @Test
void getAvailablePlugin() throws UnsupportedEncodingException, URISyntaxException { void getAvailablePlugin() throws UnsupportedEncodingException, URISyntaxException {
PluginInformation pluginInformation = new PluginInformation(); PluginInformation pluginInformation = new PluginInformation();
pluginInformation.setState(PluginState.AVAILABLE);
pluginInformation.setName("pluginName"); pluginInformation.setName("pluginName");
pluginInformation.setVersion("2.0.0"); 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 pluginDto = new PluginDto();
pluginDto.setName("pluginName"); pluginDto.setName("pluginName");
when(mapper.map(pluginInformation)).thenReturn(pluginDto); when(mapper.map(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); request.accept(VndMediaType.PLUGIN);
MockHttpResponse response = new MockHttpResponse(); MockHttpResponse response = new MockHttpResponse();
@@ -126,17 +132,26 @@ class AvailablePluginResourceTest {
@Test @Test
void installPlugin() throws URISyntaxException { void installPlugin() throws URISyntaxException {
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); request.accept(VndMediaType.PLUGIN);
MockHttpResponse response = new MockHttpResponse(); MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response); dispatcher.invoke(request, response);
verify(pluginManager).install("pluginName:2.0.0"); verify(pluginManager).install("pluginName");
assertThat(HttpServletResponse.SC_OK).isEqualTo(response.getStatus()); 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());
return new AvailablePlugin(descriptor);
}
@Nested @Nested
class WithoutAuthorization { class WithoutAuthorization {
@@ -156,7 +171,7 @@ class AvailablePluginResourceTest {
@Test @Test
void shouldNotGetAvailablePluginIfMissingPermission() throws URISyntaxException { 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); request.accept(VndMediaType.PLUGIN);
MockHttpResponse response = new MockHttpResponse(); MockHttpResponse response = new MockHttpResponse();
@@ -166,7 +181,7 @@ class AvailablePluginResourceTest {
@Test @Test
void shouldNotInstallPluginIfMissingPermission() throws URISyntaxException { void shouldNotInstallPluginIfMissingPermission() throws URISyntaxException {
ThreadContext.unbindSubject(); 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); request.accept(VndMediaType.PLUGIN);
MockHttpResponse response = new MockHttpResponse(); MockHttpResponse response = new MockHttpResponse();

View File

@@ -16,11 +16,10 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.plugin.InstalledPluginDescriptor; import sonia.scm.plugin.InstalledPluginDescriptor;
import sonia.scm.plugin.PluginInformation; import sonia.scm.plugin.PluginInformation;
import sonia.scm.plugin.PluginLoader; import sonia.scm.plugin.PluginManager;
import sonia.scm.plugin.PluginState;
import sonia.scm.plugin.InstalledPlugin;
import sonia.scm.web.VndMediaType; import sonia.scm.web.VndMediaType;
import javax.inject.Provider; import javax.inject.Provider;
@@ -28,12 +27,12 @@ import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Collections; import java.util.Collections;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.*;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class InstalledPluginResourceTest { class InstalledPluginResourceTest {
@@ -46,15 +45,15 @@ class InstalledPluginResourceTest {
@Mock @Mock
Provider<AvailablePluginResource> availablePluginResourceProvider; Provider<AvailablePluginResource> availablePluginResourceProvider;
@Mock
private PluginLoader pluginLoader;
@Mock @Mock
private PluginDtoCollectionMapper collectionMapper; private PluginDtoCollectionMapper collectionMapper;
@Mock @Mock
private PluginDtoMapper mapper; private PluginDtoMapper mapper;
@Mock
private PluginManager pluginManager;
@InjectMocks @InjectMocks
InstalledPluginResource installedPluginResource; InstalledPluginResource installedPluginResource;
@@ -86,9 +85,9 @@ class InstalledPluginResourceTest {
@Test @Test
void getInstalledPlugins() throws URISyntaxException, UnsupportedEncodingException { void getInstalledPlugins() throws URISyntaxException, UnsupportedEncodingException {
InstalledPlugin installedPlugin = new InstalledPlugin(null, null, null, null); InstalledPlugin installedPlugin = createPlugin();
when(pluginLoader.getInstalledPlugins()).thenReturn(Collections.singletonList(installedPlugin)); when(pluginManager.getInstalled()).thenReturn(Collections.singletonList(installedPlugin));
when(collectionMapper.map(Collections.singletonList(installedPlugin))).thenReturn(new MockedResultDto()); when(collectionMapper.mapInstalled(Collections.singletonList(installedPlugin))).thenReturn(new MockedResultDto());
MockHttpRequest request = MockHttpRequest.get("/v2/plugins/installed"); MockHttpRequest request = MockHttpRequest.get("/v2/plugins/installed");
request.accept(VndMediaType.PLUGIN_COLLECTION); request.accept(VndMediaType.PLUGIN_COLLECTION);
@@ -105,10 +104,9 @@ class InstalledPluginResourceTest {
PluginInformation pluginInformation = new PluginInformation(); PluginInformation pluginInformation = new PluginInformation();
pluginInformation.setVersion("2.0.0"); pluginInformation.setVersion("2.0.0");
pluginInformation.setName("pluginName"); pluginInformation.setName("pluginName");
pluginInformation.setState(PluginState.INSTALLED); InstalledPlugin installedPlugin = createPlugin(pluginInformation);
InstalledPluginDescriptor plugin = new InstalledPluginDescriptor(2, pluginInformation, null, null, false, null);
InstalledPlugin installedPlugin = new InstalledPlugin(plugin, null, null, null); when(pluginManager.getInstalled("pluginName")).thenReturn(Optional.of(installedPlugin));
when(pluginLoader.getInstalledPlugins()).thenReturn(Collections.singletonList(installedPlugin));
PluginDto pluginDto = new PluginDto(); PluginDto pluginDto = new PluginDto();
pluginDto.setName("pluginName"); pluginDto.setName("pluginName");
@@ -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 @Nested
class WithoutAuthorization { class WithoutAuthorization {

View File

@@ -4,13 +4,19 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.plugin.Plugin;
import sonia.scm.plugin.PluginDescriptor;
import sonia.scm.plugin.PluginInformation; import sonia.scm.plugin.PluginInformation;
import sonia.scm.plugin.PluginState; import sonia.scm.plugin.PluginState;
import java.net.URI; import java.net.URI;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.in;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class PluginDtoMapperTest { class PluginDtoMapperTest {
@@ -25,7 +31,8 @@ class PluginDtoMapperTest {
void shouldMapInformation() { void shouldMapInformation() {
PluginInformation information = createPluginInformation(); 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.getName()).isEqualTo("scm-cas-plugin");
assertThat(dto.getVersion()).isEqualTo("1.0.0"); assertThat(dto.getVersion()).isEqualTo("1.0.0");
@@ -48,41 +55,51 @@ class PluginDtoMapperTest {
@Test @Test
void shouldAppendInstalledSelfLink() { void shouldAppendInstalledSelfLink() {
PluginInformation information = createPluginInformation(); Plugin plugin = createPlugin(PluginState.INSTALLED);
information.setState(PluginState.INSTALLED);
PluginDto dto = mapper.map(information); PluginDto dto = mapper.map(plugin);
assertThat(dto.getLinks().getLinkBy("self").get().getHref()) assertThat(dto.getLinks().getLinkBy("self").get().getHref())
.isEqualTo("https://hitchhiker.com/v2/plugins/installed/scm-cas-plugin"); .isEqualTo("https://hitchhiker.com/v2/plugins/installed/scm-cas-plugin");
} }
@Test @Test
void shouldAppendAvailableSelfLink() { void shouldAppendAvailableSelfLink() {
PluginInformation information = createPluginInformation(); Plugin plugin = createPlugin(PluginState.AVAILABLE);
information.setState(PluginState.AVAILABLE);
PluginDto dto = mapper.map(information); PluginDto dto = mapper.map(plugin);
assertThat(dto.getLinks().getLinkBy("self").get().getHref()) 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 @Test
void shouldAppendInstallLink() { void shouldAppendInstallLink() {
PluginInformation information = createPluginInformation(); Plugin plugin = createPlugin(PluginState.AVAILABLE);
information.setState(PluginState.AVAILABLE);
PluginDto dto = mapper.map(information); PluginDto dto = mapper.map(plugin);
assertThat(dto.getLinks().getLinkBy("install").get().getHref()) 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 @Test
void shouldReturnMiscellaneousIfCategoryIsNull() { void shouldReturnMiscellaneousIfCategoryIsNull() {
PluginInformation information = createPluginInformation(); PluginInformation information = createPluginInformation();
information.setCategory(null); information.setCategory(null);
Plugin plugin = createPlugin(information, PluginState.AVAILABLE);
PluginDto dto = mapper.map(information); PluginDto dto = mapper.map(plugin);
assertThat(dto.getCategory()).isEqualTo("Miscellaneous"); assertThat(dto.getCategory()).isEqualTo("Miscellaneous");
} }
private Plugin createPlugin(PluginState state) {
return createPlugin(createPluginInformation(), state);
}
private Plugin createPlugin(PluginInformation information, PluginState state) {
Plugin plugin = Mockito.mock(Plugin.class);
when(plugin.getState()).thenReturn(state);
PluginDescriptor descriptor = mock(PluginDescriptor.class);
when(descriptor.getInformation()).thenReturn(information);
when(plugin.getDescriptor()).thenReturn(descriptor);
return plugin;
}
} }