From 99ecc8cba205ff420d2558bc12ed9369bfce28ad Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 2 Aug 2018 11:56:35 +0200 Subject: [PATCH] implemented rest endpoint for repository types --- .../src/main/java/sonia/scm/ScmState.java | 11 +- .../scm/repository/RepositoryHandler.java | 3 + .../scm/repository/RepositoryManager.java | 3 +- .../RepositoryManagerDecorator.java | 2 +- .../main/java/sonia/scm/web/VndMediaType.java | 3 + .../scm/repository/GitRepositoryHandler.java | 5 +- .../scm/repository/HgRepositoryHandler.java | 5 +- .../scm/repository/SvnRepositoryHandler.java | 5 +- .../repository/DummyRepositoryHandler.java | 6 +- .../scm/api/v2/resources/BaseMapper.java | 5 +- .../v2/resources/CollectionToDtoMapper.java | 32 ++++ .../scm/api/v2/resources/MapperModule.java | 3 + .../RepositoryTypeCollectionResource.java | 36 +++++ .../RepositoryTypeCollectionToDtoMapper.java | 21 +++ .../api/v2/resources/RepositoryTypeDto.java | 22 +++ .../v2/resources/RepositoryTypeResource.java | 53 +++++++ .../resources/RepositoryTypeRootResource.java | 35 +++++ ...positoryTypeToRepositoryTypeDtoMapper.java | 25 +++ .../scm/api/v2/resources/ResourceLinks.java | 33 ++++ .../repository/DefaultRepositoryManager.java | 4 +- ...positoryTypeCollectionToDtoMapperTest.java | 60 ++++++++ .../RepositoryTypeRootResourceTest.java | 142 ++++++++++++++++++ ...toryTypeToRepositoryTypeDtoMapperTest.java | 42 ++++++ .../api/v2/resources/ResourceLinksMock.java | 3 + .../sonia/scm/it/IntegrationTestUtil.java | 3 +- .../test/java/sonia/scm/it/UserITCase.java | 3 +- .../DefaultRepositoryManagerPerfTest.java | 3 +- .../DefaultRepositoryManagerTest.java | 9 +- 28 files changed, 545 insertions(+), 32 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/CollectionToDtoMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionToDtoMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeDto.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeRootResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeToRepositoryTypeDtoMapper.java create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionToDtoMapperTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeRootResourceTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeToRepositoryTypeDtoMapperTest.java diff --git a/scm-core/src/main/java/sonia/scm/ScmState.java b/scm-core/src/main/java/sonia/scm/ScmState.java index 5610a40c08..cd36aa707c 100644 --- a/scm-core/src/main/java/sonia/scm/ScmState.java +++ b/scm-core/src/main/java/sonia/scm/ScmState.java @@ -35,6 +35,7 @@ package sonia.scm; //~--- non-JDK imports -------------------------------------------------------- +import sonia.scm.repository.RepositoryType; import sonia.scm.security.PermissionDescriptor; import sonia.scm.user.User; @@ -82,9 +83,9 @@ public final class ScmState * @since 2.0.0 */ public ScmState(String version, User user, Collection groups, - String token, Collection repositoryTypes, String defaultUserType, - ScmClientConfig clientConfig, List assignedPermission, - List availablePermissions) + String token, Collection repositoryTypes, String defaultUserType, + ScmClientConfig clientConfig, List assignedPermission, + List availablePermissions) { this.version = version; this.user = user; @@ -165,7 +166,7 @@ public final class ScmState * * @return all available repository types */ - public Collection getRepositoryTypes() + public Collection getRepositoryTypes() { return repositoryTypes; } @@ -244,7 +245,7 @@ public final class ScmState /** Field description */ @XmlElement(name = "repositoryTypes") - private Collection repositoryTypes; + private Collection repositoryTypes; /** Field description */ private User user; diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryHandler.java index 8dc5f8418b..739d0d0177 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryHandler.java @@ -82,4 +82,7 @@ public interface RepositoryHandler * @since 1.15 */ public String getVersionInformation(); + + @Override + RepositoryType getType(); } diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java index 493d8f6dbb..629e0d4513 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java @@ -35,7 +35,6 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- -import sonia.scm.Type; import sonia.scm.TypeManager; import javax.servlet.http.HttpServletRequest; @@ -100,7 +99,7 @@ public interface RepositoryManager * * @return all configured repository types */ - public Collection getConfiguredTypes(); + public Collection getConfiguredTypes(); /** * Returns the {@link Repository} associated to the request uri. diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java index 6990baf7c5..b504359bc0 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java @@ -103,7 +103,7 @@ public class RepositoryManagerDecorator * @return */ @Override - public Collection getConfiguredTypes() + public Collection getConfiguredTypes() { return decorated.getConfiguredTypes(); } 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 6d20d14043..36bde974d0 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -6,6 +6,7 @@ import javax.ws.rs.core.MediaType; * Vendor media types used by SCMM. */ public class VndMediaType { + private static final String VERSION = "2"; private static final String TYPE = "application"; private static final String SUBTYPE_PREFIX = "vnd.scmm-"; @@ -18,6 +19,8 @@ public class VndMediaType { public static final String USER_COLLECTION = PREFIX + "userCollection" + SUFFIX; public static final String GROUP_COLLECTION = PREFIX + "groupCollection" + SUFFIX; public static final String REPOSITORY_COLLECTION = PREFIX + "repositoryCollection" + SUFFIX; + public static final String REPOSITORY_TYPE_COLLECTION = PREFIX + "repositoryTypeCollection" + SUFFIX; + public static final String REPOSITORY_TYPE = PREFIX + "repositoryType" + SUFFIX; public static final String ME = PREFIX + "me" + SUFFIX; private VndMediaType() { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java index 2338cc3b46..87f96f850f 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java @@ -41,7 +41,6 @@ import com.google.inject.Singleton; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; -import sonia.scm.Type; import sonia.scm.io.FileSystem; import sonia.scm.plugin.Extension; import sonia.scm.repository.spi.GitRepositoryServiceProvider; @@ -88,7 +87,7 @@ public class GitRepositoryHandler private static final Logger logger = LoggerFactory.getLogger(GitRepositoryHandler.class); /** Field description */ - public static final Type TYPE = new RepositoryType(TYPE_NAME, + public static final RepositoryType TYPE = new RepositoryType(TYPE_NAME, TYPE_DISPLAYNAME, GitRepositoryServiceProvider.COMMANDS); @@ -167,7 +166,7 @@ public class GitRepositoryHandler * @return */ @Override - public Type getType() + public RepositoryType getType() { return TYPE; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java index 40987fa4da..aad546f651 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java @@ -44,7 +44,6 @@ import org.slf4j.LoggerFactory; import sonia.scm.ConfigurationException; import sonia.scm.SCMContextProvider; -import sonia.scm.Type; import sonia.scm.installer.HgInstaller; import sonia.scm.installer.HgInstallerFactory; import sonia.scm.io.DirectoryFileFilter; @@ -98,7 +97,7 @@ public class HgRepositoryHandler public static final String TYPE_NAME = "hg"; /** Field description */ - public static final Type TYPE = new RepositoryType(TYPE_NAME, + public static final RepositoryType TYPE = new RepositoryType(TYPE_NAME, TYPE_DISPLAYNAME, HgRepositoryServiceProvider.COMMANDS, HgRepositoryServiceProvider.FEATURES); @@ -259,7 +258,7 @@ public class HgRepositoryHandler * @return */ @Override - public Type getType() + public RepositoryType getType() { return TYPE; } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java index dcadd5c1c6..58ada0738b 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java @@ -49,7 +49,6 @@ import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.io.SVNRepositoryFactory; import org.tmatesoft.svn.util.SVNDebugLog; -import sonia.scm.Type; import sonia.scm.io.FileSystem; import sonia.scm.logging.SVNKitLogger; import sonia.scm.plugin.Extension; @@ -87,7 +86,7 @@ public class SvnRepositoryHandler public static final String TYPE_NAME = "svn"; /** Field description */ - public static final Type TYPE = new RepositoryType(TYPE_NAME, + public static final RepositoryType TYPE = new RepositoryType(TYPE_NAME, TYPE_DISPLAYNAME, SvnRepositoryServiceProvider.COMMANDS); @@ -150,7 +149,7 @@ public class SvnRepositoryHandler * @return */ @Override - public Type getType() + public RepositoryType getType() { return TYPE; } diff --git a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java index 870127b14e..1a1d4c413a 100644 --- a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java +++ b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java @@ -33,7 +33,7 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- -import sonia.scm.Type; +import com.google.common.collect.Sets; import sonia.scm.io.DefaultFileSystem; import sonia.scm.store.ConfigurationStoreFactory; @@ -54,7 +54,7 @@ public class DummyRepositoryHandler public static final String TYPE_NAME = "dummy"; - public static final Type TYPE = new Type(TYPE_NAME, TYPE_DISPLAYNAME); + public static final RepositoryType TYPE = new RepositoryType(TYPE_NAME, TYPE_DISPLAYNAME, Sets.newHashSet()); private final Set existingRepoNames = new HashSet<>(); @@ -63,7 +63,7 @@ public class DummyRepositoryHandler } @Override - public Type getType() { + public RepositoryType getType() { return TYPE; } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java index 94d884c437..db24463be8 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseMapper.java @@ -2,14 +2,13 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.HalRepresentation; import org.mapstruct.Mapping; -import sonia.scm.ModelObject; import java.time.Instant; -abstract class BaseMapper { +abstract class BaseMapper { @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes - public abstract D map(T modelObject); + public abstract D map(T object); Instant mapTime(Long epochMilli) { return epochMilli == null? null: Instant.ofEpochMilli(epochMilli); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/CollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/CollectionToDtoMapper.java new file mode 100644 index 0000000000..4f8c6a6f3f --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/CollectionToDtoMapper.java @@ -0,0 +1,32 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import static de.otto.edison.hal.Embedded.embeddedBuilder; +import static de.otto.edison.hal.Links.linkingTo; + +abstract class CollectionToDtoMapper { + + private final String collectionName; + private final BaseMapper mapper; + + protected CollectionToDtoMapper(String collectionName, BaseMapper mapper) { + this.collectionName = collectionName; + this.mapper = mapper; + } + + public HalRepresentation map(Collection collection) { + List dtos = collection.stream().map(mapper::map).collect(Collectors.toList()); + return new HalRepresentation( + linkingTo().self(createSelfLink()).build(), + embeddedBuilder().with(collectionName, dtos).build() + ); + } + + protected abstract String createSelfLink(); + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java index 8e22755fe0..eea80a077b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -18,6 +18,9 @@ public class MapperModule extends AbstractModule { bind(RepositoryToRepositoryDtoMapper.class).to(Mappers.getMapper(RepositoryToRepositoryDtoMapper.class).getClass()); bind(RepositoryDtoToRepositoryMapper.class).to(Mappers.getMapper(RepositoryDtoToRepositoryMapper.class).getClass()); + bind(RepositoryTypeToRepositoryTypeDtoMapper.class).to(Mappers.getMapper(RepositoryTypeToRepositoryTypeDtoMapper.class).getClass()); + bind(RepositoryTypeCollectionToDtoMapper.class); + bind(UriInfoStore.class).in(ServletScopes.REQUEST); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionResource.java new file mode 100644 index 0000000000..4f9e76a939 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionResource.java @@ -0,0 +1,36 @@ +package sonia.scm.api.v2.resources; + +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import de.otto.edison.hal.HalRepresentation; +import sonia.scm.repository.RepositoryManager; +import sonia.scm.web.VndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +public class RepositoryTypeCollectionResource { + + private RepositoryManager repositoryManager; + private RepositoryTypeCollectionToDtoMapper mapper; + + @Inject + public RepositoryTypeCollectionResource(RepositoryManager repositoryManager, RepositoryTypeCollectionToDtoMapper mapper) { + this.repositoryManager = repositoryManager; + this.mapper = mapper; + } + + @GET + @Path("") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @Produces(VndMediaType.REPOSITORY_TYPE_COLLECTION) + public HalRepresentation getAll() { + return mapper.map(repositoryManager.getConfiguredTypes()); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionToDtoMapper.java new file mode 100644 index 0000000000..b9d7ff46c9 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionToDtoMapper.java @@ -0,0 +1,21 @@ +package sonia.scm.api.v2.resources; + +import sonia.scm.repository.RepositoryType; + +import javax.inject.Inject; + +public class RepositoryTypeCollectionToDtoMapper extends CollectionToDtoMapper { + + private final ResourceLinks resourceLinks; + + @Inject + public RepositoryTypeCollectionToDtoMapper(RepositoryTypeToRepositoryTypeDtoMapper mapper, ResourceLinks resourceLinks) { + super("repository-types", mapper); + this.resourceLinks = resourceLinks; + } + + @Override + protected String createSelfLink() { + return resourceLinks.repositoryTypeCollection().self(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeDto.java new file mode 100644 index 0000000000..73a1b60b9e --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeDto.java @@ -0,0 +1,22 @@ +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; + +@NoArgsConstructor +@Getter +@Setter +public class RepositoryTypeDto extends HalRepresentation { + + private String name; + private String displayName; + + @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package + protected HalRepresentation add(Links links) { + return super.add(links); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeResource.java new file mode 100644 index 0000000000..3a47fdf6c6 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeResource.java @@ -0,0 +1,53 @@ +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.repository.RepositoryManager; +import sonia.scm.repository.RepositoryType; +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; + +public class RepositoryTypeResource { + + private RepositoryManager repositoryManager; + private RepositoryTypeToRepositoryTypeDtoMapper mapper; + + @Inject + public RepositoryTypeResource(RepositoryManager repositoryManager, RepositoryTypeToRepositoryTypeDtoMapper mapper) { + this.repositoryManager = repositoryManager; + this.mapper = mapper; + } + + /** + * Returns the specified repository type. + * + * Note: This method requires "group" privilege. + * + * @param name of the requested repository type + */ + @GET + @Path("") + @Produces(VndMediaType.REPOSITORY_TYPE) + @TypeHint(RepositoryTypeDto.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 404, condition = "not found, no repository type with the specified name available"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response get(@PathParam("name") String name) { + for (RepositoryType type : repositoryManager.getConfiguredTypes()) { + if (name.equalsIgnoreCase(type.getName())) { + return Response.ok(mapper.map(type)).build(); + } + } + return Response.status(Response.Status.NOT_FOUND).build(); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeRootResource.java new file mode 100644 index 0000000000..0adc198e96 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeRootResource.java @@ -0,0 +1,35 @@ +package sonia.scm.api.v2.resources; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.ws.rs.Path; + +/** + * RESTful Web Service Resource to get available repository types. + */ +@Path(RepositoryTypeRootResource.PATH) +public class RepositoryTypeRootResource { + + static final String PATH = "v2/repository-types/"; + + private Provider collectionResourceProvider; + private Provider resourceProvider; + + @Inject + public RepositoryTypeRootResource(Provider collectionResourceProvider, Provider resourceProvider) { + this.collectionResourceProvider = collectionResourceProvider; + this.resourceProvider = resourceProvider; + } + + @Path("") + public RepositoryTypeCollectionResource getRepositoryTypeCollectionResource() { + return collectionResourceProvider.get(); + } + + @Path("{name}") + public RepositoryTypeResource getRepositoryTypeResource() { + return resourceProvider.get(); + } + + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeToRepositoryTypeDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeToRepositoryTypeDtoMapper.java new file mode 100644 index 0000000000..438273a514 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryTypeToRepositoryTypeDtoMapper.java @@ -0,0 +1,25 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Links; +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import sonia.scm.repository.RepositoryType; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Links.linkingTo; + +@Mapper +public abstract class RepositoryTypeToRepositoryTypeDtoMapper extends BaseMapper { + + @Inject + private ResourceLinks resourceLinks; + + @AfterMapping + void appendLinks(RepositoryType repositoryType, @MappingTarget RepositoryTypeDto target) { + Links.Builder linksBuilder = linkingTo().self(resourceLinks.repositoryType().self(repositoryType.getName())); + target.add(linksBuilder.build()); + } + +} 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 8f6f1eaae3..f48f160d83 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 @@ -145,6 +145,39 @@ class ResourceLinks { } } + public RepositoryTypeLinks repositoryType() { + return new RepositoryTypeLinks(uriInfoStore.get()); + } + + static class RepositoryTypeLinks { + private final LinkBuilder repositoryTypeLinkBuilder; + + RepositoryTypeLinks(UriInfo uriInfo) { + repositoryTypeLinkBuilder = new LinkBuilder(uriInfo, RepositoryTypeRootResource.class, RepositoryTypeResource.class); + } + + String self(String name) { + return repositoryTypeLinkBuilder.method("getRepositoryTypeResource").parameters(name).method("get").parameters().href(); + } + } + + public RepositoryTypeCollectionLinks repositoryTypeCollection() { + return new RepositoryTypeCollectionLinks(uriInfoStore.get()); + } + + static class RepositoryTypeCollectionLinks { + private final LinkBuilder collectionLinkBuilder; + + RepositoryTypeCollectionLinks(UriInfo uriInfo) { + collectionLinkBuilder = new LinkBuilder(uriInfo, RepositoryTypeRootResource.class, RepositoryTypeCollectionResource.class); + } + + String self() { + return collectionLinkBuilder.method("getRepositoryTypeCollectionResource").parameters().method("getAll").parameters().href(); + } + } + + public TagCollectionLinks tagCollection() { return new TagCollectionLinks(uriInfoStore.get()); } diff --git a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java index 162f825b6a..645c3a6b72 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java @@ -300,8 +300,8 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { } @Override - public Collection getConfiguredTypes() { - List validTypes = Lists.newArrayList(); + public Collection getConfiguredTypes() { + List validTypes = Lists.newArrayList(); for (RepositoryHandler handler : handlerMap.values()) { if (handler.isConfigured()) { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionToDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionToDtoMapperTest.java new file mode 100644 index 0000000000..ecde79ae1f --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeCollectionToDtoMapperTest.java @@ -0,0 +1,60 @@ +package sonia.scm.api.v2.resources; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import de.otto.edison.hal.Embedded; +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Link; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.RepositoryType; + +import java.net.URI; +import java.util.List; +import java.util.Optional; + +import static org.junit.Assert.*; + +@RunWith(MockitoJUnitRunner.class) +public class RepositoryTypeCollectionToDtoMapperTest { + + private final URI baseUri = URI.create("https://scm-manager.org/scm/"); + + @SuppressWarnings("unused") // Is injected + private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); + + @InjectMocks + private RepositoryTypeToRepositoryTypeDtoMapperImpl mapper; + + private List types = Lists.newArrayList( + new RepositoryType("hk", "Hitchhiker", Sets.newHashSet()), + new RepositoryType("hog", "Heart of Gold", Sets.newHashSet()) + ); + + private RepositoryTypeCollectionToDtoMapper collectionMapper; + + @Before + public void setUpEnvironment() { + collectionMapper = new RepositoryTypeCollectionToDtoMapper(mapper, resourceLinks); + } + + @Test + public void shouldHaveEmbeddedDtos() { + HalRepresentation mappedTypes = collectionMapper.map(types); + Embedded embedded = mappedTypes.getEmbedded(); + List embeddedTypes = embedded.getItemsBy("repository-types", RepositoryTypeDto.class); + assertEquals("hk", embeddedTypes.get(0).getName()); + assertEquals("hog", embeddedTypes.get(1).getName()); + } + + @Test + public void shouldHaveSelfLink() { + HalRepresentation mappedTypes = collectionMapper.map(types); + Optional self = mappedTypes.getLinks().getLinkBy("self"); + assertEquals("https://scm-manager.org/scm/v2/repository-types/", self.get().getHref()); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeRootResourceTest.java new file mode 100644 index 0000000000..0dfb6bf92c --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeRootResourceTest.java @@ -0,0 +1,142 @@ +package sonia.scm.api.v2.resources; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.jboss.resteasy.core.Dispatcher; +import org.jboss.resteasy.mock.MockDispatcherFactory; +import org.jboss.resteasy.mock.MockHttpRequest; +import org.jboss.resteasy.mock.MockHttpResponse; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.RepositoryManager; +import sonia.scm.repository.RepositoryType; +import sonia.scm.web.VndMediaType; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; +import static javax.servlet.http.HttpServletResponse.SC_OK; +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class RepositoryTypeRootResourceTest { + + private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + + @Mock + private RepositoryManager repositoryManager; + + private final URI baseUri = URI.create("/"); + private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); + + @InjectMocks + private RepositoryTypeToRepositoryTypeDtoMapperImpl mapper; + + private List types = Lists.newArrayList( + new RepositoryType("hk", "Hitchhiker", Sets.newHashSet()), + new RepositoryType("hog", "Heart of Gold", Sets.newHashSet()) + ); + + @Before + public void prepareEnvironment() { + when(repositoryManager.getConfiguredTypes()).thenReturn(types); + + RepositoryTypeCollectionToDtoMapper collectionMapper = new RepositoryTypeCollectionToDtoMapper(mapper, resourceLinks); + RepositoryTypeCollectionResource collectionResource = new RepositoryTypeCollectionResource(repositoryManager, collectionMapper); + RepositoryTypeResource resource = new RepositoryTypeResource(repositoryManager, mapper); + RepositoryTypeRootResource rootResource = new RepositoryTypeRootResource(MockProvider.of(collectionResource), MockProvider.of(resource)); + dispatcher.getRegistry().addSingletonResource(rootResource); + } + + @Test + public void shouldHaveCollectionVndMediaType() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + RepositoryTypeRootResource.PATH); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertEquals(SC_OK, response.getStatus()); + String contentType = response.getOutputHeaders().getFirst("Content-Type").toString(); + assertThat(VndMediaType.REPOSITORY_TYPE_COLLECTION, equalToIgnoringCase(contentType)); + } + + @Test + public void shouldHaveCollectionSelfLink() throws URISyntaxException { + String uri = "/" + RepositoryTypeRootResource.PATH; + MockHttpRequest request = MockHttpRequest.get(uri); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertEquals(SC_OK, response.getStatus()); + assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"" + uri + "\"}")); + } + + @Test + public void shouldHaveEmbeddedRepositoryTypes() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + RepositoryTypeRootResource.PATH); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertEquals(SC_OK, response.getStatus()); + assertTrue(response.getContentAsString().contains("Hitchhiker")); + assertTrue(response.getContentAsString().contains("Heart of Gold")); + } + + @Test + public void shouldHaveVndMediaType() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + RepositoryTypeRootResource.PATH + "hk"); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertEquals(SC_OK, response.getStatus()); + String contentType = response.getOutputHeaders().getFirst("Content-Type").toString(); + assertThat(VndMediaType.REPOSITORY_TYPE, equalToIgnoringCase(contentType)); + } + + @Test + public void shouldContainAttributes() throws URISyntaxException { + String uri = "/" + RepositoryTypeRootResource.PATH + "hk"; + MockHttpRequest request = MockHttpRequest.get(uri); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertEquals(SC_OK, response.getStatus()); + assertTrue(response.getContentAsString().contains("hk")); + assertTrue(response.getContentAsString().contains("Hitchhiker")); + } + + @Test + public void shouldHaveSelfLink() throws URISyntaxException { + String uri = "/" + RepositoryTypeRootResource.PATH + "hk"; + MockHttpRequest request = MockHttpRequest.get(uri); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertEquals(SC_OK, response.getStatus()); + assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"" + uri + "\"}")); + } + + @Test + public void shouldReturn404OnUnknownTypes() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + RepositoryTypeRootResource.PATH + "git"); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertEquals(SC_NOT_FOUND, response.getStatus()); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeToRepositoryTypeDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeToRepositoryTypeDtoMapperTest.java new file mode 100644 index 0000000000..9a40c253c4 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTypeToRepositoryTypeDtoMapperTest.java @@ -0,0 +1,42 @@ +package sonia.scm.api.v2.resources; + +import com.google.common.collect.Sets; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.RepositoryType; + +import java.net.URI; + +import static org.junit.Assert.assertEquals; + +@RunWith(MockitoJUnitRunner.class) +public class RepositoryTypeToRepositoryTypeDtoMapperTest { + + private final URI baseUri = URI.create("https://scm-manager.org/scm/"); + + @SuppressWarnings("unused") // Is injected + private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); + + @InjectMocks + private RepositoryTypeToRepositoryTypeDtoMapperImpl mapper; + + private RepositoryType type = new RepositoryType("hk", "Hitchhiker", Sets.newHashSet()); + + @Test + public void shouldMapSimpleProperties() { + RepositoryTypeDto dto = mapper.map(type); + assertEquals("hk", dto.getName()); + assertEquals("Hitchhiker", dto.getDisplayName()); + } + + @Test + public void shouldAppendSelfLink() { + RepositoryTypeDto dto = mapper.map(type); + assertEquals( + "https://scm-manager.org/scm/v2/repository-types/hk", + dto.getLinks().getLinkBy("self").get().getHref() + ); + } +} 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 b35bc3820c..5c3205e8c3 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 @@ -24,6 +24,9 @@ public class ResourceLinksMock { when(resourceLinks.changesetCollection()).thenReturn(new ResourceLinks.ChangesetCollectionLinks(uriInfo)); when(resourceLinks.sourceCollection()).thenReturn(new ResourceLinks.SourceCollectionLinks(uriInfo)); when(resourceLinks.permissionCollection()).thenReturn(new ResourceLinks.PermissionCollectionLinks(uriInfo)); + when(resourceLinks.repositoryType()).thenReturn(new ResourceLinks.RepositoryTypeLinks(uriInfo)); + when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(uriInfo)); + return resourceLinks; } } diff --git a/scm-webapp/src/test/java/sonia/scm/it/IntegrationTestUtil.java b/scm-webapp/src/test/java/sonia/scm/it/IntegrationTestUtil.java index e12057e47a..c2f2e062f7 100644 --- a/scm-webapp/src/test/java/sonia/scm/it/IntegrationTestUtil.java +++ b/scm-webapp/src/test/java/sonia/scm/it/IntegrationTestUtil.java @@ -47,6 +47,7 @@ import sonia.scm.Type; import sonia.scm.api.rest.JSONContextResolver; import sonia.scm.api.rest.ObjectMapperProvider; import sonia.scm.repository.Person; +import sonia.scm.repository.RepositoryType; import sonia.scm.repository.client.api.ClientCommand; import sonia.scm.repository.client.api.RepositoryClient; import sonia.scm.user.User; @@ -140,7 +141,7 @@ public final class IntegrationTestUtil assertEquals("scmadmin", user.getName()); assertTrue(user.isAdmin()); - Collection types = state.getRepositoryTypes(); + Collection types = state.getRepositoryTypes(); assertNotNull(types); assertFalse(types.isEmpty()); diff --git a/scm-webapp/src/test/java/sonia/scm/it/UserITCase.java b/scm-webapp/src/test/java/sonia/scm/it/UserITCase.java index 9a749c6456..bd5ec13ab9 100644 --- a/scm-webapp/src/test/java/sonia/scm/it/UserITCase.java +++ b/scm-webapp/src/test/java/sonia/scm/it/UserITCase.java @@ -40,6 +40,7 @@ import org.junit.Test; import sonia.scm.ScmState; import sonia.scm.Type; +import sonia.scm.repository.RepositoryType; import sonia.scm.user.User; import sonia.scm.user.UserTestData; @@ -204,7 +205,7 @@ public class UserITCase extends AbstractAdminITCaseBase assertEquals("scmadmin", user.getName()); assertTrue(user.isAdmin()); - Collection types = state.getRepositoryTypes(); + Collection types = state.getRepositoryTypes(); assertNotNull(types); assertFalse(types.isEmpty()); diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java index 665c54c9e1..15c54ccb71 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java @@ -32,6 +32,7 @@ package sonia.scm.repository; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import com.google.inject.Provider; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; @@ -109,7 +110,7 @@ public class DefaultRepositoryManagerPerfTest { */ @Before public void setUpObjectUnderTest(){ - when(repositoryHandler.getType()).thenReturn(new Type(REPOSITORY_TYPE, REPOSITORY_TYPE)); + when(repositoryHandler.getType()).thenReturn(new RepositoryType(REPOSITORY_TYPE, REPOSITORY_TYPE, Sets.newHashSet())); Set handlerSet = ImmutableSet.of(repositoryHandler); RepositoryMatcher repositoryMatcher = new RepositoryMatcher(Collections.emptySet()); NamespaceStrategy namespaceStrategy = mock(NamespaceStrategy.class); diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java index 1f361d4766..b044dfd1d7 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java @@ -36,6 +36,7 @@ import com.github.legman.Subscribe; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import org.apache.shiro.authz.UnauthorizedException; import org.junit.Rule; import org.junit.Test; @@ -482,14 +483,14 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase