diff --git a/CHANGELOG.md b/CHANGELOG.md index 9af9ce81bb..5fa1f5ff79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - New footer design +- Update jgit to version 5.6.1.202002131546-r-scm1 - Update svnkit to version 1.10.1-scm1 ### Fixed @@ -21,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - Enunciate rest documentation +- Obsolete fields in data transfer objects ## 2.0.0-rc4 - 2020-02-14 ### Added diff --git a/pom.xml b/pom.xml index f936b8890d..4641b90afe 100644 --- a/pom.xml +++ b/pom.xml @@ -856,7 +856,7 @@ 1.4.1 - v5.4.0.201906121030-r-scm2 + 5.6.1.202002131546-r-scm1 1.10.1-scm1 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-git-plugin/src/main/java/sonia/scm/repository/spi/GitLfsFilterContextListener.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLfsFilterContextListener.java index c202874781..f116bcc550 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLfsFilterContextListener.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLfsFilterContextListener.java @@ -4,54 +4,34 @@ import com.google.common.io.ByteStreams; import org.eclipse.jgit.attributes.FilterCommand; import org.eclipse.jgit.attributes.FilterCommandRegistry; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.SystemReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.SCMContextProvider; import sonia.scm.plugin.Extension; -import javax.inject.Inject; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.regex.Pattern; -import static java.nio.file.StandardOpenOption.CREATE; -import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; - @Extension public class GitLfsFilterContextListener implements ServletContextListener { - public static final String GITCONFIG = "[filter \"lfs\"]\n" + - "clean = git-lfs clean -- %f\n" + - "smudge = git-lfs smudge -- %f\n" + - "process = git-lfs filter-process\n" + - "required = true\n"; public static final Pattern COMMAND_NAME_PATTERN = Pattern.compile("git-lfs (smudge|clean) -- .*"); private static final Logger LOG = LoggerFactory.getLogger(GitLfsFilterContextListener.class); - private final SCMContextProvider contextProvider; - - @Inject - public GitLfsFilterContextListener(SCMContextProvider contextProvider) { - this.contextProvider = contextProvider; - } - @Override public void contextInitialized(ServletContextEvent sce) { - Path gitconfig = contextProvider.getBaseDirectory().toPath().resolve("gitconfig"); try { - Files.write(gitconfig, GITCONFIG.getBytes(Charset.defaultCharset()), TRUNCATE_EXISTING, CREATE); - FS.DETECTED.setGitSystemConfig(gitconfig.toFile()); - LOG.info("wrote git config file: {}", gitconfig); - } catch (IOException e) { - LOG.error("could not write git config in path {}; git lfs support may not work correctly", gitconfig, e); + SystemReader.getInstance().getSystemConfig().setString("filter", "lfs", "clean", "git-lfs clean -- %f"); + SystemReader.getInstance().getSystemConfig().setString("filter", "lfs", "smudge", "git-lfs smudge -- %f"); + SystemReader.getInstance().getSystemConfig().setString("filter", "lfs", "process", "git-lfs filter-process"); + SystemReader.getInstance().getSystemConfig().setString("filter", "lfs", "required", "true"); + } catch (Exception e) { + LOG.error("could not set git config; git lfs support may not work correctly", e); } FilterCommandRegistry.register(COMMAND_NAME_PATTERN, NoOpFilterCommand::new); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/CollectingPackParserListener.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/CollectingPackParserListener.java index fdb96d18d7..20873b3229 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/CollectingPackParserListener.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/CollectingPackParserListener.java @@ -39,8 +39,8 @@ import com.google.common.collect.ImmutableSet; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdSubclassMap; import org.eclipse.jgit.revwalk.RevObject; -import org.eclipse.jgit.transport.BaseReceivePack; -import org.eclipse.jgit.transport.BaseReceivePack.PackParserListener; +import org.eclipse.jgit.transport.ReceivePack; +import org.eclipse.jgit.transport.ReceivePack.PackParserListener; import org.eclipse.jgit.transport.PackParser; import org.slf4j.Logger; @@ -76,7 +76,7 @@ public class CollectingPackParserListener implements PackParserListener * * @return listener */ - public static CollectingPackParserListener get(BaseReceivePack pack) + public static CollectingPackParserListener get(ReceivePack pack) { PackParserListener listener = pack.getPackParserListener(); @@ -101,7 +101,7 @@ public class CollectingPackParserListener implements PackParserListener * * @param pack receive pack */ - public static void set(BaseReceivePack pack) + public static void set(ReceivePack pack) { logger.trace("apply collecting listener to receive pack"); pack.setPackParserListener(new CollectingPackParserListener()); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_LFSTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_LFSTest.java index b1a5c7bbcc..90c4985d35 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_LFSTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommand_LFSTest.java @@ -5,8 +5,8 @@ import com.github.sdorra.shiro.SubjectAware; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.revwalk.RevCommit; -import org.junit.After; -import org.junit.Before; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -38,14 +38,14 @@ public class GitModifyCommand_LFSTest extends AbstractGitCommandTestBase { private final LfsBlobStoreFactory lfsBlobStoreFactory = mock(LfsBlobStoreFactory.class); - @Before - public void registerFilter() { - new GitLfsFilterContextListener(contextProvider).contextInitialized(null); + @BeforeClass + public static void registerFilter() { + new GitLfsFilterContextListener().contextInitialized(null); } - @After - public void unregisterFilter() { - new GitLfsFilterContextListener(contextProvider).contextDestroyed(null); + @AfterClass + public static void unregisterFilter() { + new GitLfsFilterContextListener().contextDestroyed(null); } @Test 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..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 @@ -185,7 +185,8 @@ public class BranchRootResource { description = "create success", headers = @Header( name = "Location", - description = "uri to the created branch" + description = "uri to the created branch", + schema = @Schema(type = "string") ) ) @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") 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..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 @@ -95,13 +95,14 @@ public class GroupCollectionResource { @POST @Path("") @Consumes(VndMediaType.GROUP) - @Operation(summary = "Create group", description = "Creates a new group.", tags = "Group") + @Operation(summary = "Create group", description = "Creates a new group.", tags = "Group", operationId = "group_create") @ApiResponse( responseCode = "201", description = "create success", headers = @Header( name = "Location", - description = "uri to the created group" + description = "uri to the created group", + schema = @Schema(type = "string") ) ) @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java index 3566589b3c..d17ab83fef 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupDto.java @@ -12,9 +12,10 @@ import sonia.scm.util.ValidationUtil; import javax.validation.constraints.Pattern; import java.time.Instant; import java.util.List; -import java.util.Map; -@Getter @Setter @NoArgsConstructor +@Getter +@Setter +@NoArgsConstructor public class GroupDto extends HalRepresentation { private Instant creationDate; @@ -24,7 +25,6 @@ public class GroupDto extends HalRepresentation { @Pattern(regexp = ValidationUtil.REGEX_NAME) private String name; private String type; - private Map properties; private List members; private boolean external; 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..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 @@ -109,7 +109,8 @@ public class RepositoryCollectionResource { description = "create success", headers = @Header( name = "Location", - description = "uri to the created repository" + description = "uri to the created repository", + schema = @Schema(type = "string") ) ) @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java index df0fb0bafc..852d62fd5d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java @@ -7,16 +7,17 @@ import de.otto.edison.hal.Links; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotEmpty; import sonia.scm.util.ValidationUtil; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Pattern; import java.time.Instant; import java.util.List; -import java.util.Map; -@Getter @Setter @NoArgsConstructor +@Getter +@Setter +@NoArgsConstructor public class RepositoryDto extends HalRepresentation { @Email @@ -30,10 +31,8 @@ public class RepositoryDto extends HalRepresentation { private String namespace; @Pattern(regexp = ValidationUtil.REGEX_REPOSITORYNAME) private String name; - private boolean archived = false; @NotEmpty private String type; - protected Map properties; RepositoryDto(Links links, Embedded embedded) { super(links, embedded); 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..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 @@ -9,9 +9,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; @@ -71,7 +71,11 @@ public class RepositoryPermissionRootResource { @ApiResponse( responseCode = "201", description = "creates", - headers = @Header(name = "Location", description = "uri of the created permission") + headers = @Header( + name = "Location", + description = "uri of the created permission", + schema = @Schema(type = "string") + ) ) @ApiResponse( responseCode = "404", 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..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 @@ -96,7 +96,8 @@ public class RepositoryRoleCollectionResource { description = "create success", headers = @Header( name = "Location", - description = "uri to the created repository role" + description = "uri to the created repository role", + schema = @Schema(type = "string") ) ) @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") 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..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 @@ -105,7 +105,8 @@ public class UserCollectionResource { description = "create success", headers = @Header( name = "Location", - description = "uri to the created user" + description = "uri to the created user", + schema = @Schema(type = "string") ) ) @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java index d8a9885398..798c4b2513 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java @@ -7,13 +7,12 @@ import de.otto.edison.hal.Links; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotEmpty; import sonia.scm.util.ValidationUtil; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Pattern; import java.time.Instant; -import java.util.Map; @NoArgsConstructor @Getter @Setter public class UserDto extends HalRepresentation { @@ -30,7 +29,6 @@ public class UserDto extends HalRepresentation { @JsonInclude(JsonInclude.Include.NON_NULL) private String password; private String type; - private Map properties; UserDto(Links links, Embedded embedded) { super(links, embedded); 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/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java index fc0de8fe5c..60d9e3496d 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 @@ -138,19 +138,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase { assertTrue(response.getContentAsString().contains("\"name\":\"repo\"")); } - @Test - public void shouldMapProperties() throws URISyntaxException, UnsupportedEncodingException { - Repository repository = mockRepository("space", "repo"); - repository.setProperty("testKey", "testValue"); - - MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo"); - MockHttpResponse response = new MockHttpResponse(); - - dispatcher.invoke(request, response); - - assertTrue(response.getContentAsString().contains("\"testKey\":\"testValue\"")); - } - @Test public void shouldGetAll() throws URISyntaxException, UnsupportedEncodingException { PageResult singletonPageResult = createSingletonPageResult(mockRepository("space", "repo")); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java index 4b02508ae8..9c6f87a765 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java @@ -77,16 +77,6 @@ public class RepositoryToRepositoryDtoMapperTest { assertEquals("none@example.com", dto.getContact()); } - @Test - public void shouldMapPropertiesProperty() { - Repository repository = createTestRepository(); - repository.setProperty("testKey", "testValue"); - - RepositoryDto dto = mapper.map(repository); - - assertEquals("testValue", dto.getProperties().get("testKey")); - } - @Test @SubjectAware(username = "unpriv") public void shouldCreateLinksForUnprivilegedUser() { 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); + } + + +}