diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index c9696b0641..24250b26ba 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -37,6 +37,8 @@ public class VndMediaType { public static final String REPOSITORY_VERB_COLLECTION = PREFIX + "repositoryVerbCollection" + SUFFIX; public static final String REPOSITORY_TYPE_COLLECTION = PREFIX + "repositoryTypeCollection" + SUFFIX; public static final String REPOSITORY_TYPE = PREFIX + "repositoryType" + SUFFIX; + public static final String PLUGIN = PREFIX + "plugin" + SUFFIX; + public static final String PLUGIN_COLLECTION = PREFIX + "pluginCollection" + SUFFIX; public static final String UI_PLUGIN = PREFIX + "uiPlugin" + SUFFIX; public static final String UI_PLUGIN_COLLECTION = PREFIX + "uiPluginCollection" + SUFFIX; @SuppressWarnings("squid:S2068") diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java new file mode 100644 index 0000000000..0d686b237b --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDto.java @@ -0,0 +1,26 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import lombok.Getter; + +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class PluginDto extends HalRepresentation { + + private String name; + private String type; + private String version; + private String author; + private String description; + + @Override + protected HalRepresentation add(Links links) { + return super.add(links); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java new file mode 100644 index 0000000000..72178e94f3 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoCollectionMapper.java @@ -0,0 +1,45 @@ +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.Links; +import sonia.scm.plugin.PluginWrapper; + +import java.util.Collection; +import java.util.List; + +import static de.otto.edison.hal.Embedded.embeddedBuilder; +import static de.otto.edison.hal.Links.linkingTo; +import static java.util.stream.Collectors.toList; + +public class PluginDtoCollectionMapper { + + private final ResourceLinks resourceLinks; + private final PluginDtoMapper mapper; + + @Inject + public PluginDtoCollectionMapper(ResourceLinks resourceLinks, PluginDtoMapper mapper) { + this.resourceLinks = resourceLinks; + this.mapper = mapper; + } + + public HalRepresentation map(Collection plugins) { + List dtos = plugins.stream().map(mapper::map).collect(toList()); + return new HalRepresentation(createLinks(), embedDtos(dtos)); + } + + private Links createLinks() { + String baseUrl = resourceLinks.pluginCollection().self(); + + Links.Builder linksBuilder = linkingTo() + .with(Links.linkingTo().self(baseUrl).build()); + return linksBuilder.build(); + } + + private Embedded embedDtos(List dtos) { + return embeddedBuilder() + .with("plugins", dtos) + .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 new file mode 100644 index 0000000000..c910802cac --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginDtoMapper.java @@ -0,0 +1,62 @@ +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.util.HttpUtil; + +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + +import static de.otto.edison.hal.Links.linkingTo; + +public class PluginDtoMapper { + + private final ResourceLinks resourceLinks; + private final HttpServletRequest request; + + @Inject + public PluginDtoMapper(ResourceLinks resourceLinks, HttpServletRequest request) { + this.resourceLinks = resourceLinks; + this.request = request; + } + + public PluginDto map(PluginWrapper plugin) { + PluginDto pluginDto = new PluginDto(); + pluginDto.setName(plugin.getPlugin().getInformation().getName()); + pluginDto.setType(plugin.getPlugin().getInformation().getCategory() != null ? plugin.getPlugin().getInformation().getCategory() : "Sonstige/Miscellaneous"); + pluginDto.setVersion(plugin.getPlugin().getInformation().getVersion()); + pluginDto.setAuthor(plugin.getPlugin().getInformation().getAuthor()); + pluginDto.setDescription(plugin.getPlugin().getInformation().getDescription()); + + Links.Builder linksBuilder = linkingTo() + .self(resourceLinks.uiPlugin() + .self(plugin.getId())); + + pluginDto.add(linksBuilder.build()); + + return pluginDto; + } + + private Set getScriptResources(PluginWrapper wrapper) { + Set scriptResources = wrapper.getPlugin().getResources().getScriptResources(); + if (scriptResources != null) { + return scriptResources.stream() + .map(this::addContextPath) + .collect(Collectors.toSet()); + } + return Collections.emptySet(); + } + + private String addContextPath(String resource) { + String ctxPath = request.getContextPath(); + if (Strings.isNullOrEmpty(ctxPath)) { + return resource; + } + return HttpUtil.append(ctxPath, resource); + } + +} 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/PluginResource.java new file mode 100644 index 0000000000..fc49a51513 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginResource.java @@ -0,0 +1,84 @@ +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.PluginLoader; +import sonia.scm.plugin.PluginWrapper; +import sonia.scm.web.VndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class PluginResource { + + private final PluginLoader pluginLoader; + private final PluginDtoCollectionMapper collectionMapper; + private final PluginDtoMapper mapper; + + @Inject + public PluginResource(PluginLoader pluginLoader, PluginDtoCollectionMapper collectionMapper, PluginDtoMapper mapper) { + this.pluginLoader = pluginLoader; + this.collectionMapper = collectionMapper; + this.mapper = mapper; + } + + /** + * Returns a collection of installed plugins. + * + * @return collection of installed 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 getInstalledPlugins() { + List plugins = pluginLoader.getInstalledPlugins() + .stream() + .collect(Collectors.toList()); + + return Response.ok(collectionMapper.map(plugins)).build(); + } + + /** + * Returns the installed plugin with the given id. + * + * @param id id of plugin + * + * @return installed plugin with specified id + */ + @GET + @Path("{id}") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 404, condition = "not found"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(PluginDto.class) + @Produces(VndMediaType.PLUGIN) + public Response getInstalledPlugin(@PathParam("id") String id) { + Optional pluginDto = pluginLoader.getInstalledPlugins() + .stream() + .filter(plugin -> id.equals(plugin.getId())) + .map(mapper::map) + .findFirst(); + + if (pluginDto.isPresent()) { + return Response.ok(pluginDto.get()).build(); + } else { + return Response.status(Response.Status.NOT_FOUND).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 new file mode 100644 index 0000000000..e9b0f0a997 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PluginRootResource.java @@ -0,0 +1,21 @@ +package sonia.scm.api.v2.resources; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.ws.rs.Path; + +@Path("v2/") +public class PluginRootResource { + + private Provider pluginResourceProvider; + + @Inject + public PluginRootResource(Provider pluginResourceProvider) { + this.pluginResourceProvider = pluginResourceProvider; + } + + @Path("plugins") + public PluginResource plugins() { + return pluginResourceProvider.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 5e7e89a2e6..e9d47f2346 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,6 +651,38 @@ class ResourceLinks { } } + public PluginLinks plugin() { + return new PluginLinks(scmPathInfoStore.get()); + } + + static class PluginLinks { + private final LinkBuilder pluginLinkBuilder; + + PluginLinks(ScmPathInfo pathInfo) { + pluginLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, PluginResource.class); + } + + String self(String id) { + return pluginLinkBuilder.method("plugins").parameters().method("getInstalledPlugin").parameters(id).href(); + } + } + + public PluginCollectionLinks pluginCollection() { + return new PluginCollectionLinks(scmPathInfoStore.get()); + } + + static class PluginCollectionLinks { + private final LinkBuilder pluginCollectionLinkBuilder; + + PluginCollectionLinks(ScmPathInfo pathInfo) { + pluginCollectionLinkBuilder = new LinkBuilder(pathInfo, UIRootResource.class, UIPluginResource.class); + } + + String self() { + return pluginCollectionLinkBuilder.method("plugins").parameters().method("getInstalledPlugins").parameters().href(); + } + } + public AuthenticationLinks authentication() { return new AuthenticationLinks(scmPathInfoStore.get()); }