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 f306114ecb..fb80450bc8 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -17,6 +17,7 @@ public class VndMediaType { public static final String REPOSITORY = PREFIX + "repository" + SUFFIX; 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; private VndMediaType() { } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java new file mode 100644 index 0000000000..e9c1f9e34e --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java @@ -0,0 +1,54 @@ +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.Repository; +import sonia.scm.repository.RepositoryException; +import sonia.scm.repository.RepositoryManager; +import sonia.scm.web.VndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +public class RepositoryCollectionResource { + + private final CollectionResourceManagerAdapter adapter; + private final RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper; + + @Inject + public RepositoryCollectionResource(RepositoryManager manager, RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper) { + this.adapter = new CollectionResourceManagerAdapter<>(manager); + this.repositoryCollectionToDtoMapper = repositoryCollectionToDtoMapper; + } + + @GET + @Path("") + @Produces(VndMediaType.REPOSITORY_COLLECTION) + @TypeHint(UserDto[].class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repository\" privilege"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response getAll(@DefaultValue("0") @QueryParam("page") int page, + @DefaultValue("10") @QueryParam("pageSize") int pageSize, + @QueryParam("sortBy") String sortBy, + @DefaultValue("false") @QueryParam("desc") boolean desc) { + return adapter.getAll(page, pageSize, sortBy, desc, + pageResult -> repositoryCollectionToDtoMapper.map(page, pageSize, pageResult)); + } + + @POST + @Path("") + public Response create() { + throw new UnsupportedOperationException(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionToDtoMapper.java new file mode 100644 index 0000000000..a1cf0218e4 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionToDtoMapper.java @@ -0,0 +1,34 @@ +package sonia.scm.api.v2.resources; + +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryPermissions; + +import javax.inject.Inject; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +public class RepositoryCollectionToDtoMapper extends BasicCollectionToDtoMapper { + + private final ResourceLinks resourceLinks; + + @Inject + public RepositoryCollectionToDtoMapper(RepositoryToRepositoryDtoMapper repositoryToDtoMapper, ResourceLinks resourceLinks) { + super("repositories", repositoryToDtoMapper); + this.resourceLinks = resourceLinks; + } + + @Override + String createCreateLink() { + return resourceLinks.repositoryCollection().create(); + } + + @Override + String createSelfLink() { + return resourceLinks.repositoryCollection().self(); + } + + @Override + boolean isCreatePermitted() { + return RepositoryPermissions.create().isPermitted(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRootResource.java index e7861a884b..a7a6365c37 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRootResource.java @@ -12,14 +12,21 @@ public class RepositoryRootResource { static final String REPOSITORIES_PATH_V2 = "v2/repositories/"; private final Provider repositoryResource; + private final Provider repositoryCollectionResource; @Inject - public RepositoryRootResource(Provider repositoryResource) { + public RepositoryRootResource(Provider repositoryResource, Provider repositoryCollectionResource) { this.repositoryResource = repositoryResource; + this.repositoryCollectionResource = repositoryCollectionResource; } @Path("{namespace}/{name}") public RepositoryResource getRepositoryResource() { return repositoryResource.get(); } + + @Path("") + public RepositoryCollectionResource getRepositoryCollectionResource() { + return repositoryCollectionResource.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 11d1bc97ef..8f6f1eaae3 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 @@ -125,6 +125,26 @@ class ResourceLinks { } } + RepositoryCollectionLinks repositoryCollection() { + return new RepositoryCollectionLinks(uriInfoStore.get()); + } + + static class RepositoryCollectionLinks { + private final LinkBuilder collectionLinkBuilder; + + RepositoryCollectionLinks(UriInfo uriInfo) { + collectionLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryCollectionResource.class); + } + + String self() { + return collectionLinkBuilder.method("getRepositoryCollectionResource").parameters().method("getAll").parameters().href(); + } + + String create() { + return collectionLinkBuilder.method("getRepositoryCollectionResource").parameters().method("create").parameters().href(); + } + } + public TagCollectionLinks tagCollection() { return new TagCollectionLinks(uriInfoStore.get()); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java index fcd918152e..66e8aa0056 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java @@ -11,16 +11,20 @@ import org.junit.Rule; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import sonia.scm.PageResult; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; import java.net.URI; import java.net.URISyntaxException; +import static java.util.Collections.singletonList; import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import static javax.servlet.http.HttpServletResponse.SC_OK; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; @@ -49,7 +53,9 @@ public class RepositoryRootResourceTest { public void prepareEnvironment() { initMocks(this); RepositoryResource repositoryResource = new RepositoryResource(repositoryToDtoMapper, repositoryManager, null, null, null, null, null); - RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider.of(repositoryResource)); + RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks); + RepositoryCollectionResource repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper); + RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider.of(repositoryResource), MockProvider.of(repositoryCollectionResource)); dispatcher.getRegistry().addSingletonResource(repositoryRootResource); } @@ -78,6 +84,24 @@ public class RepositoryRootResourceTest { assertTrue(response.getContentAsString().contains("\"name\":\"repo\"")); } + @Test + public void shouldGetAll() throws URISyntaxException { + PageResult singletonPageResult = createSingletonPageResult(mockRepository("space", "repo")); + when(repositoryManager.getPage(any(), eq(0), eq(10))).thenReturn(singletonPageResult); + + MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + + assertEquals(SC_OK, response.getStatus()); + assertTrue(response.getContentAsString().contains("\"name\":\"repo\"")); + } + + private PageResult createSingletonPageResult(Repository repository) { + return new PageResult<>(singletonList(repository), 0); + } + private Repository mockRepository(String namespace, String name) { Repository repository = new Repository(); repository.setNamespace(namespace); 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 5dad23fe58..b35bc3820c 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 @@ -18,6 +18,7 @@ public class ResourceLinksMock { when(resourceLinks.group()).thenReturn(new ResourceLinks.GroupLinks(uriInfo)); when(resourceLinks.groupCollection()).thenReturn(new ResourceLinks.GroupCollectionLinks(uriInfo)); when(resourceLinks.repository()).thenReturn(new ResourceLinks.RepositoryLinks(uriInfo)); + when(resourceLinks.repositoryCollection()).thenReturn(new ResourceLinks.RepositoryCollectionLinks(uriInfo)); when(resourceLinks.tagCollection()).thenReturn(new ResourceLinks.TagCollectionLinks(uriInfo)); when(resourceLinks.branchCollection()).thenReturn(new ResourceLinks.BranchCollectionLinks(uriInfo)); when(resourceLinks.changesetCollection()).thenReturn(new ResourceLinks.ChangesetCollectionLinks(uriInfo));