mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 23:45:44 +01:00
Merged in feature/errorhandling (pull request #99)
Feature Error Handling
This commit is contained in:
@@ -30,19 +30,19 @@ public class ManagerDaoAdapter<T extends ModelObject> {
|
||||
|
||||
afterUpdate.handle(notModified);
|
||||
} else {
|
||||
throw new NotFoundException();
|
||||
throw new NotFoundException(object.getClass(), object.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public T create(T newObject, Supplier<PermissionCheck> permissionCheck, AroundHandler<T> beforeCreate, AroundHandler<T> afterCreate) throws AlreadyExistsException {
|
||||
public T create(T newObject, Supplier<PermissionCheck> permissionCheck, AroundHandler<T> beforeCreate, AroundHandler<T> afterCreate) {
|
||||
return create(newObject, permissionCheck, beforeCreate, afterCreate, dao::contains);
|
||||
}
|
||||
|
||||
public T create(T newObject, Supplier<PermissionCheck> permissionCheck, AroundHandler<T> beforeCreate, AroundHandler<T> afterCreate, Predicate<T> existsCheck) throws AlreadyExistsException {
|
||||
public T create(T newObject, Supplier<PermissionCheck> permissionCheck, AroundHandler<T> beforeCreate, AroundHandler<T> afterCreate, Predicate<T> 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);
|
||||
@@ -58,7 +58,7 @@ public class ManagerDaoAdapter<T extends ModelObject> {
|
||||
dao.delete(toDelete);
|
||||
afterDelete.handle(toDelete);
|
||||
} else {
|
||||
throw new NotFoundException();
|
||||
throw new NotFoundException(toDelete.getClass(), toDelete.getId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Exception> {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,16 @@
|
||||
package sonia.scm.api.rest;
|
||||
|
||||
import sonia.scm.AlreadyExistsException;
|
||||
import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.inject.Inject;
|
||||
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<AlreadyExistsException> {
|
||||
@Override
|
||||
public Response toResponse(AlreadyExistsException exception) {
|
||||
return Response.status(Status.CONFLICT)
|
||||
.entity(exception.getMessage())
|
||||
.build();
|
||||
public class AlreadyExistsExceptionMapper extends ContextualExceptionMapper<AlreadyExistsException> {
|
||||
@Inject
|
||||
public AlreadyExistsExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
super(AlreadyExistsException.class, Status.CONFLICT, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +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.ExceptionMapper;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
@Provider
|
||||
public class ConcurrentModificationExceptionMapper implements ExceptionMapper<ConcurrentModificationException> {
|
||||
@Override
|
||||
public Response toResponse(ConcurrentModificationException exception) {
|
||||
return Response.status(Response.Status.CONFLICT).build();
|
||||
public class ConcurrentModificationExceptionMapper extends ContextualExceptionMapper<ConcurrentModificationException> {
|
||||
@Inject
|
||||
public ConcurrentModificationExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
super(ConcurrentModificationException.class, Response.Status.CONFLICT, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package sonia.scm.api.rest;
|
||||
|
||||
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<E extends ExceptionWithContext> implements ExceptionMapper<E> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContextualExceptionMapper.class);
|
||||
|
||||
private final ExceptionWithContextToErrorDtoMapper mapper;
|
||||
|
||||
private final Response.Status status;
|
||||
private final Class<E> type;
|
||||
|
||||
public ContextualExceptionMapper(Class<E> type, Response.Status status, ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
this.type = type;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response toResponse(E exception) {
|
||||
logger.debug("map {} to status code {}", type.getSimpleName(), status.getStatusCode(), exception);
|
||||
return Response.status(status)
|
||||
.entity(mapper.map(exception))
|
||||
.type(VndMediaType.ERROR_TYPE)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -44,8 +44,6 @@ import org.apache.shiro.authc.credential.PasswordService;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ConcurrentModificationException;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.api.rest.RestActionResult;
|
||||
import sonia.scm.security.Role;
|
||||
import sonia.scm.security.ScmSecurityException;
|
||||
|
||||
@@ -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.retrieveContent(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);
|
||||
|
||||
@@ -45,11 +45,11 @@ import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.AlreadyExistsException;
|
||||
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;
|
||||
import sonia.scm.repository.*;
|
||||
import sonia.scm.repository.api.Command;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
@@ -394,7 +394,7 @@ public class RepositoryImportResource
|
||||
|
||||
response = Response.ok(result).build();
|
||||
}
|
||||
catch (NotSupportedFeatuerException ex)
|
||||
catch (NotSupportedFeatureException ex)
|
||||
{
|
||||
logger
|
||||
.warn(
|
||||
@@ -515,13 +515,6 @@ public class RepositoryImportResource
|
||||
// repository = new Repository(null, type, name);
|
||||
manager.create(repository);
|
||||
}
|
||||
catch (AlreadyExistsException ex)
|
||||
{
|
||||
logger.warn("a {} repository with the name {} already exists", type,
|
||||
name);
|
||||
|
||||
throw new WebApplicationException(Response.Status.CONFLICT);
|
||||
}
|
||||
catch (InternalRepositoryException ex)
|
||||
{
|
||||
handleGenericCreationFailure(ex, type, name);
|
||||
@@ -616,7 +609,7 @@ public class RepositoryImportResource
|
||||
types.add(t);
|
||||
}
|
||||
}
|
||||
catch (NotSupportedFeatuerException ex)
|
||||
catch (NotSupportedFeatureException ex)
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
@@ -718,7 +711,7 @@ public class RepositoryImportResource
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NotSupportedFeatuerException ex)
|
||||
catch (NotSupportedFeatureException ex)
|
||||
{
|
||||
throw new WebApplicationException(ex, Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -28,12 +28,14 @@
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
package sonia.scm.api.v2;
|
||||
|
||||
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.api.rest.StatusExceptionMapper;
|
||||
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;
|
||||
|
||||
@@ -41,9 +43,9 @@ import javax.ws.rs.ext.Provider;
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Provider
|
||||
public class NotFoundExceptionMapper extends StatusExceptionMapper<NotFoundException> {
|
||||
|
||||
public NotFoundExceptionMapper() {
|
||||
super(NotFoundException.class, Response.Status.NOT_FOUND);
|
||||
public class NotFoundExceptionMapper extends ContextualExceptionMapper<NotFoundException> {
|
||||
@Inject
|
||||
public NotFoundExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
super(NotFoundException.class, Response.Status.NOT_FOUND, mapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
@Provider
|
||||
public class NotSupportedFeatureExceptionMapper extends ContextualExceptionMapper<NotSupportedFeatureException> {
|
||||
@Inject
|
||||
public NotSupportedFeatureExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
super(NotSupportedFeatureException.class, Response.Status.BAD_REQUEST, mapper);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,30 @@
|
||||
package sonia.scm.api.v2;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.jboss.resteasy.api.validation.ResteasyViolationException;
|
||||
import sonia.scm.api.v2.resources.ViolationExceptionToErrorDtoMapper;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.inject.Inject;
|
||||
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.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Provider
|
||||
public class ValidationExceptionMapper implements ExceptionMapper<ResteasyViolationException> {
|
||||
|
||||
private final ViolationExceptionToErrorDtoMapper mapper;
|
||||
|
||||
@Inject
|
||||
public ValidationExceptionMapper(ViolationExceptionToErrorDtoMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response toResponse(ResteasyViolationException exception) {
|
||||
|
||||
List<ConstraintViolationBean> violations =
|
||||
exception.getConstraintViolations()
|
||||
.stream()
|
||||
.map(ConstraintViolationBean::new)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Response
|
||||
.status(Response.Status.BAD_REQUEST)
|
||||
.type(MediaType.APPLICATION_JSON_TYPE)
|
||||
.entity(new ValidationError(violations))
|
||||
.entity(mapper.map(exception))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class ValidationError {
|
||||
@XmlElement(name = "violation")
|
||||
@XmlElementWrapper(name = "violations")
|
||||
private List<ConstraintViolationBean> violations;
|
||||
|
||||
public ValidationError(List<ConstraintViolationBean> violations) {
|
||||
this.violations = violations;
|
||||
}
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "violation")
|
||||
@Getter
|
||||
public static class ConstraintViolationBean {
|
||||
private String path;
|
||||
private String message;
|
||||
|
||||
public ConstraintViolationBean(ConstraintViolation<?> violation) {
|
||||
message = violation.getMessage();
|
||||
path = violation.getPropertyPath().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -29,6 +28,9 @@ import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
|
||||
public class BranchRootResource {
|
||||
|
||||
private final RepositoryServiceFactory serviceFactory;
|
||||
@@ -79,7 +81,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();
|
||||
}
|
||||
}
|
||||
@@ -99,7 +101,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()
|
||||
@@ -107,7 +109,7 @@ public class BranchRootResource {
|
||||
.stream()
|
||||
.anyMatch(branch -> branchName.equals(branch.getName()));
|
||||
if (!branchExists){
|
||||
throw new NotFoundException("branch", branchName);
|
||||
throw notFound(entity(Branch.class, branchName).in(Repository.class, namespace + "/" + name));
|
||||
}
|
||||
Repository repository = repositoryService.getRepository();
|
||||
RepositoryPermissions.read(repository).check();
|
||||
@@ -195,8 +197,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ChangePasswordNotAllowedException> {
|
||||
@Override
|
||||
public Response toResponse(ChangePasswordNotAllowedException exception) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(exception.getMessage())
|
||||
.build();
|
||||
public class ChangePasswordNotAllowedExceptionMapper extends ContextualExceptionMapper<ChangePasswordNotAllowedException> {
|
||||
@Inject
|
||||
public ChangePasswordNotAllowedExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
super(ChangePasswordNotAllowedException.class, Response.Status.BAD_REQUEST, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,7 @@ 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.RevisionNotFoundException;
|
||||
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();
|
||||
|
||||
@@ -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<MODEL_OBJECT extends ModelObject,
|
||||
* Creates a model object for the given dto and returns a corresponding http response.
|
||||
* This handles all corner cases, eg. no conflicts or missing privileges.
|
||||
*/
|
||||
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) throws AlreadyExistsException {
|
||||
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) {
|
||||
if (dto == null) {
|
||||
return Response.status(BAD_REQUEST).build();
|
||||
}
|
||||
|
||||
@@ -6,10 +6,8 @@ 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 +62,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 +73,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 +103,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 +112,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 +125,7 @@ public class ContentResource {
|
||||
contentType.getLanguage().ifPresent(language -> responseBuilder.header("X-Programming-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];
|
||||
|
||||
@@ -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.DiffFormat;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
@@ -19,7 +18,6 @@ import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.StreamingOutput;
|
||||
|
||||
@@ -62,14 +60,10 @@ public class DiffRootResource {
|
||||
DiffFormat diffFormat = DiffFormat.valueOf(format);
|
||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||
StreamingOutput responseEntry = output -> {
|
||||
try {
|
||||
repositoryService.getDiffCommand()
|
||||
.setRevision(revision)
|
||||
repositoryService.getDiffCommand()
|
||||
.setRevision(revision)
|
||||
.setFormat(diffFormat)
|
||||
.retrieveContent(output);
|
||||
} catch (RevisionNotFoundException e) {
|
||||
throw new WebApplicationException(Response.Status.NOT_FOUND);
|
||||
}
|
||||
.retrieveContent(output);
|
||||
};
|
||||
return Response.ok(responseEntry)
|
||||
.header(HEADER_CONTENT_DISPOSITION, HttpUtil.createContentDispositionAttachmentHeader(String.format("%s-%s.diff", name, revision)))
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import sonia.scm.ContextEntry;
|
||||
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlElementWrapper;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.util.List;
|
||||
|
||||
@Getter @Setter
|
||||
public class ErrorDto {
|
||||
private String transactionId;
|
||||
private String errorCode;
|
||||
private List<ContextEntry> context;
|
||||
private String message;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@XmlElementWrapper(name = "violations")
|
||||
private List<ConstraintViolationDto> violations;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private String url;
|
||||
|
||||
@XmlRootElement(name = "violation")
|
||||
@Getter @Setter
|
||||
public static class ConstraintViolationDto {
|
||||
private String path;
|
||||
private String message;
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,8 @@ 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.RepositoryNotFoundException;
|
||||
import sonia.scm.repository.RevisionNotFoundException;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
@@ -26,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 {
|
||||
|
||||
@@ -51,8 +51,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,8 +67,9 @@ 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 {
|
||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException {
|
||||
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)
|
||||
@@ -84,9 +83,9 @@ public class FileHistoryRootResource {
|
||||
PageResult<Changeset> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ 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;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
@@ -86,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) throws AlreadyExistsException {
|
||||
public Response create(@Valid @Named("group") GroupDto groupDto) {
|
||||
return adapter.create(groupDto,
|
||||
() -> dtoToGroupMapper.map(groupDto),
|
||||
group -> resourceLinks.group().self(group.getName()));
|
||||
|
||||
@@ -3,13 +3,12 @@ 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;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
@@ -98,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) throws ConcurrentModificationException {
|
||||
public Response update(@PathParam("id") String name, @Valid @Named("group") GroupDto groupDto) {
|
||||
return adapter.update(name, existing -> dtoToGroupMapper.map(groupDto));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
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;
|
||||
import sonia.scm.PageResult;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
@@ -22,6 +21,7 @@ class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||
DTO extends HalRepresentation> {
|
||||
|
||||
private final Manager<MODEL_OBJECT> manager;
|
||||
private final String type;
|
||||
|
||||
private final SingleResourceManagerAdapter<MODEL_OBJECT, DTO> singleAdapter;
|
||||
private final CollectionResourceManagerAdapter<MODEL_OBJECT, DTO> collectionAdapter;
|
||||
@@ -30,13 +30,14 @@ class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||
this.manager = manager;
|
||||
singleAdapter = new SingleResourceManagerAdapter<>(manager, type);
|
||||
collectionAdapter = new CollectionResourceManagerAdapter<>(manager, type);
|
||||
this.type = type.getSimpleName();
|
||||
}
|
||||
|
||||
Response get(String id, Function<MODEL_OBJECT, DTO> mapToDto) {
|
||||
return singleAdapter.get(loadBy(id), mapToDto);
|
||||
}
|
||||
|
||||
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) throws ConcurrentModificationException {
|
||||
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) {
|
||||
return singleAdapter.update(
|
||||
loadBy(id),
|
||||
applyChanges,
|
||||
@@ -48,7 +49,7 @@ class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||
return collectionAdapter.getAll(page, pageSize, sortBy, desc, mapToDto);
|
||||
}
|
||||
|
||||
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) throws AlreadyExistsException {
|
||||
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) {
|
||||
return collectionAdapter.create(dto, modelObjectSupplier, uriCreator);
|
||||
}
|
||||
|
||||
@@ -56,8 +57,8 @@ class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||
return singleAdapter.delete(id);
|
||||
}
|
||||
|
||||
private Supplier<Optional<MODEL_OBJECT>> loadBy(String id) {
|
||||
return () -> Optional.ofNullable(manager.get(id));
|
||||
private Supplier<MODEL_OBJECT> loadBy(String id) {
|
||||
return () -> Optional.ofNullable(manager.get(id)).orElseThrow(() -> new NotFoundException(type, id));
|
||||
}
|
||||
|
||||
private Predicate<MODEL_OBJECT> idStaysTheSame(String id) {
|
||||
|
||||
@@ -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<InternalRepositoryException> {
|
||||
public class InternalRepositoryExceptionMapper extends ContextualExceptionMapper<InternalRepositoryException> {
|
||||
|
||||
public InternalRepositoryExceptionMapper() {
|
||||
super(InternalRepositoryException.class, Response.Status.INTERNAL_SERVER_ERROR);
|
||||
@Inject
|
||||
public InternalRepositoryExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
super(InternalRepositoryException.class, Response.Status.INTERNAL_SERVER_ERROR, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<InvalidPasswordException> {
|
||||
@Override
|
||||
public Response toResponse(InvalidPasswordException exception) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(exception.getMessage())
|
||||
.build();
|
||||
public class InvalidPasswordExceptionMapper extends ContextualExceptionMapper<InvalidPasswordException> {
|
||||
|
||||
@Inject
|
||||
public InvalidPasswordExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
|
||||
super(InvalidPasswordException.class, Response.Status.BAD_REQUEST, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -11,11 +11,11 @@ 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;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
@@ -30,6 +30,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
|
||||
@@ -71,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) throws AlreadyExistsException, NotFoundException {
|
||||
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();
|
||||
@@ -109,7 +112,7 @@ public class PermissionRootResource {
|
||||
.filter(filterPermission(permissionName))
|
||||
.map(permission -> modelToDtoMapper.map(permission, repository))
|
||||
.findFirst()
|
||||
.orElseThrow(NotFoundException::new)
|
||||
.orElseThrow(() -> notFound(entity(Permission.class, namespace).in(Repository.class, namespace + "/" + name)))
|
||||
).build();
|
||||
}
|
||||
|
||||
@@ -131,7 +134,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();
|
||||
@@ -158,23 +161,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 @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();
|
||||
String extractedPermissionName = getPermissionName(permissionName);
|
||||
if (!isPermissionExist(new PermissionDto(extractedPermissionName, isGroupPermission(permissionName)), repository)) {
|
||||
throw new NotFoundException("permission", extractedPermissionName);
|
||||
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::new);
|
||||
.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);
|
||||
@@ -237,12 +240,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(() -> notFound(entity(namespaceAndName)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,12 +253,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,10 +266,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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@ 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;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
@@ -87,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) throws AlreadyExistsException {
|
||||
public Response create(@Valid @Named("repository") RepositoryDto repositoryDto) {
|
||||
return adapter.create(repositoryDto,
|
||||
() -> dtoToRepositoryMapper.map(repositoryDto, null),
|
||||
repository -> resourceLinks.repository().self(repository.getNamespace(), repository.getName()));
|
||||
|
||||
@@ -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;
|
||||
@@ -12,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;
|
||||
@@ -26,6 +25,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;
|
||||
@@ -138,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) throws ConcurrentModificationException {
|
||||
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),
|
||||
@@ -203,8 +205,9 @@ public class RepositoryResource {
|
||||
}
|
||||
}
|
||||
|
||||
private Supplier<Optional<Repository>> loadBy(String namespace, String name) {
|
||||
return () -> Optional.ofNullable(manager.get(new NamespaceAndName(namespace, name)));
|
||||
private Supplier<Repository> loadBy(String namespace, String name) {
|
||||
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
|
||||
return () -> Optional.ofNullable(manager.get(namespaceAndName)).orElseThrow(() -> notFound(entity(namespaceAndName)));
|
||||
}
|
||||
|
||||
private Predicate<Repository> nameAndNamespaceStaysTheSame(String namespace, String name) {
|
||||
|
||||
@@ -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<String> getParameterNames(Constructor<?> constructor) {
|
||||
return Arrays.stream(constructor.getParameters()).map(this::getParameterName).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> 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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -33,45 +32,41 @@ class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||
DTO extends HalRepresentation> extends AbstractManagerResource<MODEL_OBJECT> {
|
||||
|
||||
private final Function<Throwable, Optional<Response>> errorHandler;
|
||||
private final Class<MODEL_OBJECT> type;
|
||||
|
||||
SingleResourceManagerAdapter(Manager<MODEL_OBJECT> manager, Class<MODEL_OBJECT> type) {
|
||||
this(manager, type, e -> Optional.empty());
|
||||
}
|
||||
|
||||
SingleResourceManagerAdapter(Manager<MODEL_OBJECT> manager, Class<MODEL_OBJECT> type, Function<Throwable, Optional<Response>> errorHandler) {
|
||||
SingleResourceManagerAdapter(
|
||||
Manager<MODEL_OBJECT> manager,
|
||||
Class<MODEL_OBJECT> type,
|
||||
Function<Throwable, Optional<Response>> errorHandler) {
|
||||
super(manager, type);
|
||||
this.errorHandler = errorHandler;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the model object for the given id, transforms it to a dto and returns a corresponding http response.
|
||||
* This handles all corner cases, eg. no matching object for the id or missing privileges.
|
||||
*/
|
||||
Response get(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, DTO> mapToDto) {
|
||||
return reader.get()
|
||||
.map(mapToDto)
|
||||
.map(Response::ok)
|
||||
.map(Response.ResponseBuilder::build)
|
||||
.orElseThrow(NotFoundException::new);
|
||||
}
|
||||
public Response update(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Predicate<MODEL_OBJECT> hasSameKey, Consumer<MODEL_OBJECT> checker) throws NotFoundException, ConcurrentModificationException {
|
||||
MODEL_OBJECT existingModelObject = reader.get().orElseThrow(NotFoundException::new);
|
||||
checker.accept(existingModelObject);
|
||||
return update(reader,applyChanges,hasSameKey);
|
||||
Response get(Supplier<MODEL_OBJECT> reader, Function<MODEL_OBJECT, DTO> 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<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Predicate<MODEL_OBJECT> hasSameKey) throws NotFoundException, ConcurrentModificationException {
|
||||
MODEL_OBJECT existingModelObject = reader.get().orElseThrow(NotFoundException::new);
|
||||
Response update(Supplier<MODEL_OBJECT> reader, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Predicate<MODEL_OBJECT> 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);
|
||||
}
|
||||
@@ -81,11 +76,13 @@ class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||
&& (updated.getLastModified() == null || existing.getLastModified() > updated.getLastModified());
|
||||
}
|
||||
|
||||
public Response delete(Supplier<Optional<MODEL_OBJECT>> reader) {
|
||||
return reader.get()
|
||||
.map(MODEL_OBJECT::getId)
|
||||
.map(this::delete)
|
||||
.orElse(null);
|
||||
public Response delete(Supplier<MODEL_OBJECT> reader) {
|
||||
try {
|
||||
return delete(reader.get().getId());
|
||||
} catch (NotFoundException e) {
|
||||
// due to idempotency of delete this does not matter here.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.BrowserResult;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.api.BrowseCommandBuilder;
|
||||
@@ -31,14 +30,14 @@ public class SourceRootResource {
|
||||
@GET
|
||||
@Produces(VndMediaType.SOURCE)
|
||||
@Path("")
|
||||
public Response getAllWithoutRevision(@PathParam("namespace") String namespace, @PathParam("name") String name) throws NotFoundException, 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 NotFoundException, IOException {
|
||||
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws IOException {
|
||||
return getSource(namespace, name, "/", revision);
|
||||
}
|
||||
|
||||
@@ -49,7 +48,7 @@ public class SourceRootResource {
|
||||
return getSource(namespace, name, path, revision);
|
||||
}
|
||||
|
||||
private Response getSource(String namespace, String repoName, String path, String revision) throws IOException, NotFoundException {
|
||||
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();
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import sonia.scm.NotFoundException;
|
||||
|
||||
public class TagNotFoundException extends NotFoundException {
|
||||
|
||||
}
|
||||
@@ -3,9 +3,9 @@ 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;
|
||||
import sonia.scm.repository.RepositoryPermissions;
|
||||
import sonia.scm.repository.Tag;
|
||||
import sonia.scm.repository.Tags;
|
||||
@@ -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;
|
||||
@@ -47,7 +50,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 +75,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 +83,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 +93,10 @@ public class TagRootResource {
|
||||
}
|
||||
}
|
||||
|
||||
private NotFoundException createNotFoundException(String namespace, String name, String tagName) {
|
||||
return notFound(entity("Tag", tagName).in("Repository", namespace + "/" + name));
|
||||
}
|
||||
|
||||
private Tags getTags(RepositoryService repositoryService) throws IOException {
|
||||
Repository repository = repositoryService.getRepository();
|
||||
RepositoryPermissions.read(repository).check();
|
||||
|
||||
@@ -6,12 +6,12 @@ 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;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
@@ -91,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) throws AlreadyExistsException {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ 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;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
@@ -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 @Named("user") UserDto userDto) {
|
||||
return adapter.update(name, existing -> dtoToUserMapper.map(userDto, existing.getPassword()));
|
||||
}
|
||||
|
||||
@@ -128,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();
|
||||
}
|
||||
|
||||
@@ -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<ErrorDto.ConstraintViolationDto> 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)");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -42,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 ------------------------------------------------------------
|
||||
@@ -62,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_CLIEN_HOST = "client_host";
|
||||
static final String MDC_CLIENT_HOST = "client_host";
|
||||
|
||||
/** Field description */
|
||||
@VisibleForTesting
|
||||
static final String MDC_CLIEN_IP = "client_ip";
|
||||
|
||||
/** url of the current request */
|
||||
static final String MDC_CLIENT_IP = "client_ip";
|
||||
|
||||
@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 --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
@@ -102,10 +102,11 @@ 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());
|
||||
MDC.put(MDC_TRANSACTION_ID, TRANSACTION_KEY_GENERATOR.createKey());
|
||||
|
||||
try
|
||||
{
|
||||
@@ -114,10 +115,11 @@ 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);
|
||||
MDC.remove(MDC_TRANSACTION_ID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
@@ -172,7 +171,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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -140,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);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -198,7 +201,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 +210,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
||||
if (fresh != null) {
|
||||
fresh.copyProperties(repository);
|
||||
} else {
|
||||
throw new RepositoryNotFoundException(repository);
|
||||
throw notFound(entity(repository));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,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;
|
||||
|
||||
@@ -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;
|
||||
@@ -61,7 +60,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);
|
||||
|
||||
@@ -39,7 +39,7 @@ 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.ContextEntry;
|
||||
import sonia.scm.EagerSingleton;
|
||||
import sonia.scm.HandlerEventType;
|
||||
import sonia.scm.ManagerDaoAdapter;
|
||||
@@ -137,7 +137,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());
|
||||
@@ -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);
|
||||
@@ -403,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);
|
||||
@@ -419,10 +419,10 @@ 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());
|
||||
throw new ChangePasswordNotAllowedException(ContextEntry.ContextBuilder.entity("passwordChange", "-").in(User.class, user.getName()), user.getType());
|
||||
}
|
||||
user.setPassword(newPassword);
|
||||
this.modify(user);
|
||||
|
||||
@@ -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::new);
|
||||
)).orElseThrow(() -> notFound(entity("jsonprovider", path)));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user