diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java b/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java index 74cca02d92..de0a3ca1e9 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginInformation.java @@ -282,16 +282,6 @@ public class PluginInformation return version; } - /** - * Method description - * - * - * @return - */ - public Map getLinks() { - return links; - } - /** * Method description * @@ -384,17 +374,6 @@ public class PluginInformation this.version = version; } - - /** - * Method description - * - * - * @param links - */ - public void setLinks(Map links) { - this.links = links; - } - //~--- fields --------------------------------------------------------------- /** Field description */ @@ -418,7 +397,4 @@ public class PluginInformation /** Field description */ private String version; - /** Field description */ - private Map links; - } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java new file mode 100644 index 0000000000..0f4864d2c0 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java @@ -0,0 +1,106 @@ +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.PluginLoader; +import sonia.scm.plugin.PluginManager; +import sonia.scm.plugin.PluginPermissions; +import sonia.scm.plugin.PluginState; +import sonia.scm.plugin.PluginWrapper; +import sonia.scm.web.VndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +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; + +public class AvailablePluginResource { + + private final PluginDtoCollectionMapper collectionMapper; + private PluginDtoMapper dtoMapper; + private final PluginManager pluginManager; + + @Inject + public AvailablePluginResource(PluginDtoCollectionMapper collectionMapper, PluginDtoMapper dtoMapper, PluginManager pluginManager) { + this.collectionMapper = collectionMapper; + this.dtoMapper = dtoMapper; + this.pluginManager = pluginManager; + } + + /** + * Returns a collection of available plugins. + * + * @return collection of available plugins. + */ + @GET + @Path("") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(CollectionDto.class) + @Produces(VndMediaType.PLUGIN_COLLECTION) + public Response getAvailablePlugins() { + PluginPermissions.read().check(); + Collection plugins = pluginManager.getAvailable() + .stream() + .filter(plugin -> plugin.getState().equals(PluginState.AVAILABLE)) + .collect(Collectors.toList()); + return Response.ok(collectionMapper.map(plugins)).build(); + } + + /** + * Returns available plugin. + * + * @return available plugin. + */ + @GET + @Path("/{name}/{version}") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(CollectionDto.class) + @Produces(VndMediaType.PLUGIN) + public Response getAvailablePlugin(@PathParam("name") String name, @PathParam("version") String version) { + PluginPermissions.read().check(); + Optional plugin = pluginManager.getAvailable() + .stream() + .filter(p -> p.getId().equals(name + ":" + version)) + .findFirst(); + return Response.ok(dtoMapper.map(plugin.get())).build(); + } + + /** + * Returns 200 when plugin installation is successful triggered. + * + * @return HTTP Status. + */ + @POST + @Path("/{name}/{version}/install") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(CollectionDto.class) + @Produces(VndMediaType.PLUGIN) + public Response installPlugin(@PathParam("name") String name, @PathParam("version") String version) { + PluginPermissions.manage().check(); + pluginManager.install(name + ":" + version); + return Response.ok().build(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java index c7b52861dc..906ff66759 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java @@ -42,7 +42,8 @@ public class IndexDtoGenerator extends HalAppenderMapper { link("logout", resourceLinks.authentication().logout()) ); if (PluginPermissions.read().isPermitted()) { - builder.single(link("plugins", resourceLinks.pluginCollection().self())); + builder.single(link("installedPlugins", resourceLinks.installedPluginCollection().self())); + builder.single(link("availablePlugins", resourceLinks.availablePluginCollection().self())); } if (UserPermissions.list().isPermitted()) { builder.single(link("users", resourceLinks.userCollection().self())); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InstalledPluginResource.java similarity index 75% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginResource.java rename to scm-webapp/src/main/java/sonia/scm/api/v2/resources/InstalledPluginResource.java index be57e3f674..246bbf379f 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InstalledPluginResource.java @@ -15,6 +15,7 @@ import sonia.scm.web.VndMediaType; import javax.inject.Inject; 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; @@ -28,7 +29,7 @@ import java.util.stream.Collectors; import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.NotFoundException.notFound; -public class PluginResource { +public class InstalledPluginResource { private final PluginLoader pluginLoader; private final PluginDtoCollectionMapper collectionMapper; @@ -36,7 +37,7 @@ public class PluginResource { private final PluginManager pluginManager; @Inject - public PluginResource(PluginLoader pluginLoader, PluginDtoCollectionMapper collectionMapper, PluginDtoMapper mapper, PluginManager pluginManager) { + public InstalledPluginResource(PluginLoader pluginLoader, PluginDtoCollectionMapper collectionMapper, PluginDtoMapper mapper, PluginManager pluginManager) { this.pluginLoader = pluginLoader; this.collectionMapper = collectionMapper; this.mapper = mapper; @@ -70,7 +71,7 @@ public class PluginResource { * @return installed plugin with specified id */ @GET - @Path("{id}") + @Path("/{id}") @StatusCodes({ @ResponseCode(code = 200, condition = "success"), @ResponseCode(code = 404, condition = "not found"), @@ -91,27 +92,4 @@ public class PluginResource { throw notFound(entity(Plugin.class, id)); } } - - /** - * Returns a collection of available plugins. - * - * @return collection of available plugins. - */ - @GET - @Path("/available") - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @TypeHint(CollectionDto.class) - @Produces(VndMediaType.PLUGIN_COLLECTION) - public Response getAvailablePlugins() { - PluginPermissions.read().check(); - Collection plugins = pluginManager.getAvailable() - .stream() - .filter(plugin -> plugin.getState().equals(PluginState.AVAILABLE)) - .collect(Collectors.toList()); - return Response.ok(collectionMapper.map(plugins)).build(); - } - } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java index 0bb5bd3610..5d8746c211 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java @@ -27,16 +27,24 @@ public class PluginDtoCollectionMapper { public HalRepresentation map(List plugins) { List dtos = plugins.stream().map(mapper::map).collect(toList()); - return new HalRepresentation(createLinks(), embedDtos(dtos)); + return new HalRepresentation(createInstalledPluginsLinks(), embedDtos(dtos)); } public HalRepresentation map(Collection plugins) { List dtos = plugins.stream().map(mapper::map).collect(toList()); - return new HalRepresentation(createLinks(), embedDtos(dtos)); + return new HalRepresentation(createAvailablePluginsLinks(), embedDtos(dtos)); } - private Links createLinks() { - String baseUrl = resourceLinks.pluginCollection().self(); + private Links createInstalledPluginsLinks() { + String baseUrl = resourceLinks.installedPluginCollection().self(); + + Links.Builder linksBuilder = linkingTo() + .with(Links.linkingTo().self(baseUrl).build()); + return linksBuilder.build(); + } + + private Links createAvailablePluginsLinks() { + String baseUrl = resourceLinks.availablePluginCollection().self(); Links.Builder linksBuilder = linkingTo() .with(Links.linkingTo().self(baseUrl).build()); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java index 4886138a6b..9604ccbcc0 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java @@ -2,11 +2,11 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.Links; import sonia.scm.plugin.PluginInformation; +import sonia.scm.plugin.PluginState; import sonia.scm.plugin.PluginWrapper; import javax.inject.Inject; -import java.util.Map; - +import static de.otto.edison.hal.Link.*; import static de.otto.edison.hal.Links.linkingTo; public class PluginDtoMapper { @@ -23,13 +23,18 @@ public class PluginDtoMapper { } public PluginDto map(PluginInformation pluginInformation) { - Links.Builder linksBuilder = linkingTo() - .self(resourceLinks.plugin() - .self(pluginInformation.getName())); + Links.Builder linksBuilder; + if (pluginInformation.getState() != null && pluginInformation.getState().equals(PluginState.AVAILABLE)) { + linksBuilder = linkingTo() + .self(resourceLinks.availablePlugin() + .self(pluginInformation.getName(), pluginInformation.getVersion())); - for (Object link : pluginInformation.getLinks().values()) { - System.out.println("Link is = " + link.toString()); - linksBuilder.item(((Map) link).values().iterator().next().toString()); + linksBuilder.single(link("install", resourceLinks.availablePlugin().install(pluginInformation.getName(), pluginInformation.getVersion()))); + } + else { + linksBuilder = linkingTo() + .self(resourceLinks.installedPlugin() + .self(pluginInformation.getName())); } PluginDto pluginDto = new PluginDto(linksBuilder.build()); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java index e9b0f0a997..79c46369a3 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java @@ -4,18 +4,23 @@ import javax.inject.Inject; import javax.inject.Provider; import javax.ws.rs.Path; -@Path("v2/") +@Path("v2/plugins") public class PluginRootResource { - private Provider pluginResourceProvider; + private Provider installedPluginResourceProvider; + private Provider availablePluginResourceProvider; @Inject - public PluginRootResource(Provider pluginResourceProvider) { - this.pluginResourceProvider = pluginResourceProvider; + public PluginRootResource(Provider installedPluginResourceProvider, Provider availablePluginResourceProvider) { + this.installedPluginResourceProvider = installedPluginResourceProvider; + this.availablePluginResourceProvider = availablePluginResourceProvider; } - @Path("plugins") - public PluginResource plugins() { - return pluginResourceProvider.get(); + @Path("/installed") + public InstalledPluginResource installedPlugins() { + return installedPluginResourceProvider.get(); } + + @Path("/available") + public AvailablePluginResource availablePlugins() { return availablePluginResourceProvider.get(); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index 1d06659649..268f5f8619 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -651,35 +651,71 @@ class ResourceLinks { } } - public PluginLinks plugin() { - return new PluginLinks(scmPathInfoStore.get()); + public InstalledPluginLinks installedPlugin() { + return new InstalledPluginLinks(scmPathInfoStore.get()); } - static class PluginLinks { - private final LinkBuilder pluginLinkBuilder; + static class InstalledPluginLinks { + private final LinkBuilder installedPluginLinkBuilder; - PluginLinks(ScmPathInfo pathInfo) { - pluginLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, PluginResource.class); + InstalledPluginLinks(ScmPathInfo pathInfo) { + installedPluginLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, InstalledPluginResource.class); } String self(String id) { - return pluginLinkBuilder.method("plugins").parameters().method("getInstalledPlugin").parameters(id).href(); + return installedPluginLinkBuilder.method("installedPlugins").parameters().method("getInstalledPlugin").parameters(id).href(); } } - public PluginCollectionLinks pluginCollection() { - return new PluginCollectionLinks(scmPathInfoStore.get()); + public InstalledPluginCollectionLinks installedPluginCollection() { + return new InstalledPluginCollectionLinks(scmPathInfoStore.get()); } - static class PluginCollectionLinks { - private final LinkBuilder pluginCollectionLinkBuilder; + static class InstalledPluginCollectionLinks { + private final LinkBuilder installedPluginCollectionLinkBuilder; - PluginCollectionLinks(ScmPathInfo pathInfo) { - pluginCollectionLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, PluginResource.class); + InstalledPluginCollectionLinks(ScmPathInfo pathInfo) { + installedPluginCollectionLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, InstalledPluginResource.class); } String self() { - return pluginCollectionLinkBuilder.method("plugins").parameters().method("getInstalledPlugins").parameters().href(); + return installedPluginCollectionLinkBuilder.method("installedPlugins").parameters().method("getInstalledPlugins").parameters().href(); + } + } + + public AvailablePluginLinks availablePlugin() { + return new AvailablePluginLinks(scmPathInfoStore.get()); + } + + static class AvailablePluginLinks { + private final LinkBuilder availablePluginLinkBuilder; + + AvailablePluginLinks(ScmPathInfo pathInfo) { + availablePluginLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, AvailablePluginResource.class); + } + + String self(String name, String version) { + return availablePluginLinkBuilder.method("availablePlugins").parameters().method("getAvailablePlugin").parameters(name, version).href(); + } + + String install(String name, String version) { + return availablePluginLinkBuilder.method("availablePlugins").parameters().method("installPlugin").parameters(name, version).href(); + } + } + + public AvailablePluginCollectionLinks availablePluginCollection() { + return new AvailablePluginCollectionLinks(scmPathInfoStore.get()); + } + + static class AvailablePluginCollectionLinks { + private final LinkBuilder availablePluginCollectionLinkBuilder; + + AvailablePluginCollectionLinks(ScmPathInfo pathInfo) { + availablePluginCollectionLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, AvailablePluginResource.class); + } + + String self() { + return availablePluginCollectionLinkBuilder.method("availablePlugins").parameters().method("getAvailablePlugins").parameters().href(); } } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java index 93e2e9aaa1..b48113e66d 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java @@ -186,8 +186,6 @@ public class DefaultPluginManager implements PluginManager PluginCenter center = getPluginCenter(); - // pluginHandler.install(id); - for (PluginInformation plugin : center.getPlugins()) { String pluginId = plugin.getId(); @@ -593,10 +591,10 @@ public class DefaultPluginManager implements PluginManager */ private PluginCenter getPluginCenter() { - PluginCenter center = null; // cache.get(PluginCenter.class.getName()); + PluginCenter center = cache.get(PluginCenter.class.getName()); -// if (center == null) -// { + if (center == null) + { synchronized (DefaultPluginManager.class) { String pluginUrl = configuration.getPluginUrl(); @@ -636,7 +634,7 @@ public class DefaultPluginManager implements PluginManager } } } -// } + } return center; } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java index f15c646c03..c7ed6ec3c4 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDto.java @@ -57,7 +57,7 @@ public final class PluginCenterDto implements Serializable { private Dependency dependencies; @XmlElement(name = "_links") - private Map links; + private Map links; } @XmlAccessorType(XmlAccessType.FIELD) @@ -77,18 +77,10 @@ public final class PluginCenterDto implements Serializable { private String name; } - @XmlAccessorType(XmlAccessType.FIELD) - @XmlRootElement(name = "_links") - @Getter - static class Links { - private Link link; - private boolean templated; - } - @XmlAccessorType(XmlAccessType.FIELD) @Getter static class Link { - private String url; + private String href; + private boolean templated; } - } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java index cca5da6520..8e8520f50e 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginCenterDtoMapper.java @@ -24,10 +24,6 @@ public class PluginCenterDtoMapper { pluginInformation.setCondition(new PluginCondition(condition.getMinVersion(), Collections.singletonList(condition.getOs()), condition.getArch())); } - if (plugin.getLinks() != null) { - pluginInformation.setLinks(plugin.getLinks()); - } - pluginInformationSet.add(pluginInformation); } return pluginInformationSet; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java index 539b5c8d99..1aef4e57cb 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java @@ -36,8 +36,10 @@ public class ResourceLinksMock { when(resourceLinks.modifications()).thenReturn(new ResourceLinks.ModificationsLinks(uriInfo)); when(resourceLinks.repositoryType()).thenReturn(new ResourceLinks.RepositoryTypeLinks(uriInfo)); when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(uriInfo)); - when(resourceLinks.pluginCollection()).thenReturn(new ResourceLinks.PluginCollectionLinks(uriInfo)); - when(resourceLinks.plugin()).thenReturn(new ResourceLinks.PluginLinks(uriInfo)); + when(resourceLinks.installedPluginCollection()).thenReturn(new ResourceLinks.InstalledPluginCollectionLinks(uriInfo)); + when(resourceLinks.availablePluginCollection()).thenReturn(new ResourceLinks.AvailablePluginCollectionLinks(uriInfo)); + when(resourceLinks.installedPlugin()).thenReturn(new ResourceLinks.InstalledPluginLinks(uriInfo)); + when(resourceLinks.availablePlugin()).thenReturn(new ResourceLinks.AvailablePluginLinks(uriInfo)); when(resourceLinks.uiPluginCollection()).thenReturn(new ResourceLinks.UIPluginCollectionLinks(uriInfo)); when(resourceLinks.uiPlugin()).thenReturn(new ResourceLinks.UIPluginLinks(uriInfo)); when(resourceLinks.authentication()).thenReturn(new ResourceLinks.AuthenticationLinks(uriInfo));