From b74fb814b8f32ccd238c3a30a377aa88b850152f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 18 Oct 2018 13:12:16 +0200 Subject: [PATCH 01/27] Introduce default error object with context for not found exceptions --- .../src/main/java/sonia/scm/ContextEntry.java | 27 + .../java/sonia/scm/NotFoundException.java | 68 +- .../scm/repository/NamespaceAndName.java | 6 +- .../scm/repository/PathNotFoundException.java | 84 -- .../RepositoryNotFoundException.java | 6 +- .../repository/RevisionNotFoundException.java | 83 -- .../repository/api/BrowseCommandBuilder.java | 3 +- .../scm/repository/api/CatCommandBuilder.java | 10 +- .../repository/api/DiffCommandBuilder.java | 7 +- .../scm/repository/api/LogCommandBuilder.java | 5 +- .../api/ModificationsCommandBuilder.java | 3 +- .../api/RepositoryServiceFactory.java | 31 - .../scm/repository/spi/BrowseCommand.java | 4 +- .../sonia/scm/repository/spi/CatCommand.java | 7 +- .../sonia/scm/repository/spi/DiffCommand.java | 4 +- .../sonia/scm/repository/spi/LogCommand.java | 5 +- .../repository/spi/ModificationsCommand.java | 5 +- .../main/java/sonia/scm/web/VndMediaType.java | 1 + .../scm/repository/spi/GitBrowseCommand.java | 15 +- .../scm/repository/spi/GitCatCommand.java | 17 +- .../scm/repository/spi/GitLogCommand.java | 7 +- .../repository/spi/GitBrowseCommandTest.java | 9 +- .../scm/repository/spi/GitCatCommandTest.java | 66 +- .../scm/repository/spi/HgLogCommandTest.java | 3 +- .../java/sonia/scm/repository/SvnUtil.java | 9 +- .../scm/repository/spi/SvnBrowseCommand.java | 5 +- .../scm/repository/spi/SvnCatCommand.java | 17 +- .../scm/repository/spi/SvnDiffCommand.java | 5 +- .../scm/repository/spi/SvnLogCommand.java | 11 +- .../spi/SvnModificationsCommand.java | 8 +- .../repository/spi/SvnBrowseCommandTest.java | 11 +- .../scm/repository/spi/SvnCatCommandTest.java | 62 +- .../scm/repository/spi/SvnLogCommandTest.java | 13 +- .../java/sonia/scm/ManagerDaoAdapter.java | 4 +- .../resources/BrowserStreamingOutput.java | 14 - .../rest/resources/DiffStreamingOutput.java | 17 - .../resources/RepositoryImportResource.java | 1 + .../rest/resources/RepositoryResource.java | 1067 ----------------- .../api/v2/resources/BranchRootResource.java | 3 +- .../v2/resources/ChangesetRootResource.java | 6 +- .../scm/api/v2/resources/ContentResource.java | 29 +- .../api/v2/resources/DiffRootResource.java | 12 +- .../sonia/scm/api/v2/resources/ErrorDto.java | 34 + .../v2/resources/FileHistoryRootResource.java | 5 +- .../resources/IdResourceManagerAdapter.java | 7 +- .../resources/ModificationsRootResource.java | 5 +- .../v2/resources/NotFoundExceptionMapper.java | 12 +- .../v2/resources/PermissionRootResource.java | 6 +- .../api/v2/resources/RepositoryResource.java | 4 +- .../SingleResourceManagerAdapter.java | 34 +- .../api/v2/resources/SourceRootResource.java | 8 +- .../v2/resources/TagNotFoundException.java | 7 - .../scm/api/v2/resources/TagRootResource.java | 11 +- .../sonia/scm/group/DefaultGroupManager.java | 2 +- .../sonia/scm/user/DefaultUserManager.java | 4 +- .../api/v2/resources/ContentResourceTest.java | 6 +- .../api/v2/resources/DiffResourceTest.java | 12 +- .../v2/resources/FileHistoryResourceTest.java | 8 +- .../v2/resources/SourceRootResourceTest.java | 9 +- .../v2/resources/UserRootResourceTest.java | 2 +- .../web/protocol/HttpProtocolServletTest.java | 2 +- 61 files changed, 395 insertions(+), 1553 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/ContextEntry.java delete mode 100644 scm-core/src/main/java/sonia/scm/repository/PathNotFoundException.java delete mode 100644 scm-core/src/main/java/sonia/scm/repository/RevisionNotFoundException.java delete mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java delete mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagNotFoundException.java diff --git a/scm-core/src/main/java/sonia/scm/ContextEntry.java b/scm-core/src/main/java/sonia/scm/ContextEntry.java new file mode 100644 index 0000000000..2dbccbacf7 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/ContextEntry.java @@ -0,0 +1,27 @@ +package sonia.scm; + +import sonia.scm.util.AssertUtil; + +public class ContextEntry { + private final String type; + private final String id; + + ContextEntry(Class type, String id) { + this(type.getSimpleName(), id); + } + + ContextEntry(String type, String id) { + AssertUtil.assertIsNotEmpty(type); + AssertUtil.assertIsNotEmpty(id); + this.type = type; + this.id = id; + } + + public String getType () { + return type; + } + + public String getId () { + return id; + } + } diff --git a/scm-core/src/main/java/sonia/scm/NotFoundException.java b/scm-core/src/main/java/sonia/scm/NotFoundException.java index 37546be0b8..929fa617ad 100644 --- a/scm-core/src/main/java/sonia/scm/NotFoundException.java +++ b/scm-core/src/main/java/sonia/scm/NotFoundException.java @@ -1,10 +1,72 @@ package sonia.scm; +import sonia.scm.repository.Repository; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import static java.util.Collections.unmodifiableList; +import static java.util.stream.Collectors.joining; + public class NotFoundException extends RuntimeException { - public NotFoundException(String type, String id) { - super(type + " with id '" + id + "' not found"); + + private final List context; + + public NotFoundException(Class type, String id) { + this.context = Collections.singletonList(new ContextEntry(type, id)); } - public NotFoundException() { + public NotFoundException(String type, String id) { + this.context = Collections.singletonList(new ContextEntry(type, id)); } + + private NotFoundException(List context) { + this.context = context; + } + + public static NotFoundExceptionBuilder notFound(Class type, String id) { + NotFoundExceptionBuilder builder = new NotFoundExceptionBuilder(); + return builder.in(type, id); + } + + public static NotFoundExceptionBuilder notFound(String type, String id) { + NotFoundExceptionBuilder builder = new NotFoundExceptionBuilder(); + return builder.in(type, id); + } + + public List getContext() { + return unmodifiableList(context); + } + + @Override + public String getMessage() { + return context.stream() + .map(c -> c.getType().toLowerCase() + " with id " + c.getId()) + .collect(joining(" in ", "could not find ", "")); + } + + public static class NotFoundExceptionBuilder { + private final List context = new LinkedList<>(); + + public NotFoundExceptionBuilder in(Repository repository) { + this.in(Repository.class, repository.getNamespaceAndName().logString()); + return this; + } + + public NotFoundExceptionBuilder in(Class type, String id) { + this.context.add(new ContextEntry(type, id)); + return this; + } + + public NotFoundExceptionBuilder in(String type, String id) { + this.context.add(new ContextEntry(type, id)); + return this; + } + + public NotFoundException build() { + return new NotFoundException(context); + } + } + } diff --git a/scm-core/src/main/java/sonia/scm/repository/NamespaceAndName.java b/scm-core/src/main/java/sonia/scm/repository/NamespaceAndName.java index 7b71078f67..fd0a72ad7c 100644 --- a/scm-core/src/main/java/sonia/scm/repository/NamespaceAndName.java +++ b/scm-core/src/main/java/sonia/scm/repository/NamespaceAndName.java @@ -25,9 +25,13 @@ public class NamespaceAndName implements Comparable { return name; } + public String logString() { + return getNamespace() + "/" + getName(); + } + @Override public String toString() { - return getNamespace() + "/" + getName(); + return logString(); } @Override diff --git a/scm-core/src/main/java/sonia/scm/repository/PathNotFoundException.java b/scm-core/src/main/java/sonia/scm/repository/PathNotFoundException.java deleted file mode 100644 index ed62a5967c..0000000000 --- a/scm-core/src/main/java/sonia/scm/repository/PathNotFoundException.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 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 conditions and the following disclaimer in the documentation - * and/or other 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 ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 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.repository; - -//~--- non-JDK imports -------------------------------------------------------- - -import sonia.scm.NotFoundException; -import sonia.scm.util.Util; - -/** - * Signals that the specified path could be found. - * - * @author Sebastian Sdorra - */ -public class PathNotFoundException extends NotFoundException -{ - - /** Field description */ - private static final long serialVersionUID = 4629690181172951809L; - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs a new {@link PathNotFoundException} - * with the specified path. - * - * - * @param path path which could not be found - */ - public PathNotFoundException(String path) - { - super("path", Util.nonNull(path)); - this.path = Util.nonNull(path); - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Return the path which could not be found. - * - * - * @return path which could not be found - */ - public String getPath() - { - return path; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private String path; -} diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryNotFoundException.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryNotFoundException.java index 9dd866daa4..9863aeddd3 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryNotFoundException.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryNotFoundException.java @@ -45,7 +45,7 @@ public class RepositoryNotFoundException extends NotFoundException { private static final long serialVersionUID = -6583078808900520166L; - private static final String TYPE_REPOSITORY = "repository"; + private static final String TYPE_REPOSITORY = "Repository"; //~--- constructors --------------------------------------------------------- @@ -55,7 +55,7 @@ public class RepositoryNotFoundException extends NotFoundException * */ public RepositoryNotFoundException(Repository repository) { - super(TYPE_REPOSITORY, repository.getName() + "/" + repository.getNamespace()); + super(Repository.class, repository.getNamespaceAndName().logString()); } public RepositoryNotFoundException(String repositoryId) { @@ -63,6 +63,6 @@ public class RepositoryNotFoundException extends NotFoundException } public RepositoryNotFoundException(NamespaceAndName namespaceAndName) { - super(TYPE_REPOSITORY, namespaceAndName.toString()); + super(Repository.class, namespaceAndName.logString()); } } diff --git a/scm-core/src/main/java/sonia/scm/repository/RevisionNotFoundException.java b/scm-core/src/main/java/sonia/scm/repository/RevisionNotFoundException.java deleted file mode 100644 index 4185e3223d..0000000000 --- a/scm-core/src/main/java/sonia/scm/repository/RevisionNotFoundException.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * 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 conditions and the following disclaimer in the documentation - * and/or other 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 ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 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.repository; - -//~--- non-JDK imports -------------------------------------------------------- - -import sonia.scm.NotFoundException; -import sonia.scm.util.Util; - -/** - * Signals that the specified revision could be found. - * - * @author Sebastian Sdorra - */ -public class RevisionNotFoundException extends NotFoundException { - - /** Field description */ - private static final long serialVersionUID = -5594008535358811998L; - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs a new {@link RevisionNotFoundException} - * with the specified revision. - * - * - * @param revision revision which could not be found - */ - public RevisionNotFoundException(String revision) - { - super("revision", revision); - this.revision = Util.nonNull(revision); - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Return the revision which could not be found. - * - * - * @return revision which could not be found - */ - public String getRevision() - { - return revision; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private String revision; -} diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BrowseCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/BrowseCommandBuilder.java index fe39aa0a05..3e5166c2f4 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/BrowseCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/BrowseCommandBuilder.java @@ -46,7 +46,6 @@ import sonia.scm.repository.FileObjectNameComparator; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryCacheKey; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.spi.BrowseCommand; import sonia.scm.repository.spi.BrowseCommandRequest; @@ -138,7 +137,7 @@ public final class BrowseCommandBuilder * * @throws IOException */ - public BrowserResult getBrowserResult() throws IOException, RevisionNotFoundException { + public BrowserResult getBrowserResult() throws IOException { BrowserResult result = null; if (disableCache) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/CatCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/CatCommandBuilder.java index bf896efed8..d1e0cbc5f1 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/CatCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/CatCommandBuilder.java @@ -37,9 +37,7 @@ import com.google.common.base.Preconditions; import com.google.common.base.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.repository.PathNotFoundException; import sonia.scm.repository.Repository; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.spi.CatCommand; import sonia.scm.repository.spi.CatCommandRequest; import sonia.scm.util.IOUtil; @@ -107,7 +105,7 @@ public final class CatCommandBuilder * @param outputStream output stream for the content * @param path file path */ - public void retriveContent(OutputStream outputStream, String path) throws IOException, PathNotFoundException, RevisionNotFoundException { + public void retriveContent(OutputStream outputStream, String path) throws IOException { getCatResult(outputStream, path); } @@ -116,7 +114,7 @@ public final class CatCommandBuilder * * @param path file path */ - public InputStream getStream(String path) throws IOException, PathNotFoundException, RevisionNotFoundException { + public InputStream getStream(String path) throws IOException { Preconditions.checkArgument(!Strings.isNullOrEmpty(path), "path is required"); @@ -139,7 +137,7 @@ public final class CatCommandBuilder * * @throws IOException */ - public String getContent(String path) throws IOException, PathNotFoundException, RevisionNotFoundException { + public String getContent(String path) throws IOException { String content = null; ByteArrayOutputStream baos = null; @@ -186,7 +184,7 @@ public final class CatCommandBuilder * @throws IOException */ private void getCatResult(OutputStream outputStream, String path) - throws IOException, PathNotFoundException, RevisionNotFoundException { + throws IOException { Preconditions.checkNotNull(outputStream, "OutputStream is required"); Preconditions.checkArgument(!Strings.isNullOrEmpty(path), "path is required"); diff --git a/scm-core/src/main/java/sonia/scm/repository/api/DiffCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/DiffCommandBuilder.java index c0e9e3f622..d8536f9abe 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/DiffCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/DiffCommandBuilder.java @@ -38,7 +38,6 @@ package sonia.scm.repository.api; import com.google.common.base.Preconditions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.spi.DiffCommand; import sonia.scm.repository.spi.DiffCommandRequest; import sonia.scm.util.IOUtil; @@ -104,7 +103,7 @@ public final class DiffCommandBuilder * * @throws IOException */ - public DiffCommandBuilder retriveContent(OutputStream outputStream) throws IOException, RevisionNotFoundException { + public DiffCommandBuilder retriveContent(OutputStream outputStream) throws IOException { getDiffResult(outputStream); return this; @@ -119,7 +118,7 @@ public final class DiffCommandBuilder * * @throws IOException */ - public String getContent() throws IOException, RevisionNotFoundException { + public String getContent() throws IOException { String content = null; ByteArrayOutputStream baos = null; @@ -199,7 +198,7 @@ public final class DiffCommandBuilder * * @throws IOException */ - private void getDiffResult(OutputStream outputStream) throws IOException, RevisionNotFoundException { + private void getDiffResult(OutputStream outputStream) throws IOException { Preconditions.checkNotNull(outputStream, "OutputStream is required"); Preconditions.checkArgument(request.isValid(), "path and/or revision is required"); diff --git a/scm-core/src/main/java/sonia/scm/repository/api/LogCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/LogCommandBuilder.java index 9c782a781b..f718df5b6e 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/LogCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/LogCommandBuilder.java @@ -46,7 +46,6 @@ import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryCacheKey; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.spi.LogCommand; import sonia.scm.repository.spi.LogCommandRequest; @@ -165,7 +164,7 @@ public final class LogCommandBuilder * * @throws IOException */ - public Changeset getChangeset(String id) throws IOException, RevisionNotFoundException { + public Changeset getChangeset(String id) throws IOException { Changeset changeset; if (disableCache) @@ -224,7 +223,7 @@ public final class LogCommandBuilder * * @throws IOException */ - public ChangesetPagingResult getChangesets() throws IOException, RevisionNotFoundException { + public ChangesetPagingResult getChangesets() throws IOException { ChangesetPagingResult cpr; if (disableCache) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/ModificationsCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/ModificationsCommandBuilder.java index 6459b47cd5..498746cc60 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/ModificationsCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/ModificationsCommandBuilder.java @@ -13,7 +13,6 @@ import sonia.scm.repository.Modifications; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryCacheKey; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.spi.ModificationsCommand; import sonia.scm.repository.spi.ModificationsCommandRequest; @@ -67,7 +66,7 @@ public final class ModificationsCommandBuilder { return this; } - public Modifications getModifications() throws IOException, RevisionNotFoundException { + public Modifications getModifications() throws IOException { Modifications modifications; if (disableCache) { log.info("Get modifications for {} with disabled cache", request); diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java index fbb1ee6b58..7160c2723d 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java @@ -152,37 +152,6 @@ public final class RepositoryServiceFactory //~--- methods -------------------------------------------------------------- - /** - * Creates a new RepositoryService for the given repository. - * - * - * @param repositoryId id of the repository - * - * @return a implementation of RepositoryService - * for the given type of repository - * - * @throws RepositoryNotFoundException if no repository - * with the given id is available - * @throws RepositoryServiceNotFoundException if no repository service - * implementation for this kind of repository is available - * @throws IllegalArgumentException if the repository id is null or empty - * @throws ScmSecurityException if current user has not read permissions - * for that repository - */ - public RepositoryService create(String repositoryId) throws RepositoryNotFoundException { - Preconditions.checkArgument(!Strings.isNullOrEmpty(repositoryId), - "a non empty repositoryId is required"); - - Repository repository = repositoryManager.get(repositoryId); - - if (repository == null) - { - throw new RepositoryNotFoundException(repositoryId); - } - - return create(repository); - } - /** * Creates a new RepositoryService for the given repository. * diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java index 2c9fff589c..ee37d6243e 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommand.java @@ -36,7 +36,6 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- import sonia.scm.repository.BrowserResult; -import sonia.scm.repository.RevisionNotFoundException; import java.io.IOException; @@ -60,4 +59,5 @@ public interface BrowseCommand * * @throws IOException */ - BrowserResult getBrowserResult(BrowseCommandRequest request) throws IOException, RevisionNotFoundException;} + BrowserResult getBrowserResult(BrowseCommandRequest request) throws IOException; +} diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/CatCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/CatCommand.java index 06f242783b..81600230db 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/CatCommand.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/CatCommand.java @@ -33,9 +33,6 @@ package sonia.scm.repository.spi; -import sonia.scm.repository.PathNotFoundException; -import sonia.scm.repository.RevisionNotFoundException; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -47,7 +44,7 @@ import java.io.OutputStream; */ public interface CatCommand { - void getCatResult(CatCommandRequest request, OutputStream output) throws IOException, RevisionNotFoundException, PathNotFoundException; + void getCatResult(CatCommandRequest request, OutputStream output) throws IOException; - InputStream getCatResultStream(CatCommandRequest request) throws IOException, RevisionNotFoundException, PathNotFoundException; + InputStream getCatResultStream(CatCommandRequest request) throws IOException; } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommand.java index 105f4e48e1..bba42cd86d 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommand.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/DiffCommand.java @@ -33,8 +33,6 @@ package sonia.scm.repository.spi; -import sonia.scm.repository.RevisionNotFoundException; - import java.io.IOException; import java.io.OutputStream; @@ -56,5 +54,5 @@ public interface DiffCommand * @throws IOException * @throws RuntimeException */ - public void getDiffResult(DiffCommandRequest request, OutputStream output) throws IOException, RevisionNotFoundException; + public void getDiffResult(DiffCommandRequest request, OutputStream output) throws IOException; } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java index e4a7f3437b..f4babcee72 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/LogCommand.java @@ -37,7 +37,6 @@ package sonia.scm.repository.spi; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; -import sonia.scm.repository.RevisionNotFoundException; import java.io.IOException; @@ -50,7 +49,7 @@ import java.io.IOException; */ public interface LogCommand { - Changeset getChangeset(String id) throws IOException, RevisionNotFoundException; + Changeset getChangeset(String id) throws IOException; - ChangesetPagingResult getChangesets(LogCommandRequest request) throws IOException, RevisionNotFoundException; + ChangesetPagingResult getChangesets(LogCommandRequest request) throws IOException; } diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/ModificationsCommand.java b/scm-core/src/main/java/sonia/scm/repository/spi/ModificationsCommand.java index e9b40e8a17..322468f827 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/ModificationsCommand.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/ModificationsCommand.java @@ -32,7 +32,6 @@ package sonia.scm.repository.spi; import sonia.scm.repository.Modifications; -import sonia.scm.repository.RevisionNotFoundException; import java.io.IOException; @@ -46,8 +45,8 @@ import java.io.IOException; */ public interface ModificationsCommand { - Modifications getModifications(String revision) throws IOException, RevisionNotFoundException; + Modifications getModifications(String revision) throws IOException; - Modifications getModifications(ModificationsCommandRequest request) throws IOException, RevisionNotFoundException; + Modifications getModifications(ModificationsCommandRequest request) throws IOException; } 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 7b6d5cb039..e2a2218d34 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 ME = PREFIX + "me" + SUFFIX; public static final String SOURCE = PREFIX + "source" + SUFFIX; + public static final String ERROR_TYPE = PREFIX + "error" + SUFFIX; private VndMediaType() { } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java index f194796bdc..2be4c34fb8 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java @@ -50,13 +50,12 @@ import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.NotFoundException; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.GitSubModuleParser; import sonia.scm.repository.GitUtil; -import sonia.scm.repository.PathNotFoundException; import sonia.scm.repository.Repository; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.SubRepository; import sonia.scm.util.Util; @@ -103,7 +102,7 @@ public class GitBrowseCommand extends AbstractGitCommand @Override @SuppressWarnings("unchecked") public BrowserResult getBrowserResult(BrowseCommandRequest request) - throws IOException, RevisionNotFoundException { + throws IOException { logger.debug("try to create browse result for {}", request); BrowserResult result; @@ -157,7 +156,7 @@ public class GitBrowseCommand extends AbstractGitCommand */ private FileObject createFileObject(org.eclipse.jgit.lib.Repository repo, BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk) - throws IOException, RevisionNotFoundException { + throws IOException { FileObject file; try @@ -267,7 +266,7 @@ public class GitBrowseCommand extends AbstractGitCommand private BrowserResult getResult(org.eclipse.jgit.lib.Repository repo, BrowseCommandRequest request, ObjectId revId) - throws IOException, RevisionNotFoundException { + throws IOException { BrowserResult result = null; RevWalk revWalk = null; TreeWalk treeWalk = null; @@ -364,7 +363,7 @@ public class GitBrowseCommand extends AbstractGitCommand private Map getSubRepositories(org.eclipse.jgit.lib.Repository repo, ObjectId revision) - throws IOException, RevisionNotFoundException { + throws IOException { if (logger.isDebugEnabled()) { logger.debug("read submodules of {} at {}", repository.getName(), @@ -378,7 +377,7 @@ public class GitBrowseCommand extends AbstractGitCommand PATH_MODULES, baos); subRepositories = GitSubModuleParser.parse(baos.toString()); } - catch (PathNotFoundException ex) + catch (NotFoundException ex) { logger.trace("could not find .gitmodules", ex); subRepositories = Collections.EMPTY_MAP; @@ -389,7 +388,7 @@ public class GitBrowseCommand extends AbstractGitCommand private SubRepository getSubRepository(org.eclipse.jgit.lib.Repository repo, ObjectId revId, String path) - throws IOException, RevisionNotFoundException { + throws IOException { Map subRepositories = subrepositoryCache.get(revId); if (subRepositories == null) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java index 4b05098d03..40f044ebde 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java @@ -44,9 +44,8 @@ import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.NotFoundException; import sonia.scm.repository.GitUtil; -import sonia.scm.repository.PathNotFoundException; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.util.Util; import java.io.Closeable; @@ -65,7 +64,7 @@ public class GitCatCommand extends AbstractGitCommand implements CatCommand { } @Override - public void getCatResult(CatCommandRequest request, OutputStream output) throws IOException, PathNotFoundException, RevisionNotFoundException { + public void getCatResult(CatCommandRequest request, OutputStream output) throws IOException { logger.debug("try to read content for {}", request); try (ClosableObjectLoaderContainer closableObjectLoaderContainer = getLoader(request)) { closableObjectLoaderContainer.objectLoader.copyTo(output); @@ -73,24 +72,24 @@ public class GitCatCommand extends AbstractGitCommand implements CatCommand { } @Override - public InputStream getCatResultStream(CatCommandRequest request) throws IOException, PathNotFoundException, RevisionNotFoundException { + public InputStream getCatResultStream(CatCommandRequest request) throws IOException { logger.debug("try to read content for {}", request); return new InputStreamWrapper(getLoader(request)); } - void getContent(org.eclipse.jgit.lib.Repository repo, ObjectId revId, String path, OutputStream output) throws IOException, PathNotFoundException, RevisionNotFoundException { + void getContent(org.eclipse.jgit.lib.Repository repo, ObjectId revId, String path, OutputStream output) throws IOException { try (ClosableObjectLoaderContainer closableObjectLoaderContainer = getLoader(repo, revId, path)) { closableObjectLoaderContainer.objectLoader.copyTo(output); } } - private ClosableObjectLoaderContainer getLoader(CatCommandRequest request) throws IOException, PathNotFoundException, RevisionNotFoundException { + private ClosableObjectLoaderContainer getLoader(CatCommandRequest request) throws IOException { org.eclipse.jgit.lib.Repository repo = open(); ObjectId revId = getCommitOrDefault(repo, request.getRevision()); return getLoader(repo, revId, request.getPath()); } - private ClosableObjectLoaderContainer getLoader(Repository repo, ObjectId revId, String path) throws IOException, PathNotFoundException, RevisionNotFoundException { + private ClosableObjectLoaderContainer getLoader(Repository repo, ObjectId revId, String path) throws IOException { TreeWalk treeWalk = new TreeWalk(repo); treeWalk.setRecursive(Util.nonNull(path).contains("/")); @@ -102,7 +101,7 @@ public class GitCatCommand extends AbstractGitCommand implements CatCommand { try { entry = revWalk.parseCommit(revId); } catch (MissingObjectException e) { - throw new RevisionNotFoundException(revId.getName()); + throw NotFoundException.notFound("Revision", revId.getName()).in(repository).build(); } RevTree revTree = entry.getTree(); @@ -120,7 +119,7 @@ public class GitCatCommand extends AbstractGitCommand implements CatCommand { return new ClosableObjectLoaderContainer(loader, treeWalk, revWalk); } else { - throw new PathNotFoundException(path); + throw NotFoundException.notFound("Path", path).in("Revision", revId.getName()).in(repository).build(); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java index 4e9261f517..40b25ef75a 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java @@ -48,12 +48,12 @@ import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.NotFoundException; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.GitChangesetConverter; import sonia.scm.repository.GitUtil; import sonia.scm.repository.InternalRepositoryException; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.util.IOUtil; import java.io.IOException; @@ -85,7 +85,6 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand * * @param context * @param repository - * @param repositoryDirectory */ GitLogCommand(GitContext context, sonia.scm.repository.Repository repository) { @@ -162,7 +161,7 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand */ @Override @SuppressWarnings("unchecked") - public ChangesetPagingResult getChangesets(LogCommandRequest request) throws RevisionNotFoundException { + public ChangesetPagingResult getChangesets(LogCommandRequest request) { if (logger.isDebugEnabled()) { logger.debug("fetch changesets for request: {}", request); } @@ -249,7 +248,7 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand } catch (MissingObjectException e) { - throw new RevisionNotFoundException(e.getObjectId().name()); + throw NotFoundException.notFound("Revision", e.getObjectId().getName()).in(repository).build(); } catch (Exception ex) { diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java index d71c85a152..a6e704501e 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java @@ -39,7 +39,6 @@ import org.junit.Test; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.GitConstants; -import sonia.scm.repository.RevisionNotFoundException; import java.io.IOException; import java.util.List; @@ -63,7 +62,7 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase * Test browse command with default branch. */ @Test - public void testDefaultBranch() throws IOException, RevisionNotFoundException { + public void testDefaultBranch() throws IOException { // without default branch, the repository head should be used BrowserResult result = createCommand().getBrowserResult(new BrowseCommandRequest()); assertNotNull(result); @@ -93,7 +92,7 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase } @Test - public void testBrowse() throws IOException, RevisionNotFoundException { + public void testBrowse() throws IOException { BrowserResult result = createCommand().getBrowserResult(new BrowseCommandRequest()); @@ -134,7 +133,7 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase } @Test - public void testBrowseSubDirectory() throws IOException, RevisionNotFoundException { + public void testBrowseSubDirectory() throws IOException { BrowseCommandRequest request = new BrowseCommandRequest(); request.setPath("c"); @@ -181,7 +180,7 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase } @Test - public void testRecusive() throws IOException, RevisionNotFoundException { + public void testRecusive() throws IOException { BrowseCommandRequest request = new BrowseCommandRequest(); request.setRecursive(true); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitCatCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitCatCommandTest.java index 3611c9c636..079fcac1da 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitCatCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitCatCommandTest.java @@ -32,10 +32,13 @@ package sonia.scm.repository.spi; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import sonia.scm.NotFoundException; import sonia.scm.repository.GitConstants; -import sonia.scm.repository.PathNotFoundException; -import sonia.scm.repository.RevisionNotFoundException; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -51,9 +54,12 @@ import static org.junit.Assert.assertEquals; * @author Sebastian Sdorra */ public class GitCatCommandTest extends AbstractGitCommandTestBase { - + + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + @Test - public void testDefaultBranch() throws IOException, PathNotFoundException, RevisionNotFoundException { + public void testDefaultBranch() throws IOException { // without default branch, the repository head should be used CatCommandRequest request = new CatCommandRequest(); request.setPath("a.txt"); @@ -66,7 +72,7 @@ public class GitCatCommandTest extends AbstractGitCommandTestBase { } @Test - public void testCat() throws IOException, PathNotFoundException, RevisionNotFoundException { + public void testCat() throws IOException { CatCommandRequest request = new CatCommandRequest(); request.setPath("a.txt"); @@ -75,32 +81,58 @@ public class GitCatCommandTest extends AbstractGitCommandTestBase { } @Test - public void testSimpleCat() throws IOException, PathNotFoundException, RevisionNotFoundException { + public void testSimpleCat() throws IOException { CatCommandRequest request = new CatCommandRequest(); request.setPath("b.txt"); assertEquals("b", execute(request)); } - @Test(expected = PathNotFoundException.class) - public void testUnknownFile() throws IOException, PathNotFoundException, RevisionNotFoundException { + @Test + public void testUnknownFile() throws IOException { CatCommandRequest request = new CatCommandRequest(); request.setPath("unknown"); - execute(request); - } - @Test(expected = RevisionNotFoundException.class) - public void testUnknownRevision() throws IOException, PathNotFoundException, RevisionNotFoundException { - CatCommandRequest request = new CatCommandRequest(); + expectedException.expect(new BaseMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("expected NotFoundException for path"); + } + + @Override + public boolean matches(Object item) { + return "Path".equals(((NotFoundException)item).getContext().get(0).getType()); + } + }); - request.setRevision("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - request.setPath("a.txt"); execute(request); } @Test - public void testSimpleStream() throws IOException, PathNotFoundException, RevisionNotFoundException { + public void testUnknownRevision() throws IOException { + CatCommandRequest request = new CatCommandRequest(); + + request.setRevision("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + request.setPath("a.txt"); + + expectedException.expect(new BaseMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("expected NotFoundException for revision"); + } + + @Override + public boolean matches(Object item) { + return "Revision".equals(((NotFoundException)item).getContext().get(0).getType()); + } + }); + + execute(request); + } + + @Test + public void testSimpleStream() throws IOException { CatCommandRequest request = new CatCommandRequest(); request.setPath("b.txt"); @@ -113,7 +145,7 @@ public class GitCatCommandTest extends AbstractGitCommandTestBase { catResultStream.close(); } - private String execute(CatCommandRequest request) throws IOException, PathNotFoundException, RevisionNotFoundException { + private String execute(CatCommandRequest request) throws IOException { String content = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgLogCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgLogCommandTest.java index 29fc46ed57..1daa395e07 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgLogCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgLogCommandTest.java @@ -39,7 +39,6 @@ import org.junit.Test; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.Modifications; -import sonia.scm.repository.RevisionNotFoundException; import java.io.IOException; @@ -151,7 +150,7 @@ public class HgLogCommandTest extends AbstractHgCommandTestBase } @Test - public void testGetCommit() throws IOException, RevisionNotFoundException { + public void testGetCommit() throws IOException { HgLogCommand command = createComamnd(); String revision = "a9bacaf1b7fa0cebfca71fed4e59ed69a6319427"; Changeset c = diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnUtil.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnUtil.java index 480026c27a..deff53eeac 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnUtil.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnUtil.java @@ -49,6 +49,7 @@ import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil; import org.tmatesoft.svn.core.internal.util.SVNXMLUtil; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.SVNClientManager; +import sonia.scm.NotFoundException; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; @@ -102,7 +103,7 @@ public final class SvnUtil //~--- methods -------------------------------------------------------------- - public static long parseRevision(String v) throws RevisionNotFoundException { + public static long parseRevision(String v, Repository repository) { long result = -1l; if (!Strings.isNullOrEmpty(v)) @@ -113,7 +114,7 @@ public final class SvnUtil } catch (NumberFormatException ex) { - throw new RevisionNotFoundException(v); + throw NotFoundException.notFound("Revision", v).in(repository).build(); } } @@ -339,7 +340,7 @@ public final class SvnUtil } } - public static long getRevisionNumber(String revision) throws RevisionNotFoundException { + public static long getRevisionNumber(String revision, Repository repository) { // REVIEW Bei SVN wird ohne Revision die -1 genommen, was zu einem Fehler führt long revisionNumber = -1; @@ -351,7 +352,7 @@ public final class SvnUtil } catch (NumberFormatException ex) { - throw new RevisionNotFoundException(revision); + throw NotFoundException.notFound("Revision", revision).in(repository).build(); } } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java index a75adf6b78..d5627e4d8b 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBrowseCommand.java @@ -47,7 +47,6 @@ import org.tmatesoft.svn.core.io.SVNRepository; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.Repository; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.SubRepository; import sonia.scm.repository.SvnUtil; import sonia.scm.util.Util; @@ -78,9 +77,9 @@ public class SvnBrowseCommand extends AbstractSvnCommand @Override @SuppressWarnings("unchecked") - public BrowserResult getBrowserResult(BrowseCommandRequest request) throws RevisionNotFoundException { + public BrowserResult getBrowserResult(BrowseCommandRequest request) { String path = request.getPath(); - long revisionNumber = SvnUtil.getRevisionNumber(request.getRevision()); + long revisionNumber = SvnUtil.getRevisionNumber(request.getRevision(), repository); if (logger.isDebugEnabled()) { logger.debug("browser repository {} in path {} at revision {}", repository.getName(), path, revisionNumber); diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnCatCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnCatCommand.java index 4936a16a8c..93d52ea907 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnCatCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnCatCommand.java @@ -43,10 +43,9 @@ import org.tmatesoft.svn.core.SVNProperties; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.SVNClientManager; import org.tmatesoft.svn.core.wc.admin.SVNLookClient; +import sonia.scm.NotFoundException; import sonia.scm.repository.InternalRepositoryException; -import sonia.scm.repository.PathNotFoundException; import sonia.scm.repository.Repository; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.SvnUtil; import java.io.ByteArrayInputStream; @@ -79,7 +78,7 @@ public class SvnCatCommand extends AbstractSvnCommand implements CatCommand //~--- get methods ---------------------------------------------------------- @Override - public void getCatResult(CatCommandRequest request, OutputStream output) throws RevisionNotFoundException, PathNotFoundException { + public void getCatResult(CatCommandRequest request, OutputStream output) { if (logger.isDebugEnabled()) { logger.debug("try to get content for {}", request); @@ -96,14 +95,14 @@ public class SvnCatCommand extends AbstractSvnCommand implements CatCommand else { - long revisionNumber = SvnUtil.getRevisionNumber(revision); + long revisionNumber = SvnUtil.getRevisionNumber(revision, repository); getCatFromRevision(request, output, revisionNumber); } } @Override - public InputStream getCatResultStream(CatCommandRequest request) throws RevisionNotFoundException, PathNotFoundException { + public InputStream getCatResultStream(CatCommandRequest request) { // There seems to be no method creating an input stream as a result, so // we have no other possibility then to copy the content into a buffer and // stream it from there. @@ -112,7 +111,7 @@ public class SvnCatCommand extends AbstractSvnCommand implements CatCommand return new ByteArrayInputStream(output.toByteArray()); } - private void getCatFromRevision(CatCommandRequest request, OutputStream output, long revision) throws PathNotFoundException, RevisionNotFoundException { + private void getCatFromRevision(CatCommandRequest request, OutputStream output, long revision) { logger.debug("try to read content from revision {} and path {}", revision, request.getPath()); @@ -129,12 +128,12 @@ public class SvnCatCommand extends AbstractSvnCommand implements CatCommand } } - private void handleSvnException(CatCommandRequest request, SVNException ex) throws PathNotFoundException, RevisionNotFoundException { + private void handleSvnException(CatCommandRequest request, SVNException ex) { int svnErrorCode = ex.getErrorMessage().getErrorCode().getCode(); if (SVNErrorCode.FS_NOT_FOUND.getCode() == svnErrorCode) { - throw new PathNotFoundException(request.getPath()); + throw NotFoundException.notFound("Path", request.getPath()).in("Revision", request.getRevision()).in(repository).build(); } else if (SVNErrorCode.FS_NO_SUCH_REVISION.getCode() == svnErrorCode) { - throw new RevisionNotFoundException(request.getRevision()); + throw NotFoundException.notFound("Revision", request.getRevision()).in(repository).build(); } else { throw new InternalRepositoryException("could not get content from revision", ex); } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java index 0b65466a42..9e81f1a7a9 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java @@ -48,7 +48,6 @@ import org.tmatesoft.svn.core.wc.SVNDiffClient; import org.tmatesoft.svn.core.wc.SVNRevision; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.SvnUtil; import sonia.scm.repository.api.DiffFormat; import sonia.scm.util.Util; @@ -76,7 +75,7 @@ public class SvnDiffCommand extends AbstractSvnCommand implements DiffCommand } @Override - public void getDiffResult(DiffCommandRequest request, OutputStream output) throws RevisionNotFoundException { + public void getDiffResult(DiffCommandRequest request, OutputStream output) { if (logger.isDebugEnabled()) { logger.debug("create diff for {}", request); @@ -111,7 +110,7 @@ public class SvnDiffCommand extends AbstractSvnCommand implements DiffCommand diffGenerator.setDiffDeleted(true); diffClient.setDiffGenerator(diffGenerator); - long currentRev = SvnUtil.getRevisionNumber(request.getRevision()); + long currentRev = SvnUtil.getRevisionNumber(request.getRevision(), repository); diffClient.setGitDiffFormat(request.getFormat() == DiffFormat.GIT); diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java index 332dcb55a6..a29b8f4a0c 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java @@ -47,7 +47,6 @@ import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.SvnUtil; import sonia.scm.util.Util; @@ -76,7 +75,7 @@ public class SvnLogCommand extends AbstractSvnCommand implements LogCommand @Override @SuppressWarnings("unchecked") - public Changeset getChangeset(String revision) throws RevisionNotFoundException { + public Changeset getChangeset(String revision) { Changeset changeset = null; if (logger.isDebugEnabled()) @@ -86,7 +85,7 @@ public class SvnLogCommand extends AbstractSvnCommand implements LogCommand try { - long revisioNumber = parseRevision(revision); + long revisioNumber = parseRevision(revision, repository); SVNRepository repo = open(); Collection entries = repo.log(null, null, revisioNumber, revisioNumber, true, true); @@ -106,7 +105,7 @@ public class SvnLogCommand extends AbstractSvnCommand implements LogCommand @Override @SuppressWarnings("unchecked") - public ChangesetPagingResult getChangesets(LogCommandRequest request) throws RevisionNotFoundException { + public ChangesetPagingResult getChangesets(LogCommandRequest request) { if (logger.isDebugEnabled()) { logger.debug("fetch changesets for {}", request); @@ -115,8 +114,8 @@ public class SvnLogCommand extends AbstractSvnCommand implements LogCommand ChangesetPagingResult changesets = null; int start = request.getPagingStart(); int limit = request.getPagingLimit(); - long startRevision = parseRevision(request.getStartChangeset()); - long endRevision = parseRevision(request.getEndChangeset()); + long startRevision = parseRevision(request.getStartChangeset(), repository); + long endRevision = parseRevision(request.getEndChangeset(), repository); String[] pathArray = null; if (!Strings.isNullOrEmpty(request.getPath())) diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java index e6cedc8ebf..00fb1dc5a0 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java @@ -7,11 +7,9 @@ import org.tmatesoft.svn.core.io.SVNRepository; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Modifications; import sonia.scm.repository.Repository; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.SvnUtil; import sonia.scm.util.Util; -import java.io.IOException; import java.util.Collection; @Slf4j @@ -24,11 +22,11 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif @Override @SuppressWarnings("unchecked") - public Modifications getModifications(String revision) throws IOException, RevisionNotFoundException { + public Modifications getModifications(String revision) { Modifications modifications = null; log.debug("get modifications {}", revision); try { - long revisionNumber = SvnUtil.parseRevision(revision); + long revisionNumber = SvnUtil.parseRevision(revision, repository); SVNRepository repo = open(); Collection entries = repo.log(null, null, revisionNumber, revisionNumber, true, true); @@ -42,7 +40,7 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif } @Override - public Modifications getModifications(ModificationsCommandRequest request) throws IOException, RevisionNotFoundException { + public Modifications getModifications(ModificationsCommandRequest request) { return getModifications(request.getRevision()); } diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java index c4c658ea7a..d76e4f3e12 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnBrowseCommandTest.java @@ -38,7 +38,6 @@ package sonia.scm.repository.spi; import org.junit.Test; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; -import sonia.scm.repository.RevisionNotFoundException; import java.io.IOException; import java.util.List; @@ -59,7 +58,7 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase { @Test - public void testBrowse() throws RevisionNotFoundException { + public void testBrowse() { List foList = getRootFromTip(new BrowseCommandRequest()); FileObject a = getFileObject(foList, "a.txt"); @@ -83,7 +82,7 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase * @throws IOException */ @Test - public void testBrowseSubDirectory() throws RevisionNotFoundException { + public void testBrowseSubDirectory() { BrowseCommandRequest request = new BrowseCommandRequest(); request.setPath("c"); @@ -130,7 +129,7 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase } @Test - public void testDisableLastCommit() throws RevisionNotFoundException { + public void testDisableLastCommit() { BrowseCommandRequest request = new BrowseCommandRequest(); request.setDisableLastCommit(true); @@ -144,7 +143,7 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase } @Test - public void testRecursive() throws RevisionNotFoundException { + public void testRecursive() { BrowseCommandRequest request = new BrowseCommandRequest(); request.setRecursive(true); BrowserResult result = createCommand().getBrowserResult(request); @@ -203,7 +202,7 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase return a; } - private List getRootFromTip(BrowseCommandRequest request) throws RevisionNotFoundException { + private List getRootFromTip(BrowseCommandRequest request) { BrowserResult result = createCommand().getBrowserResult(request); assertNotNull(result); diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnCatCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnCatCommandTest.java index 3980b3e558..e899c63b19 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnCatCommandTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnCatCommandTest.java @@ -32,9 +32,12 @@ package sonia.scm.repository.spi; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.junit.Rule; import org.junit.Test; -import sonia.scm.repository.PathNotFoundException; -import sonia.scm.repository.RevisionNotFoundException; +import org.junit.rules.ExpectedException; +import sonia.scm.NotFoundException; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -46,8 +49,11 @@ import static org.junit.Assert.assertEquals; public class SvnCatCommandTest extends AbstractSvnCommandTestBase { + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + @Test - public void testCat() throws PathNotFoundException, RevisionNotFoundException { + public void testCat() { CatCommandRequest request = new CatCommandRequest(); request.setPath("a.txt"); @@ -56,35 +62,59 @@ public class SvnCatCommandTest extends AbstractSvnCommandTestBase { } @Test - public void testSimpleCat() throws PathNotFoundException, RevisionNotFoundException { + public void testSimpleCat() { CatCommandRequest request = new CatCommandRequest(); request.setPath("c/d.txt"); assertEquals("d", execute(request)); } - @Test(expected = PathNotFoundException.class) - public void testUnknownFile() throws PathNotFoundException, RevisionNotFoundException { + @Test + public void testUnknownFile() { CatCommandRequest request = new CatCommandRequest(); request.setPath("unknown"); request.setRevision("1"); - execute(request); - } + expectedException.expect(new BaseMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("expected NotFoundException for path"); + } - @Test(expected = RevisionNotFoundException.class) - public void testUnknownRevision() throws PathNotFoundException, RevisionNotFoundException { - CatCommandRequest request = new CatCommandRequest(); - - request.setPath("a.txt"); - request.setRevision("42"); + @Override + public boolean matches(Object item) { + return "Path".equals(((NotFoundException)item).getContext().get(0).getType()); + } + }); execute(request); } @Test - public void testSimpleStream() throws IOException, PathNotFoundException, RevisionNotFoundException { + public void testUnknownRevision() { + CatCommandRequest request = new CatCommandRequest(); + + request.setPath("a.txt"); + request.setRevision("42"); + + expectedException.expect(new BaseMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("expected NotFoundException for revision"); + } + + @Override + public boolean matches(Object item) { + return "Revision".equals(((NotFoundException)item).getContext().get(0).getType()); + } + }); + + execute(request); + } + + @Test + public void testSimpleStream() throws IOException { CatCommandRequest request = new CatCommandRequest(); request.setPath("a.txt"); request.setRevision("1"); @@ -98,7 +128,7 @@ public class SvnCatCommandTest extends AbstractSvnCommandTestBase { catResultStream.close(); } - private String execute(CatCommandRequest request) throws PathNotFoundException, RevisionNotFoundException { + private String execute(CatCommandRequest request) { String content = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java index a55138f151..f2511a9ad9 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/spi/SvnLogCommandTest.java @@ -38,7 +38,6 @@ import org.junit.Test; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.Modifications; -import sonia.scm.repository.RevisionNotFoundException; import java.io.IOException; @@ -57,7 +56,7 @@ public class SvnLogCommandTest extends AbstractSvnCommandTestBase { @Test - public void testGetAll() throws RevisionNotFoundException { + public void testGetAll() { ChangesetPagingResult result = createCommand().getChangesets(new LogCommandRequest()); @@ -67,7 +66,7 @@ public class SvnLogCommandTest extends AbstractSvnCommandTestBase } @Test - public void testGetAllByPath() throws RevisionNotFoundException { + public void testGetAllByPath() { LogCommandRequest request = new LogCommandRequest(); request.setPath("a.txt"); @@ -83,7 +82,7 @@ public class SvnLogCommandTest extends AbstractSvnCommandTestBase } @Test - public void testGetAllWithLimit() throws RevisionNotFoundException { + public void testGetAllWithLimit() { LogCommandRequest request = new LogCommandRequest(); request.setPagingLimit(2); @@ -106,7 +105,7 @@ public class SvnLogCommandTest extends AbstractSvnCommandTestBase } @Test - public void testGetAllWithPaging() throws RevisionNotFoundException { + public void testGetAllWithPaging() { LogCommandRequest request = new LogCommandRequest(); request.setPagingStart(1); @@ -130,7 +129,7 @@ public class SvnLogCommandTest extends AbstractSvnCommandTestBase } @Test - public void testGetCommit() throws RevisionNotFoundException, IOException { + public void testGetCommit() { Changeset c = createCommand().getChangeset("3"); assertNotNull(c); @@ -151,7 +150,7 @@ public class SvnLogCommandTest extends AbstractSvnCommandTestBase } @Test - public void testGetRange() throws RevisionNotFoundException { + public void testGetRange() { LogCommandRequest request = new LogCommandRequest(); request.setStartChangeset("2"); diff --git a/scm-webapp/src/main/java/sonia/scm/ManagerDaoAdapter.java b/scm-webapp/src/main/java/sonia/scm/ManagerDaoAdapter.java index 7979eca0e7..4fdd74063e 100644 --- a/scm-webapp/src/main/java/sonia/scm/ManagerDaoAdapter.java +++ b/scm-webapp/src/main/java/sonia/scm/ManagerDaoAdapter.java @@ -30,7 +30,7 @@ public class ManagerDaoAdapter { afterUpdate.handle(notModified); } else { - throw new NotFoundException(); + throw new NotFoundException(object.getClass(), object.getId()); } } @@ -58,7 +58,7 @@ public class ManagerDaoAdapter { dao.delete(toDelete); afterDelete.handle(toDelete); } else { - throw new NotFoundException(); + throw new NotFoundException(toDelete.getClass(), toDelete.getId()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/BrowserStreamingOutput.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/BrowserStreamingOutput.java index 2c78df627b..d2ce744c19 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/BrowserStreamingOutput.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/BrowserStreamingOutput.java @@ -2,8 +2,6 @@ package sonia.scm.api.rest.resources; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.repository.PathNotFoundException; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.api.CatCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.util.IOUtil; @@ -34,18 +32,6 @@ public class BrowserStreamingOutput implements StreamingOutput { public void write(OutputStream output) throws IOException { try { builder.retriveContent(output, path); - } catch (PathNotFoundException ex) { - if (logger.isWarnEnabled()) { - logger.warn("could not find path {}", ex.getPath()); - } - - throw new WebApplicationException(Response.Status.NOT_FOUND); - } catch (RevisionNotFoundException ex) { - if (logger.isWarnEnabled()) { - logger.warn("could not find revision {}", ex.getRevision()); - } - - throw new WebApplicationException(Response.Status.NOT_FOUND); } finally { IOUtil.close(repositoryService); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/DiffStreamingOutput.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/DiffStreamingOutput.java index db29725917..c5ce925797 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/DiffStreamingOutput.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/DiffStreamingOutput.java @@ -37,7 +37,6 @@ package sonia.scm.api.rest.resources; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.api.DiffCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.util.IOUtil; @@ -95,22 +94,6 @@ public class DiffStreamingOutput implements StreamingOutput { builder.retriveContent(output); } - catch (RevisionNotFoundException ex) - { - if (logger.isWarnEnabled()) - { - logger.warn("could not find revision {}", ex.getRevision()); - } - - throw new WebApplicationException(Response.Status.NOT_FOUND); - } -// catch (RepositoryException ex) -// { -// logger.error("could not write content to page", ex); -// -// throw new WebApplicationException(ex, -// Response.Status.INTERNAL_SERVER_ERROR); -// } finally { IOUtil.close(repositoryService); diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java index 8a5ba8b1e3..6bd20688d6 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java @@ -50,6 +50,7 @@ import sonia.scm.NotFoundException; import sonia.scm.NotSupportedFeatuerException; import sonia.scm.Type; import sonia.scm.api.rest.RestActionUploadResult; +import sonia.scm.api.v2.resources.RepositoryResource; import sonia.scm.repository.*; import sonia.scm.repository.api.Command; import sonia.scm.repository.api.RepositoryService; diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java deleted file mode 100644 index 4a9fd9e38f..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java +++ /dev/null @@ -1,1067 +0,0 @@ -/** - * 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 conditions and the following disclaimer in the documentation - * and/or other 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 ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 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.api.rest.resources; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.base.Strings; -import com.google.inject.Inject; -import com.google.inject.Singleton; -import com.webcohesion.enunciate.metadata.rs.ResponseCode; -import com.webcohesion.enunciate.metadata.rs.ResponseHeader; -import com.webcohesion.enunciate.metadata.rs.StatusCodes; -import com.webcohesion.enunciate.metadata.rs.TypeHint; -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.authz.AuthorizationException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import sonia.scm.NotFoundException; -import sonia.scm.repository.BlameResult; -import sonia.scm.repository.Branches; -import sonia.scm.repository.BrowserResult; -import sonia.scm.repository.Changeset; -import sonia.scm.repository.ChangesetPagingResult; -import sonia.scm.repository.HealthChecker; -import sonia.scm.repository.Permission; -import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryIsNotArchivedException; -import sonia.scm.repository.RepositoryManager; -import sonia.scm.repository.RepositoryNotFoundException; -import sonia.scm.repository.Tags; -import sonia.scm.repository.api.BlameCommandBuilder; -import sonia.scm.repository.api.BrowseCommandBuilder; -import sonia.scm.repository.api.CatCommandBuilder; -import sonia.scm.repository.api.CommandNotSupportedException; -import sonia.scm.repository.api.DiffCommandBuilder; -import sonia.scm.repository.api.DiffFormat; -import sonia.scm.repository.api.LogCommandBuilder; -import sonia.scm.repository.api.RepositoryService; -import sonia.scm.repository.api.RepositoryServiceFactory; -import sonia.scm.util.AssertUtil; -import sonia.scm.util.HttpUtil; -import sonia.scm.util.IOUtil; -import sonia.scm.util.Util; - -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.GenericEntity; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Request; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.StreamingOutput; -import javax.ws.rs.core.UriInfo; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; - -//~--- JDK imports ------------------------------------------------------------ - -/** - * Repository related RESTful Web Service Endpoint. - * - * @author Sebastian Sdorra - */ -@Singleton -@Path("repositories") -public class RepositoryResource extends AbstractManagerResource -{ - - /** Field description */ - public static final String PATH_PART = "repositories"; - - /** the logger for RepositoryResource */ - private static final Logger logger = - LoggerFactory.getLogger(RepositoryResource.class); - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * @param repositoryManager - * @param servicefactory - * @param healthChecker - */ - @Inject - public RepositoryResource(RepositoryManager repositoryManager, - RepositoryServiceFactory servicefactory, HealthChecker healthChecker) - { - super(repositoryManager, Repository.class); - this.repositoryManager = repositoryManager; - this.servicefactory = servicefactory; - this.healthChecker = healthChecker; - setDisableCache(false); - } - - //~--- methods -------------------------------------------------------------- - - /** - * Creates a new repository.Note: This method requires admin privileges. - * - * @param uriInfo current uri informations - * @param repository the repository to be created - * - * @return empty response with location header to the new repository - */ - @POST - @StatusCodes({ - @ResponseCode(code = 201, condition = "success", additionalHeaders = { - @ResponseHeader(name = "Location", description = "uri to the new created repository") - }), - @ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @TypeHint(TypeHint.NO_CONTENT.class) - @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - @Override - public Response create(@Context UriInfo uriInfo, Repository repository) - { - return super.create(uriInfo, repository); - } - - /** - * Deletes a repository. Note: This method requires owner privileges. - * - * @param id the id of the repository to delete. - * - * @return - */ - @DELETE - @Path("{id}") - @StatusCodes({ - @ResponseCode(code = 204, condition = "delete success"), - @ResponseCode(code = 403, condition = "forbidden, the current user has no owner privileges"), - @ResponseCode(code = 404, condition = "could not find repository"), - @ResponseCode( - code = 412, - condition = "precondition failed, the repository is not archived, this error occurs only with enabled repository archive" - ), - @ResponseCode(code = 500, condition = "internal server error") - }) - @TypeHint(TypeHint.NO_CONTENT.class) - @Override - public Response delete(@PathParam("id") String id) - { - Response response; - Repository repository = manager.get(id); - - if (repository != null) - { - preDelete(repository); - - try - { - manager.delete(repository); - response = Response.noContent().build(); - } - catch (RepositoryIsNotArchivedException ex) - { - logger.warn("non archived repository could not be deleted", ex); - response = Response.status(Response.Status.PRECONDITION_FAILED).build(); - } - catch (AuthorizationException ex) - { - logger.warn("delete not allowed", ex); - response = Response.status(Response.Status.FORBIDDEN).build(); - } catch (NotFoundException e) { - // there is nothing to do because delete should be idempotent - response = Response.ok().build(); - } -// catch (IOException ex) -// { -// logger.error("error during delete", ex); -// response = Response.serverError().build(); -// } - } - else - { - logger.warn("could not find repository {}", id); - response = Response.status(Status.NOT_FOUND).build(); - } - - return response; - } - - /** - * Re run repository health checks. - * - * @param id id of the repository - * - * @return - */ - @POST - @StatusCodes({ - @ResponseCode(code = 200, condition = "re run success"), - @ResponseCode(code = 403, condition = "forbidden, the current user has no owner privileges"), - @ResponseCode(code = 404, condition = "could not find repository"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @TypeHint(TypeHint.NO_CONTENT.class) - @Path("{id}/healthcheck") - public Response runHealthChecks(@PathParam("id") String id) - { - Response response; - - try - { - healthChecker.check(id); - // TODO should return 204 instead of 200 - response = Response.ok().build(); - } - catch (RepositoryNotFoundException ex) - { - logger.warn("could not find repository ".concat(id), ex); - response = Response.status(Status.NOT_FOUND).build(); - } catch (NotFoundException e) { - logger.error("error occured during health check", e); - response = Response.serverError().build(); - } - - return response; - } - - /** - * Modifies the given repository. Note: This method requires owner privileges. - * - * @param id id of the repository to be modified - * @param repository repository object to modify - * - * @return - */ - @PUT - @Path("{id}") - @StatusCodes({ - @ResponseCode(code = 204, condition = "update successful"), - @ResponseCode(code = 403, condition = "forbidden, the current user has no owner privileges"), - @ResponseCode(code = 404, condition = "could not find repository"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @TypeHint(TypeHint.NO_CONTENT.class) - @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - @Override - public Response update(@PathParam("id") String id, Repository repository) - { - return super.update(id, repository); - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Returns the {@link Repository} with the specified id. - * - * @param request the current request - * @param id the id/name of the user - * - * @return the {@link Repository} with the specified id - */ - @GET - @Path("{id}") - @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 404, condition = "not found, no repository with the specified id available"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @TypeHint(Repository.class) - @Override - public Response get(@Context Request request, @PathParam("id") String id) - { - return super.get(request, id); - } - - /** - * Returns all repositories. - * - * @param request the current request - * @param start the start value for paging - * @param limit the limit value for paging - * @param sortby sort parameter - * @param desc sort direction desc or aesc - * - * @return all repositories - */ - @GET - @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @TypeHint(Repository[].class) - @Override - public Response getAll(@Context Request request, @DefaultValue("0") - @QueryParam("start") int start, @DefaultValue("-1") - @QueryParam("limit") int limit, @QueryParam("sortby") String sortby, - @DefaultValue("false") - @QueryParam("desc") boolean desc) - { - return super.getAll(request, start, limit, sortby, desc); - } - - /** - * Returns a annotate/blame view for the given path. - * - * @param id the id of the repository - * @param revision the revision of the file - * @param path the path of the file - * - * @return a annotate/blame view for the given path - * - * @throws IOException - */ - @GET - @Path("{id}/blame") - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 400, condition = "bad request, the blame feature is not supported by this type of repositories."), - @ResponseCode(code = 404, condition = "not found, the repository or the path could not be found"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @TypeHint(BlameResult.class) - @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - public Response getBlame(@PathParam("id") String id, - @QueryParam("revision") String revision, @QueryParam("path") String path) - throws IOException - { - Response response = null; - RepositoryService service = null; - - try - { - AssertUtil.assertIsNotNull(path); - service = servicefactory.create(id); - - BlameCommandBuilder builder = service.getBlameCommand(); - - if (!Strings.isNullOrEmpty(revision)) - { - builder.setRevision(revision); - } - - BlameResult blamePagingResult = builder.getBlameResult(path); - - if (blamePagingResult != null) - { - response = Response.ok(blamePagingResult).build(); - } - else - { - response = Response.ok().build(); - } - } - catch (IllegalStateException ex) - { - response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); - } - catch (RepositoryNotFoundException ex) - { - response = Response.status(Response.Status.NOT_FOUND).build(); - } - catch (CommandNotSupportedException ex) - { - response = Response.status(Response.Status.BAD_REQUEST).build(); - } - finally - { - IOUtil.close(service); - } - - return response; - } - - /** - * Returns all {@link Branches} of a repository. - * - * @param id the id of the repository - * - * @return all {@link Branches} of a repository - * - * @throws IOException - * - * @since 1.18 - */ - @GET - @Path("{id}/branches") - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 400, condition = "bad request, the branch feature is not supported by this type of repositories."), - @ResponseCode(code = 404, condition = "not found, the repository could not be found"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @TypeHint(Branches.class) - @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - public Response getBranches(@PathParam("id") String id) - throws IOException - { - Response response = null; - RepositoryService service = null; - - try - { - service = servicefactory.create(id); - - Branches branches = service.getBranchesCommand().getBranches(); - - if (branches != null) - { - response = Response.ok(branches).build(); - } - else - { - response = Response.status(Status.NOT_FOUND).build(); - } - } - catch (RepositoryNotFoundException ex) - { - response = Response.status(Response.Status.NOT_FOUND).build(); - } - catch (CommandNotSupportedException ex) - { - response = Response.status(Response.Status.BAD_REQUEST).build(); - } - finally - { - IOUtil.close(service); - } - - return response; - } - - /** - * Returns a list of folders and files for the given folder. - * - * @param id the id of the repository - * @param revision the revision of the file - * @param path the path of the folder - * @param disableLastCommit true disables fetch of last commit message - * @param disableSubRepositoryDetection true disables sub repository detection - * @param recursive true to enable recursive browsing - * - * @return a list of folders and files for the given folder - * - * @throws IOException - */ - @GET - @Path("{id}/browse") - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 400, condition = "bad request, the browse feature is not supported by this type of repositories."), - @ResponseCode(code = 404, condition = "not found, the repository or the path could not be found"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @TypeHint(BrowserResult.class) - @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - //J- - public Response getBrowserResult( - @PathParam("id") String id, - @QueryParam("revision") String revision, - @QueryParam("path") String path, - @QueryParam("disableLastCommit") @DefaultValue("false") boolean disableLastCommit, - @QueryParam("disableSubRepositoryDetection") @DefaultValue("false") boolean disableSubRepositoryDetection, - @QueryParam("recursive") @DefaultValue("false") boolean recursive) - throws IOException - //J+ - { - Response response = null; - RepositoryService service = null; - - try - { - service = servicefactory.create(id); - - BrowseCommandBuilder builder = service.getBrowseCommand(); - - if (!Strings.isNullOrEmpty(revision)) - { - builder.setRevision(revision); - } - - if (!Strings.isNullOrEmpty(path)) - { - builder.setPath(path); - } - - //J- - builder.setDisableLastCommit(disableLastCommit) - .setDisableSubRepositoryDetection(disableSubRepositoryDetection) - .setRecursive(recursive); - //J+ - - BrowserResult result = builder.getBrowserResult(); - - if (result != null) - { - response = Response.ok(result).build(); - } - else - { - response = Response.status(Response.Status.NOT_FOUND).build(); - } - } - catch (NotFoundException ex) - { - response = Response.status(Response.Status.NOT_FOUND).build(); - } - catch (CommandNotSupportedException ex) - { - response = Response.status(Response.Status.BAD_REQUEST).build(); - } - finally - { - IOUtil.close(service); - } - - return response; - } - - /** - * Returns the {@link Changeset} from the given repository - * with the specified revision. - * - * @param id the id of the repository - * @param revision the revision of the changeset - * - * @return a {@link Changeset} from the given repository - * - * @throws IOException - */ - @GET - @Path("{id}/changeset/{revision}") - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 400, condition = "bad request, the changeset feature is not supported by this type of repositories."), - @ResponseCode(code = 404, condition = "not found, the repository or the revision could not be found"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @TypeHint(Changeset.class) - @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - public Response getChangeset(@PathParam("id") String id, - @PathParam("revision") String revision) - throws IOException - { - Response response = null; - - if (Util.isNotEmpty(id) && Util.isNotEmpty(revision)) - { - RepositoryService service = null; - - try - { - service = servicefactory.create(id); - - Changeset changeset = service.getLogCommand().getChangeset(revision); - - if (changeset != null) - { - response = Response.ok(changeset).build(); - } - else - { - response = Response.status(Status.NOT_FOUND).build(); - } - } - catch (NotFoundException ex) - { - response = Response.status(Response.Status.NOT_FOUND).build(); - } - catch (CommandNotSupportedException ex) - { - response = Response.status(Response.Status.BAD_REQUEST).build(); - } - finally - { - IOUtil.close(service); - } - } - else - { - logger.warn("id or revision is empty"); - response = Response.status(Status.BAD_REQUEST).build(); - } - - return response; - } - - /** - * Returns a list of {@link Changeset} for the given repository. - * - * @param id the id of the repository - * @param path path of a file - * @param revision the revision of the file specified by the path parameter - * @param branch name of the branch - * @param start the start value for paging - * @param limit the limit value for paging - * - * @return a list of {@link Changeset} for the given repository - * - * @throws IOException - */ - @GET - @Path("{id}/changesets") - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 400, condition = "bad request, the changeset feature is not supported by this type of repositories."), - @ResponseCode(code = 404, condition = "not found, the repository or the path could not be found"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @TypeHint(ChangesetPagingResult.class) - @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - //J- - public Response getChangesets( - @PathParam("id") String id, - @QueryParam("path") String path, - @QueryParam("revision") String revision, - @QueryParam("branch") String branch, - @DefaultValue("0") @QueryParam("start") int start, - @DefaultValue("20") @QueryParam("limit") int limit - ) throws IOException - //J+ - { - Response response = null; - RepositoryService service = null; - - try - { - ChangesetPagingResult changesets; - - service = servicefactory.create(id); - - LogCommandBuilder builder = service.getLogCommand(); - - if (!Strings.isNullOrEmpty(path)) - { - builder.setPath(path); - } - - if (!Strings.isNullOrEmpty(revision)) - { - builder.setStartChangeset(revision); - } - - if (!Strings.isNullOrEmpty(branch)) - { - builder.setBranch(branch); - } - - changesets = - builder.setPagingStart(start).setPagingLimit(limit).getChangesets(); - - if (changesets != null) - { - response = Response.ok(changesets).build(); - } - else - { - response = Response.ok().build(); - } - } - catch (NotFoundException ex) - { - response = Response.status(Response.Status.NOT_FOUND).build(); - } - catch (CommandNotSupportedException ex) - { - response = Response.status(Response.Status.BAD_REQUEST).build(); - } - finally - { - IOUtil.close(service); - } - - return response; - } - - /** - * Returns the content of a file. - * - * @param id the id of the repository - * @param revision the revision of the file - * @param path path to the file - * - * @return the content of a file - */ - @GET - @Path("{id}/content") - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 400, condition = "bad request, the content feature is not supported by this type of repositories."), - @ResponseCode(code = 404, condition = "not found, the repository or the path could not be found"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @TypeHint(StreamingOutput.class) - @Produces({ MediaType.APPLICATION_OCTET_STREAM }) - public Response getContent(@PathParam("id") String id, - @QueryParam("revision") String revision, @QueryParam("path") String path) - { - Response response; - StreamingOutput output; - RepositoryService service; - - try - { - service = servicefactory.create(id); - - CatCommandBuilder builder = service.getCatCommand(); - - if (!Strings.isNullOrEmpty(revision)) - { - builder.setRevision(revision); - } - - output = new BrowserStreamingOutput(service, builder, path); - - /** - * protection for crlf injection - * see https://bitbucket.org/sdorra/scm-manager/issue/320/crlf-injection-vulnerability-in-diff-api - */ - path = HttpUtil.removeCRLFInjectionChars(path); - - String contentDispositionName = getContentDispositionNameFromPath(path); - - response = Response.ok(output).header("Content-Disposition", - contentDispositionName).build(); - } - catch (RepositoryNotFoundException ex) - { - logger.warn("could not find repository browser for respository {}", id); - response = Response.status(Response.Status.NOT_FOUND).build(); - } - catch (CommandNotSupportedException ex) - { - response = Response.status(Response.Status.BAD_REQUEST).build(); - } - catch (Exception ex) - { - logger.error("could not retrive content", ex); - response = createErrorResponse(ex); - } - - return response; - } - - /** - * Returns the modifications of a {@link Changeset}. - * - * @param id the id of the repository - * @param revision the revision of the file - * @param path path to the file - * @param format - * - * @return the modifications of a {@link Changeset} - * - * @throws IOException - */ - @GET - @Path("{id}/diff") - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 400, condition = "bad request, the diff feature is not supported by this type of repositories."), - @ResponseCode(code = 404, condition = "not found, the repository or the path could not be found"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @TypeHint(DiffStreamingOutput.class) - @Produces(MediaType.APPLICATION_OCTET_STREAM) - public Response getDiff(@PathParam("id") String id, - @QueryParam("revision") String revision, @QueryParam("path") String path, - @QueryParam("format") DiffFormat format) - throws IOException - { - AssertUtil.assertIsNotEmpty(id); - AssertUtil.assertIsNotEmpty(revision); - - /** - * check for a crlf injection attack - * see https://bitbucket.org/sdorra/scm-manager/issue/320/crlf-injection-vulnerability-in-diff-api - */ - HttpUtil.checkForCRLFInjection(revision); - - RepositoryService service; - Response response; - - try - { - service = servicefactory.create(id); - - DiffCommandBuilder builder = service.getDiffCommand(); - - if (!Strings.isNullOrEmpty(revision)) - { - builder.setRevision(revision); - } - - if (!Strings.isNullOrEmpty(path)) - { - builder.setPath(path); - } - - if (format != null) - { - builder.setFormat(format); - } - - String name = service.getRepository().getName().concat("-").concat( - revision).concat(".diff"); - String contentDispositionName = getContentDispositionName(name); - - response = Response.ok(new DiffStreamingOutput(service, - builder)).header("Content-Disposition", contentDispositionName).build(); - } - catch (RepositoryNotFoundException ex) - { - response = Response.status(Response.Status.NOT_FOUND).build(); - } - catch (CommandNotSupportedException ex) - { - response = Response.status(Response.Status.BAD_REQUEST).build(); - } - catch (Exception ex) - { - logger.error("could not create diff", ex); - response = createErrorResponse(ex); - } - - return response; - } - - /** - * Returns all {@link Tags} of a repository. - * - * @param id the id of the repository - * - * @return all {@link Tags} of a repository - * - * @throws IOException - * - * @since 1.18 - */ - @GET - @Path("{id}/tags") - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 400, condition = "bad request, the tag feature is not supported by this type of repositories."), - @ResponseCode(code = 404, condition = "not found, the repository could not be found"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @TypeHint(Tags.class) - @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - public Response getTags(@PathParam("id") String id) - throws IOException - { - Response response = null; - RepositoryService service = null; - - try - { - service = servicefactory.create(id); - - Tags tags = service.getTagsCommand().getTags(); - - if (tags != null) - { - response = Response.ok(tags).build(); - } - else - { - response = Response.status(Status.NOT_FOUND).build(); - } - } - catch (RepositoryNotFoundException ex) - { - response = Response.status(Response.Status.NOT_FOUND).build(); - } - catch (CommandNotSupportedException ex) - { - response = Response.status(Response.Status.BAD_REQUEST).build(); - } - finally - { - IOUtil.close(service); - } - - return response; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param items - * - * @return - */ - @Override - protected GenericEntity> createGenericEntity( - Collection items) - { - return new GenericEntity>(items) {} - ; - } - - /** - * Method description - * - * - * - * @param repositories - * @return - */ - @Override - protected Collection prepareForReturn( - Collection repositories) - { - for (Repository repository : repositories) - { - prepareForReturn(repository); - } - - return repositories; - } - - /** - * Method description - * - * - * @param repository - * - * @return - */ - @Override - protected Repository prepareForReturn(Repository repository) - { - if (SecurityUtils.getSubject().isPermitted( - "repository:modify:".concat(repository.getId()))) - { - if (repository.getPermissions() == null) - { - repository.setPermissions(new ArrayList()); - } - } - else - { - logger.trace("remove properties and permissions from repository, " - + "because the user is not privileged"); - - repository.setProperties(null); - repository.setPermissions(null); - repository.setHealthCheckFailures(null); - } - - return repository; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param item - * - * @return - */ - @Override - protected String getId(Repository item) - { - return item.getId(); - } - - /** - * Method description - * - * - * @return - */ - @Override - protected String getPathPart() - { - return PATH_PART; - } - - /** - * Method description - * - * - * - * @param name - * - * @return - */ - private String getContentDispositionName(String name) - { - return HttpUtil.createContentDispositionAttachmentHeader(name); - } - - /** - * Method description - * - * - * @param path - * - * @return - */ - private String getContentDispositionNameFromPath(String path) - { - String name = path; - int index = path.lastIndexOf('/'); - - if (index >= 0) - { - name = path.substring(index + 1); - } - - return getContentDispositionName(name); - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private final HealthChecker healthChecker; - - /** Field description */ - private final RepositoryManager repositoryManager; - - /** Field description */ - private final RepositoryServiceFactory servicefactory; -} 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 5f57d1bcc8..e308aeda25 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 @@ -5,6 +5,7 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; import sonia.scm.NotFoundException; import sonia.scm.PageResult; +import sonia.scm.repository.Branch; import sonia.scm.repository.Branches; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; @@ -105,7 +106,7 @@ public class BranchRootResource { .stream() .anyMatch(branch -> branchName.equals(branch.getName())); if (!branchExists){ - throw new NotFoundException("branch", branchName); + throw NotFoundException.notFound(Branch.class, branchName).in(Repository.class, namespace + "/" + name).build(); } Repository repository = repositoryService.getRepository(); RepositoryPermissions.read(repository).check(); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java index 8900183f5b..bae521ae4c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java @@ -11,8 +11,6 @@ import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.RepositoryPermissions; -import sonia.scm.repository.RevisionNotFoundException; -import sonia.scm.repository.api.LogCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.web.VndMediaType; @@ -56,7 +54,7 @@ public class ChangesetRootResource { @Produces(VndMediaType.CHANGESET_COLLECTION) @TypeHint(CollectionDto.class) public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @DefaultValue("0") @QueryParam("page") int page, - @DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException, RevisionNotFoundException, RepositoryNotFoundException { + @DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException { try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { Repository repository = repositoryService.getRepository(); RepositoryPermissions.read(repository).check(); @@ -89,7 +87,7 @@ public class ChangesetRootResource { @Produces(VndMediaType.CHANGESET) @TypeHint(ChangesetDto.class) @Path("{id}") - public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("id") String id) throws IOException, RevisionNotFoundException, RepositoryNotFoundException { + public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("id") String id) throws IOException { try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { Repository repository = repositoryService.getRepository(); RepositoryPermissions.read(repository).check(); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java index dc7c305823..9680a319a5 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java @@ -6,10 +6,9 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.NotFoundException; import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.PathNotFoundException; import sonia.scm.repository.RepositoryNotFoundException; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.util.IOUtil; @@ -64,8 +63,8 @@ public class ContentResource { try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { Response.ResponseBuilder responseBuilder = Response.ok(stream); return createContentHeader(namespace, name, revision, path, repositoryService, responseBuilder); - } catch (RepositoryNotFoundException e) { - LOG.debug("path '{}' not found in repository {}/{}", path, namespace, name, e); + } catch (NotFoundException e) { + LOG.debug(e.getMessage()); return Response.status(Status.NOT_FOUND).build(); } } @@ -75,14 +74,8 @@ public class ContentResource { try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { repositoryService.getCatCommand().setRevision(revision).retriveContent(os, path); os.close(); - } catch (RepositoryNotFoundException e) { - LOG.debug("repository {}/{} not found", path, namespace, name, e); - throw new WebApplicationException(Status.NOT_FOUND); - } catch (PathNotFoundException e) { - LOG.debug("path '{}' not found in repository {}/{}", path, namespace, name, e); - throw new WebApplicationException(Status.NOT_FOUND); - } catch (RevisionNotFoundException e) { - LOG.debug("revision '{}' not found in repository {}/{}", revision, namespace, name, e); + } catch (NotFoundException e) { + LOG.debug(e.getMessage()); throw new WebApplicationException(Status.NOT_FOUND); } }; @@ -111,8 +104,8 @@ public class ContentResource { try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { Response.ResponseBuilder responseBuilder = Response.ok(); return createContentHeader(namespace, name, revision, path, repositoryService, responseBuilder); - } catch (RepositoryNotFoundException e) { - LOG.debug("path '{}' not found in repository {}/{}", path, namespace, name, e); + } catch (NotFoundException e) { + LOG.debug(e.getMessage()); return Response.status(Status.NOT_FOUND).build(); } } @@ -120,12 +113,6 @@ public class ContentResource { private Response createContentHeader(String namespace, String name, String revision, String path, RepositoryService repositoryService, Response.ResponseBuilder responseBuilder) { try { appendContentHeader(path, getHead(revision, path, repositoryService), responseBuilder); - } catch (PathNotFoundException e) { - LOG.debug("path '{}' not found in repository {}/{}", path, namespace, name, e); - return Response.status(Status.NOT_FOUND).build(); - } catch (RevisionNotFoundException e) { - LOG.debug("revision '{}' not found in repository {}/{}", revision, namespace, name, e); - return Response.status(Status.NOT_FOUND).build(); } catch (IOException e) { LOG.info("error reading repository resource {} from {}/{}", path, namespace, name, e); return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build(); @@ -139,7 +126,7 @@ public class ContentResource { contentType.getLanguage().ifPresent(language -> responseBuilder.header("Language", language)); } - private byte[] getHead(String revision, String path, RepositoryService repositoryService) throws IOException, PathNotFoundException, RevisionNotFoundException { + private byte[] getHead(String revision, String path, RepositoryService repositoryService) throws IOException { InputStream stream = repositoryService.getCatCommand().setRevision(revision).getStream(path); try { byte[] buffer = new byte[HEAD_BUFFER_SIZE]; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java index cb78c961a2..212a993f28 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java @@ -4,7 +4,6 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import sonia.scm.NotFoundException; import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.util.HttpUtil; @@ -15,7 +14,6 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; @@ -54,13 +52,9 @@ public class DiffRootResource { HttpUtil.checkForCRLFInjection(revision); try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { StreamingOutput responseEntry = output -> { - try { - repositoryService.getDiffCommand() - .setRevision(revision) - .retriveContent(output); - } catch (RevisionNotFoundException e) { - throw new WebApplicationException(Response.Status.NOT_FOUND); - } + repositoryService.getDiffCommand() + .setRevision(revision) + .retriveContent(output); }; return Response.ok(responseEntry) .header(HEADER_CONTENT_DISPOSITION, HttpUtil.createContentDispositionAttachmentHeader(String.format("%s-%s.diff", name, revision))) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java new file mode 100644 index 0000000000..8e97eabf8e --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java @@ -0,0 +1,34 @@ +package sonia.scm.api.v2.resources; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; +import sonia.scm.ContextEntry; +import sonia.scm.NotFoundException; + +import java.util.List; + +@Getter +public class ErrorDto { + private final String transactionId; + private final String errorCode; + private final List context; + private final String message; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private final String url; + + private ErrorDto(String transactionId, String errorCode, List context, String message) { + this(transactionId, errorCode, context, message, null); + } + private ErrorDto(String transactionId, String errorCode, List context, String message, String url) { + this.transactionId = transactionId; + this.errorCode = errorCode; + this.context = context; + this.message = message; + this.url = url; + } + + static ErrorDto from(NotFoundException notFoundException) { + return new ErrorDto("todo", "todo", notFoundException.getContext(), notFoundException.getMessage()); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java index e38a6f699a..ae4bcb1064 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java @@ -11,7 +11,6 @@ import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryNotFoundException; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.web.VndMediaType; @@ -51,8 +50,6 @@ public class FileHistoryRootResource { * @param pageSize pagination * @return all changesets related to the given file starting with the given revision * @throws IOException on io error - * @throws RevisionNotFoundException on missing revision - * @throws RepositoryNotFoundException on missing repository */ @GET @Path("{revision}/{path: .*}") @@ -69,7 +66,7 @@ public class FileHistoryRootResource { @PathParam("revision") String revision, @PathParam("path") String path, @DefaultValue("0") @QueryParam("page") int page, - @DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException, RevisionNotFoundException, RepositoryNotFoundException { + @DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException { try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { log.info("Get changesets of the file {} and revision {}", path, revision); Repository repository = repositoryService.getRepository(); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IdResourceManagerAdapter.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IdResourceManagerAdapter.java index 9fe3d8284d..533bc49da0 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IdResourceManagerAdapter.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IdResourceManagerAdapter.java @@ -5,6 +5,7 @@ import sonia.scm.AlreadyExistsException; import sonia.scm.ConcurrentModificationException; import sonia.scm.Manager; import sonia.scm.ModelObject; +import sonia.scm.NotFoundException; import sonia.scm.PageResult; import javax.ws.rs.core.Response; @@ -22,6 +23,7 @@ class IdResourceManagerAdapter { private final Manager manager; + private final String type; private final SingleResourceManagerAdapter singleAdapter; private final CollectionResourceManagerAdapter collectionAdapter; @@ -30,6 +32,7 @@ class IdResourceManagerAdapter(manager, type); collectionAdapter = new CollectionResourceManagerAdapter<>(manager, type); + this.type = type.getSimpleName(); } Response get(String id, Function mapToDto) { @@ -56,8 +59,8 @@ class IdResourceManagerAdapter> loadBy(String id) { - return () -> Optional.ofNullable(manager.get(id)); + private Supplier loadBy(String id) { + return () -> Optional.ofNullable(manager.get(id)).orElseThrow(() -> new NotFoundException(type, id)); } private Predicate idStaysTheSame(String id) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsRootResource.java index 28f855f40c..eaf165cda1 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ModificationsRootResource.java @@ -3,11 +3,8 @@ package sonia.scm.api.v2.resources; import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; -import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Modifications; import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.RepositoryNotFoundException; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.web.VndMediaType; @@ -46,7 +43,7 @@ public class ModificationsRootResource { @Produces(VndMediaType.MODIFICATIONS) @TypeHint(ModificationsDto.class) @Path("{revision}") - public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws IOException, RevisionNotFoundException, RepositoryNotFoundException , InternalRepositoryException { + public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws IOException { try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { Modifications modifications = repositoryService.getModificationsCommand() .revision(revision) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NotFoundExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NotFoundExceptionMapper.java index 04264a7629..05c5eccec3 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NotFoundExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/NotFoundExceptionMapper.java @@ -32,18 +32,20 @@ package sonia.scm.api.v2.resources; import sonia.scm.NotFoundException; -import sonia.scm.api.rest.StatusExceptionMapper; +import sonia.scm.web.VndMediaType; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; /** * @since 2.0.0 */ @Provider -public class NotFoundExceptionMapper extends StatusExceptionMapper { - - public NotFoundExceptionMapper() { - super(NotFoundException.class, Response.Status.NOT_FOUND); +public class NotFoundExceptionMapper implements ExceptionMapper { + @Override + public Response toResponse(NotFoundException exception) { + return Response.status(Response.Status.NOT_FOUND).entity(ErrorDto.from(exception)).type(VndMediaType.ERROR_TYPE).build(); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java index b1ea912acf..71f0e03516 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java @@ -109,7 +109,7 @@ public class PermissionRootResource { .filter(filterPermission(permissionName)) .map(permission -> modelToDtoMapper.map(permission, repository)) .findFirst() - .orElseThrow(NotFoundException::new) + .orElseThrow(() -> NotFoundException.notFound(Permission.class, namespace).in(Repository.class, namespace + "/" + name).build()) ).build(); } @@ -164,7 +164,7 @@ public class PermissionRootResource { RepositoryPermissions.permissionWrite(repository).check(); String extractedPermissionName = getPermissionName(permissionName); if (!isPermissionExist(new PermissionDto(extractedPermissionName, isGroupPermission(permissionName)), repository)) { - throw new NotFoundException("permission", extractedPermissionName); + throw NotFoundException.notFound(Permission.class, namespace).in(Repository.class, namespace + "/" + name).build(); } permission.setGroupPermission(isGroupPermission(permissionName)); if (!extractedPermissionName.equals(permission.getName())) { @@ -174,7 +174,7 @@ public class PermissionRootResource { .stream() .filter(filterPermission(permissionName)) .findFirst() - .orElseThrow(NotFoundException::new); + .orElseThrow(() -> NotFoundException.notFound(Permission.class, namespace).in(Repository.class, namespace + "/" + name).build()); dtoToModelMapper.modify(existingPermission, permission); manager.modify(repository); log.info("the permission with name: {} is updated.", permissionName); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java index 56bb50d4e6..77df2d8e50 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java @@ -203,8 +203,8 @@ public class RepositoryResource { } } - private Supplier> loadBy(String namespace, String name) { - return () -> Optional.ofNullable(manager.get(new NamespaceAndName(namespace, name))); + private Supplier loadBy(String namespace, String name) { + return () -> Optional.ofNullable(manager.get(new NamespaceAndName(namespace, name))).orElseThrow(() -> new NotFoundException(Repository.class, namespace + "/" + name)); } private Predicate nameAndNamespaceStaysTheSame(String namespace, String name) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SingleResourceManagerAdapter.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SingleResourceManagerAdapter.java index d484567bf8..334dc72b20 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SingleResourceManagerAdapter.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SingleResourceManagerAdapter.java @@ -38,7 +38,10 @@ class SingleResourceManagerAdapter Optional.empty()); } - SingleResourceManagerAdapter(Manager manager, Class type, Function> errorHandler) { + SingleResourceManagerAdapter( + Manager manager, + Class type, + Function> errorHandler) { super(manager, type); this.errorHandler = errorHandler; } @@ -47,25 +50,16 @@ class SingleResourceManagerAdapter> reader, Function mapToDto) { - return reader.get() - .map(mapToDto) - .map(Response::ok) - .map(Response.ResponseBuilder::build) - .orElseThrow(NotFoundException::new); - } - public Response update(Supplier> reader, Function applyChanges, Predicate hasSameKey, Consumer checker) throws NotFoundException, ConcurrentModificationException { - MODEL_OBJECT existingModelObject = reader.get().orElseThrow(NotFoundException::new); - checker.accept(existingModelObject); - return update(reader,applyChanges,hasSameKey); + Response get(Supplier reader, Function mapToDto) { + return Response.ok(mapToDto.apply(reader.get())).build(); } /** * Update the model object for the given id according to the given function and returns a corresponding http response. * This handles all corner cases, eg. no matching object for the id or missing privileges. */ - public Response update(Supplier> reader, Function applyChanges, Predicate hasSameKey) throws NotFoundException, ConcurrentModificationException { - MODEL_OBJECT existingModelObject = reader.get().orElseThrow(NotFoundException::new); + Response update(Supplier reader, Function applyChanges, Predicate hasSameKey) throws ConcurrentModificationException { + MODEL_OBJECT existingModelObject = reader.get(); MODEL_OBJECT changedModelObject = applyChanges.apply(existingModelObject); if (!hasSameKey.test(changedModelObject)) { return Response.status(BAD_REQUEST).entity("illegal change of id").build(); @@ -81,11 +75,13 @@ class SingleResourceManagerAdapter updated.getLastModified()); } - public Response delete(Supplier> reader) { - return reader.get() - .map(MODEL_OBJECT::getId) - .map(this::delete) - .orElse(null); + public Response delete(Supplier reader) { + try { + return delete(reader.get().getId()); + } catch (NotFoundException e) { + // due to idempotency of delete this does not matter here. + return null; + } } @Override diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java index e3cf17d3a4..42be4daf89 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java @@ -1,10 +1,8 @@ package sonia.scm.api.v2.resources; -import sonia.scm.NotFoundException; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.RepositoryNotFoundException; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.api.BrowseCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; @@ -33,14 +31,14 @@ public class SourceRootResource { @GET @Produces(VndMediaType.SOURCE) @Path("") - public Response getAllWithoutRevision(@PathParam("namespace") String namespace, @PathParam("name") String name) throws RevisionNotFoundException, RepositoryNotFoundException, IOException { + public Response getAllWithoutRevision(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException { return getSource(namespace, name, "/", null); } @GET @Produces(VndMediaType.SOURCE) @Path("{revision}") - public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws RevisionNotFoundException, RepositoryNotFoundException, IOException { + public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws IOException { return getSource(namespace, name, "/", revision); } @@ -51,7 +49,7 @@ public class SourceRootResource { return getSource(namespace, name, path, revision); } - private Response getSource(String namespace, String repoName, String path, String revision) throws IOException, RevisionNotFoundException, RepositoryNotFoundException { + private Response getSource(String namespace, String repoName, String path, String revision) throws IOException { NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, repoName); try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) { BrowseCommandBuilder browseCommand = repositoryService.getBrowseCommand(); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagNotFoundException.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagNotFoundException.java deleted file mode 100644 index b572d20c5b..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagNotFoundException.java +++ /dev/null @@ -1,7 +0,0 @@ -package sonia.scm.api.v2.resources; - -import sonia.scm.NotFoundException; - -public class TagNotFoundException extends NotFoundException { - -} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java index b3601f7709..6b4f8b2f96 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java @@ -3,6 +3,7 @@ package sonia.scm.api.v2.resources; import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; +import sonia.scm.NotFoundException; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryNotFoundException; @@ -47,7 +48,7 @@ public class TagRootResource { }) @Produces(VndMediaType.TAG_COLLECTION) @TypeHint(CollectionDto.class) - public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException, RepositoryNotFoundException { + public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException { try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { Tags tags = getTags(repositoryService); if (tags != null && tags.getTags() != null) { @@ -72,7 +73,7 @@ public class TagRootResource { @Produces(VndMediaType.TAG) @TypeHint(TagDto.class) @Path("{tagName}") - public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("tagName") String tagName) throws IOException, RepositoryNotFoundException, TagNotFoundException { + public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("tagName") String tagName) throws IOException { NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name); try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) { Tags tags = getTags(repositoryService); @@ -80,7 +81,7 @@ public class TagRootResource { Tag tag = tags.getTags().stream() .filter(t -> tagName.equals(t.getName())) .findFirst() - .orElseThrow(TagNotFoundException::new); + .orElseThrow(() -> createNotFoundException(namespace, name, tagName)); return Response.ok(tagToTagDtoMapper.map(tag, namespaceAndName)).build(); } else { return Response.status(Response.Status.INTERNAL_SERVER_ERROR) @@ -90,6 +91,10 @@ public class TagRootResource { } } + private NotFoundException createNotFoundException(String namespace, String name, String tagName) { + return NotFoundException.notFound("Tag", tagName).in("Repository", namespace + "/" + name).build(); + } + private Tags getTags(RepositoryService repositoryService) throws IOException { Repository repository = repositoryService.getRepository(); RepositoryPermissions.read(repository).check(); diff --git a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java index 236c6d0ebc..662d9407ca 100644 --- a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java +++ b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java @@ -172,7 +172,7 @@ public class DefaultGroupManager extends AbstractGroupManager if (fresh == null) { - throw new NotFoundException("group", group.getId()); + throw new NotFoundException(Group.class, group.getId()); } fresh.copyProperties(group); diff --git a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java index 4efaaa1d1c..0f2f45ff0b 100644 --- a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java +++ b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java @@ -219,7 +219,7 @@ public class DefaultUserManager extends AbstractUserManager if (fresh == null) { - throw new NotFoundException(); + throw new NotFoundException(User.class, user.getName()); } fresh.copyProperties(user); @@ -419,7 +419,7 @@ public class DefaultUserManager extends AbstractUserManager public void overwritePassword(String userId, String newPassword) { User user = get(userId); if (user == null) { - throw new NotFoundException(); + throw new NotFoundException(User.class, userId); } if (!isTypeDefault(user)) { throw new ChangePasswordNotAllowedException(user.getType()); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ContentResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ContentResourceTest.java index 3d898119fb..bb7b0e5458 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ContentResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ContentResourceTest.java @@ -8,8 +8,8 @@ import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.NotFoundException; import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.PathNotFoundException; import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.api.CatCommandBuilder; import sonia.scm.repository.api.RepositoryService; @@ -59,7 +59,7 @@ public class ContentResourceTest { // defaults for unknown things doThrow(new RepositoryNotFoundException("x")).when(repositoryServiceFactory).create(not(eq(existingNamespaceAndName))); - doThrow(new PathNotFoundException("x")).when(catCommand).getStream(any()); + doThrow(new NotFoundException("Test", "X")).when(catCommand).getStream(any()); } @Test @@ -175,7 +175,7 @@ public class ContentResourceTest { } @Override - public void close() throws IOException { + public void close() { closed = true; } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java index fe22c944c4..406547b951 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java @@ -17,11 +17,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.NotFoundException; import sonia.scm.api.rest.AuthorizationExceptionMapper; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryNotFoundException; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.api.DiffCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; @@ -62,7 +62,7 @@ public class DiffResourceTest extends RepositoryTestBase { @Before - public void prepareEnvironment() throws Exception { + public void prepareEnvironment() { diffRootResource = new DiffRootResource(serviceFactory); super.diffRootResource = Providers.of(diffRootResource); dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource()); @@ -108,7 +108,7 @@ public class DiffResourceTest extends RepositoryTestBase { @Test public void shouldGet404OnMissingRepository() throws URISyntaxException, RepositoryNotFoundException { - when(serviceFactory.create(any(NamespaceAndName.class))).thenThrow(RepositoryNotFoundException.class); + when(serviceFactory.create(any(NamespaceAndName.class))).thenThrow(new NotFoundException("Text", "x")); MockHttpRequest request = MockHttpRequest .get(DIFF_URL + "revision") .accept(VndMediaType.DIFF); @@ -120,20 +120,22 @@ public class DiffResourceTest extends RepositoryTestBase { @Test public void shouldGet404OnMissingRevision() throws Exception { when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder); - when(diffCommandBuilder.retriveContent(any())).thenThrow(RevisionNotFoundException.class); + when(diffCommandBuilder.retriveContent(any())).thenThrow(new NotFoundException("Text", "x")); MockHttpRequest request = MockHttpRequest .get(DIFF_URL + "revision") .accept(VndMediaType.DIFF); MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(404, response.getStatus()); } @Test public void shouldGet400OnCrlfInjection() throws Exception { when(diffCommandBuilder.setRevision(anyString())).thenReturn(diffCommandBuilder); - when(diffCommandBuilder.retriveContent(any())).thenThrow(RevisionNotFoundException.class); + when(diffCommandBuilder.retriveContent(any())).thenThrow(new NotFoundException("Text", "x")); MockHttpRequest request = MockHttpRequest .get(DIFF_URL + "ny%0D%0ASet-cookie:%20Tamper=3079675143472450634") diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java index 934e05d0d1..4f00f9ba5b 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java @@ -18,6 +18,7 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.NotFoundException; import sonia.scm.api.rest.AuthorizationExceptionMapper; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; @@ -26,7 +27,6 @@ import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Person; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryNotFoundException; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.api.LogCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; @@ -79,7 +79,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase { @Before - public void prepareEnvironment() throws Exception { + public void prepareEnvironment() { fileHistoryCollectionToDtoMapper = new FileHistoryCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks); fileHistoryRootResource = new FileHistoryRootResource(serviceFactory, fileHistoryCollectionToDtoMapper); super.fileHistoryRootResource = Providers.of(fileHistoryRootResource); @@ -134,7 +134,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase { @Test public void shouldGet404OnMissingRepository() throws URISyntaxException, RepositoryNotFoundException { - when(serviceFactory.create(any(NamespaceAndName.class))).thenThrow(RepositoryNotFoundException.class); + when(serviceFactory.create(any(NamespaceAndName.class))).thenThrow(new NotFoundException("Text", "x")); MockHttpRequest request = MockHttpRequest .get(FILE_HISTORY_URL + "revision/a.txt") .accept(VndMediaType.CHANGESET_COLLECTION); @@ -152,7 +152,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase { when(logCommandBuilder.setPagingLimit(anyInt())).thenReturn(logCommandBuilder); when(logCommandBuilder.setStartChangeset(eq(id))).thenReturn(logCommandBuilder); when(logCommandBuilder.setPath(eq(path))).thenReturn(logCommandBuilder); - when(logCommandBuilder.getChangesets()).thenThrow(RevisionNotFoundException.class); + when(logCommandBuilder.getChangesets()).thenThrow(new NotFoundException("Text", "x")); MockHttpRequest request = MockHttpRequest .get(FILE_HISTORY_URL + id + "/" + path) diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java index c84a74bc92..cca7493162 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java @@ -14,7 +14,6 @@ import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.RepositoryNotFoundException; -import sonia.scm.repository.RevisionNotFoundException; import sonia.scm.repository.api.BrowseCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; @@ -69,7 +68,7 @@ public class SourceRootResourceTest extends RepositoryTestBase { } @Test - public void shouldReturnSources() throws URISyntaxException, IOException, RevisionNotFoundException { + public void shouldReturnSources() throws URISyntaxException, IOException { BrowserResult result = createBrowserResult(); when(browseCommandBuilder.getBrowserResult()).thenReturn(result); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/sources"); @@ -83,7 +82,7 @@ public class SourceRootResourceTest extends RepositoryTestBase { @Test public void shouldReturn404IfRepoNotFound() throws URISyntaxException, RepositoryNotFoundException { - when(serviceFactory.create(new NamespaceAndName("idont", "exist"))).thenThrow(RepositoryNotFoundException.class); + when(serviceFactory.create(new NamespaceAndName("idont", "exist"))).thenThrow(new RepositoryNotFoundException("abc")); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "idont/exist/sources"); MockHttpResponse response = new MockHttpResponse(); @@ -92,7 +91,7 @@ public class SourceRootResourceTest extends RepositoryTestBase { } @Test - public void shouldGetResultForSingleFile() throws URISyntaxException, IOException, RevisionNotFoundException { + public void shouldGetResultForSingleFile() throws URISyntaxException, IOException { BrowserResult browserResult = new BrowserResult(); browserResult.setRevision("revision"); FileObject fileObject = new FileObject(); @@ -111,7 +110,7 @@ public class SourceRootResourceTest extends RepositoryTestBase { @Test public void shouldGet404ForSingleFileIfRepoNotFound() throws URISyntaxException, RepositoryNotFoundException { - when(serviceFactory.create(new NamespaceAndName("idont", "exist"))).thenThrow(RepositoryNotFoundException.class); + when(serviceFactory.create(new NamespaceAndName("idont", "exist"))).thenThrow(new RepositoryNotFoundException("abc")); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "idont/exist/sources/revision/fileabc"); MockHttpResponse response = new MockHttpResponse(); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java index b363b66bfb..1451498124 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java @@ -185,7 +185,7 @@ public class UserRootResourceTest { .content(content.getBytes()); MockHttpResponse response = new MockHttpResponse(); - doThrow(NotFoundException.class).when(userManager).overwritePassword(any(), any()); + doThrow(new NotFoundException("Test", "x")).when(userManager).overwritePassword(any(), any()); dispatcher.invoke(request, response); diff --git a/scm-webapp/src/test/java/sonia/scm/web/protocol/HttpProtocolServletTest.java b/scm-webapp/src/test/java/sonia/scm/web/protocol/HttpProtocolServletTest.java index 077020f60c..c22491d045 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/protocol/HttpProtocolServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/protocol/HttpProtocolServletTest.java @@ -63,7 +63,7 @@ public class HttpProtocolServletTest { when(userAgentParser.parse(request)).thenReturn(userAgent); when(userAgent.isBrowser()).thenReturn(false); NamespaceAndName existingRepo = new NamespaceAndName("space", "repo"); - when(serviceFactory.create(not(eq(existingRepo)))).thenThrow(RepositoryNotFoundException.class); + when(serviceFactory.create(not(eq(existingRepo)))).thenThrow(new RepositoryNotFoundException("x")); when(serviceFactory.create(existingRepo)).thenReturn(repositoryService); when(requestProvider.get()).thenReturn(httpServletRequest); } From bf1effb9c0939205fb646196ce6eb130a09829ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 18 Oct 2018 16:08:49 +0200 Subject: [PATCH 02/27] Remove special RepositoryNotFoundException --- .../java/sonia/scm/NotFoundException.java | 26 ++++--- .../RepositoryNotFoundException.java | 68 ------------------- .../api/RepositoryServiceFactory.java | 8 +-- .../scm/repository/spi/HookEventFacade.java | 10 +-- .../sonia/scm/web/HgHookCallbackServlet.java | 14 ++-- .../api/v2/resources/BranchRootResource.java | 7 +- .../v2/resources/ChangesetRootResource.java | 1 - .../scm/api/v2/resources/ContentResource.java | 1 - .../v2/resources/FileHistoryRootResource.java | 1 - .../v2/resources/PermissionRootResource.java | 9 ++- .../api/v2/resources/SourceRootResource.java | 1 - .../scm/api/v2/resources/TagRootResource.java | 1 - .../repository/DefaultRepositoryManager.java | 4 +- .../sonia/scm/repository/HealthChecker.java | 2 +- .../scm/web/protocol/HttpProtocolServlet.java | 6 +- .../api/v2/resources/ContentResourceTest.java | 3 +- .../api/v2/resources/DiffResourceTest.java | 3 +- .../v2/resources/FileHistoryResourceTest.java | 3 +- .../v2/resources/SourceRootResourceTest.java | 10 +-- .../DefaultRepositoryManagerTest.java | 4 +- .../web/protocol/HttpProtocolServletTest.java | 8 +-- 21 files changed, 56 insertions(+), 134 deletions(-) delete mode 100644 scm-core/src/main/java/sonia/scm/repository/RepositoryNotFoundException.java diff --git a/scm-core/src/main/java/sonia/scm/NotFoundException.java b/scm-core/src/main/java/sonia/scm/NotFoundException.java index 929fa617ad..d562a674bb 100644 --- a/scm-core/src/main/java/sonia/scm/NotFoundException.java +++ b/scm-core/src/main/java/sonia/scm/NotFoundException.java @@ -1,5 +1,6 @@ package sonia.scm; +import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import java.util.Collections; @@ -25,14 +26,20 @@ public class NotFoundException extends RuntimeException { this.context = context; } + public static NotFoundExceptionBuilder notFound(Repository repository) { + return new NotFoundExceptionBuilder().in(repository); + } + + public static NotFoundExceptionBuilder notFound(NamespaceAndName namespaceAndName) { + return new NotFoundExceptionBuilder().in(namespaceAndName); + } + public static NotFoundExceptionBuilder notFound(Class type, String id) { - NotFoundExceptionBuilder builder = new NotFoundExceptionBuilder(); - return builder.in(type, id); + return new NotFoundExceptionBuilder().in(type, id); } public static NotFoundExceptionBuilder notFound(String type, String id) { - NotFoundExceptionBuilder builder = new NotFoundExceptionBuilder(); - return builder.in(type, id); + return new NotFoundExceptionBuilder().in(type, id); } public List getContext() { @@ -50,17 +57,20 @@ public class NotFoundException extends RuntimeException { private final List context = new LinkedList<>(); public NotFoundExceptionBuilder in(Repository repository) { - this.in(Repository.class, repository.getNamespaceAndName().logString()); - return this; + return in(repository.getNamespaceAndName()); + } + + public NotFoundExceptionBuilder in(NamespaceAndName namespaceAndName) { + return this.in(Repository.class, namespaceAndName.logString()); } public NotFoundExceptionBuilder in(Class type, String id) { - this.context.add(new ContextEntry(type, id)); + context.add(new ContextEntry(type, id)); return this; } public NotFoundExceptionBuilder in(String type, String id) { - this.context.add(new ContextEntry(type, id)); + context.add(new ContextEntry(type, id)); return this; } diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryNotFoundException.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryNotFoundException.java deleted file mode 100644 index 9863aeddd3..0000000000 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryNotFoundException.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * 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 conditions and the following disclaimer in the documentation - * and/or other 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 ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 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.repository; - -import sonia.scm.NotFoundException; - -/** - * Signals that the specified {@link Repository} could be found. - * - * @author Sebastian Sdorra - * @since 1.6 - */ -public class RepositoryNotFoundException extends NotFoundException -{ - - private static final long serialVersionUID = -6583078808900520166L; - private static final String TYPE_REPOSITORY = "Repository"; - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs a new {@link RepositoryNotFoundException} with null as its - * error detail message. - * - */ - public RepositoryNotFoundException(Repository repository) { - super(Repository.class, repository.getNamespaceAndName().logString()); - } - - public RepositoryNotFoundException(String repositoryId) { - super(TYPE_REPOSITORY, repositoryId); - } - - public RepositoryNotFoundException(NamespaceAndName namespaceAndName) { - super(Repository.class, namespaceAndName.logString()); - } -} diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java index 7160c2723d..4a53dbb449 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java @@ -38,13 +38,13 @@ package sonia.scm.repository.api; import com.github.legman.ReferenceType; import com.github.legman.Subscribe; import com.google.common.base.Preconditions; -import com.google.common.base.Strings; import com.google.common.collect.Sets; import com.google.inject.Inject; import com.google.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.HandlerEventType; +import sonia.scm.NotFoundException; import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; import sonia.scm.config.ScmConfiguration; @@ -57,7 +57,6 @@ import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryCacheKeyPredicate; import sonia.scm.repository.RepositoryEvent; import sonia.scm.repository.RepositoryManager; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.spi.RepositoryServiceProvider; import sonia.scm.repository.spi.RepositoryServiceResolver; @@ -161,7 +160,7 @@ public final class RepositoryServiceFactory * @return a implementation of RepositoryService * for the given type of repository * - * @throws RepositoryNotFoundException if no repository + * @throws NotFoundException if no repository * with the given id is available * @throws RepositoryServiceNotFoundException if no repository service * implementation for this kind of repository is available @@ -170,7 +169,6 @@ public final class RepositoryServiceFactory * for that repository */ public RepositoryService create(NamespaceAndName namespaceAndName) - throws RepositoryNotFoundException { Preconditions.checkArgument(namespaceAndName != null, "a non empty namespace and name is required"); @@ -179,7 +177,7 @@ public final class RepositoryServiceFactory if (repository == null) { - throw new RepositoryNotFoundException(namespaceAndName); + throw NotFoundException.notFound(namespaceAndName).build(); } return create(repository); diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/HookEventFacade.java b/scm-core/src/main/java/sonia/scm/repository/spi/HookEventFacade.java index 52ef68f272..359b068655 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/HookEventFacade.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/HookEventFacade.java @@ -35,12 +35,12 @@ package sonia.scm.repository.spi; import com.google.inject.Inject; import com.google.inject.Provider; +import sonia.scm.NotFoundException; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryHookEvent; import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.RepositoryManager; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.api.HookContext; import sonia.scm.repository.api.HookContextFactory; @@ -71,18 +71,18 @@ public final class HookEventFacade //~--- methods -------------------------------------------------------------- - public HookEventHandler handle(String id) throws RepositoryNotFoundException { + public HookEventHandler handle(String id) { return handle(repositoryManagerProvider.get().get(id)); } - public HookEventHandler handle(NamespaceAndName namespaceAndName) throws RepositoryNotFoundException { + public HookEventHandler handle(NamespaceAndName namespaceAndName) { return handle(repositoryManagerProvider.get().get(namespaceAndName)); } - public HookEventHandler handle(Repository repository) throws RepositoryNotFoundException { + public HookEventHandler handle(Repository repository) { if (repository == null) { - throw new RepositoryNotFoundException(repository); + throw NotFoundException.notFound(repository).build(); } return new HookEventHandler(repositoryManagerProvider.get(), diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java index 2734996686..25a368d25b 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java @@ -44,11 +44,11 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.NotFoundException; import sonia.scm.repository.HgContext; import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.RepositoryHookType; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.RepositoryUtil; import sonia.scm.repository.api.HgHookMessage; import sonia.scm.repository.api.HgHookMessage.Severity; @@ -275,17 +275,11 @@ public class HgHookCallbackServlet extends HttpServlet printMessages(response, context); } - catch (RepositoryNotFoundException ex) + catch (NotFoundException ex) { - if (logger.isErrorEnabled()) - { - logger.error("could not find repository with id {}", id); + logger.error(ex.getMessage()); - if (logger.isTraceEnabled()) - { - logger.trace("repository not found", ex); - } - } + logger.trace("repository not found", ex); response.sendError(HttpServletResponse.SC_NOT_FOUND); } 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 e308aeda25..2c0338309c 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 @@ -11,7 +11,6 @@ import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.api.CommandNotSupportedException; import sonia.scm.repository.api.RepositoryService; @@ -78,7 +77,7 @@ public class BranchRootResource { .build(); } catch (CommandNotSupportedException ex) { return Response.status(Response.Status.BAD_REQUEST).build(); - } catch (RepositoryNotFoundException e) { + } catch (NotFoundException e) { return Response.status(Response.Status.NOT_FOUND).build(); } } @@ -98,7 +97,7 @@ public class BranchRootResource { @PathParam("name") String name, @PathParam("branch") String branchName, @DefaultValue("0") @QueryParam("page") int page, - @DefaultValue("10") @QueryParam("pageSize") int pageSize) throws Exception { + @DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException { try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { boolean branchExists = repositoryService.getBranchesCommand() .getBranches() @@ -151,8 +150,6 @@ public class BranchRootResource { return Response.ok(branchCollectionToDtoMapper.map(namespace, name, branches.getBranches())).build(); } catch (CommandNotSupportedException ex) { return Response.status(Response.Status.BAD_REQUEST).build(); - } catch (RepositoryNotFoundException e) { - return Response.status(Response.Status.NOT_FOUND).build(); } } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java index bae521ae4c..dc33d57ce9 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java @@ -9,7 +9,6 @@ import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java index 9680a319a5..da5a8a4a0a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ContentResource.java @@ -8,7 +8,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.NotFoundException; import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.util.IOUtil; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java index ae4bcb1064..faa7f4975c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java @@ -10,7 +10,6 @@ import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.web.VndMediaType; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java index 71f0e03516..4b088a62a5 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java @@ -11,7 +11,6 @@ import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Permission; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.RepositoryPermissions; import sonia.scm.web.VndMediaType; @@ -131,7 +130,7 @@ public class PermissionRootResource { @Produces(VndMediaType.PERMISSION) @TypeHint(PermissionDto.class) @Path("") - public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws RepositoryNotFoundException { + public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) { Repository repository = load(namespace, name); RepositoryPermissions.permissionRead(repository).check(); return Response.ok(permissionCollectionToDtoMapper.map(repository)).build(); @@ -237,12 +236,12 @@ public class PermissionRootResource { * @param namespace the repository namespace * @param name the repository name * @return the repository if the user is permitted - * @throws RepositoryNotFoundException if the repository does not exists + * @throws NotFoundException if the repository does not exists */ - private Repository load(String namespace, String name) throws RepositoryNotFoundException { + private Repository load(String namespace, String name) { NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name); return Optional.ofNullable(manager.get(namespaceAndName)) - .orElseThrow(() -> new RepositoryNotFoundException(namespaceAndName)); + .orElseThrow(() -> NotFoundException.notFound(namespaceAndName).build()); } /** diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java index 42be4daf89..01a25f461c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java @@ -2,7 +2,6 @@ package sonia.scm.api.v2.resources; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.api.BrowseCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java index 6b4f8b2f96..632c428e62 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java @@ -6,7 +6,6 @@ import com.webcohesion.enunciate.metadata.rs.TypeHint; import sonia.scm.NotFoundException; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.Tag; import sonia.scm.repository.Tags; 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 c2e8bc3ad8..dc4767e024 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java @@ -198,7 +198,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { } @Override - public void refresh(Repository repository) throws RepositoryNotFoundException { + public void refresh(Repository repository) { AssertUtil.assertIsNotNull(repository); RepositoryPermissions.read(repository).check(); @@ -207,7 +207,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { if (fresh != null) { fresh.copyProperties(repository); } else { - throw new RepositoryNotFoundException(repository); + throw NotFoundException.notFound(repository).build(); } } diff --git a/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java b/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java index e9c415c4fa..61d488d641 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java @@ -61,7 +61,7 @@ public final class HealthChecker { Repository repository = repositoryManager.get(id); if (repository == null) { - throw new RepositoryNotFoundException(id); + throw new NotFoundException(Repository.class, id); } doCheck(repository); diff --git a/scm-webapp/src/main/java/sonia/scm/web/protocol/HttpProtocolServlet.java b/scm-webapp/src/main/java/sonia/scm/web/protocol/HttpProtocolServlet.java index bc085d2cc7..c6a96e4b9e 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/protocol/HttpProtocolServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/web/protocol/HttpProtocolServlet.java @@ -4,11 +4,11 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpStatus; +import sonia.scm.NotFoundException; import sonia.scm.PushStateDispatcher; import sonia.scm.filter.WebElement; import sonia.scm.repository.DefaultRepositoryProvider; import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.repository.spi.HttpScmProtocol; @@ -71,8 +71,8 @@ public class HttpProtocolServlet extends HttpServlet { requestProvider.get().setAttribute(DefaultRepositoryProvider.ATTRIBUTE_NAME, repositoryService.getRepository()); HttpScmProtocol protocol = repositoryService.getProtocol(HttpScmProtocol.class); protocol.serve(req, resp, getServletConfig()); - } catch (RepositoryNotFoundException e) { - log.debug("Repository not found for namespace and name {}", namespaceAndName, e); + } catch (NotFoundException e) { + log.debug(e.getMessage()); resp.setStatus(HttpStatus.SC_NOT_FOUND); } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ContentResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ContentResourceTest.java index bb7b0e5458..1ad0aad435 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ContentResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ContentResourceTest.java @@ -10,7 +10,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.NotFoundException; import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.api.CatCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; @@ -58,7 +57,7 @@ public class ContentResourceTest { when(catCommand.setRevision(REV)).thenReturn(catCommand); // defaults for unknown things - doThrow(new RepositoryNotFoundException("x")).when(repositoryServiceFactory).create(not(eq(existingNamespaceAndName))); + doThrow(new NotFoundException("Test", "r")).when(repositoryServiceFactory).create(not(eq(existingNamespaceAndName))); doThrow(new NotFoundException("Test", "X")).when(catCommand).getStream(any()); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java index 406547b951..e762e5d186 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java @@ -21,7 +21,6 @@ import sonia.scm.NotFoundException; import sonia.scm.api.rest.AuthorizationExceptionMapper; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.api.DiffCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; @@ -107,7 +106,7 @@ public class DiffResourceTest extends RepositoryTestBase { } @Test - public void shouldGet404OnMissingRepository() throws URISyntaxException, RepositoryNotFoundException { + public void shouldGet404OnMissingRepository() throws URISyntaxException { when(serviceFactory.create(any(NamespaceAndName.class))).thenThrow(new NotFoundException("Text", "x")); MockHttpRequest request = MockHttpRequest .get(DIFF_URL + "revision") diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java index 4f00f9ba5b..322995eaa7 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java @@ -26,7 +26,6 @@ import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Person; import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.api.LogCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; @@ -133,7 +132,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase { @Test - public void shouldGet404OnMissingRepository() throws URISyntaxException, RepositoryNotFoundException { + public void shouldGet404OnMissingRepository() throws URISyntaxException { when(serviceFactory.create(any(NamespaceAndName.class))).thenThrow(new NotFoundException("Text", "x")); MockHttpRequest request = MockHttpRequest .get(FILE_HISTORY_URL + "revision/a.txt") diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java index cca7493162..311bff194a 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/SourceRootResourceTest.java @@ -10,10 +10,10 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.NotFoundException; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.api.BrowseCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; @@ -81,8 +81,8 @@ public class SourceRootResourceTest extends RepositoryTestBase { } @Test - public void shouldReturn404IfRepoNotFound() throws URISyntaxException, RepositoryNotFoundException { - when(serviceFactory.create(new NamespaceAndName("idont", "exist"))).thenThrow(new RepositoryNotFoundException("abc")); + public void shouldReturn404IfRepoNotFound() throws URISyntaxException { + when(serviceFactory.create(new NamespaceAndName("idont", "exist"))).thenThrow(new NotFoundException("Test", "a")); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "idont/exist/sources"); MockHttpResponse response = new MockHttpResponse(); @@ -109,8 +109,8 @@ public class SourceRootResourceTest extends RepositoryTestBase { } @Test - public void shouldGet404ForSingleFileIfRepoNotFound() throws URISyntaxException, RepositoryNotFoundException { - when(serviceFactory.create(new NamespaceAndName("idont", "exist"))).thenThrow(new RepositoryNotFoundException("abc")); + public void shouldGet404ForSingleFileIfRepoNotFound() throws URISyntaxException { + when(serviceFactory.create(new NamespaceAndName("idont", "exist"))).thenThrow(new NotFoundException("Test", "a")); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "idont/exist/sources/revision/fileabc"); MockHttpResponse response = new MockHttpResponse(); 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 4a28c3bc83..8876903637 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java @@ -353,7 +353,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { manager.refresh(heartOfGold); } - @Test(expected = RepositoryNotFoundException.class) + @Test(expected = NotFoundException.class) public void testRefreshNotFound(){ manager.refresh(createRepositoryWithId()); } @@ -383,7 +383,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { RepositoryManager repoManager = createRepositoryManager(false); Repository repository = spy(createTestRepository()); repository.setName("Testrepo"); - ((DefaultRepositoryManager) repoManager).create(repository); + repoManager.create(repository); assertEquals("default_namespace", repository.getNamespace()); } diff --git a/scm-webapp/src/test/java/sonia/scm/web/protocol/HttpProtocolServletTest.java b/scm-webapp/src/test/java/sonia/scm/web/protocol/HttpProtocolServletTest.java index c22491d045..1bd6358c95 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/protocol/HttpProtocolServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/protocol/HttpProtocolServletTest.java @@ -4,11 +4,11 @@ import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import sonia.scm.NotFoundException; import sonia.scm.PushStateDispatcher; import sonia.scm.repository.DefaultRepositoryProvider; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.repository.spi.HttpScmProtocol; @@ -58,12 +58,12 @@ public class HttpProtocolServletTest { private HttpScmProtocol protocol; @Before - public void init() throws RepositoryNotFoundException { + public void init() { initMocks(this); when(userAgentParser.parse(request)).thenReturn(userAgent); when(userAgent.isBrowser()).thenReturn(false); NamespaceAndName existingRepo = new NamespaceAndName("space", "repo"); - when(serviceFactory.create(not(eq(existingRepo)))).thenThrow(new RepositoryNotFoundException("x")); + when(serviceFactory.create(not(eq(existingRepo)))).thenThrow(new NotFoundException("Test", "a")); when(serviceFactory.create(existingRepo)).thenReturn(repositoryService); when(requestProvider.get()).thenReturn(httpServletRequest); } @@ -97,7 +97,7 @@ public class HttpProtocolServletTest { } @Test - public void shouldDelegateToProvider() throws RepositoryNotFoundException, IOException, ServletException { + public void shouldDelegateToProvider() throws IOException, ServletException { when(request.getPathInfo()).thenReturn("/space/name"); NamespaceAndName namespaceAndName = new NamespaceAndName("space", "name"); doReturn(repositoryService).when(serviceFactory).create(namespaceAndName); From a36768fbb3f94d838e6a5c56bc2f228a70399bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 25 Oct 2018 10:47:44 +0200 Subject: [PATCH 03/27] Fix typo --- .../src/main/java/sonia/scm/filter/MDCFilter.java | 13 ++++++------- .../test/java/sonia/scm/filter/MDCFilterTest.java | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java index 9198c19ef7..ffc976198c 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java @@ -34,7 +34,6 @@ package sonia.scm.filter; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.annotations.VisibleForTesting; -import com.google.inject.Singleton; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; @@ -65,11 +64,11 @@ public class MDCFilter extends HttpFilter /** Field description */ @VisibleForTesting - static final String MDC_CLIEN_HOST = "client_host"; + static final String MDC_CLIENT_HOST = "client_host"; /** Field description */ @VisibleForTesting - static final String MDC_CLIEN_IP = "client_ip"; + static final String MDC_CLIENT_IP = "client_ip"; /** url of the current request */ @VisibleForTesting @@ -102,8 +101,8 @@ public class MDCFilter extends HttpFilter throws IOException, ServletException { MDC.put(MDC_USERNAME, getUsername()); - MDC.put(MDC_CLIEN_IP, request.getRemoteAddr()); - MDC.put(MDC_CLIEN_HOST, request.getRemoteHost()); + MDC.put(MDC_CLIENT_IP, request.getRemoteAddr()); + MDC.put(MDC_CLIENT_HOST, request.getRemoteHost()); MDC.put(MDC_REQUEST_METHOD, request.getMethod()); MDC.put(MDC_REQUEST_URI, request.getRequestURI()); @@ -114,8 +113,8 @@ public class MDCFilter extends HttpFilter finally { MDC.remove(MDC_USERNAME); - MDC.remove(MDC_CLIEN_IP); - MDC.remove(MDC_CLIEN_HOST); + MDC.remove(MDC_CLIENT_IP); + MDC.remove(MDC_CLIENT_HOST); MDC.remove(MDC_REQUEST_METHOD); MDC.remove(MDC_REQUEST_URI); } diff --git a/scm-webapp/src/test/java/sonia/scm/filter/MDCFilterTest.java b/scm-webapp/src/test/java/sonia/scm/filter/MDCFilterTest.java index ac1b7335fc..56e1a74572 100644 --- a/scm-webapp/src/test/java/sonia/scm/filter/MDCFilterTest.java +++ b/scm-webapp/src/test/java/sonia/scm/filter/MDCFilterTest.java @@ -98,8 +98,8 @@ public class MDCFilterTest extends AbstractTestBase { assertNotNull(chain.ctx); assertEquals("trillian", chain.ctx.get(MDCFilter.MDC_USERNAME)); assertEquals("api/v1/repositories", chain.ctx.get(MDCFilter.MDC_REQUEST_URI)); - assertEquals("127.0.0.1", chain.ctx.get(MDCFilter.MDC_CLIEN_IP)); - assertEquals("localhost", chain.ctx.get(MDCFilter.MDC_CLIEN_HOST)); + assertEquals("127.0.0.1", chain.ctx.get(MDCFilter.MDC_CLIENT_IP)); + assertEquals("localhost", chain.ctx.get(MDCFilter.MDC_CLIENT_HOST)); assertEquals("GET", chain.ctx.get(MDCFilter.MDC_REQUEST_METHOD)); } From bda5b4127114fd96982d19d97c6f18b92b2e1c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 25 Oct 2018 12:34:32 +0200 Subject: [PATCH 04/27] Add transaction id to http request --- .../sonia/scm/api/v2/resources/ErrorDto.java | 3 ++- .../main/java/sonia/scm/filter/MDCFilter.java | 17 ++++++++++------- .../src/main/resources/logback.default.xml | 2 +- .../src/main/resources/logback.release.xml | 4 ++-- .../java/sonia/scm/filter/MDCFilterTest.java | 1 + 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java index 8e97eabf8e..3ebcdc6e8e 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java @@ -2,6 +2,7 @@ package sonia.scm.api.v2.resources; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Getter; +import org.slf4j.MDC; import sonia.scm.ContextEntry; import sonia.scm.NotFoundException; @@ -29,6 +30,6 @@ public class ErrorDto { } static ErrorDto from(NotFoundException notFoundException) { - return new ErrorDto("todo", "todo", notFoundException.getContext(), notFoundException.getMessage()); + return new ErrorDto(MDC.get("transaction_id"), "todo", notFoundException.getContext(), notFoundException.getMessage()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java index ffc976198c..b77a927a2d 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/MDCFilter.java @@ -41,6 +41,8 @@ import org.apache.shiro.subject.Subject; import org.slf4j.MDC; import sonia.scm.SCMContext; +import sonia.scm.security.DefaultKeyGenerator; +import sonia.scm.security.KeyGenerator; import sonia.scm.web.filter.HttpFilter; //~--- JDK imports ------------------------------------------------------------ @@ -61,27 +63,26 @@ import sonia.scm.Priority; @WebElement(Filters.PATTERN_ALL) public class MDCFilter extends HttpFilter { + private static final DefaultKeyGenerator TRANSACTION_KEY_GENERATOR = new DefaultKeyGenerator(); - /** Field description */ @VisibleForTesting static final String MDC_CLIENT_HOST = "client_host"; - /** Field description */ @VisibleForTesting static final String MDC_CLIENT_IP = "client_ip"; - - /** url of the current request */ + @VisibleForTesting static final String MDC_REQUEST_URI = "request_uri"; - - /** request method */ + @VisibleForTesting static final String MDC_REQUEST_METHOD = "request_method"; - /** Field description */ @VisibleForTesting static final String MDC_USERNAME = "username"; + @VisibleForTesting + static final String MDC_TRANSACTION_ID = "transaction_id"; + //~--- methods -------------------------------------------------------------- /** @@ -105,6 +106,7 @@ public class MDCFilter extends HttpFilter MDC.put(MDC_CLIENT_HOST, request.getRemoteHost()); MDC.put(MDC_REQUEST_METHOD, request.getMethod()); MDC.put(MDC_REQUEST_URI, request.getRequestURI()); + MDC.put(MDC_TRANSACTION_ID, TRANSACTION_KEY_GENERATOR.createKey()); try { @@ -117,6 +119,7 @@ public class MDCFilter extends HttpFilter MDC.remove(MDC_CLIENT_HOST); MDC.remove(MDC_REQUEST_METHOD); MDC.remove(MDC_REQUEST_URI); + MDC.remove(MDC_TRANSACTION_ID); } } diff --git a/scm-webapp/src/main/resources/logback.default.xml b/scm-webapp/src/main/resources/logback.default.xml index 33e914d04d..d03c5a5012 100644 --- a/scm-webapp/src/main/resources/logback.default.xml +++ b/scm-webapp/src/main/resources/logback.default.xml @@ -46,7 +46,7 @@ - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-10X{transaction_id}] %-5level %logger - %msg%n diff --git a/scm-webapp/src/main/resources/logback.release.xml b/scm-webapp/src/main/resources/logback.release.xml index 4cf5906581..03ac987992 100644 --- a/scm-webapp/src/main/resources/logback.release.xml +++ b/scm-webapp/src/main/resources/logback.release.xml @@ -61,14 +61,14 @@ true - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-10X{transaction_id}] %-5level %logger - %msg%n - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-10X{transaction_id}] %-5level %logger - %msg%n diff --git a/scm-webapp/src/test/java/sonia/scm/filter/MDCFilterTest.java b/scm-webapp/src/test/java/sonia/scm/filter/MDCFilterTest.java index 56e1a74572..efd3dbbbc0 100644 --- a/scm-webapp/src/test/java/sonia/scm/filter/MDCFilterTest.java +++ b/scm-webapp/src/test/java/sonia/scm/filter/MDCFilterTest.java @@ -101,6 +101,7 @@ public class MDCFilterTest extends AbstractTestBase { assertEquals("127.0.0.1", chain.ctx.get(MDCFilter.MDC_CLIENT_IP)); assertEquals("localhost", chain.ctx.get(MDCFilter.MDC_CLIENT_HOST)); assertEquals("GET", chain.ctx.get(MDCFilter.MDC_REQUEST_METHOD)); + assertNotNull(chain.ctx.get(MDCFilter.MDC_TRANSACTION_ID)); } /** From d185743ef057d229fbadbba3b55dac213071037e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 25 Oct 2018 13:48:00 +0200 Subject: [PATCH 05/27] Create error response for concurrent modifications --- .../scm/ConcurrentModificationException.java | 32 ++++++++++++++++++- .../java/sonia/scm/NotFoundException.java | 8 ++--- .../sonia/scm/user/UserManagerTestBase.java | 8 ++--- ...ConcurrentModificationExceptionMapper.java | 4 ++- .../sonia/scm/api/v2/resources/ErrorDto.java | 5 +++ .../scm/api/v2/resources/GroupResource.java | 2 +- .../resources/IdResourceManagerAdapter.java | 2 +- .../api/v2/resources/RepositoryResource.java | 5 +-- .../SingleResourceManagerAdapter.java | 6 ++-- .../scm/api/v2/resources/UserResource.java | 2 +- 10 files changed, 57 insertions(+), 17 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java b/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java index f0340a6a59..f0129093a3 100644 --- a/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java +++ b/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java @@ -1,4 +1,34 @@ package sonia.scm; -public class ConcurrentModificationException extends Exception { +import java.util.Collections; +import java.util.List; + +import static java.util.Collections.unmodifiableList; +import static java.util.stream.Collectors.joining; + +public class ConcurrentModificationException extends RuntimeException { + private final List context; + + public ConcurrentModificationException(Class type, String id) { + this(Collections.singletonList(new ContextEntry(type, id))); + } + + public ConcurrentModificationException(String type, String id) { + this(Collections.singletonList(new ContextEntry(type, id))); + } + + private ConcurrentModificationException(List context) { + super(createMessage(context)); + this.context = context; + } + + public List getContext() { + return unmodifiableList(context); + } + + private static String createMessage(List context) { + return context.stream() + .map(c -> c.getType().toLowerCase() + " with id " + c.getId()) + .collect(joining(" in ", "", " has been modified concurrently")); + } } diff --git a/scm-core/src/main/java/sonia/scm/NotFoundException.java b/scm-core/src/main/java/sonia/scm/NotFoundException.java index d562a674bb..36b4d37cb1 100644 --- a/scm-core/src/main/java/sonia/scm/NotFoundException.java +++ b/scm-core/src/main/java/sonia/scm/NotFoundException.java @@ -15,14 +15,15 @@ public class NotFoundException extends RuntimeException { private final List context; public NotFoundException(Class type, String id) { - this.context = Collections.singletonList(new ContextEntry(type, id)); + this(Collections.singletonList(new ContextEntry(type, id))); } public NotFoundException(String type, String id) { - this.context = Collections.singletonList(new ContextEntry(type, id)); + this(Collections.singletonList(new ContextEntry(type, id))); } private NotFoundException(List context) { + super(createMessage(context)); this.context = context; } @@ -46,8 +47,7 @@ public class NotFoundException extends RuntimeException { return unmodifiableList(context); } - @Override - public String getMessage() { + private static String createMessage(List context) { return context.stream() .map(c -> c.getType().toLowerCase() + " with id " + c.getId()) .collect(joining(" in ", "could not find ", "")); diff --git a/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java b/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java index dddce344ce..9fe8669fc9 100644 --- a/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java +++ b/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java @@ -97,7 +97,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase { } @Test(expected = NotFoundException.class) - public void testDeleteNotFound() throws Exception { + public void testDeleteNotFound() { manager.delete(UserTestData.createDent()); } @@ -181,7 +181,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase { } @Test - public void testModify() throws AlreadyExistsException, NotFoundException, ConcurrentModificationException { + public void testModify() throws AlreadyExistsException { User zaphod = UserTestData.createZaphod(); manager.create(zaphod); @@ -238,7 +238,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase { } @Test - public void testRefresh() throws AlreadyExistsException, NotFoundException { + public void testRefresh() throws AlreadyExistsException { User zaphod = UserTestData.createZaphod(); manager.create(zaphod); @@ -299,7 +299,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase { return user; } - private void modifyAndDeleteUser(User user) throws IOException, NotFoundException, ConcurrentModificationException { + private void modifyAndDeleteUser(User user) { String name = user.getName(); String nd = name.concat(" new displayname"); diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/ConcurrentModificationExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/rest/ConcurrentModificationExceptionMapper.java index d01b2fd7de..49aed6a29a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/ConcurrentModificationExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/ConcurrentModificationExceptionMapper.java @@ -1,6 +1,8 @@ package sonia.scm.api.rest; import sonia.scm.ConcurrentModificationException; +import sonia.scm.api.v2.resources.ErrorDto; +import sonia.scm.web.VndMediaType; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; @@ -10,6 +12,6 @@ import javax.ws.rs.ext.Provider; public class ConcurrentModificationExceptionMapper implements ExceptionMapper { @Override public Response toResponse(ConcurrentModificationException exception) { - return Response.status(Response.Status.CONFLICT).build(); + return Response.status(Response.Status.CONFLICT).entity(ErrorDto.from(exception)).type(VndMediaType.ERROR_TYPE).build(); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java index 3ebcdc6e8e..3b43c37c5a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java @@ -3,6 +3,7 @@ package sonia.scm.api.v2.resources; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Getter; import org.slf4j.MDC; +import sonia.scm.ConcurrentModificationException; import sonia.scm.ContextEntry; import sonia.scm.NotFoundException; @@ -32,4 +33,8 @@ public class ErrorDto { static ErrorDto from(NotFoundException notFoundException) { return new ErrorDto(MDC.get("transaction_id"), "todo", notFoundException.getContext(), notFoundException.getMessage()); } + + public static ErrorDto from(ConcurrentModificationException concurrentModificationException) { + return new ErrorDto(MDC.get("transaction_id"), "todo", concurrentModificationException.getContext(), concurrentModificationException.getMessage()); + } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java index 789a9847cb..308b40b1a5 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java @@ -98,7 +98,7 @@ public class GroupResource { @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) - public Response update(@PathParam("id") String name, @Valid GroupDto groupDto) throws ConcurrentModificationException { + public Response update(@PathParam("id") String name, @Valid GroupDto groupDto) { return adapter.update(name, existing -> dtoToGroupMapper.map(groupDto)); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IdResourceManagerAdapter.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IdResourceManagerAdapter.java index 533bc49da0..d2e4bd856a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IdResourceManagerAdapter.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IdResourceManagerAdapter.java @@ -39,7 +39,7 @@ class IdResourceManagerAdapter applyChanges) throws ConcurrentModificationException { + public Response update(String id, Function applyChanges) { return singleAdapter.update( loadBy(id), applyChanges, diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java index 77df2d8e50..f8b56229b1 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java @@ -138,7 +138,7 @@ public class RepositoryResource { @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) - public Response update(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid RepositoryDto repositoryDto) throws ConcurrentModificationException { + public Response update(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid RepositoryDto repositoryDto) { return adapter.update( loadBy(namespace, name), existing -> processUpdate(repositoryDto, existing), @@ -204,7 +204,8 @@ public class RepositoryResource { } private Supplier loadBy(String namespace, String name) { - return () -> Optional.ofNullable(manager.get(new NamespaceAndName(namespace, name))).orElseThrow(() -> new NotFoundException(Repository.class, namespace + "/" + name)); + NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name); + return () -> Optional.ofNullable(manager.get(namespaceAndName)).orElseThrow(() -> NotFoundException.notFound(namespaceAndName).build()); } private Predicate nameAndNamespaceStaysTheSame(String namespace, String name) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SingleResourceManagerAdapter.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SingleResourceManagerAdapter.java index 334dc72b20..963f25eaf6 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SingleResourceManagerAdapter.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SingleResourceManagerAdapter.java @@ -33,6 +33,7 @@ class SingleResourceManagerAdapter extends AbstractManagerResource { private final Function> errorHandler; + private final Class type; SingleResourceManagerAdapter(Manager manager, Class type) { this(manager, type, e -> Optional.empty()); @@ -44,6 +45,7 @@ class SingleResourceManagerAdapter> errorHandler) { super(manager, type); this.errorHandler = errorHandler; + this.type = type; } /** @@ -58,14 +60,14 @@ class SingleResourceManagerAdapter reader, Function applyChanges, Predicate hasSameKey) throws ConcurrentModificationException { + Response update(Supplier reader, Function applyChanges, Predicate hasSameKey) { MODEL_OBJECT existingModelObject = reader.get(); MODEL_OBJECT changedModelObject = applyChanges.apply(existingModelObject); if (!hasSameKey.test(changedModelObject)) { return Response.status(BAD_REQUEST).entity("illegal change of id").build(); } else if (modelObjectWasModifiedConcurrently(existingModelObject, changedModelObject)) { - throw new ConcurrentModificationException(); + throw new ConcurrentModificationException(type, existingModelObject.getId()); } return update(getId(existingModelObject), changedModelObject); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java index ef4b8a6eb4..c2fbc00453 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java @@ -101,7 +101,7 @@ public class UserResource { @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) - public Response update(@PathParam("id") String name, @Valid UserDto userDto) throws ConcurrentModificationException { + public Response update(@PathParam("id") String name, @Valid UserDto userDto) { return adapter.update(name, existing -> dtoToUserMapper.map(userDto, existing.getPassword())); } From 6eb3c386556f4724e1f837919bb0cb87fb1af07d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 25 Oct 2018 15:31:42 +0200 Subject: [PATCH 06/27] Create error response for already existing entities --- .../sonia/scm/AlreadyExistsException.java | 32 +++++++-- .../scm/ConcurrentModificationException.java | 2 +- .../src/main/java/sonia/scm/ContextEntry.java | 65 ++++++++++++++++--- .../java/sonia/scm/ExceptionWithContext.java | 9 +++ .../src/main/java/sonia/scm/HandlerBase.java | 10 +-- scm-core/src/main/java/sonia/scm/Manager.java | 2 +- .../main/java/sonia/scm/ManagerDecorator.java | 2 +- .../java/sonia/scm/NotFoundException.java | 49 ++------------ .../scm/repository/AbstactImportHandler.java | 3 +- .../AbstractSimpleRepositoryHandler.java | 14 ++-- .../scm/repository/RepositoryManager.java | 3 +- .../RepositoryManagerDecorator.java | 3 +- .../repository/api/BrowseCommandBuilder.java | 1 - .../api/RepositoryServiceFactory.java | 5 +- .../scm/repository/spi/BrowseCommand.java | 1 - .../scm/repository/spi/HookEventFacade.java | 6 +- .../scm/security/SyncingRealmHelperTest.java | 9 ++- .../scm/repository/spi/GitBrowseCommand.java | 4 +- .../scm/repository/spi/GitCatCommand.java | 8 ++- .../scm/repository/spi/GitLogCommand.java | 6 +- .../repository/spi/GitBrowseCommandTest.java | 5 +- .../java/sonia/scm/repository/SvnUtil.java | 8 ++- .../scm/repository/spi/SvnCatCommand.java | 8 ++- .../repository/DummyRepositoryHandler.java | 4 +- .../SimpleRepositoryHandlerTestBase.java | 9 ++- .../sonia/scm/user/UserManagerTestBase.java | 18 +++-- .../java/sonia/scm/ManagerDaoAdapter.java | 6 +- .../rest/AlreadyExistsExceptionMapper.java | 5 +- .../resources/ChangePasswordResource.java | 2 - .../resources/RepositoryImportResource.java | 8 --- .../NotFoundExceptionMapper.java | 4 +- .../api/v2/resources/BranchRootResource.java | 5 +- .../CollectionResourceManagerAdapter.java | 3 +- .../sonia/scm/api/v2/resources/ErrorDto.java | 11 +--- .../v2/resources/GroupCollectionResource.java | 3 +- .../scm/api/v2/resources/GroupResource.java | 2 - .../resources/IdResourceManagerAdapter.java | 4 +- .../v2/resources/PermissionRootResource.java | 26 ++++---- .../RepositoryCollectionResource.java | 3 +- .../api/v2/resources/RepositoryResource.java | 7 +- .../SingleResourceManagerAdapter.java | 1 - .../scm/api/v2/resources/TagRootResource.java | 5 +- .../v2/resources/UserCollectionResource.java | 3 +- .../scm/api/v2/resources/UserResource.java | 1 - .../sonia/scm/group/DefaultGroupManager.java | 3 +- .../repository/DefaultRepositoryManager.java | 11 ++-- .../sonia/scm/repository/HealthChecker.java | 1 - .../sonia/scm/user/DefaultUserManager.java | 3 +- .../java/sonia/scm/web/i18n/I18nServlet.java | 5 +- .../resources/ChangesetRootResourceTest.java | 1 + .../api/v2/resources/DiffResourceTest.java | 1 + .../scm/api/v2/resources/DispatcherMock.java | 1 + .../v2/resources/FileHistoryResourceTest.java | 1 + .../resources/ModificationsResourceTest.java | 1 + .../resources/PermissionRootResourceTest.java | 10 +-- .../api/v2/resources/TagRootResourceTest.java | 1 + .../DefaultRepositoryManagerTest.java | 48 +++++++------- .../scm/user/DefaultUserManagerTest.java | 3 - 58 files changed, 249 insertions(+), 226 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/ExceptionWithContext.java rename scm-webapp/src/main/java/sonia/scm/api/v2/{resources => }/NotFoundExceptionMapper.java (96%) diff --git a/scm-core/src/main/java/sonia/scm/AlreadyExistsException.java b/scm-core/src/main/java/sonia/scm/AlreadyExistsException.java index 4c6a52b878..95fc24ecb2 100644 --- a/scm-core/src/main/java/sonia/scm/AlreadyExistsException.java +++ b/scm-core/src/main/java/sonia/scm/AlreadyExistsException.java @@ -1,11 +1,35 @@ package sonia.scm; -public class AlreadyExistsException extends Exception { +import java.util.List; - public AlreadyExistsException(String message) { - super(message); +import static java.util.Collections.singletonList; +import static java.util.Collections.unmodifiableList; +import static java.util.stream.Collectors.joining; + +public class AlreadyExistsException extends RuntimeException implements ExceptionWithContext { + + private final List context; + + public AlreadyExistsException(ModelObject object) { + this(singletonList(new ContextEntry(object.getClass(), object.getId()))); } - public AlreadyExistsException() { + public static AlreadyExistsException alreadyExists(ContextEntry.ContextBuilder builder) { + return new AlreadyExistsException(builder.build()); + } + + private AlreadyExistsException(List context) { + super(createMessage(context)); + this.context = context; + } + + public List getContext() { + return unmodifiableList(context); + } + + private static String createMessage(List context) { + return context.stream() + .map(c -> c.getType().toLowerCase() + " with id " + c.getId()) + .collect(joining(" in ", "", " already exists")); } } diff --git a/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java b/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java index f0129093a3..2a3e65781d 100644 --- a/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java +++ b/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java @@ -6,7 +6,7 @@ import java.util.List; import static java.util.Collections.unmodifiableList; import static java.util.stream.Collectors.joining; -public class ConcurrentModificationException extends RuntimeException { +public class ConcurrentModificationException extends RuntimeException implements ExceptionWithContext { private final List context; public ConcurrentModificationException(Class type, String id) { diff --git a/scm-core/src/main/java/sonia/scm/ContextEntry.java b/scm-core/src/main/java/sonia/scm/ContextEntry.java index 2dbccbacf7..591f29972a 100644 --- a/scm-core/src/main/java/sonia/scm/ContextEntry.java +++ b/scm-core/src/main/java/sonia/scm/ContextEntry.java @@ -1,7 +1,13 @@ package sonia.scm; +import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.Repository; import sonia.scm.util.AssertUtil; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + public class ContextEntry { private final String type; private final String id; @@ -11,17 +17,60 @@ public class ContextEntry { } ContextEntry(String type, String id) { - AssertUtil.assertIsNotEmpty(type); - AssertUtil.assertIsNotEmpty(id); - this.type = type; - this.id = id; + AssertUtil.assertIsNotEmpty(type); + AssertUtil.assertIsNotEmpty(id); + this.type = type; + this.id = id; + } + + public String getType() { + return type; + } + + public String getId() { + return id; + } + + + public static class ContextBuilder { + private final List context = new LinkedList<>(); + + public static ContextBuilder entity(Repository repository) { + return new ContextBuilder().in(repository.getNamespaceAndName()); } - public String getType () { - return type; + public static ContextBuilder entity(NamespaceAndName namespaceAndName) { + return new ContextBuilder().in(Repository.class, namespaceAndName.logString()); } - public String getId () { - return id; + public static ContextBuilder entity(Class type, String id) { + return new ContextBuilder().in(type, id); + } + + public static ContextBuilder entity(String type, String id) { + return new ContextBuilder().in(type, id); + } + + public ContextBuilder in(Repository repository) { + return in(repository.getNamespaceAndName()); + } + + public ContextBuilder in(NamespaceAndName namespaceAndName) { + return this.in(Repository.class, namespaceAndName.logString()); + } + + public ContextBuilder in(Class type, String id) { + context.add(new ContextEntry(type, id)); + return this; + } + + public ContextBuilder in(String type, String id) { + context.add(new ContextEntry(type, id)); + return this; + } + + public List build() { + return Collections.unmodifiableList(context); } } +} diff --git a/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java b/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java new file mode 100644 index 0000000000..d10411ca9a --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java @@ -0,0 +1,9 @@ +package sonia.scm; + +import java.util.List; + +public interface ExceptionWithContext { + List getContext(); + + String getMessage(); +} diff --git a/scm-core/src/main/java/sonia/scm/HandlerBase.java b/scm-core/src/main/java/sonia/scm/HandlerBase.java index bbe78c8cf7..a621f4f697 100644 --- a/scm-core/src/main/java/sonia/scm/HandlerBase.java +++ b/scm-core/src/main/java/sonia/scm/HandlerBase.java @@ -54,25 +54,21 @@ public interface HandlerBase * * @return The persisted object. */ - T create(T object) throws AlreadyExistsException; + T create(T object); /** * Removes a persistent object. * * * @param object to delete - * - * @throws IOException */ - void delete(T object) throws NotFoundException; + void delete(T object); /** * Modifies a persistent object. * * * @param object to modify - * - * @throws IOException */ - void modify(T object) throws NotFoundException; + void modify(T object); } diff --git a/scm-core/src/main/java/sonia/scm/Manager.java b/scm-core/src/main/java/sonia/scm/Manager.java index 20b3a16a8e..390777958d 100644 --- a/scm-core/src/main/java/sonia/scm/Manager.java +++ b/scm-core/src/main/java/sonia/scm/Manager.java @@ -58,7 +58,7 @@ public interface Manager * * @throws NotFoundException */ - void refresh(T object) throws NotFoundException; + void refresh(T object); //~--- get methods ---------------------------------------------------------- diff --git a/scm-core/src/main/java/sonia/scm/ManagerDecorator.java b/scm-core/src/main/java/sonia/scm/ManagerDecorator.java index ef20a374cb..f6e91aeced 100644 --- a/scm-core/src/main/java/sonia/scm/ManagerDecorator.java +++ b/scm-core/src/main/java/sonia/scm/ManagerDecorator.java @@ -66,7 +66,7 @@ public class ManagerDecorator implements Manager { } @Override - public T create(T object) throws AlreadyExistsException { + public T create(T object) { return decorated.create(object); } diff --git a/scm-core/src/main/java/sonia/scm/NotFoundException.java b/scm-core/src/main/java/sonia/scm/NotFoundException.java index 36b4d37cb1..110402043d 100644 --- a/scm-core/src/main/java/sonia/scm/NotFoundException.java +++ b/scm-core/src/main/java/sonia/scm/NotFoundException.java @@ -10,7 +10,7 @@ import java.util.List; import static java.util.Collections.unmodifiableList; import static java.util.stream.Collectors.joining; -public class NotFoundException extends RuntimeException { +public class NotFoundException extends RuntimeException implements ExceptionWithContext { private final List context; @@ -22,27 +22,15 @@ public class NotFoundException extends RuntimeException { this(Collections.singletonList(new ContextEntry(type, id))); } + public static NotFoundException notFound(ContextEntry.ContextBuilder contextBuilder) { + return new NotFoundException(contextBuilder.build()); + } + private NotFoundException(List context) { super(createMessage(context)); this.context = context; } - public static NotFoundExceptionBuilder notFound(Repository repository) { - return new NotFoundExceptionBuilder().in(repository); - } - - public static NotFoundExceptionBuilder notFound(NamespaceAndName namespaceAndName) { - return new NotFoundExceptionBuilder().in(namespaceAndName); - } - - public static NotFoundExceptionBuilder notFound(Class type, String id) { - return new NotFoundExceptionBuilder().in(type, id); - } - - public static NotFoundExceptionBuilder notFound(String type, String id) { - return new NotFoundExceptionBuilder().in(type, id); - } - public List getContext() { return unmodifiableList(context); } @@ -52,31 +40,4 @@ public class NotFoundException extends RuntimeException { .map(c -> c.getType().toLowerCase() + " with id " + c.getId()) .collect(joining(" in ", "could not find ", "")); } - - public static class NotFoundExceptionBuilder { - private final List context = new LinkedList<>(); - - public NotFoundExceptionBuilder in(Repository repository) { - return in(repository.getNamespaceAndName()); - } - - public NotFoundExceptionBuilder in(NamespaceAndName namespaceAndName) { - return this.in(Repository.class, namespaceAndName.logString()); - } - - public NotFoundExceptionBuilder in(Class type, String id) { - context.add(new ContextEntry(type, id)); - return this; - } - - public NotFoundExceptionBuilder in(String type, String id) { - context.add(new ContextEntry(type, id)); - return this; - } - - public NotFoundException build() { - return new NotFoundException(context); - } - } - } diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java index 2195e87730..f4438807c6 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java @@ -37,7 +37,6 @@ package sonia.scm.repository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.AlreadyExistsException; import sonia.scm.repository.ImportResult.Builder; import java.io.File; @@ -243,7 +242,7 @@ public abstract class AbstactImportHandler implements AdvancedImportHandler */ private void importRepository(RepositoryManager manager, String repositoryName) - throws IOException, AlreadyExistsException { + throws IOException { Repository repository = createRepository(getRepositoryDirectory(repositoryName), repositoryName); diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java index a717cdf05b..997413aacb 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java @@ -81,11 +81,11 @@ public abstract class AbstractSimpleRepositoryHandler files = Lists.newArrayList(); while (treeWalk.next()) { @@ -335,7 +335,7 @@ public class GitBrowseCommand extends AbstractGitCommand } private FileObject findFirstMatch(org.eclipse.jgit.lib.Repository repo, - BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk) throws IOException, NotFoundException { + BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk) throws IOException { String[] pathElements = request.getPath().split("/"); int currentDepth = 0; int limit = pathElements.length; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java index 40f044ebde..7477e0aee3 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java @@ -44,7 +44,6 @@ import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.NotFoundException; import sonia.scm.repository.GitUtil; import sonia.scm.util.Util; @@ -54,6 +53,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; + public class GitCatCommand extends AbstractGitCommand implements CatCommand { @@ -101,7 +103,7 @@ public class GitCatCommand extends AbstractGitCommand implements CatCommand { try { entry = revWalk.parseCommit(revId); } catch (MissingObjectException e) { - throw NotFoundException.notFound("Revision", revId.getName()).in(repository).build(); + throw notFound(entity("Revision", revId.getName()).in(repository)); } RevTree revTree = entry.getTree(); @@ -119,7 +121,7 @@ public class GitCatCommand extends AbstractGitCommand implements CatCommand { return new ClosableObjectLoaderContainer(loader, treeWalk, revWalk); } else { - throw NotFoundException.notFound("Path", path).in("Revision", revId.getName()).in(repository).build(); + throw notFound(entity("Path", path).in("Revision", revId.getName()).in(repository)); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java index 40b25ef75a..8a3745dce7 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java @@ -48,7 +48,6 @@ import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.NotFoundException; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.GitChangesetConverter; @@ -61,6 +60,9 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; + //~--- JDK imports ------------------------------------------------------------ /** @@ -248,7 +250,7 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand } catch (MissingObjectException e) { - throw NotFoundException.notFound("Revision", e.getObjectId().getName()).in(repository).build(); + throw notFound(entity("Revision", e.getObjectId().getName()).in(repository)); } catch (Exception ex) { diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java index 22116eff01..5e63adfb70 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java @@ -32,7 +32,6 @@ package sonia.scm.repository.spi; import org.junit.Test; -import sonia.scm.NotFoundException; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.GitConstants; @@ -63,7 +62,7 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase { } @Test - public void testDefaultDefaultBranch() throws IOException, NotFoundException { + public void testDefaultDefaultBranch() throws IOException { // without default branch, the repository head should be used FileObject root = createCommand().getBrowserResult(new BrowseCommandRequest()).getFile(); assertNotNull(root); @@ -78,7 +77,7 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase { } @Test - public void testExplicitDefaultBranch() throws IOException, NotFoundException { + public void testExplicitDefaultBranch() throws IOException { repository.setProperty(GitConstants.PROPERTY_DEFAULT_BRANCH, "test-branch"); FileObject root = createCommand().getBrowserResult(new BrowseCommandRequest()).getFile(); diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnUtil.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnUtil.java index deff53eeac..b4be68a51f 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnUtil.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnUtil.java @@ -49,7 +49,6 @@ import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil; import org.tmatesoft.svn.core.internal.util.SVNXMLUtil; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.SVNClientManager; -import sonia.scm.NotFoundException; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; @@ -60,6 +59,9 @@ import java.io.PrintWriter; import java.util.List; import java.util.Map; +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; + //~--- JDK imports ------------------------------------------------------------ /** @@ -114,7 +116,7 @@ public final class SvnUtil } catch (NumberFormatException ex) { - throw NotFoundException.notFound("Revision", v).in(repository).build(); + throw notFound(entity("Revision", v).in(repository)); } } @@ -352,7 +354,7 @@ public final class SvnUtil } catch (NumberFormatException ex) { - throw NotFoundException.notFound("Revision", revision).in(repository).build(); + throw notFound(entity("Revision", revision).in(repository)); } } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnCatCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnCatCommand.java index 93d52ea907..9a5c39af46 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnCatCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnCatCommand.java @@ -43,7 +43,6 @@ import org.tmatesoft.svn.core.SVNProperties; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.SVNClientManager; import org.tmatesoft.svn.core.wc.admin.SVNLookClient; -import sonia.scm.NotFoundException; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; import sonia.scm.repository.SvnUtil; @@ -53,6 +52,9 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; + //~--- JDK imports ------------------------------------------------------------ /** @@ -131,9 +133,9 @@ public class SvnCatCommand extends AbstractSvnCommand implements CatCommand private void handleSvnException(CatCommandRequest request, SVNException ex) { int svnErrorCode = ex.getErrorMessage().getErrorCode().getCode(); if (SVNErrorCode.FS_NOT_FOUND.getCode() == svnErrorCode) { - throw NotFoundException.notFound("Path", request.getPath()).in("Revision", request.getRevision()).in(repository).build(); + throw notFound(entity("Path", request.getPath()).in("Revision", request.getRevision()).in(repository)); } else if (SVNErrorCode.FS_NO_SUCH_REVISION.getCode() == svnErrorCode) { - throw NotFoundException.notFound("Revision", request.getRevision()).in(repository).build(); + throw notFound(entity("Revision", request.getRevision()).in(repository)); } else { throw new InternalRepositoryException("could not get content from revision", ex); } diff --git a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java index 5a1dc605bb..df974ab566 100644 --- a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java +++ b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java @@ -71,10 +71,10 @@ public class DummyRepositoryHandler @Override - protected void create(Repository repository, File directory) throws AlreadyExistsException { + protected void create(Repository repository, File directory) { String key = repository.getNamespace() + "/" + repository.getName(); if (existingRepoNames.contains(key)) { - throw new AlreadyExistsException(); + throw new AlreadyExistsException(repository); } else { existingRepoNames.add(key); } diff --git a/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java b/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java index 999ef1dc45..d9985d3332 100644 --- a/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java +++ b/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java @@ -35,7 +35,6 @@ package sonia.scm.repository; import org.junit.Test; import sonia.scm.AbstractTestBase; -import sonia.scm.AlreadyExistsException; import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.InMemoryConfigurationStoreFactory; import sonia.scm.util.IOUtil; @@ -61,12 +60,12 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { ConfigurationStoreFactory factory, File directory); @Test - public void testCreate() throws AlreadyExistsException { + public void testCreate() { createRepository(); } @Test - public void testCreateResourcePath() throws AlreadyExistsException { + public void testCreateResourcePath() { Repository repository = createRepository(); String path = handler.createResourcePath(repository); @@ -76,7 +75,7 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { } @Test - public void testDelete() throws Exception { + public void testDelete() { Repository repository = createRepository(); handler.delete(repository); @@ -101,7 +100,7 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { } } - private Repository createRepository() throws AlreadyExistsException { + private Repository createRepository() { Repository repository = RepositoryTestData.createHeartOfGold(); handler.create(repository); diff --git a/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java b/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java index 9fe8669fc9..3c684f58c9 100644 --- a/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java +++ b/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java @@ -39,12 +39,10 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.junit.Test; import sonia.scm.AlreadyExistsException; -import sonia.scm.ConcurrentModificationException; import sonia.scm.Manager; import sonia.scm.ManagerTestBase; import sonia.scm.NotFoundException; -import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -63,7 +61,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase { public static final int THREAD_COUNT = 10; @Test - public void testCreate() throws AlreadyExistsException { + public void testCreate() { User zaphod = UserTestData.createZaphod(); manager.create(zaphod); @@ -75,7 +73,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase { } @Test(expected = AlreadyExistsException.class) - public void testCreateExisting() throws AlreadyExistsException { + public void testCreateExisting() { User zaphod = UserTestData.createZaphod(); manager.create(zaphod); @@ -87,7 +85,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase { } @Test - public void testDelete() throws Exception { + public void testDelete() { User zaphod = UserTestData.createZaphod(); manager.create(zaphod); @@ -102,7 +100,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase { } @Test - public void testGet() throws AlreadyExistsException { + public void testGet() { User zaphod = UserTestData.createZaphod(); manager.create(zaphod); @@ -116,7 +114,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase { } @Test - public void testGetAll() throws AlreadyExistsException { + public void testGetAll() { User zaphod = UserTestData.createZaphod(); manager.create(zaphod); @@ -181,7 +179,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase { } @Test - public void testModify() throws AlreadyExistsException { + public void testModify() { User zaphod = UserTestData.createZaphod(); manager.create(zaphod); @@ -238,7 +236,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase { } @Test - public void testRefresh() throws AlreadyExistsException { + public void testRefresh() { User zaphod = UserTestData.createZaphod(); manager.create(zaphod); @@ -289,7 +287,7 @@ public abstract class UserManagerTestBase extends ManagerTestBase { finished = true; } - private User createUser() throws AlreadyExistsException { + private User createUser() { String id = UUID.randomUUID().toString(); User user = new User(id, id.concat(" displayName"), id.concat("@mail.com")); diff --git a/scm-webapp/src/main/java/sonia/scm/ManagerDaoAdapter.java b/scm-webapp/src/main/java/sonia/scm/ManagerDaoAdapter.java index 4fdd74063e..fb5cd9f5cc 100644 --- a/scm-webapp/src/main/java/sonia/scm/ManagerDaoAdapter.java +++ b/scm-webapp/src/main/java/sonia/scm/ManagerDaoAdapter.java @@ -34,15 +34,15 @@ public class ManagerDaoAdapter { } } - public T create(T newObject, Supplier permissionCheck, AroundHandler beforeCreate, AroundHandler afterCreate) throws AlreadyExistsException { + public T create(T newObject, Supplier permissionCheck, AroundHandler beforeCreate, AroundHandler afterCreate) { return create(newObject, permissionCheck, beforeCreate, afterCreate, dao::contains); } - public T create(T newObject, Supplier permissionCheck, AroundHandler beforeCreate, AroundHandler afterCreate, Predicate existsCheck) throws AlreadyExistsException { + public T create(T newObject, Supplier permissionCheck, AroundHandler beforeCreate, AroundHandler afterCreate, Predicate existsCheck) { permissionCheck.get().check(); AssertUtil.assertIsValid(newObject); if (existsCheck.test(newObject)) { - throw new AlreadyExistsException(); + throw new AlreadyExistsException(newObject); } newObject.setCreationDate(System.currentTimeMillis()); beforeCreate.handle(newObject); diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/AlreadyExistsExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/rest/AlreadyExistsExceptionMapper.java index 13d341e1e6..f923eef693 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/AlreadyExistsExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/AlreadyExistsExceptionMapper.java @@ -1,6 +1,8 @@ package sonia.scm.api.rest; import sonia.scm.AlreadyExistsException; +import sonia.scm.api.v2.resources.ErrorDto; +import sonia.scm.web.VndMediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; @@ -12,7 +14,8 @@ public class AlreadyExistsExceptionMapper implements ExceptionMapper branchName.equals(branch.getName())); if (!branchExists){ - throw NotFoundException.notFound(Branch.class, branchName).in(Repository.class, namespace + "/" + name).build(); + throw notFound(entity(Branch.class, branchName).in(Repository.class, namespace + "/" + name)); } Repository repository = repositoryService.getRepository(); RepositoryPermissions.read(repository).check(); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/CollectionResourceManagerAdapter.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/CollectionResourceManagerAdapter.java index 3ef19f7294..052bf771b1 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/CollectionResourceManagerAdapter.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/CollectionResourceManagerAdapter.java @@ -1,7 +1,6 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.HalRepresentation; -import sonia.scm.AlreadyExistsException; import sonia.scm.Manager; import sonia.scm.ModelObject; import sonia.scm.PageResult; @@ -47,7 +46,7 @@ class CollectionResourceManagerAdapter modelObjectSupplier, Function uriCreator) throws AlreadyExistsException { + public Response create(DTO dto, Supplier modelObjectSupplier, Function uriCreator) { if (dto == null) { return Response.status(BAD_REQUEST).build(); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java index 3b43c37c5a..a047aece38 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java @@ -3,9 +3,8 @@ package sonia.scm.api.v2.resources; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Getter; import org.slf4j.MDC; -import sonia.scm.ConcurrentModificationException; import sonia.scm.ContextEntry; -import sonia.scm.NotFoundException; +import sonia.scm.ExceptionWithContext; import java.util.List; @@ -30,11 +29,7 @@ public class ErrorDto { this.url = url; } - static ErrorDto from(NotFoundException notFoundException) { - return new ErrorDto(MDC.get("transaction_id"), "todo", notFoundException.getContext(), notFoundException.getMessage()); - } - - public static ErrorDto from(ConcurrentModificationException concurrentModificationException) { - return new ErrorDto(MDC.get("transaction_id"), "todo", concurrentModificationException.getContext(), concurrentModificationException.getMessage()); + public static ErrorDto from(ExceptionWithContext exception) { + return new ErrorDto(MDC.get("transaction_id"), "todo", exception.getContext(), exception.getMessage()); } } 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 8abab0f720..d845aa181e 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 @@ -5,7 +5,6 @@ import com.webcohesion.enunciate.metadata.rs.ResponseHeader; import com.webcohesion.enunciate.metadata.rs.ResponseHeaders; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; -import sonia.scm.AlreadyExistsException; import sonia.scm.group.Group; import sonia.scm.group.GroupManager; import sonia.scm.web.VndMediaType; @@ -86,7 +85,7 @@ public class GroupCollectionResource { }) @TypeHint(TypeHint.NO_CONTENT.class) @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created group")) - public Response create(@Valid GroupDto groupDto) throws AlreadyExistsException { + public Response create(@Valid GroupDto groupDto) { return adapter.create(groupDto, () -> dtoToGroupMapper.map(groupDto), group -> resourceLinks.group().self(group.getName())); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java index 308b40b1a5..f2341262ce 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java @@ -3,8 +3,6 @@ package sonia.scm.api.v2.resources; import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; -import sonia.scm.ConcurrentModificationException; -import sonia.scm.NotFoundException; import sonia.scm.group.Group; import sonia.scm.group.GroupManager; import sonia.scm.web.VndMediaType; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IdResourceManagerAdapter.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IdResourceManagerAdapter.java index d2e4bd856a..2b2a4cf0ad 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IdResourceManagerAdapter.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IdResourceManagerAdapter.java @@ -1,8 +1,6 @@ package sonia.scm.api.v2.resources; import de.otto.edison.hal.HalRepresentation; -import sonia.scm.AlreadyExistsException; -import sonia.scm.ConcurrentModificationException; import sonia.scm.Manager; import sonia.scm.ModelObject; import sonia.scm.NotFoundException; @@ -51,7 +49,7 @@ class IdResourceManagerAdapter modelObjectSupplier, Function uriCreator) throws AlreadyExistsException { + public Response create(DTO dto, Supplier modelObjectSupplier, Function uriCreator) { return collectionAdapter.create(dto, modelObjectSupplier, uriCreator); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java index 4b088a62a5..c997a706fd 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java @@ -29,6 +29,9 @@ import java.net.URI; import java.util.Optional; import java.util.function.Predicate; +import static sonia.scm.AlreadyExistsException.alreadyExists; +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; import static sonia.scm.api.v2.resources.PermissionDto.GROUP_PREFIX; @Slf4j @@ -70,7 +73,7 @@ public class PermissionRootResource { @TypeHint(TypeHint.NO_CONTENT.class) @Consumes(VndMediaType.PERMISSION) @Path("") - public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name,@Valid PermissionDto permission) throws AlreadyExistsException, NotFoundException { + public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name,@Valid PermissionDto permission) { log.info("try to add new permission: {}", permission); Repository repository = load(namespace, name); RepositoryPermissions.permissionWrite(repository).check(); @@ -108,7 +111,7 @@ public class PermissionRootResource { .filter(filterPermission(permissionName)) .map(permission -> modelToDtoMapper.map(permission, repository)) .findFirst() - .orElseThrow(() -> NotFoundException.notFound(Permission.class, namespace).in(Repository.class, namespace + "/" + name).build()) + .orElseThrow(() -> notFound(entity(Permission.class, namespace).in(Repository.class, namespace + "/" + name))) ).build(); } @@ -157,23 +160,23 @@ public class PermissionRootResource { public Response update(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("permission-name") String permissionName, - @Valid PermissionDto permission) throws AlreadyExistsException { + @Valid PermissionDto permission) { log.info("try to update the permission with name: {}. the modified permission is: {}", permissionName, permission); Repository repository = load(namespace, name); RepositoryPermissions.permissionWrite(repository).check(); String extractedPermissionName = getPermissionName(permissionName); if (!isPermissionExist(new PermissionDto(extractedPermissionName, isGroupPermission(permissionName)), repository)) { - throw NotFoundException.notFound(Permission.class, namespace).in(Repository.class, namespace + "/" + name).build(); + throw notFound(entity(Permission.class, namespace).in(Repository.class, namespace + "/" + name)); } permission.setGroupPermission(isGroupPermission(permissionName)); if (!extractedPermissionName.equals(permission.getName())) { - checkPermissionAlreadyExists(permission, repository, "target permission " + permission.getName() + " already exists"); + checkPermissionAlreadyExists(permission, repository); } Permission existingPermission = repository.getPermissions() .stream() .filter(filterPermission(permissionName)) .findFirst() - .orElseThrow(() -> NotFoundException.notFound(Permission.class, namespace).in(Repository.class, namespace + "/" + name).build()); + .orElseThrow(() -> notFound(entity(Permission.class, namespace).in(Repository.class, namespace + "/" + name))); dtoToModelMapper.modify(existingPermission, permission); manager.modify(repository); log.info("the permission with name: {} is updated.", permissionName); @@ -241,7 +244,7 @@ public class PermissionRootResource { private Repository load(String namespace, String name) { NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name); return Optional.ofNullable(manager.get(namespaceAndName)) - .orElseThrow(() -> NotFoundException.notFound(namespaceAndName).build()); + .orElseThrow(() -> notFound(entity(namespaceAndName))); } /** @@ -249,12 +252,11 @@ public class PermissionRootResource { * * @param permission the searched permission * @param repository the repository to be inspected - * @param errorMessage error message * @throws AlreadyExistsException if the permission already exists in the repository */ - private void checkPermissionAlreadyExists(PermissionDto permission, Repository repository, String errorMessage) throws AlreadyExistsException { + private void checkPermissionAlreadyExists(PermissionDto permission, Repository repository) { if (isPermissionExist(permission, repository)) { - throw new AlreadyExistsException(errorMessage); + throw alreadyExists(entity("permission", permission.getName()).in(repository)); } } @@ -263,10 +265,6 @@ public class PermissionRootResource { .stream() .anyMatch(p -> p.getName().equals(permission.getName()) && p.isGroupPermission() == permission.isGroupPermission()); } - - private void checkPermissionAlreadyExists(PermissionDto permission, Repository repository) throws AlreadyExistsException { - checkPermissionAlreadyExists(permission, repository, "the permission " + permission.getName() + " already exist."); - } } 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 9f4858d2f6..665e2131d9 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 @@ -5,7 +5,6 @@ import com.webcohesion.enunciate.metadata.rs.ResponseHeader; import com.webcohesion.enunciate.metadata.rs.ResponseHeaders; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; -import sonia.scm.AlreadyExistsException; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; import sonia.scm.web.VndMediaType; @@ -87,7 +86,7 @@ public class RepositoryCollectionResource { }) @TypeHint(TypeHint.NO_CONTENT.class) @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created repository")) - public Response create(@Valid RepositoryDto repositoryDto) throws AlreadyExistsException { + public Response create(@Valid RepositoryDto repositoryDto) { return adapter.create(repositoryDto, () -> dtoToRepositoryMapper.map(repositoryDto, null), repository -> resourceLinks.repository().self(repository.getNamespace(), repository.getName())); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java index f8b56229b1..141cf64ff6 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java @@ -3,8 +3,6 @@ package sonia.scm.api.v2.resources; import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; -import sonia.scm.ConcurrentModificationException; -import sonia.scm.NotFoundException; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryIsNotArchivedException; @@ -26,6 +24,9 @@ import java.util.Optional; import java.util.function.Predicate; import java.util.function.Supplier; +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; + public class RepositoryResource { private final RepositoryToRepositoryDtoMapper repositoryToDtoMapper; @@ -205,7 +206,7 @@ public class RepositoryResource { private Supplier loadBy(String namespace, String name) { NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name); - return () -> Optional.ofNullable(manager.get(namespaceAndName)).orElseThrow(() -> NotFoundException.notFound(namespaceAndName).build()); + return () -> Optional.ofNullable(manager.get(namespaceAndName)).orElseThrow(() -> notFound(entity(namespaceAndName))); } private Predicate nameAndNamespaceStaysTheSame(String namespace, String name) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SingleResourceManagerAdapter.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SingleResourceManagerAdapter.java index 963f25eaf6..e61a9f3455 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SingleResourceManagerAdapter.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SingleResourceManagerAdapter.java @@ -11,7 +11,6 @@ import javax.ws.rs.core.GenericEntity; import javax.ws.rs.core.Response; import java.util.Collection; import java.util.Optional; -import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java index 632c428e62..7acd59e3e1 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java @@ -21,6 +21,9 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.Response; import java.io.IOException; +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; + public class TagRootResource { private final RepositoryServiceFactory serviceFactory; @@ -91,7 +94,7 @@ public class TagRootResource { } private NotFoundException createNotFoundException(String namespace, String name, String tagName) { - return NotFoundException.notFound("Tag", tagName).in("Repository", namespace + "/" + name).build(); + return notFound(entity("Tag", tagName).in("Repository", namespace + "/" + name)); } private Tags getTags(RepositoryService repositoryService) throws IOException { 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 9b39888104..e7a49bc1fd 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 @@ -6,7 +6,6 @@ import com.webcohesion.enunciate.metadata.rs.ResponseHeaders; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; import org.apache.shiro.authc.credential.PasswordService; -import sonia.scm.AlreadyExistsException; import sonia.scm.user.User; import sonia.scm.user.UserManager; import sonia.scm.web.VndMediaType; @@ -91,7 +90,7 @@ public class UserCollectionResource { }) @TypeHint(TypeHint.NO_CONTENT.class) @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created user")) - public Response create(@Valid UserDto userDto) throws AlreadyExistsException { + public Response create(@Valid UserDto userDto) { return adapter.create(userDto, () -> dtoToUserMapper.map(userDto, passwordService.encryptPassword(userDto.getPassword())), user -> resourceLinks.user().self(user.getName())); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java index c2fbc00453..d0ce8c8a63 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java @@ -4,7 +4,6 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; import org.apache.shiro.authc.credential.PasswordService; -import sonia.scm.ConcurrentModificationException; import sonia.scm.user.User; import sonia.scm.user.UserManager; import sonia.scm.web.VndMediaType; diff --git a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java index 662d9407ca..6bf850f99d 100644 --- a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java +++ b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java @@ -42,7 +42,6 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.AlreadyExistsException; import sonia.scm.HandlerEventType; import sonia.scm.ManagerDaoAdapter; import sonia.scm.NotFoundException; @@ -106,7 +105,7 @@ public class DefaultGroupManager extends AbstractGroupManager } @Override - public Group create(Group group) throws AlreadyExistsException { + public Group create(Group group) { String type = group.getType(); if (Util.isEmpty(type)) { group.setType(groupDAO.getType()); 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 dc4767e024..6c903815a7 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java @@ -65,6 +65,9 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; + /** * Default implementation of {@link RepositoryManager}. * @@ -122,11 +125,11 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { } @Override - public Repository create(Repository repository) throws AlreadyExistsException { + public Repository create(Repository repository) { return create(repository, true); } - public Repository create(Repository repository, boolean initRepository) throws AlreadyExistsException { + public Repository create(Repository repository, boolean initRepository) { repository.setId(keyGenerator.createKey()); repository.setNamespace(namespaceStrategy.createNamespace(repository)); @@ -170,7 +173,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { } @Override - public void importRepository(Repository repository) throws AlreadyExistsException { + public void importRepository(Repository repository) { create(repository, false); } @@ -207,7 +210,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { if (fresh != null) { fresh.copyProperties(repository); } else { - throw NotFoundException.notFound(repository).build(); + throw notFound(entity(repository)); } } diff --git a/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java b/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java index 61d488d641..8057db53dc 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java @@ -33,7 +33,6 @@ import com.google.common.collect.ImmutableList; import com.google.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.ConcurrentModificationException; import sonia.scm.NotFoundException; import java.util.Set; diff --git a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java index 0f2f45ff0b..807024c36b 100644 --- a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java +++ b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java @@ -39,7 +39,6 @@ import com.google.inject.Singleton; import org.apache.shiro.SecurityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.AlreadyExistsException; import sonia.scm.EagerSingleton; import sonia.scm.HandlerEventType; import sonia.scm.ManagerDaoAdapter; @@ -137,7 +136,7 @@ public class DefaultUserManager extends AbstractUserManager * @throws IOException */ @Override - public User create(User user) throws AlreadyExistsException { + public User create(User user) { String type = user.getType(); if (Util.isEmpty(type)) { user.setType(userDAO.getType()); diff --git a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java index f80b90e22a..b074781fec 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/web/i18n/I18nServlet.java @@ -30,6 +30,9 @@ import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Function; +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; + /** * Collect the plugin translations. @@ -69,7 +72,7 @@ public class I18nServlet extends HttpServlet { createdFile.ifPresent(map -> createdJsonFileConsumer.accept(path, map)); return createdFile.orElse(null); } - )).orElseThrow(() -> NotFoundException.notFound("jsonprovider", path).build()); + )).orElseThrow(() -> notFound(entity("jsonprovider", path))); } @VisibleForTesting diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java index 4e1a0f90f1..098ec40c3f 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java @@ -20,6 +20,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.api.rest.AuthorizationExceptionMapper; +import sonia.scm.api.v2.NotFoundExceptionMapper; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.NamespaceAndName; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java index e762e5d186..f1a1c3b441 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java @@ -19,6 +19,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.NotFoundException; import sonia.scm.api.rest.AuthorizationExceptionMapper; +import sonia.scm.api.v2.NotFoundExceptionMapper; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.api.DiffCommandBuilder; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java index 8a71abb670..7dff9f5ff0 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java @@ -6,6 +6,7 @@ import sonia.scm.api.rest.AlreadyExistsExceptionMapper; import sonia.scm.api.rest.AuthorizationExceptionMapper; import sonia.scm.api.rest.ConcurrentModificationExceptionMapper; import sonia.scm.api.rest.IllegalArgumentExceptionMapper; +import sonia.scm.api.v2.NotFoundExceptionMapper; public class DispatcherMock { public static Dispatcher createDispatcher(Object resource) { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java index 322995eaa7..2963f5d813 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java @@ -20,6 +20,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.NotFoundException; import sonia.scm.api.rest.AuthorizationExceptionMapper; +import sonia.scm.api.v2.NotFoundExceptionMapper; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.InternalRepositoryException; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java index 64942fb84b..0377fe0942 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java @@ -19,6 +19,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.api.rest.AuthorizationExceptionMapper; +import sonia.scm.api.v2.NotFoundExceptionMapper; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Modifications; import sonia.scm.repository.NamespaceAndName; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java index 635d994763..0487e445c1 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java @@ -164,10 +164,7 @@ public class PermissionRootResourceTest extends RepositoryTestBase { @TestFactory @DisplayName("test endpoints on missing permissions and user is Admin") Stream missedPermissionTestFactory() { - Repository mockRepository = mock(Repository.class); - when(mockRepository.getId()).thenReturn(REPOSITORY_NAME); - when(mockRepository.getNamespace()).thenReturn(REPOSITORY_NAMESPACE); - when(mockRepository.getName()).thenReturn(REPOSITORY_NAME); + Repository mockRepository = new Repository(REPOSITORY_NAME, "git", REPOSITORY_NAMESPACE, REPOSITORY_NAME); when(repositoryManager.get(any(NamespaceAndName.class))).thenReturn(mockRepository); return createDynamicTestsToAssertResponses( requestGETPermission.expectedResponseStatus(404), @@ -180,10 +177,6 @@ public class PermissionRootResourceTest extends RepositoryTestBase { @TestFactory @DisplayName("test endpoints on missing permissions and user is not Admin") Stream missedPermissionUserForbiddenTestFactory() { - Repository mockRepository = mock(Repository.class); - when(mockRepository.getId()).thenReturn(REPOSITORY_NAME); - when(mockRepository.getNamespace()).thenReturn(REPOSITORY_NAMESPACE); - when(mockRepository.getName()).thenReturn(REPOSITORY_NAME); doThrow(AuthorizationException.class).when(repositoryManager).get(any(NamespaceAndName.class)); return createDynamicTestsToAssertResponses( requestGETPermission.expectedResponseStatus(403), @@ -413,6 +406,7 @@ public class PermissionRootResourceTest extends RepositoryTestBase { when(mockRepository.getId()).thenReturn(REPOSITORY_NAME); when(mockRepository.getNamespace()).thenReturn(REPOSITORY_NAMESPACE); when(mockRepository.getName()).thenReturn(REPOSITORY_NAME); + when(mockRepository.getNamespaceAndName()).thenReturn(new NamespaceAndName(REPOSITORY_NAMESPACE, REPOSITORY_NAME)); when(repositoryManager.get(any(NamespaceAndName.class))).thenReturn(mockRepository); when(subject.isPermitted(userPermission != null ? eq(userPermission) : any(String.class))).thenReturn(true); return mockRepository; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java index 5f49f31183..803f2b106c 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagRootResourceTest.java @@ -18,6 +18,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.api.rest.AuthorizationExceptionMapper; +import sonia.scm.api.v2.NotFoundExceptionMapper; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.Tag; 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 8876903637..2b5aeb7f53 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java @@ -110,7 +110,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { private String mockedNamespace = "default_namespace"; @Test - public void testCreate() throws AlreadyExistsException { + public void testCreate() { Repository heartOfGold = createTestRepository(); Repository dbRepo = manager.get(heartOfGold.getId()); @@ -122,18 +122,18 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { username = "unpriv" ) @Test(expected = UnauthorizedException.class) - public void testCreateWithoutPrivileges() throws AlreadyExistsException { + public void testCreateWithoutPrivileges() { createTestRepository(); } @Test(expected = AlreadyExistsException.class) - public void testCreateExisting() throws AlreadyExistsException { + public void testCreateExisting() { createTestRepository(); createTestRepository(); } @Test - public void testDelete() throws Exception { + public void testDelete() { delete(manager, createTestRepository()); } @@ -141,12 +141,12 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { username = "unpriv" ) @Test(expected = UnauthorizedException.class) - public void testDeleteWithoutPrivileges() throws Exception { + public void testDeleteWithoutPrivileges() { delete(manager, createTestRepository()); } @Test(expected = RepositoryIsNotArchivedException.class) - public void testDeleteNonArchived() throws Exception { + public void testDeleteNonArchived() { configuration.setEnableRepositoryArchive(true); delete(manager, createTestRepository()); } @@ -157,7 +157,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { } @Test - public void testDeleteWithEnabledArchive() throws Exception { + public void testDeleteWithEnabledArchive() { Repository repository = createTestRepository(); repository.setArchived(true); @@ -167,7 +167,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { } @Test - public void testGet() throws AlreadyExistsException { + public void testGet() { Repository heartOfGold = createTestRepository(); String id = heartOfGold.getId(); String description = heartOfGold.getDescription(); @@ -185,7 +185,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { @SubjectAware( username = "crato" ) - public void testGetWithoutRequiredPrivileges() throws AlreadyExistsException { + public void testGetWithoutRequiredPrivileges() { Repository heartOfGold = RepositoryTestData.createHeartOfGold(); manager.create(heartOfGold); @@ -194,7 +194,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { } @Test - public void testGetAll() throws AlreadyExistsException { + public void testGetAll() { Repository heartOfGold = createTestRepository(); Repository happyVerticalPeopleTransporter = createSecondTestRepository(); boolean foundHeart = false; @@ -232,7 +232,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { @Test @SuppressWarnings("unchecked") @SubjectAware(username = "dent") - public void testGetAllWithPermissionsForTwoOrThreeRepos() throws AlreadyExistsException { + public void testGetAllWithPermissionsForTwoOrThreeRepos() { // mock key generator KeyGenerator keyGenerator = mock(KeyGenerator.class); Stack keys = new Stack<>(); @@ -273,7 +273,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { } @Test - public void testEvents() throws Exception { + public void testEvents() { RepositoryManager repoManager = createRepositoryManager(false); repoManager.init(contextProvider); TestListener listener = new TestListener(); @@ -304,7 +304,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { } @Test - public void testModify() throws AlreadyExistsException { + public void testModify() { Repository heartOfGold = createTestRepository(); heartOfGold.setDescription("prototype ship"); @@ -318,7 +318,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { @Test @SubjectAware(username = "crato") - public void testModifyWithoutRequiredPermissions() throws AlreadyExistsException, NotFoundException { + public void testModifyWithoutRequiredPermissions() { Repository heartOfGold = RepositoryTestData.createHeartOfGold(); manager.create(heartOfGold); heartOfGold.setDescription("prototype ship"); @@ -333,7 +333,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { } @Test - public void testRefresh() throws AlreadyExistsException { + public void testRefresh() { Repository heartOfGold = createTestRepository(); String description = heartOfGold.getDescription(); @@ -344,7 +344,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { @Test @SubjectAware(username = "crato") - public void testRefreshWithoutRequiredPermissions() throws AlreadyExistsException, NotFoundException { + public void testRefreshWithoutRequiredPermissions() { Repository heartOfGold = RepositoryTestData.createHeartOfGold(); manager.create(heartOfGold); heartOfGold.setDescription("prototype ship"); @@ -359,7 +359,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { } @Test - public void testRepositoryHook() throws AlreadyExistsException { + public void testRepositoryHook() { CountingReceiveHook hook = new CountingReceiveHook(); RepositoryManager repoManager = createRepositoryManager(false); @@ -379,7 +379,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { } @Test - public void testNamespaceSet() throws Exception { + public void testNamespaceSet() { RepositoryManager repoManager = createRepositoryManager(false); Repository repository = spy(createTestRepository()); repository.setName("Testrepo"); @@ -388,14 +388,14 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { } @Test - public void shouldSetNamespace() throws AlreadyExistsException { + public void shouldSetNamespace() { Repository repository = new Repository(null, "hg", null, "scm"); manager.create(repository); assertNotNull(repository.getId()); assertNotNull(repository.getNamespace()); } - private void createUriTestRepositories(RepositoryManager m) throws AlreadyExistsException { + private void createUriTestRepositories(RepositoryManager m) { mockedNamespace = "namespace"; createRepository(m, new Repository("1", "hg", "namespace", "scm")); createRepository(m, new Repository("2", "hg", "namespace", "scm-test")); @@ -448,7 +448,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { keyGenerator, repositoryDAO, handlerSet, namespaceStrategy); } - private void createRepository(RepositoryManager m, Repository repository) throws AlreadyExistsException { + private void createRepository(RepositoryManager m, Repository repository) { m.create(repository); } @@ -471,7 +471,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { assertEquals(repo.getLastModified(), other.getLastModified()); } - private Repository createRepository(Repository repository) throws AlreadyExistsException { + private Repository createRepository(Repository repository) { manager.create(repository); assertNotNull(repository.getId()); assertNotNull(manager.get(repository.getId())); @@ -486,12 +486,12 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase { return repository; } - private Repository createSecondTestRepository() throws AlreadyExistsException { + private Repository createSecondTestRepository() { return createRepository( RepositoryTestData.createHappyVerticalPeopleTransporter()); } - private Repository createTestRepository() throws AlreadyExistsException { + private Repository createTestRepository() { return createRepository(RepositoryTestData.createHeartOfGold()); } diff --git a/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java b/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java index df32528e5e..1614bf790a 100644 --- a/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java @@ -40,7 +40,6 @@ import com.github.sdorra.shiro.SubjectAware; import com.google.common.collect.Lists; import org.assertj.core.api.Assertions; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -48,7 +47,6 @@ import org.mockito.ArgumentCaptor; import sonia.scm.NotFoundException; import sonia.scm.store.JAXBConfigurationStoreFactory; import sonia.scm.user.xml.XmlUserDAO; -import sonia.scm.util.MockUtil; import static org.mockito.Mockito.*; @@ -57,7 +55,6 @@ import static org.mockito.Mockito.*; import java.util.Collections; import java.util.List; import org.junit.Rule; -import sonia.scm.store.ConfigurationStoreFactory; /** * From 7f0b7c5831d6f75b35792f783d0c3b397fc6b1c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 25 Oct 2018 16:23:43 +0200 Subject: [PATCH 07/27] Add generated error code to exceptions --- .../src/main/java/sonia/scm/AlreadyExistsException.java | 7 +++++++ .../java/sonia/scm/ConcurrentModificationException.java | 8 ++++++++ .../src/main/java/sonia/scm/ExceptionWithContext.java | 2 ++ scm-core/src/main/java/sonia/scm/NotFoundException.java | 7 +++++++ .../main/java/sonia/scm/api/v2/resources/ErrorDto.java | 2 +- 5 files changed, 25 insertions(+), 1 deletion(-) diff --git a/scm-core/src/main/java/sonia/scm/AlreadyExistsException.java b/scm-core/src/main/java/sonia/scm/AlreadyExistsException.java index 95fc24ecb2..17863c364c 100644 --- a/scm-core/src/main/java/sonia/scm/AlreadyExistsException.java +++ b/scm-core/src/main/java/sonia/scm/AlreadyExistsException.java @@ -8,6 +8,8 @@ import static java.util.stream.Collectors.joining; public class AlreadyExistsException extends RuntimeException implements ExceptionWithContext { + private static final String CODE = "FtR7UznKU1"; + private final List context; public AlreadyExistsException(ModelObject object) { @@ -27,6 +29,11 @@ public class AlreadyExistsException extends RuntimeException implements Exceptio return unmodifiableList(context); } + @Override + public String getCode() { + return CODE; + } + private static String createMessage(List context) { return context.stream() .map(c -> c.getType().toLowerCase() + " with id " + c.getId()) diff --git a/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java b/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java index 2a3e65781d..6fb8ac2b62 100644 --- a/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java +++ b/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java @@ -7,6 +7,9 @@ import static java.util.Collections.unmodifiableList; import static java.util.stream.Collectors.joining; public class ConcurrentModificationException extends RuntimeException implements ExceptionWithContext { + + private static final String CODE = "2wR7UzpPG1"; + private final List context; public ConcurrentModificationException(Class type, String id) { @@ -26,6 +29,11 @@ public class ConcurrentModificationException extends RuntimeException implements return unmodifiableList(context); } + @Override + public String getCode() { + return CODE; + } + private static String createMessage(List context) { return context.stream() .map(c -> c.getType().toLowerCase() + " with id " + c.getId()) diff --git a/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java b/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java index d10411ca9a..93edc1f63b 100644 --- a/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java +++ b/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java @@ -6,4 +6,6 @@ public interface ExceptionWithContext { List getContext(); String getMessage(); + + String getCode(); } diff --git a/scm-core/src/main/java/sonia/scm/NotFoundException.java b/scm-core/src/main/java/sonia/scm/NotFoundException.java index 110402043d..30696c73b6 100644 --- a/scm-core/src/main/java/sonia/scm/NotFoundException.java +++ b/scm-core/src/main/java/sonia/scm/NotFoundException.java @@ -12,6 +12,8 @@ import static java.util.stream.Collectors.joining; public class NotFoundException extends RuntimeException implements ExceptionWithContext { + private static final String CODE = "AGR7UzkhA1"; + private final List context; public NotFoundException(Class type, String id) { @@ -35,6 +37,11 @@ public class NotFoundException extends RuntimeException implements ExceptionWith return unmodifiableList(context); } + @Override + public String getCode() { + return CODE; + } + private static String createMessage(List context) { return context.stream() .map(c -> c.getType().toLowerCase() + " with id " + c.getId()) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java index a047aece38..48aa158451 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java @@ -30,6 +30,6 @@ public class ErrorDto { } public static ErrorDto from(ExceptionWithContext exception) { - return new ErrorDto(MDC.get("transaction_id"), "todo", exception.getContext(), exception.getMessage()); + return new ErrorDto(MDC.get("transaction_id"), exception.getCode(), exception.getContext(), exception.getMessage()); } } From f34ea4ece1297d715c5dfa1811412353dc68877d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 26 Oct 2018 09:47:30 +0200 Subject: [PATCH 08/27] Let validation error dto inherit from 'normal' error --- .../scm/api/v2/ValidationExceptionMapper.java | 22 ++++++++++++------- .../sonia/scm/api/v2/resources/ErrorDto.java | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/ValidationExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/ValidationExceptionMapper.java index c6af6b8921..8cd2c6d649 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/ValidationExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/ValidationExceptionMapper.java @@ -2,6 +2,8 @@ package sonia.scm.api.v2; import lombok.Getter; import org.jboss.resteasy.api.validation.ResteasyViolationException; +import org.slf4j.MDC; +import sonia.scm.api.v2.resources.ErrorDto; import javax.validation.ConstraintViolation; import javax.ws.rs.core.MediaType; @@ -11,46 +13,50 @@ import javax.ws.rs.ext.Provider; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import static java.util.Collections.emptyList; + @Provider public class ValidationExceptionMapper implements ExceptionMapper { @Override public Response toResponse(ResteasyViolationException exception) { - List violations = + List violations = exception.getConstraintViolations() .stream() - .map(ConstraintViolationBean::new) + .map(ConstraintViolationDto::new) .collect(Collectors.toList()); return Response .status(Response.Status.BAD_REQUEST) .type(MediaType.APPLICATION_JSON_TYPE) - .entity(new ValidationError(violations)) + .entity(new ValidationErrorDto(violations)) .build(); } @Getter - public static class ValidationError { + public static class ValidationErrorDto extends ErrorDto { @XmlElement(name = "violation") @XmlElementWrapper(name = "violations") - private List violations; + private List violations; - public ValidationError(List violations) { + public ValidationErrorDto(List violations) { + super(MDC.get("transaction_id"), "1wR7ZBe7H1", emptyList(), "input violates conditions (see violation list)"); this.violations = violations; } } @XmlRootElement(name = "violation") @Getter - public static class ConstraintViolationBean { + public static class ConstraintViolationDto { private String path; private String message; - public ConstraintViolationBean(ConstraintViolation violation) { + public ConstraintViolationDto(ConstraintViolation violation) { message = violation.getMessage(); path = violation.getPropertyPath().toString(); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java index 48aa158451..df8a33684b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java @@ -18,7 +18,7 @@ public class ErrorDto { @JsonInclude(JsonInclude.Include.NON_NULL) private final String url; - private ErrorDto(String transactionId, String errorCode, List context, String message) { + protected ErrorDto(String transactionId, String errorCode, List context, String message) { this(transactionId, errorCode, context, message, null); } private ErrorDto(String transactionId, String errorCode, List context, String message, String url) { From 1b89859128a6ae117a1ddd5a6e1248de77a4c440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 26 Oct 2018 12:46:13 +0200 Subject: [PATCH 09/27] Name parameters for validation --- .../v2/resources/GroupCollectionResource.java | 3 +- .../scm/api/v2/resources/GroupResource.java | 3 +- .../scm/api/v2/resources/MeResource.java | 3 +- .../v2/resources/PermissionRootResource.java | 5 ++-- .../RepositoryCollectionResource.java | 3 +- .../api/v2/resources/RepositoryResource.java | 3 +- .../ResourceParameterNameProvider.java | 29 +++++++++++++++++++ .../v2/resources/UserCollectionResource.java | 3 +- .../scm/api/v2/resources/UserResource.java | 5 ++-- .../main/resources/META-INF/validation.xml | 14 +++++++++ 10 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceParameterNameProvider.java create mode 100644 scm-webapp/src/main/resources/META-INF/validation.xml 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 d845aa181e..0683a1d17b 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 @@ -10,6 +10,7 @@ import sonia.scm.group.GroupManager; import sonia.scm.web.VndMediaType; import javax.inject.Inject; +import javax.inject.Named; import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; @@ -85,7 +86,7 @@ public class GroupCollectionResource { }) @TypeHint(TypeHint.NO_CONTENT.class) @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created group")) - public Response create(@Valid GroupDto groupDto) { + public Response create(@Valid @Named("group") GroupDto groupDto) { return adapter.create(groupDto, () -> dtoToGroupMapper.map(groupDto), group -> resourceLinks.group().self(group.getName())); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java index f2341262ce..88954212f6 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java @@ -8,6 +8,7 @@ import sonia.scm.group.GroupManager; import sonia.scm.web.VndMediaType; import javax.inject.Inject; +import javax.inject.Named; import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -96,7 +97,7 @@ public class GroupResource { @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) - public Response update(@PathParam("id") String name, @Valid GroupDto groupDto) { + public Response update(@PathParam("id") String name, @Valid @Named("group") GroupDto groupDto) { return adapter.update(name, existing -> dtoToGroupMapper.map(groupDto)); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeResource.java index f616aff852..62917da8b0 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeResource.java @@ -10,6 +10,7 @@ import sonia.scm.user.UserManager; import sonia.scm.web.VndMediaType; import javax.inject.Inject; +import javax.inject.Named; import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -73,7 +74,7 @@ public class MeResource { }) @TypeHint(TypeHint.NO_CONTENT.class) @Consumes(VndMediaType.PASSWORD_CHANGE) - public Response changePassword(@Valid PasswordChangeDto passwordChangeDto) { + public Response changePassword(@Valid @Named("passwordChange") PasswordChangeDto passwordChangeDto) { userManager.changePasswordForLoggedInUser(passwordService.encryptPassword(passwordChangeDto.getOldPassword()), passwordService.encryptPassword(passwordChangeDto.getNewPassword())); return Response.noContent().build(); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java index c997a706fd..9359746f1e 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java @@ -15,6 +15,7 @@ import sonia.scm.repository.RepositoryPermissions; import sonia.scm.web.VndMediaType; import javax.inject.Inject; +import javax.inject.Named; import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -73,7 +74,7 @@ public class PermissionRootResource { @TypeHint(TypeHint.NO_CONTENT.class) @Consumes(VndMediaType.PERMISSION) @Path("") - public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name,@Valid PermissionDto permission) { + public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name,@Valid @Named("permission") PermissionDto permission) { log.info("try to add new permission: {}", permission); Repository repository = load(namespace, name); RepositoryPermissions.permissionWrite(repository).check(); @@ -160,7 +161,7 @@ public class PermissionRootResource { public Response update(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("permission-name") String permissionName, - @Valid PermissionDto permission) { + @Valid @Named("permission") PermissionDto permission) { log.info("try to update the permission with name: {}. the modified permission is: {}", permissionName, permission); Repository repository = load(namespace, name); RepositoryPermissions.permissionWrite(repository).check(); 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 665e2131d9..d109714ebe 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 @@ -10,6 +10,7 @@ import sonia.scm.repository.RepositoryManager; import sonia.scm.web.VndMediaType; import javax.inject.Inject; +import javax.inject.Named; import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; @@ -86,7 +87,7 @@ public class RepositoryCollectionResource { }) @TypeHint(TypeHint.NO_CONTENT.class) @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created repository")) - public Response create(@Valid RepositoryDto repositoryDto) { + public Response create(@Valid @Named("repository") RepositoryDto repositoryDto) { return adapter.create(repositoryDto, () -> dtoToRepositoryMapper.map(repositoryDto, null), repository -> resourceLinks.repository().self(repository.getNamespace(), repository.getName())); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java index 141cf64ff6..75615784ba 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java @@ -10,6 +10,7 @@ import sonia.scm.repository.RepositoryManager; import sonia.scm.web.VndMediaType; import javax.inject.Inject; +import javax.inject.Named; import javax.inject.Provider; import javax.validation.Valid; import javax.ws.rs.Consumes; @@ -139,7 +140,7 @@ public class RepositoryResource { @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) - public Response update(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid RepositoryDto repositoryDto) { + public Response update(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid @Named("repository") RepositoryDto repositoryDto) { return adapter.update( loadBy(namespace, name), existing -> processUpdate(repositoryDto, existing), diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceParameterNameProvider.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceParameterNameProvider.java new file mode 100644 index 0000000000..986a3ca652 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceParameterNameProvider.java @@ -0,0 +1,29 @@ +package sonia.scm.api.v2.resources; + +import javax.inject.Named; +import javax.validation.ParameterNameProvider; +import javax.ws.rs.ext.Provider; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Provider +public class ResourceParameterNameProvider implements ParameterNameProvider { + @Override + public List getParameterNames(Constructor constructor) { + return Arrays.stream(constructor.getParameters()).map(this::getParameterName).collect(Collectors.toList()); + } + + @Override + public List getParameterNames(Method method) { + return Arrays.stream(method.getParameters()).map(this::getParameterName).collect(Collectors.toList()); + } + + private String getParameterName(Parameter parameter) { + return Optional.ofNullable(parameter.getAnnotation(Named.class)).map(Named::value).orElse(parameter.getName()); + } +} 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 e7a49bc1fd..c19e6b35ba 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 @@ -11,6 +11,7 @@ import sonia.scm.user.UserManager; import sonia.scm.web.VndMediaType; import javax.inject.Inject; +import javax.inject.Named; import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; @@ -90,7 +91,7 @@ public class UserCollectionResource { }) @TypeHint(TypeHint.NO_CONTENT.class) @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created user")) - public Response create(@Valid UserDto userDto) { + public Response create(@Valid @Named("user") UserDto userDto) { return adapter.create(userDto, () -> dtoToUserMapper.map(userDto, passwordService.encryptPassword(userDto.getPassword())), user -> resourceLinks.user().self(user.getName())); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java index d0ce8c8a63..565cc48eb9 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java @@ -9,6 +9,7 @@ import sonia.scm.user.UserManager; import sonia.scm.web.VndMediaType; import javax.inject.Inject; +import javax.inject.Named; import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -100,7 +101,7 @@ public class UserResource { @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) - public Response update(@PathParam("id") String name, @Valid UserDto userDto) { + public Response update(@PathParam("id") String name, @Valid @Named("user") UserDto userDto) { return adapter.update(name, existing -> dtoToUserMapper.map(userDto, existing.getPassword())); } @@ -127,7 +128,7 @@ public class UserResource { @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) - public Response overwritePassword(@PathParam("id") String name, @Valid PasswordOverwriteDto passwordOverwriteDto) { + public Response overwritePassword(@PathParam("id") String name, @Valid @Named("passwordChange") PasswordOverwriteDto passwordOverwriteDto) { userManager.overwritePassword(name, passwordService.encryptPassword(passwordOverwriteDto.getNewPassword())); return Response.noContent().build(); } diff --git a/scm-webapp/src/main/resources/META-INF/validation.xml b/scm-webapp/src/main/resources/META-INF/validation.xml new file mode 100644 index 0000000000..b6feda4713 --- /dev/null +++ b/scm-webapp/src/main/resources/META-INF/validation.xml @@ -0,0 +1,14 @@ + + + org.hibernate.validator.HibernateValidator + org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator + org.hibernate.validator.internal.engine.resolver.DefaultTraversableResolver + org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl + sonia.scm.api.v2.resources.ResourceParameterNameProvider + From cd0964d850ebbc6ed6c29bf7423f6dba728277ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 26 Oct 2018 13:07:41 +0200 Subject: [PATCH 10/27] Fix typo in class name --- ...erException.java => NotSupportedFeatureException.java} | 6 +++--- .../sonia/scm/repository/AbstractRepositoryHandler.java | 8 ++++---- .../main/java/sonia/scm/repository/RepositoryHandler.java | 6 +++--- .../scm/api/rest/resources/RepositoryImportResource.java | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) rename scm-core/src/main/java/sonia/scm/{NotSupportedFeatuerException.java => NotSupportedFeatureException.java} (92%) diff --git a/scm-core/src/main/java/sonia/scm/NotSupportedFeatuerException.java b/scm-core/src/main/java/sonia/scm/NotSupportedFeatureException.java similarity index 92% rename from scm-core/src/main/java/sonia/scm/NotSupportedFeatuerException.java rename to scm-core/src/main/java/sonia/scm/NotSupportedFeatureException.java index e6fea6cfa7..2270ed37b3 100644 --- a/scm-core/src/main/java/sonia/scm/NotSupportedFeatuerException.java +++ b/scm-core/src/main/java/sonia/scm/NotSupportedFeatureException.java @@ -38,7 +38,7 @@ package sonia.scm; * @author Sebastian Sdorra * @version 1.6 */ -public class NotSupportedFeatuerException extends Exception +public class NotSupportedFeatureException extends Exception { /** Field description */ @@ -50,7 +50,7 @@ public class NotSupportedFeatuerException extends Exception * Constructs ... * */ - public NotSupportedFeatuerException() {} + public NotSupportedFeatureException() {} /** * Constructs ... @@ -58,7 +58,7 @@ public class NotSupportedFeatuerException extends Exception * * @param message */ - public NotSupportedFeatuerException(String message) + public NotSupportedFeatureException(String message) { super(message); } diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java index 33b6cb8030..a545aefdc8 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java @@ -38,7 +38,7 @@ package sonia.scm.repository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.NotSupportedFeatuerException; +import sonia.scm.NotSupportedFeatureException; import sonia.scm.SCMContextProvider; import sonia.scm.event.ScmEventBus; @@ -165,12 +165,12 @@ public abstract class AbstractRepositoryHandler * * @return * - * @throws NotSupportedFeatuerException + * @throws NotSupportedFeatureException */ @Override - public ImportHandler getImportHandler() throws NotSupportedFeatuerException + public ImportHandler getImportHandler() throws NotSupportedFeatureException { - throw new NotSupportedFeatuerException( + throw new NotSupportedFeatureException( "import handler is not supported by this repository handler"); } diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryHandler.java index 445fc22ab8..79c06d03f9 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryHandler.java @@ -36,7 +36,7 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- import sonia.scm.Handler; -import sonia.scm.NotSupportedFeatuerException; +import sonia.scm.NotSupportedFeatureException; import sonia.scm.plugin.ExtensionPoint; /** @@ -70,9 +70,9 @@ public interface RepositoryHandler * @return {@link ImportHandler} for the repository type of this handler * @since 1.12 * - * @throws NotSupportedFeatuerException + * @throws NotSupportedFeatureException */ - public ImportHandler getImportHandler() throws NotSupportedFeatuerException; + public ImportHandler getImportHandler() throws NotSupportedFeatureException; /** * Returns informations about the version of the RepositoryHandler. diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java index 178756f0f9..6bf6c8e803 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java @@ -46,7 +46,7 @@ import org.apache.shiro.SecurityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.NotFoundException; -import sonia.scm.NotSupportedFeatuerException; +import sonia.scm.NotSupportedFeatureException; import sonia.scm.Type; import sonia.scm.api.rest.RestActionUploadResult; import sonia.scm.api.v2.resources.RepositoryResource; @@ -394,7 +394,7 @@ public class RepositoryImportResource response = Response.ok(result).build(); } - catch (NotSupportedFeatuerException ex) + catch (NotSupportedFeatureException ex) { logger .warn( @@ -609,7 +609,7 @@ public class RepositoryImportResource types.add(t); } } - catch (NotSupportedFeatuerException ex) + catch (NotSupportedFeatureException ex) { if (logger.isTraceEnabled()) { @@ -711,7 +711,7 @@ public class RepositoryImportResource } } } - catch (NotSupportedFeatuerException ex) + catch (NotSupportedFeatureException ex) { throw new WebApplicationException(ex, Response.Status.BAD_REQUEST); } From 9279bfca5f5a3706b2bedb1bbc7a5be0d18fdcda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 26 Oct 2018 14:20:19 +0200 Subject: [PATCH 11/27] Use mappers for errors --- .../rest/AlreadyExistsExceptionMapper.java | 14 ++--- ...ConcurrentModificationExceptionMapper.java | 10 ++-- .../api/rest/ContextualExceptionMapper.java | 36 +++++++++++++ .../scm/api/v2/NotFoundExceptionMapper.java | 11 ++-- .../scm/api/v2/ValidationExceptionMapper.java | 50 +++-------------- .../sonia/scm/api/v2/resources/ErrorDto.java | 40 +++++++------- .../ExceptionWithContextToErrorDtoMapper.java | 23 ++++++++ .../scm/api/v2/resources/MapperModule.java | 3 ++ .../ViolationExceptionToErrorDtoMapper.java | 54 +++++++++++++++++++ 9 files changed, 151 insertions(+), 90 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/ExceptionWithContextToErrorDtoMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/ViolationExceptionToErrorDtoMapper.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/AlreadyExistsExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/rest/AlreadyExistsExceptionMapper.java index f923eef693..1718d09e9b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/AlreadyExistsExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/AlreadyExistsExceptionMapper.java @@ -1,21 +1,13 @@ package sonia.scm.api.rest; import sonia.scm.AlreadyExistsException; -import sonia.scm.api.v2.resources.ErrorDto; -import sonia.scm.web.VndMediaType; -import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; -import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; @Provider -public class AlreadyExistsExceptionMapper implements ExceptionMapper { - @Override - public Response toResponse(AlreadyExistsException exception) { - return Response.status(Status.CONFLICT) - .entity(ErrorDto.from(exception)) - .type(VndMediaType.ERROR_TYPE) - .build(); +public class AlreadyExistsExceptionMapper extends ContextualExceptionMapper { + public AlreadyExistsExceptionMapper() { + super(AlreadyExistsException.class, Status.CONFLICT); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/ConcurrentModificationExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/rest/ConcurrentModificationExceptionMapper.java index 49aed6a29a..ed2160f8fb 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/ConcurrentModificationExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/ConcurrentModificationExceptionMapper.java @@ -1,17 +1,13 @@ package sonia.scm.api.rest; import sonia.scm.ConcurrentModificationException; -import sonia.scm.api.v2.resources.ErrorDto; -import sonia.scm.web.VndMediaType; import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; @Provider -public class ConcurrentModificationExceptionMapper implements ExceptionMapper { - @Override - public Response toResponse(ConcurrentModificationException exception) { - return Response.status(Response.Status.CONFLICT).entity(ErrorDto.from(exception)).type(VndMediaType.ERROR_TYPE).build(); +public class ConcurrentModificationExceptionMapper extends ContextualExceptionMapper { + public ConcurrentModificationExceptionMapper() { + super(ConcurrentModificationException.class, Response.Status.CONFLICT); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java new file mode 100644 index 0000000000..7aee62575a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java @@ -0,0 +1,36 @@ +package sonia.scm.api.rest; + +import com.google.inject.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.ExceptionWithContext; +import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper; +import sonia.scm.web.VndMediaType; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; + +public class ContextualExceptionMapper implements ExceptionMapper { + + @Inject + private ExceptionWithContextToErrorDtoMapper mapper; + + private static final Logger logger = LoggerFactory.getLogger(ContextualExceptionMapper.class); + + private final Response.Status status; + private final Class type; + + public ContextualExceptionMapper(Class type, Response.Status status) { + this.type = type; + this.status = status; + } + + @Override + public Response toResponse(E exception) { + logger.debug("map {} to status code {}", type.getSimpleName(), status.getStatusCode()); + return Response.status(status) + .entity(mapper.map(exception)) + .type(VndMediaType.ERROR_TYPE) + .build(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/NotFoundExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/NotFoundExceptionMapper.java index c1f046620a..add41bc3b2 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/NotFoundExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/NotFoundExceptionMapper.java @@ -32,20 +32,17 @@ package sonia.scm.api.v2; import sonia.scm.NotFoundException; -import sonia.scm.api.v2.resources.ErrorDto; -import sonia.scm.web.VndMediaType; +import sonia.scm.api.rest.ContextualExceptionMapper; import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; /** * @since 2.0.0 */ @Provider -public class NotFoundExceptionMapper implements ExceptionMapper { - @Override - public Response toResponse(NotFoundException exception) { - return Response.status(Response.Status.NOT_FOUND).entity(ErrorDto.from(exception)).type(VndMediaType.ERROR_TYPE).build(); +public class NotFoundExceptionMapper extends ContextualExceptionMapper { + public NotFoundExceptionMapper() { + super(NotFoundException.class, Response.Status.NOT_FOUND); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/ValidationExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/ValidationExceptionMapper.java index 8cd2c6d649..4926ed0f66 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/ValidationExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/ValidationExceptionMapper.java @@ -1,64 +1,26 @@ package sonia.scm.api.v2; -import lombok.Getter; +import com.google.inject.Inject; import org.jboss.resteasy.api.validation.ResteasyViolationException; -import org.slf4j.MDC; -import sonia.scm.api.v2.resources.ErrorDto; +import sonia.scm.api.v2.resources.ViolationExceptionToErrorDtoMapper; -import javax.validation.ConstraintViolation; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlElementWrapper; -import javax.xml.bind.annotation.XmlRootElement; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import static java.util.Collections.emptyList; @Provider public class ValidationExceptionMapper implements ExceptionMapper { + @Inject + private ViolationExceptionToErrorDtoMapper mapper; + @Override public Response toResponse(ResteasyViolationException exception) { - - List violations = - exception.getConstraintViolations() - .stream() - .map(ConstraintViolationDto::new) - .collect(Collectors.toList()); - return Response .status(Response.Status.BAD_REQUEST) .type(MediaType.APPLICATION_JSON_TYPE) - .entity(new ValidationErrorDto(violations)) + .entity(mapper.map(exception)) .build(); } - - @Getter - public static class ValidationErrorDto extends ErrorDto { - @XmlElement(name = "violation") - @XmlElementWrapper(name = "violations") - private List violations; - - public ValidationErrorDto(List violations) { - super(MDC.get("transaction_id"), "1wR7ZBe7H1", emptyList(), "input violates conditions (see violation list)"); - this.violations = violations; - } - } - - @XmlRootElement(name = "violation") - @Getter - public static class ConstraintViolationDto { - private String path; - private String message; - - public ConstraintViolationDto(ConstraintViolation violation) { - message = violation.getMessage(); - path = violation.getPropertyPath().toString(); - } - } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java index df8a33684b..f0df654866 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java @@ -2,34 +2,32 @@ package sonia.scm.api.v2.resources; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Getter; -import org.slf4j.MDC; +import lombok.Setter; import sonia.scm.ContextEntry; -import sonia.scm.ExceptionWithContext; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; import java.util.List; -@Getter +@Getter @Setter public class ErrorDto { - private final String transactionId; - private final String errorCode; - private final List context; - private final String message; + private String transactionId; + private String errorCode; + private List context; + private String message; + + @XmlElement(name = "violation") + @XmlElementWrapper(name = "violations") + private List violations; @JsonInclude(JsonInclude.Include.NON_NULL) - private final String url; + private String url; - protected ErrorDto(String transactionId, String errorCode, List context, String message) { - this(transactionId, errorCode, context, message, null); - } - private ErrorDto(String transactionId, String errorCode, List context, String message, String url) { - this.transactionId = transactionId; - this.errorCode = errorCode; - this.context = context; - this.message = message; - this.url = url; - } - - public static ErrorDto from(ExceptionWithContext exception) { - return new ErrorDto(MDC.get("transaction_id"), exception.getCode(), exception.getContext(), exception.getMessage()); + @XmlRootElement(name = "violation") + @Getter @Setter + public static class ConstraintViolationDto { + private String path; + private String message; } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ExceptionWithContextToErrorDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ExceptionWithContextToErrorDtoMapper.java new file mode 100644 index 0000000000..cdd545542a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ExceptionWithContextToErrorDtoMapper.java @@ -0,0 +1,23 @@ +package sonia.scm.api.v2.resources; + +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.slf4j.MDC; +import sonia.scm.ExceptionWithContext; + +@Mapper +public abstract class ExceptionWithContextToErrorDtoMapper { + + @Mapping(target = "errorCode", source = "code") + @Mapping(target = "transactionId", ignore = true) + @Mapping(target = "violations", ignore = true) + @Mapping(target = "url", ignore = true) + public abstract ErrorDto map(ExceptionWithContext exception); + + @AfterMapping + void setTransactionId(@MappingTarget ErrorDto dto) { + dto.setTransactionId(MDC.get("transaction_id")); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java index 6497cb9315..35f58cef90 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -39,6 +39,9 @@ public class MapperModule extends AbstractModule { bind(ReducedObjectModelToDtoMapper.class).to(Mappers.getMapper(ReducedObjectModelToDtoMapper.class).getClass()); + bind(ViolationExceptionToErrorDtoMapper.class).to(Mappers.getMapper(ViolationExceptionToErrorDtoMapper.class).getClass()); + bind(ExceptionWithContextToErrorDtoMapper.class).to(Mappers.getMapper(ExceptionWithContextToErrorDtoMapper.class).getClass()); + // no mapstruct required bind(UIPluginDtoMapper.class); bind(UIPluginDtoCollectionMapper.class); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ViolationExceptionToErrorDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ViolationExceptionToErrorDtoMapper.java new file mode 100644 index 0000000000..e713b031f7 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ViolationExceptionToErrorDtoMapper.java @@ -0,0 +1,54 @@ +package sonia.scm.api.v2.resources; + +import org.jboss.resteasy.api.validation.ResteasyViolationException; +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.slf4j.MDC; + +import javax.validation.ConstraintViolation; +import java.util.List; +import java.util.stream.Collectors; + +@Mapper +public abstract class ViolationExceptionToErrorDtoMapper { + + @Mapping(target = "errorCode", ignore = true) + @Mapping(target = "transactionId", ignore = true) + @Mapping(target = "context", ignore = true) + @Mapping(target = "url", ignore = true) + public abstract ErrorDto map(ResteasyViolationException exception); + + @AfterMapping + void setTransactionId(@MappingTarget ErrorDto dto) { + dto.setTransactionId(MDC.get("transaction_id")); + } + + @AfterMapping + void mapViolations(ResteasyViolationException exception, @MappingTarget ErrorDto dto) { + List violations = + exception.getConstraintViolations() + .stream() + .map(this::createViolationDto) + .collect(Collectors.toList()); + dto.setViolations(violations); + } + + private ErrorDto.ConstraintViolationDto createViolationDto(ConstraintViolation violation) { + ErrorDto.ConstraintViolationDto constraintViolationDto = new ErrorDto.ConstraintViolationDto(); + constraintViolationDto.setMessage(violation.getMessage()); + constraintViolationDto.setPath(violation.getPropertyPath().toString()); + return constraintViolationDto; + } + + @AfterMapping + void setErrorCode(@MappingTarget ErrorDto dto) { + dto.setErrorCode("1wR7ZBe7H1"); + } + + @AfterMapping + void setMessage(@MappingTarget ErrorDto dto) { + dto.setMessage("input violates conditions (see violation list)"); + } +} From 22069914b0ff7046fad7cd65803a4acae6bf674e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 26 Oct 2018 14:54:55 +0200 Subject: [PATCH 12/27] Fix injection for unit tests --- .../sonia/scm/api/rest/AlreadyExistsExceptionMapper.java | 7 +++++-- .../api/rest/ConcurrentModificationExceptionMapper.java | 7 +++++-- .../sonia/scm/api/rest/ContextualExceptionMapper.java | 8 ++++---- .../java/sonia/scm/api/v2/NotFoundExceptionMapper.java | 7 +++++-- .../scm/api/v2/resources/ChangesetRootResourceTest.java | 7 +++---- .../sonia/scm/api/v2/resources/DiffResourceTest.java | 4 +++- .../java/sonia/scm/api/v2/resources/DispatcherMock.java | 7 ++++--- .../scm/api/v2/resources/FileHistoryResourceTest.java | 9 +++------ .../scm/api/v2/resources/GroupRootResourceTest.java | 2 +- .../scm/api/v2/resources/ModificationsResourceTest.java | 8 +++----- 10 files changed, 36 insertions(+), 30 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/AlreadyExistsExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/rest/AlreadyExistsExceptionMapper.java index 1718d09e9b..65e4426fde 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/AlreadyExistsExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/AlreadyExistsExceptionMapper.java @@ -1,13 +1,16 @@ package sonia.scm.api.rest; import sonia.scm.AlreadyExistsException; +import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper; +import javax.inject.Inject; import javax.ws.rs.core.Response.Status; import javax.ws.rs.ext.Provider; @Provider public class AlreadyExistsExceptionMapper extends ContextualExceptionMapper { - public AlreadyExistsExceptionMapper() { - super(AlreadyExistsException.class, Status.CONFLICT); + @Inject + public AlreadyExistsExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) { + super(AlreadyExistsException.class, Status.CONFLICT, mapper); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/ConcurrentModificationExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/rest/ConcurrentModificationExceptionMapper.java index ed2160f8fb..edcb48c91b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/ConcurrentModificationExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/ConcurrentModificationExceptionMapper.java @@ -1,13 +1,16 @@ package sonia.scm.api.rest; import sonia.scm.ConcurrentModificationException; +import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper; +import javax.inject.Inject; import javax.ws.rs.core.Response; import javax.ws.rs.ext.Provider; @Provider public class ConcurrentModificationExceptionMapper extends ContextualExceptionMapper { - public ConcurrentModificationExceptionMapper() { - super(ConcurrentModificationException.class, Response.Status.CONFLICT); + @Inject + public ConcurrentModificationExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) { + super(ConcurrentModificationException.class, Response.Status.CONFLICT, mapper); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java index 7aee62575a..b25af05d50 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java @@ -12,15 +12,15 @@ import javax.ws.rs.ext.ExceptionMapper; public class ContextualExceptionMapper implements ExceptionMapper { - @Inject - private ExceptionWithContextToErrorDtoMapper mapper; - private static final Logger logger = LoggerFactory.getLogger(ContextualExceptionMapper.class); + private ExceptionWithContextToErrorDtoMapper mapper; + private final Response.Status status; private final Class type; - public ContextualExceptionMapper(Class type, Response.Status status) { + public ContextualExceptionMapper(Class type, Response.Status status, ExceptionWithContextToErrorDtoMapper mapper) { + this.mapper = mapper; this.type = type; this.status = status; } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/NotFoundExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/NotFoundExceptionMapper.java index add41bc3b2..944ec276ce 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/NotFoundExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/NotFoundExceptionMapper.java @@ -33,7 +33,9 @@ package sonia.scm.api.v2; import sonia.scm.NotFoundException; import sonia.scm.api.rest.ContextualExceptionMapper; +import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper; +import javax.inject.Inject; import javax.ws.rs.core.Response; import javax.ws.rs.ext.Provider; @@ -42,7 +44,8 @@ import javax.ws.rs.ext.Provider; */ @Provider public class NotFoundExceptionMapper extends ContextualExceptionMapper { - public NotFoundExceptionMapper() { - super(NotFoundException.class, Response.Status.NOT_FOUND); + @Inject + public NotFoundExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) { + super(NotFoundException.class, Response.Status.NOT_FOUND, mapper); } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java index 098ec40c3f..c8ba5e1fab 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ChangesetRootResourceTest.java @@ -51,7 +51,8 @@ public class ChangesetRootResourceTest extends RepositoryTestBase { public static final String CHANGESET_PATH = "space/repo/changesets/"; public static final String CHANGESET_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + CHANGESET_PATH; - private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + + private Dispatcher dispatcher; private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @@ -82,12 +83,10 @@ public class ChangesetRootResourceTest extends RepositoryTestBase { changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks); changesetRootResource = new ChangesetRootResource(serviceFactory, changesetCollectionToDtoMapper, changesetToChangesetDtoMapper); super.changesetRootResource = Providers.of(changesetRootResource); - dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource()); + dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource()); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService); when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService); when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); - dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class); - dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); when(repositoryService.getLogCommand()).thenReturn(logCommandBuilder); subjectThreadState.bind(); ThreadContext.bind(subject); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java index f1a1c3b441..516b2ff15c 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DiffResourceTest.java @@ -19,6 +19,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.NotFoundException; import sonia.scm.api.rest.AuthorizationExceptionMapper; +import sonia.scm.api.rest.ContextualExceptionMapper; import sonia.scm.api.v2.NotFoundExceptionMapper; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; @@ -69,7 +70,8 @@ public class DiffResourceTest extends RepositoryTestBase { when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); when(serviceFactory.create(any(Repository.class))).thenReturn(service); when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); - dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class); + ExceptionWithContextToErrorDtoMapperImpl mapper = new ExceptionWithContextToErrorDtoMapperImpl(); + dispatcher.getProviderFactory().register(new NotFoundExceptionMapper(mapper)); dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); dispatcher.getProviderFactory().registerProvider(CRLFInjectionExceptionMapper.class); when(service.getDiffCommand()).thenReturn(diffCommandBuilder); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java index 7dff9f5ff0..9c34549d34 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java @@ -12,10 +12,11 @@ public class DispatcherMock { public static Dispatcher createDispatcher(Object resource) { Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); dispatcher.getRegistry().addSingletonResource(resource); - dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class); - dispatcher.getProviderFactory().registerProvider(AlreadyExistsExceptionMapper.class); + ExceptionWithContextToErrorDtoMapperImpl mapper = new ExceptionWithContextToErrorDtoMapperImpl(); + dispatcher.getProviderFactory().register(new NotFoundExceptionMapper(mapper)); + dispatcher.getProviderFactory().register(new AlreadyExistsExceptionMapper(mapper)); + dispatcher.getProviderFactory().register(new ConcurrentModificationExceptionMapper(mapper)); dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); - dispatcher.getProviderFactory().registerProvider(ConcurrentModificationExceptionMapper.class); dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class); dispatcher.getProviderFactory().registerProvider(ChangePasswordNotAllowedExceptionMapper.class); dispatcher.getProviderFactory().registerProvider(InvalidPasswordExceptionMapper.class); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java index 2963f5d813..3898ac07e0 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java @@ -52,7 +52,6 @@ public class FileHistoryResourceTest extends RepositoryTestBase { public static final String FILE_HISTORY_PATH = "space/repo/history/"; public static final String FILE_HISTORY_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + FILE_HISTORY_PATH; - private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @@ -73,23 +72,21 @@ public class FileHistoryResourceTest extends RepositoryTestBase { private FileHistoryRootResource fileHistoryRootResource; + private Dispatcher dispatcher; private final Subject subject = mock(Subject.class); private final ThreadState subjectThreadState = new SubjectThreadState(subject); - @Before public void prepareEnvironment() { fileHistoryCollectionToDtoMapper = new FileHistoryCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks); fileHistoryRootResource = new FileHistoryRootResource(serviceFactory, fileHistoryCollectionToDtoMapper); super.fileHistoryRootResource = Providers.of(fileHistoryRootResource); - dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource()); + dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource()); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); when(serviceFactory.create(any(Repository.class))).thenReturn(service); when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); - dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class); - dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); - dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class); + ExceptionWithContextToErrorDtoMapperImpl mapper = new ExceptionWithContextToErrorDtoMapperImpl(); when(service.getLogCommand()).thenReturn(logCommandBuilder); subjectThreadState.bind(); ThreadContext.bind(subject); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java index 036579e9dd..f28cf49d03 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java @@ -62,7 +62,7 @@ public class GroupRootResourceTest { private ArgumentCaptor groupCaptor = ArgumentCaptor.forClass(Group.class); @Before - public void prepareEnvironment() throws Exception { + public void prepareEnvironment() { initMocks(this); when(groupManager.create(groupCaptor.capture())).thenAnswer(invocation -> invocation.getArguments()[0]); doNothing().when(groupManager).modify(groupCaptor.capture()); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java index 0377fe0942..b3eacf314e 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java @@ -46,7 +46,8 @@ public class ModificationsResourceTest extends RepositoryTestBase { public static final String MODIFICATIONS_PATH = "space/repo/modifications/"; public static final String MODIFICATIONS_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + MODIFICATIONS_PATH; - private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + + private Dispatcher dispatcher; private final URI baseUri = URI.create("/"); private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @@ -74,13 +75,10 @@ public class ModificationsResourceTest extends RepositoryTestBase { public void prepareEnvironment() throws Exception { modificationsRootResource = new ModificationsRootResource(serviceFactory, modificationsToDtoMapper); super.modificationsRootResource = Providers.of(modificationsRootResource); - dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource()); + dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource()); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(repositoryService); when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService); when(repositoryService.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo")); - dispatcher.getProviderFactory().registerProvider(NotFoundExceptionMapper.class); - dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); - dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class); when(repositoryService.getModificationsCommand()).thenReturn(modificationsCommandBuilder); subjectThreadState.bind(); ThreadContext.bind(subject); From f6fe299bb1fa11c287ac5f0246ede9f25bc4f497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 26 Oct 2018 14:59:26 +0200 Subject: [PATCH 13/27] Use constructor injection --- .../java/sonia/scm/api/v2/ValidationExceptionMapper.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/ValidationExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/ValidationExceptionMapper.java index 4926ed0f66..6fadce8500 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/ValidationExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/ValidationExceptionMapper.java @@ -1,9 +1,9 @@ package sonia.scm.api.v2; -import com.google.inject.Inject; import org.jboss.resteasy.api.validation.ResteasyViolationException; import sonia.scm.api.v2.resources.ViolationExceptionToErrorDtoMapper; +import javax.inject.Inject; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; @@ -12,8 +12,12 @@ import javax.ws.rs.ext.Provider; @Provider public class ValidationExceptionMapper implements ExceptionMapper { + private final ViolationExceptionToErrorDtoMapper mapper; + @Inject - private ViolationExceptionToErrorDtoMapper mapper; + public ValidationExceptionMapper(ViolationExceptionToErrorDtoMapper mapper) { + this.mapper = mapper; + } @Override public Response toResponse(ResteasyViolationException exception) { From 4a307379a2effeca2f42a0b6c4c938386702abf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 26 Oct 2018 15:15:51 +0200 Subject: [PATCH 14/27] Remove code duplication --- .../java/sonia/scm/AlreadyExistsException.java | 12 ++---------- .../scm/ConcurrentModificationException.java | 12 ++---------- .../java/sonia/scm/ExceptionWithContext.java | 18 ++++++++++++++---- .../main/java/sonia/scm/NotFoundException.java | 16 ++-------------- .../api/rest/ContextualExceptionMapper.java | 3 +-- 5 files changed, 21 insertions(+), 40 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/AlreadyExistsException.java b/scm-core/src/main/java/sonia/scm/AlreadyExistsException.java index 17863c364c..3ca8335346 100644 --- a/scm-core/src/main/java/sonia/scm/AlreadyExistsException.java +++ b/scm-core/src/main/java/sonia/scm/AlreadyExistsException.java @@ -3,15 +3,12 @@ package sonia.scm; import java.util.List; import static java.util.Collections.singletonList; -import static java.util.Collections.unmodifiableList; import static java.util.stream.Collectors.joining; -public class AlreadyExistsException extends RuntimeException implements ExceptionWithContext { +public class AlreadyExistsException extends ExceptionWithContext { private static final String CODE = "FtR7UznKU1"; - private final List context; - public AlreadyExistsException(ModelObject object) { this(singletonList(new ContextEntry(object.getClass(), object.getId()))); } @@ -21,12 +18,7 @@ public class AlreadyExistsException extends RuntimeException implements Exceptio } private AlreadyExistsException(List context) { - super(createMessage(context)); - this.context = context; - } - - public List getContext() { - return unmodifiableList(context); + super(context, createMessage(context)); } @Override diff --git a/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java b/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java index 6fb8ac2b62..d566859b4c 100644 --- a/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java +++ b/scm-core/src/main/java/sonia/scm/ConcurrentModificationException.java @@ -3,15 +3,12 @@ package sonia.scm; import java.util.Collections; import java.util.List; -import static java.util.Collections.unmodifiableList; import static java.util.stream.Collectors.joining; -public class ConcurrentModificationException extends RuntimeException implements ExceptionWithContext { +public class ConcurrentModificationException extends ExceptionWithContext { private static final String CODE = "2wR7UzpPG1"; - private final List context; - public ConcurrentModificationException(Class type, String id) { this(Collections.singletonList(new ContextEntry(type, id))); } @@ -21,12 +18,7 @@ public class ConcurrentModificationException extends RuntimeException implements } private ConcurrentModificationException(List context) { - super(createMessage(context)); - this.context = context; - } - - public List getContext() { - return unmodifiableList(context); + super(context, createMessage(context)); } @Override diff --git a/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java b/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java index 93edc1f63b..255738455c 100644 --- a/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java +++ b/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java @@ -2,10 +2,20 @@ package sonia.scm; import java.util.List; -public interface ExceptionWithContext { - List getContext(); +import static java.util.Collections.unmodifiableList; - String getMessage(); +public abstract class ExceptionWithContext extends RuntimeException { - String getCode(); + private final List context; + + public ExceptionWithContext(List context, String message) { + super(message); + this.context = context; + } + + public List getContext() { + return unmodifiableList(context); + } + + public abstract String getCode(); } diff --git a/scm-core/src/main/java/sonia/scm/NotFoundException.java b/scm-core/src/main/java/sonia/scm/NotFoundException.java index 30696c73b6..69b9617e93 100644 --- a/scm-core/src/main/java/sonia/scm/NotFoundException.java +++ b/scm-core/src/main/java/sonia/scm/NotFoundException.java @@ -1,21 +1,14 @@ package sonia.scm; -import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.Repository; - import java.util.Collections; -import java.util.LinkedList; import java.util.List; -import static java.util.Collections.unmodifiableList; import static java.util.stream.Collectors.joining; -public class NotFoundException extends RuntimeException implements ExceptionWithContext { +public class NotFoundException extends ExceptionWithContext { private static final String CODE = "AGR7UzkhA1"; - private final List context; - public NotFoundException(Class type, String id) { this(Collections.singletonList(new ContextEntry(type, id))); } @@ -29,12 +22,7 @@ public class NotFoundException extends RuntimeException implements ExceptionWith } private NotFoundException(List context) { - super(createMessage(context)); - this.context = context; - } - - public List getContext() { - return unmodifiableList(context); + super(context, createMessage(context)); } @Override diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java index b25af05d50..43b8a38ccc 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java @@ -1,6 +1,5 @@ package sonia.scm.api.rest; -import com.google.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.ExceptionWithContext; @@ -10,7 +9,7 @@ import sonia.scm.web.VndMediaType; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; -public class ContextualExceptionMapper implements ExceptionMapper { +public class ContextualExceptionMapper implements ExceptionMapper { private static final Logger logger = LoggerFactory.getLogger(ContextualExceptionMapper.class); From a285ddb2530600463abe198dec3a342213c57103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 26 Oct 2018 15:24:25 +0200 Subject: [PATCH 15/27] Adapt InvalidPasswordException and mapper --- .../sonia/scm/user/InvalidPasswordException.java | 16 +++++++++++++--- .../InvalidPasswordExceptionMapper.java | 14 +++++++------- .../java/sonia/scm/user/DefaultUserManager.java | 3 ++- .../scm/api/v2/resources/DispatcherMock.java | 2 +- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java b/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java index 870430a1bb..11fb11bef5 100644 --- a/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java +++ b/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java @@ -1,8 +1,18 @@ package sonia.scm.user; -public class InvalidPasswordException extends RuntimeException { +import sonia.scm.ContextEntry; +import sonia.scm.ExceptionWithContext; - public InvalidPasswordException() { - super("The given Password does not match with the stored one."); +public class InvalidPasswordException extends ExceptionWithContext { + + private static final String CODE = "8YR7aawFW1"; + + public InvalidPasswordException(ContextEntry.ContextBuilder passwordChange) { + super(passwordChange.build(), "The given old password does not match with the stored one."); + } + + @Override + public String getCode() { + return CODE; } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InvalidPasswordExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InvalidPasswordExceptionMapper.java index 7c5364ba03..7a1d311a1c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InvalidPasswordExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InvalidPasswordExceptionMapper.java @@ -1,17 +1,17 @@ package sonia.scm.api.v2.resources; +import sonia.scm.api.rest.ContextualExceptionMapper; import sonia.scm.user.InvalidPasswordException; +import javax.inject.Inject; import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; @Provider -public class InvalidPasswordExceptionMapper implements ExceptionMapper { - @Override - public Response toResponse(InvalidPasswordException exception) { - return Response.status(Response.Status.BAD_REQUEST) - .entity(exception.getMessage()) - .build(); +public class InvalidPasswordExceptionMapper extends ContextualExceptionMapper { + + @Inject + public InvalidPasswordExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) { + super(InvalidPasswordException.class, Response.Status.BAD_REQUEST, mapper); } } diff --git a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java index 807024c36b..41d2ed467f 100644 --- a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java +++ b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java @@ -39,6 +39,7 @@ import com.google.inject.Singleton; import org.apache.shiro.SecurityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.ContextEntry; import sonia.scm.EagerSingleton; import sonia.scm.HandlerEventType; import sonia.scm.ManagerDaoAdapter; @@ -402,7 +403,7 @@ public class DefaultUserManager extends AbstractUserManager User user = get((String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal()); if (!user.getPassword().equals(oldPassword)) { - throw new InvalidPasswordException(); + throw new InvalidPasswordException(ContextEntry.ContextBuilder.entity("passwordChange", "-").in(User.class, user.getName())); } user.setPassword(newPassword); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java index 9c34549d34..0d95d0a642 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java @@ -19,7 +19,7 @@ public class DispatcherMock { dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class); dispatcher.getProviderFactory().registerProvider(ChangePasswordNotAllowedExceptionMapper.class); - dispatcher.getProviderFactory().registerProvider(InvalidPasswordExceptionMapper.class); + dispatcher.getProviderFactory().register(new InvalidPasswordExceptionMapper(mapper)); dispatcher.getProviderFactory().registerProvider(IllegalArgumentExceptionMapper.class); return dispatcher; } From 007baecc86eef6b60e8ad887d29c17f29fbeafa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 26 Oct 2018 16:43:52 +0200 Subject: [PATCH 16/27] Fix unit test --- .../main/java/sonia/scm/user/InvalidPasswordException.java | 4 ++-- .../test/java/sonia/scm/api/v2/resources/MeResourceTest.java | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java b/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java index 11fb11bef5..93a6a7c1d1 100644 --- a/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java +++ b/scm-core/src/main/java/sonia/scm/user/InvalidPasswordException.java @@ -7,8 +7,8 @@ public class InvalidPasswordException extends ExceptionWithContext { private static final String CODE = "8YR7aawFW1"; - public InvalidPasswordException(ContextEntry.ContextBuilder passwordChange) { - super(passwordChange.build(), "The given old password does not match with the stored one."); + public InvalidPasswordException(ContextEntry.ContextBuilder context) { + super(context.build(), "The given old password does not match with the stored one."); } @Override diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java index 143b469650..454e24aa92 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java @@ -13,6 +13,7 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; +import sonia.scm.ContextEntry; import sonia.scm.user.InvalidPasswordException; import sonia.scm.user.User; import sonia.scm.user.UserManager; @@ -168,7 +169,8 @@ public class MeResourceTest { .content(content.getBytes()); MockHttpResponse response = new MockHttpResponse(); - doThrow(InvalidPasswordException.class).when(userManager).changePasswordForLoggedInUser(any(), any()); + doThrow(new InvalidPasswordException(ContextEntry.ContextBuilder.entity("passwortChange", "-"))) + .when(userManager).changePasswordForLoggedInUser(any(), any()); dispatcher.invoke(request, response); From 4425243f03c7acebd6a00901e3676734f675e3de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 29 Oct 2018 09:26:23 +0100 Subject: [PATCH 17/27] Adapt ChangePasswordNotAllowedException and mapper --- .../user/ChangePasswordNotAllowedException.java | 16 ++++++++++++---- .../ChangePasswordNotAllowedExceptionMapper.java | 14 +++++++------- .../java/sonia/scm/user/DefaultUserManager.java | 2 +- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java b/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java index fda5e69323..da0b1b0ba3 100644 --- a/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java +++ b/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java @@ -1,11 +1,19 @@ package sonia.scm.user; -public class ChangePasswordNotAllowedException extends RuntimeException { +import sonia.scm.ContextEntry; +import sonia.scm.ExceptionWithContext; - public static final String WRONG_USER_TYPE = "User of type %s are not allowed to change password"; +public class ChangePasswordNotAllowedException extends ExceptionWithContext { - public ChangePasswordNotAllowedException(String type) { - super(String.format(WRONG_USER_TYPE, type)); + private static final String CODE = "9BR7qpDAe1"; + public static final String WRONG_USER_TYPE = "User of given type are not allowed to change password"; + + public ChangePasswordNotAllowedException(ContextEntry.ContextBuilder context) { + super(context.build(), WRONG_USER_TYPE); } + @Override + public String getCode() { + return CODE; + } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangePasswordNotAllowedExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangePasswordNotAllowedExceptionMapper.java index e9bb5304a5..18a6e6e75c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangePasswordNotAllowedExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangePasswordNotAllowedExceptionMapper.java @@ -1,17 +1,17 @@ package sonia.scm.api.v2.resources; +import sonia.scm.api.rest.ContextualExceptionMapper; import sonia.scm.user.ChangePasswordNotAllowedException; +import sonia.scm.user.InvalidPasswordException; +import javax.inject.Inject; import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; @Provider -public class ChangePasswordNotAllowedExceptionMapper implements ExceptionMapper { - @Override - public Response toResponse(ChangePasswordNotAllowedException exception) { - return Response.status(Response.Status.BAD_REQUEST) - .entity(exception.getMessage()) - .build(); +public class ChangePasswordNotAllowedExceptionMapper extends ContextualExceptionMapper { + @Inject + public ChangePasswordNotAllowedExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) { + super(ChangePasswordNotAllowedException.class, Response.Status.BAD_REQUEST, mapper); } } diff --git a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java index 41d2ed467f..19d6a8343b 100644 --- a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java +++ b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java @@ -422,7 +422,7 @@ public class DefaultUserManager extends AbstractUserManager throw new NotFoundException(User.class, userId); } if (!isTypeDefault(user)) { - throw new ChangePasswordNotAllowedException(user.getType()); + throw new ChangePasswordNotAllowedException(ContextEntry.ContextBuilder.entity("passwordChange", "-").in(User.class, user.getName())); } user.setPassword(newPassword); this.modify(user); From 224b8e6af4d2924379f4eee82b67908bf0d1938d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 29 Oct 2018 09:41:10 +0100 Subject: [PATCH 18/27] Add type back to exception message --- .../sonia/scm/user/ChangePasswordNotAllowedException.java | 6 +++--- .../src/main/java/sonia/scm/user/DefaultUserManager.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java b/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java index da0b1b0ba3..caa35e0b88 100644 --- a/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java +++ b/scm-core/src/main/java/sonia/scm/user/ChangePasswordNotAllowedException.java @@ -6,10 +6,10 @@ import sonia.scm.ExceptionWithContext; public class ChangePasswordNotAllowedException extends ExceptionWithContext { private static final String CODE = "9BR7qpDAe1"; - public static final String WRONG_USER_TYPE = "User of given type are not allowed to change password"; + public static final String WRONG_USER_TYPE = "User of type %s are not allowed to change password"; - public ChangePasswordNotAllowedException(ContextEntry.ContextBuilder context) { - super(context.build(), WRONG_USER_TYPE); + public ChangePasswordNotAllowedException(ContextEntry.ContextBuilder context, String type) { + super(context.build(), String.format(WRONG_USER_TYPE, type)); } @Override diff --git a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java index 19d6a8343b..33762a5941 100644 --- a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java +++ b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java @@ -422,7 +422,7 @@ public class DefaultUserManager extends AbstractUserManager throw new NotFoundException(User.class, userId); } if (!isTypeDefault(user)) { - throw new ChangePasswordNotAllowedException(ContextEntry.ContextBuilder.entity("passwordChange", "-").in(User.class, user.getName())); + throw new ChangePasswordNotAllowedException(ContextEntry.ContextBuilder.entity("passwordChange", "-").in(User.class, user.getName()), user.getType()); } user.setPassword(newPassword); this.modify(user); From 8a07287b58e0d363eb73b141d51ad95842b5213b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 29 Oct 2018 10:10:53 +0100 Subject: [PATCH 19/27] Fix unit tests --- .../test/java/sonia/scm/api/v2/resources/DispatcherMock.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java index 0d95d0a642..4bda5c875d 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java @@ -18,7 +18,7 @@ public class DispatcherMock { dispatcher.getProviderFactory().register(new ConcurrentModificationExceptionMapper(mapper)); dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class); - dispatcher.getProviderFactory().registerProvider(ChangePasswordNotAllowedExceptionMapper.class); + dispatcher.getProviderFactory().register(new ChangePasswordNotAllowedExceptionMapper(mapper)); dispatcher.getProviderFactory().register(new InvalidPasswordExceptionMapper(mapper)); dispatcher.getProviderFactory().registerProvider(IllegalArgumentExceptionMapper.class); return dispatcher; From f1c9fe56f0fd1a44588ec292973054450147dc04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 29 Oct 2018 10:30:17 +0100 Subject: [PATCH 20/27] Fix unit test --- .../java/sonia/scm/api/v2/resources/UserRootResourceTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java index 1451498124..077381cc37 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java @@ -14,6 +14,7 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; +import sonia.scm.ContextEntry; import sonia.scm.NotFoundException; import sonia.scm.PageResult; import sonia.scm.user.ChangePasswordNotAllowedException; @@ -167,7 +168,7 @@ public class UserRootResourceTest { .content(content.getBytes()); MockHttpResponse response = new MockHttpResponse(); - doThrow(ChangePasswordNotAllowedException.class).when(userManager).overwritePassword(any(), any()); + doThrow(new ChangePasswordNotAllowedException(ContextEntry.ContextBuilder.entity("passwordChange", "-"), "xml")).when(userManager).overwritePassword(any(), any()); dispatcher.invoke(request, response); From a1f16713ccf909aab2699e0e3ba2dbd840d54747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 2 Nov 2018 08:20:57 +0100 Subject: [PATCH 21/27] Remove unused constructor --- .../scm/NotSupportedFeatureException.java | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/NotSupportedFeatureException.java b/scm-core/src/main/java/sonia/scm/NotSupportedFeatureException.java index 2270ed37b3..0d18882675 100644 --- a/scm-core/src/main/java/sonia/scm/NotSupportedFeatureException.java +++ b/scm-core/src/main/java/sonia/scm/NotSupportedFeatureException.java @@ -38,26 +38,10 @@ package sonia.scm; * @author Sebastian Sdorra * @version 1.6 */ -public class NotSupportedFeatureException extends Exception -{ +public class NotSupportedFeatureException extends Exception { - /** Field description */ private static final long serialVersionUID = 256498734456613496L; - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - */ - public NotSupportedFeatureException() {} - - /** - * Constructs ... - * - * - * @param message - */ public NotSupportedFeatureException(String message) { super(message); From 359a857ca3e31ad14c1c56b9c27d9f1adb545fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 2 Nov 2018 16:21:23 +0100 Subject: [PATCH 22/27] Migrate NotSupportedFeatureException --- .../scm/NotSupportedFeatureException.java | 19 ++++++++++++++++--- .../repository/AbstractRepositoryHandler.java | 3 +-- .../NotSupportedFeatureExceptionMapper.java | 15 +++++++++++++++ .../scm/api/v2/resources/DispatcherMock.java | 2 ++ 4 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/NotSupportedFeatureExceptionMapper.java diff --git a/scm-core/src/main/java/sonia/scm/NotSupportedFeatureException.java b/scm-core/src/main/java/sonia/scm/NotSupportedFeatureException.java index 0d18882675..daf996ee6c 100644 --- a/scm-core/src/main/java/sonia/scm/NotSupportedFeatureException.java +++ b/scm-core/src/main/java/sonia/scm/NotSupportedFeatureException.java @@ -33,17 +33,30 @@ package sonia.scm; +import java.util.Collections; + /** * * @author Sebastian Sdorra * @version 1.6 */ -public class NotSupportedFeatureException extends Exception { +public class NotSupportedFeatureException extends ExceptionWithContext { private static final long serialVersionUID = 256498734456613496L; - public NotSupportedFeatureException(String message) + private static final String CODE = "9SR8G0kmU1"; + + public NotSupportedFeatureException(String feature) { - super(message); + super(Collections.emptyList(),createMessage(feature)); + } + + @Override + public String getCode() { + return CODE; + } + + private static String createMessage(String feature) { + return "feature " + feature + " is not supported by this repository"; } } diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java index a545aefdc8..42c8f22a0f 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java @@ -170,8 +170,7 @@ public abstract class AbstractRepositoryHandler @Override public ImportHandler getImportHandler() throws NotSupportedFeatureException { - throw new NotSupportedFeatureException( - "import handler is not supported by this repository handler"); + throw new NotSupportedFeatureException("import"); } /** diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/NotSupportedFeatureExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/NotSupportedFeatureExceptionMapper.java new file mode 100644 index 0000000000..b2cba5b3d4 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/NotSupportedFeatureExceptionMapper.java @@ -0,0 +1,15 @@ +package sonia.scm.api.v2; + +import sonia.scm.NotSupportedFeatureException; +import sonia.scm.api.rest.ContextualExceptionMapper; +import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper; + +import javax.inject.Inject; +import javax.ws.rs.core.Response; + +public class NotSupportedFeatureExceptionMapper extends ContextualExceptionMapper { + @Inject + public NotSupportedFeatureExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) { + super(NotSupportedFeatureException.class, Response.Status.BAD_REQUEST, mapper); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java index 4bda5c875d..c8609b0712 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java @@ -7,6 +7,7 @@ import sonia.scm.api.rest.AuthorizationExceptionMapper; import sonia.scm.api.rest.ConcurrentModificationExceptionMapper; import sonia.scm.api.rest.IllegalArgumentExceptionMapper; import sonia.scm.api.v2.NotFoundExceptionMapper; +import sonia.scm.api.v2.NotSupportedFeatureExceptionMapper; public class DispatcherMock { public static Dispatcher createDispatcher(Object resource) { @@ -21,6 +22,7 @@ public class DispatcherMock { dispatcher.getProviderFactory().register(new ChangePasswordNotAllowedExceptionMapper(mapper)); dispatcher.getProviderFactory().register(new InvalidPasswordExceptionMapper(mapper)); dispatcher.getProviderFactory().registerProvider(IllegalArgumentExceptionMapper.class); + dispatcher.getProviderFactory().register(new NotSupportedFeatureExceptionMapper(mapper)); return dispatcher; } } From ee88cea1ea9817715579f6432d8b02fc1ec2434c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 2 Nov 2018 17:12:11 +0100 Subject: [PATCH 23/27] Mark exception mapper as provider --- .../sonia/scm/api/v2/NotSupportedFeatureExceptionMapper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/NotSupportedFeatureExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/NotSupportedFeatureExceptionMapper.java index b2cba5b3d4..6a48663aa5 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/NotSupportedFeatureExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/NotSupportedFeatureExceptionMapper.java @@ -6,7 +6,9 @@ import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper; import javax.inject.Inject; import javax.ws.rs.core.Response; +import javax.ws.rs.ext.Provider; +@Provider public class NotSupportedFeatureExceptionMapper extends ContextualExceptionMapper { @Inject public NotSupportedFeatureExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) { From 9a1878f3e8d99e218250ddbba6f081f57c5dcba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 2 Nov 2018 17:12:49 +0100 Subject: [PATCH 24/27] Add fallback exception mapper --- .../scm/api/FallbackExceptionMapper.java | 43 +++++++++++++++++++ .../api/rest/ContextualExceptionMapper.java | 2 +- .../sonia/scm/api/v2/resources/ErrorDto.java | 2 +- 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/FallbackExceptionMapper.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/FallbackExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/FallbackExceptionMapper.java new file mode 100644 index 0000000000..feb5341e2d --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/FallbackExceptionMapper.java @@ -0,0 +1,43 @@ +package sonia.scm.api; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import sonia.scm.api.v2.resources.ErrorDto; +import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper; +import sonia.scm.web.VndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.Collections; + +@Provider +public class FallbackExceptionMapper implements ExceptionMapper { + + private static final Logger logger = LoggerFactory.getLogger(FallbackExceptionMapper.class); + + private static final String ERROR_CODE = "CmR8GCJb31"; + + private final ExceptionWithContextToErrorDtoMapper mapper; + + @Inject + public FallbackExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) { + this.mapper = mapper; + } + + @Override + public Response toResponse(Exception exception) { + logger.debug("map {} to status code 500", exception); + ErrorDto errorDto = new ErrorDto(); + errorDto.setMessage("internal server error"); + errorDto.setContext(Collections.emptyList()); + errorDto.setErrorCode(ERROR_CODE); + errorDto.setTransactionId(MDC.get("transaction_id")); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(errorDto) + .type(VndMediaType.ERROR_TYPE) + .build(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java index 43b8a38ccc..d8ab4033d1 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java @@ -13,7 +13,7 @@ public class ContextualExceptionMapper implement private static final Logger logger = LoggerFactory.getLogger(ContextualExceptionMapper.class); - private ExceptionWithContextToErrorDtoMapper mapper; + private final ExceptionWithContextToErrorDtoMapper mapper; private final Response.Status status; private final Class type; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java index f0df654866..bd889d5de5 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ErrorDto.java @@ -17,7 +17,7 @@ public class ErrorDto { private List context; private String message; - @XmlElement(name = "violation") + @JsonInclude(JsonInclude.Include.NON_NULL) @XmlElementWrapper(name = "violations") private List violations; From cf05235dfa8db72bed864119039e52278ee48737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 6 Nov 2018 09:14:20 +0100 Subject: [PATCH 25/27] Add context to InternalRepositoryException --- .../src/main/java/sonia/scm/ContextEntry.java | 8 +++++ .../java/sonia/scm/ExceptionWithContext.java | 5 ++++ .../AbstractSimpleRepositoryHandler.java | 3 +- .../InternalRepositoryException.java | 29 ++++++++++++++----- .../java/sonia/scm/repository/GitUtil.java | 3 +- .../AbstractGitIncomingOutgoingCommand.java | 8 +---- .../spi/AbstractGitPushOrPullCommand.java | 2 +- .../scm/repository/spi/GitBlameCommand.java | 9 +++--- .../repository/spi/GitBranchesCommand.java | 2 +- .../scm/repository/spi/GitLogCommand.java | 2 +- .../spi/GitModificationsCommand.java | 13 ++++----- .../scm/repository/spi/GitPullCommand.java | 2 +- .../scm/repository/spi/GitTagsCommand.java | 2 +- .../UnsupportedModificationTypeException.java | 5 ++-- .../scm/repository/AbstractHgHandler.java | 2 +- .../scm/repository/spi/HgCatCommand.java | 8 ++++- .../scm/repository/spi/HgIncomingCommand.java | 2 +- .../scm/repository/spi/HgOutgoingCommand.java | 2 +- .../scm/repository/spi/HgPullCommand.java | 2 +- .../scm/repository/spi/HgPushCommand.java | 2 +- .../scm/repository/SvnRepositoryHandler.java | 5 +++- .../scm/repository/spi/SvnBlameCommand.java | 2 +- .../scm/repository/spi/SvnCatCommand.java | 4 +-- .../scm/repository/spi/SvnDiffCommand.java | 2 +- .../scm/repository/spi/SvnLogCommand.java | 4 +-- .../spi/SvnModificationsCommand.java | 2 +- .../api/rest/ContextualExceptionMapper.java | 2 +- .../v2/resources/FileHistoryRootResource.java | 11 ++++--- .../InternalRepositoryExceptionMapper.java | 10 ++++--- .../repository/DefaultRepositoryManager.java | 6 ++-- .../scm/api/v2/resources/DispatcherMock.java | 2 +- .../v2/resources/FileHistoryResourceTest.java | 10 +++---- .../resources/ModificationsResourceTest.java | 8 ++--- 33 files changed, 106 insertions(+), 73 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/ContextEntry.java b/scm-core/src/main/java/sonia/scm/ContextEntry.java index 591f29972a..bbd2fadc5d 100644 --- a/scm-core/src/main/java/sonia/scm/ContextEntry.java +++ b/scm-core/src/main/java/sonia/scm/ContextEntry.java @@ -35,6 +35,14 @@ public class ContextEntry { public static class ContextBuilder { private final List context = new LinkedList<>(); + public static List noContext() { + return new ContextBuilder().build(); + } + + public static List only(String type, String id) { + return new ContextBuilder().in(type, id).build(); + } + public static ContextBuilder entity(Repository repository) { return new ContextBuilder().in(repository.getNamespaceAndName()); } diff --git a/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java b/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java index 255738455c..dd87b77210 100644 --- a/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java +++ b/scm-core/src/main/java/sonia/scm/ExceptionWithContext.java @@ -13,6 +13,11 @@ public abstract class ExceptionWithContext extends RuntimeException { this.context = context; } + public ExceptionWithContext(List context, String message, Exception cause) { + super(message, cause); + this.context = context; + } + public List getContext() { return unmodifiableList(context); } diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java index 997413aacb..6cfa08975e 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java @@ -40,6 +40,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.AlreadyExistsException; import sonia.scm.ConfigurationException; +import sonia.scm.ContextEntry; import sonia.scm.io.CommandResult; import sonia.scm.io.ExtendedCommand; import sonia.scm.io.FileSystem; @@ -128,7 +129,7 @@ public abstract class AbstractSimpleRepositoryHandler context, String message, Exception cause) { + super(context, message, cause); + } + + @Override + public String getCode() { + return null; } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java index 13340a20e7..4524822a6a 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java @@ -55,6 +55,7 @@ import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FS; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.ContextEntry; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; import sonia.scm.web.GitUserAgentProvider; @@ -203,7 +204,7 @@ public final class GitUtil } catch (GitAPIException ex) { - throw new InternalRepositoryException("could not fetch", ex); + throw new InternalRepositoryException(ContextEntry.ContextBuilder.entity("remote", directory.toString()).in(remoteRepository), "could not fetch", ex); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitIncomingOutgoingCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitIncomingOutgoingCommand.java index e90a1c11ed..3cf72166ea 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitIncomingOutgoingCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitIncomingOutgoingCommand.java @@ -160,7 +160,7 @@ public abstract class AbstractGitIncomingOutgoingCommand } catch (Exception ex) { - throw new InternalRepositoryException("could not execute incoming command", ex); + throw new InternalRepositoryException(repository, "could not execute incoming command", ex); } finally { @@ -200,13 +200,7 @@ public abstract class AbstractGitIncomingOutgoingCommand { if (e.getKey().startsWith(prefix)) { - if (ref != null) - { - throw new InternalRepositoryException("could not find remote branch"); - } - ref = e.getValue(); - break; } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java index 75050c26ea..e4e37d6fed 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitPushOrPullCommand.java @@ -114,7 +114,7 @@ public abstract class AbstractGitPushOrPullCommand extends AbstractGitCommand } catch (Exception ex) { - throw new InternalRepositoryException("could not execute push/pull command", ex); + throw new InternalRepositoryException(repository, "could not execute push/pull command", ex); } return counter; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBlameCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBlameCommand.java index f50f245963..2ad38648da 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBlameCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBlameCommand.java @@ -55,6 +55,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import static sonia.scm.ContextEntry.ContextBuilder.entity; + //~--- JDK imports ------------------------------------------------------------ /** @@ -108,9 +110,8 @@ public class GitBlameCommand extends AbstractGitCommand implements BlameCommand if (gitBlameResult == null) { - throw new InternalRepositoryException( - "could not create blame result for path ".concat( - request.getPath())); + throw new InternalRepositoryException(entity("path", request.getPath()).in(repository), + "could not create blame result for path"); } List blameLines = new ArrayList(); @@ -150,7 +151,7 @@ public class GitBlameCommand extends AbstractGitCommand implements BlameCommand } catch (GitAPIException ex) { - throw new InternalRepositoryException("could not create blame view", ex); + throw new InternalRepositoryException(repository, "could not create blame view", ex); } return result; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java index 0cc47100de..4922752b6f 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java @@ -102,7 +102,7 @@ public class GitBranchesCommand extends AbstractGitCommand } catch (GitAPIException ex) { - throw new InternalRepositoryException("could not read branches", ex); + throw new InternalRepositoryException(repository, "could not read branches", ex); } return branches; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java index 8a3745dce7..f8a5b79a8b 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java @@ -254,7 +254,7 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand } catch (Exception ex) { - throw new InternalRepositoryException("could not create change log", ex); + throw new InternalRepositoryException(repository, "could not create change log", ex); } finally { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModificationsCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModificationsCommand.java index 2b35ba74f6..5040069c12 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModificationsCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModificationsCommand.java @@ -17,6 +17,8 @@ import java.io.IOException; import java.text.MessageFormat; import java.util.List; +import static sonia.scm.ContextEntry.ContextBuilder.entity; + @Slf4j public class GitModificationsCommand extends AbstractGitCommand implements ModificationsCommand { @@ -26,7 +28,7 @@ public class GitModificationsCommand extends AbstractGitCommand implements Modif } private Modifications createModifications(TreeWalk treeWalk, RevCommit commit, RevWalk revWalk, String revision) - throws IOException, UnsupportedModificationTypeException { + throws IOException { treeWalk.reset(); treeWalk.setRecursive(true); if (commit.getParentCount() > 0) { @@ -73,12 +75,7 @@ public class GitModificationsCommand extends AbstractGitCommand implements Modif } } catch (IOException ex) { log.error("could not open repository", ex); - throw new InternalRepositoryException(ex); - - } catch (UnsupportedModificationTypeException ex) { - log.error("Unsupported modification type", ex); - throw new InternalRepositoryException(ex); - + throw new InternalRepositoryException(entity(repository), "could not open repository", ex); } finally { GitUtil.release(revWalk); GitUtil.close(gitRepository); @@ -100,7 +97,7 @@ public class GitModificationsCommand extends AbstractGitCommand implements Modif } else if (type == DiffEntry.ChangeType.DELETE) { modifications.getRemoved().add(entry.getOldPath()); } else { - throw new UnsupportedModificationTypeException(MessageFormat.format("The modification type: {0} is not supported.", type)); + throw new UnsupportedModificationTypeException(entity(repository), MessageFormat.format("The modification type: {0} is not supported.", type)); } } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java index a7b341ff5d..bb06e1fd9d 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java @@ -249,7 +249,7 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand } catch (GitAPIException ex) { - throw new InternalRepositoryException("error durring pull", ex); + throw new InternalRepositoryException(repository, "error during pull", ex); } return response; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java index 02fee3cef0..807ec807e6 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java @@ -95,7 +95,7 @@ public class GitTagsCommand extends AbstractGitCommand implements TagsCommand } catch (GitAPIException ex) { - throw new InternalRepositoryException("could not read tags from repository", ex); + throw new InternalRepositoryException(repository, "could not read tags from repository", ex); } finally { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/UnsupportedModificationTypeException.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/UnsupportedModificationTypeException.java index 5081a29d21..85119a3e9f 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/UnsupportedModificationTypeException.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/UnsupportedModificationTypeException.java @@ -1,9 +1,10 @@ package sonia.scm.repository.spi; +import sonia.scm.ContextEntry; import sonia.scm.repository.InternalRepositoryException; public class UnsupportedModificationTypeException extends InternalRepositoryException { - public UnsupportedModificationTypeException(String message) { - super(message); + public UnsupportedModificationTypeException(ContextEntry.ContextBuilder entity, String message) { + super(entity, message); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/AbstractHgHandler.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/AbstractHgHandler.java index 19cfd37665..4115a514a2 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/AbstractHgHandler.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/AbstractHgHandler.java @@ -272,7 +272,7 @@ public class AbstractHgHandler } catch (JAXBException ex) { logger.error("could not parse result", ex); - throw new InternalRepositoryException("could not parse result", ex); + throw new InternalRepositoryException(repository, "could not parse result", ex); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCatCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCatCommand.java index 554d6aaf05..6309720f75 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCatCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgCatCommand.java @@ -36,6 +36,9 @@ package sonia.scm.repository.spi; import com.aragost.javahg.commands.ExecutionException; import com.google.common.io.ByteStreams; import com.google.common.io.Closeables; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.ContextEntry; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; import sonia.scm.web.HgUtil; @@ -46,6 +49,8 @@ import java.io.OutputStream; public class HgCatCommand extends AbstractCommand implements CatCommand { + private static final Logger log = LoggerFactory.getLogger(HgCatCommand.class); + HgCatCommand(HgCommandContext context, Repository repository) { super(context, repository); } @@ -70,7 +75,8 @@ public class HgCatCommand extends AbstractCommand implements CatCommand { try { return cmd.execute(request.getPath()); } catch (ExecutionException e) { - throw new InternalRepositoryException(e); + log.error("could not execute cat command", e); + throw new InternalRepositoryException(ContextEntry.ContextBuilder.entity(getRepository()), "could not execute cat command", e); } } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgIncomingCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgIncomingCommand.java index 55cf0fbe01..c60f2c5712 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgIncomingCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgIncomingCommand.java @@ -103,7 +103,7 @@ public class HgIncomingCommand extends AbstractCommand } else { - throw new InternalRepositoryException("could not execute incoming command", ex); + throw new InternalRepositoryException(getRepository(), "could not execute incoming command", ex); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgOutgoingCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgOutgoingCommand.java index b476b6c2ab..81bee6b9ff 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgOutgoingCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgOutgoingCommand.java @@ -103,7 +103,7 @@ public class HgOutgoingCommand extends AbstractCommand } else { - throw new InternalRepositoryException("could not execute outgoing command", ex); + throw new InternalRepositoryException(getRepository(), "could not execute outgoing command", ex); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPullCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPullCommand.java index 0eb23ef4e7..1d130a0f79 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPullCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPullCommand.java @@ -97,7 +97,7 @@ public class HgPullCommand extends AbstractHgPushOrPullCommand } catch (ExecutionException ex) { - throw new InternalRepositoryException("could not execute push command", ex); + throw new InternalRepositoryException(getRepository(), "could not execute push command", ex); } return new PullResponse(result.size()); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPushCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPushCommand.java index fb0a1a1c73..b1c12037db 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPushCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgPushCommand.java @@ -97,7 +97,7 @@ public class HgPushCommand extends AbstractHgPushOrPullCommand } catch (ExecutionException ex) { - throw new InternalRepositoryException("could not execute push command", ex); + throw new InternalRepositoryException(getRepository(), "could not execute push command", ex); } return new PushResponse(result.size()); diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java index 7d5c9695c9..cd242faa2d 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java @@ -56,6 +56,8 @@ import sonia.scm.util.Util; import java.io.File; +import static sonia.scm.ContextEntry.ContextBuilder.entity; + //~--- JDK imports ------------------------------------------------------------ /** @@ -173,7 +175,8 @@ public class SvnRepositoryHandler } catch (SVNException ex) { - throw new InternalRepositoryException(ex); + logger.error("could not create svn repository", ex); + throw new InternalRepositoryException(entity(repository), "could not create repository", ex); } finally { diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBlameCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBlameCommand.java index fe9a72ffce..b589734d03 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBlameCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnBlameCommand.java @@ -98,7 +98,7 @@ public class SvnBlameCommand extends AbstractSvnCommand implements BlameCommand } catch (SVNException ex) { - throw new InternalRepositoryException("could not create blame result", ex); + throw new InternalRepositoryException(repository, "could not create blame result", ex); } return new BlameResult(blameLines.size(), blameLines); diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnCatCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnCatCommand.java index 9a5c39af46..9ee43b2259 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnCatCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnCatCommand.java @@ -137,7 +137,7 @@ public class SvnCatCommand extends AbstractSvnCommand implements CatCommand } else if (SVNErrorCode.FS_NO_SUCH_REVISION.getCode() == svnErrorCode) { throw notFound(entity("Revision", request.getRevision()).in(repository)); } else { - throw new InternalRepositoryException("could not get content from revision", ex); + throw new InternalRepositoryException(repository, "could not get content from revision", ex); } } @@ -157,7 +157,7 @@ public class SvnCatCommand extends AbstractSvnCommand implements CatCommand } catch (SVNException ex) { - throw new InternalRepositoryException("could not get content from transaction", ex); + throw new InternalRepositoryException(repository, "could not get content from transaction", ex); } finally { diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java index 9e81f1a7a9..8418fd63b8 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java @@ -120,7 +120,7 @@ public class SvnDiffCommand extends AbstractSvnCommand implements DiffCommand } catch (SVNException ex) { - throw new InternalRepositoryException("could not create diff", ex); + throw new InternalRepositoryException(repository, "could not create diff", ex); } finally { diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java index a29b8f4a0c..be102be1bd 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnLogCommand.java @@ -97,7 +97,7 @@ public class SvnLogCommand extends AbstractSvnCommand implements LogCommand } catch (SVNException ex) { - throw new InternalRepositoryException("could not open repository", ex); + throw new InternalRepositoryException(repository, "could not open repository", ex); } return changeset; @@ -139,7 +139,7 @@ public class SvnLogCommand extends AbstractSvnCommand implements LogCommand } catch (SVNException ex) { - throw new InternalRepositoryException("could not open repository", ex); + throw new InternalRepositoryException(repository, "could not open repository", ex); } return changesets; diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java index 00fb1dc5a0..4b4f655b12 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnModificationsCommand.java @@ -34,7 +34,7 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif modifications = SvnUtil.createModifications(entries.iterator().next(), revision); } } catch (SVNException ex) { - throw new InternalRepositoryException("could not open repository", ex); + throw new InternalRepositoryException(repository, "could not open repository", ex); } return modifications; } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java index 43b8a38ccc..97d29aaa0f 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/ContextualExceptionMapper.java @@ -26,7 +26,7 @@ public class ContextualExceptionMapper implement @Override public Response toResponse(E exception) { - logger.debug("map {} to status code {}", type.getSimpleName(), status.getStatusCode()); + logger.debug("map {} to status code {}", type.getSimpleName(), status.getStatusCode(), exception); return Response.status(status) .entity(mapper.map(exception)) .type(VndMediaType.ERROR_TYPE) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java index faa7f4975c..017568b613 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileHistoryRootResource.java @@ -7,7 +7,6 @@ import lombok.extern.slf4j.Slf4j; import sonia.scm.PageResult; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; -import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.api.RepositoryService; @@ -24,6 +23,9 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import java.io.IOException; +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; + @Slf4j public class FileHistoryRootResource { @@ -66,7 +68,8 @@ public class FileHistoryRootResource { @PathParam("path") String path, @DefaultValue("0") @QueryParam("page") int page, @DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException { - try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { + NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name); + try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) { log.info("Get changesets of the file {} and revision {}", path, revision); Repository repository = repositoryService.getRepository(); ChangesetPagingResult changesets = new PagedLogCommandBuilder(repositoryService) @@ -80,9 +83,9 @@ public class FileHistoryRootResource { PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); return Response.ok(fileHistoryCollectionToDtoMapper.map(page, pageSize, pageResult, repository, revision, path)).build(); } else { - String message = String.format("for the revision %s and the file %s there is no changesets", revision, path); + String message = String.format("for the revision %s and the file %s there are no changesets", revision, path); log.error(message); - throw new InternalRepositoryException(message); + throw notFound(entity("path", path).in("revision", revision).in(namespaceAndName)); } } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InternalRepositoryExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InternalRepositoryExceptionMapper.java index d30677c5f7..856a9310a0 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InternalRepositoryExceptionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InternalRepositoryExceptionMapper.java @@ -1,15 +1,17 @@ package sonia.scm.api.v2.resources; -import sonia.scm.api.rest.StatusExceptionMapper; +import sonia.scm.api.rest.ContextualExceptionMapper; import sonia.scm.repository.InternalRepositoryException; +import javax.inject.Inject; import javax.ws.rs.core.Response; import javax.ws.rs.ext.Provider; @Provider -public class InternalRepositoryExceptionMapper extends StatusExceptionMapper { +public class InternalRepositoryExceptionMapper extends ContextualExceptionMapper { - public InternalRepositoryExceptionMapper() { - super(InternalRepositoryException.class, Response.Status.INTERNAL_SERVER_ERROR); + @Inject + public InternalRepositoryExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) { + super(InternalRepositoryException.class, Response.Status.INTERNAL_SERVER_ERROR, mapper); } } 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 6c903815a7..c409a860ec 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java @@ -143,7 +143,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { try { getHandler(newRepository).create(newRepository); } catch (AlreadyExistsException e) { - throw new InternalRepositoryException("directory for repository does already exist", e); + throw new InternalRepositoryException(repository, "directory for repository does already exist", e); } } fireEvent(HandlerEventType.BEFORE_CREATE, newRepository); @@ -353,9 +353,9 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { RepositoryHandler handler = handlerMap.get(type); if (handler == null) { - throw new InternalRepositoryException("could not find handler for " + type); + throw new InternalRepositoryException(entity(repository), "could not find handler for " + type); } else if (!handler.isConfigured()) { - throw new InternalRepositoryException("handler is not configured for type " + type); + throw new InternalRepositoryException(entity(repository), "handler is not configured for type " + type); } return handler; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java index 4bda5c875d..4b3e21fa60 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/DispatcherMock.java @@ -17,7 +17,7 @@ public class DispatcherMock { dispatcher.getProviderFactory().register(new AlreadyExistsExceptionMapper(mapper)); dispatcher.getProviderFactory().register(new ConcurrentModificationExceptionMapper(mapper)); dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class); - dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class); + dispatcher.getProviderFactory().register(new InternalRepositoryExceptionMapper(mapper)); dispatcher.getProviderFactory().register(new ChangePasswordNotAllowedExceptionMapper(mapper)); dispatcher.getProviderFactory().register(new InvalidPasswordExceptionMapper(mapper)); dispatcher.getProviderFactory().registerProvider(IllegalArgumentExceptionMapper.class); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java index 3898ac07e0..52c9a434c0 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/FileHistoryResourceTest.java @@ -8,7 +8,6 @@ import org.apache.shiro.util.ThreadContext; import org.apache.shiro.util.ThreadState; import org.assertj.core.util.Lists; import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; @@ -18,9 +17,8 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.ContextEntry; import sonia.scm.NotFoundException; -import sonia.scm.api.rest.AuthorizationExceptionMapper; -import sonia.scm.api.v2.NotFoundExceptionMapper; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.InternalRepositoryException; @@ -168,7 +166,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase { when(logCommandBuilder.setPagingLimit(anyInt())).thenReturn(logCommandBuilder); when(logCommandBuilder.setStartChangeset(eq(id))).thenReturn(logCommandBuilder); when(logCommandBuilder.setPath(eq(path))).thenReturn(logCommandBuilder); - when(logCommandBuilder.getChangesets()).thenThrow(InternalRepositoryException.class); + when(logCommandBuilder.getChangesets()).thenThrow(new InternalRepositoryException(ContextEntry.ContextBuilder.noContext(), "", new RuntimeException())); MockHttpRequest request = MockHttpRequest .get(FILE_HISTORY_URL + id + "/" + path) @@ -179,7 +177,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase { } @Test - public void shouldGet500OnNullChangesets() throws Exception { + public void shouldGet404OnNullChangesets() throws Exception { String id = "revision_123"; String path = "root_dir/sub_dir/file-to-inspect.txt"; @@ -194,6 +192,6 @@ public class FileHistoryResourceTest extends RepositoryTestBase { .accept(VndMediaType.CHANGESET_COLLECTION); MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); - assertEquals(500, response.getStatus()); + assertEquals(404, response.getStatus()); } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java index b3eacf314e..fc4598081d 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ModificationsResourceTest.java @@ -8,7 +8,6 @@ import org.apache.shiro.util.ThreadContext; import org.apache.shiro.util.ThreadState; import org.assertj.core.util.Lists; import org.jboss.resteasy.core.Dispatcher; -import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.After; @@ -18,8 +17,6 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import sonia.scm.api.rest.AuthorizationExceptionMapper; -import sonia.scm.api.v2.NotFoundExceptionMapper; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Modifications; import sonia.scm.repository.NamespaceAndName; @@ -38,6 +35,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static sonia.scm.ContextEntry.ContextBuilder.noContext; @Slf4j @RunWith(MockitoJUnitRunner.Silent.class) @@ -72,7 +70,7 @@ public class ModificationsResourceTest extends RepositoryTestBase { @Before - public void prepareEnvironment() throws Exception { + public void prepareEnvironment() { modificationsRootResource = new ModificationsRootResource(serviceFactory, modificationsToDtoMapper); super.modificationsRootResource = Providers.of(modificationsRootResource); dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource()); @@ -106,7 +104,7 @@ public class ModificationsResourceTest extends RepositoryTestBase { @Test public void shouldGet500OnModificationsCommandError() throws Exception { when(modificationsCommandBuilder.revision(any())).thenReturn(modificationsCommandBuilder); - when(modificationsCommandBuilder.getModifications()).thenThrow(InternalRepositoryException.class); + when(modificationsCommandBuilder.getModifications()).thenThrow(new InternalRepositoryException(noContext(), "", new RuntimeException())); MockHttpRequest request = MockHttpRequest .get(MODIFICATIONS_URL + "revision") From f7348d8882d30e34ea395e00d76db98daa5f4058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 7 Nov 2018 16:10:32 +0100 Subject: [PATCH 26/27] Incorporate comments from peer review --- .../AbstractSimpleRepositoryHandler.java | 6 ++-- .../InternalRepositoryException.java | 4 +++ .../api/RepositoryServiceFactory.java | 32 +++++++++++++++++++ .../resources/ChangesetRootResourceTest.java | 6 +--- 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java index 6cfa08975e..d190935fae 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java @@ -89,7 +89,7 @@ public abstract class AbstractSimpleRepositoryHandler Date: Thu, 8 Nov 2018 09:36:18 +0000 Subject: [PATCH 27/27] Close branch feature/errorhandling