From 04d480684ad85c6f91d767da83467513e88e3603 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 3 Mar 2020 10:39:07 +0100 Subject: [PATCH 1/4] prevent using same classloader multiple times --- .../sonia/scm/plugin/UberClassLoader.java | 132 ++++++------------ .../sonia/scm/plugin/UberClassLoaderTest.java | 74 ++++++++++ 2 files changed, 116 insertions(+), 90 deletions(-) create mode 100644 scm-webapp/src/test/java/sonia/scm/plugin/UberClassLoaderTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/UberClassLoader.java b/scm-webapp/src/main/java/sonia/scm/plugin/UberClassLoader.java index 62b1073a85..411fa8173c 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/UberClassLoader.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/UberClassLoader.java @@ -1,9 +1,9 @@ /** * Copyright (c) 2010, Sebastian Sdorra All rights reserved. - * + *

* Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * + *

* 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. 2. Redistributions in * binary form must reproduce the above copyright notice, this list of @@ -11,7 +11,7 @@ * materials provided with the distribution. 3. Neither the name of SCM-Manager; * nor the names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. - * + *

* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE @@ -22,60 +22,59 @@ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + *

* http://bitbucket.org/sdorra/scm-manager - * */ - package sonia.scm.plugin; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.collect.Lists; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; -//~--- JDK imports ------------------------------------------------------------ - import java.io.IOException; - import java.lang.ref.WeakReference; - import java.net.URL; - import java.util.Collections; import java.util.Enumeration; -import java.util.List; +import java.util.LinkedHashSet; +import java.util.Set; import java.util.concurrent.ConcurrentMap; +//~--- JDK imports ------------------------------------------------------------ + /** * {@link ClassLoader} which is able to load classes and resources from all * plugins. * * @author Sebastian Sdorra */ -public final class UberClassLoader extends ClassLoader -{ +public final class UberClassLoader extends ClassLoader { - /** - * Constructs ... - * - * - * @param parent - * @param plugins - */ - public UberClassLoader(ClassLoader parent, Iterable plugins) - { - super(parent); - this.plugins = plugins; + private final Set pluginClassLoaders; + private final ConcurrentMap>> cache = Maps.newConcurrentMap(); + + public UberClassLoader(ClassLoader parent, Iterable plugins) { + this(parent, collectClassLoaders(plugins)); } - //~--- methods -------------------------------------------------------------- + private static Set collectClassLoaders(Iterable plugins) { + ImmutableSet.Builder classLoaders = ImmutableSet.builder(); + plugins.forEach(plugin -> classLoaders.add(plugin.getClassLoader())); + return classLoaders.build(); + } + + @VisibleForTesting + UberClassLoader(ClassLoader parent, Set pluginClassLoaders) { + super(parent); + this.pluginClassLoaders = pluginClassLoaders; + } @Override - protected Class findClass(String name) throws ClassNotFoundException - { + protected Class findClass(String name) throws ClassNotFoundException { Class clazz = getFromCache(name); if (clazz == null) { @@ -87,8 +86,8 @@ public final class UberClassLoader extends ClassLoader } private Class findClassInPlugins(String name) throws ClassNotFoundException { - for (InstalledPlugin plugin : plugins) { - Class clazz = findClass(plugin.getClassLoader(), name); + for (ClassLoader pluginClassLoader : pluginClassLoaders) { + Class clazz = findClass(pluginClassLoader, name); if (clazz != null) { return clazz; } @@ -106,27 +105,14 @@ public final class UberClassLoader extends ClassLoader } } - /** - * Method description - * - * - * @param name - * - * @return - */ @Override - protected URL findResource(String name) - { + protected URL findResource(String name) { URL url = null; - for (InstalledPlugin plugin : plugins) - { - ClassLoader cl = plugin.getClassLoader(); + for (ClassLoader pluginClassLoader : pluginClassLoaders) { + url = pluginClassLoader.getResource(name); - url = cl.getResource(name); - - if (url != null) - { + if (url != null) { break; } } @@ -134,52 +120,26 @@ public final class UberClassLoader extends ClassLoader return url; } - /** - * Method description - * - * - * @param name - * - * @return - * - * @throws IOException - */ @Override - protected Enumeration findResources(String name) throws IOException - { - List urls = Lists.newArrayList(); + @SuppressWarnings("squid:S2112") + protected Enumeration findResources(String name) throws IOException { + Set urls = new LinkedHashSet<>(); - for (InstalledPlugin plugin : plugins) - { - ClassLoader cl = plugin.getClassLoader(); - - urls.addAll(Collections.list(cl.getResources(name))); + for (ClassLoader pluginClassLoader : pluginClassLoaders) { + urls.addAll(Collections.list(pluginClassLoader.getResources(name))); } return Collections.enumeration(urls); } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param name - * - * @return - */ - private Class getFromCache(String name) - { + private Class getFromCache(String name) { Class clazz = null; WeakReference> ref = cache.get(name); - if (ref != null) - { + if (ref != null) { clazz = ref.get(); - if (clazz == null) - { + if (clazz == null) { cache.remove(name); } } @@ -187,12 +147,4 @@ public final class UberClassLoader extends ClassLoader return clazz; } - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private final ConcurrentMap>> cache = - Maps.newConcurrentMap(); - - /** Field description */ - private final Iterable plugins; } diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/UberClassLoaderTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/UberClassLoaderTest.java new file mode 100644 index 0000000000..319e534c56 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/plugin/UberClassLoaderTest.java @@ -0,0 +1,74 @@ +package sonia.scm.plugin; + +import com.google.common.collect.ImmutableSet; +import com.google.common.io.Resources; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.TempDirectory; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(TempDirectory.class) +class UberClassLoaderTest { + + private final URLClassLoader parentClassLoader = new URLClassLoader(new URL[0]); + + @Test + void shouldOnlyUseClassloaderOnce(@TempDirectory.TempDir Path tempDir) throws IOException { + ClassLoader mailClassLoader = createClassLoader(tempDir, "plugin.txt", "mail"); + ClassLoader reviewClassLoader = createClassLoader(mailClassLoader, tempDir, "plugin.txt", "review"); + + UberClassLoader uberClassLoader = new UberClassLoader(parentClassLoader, ImmutableSet.of(mailClassLoader, reviewClassLoader)); + List resources = Collections.list(uberClassLoader.findResources("plugin.txt")); + + assertThat(resources).hasSize(2); + assertThat(toContent(resources)).containsOnly("mail", "review"); + } + + @Test + void shouldReturnResourceFromEachPluginClassLoader(@TempDirectory.TempDir Path tempDir) throws IOException { + ClassLoader mailClassLoader = createClassLoader(tempDir, "scm.txt", "mail"); + ClassLoader reviewClassLoader = createClassLoader(tempDir, "scm.txt", "review"); + + UberClassLoader uberClassLoader = new UberClassLoader(parentClassLoader, ImmutableSet.of(mailClassLoader, reviewClassLoader)); + List resources = Collections.list(uberClassLoader.findResources("scm.txt")); + assertThat(toContent(resources)).containsOnly("mail", "review"); + } + + @SuppressWarnings("UnstableApiUsage") + private List toContent(Iterable resources) throws IOException { + List content = new ArrayList<>(); + for (URL resource : resources) { + content.add(Resources.toString(resource, StandardCharsets.UTF_8)); + } + return content; + } + + private ClassLoader createClassLoader(Path tempDir, String resource, String value) throws IOException { + return createClassLoader(Thread.currentThread().getContextClassLoader(), tempDir, resource, value); + } + + private ClassLoader createClassLoader(ClassLoader parent, Path tempDir, String resource, String value) throws IOException { + Path directory = tempDir.resolve(UUID.randomUUID().toString()); + Files.createDirectory(directory); + + Files.write(directory.resolve(resource), value.getBytes(StandardCharsets.UTF_8)); + + return new URLClassLoader(new URL[]{ + directory.toUri().toURL() + }, parent); + } + + +} From 5897863f7e371c8d4666355f39139b620626cbfd Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 3 Mar 2020 14:49:35 +0100 Subject: [PATCH 2/4] fix several openapi errors --- .../scm/api/v2/resources/GitConfigResource.java | 9 +++------ .../sonia/scm/api/v2/resources/HgConfigResource.java | 4 ++-- .../scm/api/v2/resources/SvnConfigResource.java | 4 ++-- .../scm/api/v2/resources/BranchRootResource.java | 10 +--------- .../api/v2/resources/GroupCollectionResource.java | 12 ++---------- .../v2/resources/RepositoryCollectionResource.java | 10 +--------- .../resources/RepositoryPermissionRootResource.java | 9 ++------- .../resources/RepositoryRoleCollectionResource.java | 10 +--------- .../scm/api/v2/resources/UserCollectionResource.java | 10 +--------- 9 files changed, 15 insertions(+), 63 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java index 098396098f..a7f56ec15d 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java @@ -51,7 +51,7 @@ public class GitConfigResource { @GET @Path("") @Produces(GitVndMediaType.GIT_CONFIG) - @Operation(summary = "Git configuration", description = "Returns the global git configuration.", tags = "Git") + @Operation(summary = "Git configuration", description = "Returns the global git configuration.", tags = "Git", operationId = "git_get_config") @ApiResponse( responseCode = "200", description = "success", @@ -91,11 +91,8 @@ public class GitConfigResource { @PUT @Path("") @Consumes(GitVndMediaType.GIT_CONFIG) - @Operation(summary = "Modify git configuration", description = "Modifies the global git configuration.", tags = "Git") - @ApiResponse( - responseCode = "204", - description = "update success" - ) + @Operation(summary = "Modify git configuration", description = "Modifies the global git configuration.", tags = "Git", operationId = "git_put_config") + @ApiResponse(responseCode = "204", description = "update success") @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"configuration:write:git\" privilege") @ApiResponse( diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java index be534f4345..8a9b5aac1e 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigResource.java @@ -57,7 +57,7 @@ public class HgConfigResource { @GET @Path("") @Produces(HgVndMediaType.CONFIG) - @Operation(summary = "Hg configuration", description = "Returns the global mercurial configuration.", tags = "Mercurial") + @Operation(summary = "Hg configuration", description = "Returns the global mercurial configuration.", tags = "Mercurial", operationId = "hg_get_config") @ApiResponse( responseCode = "200", description = "success", @@ -97,7 +97,7 @@ public class HgConfigResource { @PUT @Path("") @Consumes(HgVndMediaType.CONFIG) - @Operation(summary = "Modify hg configuration", description = "Modifies the global mercurial configuration.", tags = "Mercurial") + @Operation(summary = "Modify hg configuration", description = "Modifies the global mercurial configuration.", tags = "Mercurial", operationId = "hg_put_config") @ApiResponse( responseCode = "204", description = "update success" diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java index 9ff13ffb46..2e2963f076 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigResource.java @@ -48,7 +48,7 @@ public class SvnConfigResource { @GET @Path("") @Produces(SvnVndMediaType.SVN_CONFIG) - @Operation(summary = "Svn configuration", description = "Returns the global subversion configuration.", tags = "Subversion") + @Operation(summary = "Svn configuration", description = "Returns the global subversion configuration.", tags = "Subversion", operationId = "svn_get_config") @ApiResponse( responseCode = "200", description = "success", @@ -88,7 +88,7 @@ public class SvnConfigResource { @PUT @Path("") @Consumes(SvnVndMediaType.SVN_CONFIG) - @Operation(summary = "Modify svn configuration", description = "Modifies the global subversion configuration.", tags = "Subversion") + @Operation(summary = "Modify svn configuration", description = "Modifies the global subversion configuration.", tags = "Subversion", operationId = "svn_put_config") @ApiResponse( responseCode = "204", description = "update success" diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java index 33c6fb0908..62725cb2e4 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java @@ -2,7 +2,6 @@ package sonia.scm.api.v2.resources; import com.google.common.base.Strings; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -180,14 +179,7 @@ public class BranchRootResource { @Path("") @Consumes(VndMediaType.BRANCH_REQUEST) @Operation(summary = "Create branch", description = "Creates a new branch.", tags = "Repository") - @ApiResponse( - responseCode = "201", - description = "create success", - headers = @Header( - name = "Location", - description = "uri to the created branch" - ) - ) + @ApiResponse(responseCode = "201", description = "create success") @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"push\" privilege") @ApiResponse(responseCode = "409", description = "conflict, a branch with this name already exists") diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java index 455a82c4f9..62cbe83c33 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java @@ -1,7 +1,6 @@ package sonia.scm.api.v2.resources; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -95,15 +94,8 @@ public class GroupCollectionResource { @POST @Path("") @Consumes(VndMediaType.GROUP) - @Operation(summary = "Create group", description = "Creates a new group.", tags = "Group") - @ApiResponse( - responseCode = "201", - description = "create success", - headers = @Header( - name = "Location", - description = "uri to the created group" - ) - ) + @Operation(summary = "Create group", description = "Creates a new group.", tags = "Group", operationId = "group_create") + @ApiResponse(responseCode = "201", description = "create success") @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"group\" privilege") @ApiResponse(responseCode = "409", description = "conflict, a group with this name already exists") 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 index d7fd6a79a4..70dfa48074 100644 --- 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 @@ -1,7 +1,6 @@ package sonia.scm.api.v2.resources; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -104,14 +103,7 @@ public class RepositoryCollectionResource { @Path("") @Consumes(VndMediaType.REPOSITORY) @Operation(summary = "Create repository", description = "Creates a new repository.", tags = "Repository") - @ApiResponse( - responseCode = "201", - description = "create success", - headers = @Header( - name = "Location", - description = "uri to the created repository" - ) - ) + @ApiResponse(responseCode = "201", description = "create success") @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repository\" privilege") @ApiResponse(responseCode = "409", description = "conflict, a repository with this name already exists") diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java index 1cebbba3c7..f0f8100add 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java @@ -1,7 +1,6 @@ package sonia.scm.api.v2.resources; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -9,9 +8,9 @@ import lombok.extern.slf4j.Slf4j; import sonia.scm.AlreadyExistsException; import sonia.scm.NotFoundException; import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.RepositoryPermission; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; +import sonia.scm.repository.RepositoryPermission; import sonia.scm.repository.RepositoryPermissions; import sonia.scm.web.VndMediaType; @@ -68,11 +67,7 @@ public class RepositoryPermissionRootResource { @Path("") @Consumes(VndMediaType.REPOSITORY_PERMISSION) @Operation(summary = "Create repository-specific permission", description = "Adds a new permission to the user or group managed by the repository.", tags = {"Repository", "Permissions"}) - @ApiResponse( - responseCode = "201", - description = "creates", - headers = @Header(name = "Location", description = "uri of the created permission") - ) + @ApiResponse(responseCode = "201", description = "creates") @ApiResponse( responseCode = "404", description = "not found", diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java index a039b0cb35..5e58929308 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java @@ -1,7 +1,6 @@ package sonia.scm.api.v2.resources; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -91,14 +90,7 @@ public class RepositoryRoleCollectionResource { @Path("") @Consumes(VndMediaType.REPOSITORY_ROLE) @Operation(summary = "Create repository role", description = "Creates a new repository role.", tags = "Repository role") - @ApiResponse( - responseCode = "201", - description = "create success", - headers = @Header( - name = "Location", - description = "uri to the created repository role" - ) - ) + @ApiResponse(responseCode = "201", description = "create success") @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repositoryRole\" privilege") @ApiResponse(responseCode = "409", description = "conflict, a repository role with this name already exists") diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java index ef8b230f8a..0b153d87c6 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java @@ -1,7 +1,6 @@ package sonia.scm.api.v2.resources; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -100,14 +99,7 @@ public class UserCollectionResource { @Path("") @Consumes(VndMediaType.USER) @Operation(summary = "Create user", description = "Creates a new user.", tags = "User") - @ApiResponse( - responseCode = "201", - description = "create success", - headers = @Header( - name = "Location", - description = "uri to the created user" - ) - ) + @ApiResponse(responseCode = "201", description = "create success") @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"user\" privilege") @ApiResponse(responseCode = "409", description = "conflict, a user with this name already exists") From 8b738f52c1a04106f942cb31c310d1be493a21ce Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 4 Mar 2020 08:46:33 +0100 Subject: [PATCH 3/4] add schema for response headers --- .../scm/api/v2/resources/BranchRootResource.java | 11 ++++++++++- .../scm/api/v2/resources/GroupCollectionResource.java | 11 ++++++++++- .../v2/resources/RepositoryCollectionResource.java | 11 ++++++++++- .../resources/RepositoryPermissionRootResource.java | 11 ++++++++++- .../resources/RepositoryRoleCollectionResource.java | 11 ++++++++++- .../scm/api/v2/resources/UserCollectionResource.java | 11 ++++++++++- 6 files changed, 60 insertions(+), 6 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java index 62725cb2e4..2efcad3926 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java @@ -2,6 +2,7 @@ package sonia.scm.api.v2.resources; import com.google.common.base.Strings; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -179,7 +180,15 @@ public class BranchRootResource { @Path("") @Consumes(VndMediaType.BRANCH_REQUEST) @Operation(summary = "Create branch", description = "Creates a new branch.", tags = "Repository") - @ApiResponse(responseCode = "201", description = "create success") + @ApiResponse( + responseCode = "201", + description = "create success", + headers = @Header( + name = "Location", + description = "uri to the created branch", + schema = @Schema(type = "string") + ) + ) @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"push\" privilege") @ApiResponse(responseCode = "409", description = "conflict, a branch with this name already exists") diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java index 62cbe83c33..5524e16376 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java @@ -1,6 +1,7 @@ package sonia.scm.api.v2.resources; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -95,7 +96,15 @@ public class GroupCollectionResource { @Path("") @Consumes(VndMediaType.GROUP) @Operation(summary = "Create group", description = "Creates a new group.", tags = "Group", operationId = "group_create") - @ApiResponse(responseCode = "201", description = "create success") + @ApiResponse( + responseCode = "201", + description = "create success", + headers = @Header( + name = "Location", + description = "uri to the created group", + schema = @Schema(type = "string") + ) + ) @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"group\" privilege") @ApiResponse(responseCode = "409", description = "conflict, a group with this name already exists") 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 index 70dfa48074..469385ebae 100644 --- 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 @@ -1,6 +1,7 @@ package sonia.scm.api.v2.resources; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -103,7 +104,15 @@ public class RepositoryCollectionResource { @Path("") @Consumes(VndMediaType.REPOSITORY) @Operation(summary = "Create repository", description = "Creates a new repository.", tags = "Repository") - @ApiResponse(responseCode = "201", description = "create success") + @ApiResponse( + responseCode = "201", + description = "create success", + headers = @Header( + name = "Location", + description = "uri to the created repository", + schema = @Schema(type = "string") + ) + ) @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repository\" privilege") @ApiResponse(responseCode = "409", description = "conflict, a repository with this name already exists") diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java index f0f8100add..f3b1b9fd03 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java @@ -1,6 +1,7 @@ package sonia.scm.api.v2.resources; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -67,7 +68,15 @@ public class RepositoryPermissionRootResource { @Path("") @Consumes(VndMediaType.REPOSITORY_PERMISSION) @Operation(summary = "Create repository-specific permission", description = "Adds a new permission to the user or group managed by the repository.", tags = {"Repository", "Permissions"}) - @ApiResponse(responseCode = "201", description = "creates") + @ApiResponse( + responseCode = "201", + description = "creates", + headers = @Header( + name = "Location", + description = "uri of the created permission", + schema = @Schema(type = "string") + ) + ) @ApiResponse( responseCode = "404", description = "not found", diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java index 5e58929308..1c272c79e0 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java @@ -1,6 +1,7 @@ package sonia.scm.api.v2.resources; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -90,7 +91,15 @@ public class RepositoryRoleCollectionResource { @Path("") @Consumes(VndMediaType.REPOSITORY_ROLE) @Operation(summary = "Create repository role", description = "Creates a new repository role.", tags = "Repository role") - @ApiResponse(responseCode = "201", description = "create success") + @ApiResponse( + responseCode = "201", + description = "create success", + headers = @Header( + name = "Location", + description = "uri to the created repository role", + schema = @Schema(type = "string") + ) + ) @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repositoryRole\" privilege") @ApiResponse(responseCode = "409", description = "conflict, a repository role with this name already exists") diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java index 0b153d87c6..43ba8c3f3d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java @@ -1,6 +1,7 @@ package sonia.scm.api.v2.resources; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -99,7 +100,15 @@ public class UserCollectionResource { @Path("") @Consumes(VndMediaType.USER) @Operation(summary = "Create user", description = "Creates a new user.", tags = "User") - @ApiResponse(responseCode = "201", description = "create success") + @ApiResponse( + responseCode = "201", + description = "create success", + headers = @Header( + name = "Location", + description = "uri to the created user", + schema = @Schema(type = "string") + ) + ) @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"user\" privilege") @ApiResponse(responseCode = "409", description = "conflict, a user with this name already exists") From 6fe273e821b9b8b7c0135673128a44b6a9b9498a Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 4 Mar 2020 08:31:08 +0000 Subject: [PATCH 4/4] Close branch bugfix/openapi_errors