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 7269b3517f..24d7037b08 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java @@ -24,15 +24,11 @@ package sonia.scm.repository; -//~--- non-JDK imports -------------------------------------------------------- - import sonia.scm.TypeManager; import java.io.IOException; import java.util.Collection; -//~--- JDK imports ------------------------------------------------------------ - /** * The central class for managing {@link Repository} objects. * This class is a singleton and is available via injection. @@ -49,7 +45,7 @@ public interface RepositoryManager * @param event hook event * @since 2.0.0 */ - public void fireHookEvent(RepositoryHookEvent event); + void fireHookEvent(RepositoryHookEvent event); /** * Imports an existing {@link Repository}. @@ -58,9 +54,7 @@ public interface RepositoryManager * @param repository {@link Repository} to import * @throws IOException */ - public void importRepository(Repository repository) throws IOException; - - //~--- get methods ---------------------------------------------------------- + void importRepository(Repository repository) throws IOException; /** * Returns a {@link Repository} by its namespace and name or @@ -70,14 +64,14 @@ public interface RepositoryManager * @return {@link Repository} by its namespace and name or null * if the {@link Repository} could not be found */ - public Repository get(NamespaceAndName namespaceAndName); + Repository get(NamespaceAndName namespaceAndName); /** * Returns all configured repository types. * * @return all configured repository types */ - public Collection getConfiguredTypes(); + Collection getConfiguredTypes(); /** * Returns a {@link RepositoryHandler} by the given type (hg, git, svn ...). @@ -86,7 +80,7 @@ public interface RepositoryManager * @return {@link RepositoryHandler} by the given type */ @Override - public RepositoryHandler getHandler(String type); + RepositoryHandler getHandler(String type); /** * @param repository the repository {@link Repository} @@ -94,5 +88,12 @@ public interface RepositoryManager * @param newName the new repository name * @return {@link Repository} the renamed repository */ - public Repository rename(Repository repository, String newNameSpace, String newName); + Repository rename(Repository repository, String newNameSpace, String newName); + + /** + * Returns all namespaces. + * + * @return all namespaces + */ + Collection getAllNamespaces(); } 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 fdb8dfc1a8..6a661c83e5 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java @@ -131,6 +131,11 @@ public class RepositoryManagerDecorator return decorated.rename(repository, newNamespace, newName); } + @Override + public Collection getAllNamespaces() { + return decorated.getAllNamespaces(); + } + //~--- fields --------------------------------------------------------------- /** 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 14fd4b4532..4b79e3621c 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -44,6 +44,7 @@ public class VndMediaType { public static final String GROUP = PREFIX + "group" + SUFFIX; public static final String AUTOCOMPLETE = PREFIX + "autocomplete" + SUFFIX; public static final String REPOSITORY = PREFIX + "repository" + SUFFIX; + public static final String NAMESPACE = PREFIX + "namespace" + SUFFIX; public static final String REPOSITORY_PERMISSION = PREFIX + "repositoryPermission" + SUFFIX; public static final String CHANGESET = PREFIX + "changeset" + SUFFIX; public static final String CHANGESET_COLLECTION = PREFIX + "changesetCollection" + SUFFIX; @@ -57,6 +58,7 @@ 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 NAMESPACE_COLLECTION = PREFIX + "namespaceCollection" + SUFFIX; public static final String BRANCH_COLLECTION = PREFIX + "branchCollection" + SUFFIX; public static final String CONFIG = PREFIX + "config" + SUFFIX; public static final String REPOSITORY_VERB_COLLECTION = PREFIX + "repositoryVerbCollection" + SUFFIX; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceCollectionResource.java new file mode 100644 index 0000000000..6e76a7b951 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceCollectionResource.java @@ -0,0 +1,77 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +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 NamespaceCollectionResource { + + private final RepositoryManager manager; + private final NamespaceCollectionToDtoMapper repositoryCollectionToDtoMapper; + + @Inject + public NamespaceCollectionResource(RepositoryManager manager, NamespaceCollectionToDtoMapper repositoryCollectionToDtoMapper) { + this.manager = manager; + this.repositoryCollectionToDtoMapper = repositoryCollectionToDtoMapper; + } + + /** + * Returns all namespaces. + */ + @GET + @Path("") + @Produces(VndMediaType.NAMESPACE_COLLECTION) + @Operation(summary = "List of namespaces", description = "Returns all namespaces.", tags = "Namespace") + @ApiResponse( + responseCode = "200", + description = "success", + content = @Content( + mediaType = VndMediaType.NAMESPACE_COLLECTION, + schema = @Schema(implementation = CollectionDto.class) + ) + ) + @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") + @ApiResponse( + responseCode = "500", + description = "internal server error", + content = @Content( + mediaType = VndMediaType.ERROR_TYPE, + schema = @Schema(implementation = ErrorDto.class) + )) + public HalRepresentation getAll() { + return repositoryCollectionToDtoMapper.map(manager.getAllNamespaces()); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceCollectionToDtoMapper.java new file mode 100644 index 0000000000..64d0b4cb55 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceCollectionToDtoMapper.java @@ -0,0 +1,57 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Embedded; +import de.otto.edison.hal.HalRepresentation; + +import javax.inject.Inject; +import java.util.Collection; + +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 NamespaceCollectionToDtoMapper { + + private final NamespaceToNamespaceDtoMapper namespaceMapper; + private final ResourceLinks links; + + @Inject + public NamespaceCollectionToDtoMapper(NamespaceToNamespaceDtoMapper namespaceMapper, ResourceLinks links) { + this.namespaceMapper = namespaceMapper; + this.links = links; + } + + public HalRepresentation map(Collection namespaces) { + Embedded namespaceDtos = embeddedBuilder() + .with("namespaces", namespaces.stream().map(namespaceMapper::map).collect(toList())) + .build(); + return new HalRepresentation( + linkingTo().self(links.namespaceCollection().self()).build(), + namespaceDtos + ); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceDto.java new file mode 100644 index 0000000000..527792cf75 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceDto.java @@ -0,0 +1,42 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class NamespaceDto extends HalRepresentation { + + private String namespace; + + public NamespaceDto(String namespace, Links links) { + super(links); + this.namespace = namespace; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceResource.java new file mode 100644 index 0000000000..8c7fdc0a05 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceResource.java @@ -0,0 +1,100 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.api.v2.resources; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +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.PathParam; +import javax.ws.rs.Produces; + +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; + +public class NamespaceResource { + + private final RepositoryManager manager; + private final NamespaceToNamespaceDtoMapper namespaceMapper; + + @Inject + public NamespaceResource(RepositoryManager manager, NamespaceToNamespaceDtoMapper namespaceMapper) { + this.manager = manager; + this.namespaceMapper = namespaceMapper; + } + + /** + * Returns a namespace. + * + * @param namespace the requested namespace + */ + @GET + @Path("") + @Produces(VndMediaType.NAMESPACE) + @Operation(summary = "Get single namespace", description = "Returns the namespace for the given name.", tags = "Namespace") + @ApiResponse( + responseCode = "200", + description = "success", + content = @Content( + mediaType = VndMediaType.NAMESPACE, + schema = @Schema(implementation = NamespaceDto.class) + ) + ) + @ApiResponse( + responseCode = "401", + description = "not authenticated / invalid credentials" + ) + @ApiResponse( + responseCode = "404", + description = "not found, no namespace with the specified name available", + content = @Content( + mediaType = VndMediaType.ERROR_TYPE, + schema = @Schema(implementation = ErrorDto.class) + ) + ) + @ApiResponse( + responseCode = "500", + description = "internal server error", + content = @Content( + mediaType = VndMediaType.ERROR_TYPE, + schema = @Schema(implementation = ErrorDto.class) + ) + ) + public NamespaceDto get(@PathParam("namespace") String namespace) { + return manager.getAllNamespaces() + .stream() + .filter(n -> n.equals(namespace)) + .map(namespaceMapper::map) + .findFirst() + .orElseThrow(() -> notFound(entity("Namespace", namespace))); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceRootResource.java new file mode 100644 index 0000000000..bf0757a057 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceRootResource.java @@ -0,0 +1,64 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.api.v2.resources; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.tags.Tag; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.ws.rs.Path; + +/** + * RESTful Web Service Resource to manage namespaces. + */ +@OpenAPIDefinition( + tags = { + @Tag(name = "Namespace", description = "Namespace related endpoints") + } +) +@Path(NamespaceRootResource.NAMESPACE_PATH_V2) +public class NamespaceRootResource { + static final String NAMESPACE_PATH_V2 = "v2/namespaces/"; + + private final Provider namespaceCollectionResource; + private final Provider namespaceResource; + + @Inject + public NamespaceRootResource(Provider namespaceCollectionResource, Provider namespaceResource) { + this.namespaceCollectionResource = namespaceCollectionResource; + this.namespaceResource = namespaceResource; + } + + @Path("{namespace}") + public NamespaceResource getNamespaceResource() { + return namespaceResource.get(); + } + + @Path("") + public NamespaceCollectionResource getNamespaceCollectionResource() { + return namespaceCollectionResource.get(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceStrategyResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceStrategyResource.java index 47a349fe79..e03ea1ef5a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceStrategyResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceStrategyResource.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.api.v2.resources; import de.otto.edison.hal.Links; @@ -48,8 +48,8 @@ public class NamespaceStrategyResource { static final String PATH = "v2/namespaceStrategies"; - private Set namespaceStrategies; - private Provider namespaceStrategyProvider; + private final Set namespaceStrategies; + private final Provider namespaceStrategyProvider; @Inject public NamespaceStrategyResource(Set namespaceStrategies, Provider namespaceStrategyProvider) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceToNamespaceDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceToNamespaceDtoMapper.java new file mode 100644 index 0000000000..53e42de3cf --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NamespaceToNamespaceDtoMapper.java @@ -0,0 +1,43 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.api.v2.resources; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Links.linkingTo; + +class NamespaceToNamespaceDtoMapper { + + private final ResourceLinks links; + + @Inject + NamespaceToNamespaceDtoMapper(ResourceLinks links) { + this.links = links; + } + + NamespaceDto map(String namespace) { + return new NamespaceDto(namespace, linkingTo().self(links.namespace().self(namespace)).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 1ed150a2f1..b497e4a5df 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 @@ -876,4 +876,36 @@ class ResourceLinks { return permissionsLinkBuilder.method("getAll").parameters().href(); } } + + public NamespaceCollectionLinks namespaceCollection() { + return new NamespaceCollectionLinks(scmPathInfoStore.get()); + } + + static class NamespaceCollectionLinks { + private final LinkBuilder namespaceCollectionLinkBuilder; + + NamespaceCollectionLinks(ScmPathInfo scmPathInfo) { + this.namespaceCollectionLinkBuilder = new LinkBuilder(scmPathInfo, NamespaceRootResource.class, NamespaceCollectionResource.class); + } + + String self() { + return namespaceCollectionLinkBuilder.method("getNamespaceCollectionResource").parameters().method("getAll").parameters().href(); + } + } + + public NamespaceLinks namespace() { + return new NamespaceLinks(scmPathInfoStore.get()); + } + + static class NamespaceLinks { + private final LinkBuilder namespaceLinkBuilder; + + NamespaceLinks(ScmPathInfo scmPathInfo) { + this.namespaceLinkBuilder = new LinkBuilder(scmPathInfo, NamespaceRootResource.class, NamespaceResource.class); + } + + String self(String namespace) { + return namespaceLinkBuilder.method("getNamespaceResource").parameters().method("get").parameters(namespace).href(); + } + } } 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 4886e81ea1..c75f54c3fb 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java @@ -61,6 +61,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.function.Predicate; +import static java.util.stream.Collectors.toSet; import static sonia.scm.AlreadyExistsException.alreadyExists; import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.NotFoundException.notFound; @@ -330,6 +331,13 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { return getAll(null, start, limit); } + @Override + public Collection getAllNamespaces() { + return getAll().stream() + .map(Repository::getNamespace) + .collect(toSet()); + } + @Override public Collection getConfiguredTypes() { List validTypes = Lists.newArrayList(); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/NamespaceRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/NamespaceRootResourceTest.java new file mode 100644 index 0000000000..669cc70544 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/NamespaceRootResourceTest.java @@ -0,0 +1,106 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.api.v2.resources; + +import org.jboss.resteasy.mock.MockHttpRequest; +import org.jboss.resteasy.mock.MockHttpResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.repository.RepositoryManager; +import sonia.scm.web.RestDispatcher; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; + +import static com.google.inject.util.Providers.of; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class NamespaceRootResourceTest { + + @Mock + RepositoryManager repositoryManager; + + RestDispatcher dispatcher = new RestDispatcher(); + MockHttpResponse response = new MockHttpResponse(); + + ResourceLinks links = ResourceLinksMock.createMock(URI.create("/")); + + @BeforeEach + void setUpResources() { + NamespaceToNamespaceDtoMapper namespaceMapper = new NamespaceToNamespaceDtoMapper(links); + NamespaceCollectionToDtoMapper namespaceCollectionToDtoMapper = new NamespaceCollectionToDtoMapper(namespaceMapper, links); + NamespaceCollectionResource namespaceCollectionResource = new NamespaceCollectionResource(repositoryManager, namespaceCollectionToDtoMapper); + NamespaceResource namespaceResource = new NamespaceResource(repositoryManager, namespaceMapper); + dispatcher.addSingletonResource(new NamespaceRootResource(of(namespaceCollectionResource), of(namespaceResource))); + } + + @Test + void shouldReturnAllNamespaces() throws URISyntaxException, UnsupportedEncodingException { + when(repositoryManager.getAllNamespaces()).thenReturn(asList("hitchhiker", "space")); + + MockHttpRequest request = MockHttpRequest.get("/" + NamespaceRootResource.NAMESPACE_PATH_V2); + + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(200); + assertThat(response.getContentAsString()) + .contains("\"self\":{\"href\":\"/v2/namespaces/\"}") + .contains("\"_embedded\"") + .contains("\"namespace\":\"hitchhiker\"") + .contains("\"namespace\":\"space\""); + } + + @Test + void shouldReturnSingleNamespace() throws URISyntaxException, UnsupportedEncodingException { + when(repositoryManager.getAllNamespaces()).thenReturn(asList("hitchhiker", "space")); + + MockHttpRequest request = MockHttpRequest.get("/" + NamespaceRootResource.NAMESPACE_PATH_V2 + "space"); + + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(200); + assertThat(response.getContentAsString()) + .contains("\"namespace\":\"space\"") + .contains("\"self\":{\"href\":\"/v2/namespaces/space\"}"); + } + + @Test + void shouldHandleUnknownNamespace() throws URISyntaxException, UnsupportedEncodingException { + when(repositoryManager.getAllNamespaces()).thenReturn(asList("hitchhiker", "space")); + + MockHttpRequest request = MockHttpRequest.get("/" + NamespaceRootResource.NAMESPACE_PATH_V2 + "unknown"); + + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(404); + } +} 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 96cf705625..38a1055211 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 @@ -76,6 +76,8 @@ public class ResourceLinksMock { lenient().when(resourceLinks.repositoryRoleCollection()).thenReturn(new ResourceLinks.RepositoryRoleCollectionLinks(pathInfo)); lenient().when(resourceLinks.namespaceStrategies()).thenReturn(new ResourceLinks.NamespaceStrategiesLinks(pathInfo)); lenient().when(resourceLinks.annotate()).thenReturn(new ResourceLinks.AnnotateLinks(pathInfo)); + lenient().when(resourceLinks.namespace()).thenReturn(new ResourceLinks.NamespaceLinks(pathInfo)); + lenient().when(resourceLinks.namespaceCollection()).thenReturn(new ResourceLinks.NamespaceCollectionLinks(pathInfo)); return resourceLinks; } 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 bab8d873f1..34189a717c 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java @@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableSet; import com.google.inject.util.Providers; import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.util.ThreadContext; +import org.assertj.core.api.Assertions; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -444,6 +445,18 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { assertEquals("hitchhiker", changedRepo.getNamespace()); } + @Test + public void shouldReturnDistinctNamespaces() { + createTestRepository(); + createSecondTestRepository(); + + Collection namespaces = ((RepositoryManager) manager).getAllNamespaces(); + + Assertions.assertThat(namespaces) + .hasSize(1) + .contains("default_namespace"); + } + //~--- methods -------------------------------------------------------------- @Override