Merged changes of 2.0.0-m3

This commit is contained in:
Philipp Czora
2018-09-06 13:16:19 +02:00
31 changed files with 643 additions and 109 deletions

View File

@@ -11,6 +11,8 @@ import javax.ws.rs.ext.Provider;
public class AlreadyExistsExceptionMapper implements ExceptionMapper<AlreadyExistsException> {
@Override
public Response toResponse(AlreadyExistsException exception) {
return Response.status(Status.CONFLICT).build();
return Response.status(Status.CONFLICT)
.entity(exception.getMessage())
.build();
}
}

View File

@@ -33,6 +33,8 @@ package sonia.scm.api.rest;
//~--- JDK imports ------------------------------------------------------------
import lombok.extern.slf4j.Slf4j;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
@@ -43,7 +45,7 @@ import javax.ws.rs.ext.Provider;
* @author Sebastian Sdorra
* @since 1.36
*/
@Provider
@Provider @Slf4j
public class IllegalArgumentExceptionMapper
implements ExceptionMapper<IllegalArgumentException>
{
@@ -59,6 +61,7 @@ public class IllegalArgumentExceptionMapper
@Override
public Response toResponse(IllegalArgumentException exception)
{
log.info("caught IllegalArgumentException -- mapping to bad request", exception);
return Response.status(Status.BAD_REQUEST).build();
}
}

View File

@@ -90,6 +90,8 @@ public class StatusExceptionMapper<E extends Throwable>
logger.debug(msg.toString());
}
return Response.status(status).build();
return Response.status(status)
.entity(exception.getMessage())
.build();
}
}

View File

@@ -1,32 +0,0 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Links.linkingTo;
abstract class CollectionToDtoMapper<E, D extends HalRepresentation> {
private final String collectionName;
private final BaseMapper<E, D> mapper;
protected CollectionToDtoMapper(String collectionName, BaseMapper<E, D> mapper) {
this.collectionName = collectionName;
this.mapper = mapper;
}
public HalRepresentation map(Collection<E> collection) {
List<D> dtos = collection.stream().map(mapper::map).collect(Collectors.toList());
return new HalRepresentation(
linkingTo().self(createSelfLink()).build(),
embeddedBuilder().with(collectionName, dtos).build()
);
}
protected abstract String createSelfLink();
}

View File

@@ -10,6 +10,8 @@ import lombok.ToString;
@Getter @Setter @ToString
public class PermissionDto extends HalRepresentation {
public static final String GROUP_PREFIX = "@";
@JsonInclude(JsonInclude.Include.NON_NULL)
private String name;
@@ -21,11 +23,19 @@ public class PermissionDto extends HalRepresentation {
*
**/
@JsonInclude(JsonInclude.Include.NON_NULL)
private String type ;
private String type;
private boolean groupPermission = false;
public PermissionDto() {
}
public PermissionDto(String permissionName, boolean groupPermission) {
name = permissionName;
this.groupPermission = groupPermission;
}
@Override
@SuppressWarnings("squid:S1185") // We want to have this method available in this package

View File

@@ -5,7 +5,6 @@ import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import sonia.scm.AlreadyExistsException;
import sonia.scm.NotFoundException;
import sonia.scm.repository.NamespaceAndName;
@@ -28,10 +27,14 @@ import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.Optional;
import java.util.function.Predicate;
import static sonia.scm.api.v2.resources.PermissionDto.GROUP_PREFIX;
@Slf4j
public class PermissionRootResource {
private PermissionDtoToPermissionMapper dtoToModelMapper;
private PermissionToPermissionDtoMapper modelToDtoMapper;
private PermissionCollectionToDtoMapper permissionCollectionToDtoMapper;
@@ -101,7 +104,7 @@ public class PermissionRootResource {
return Response.ok(
repository.getPermissions()
.stream()
.filter(permission -> permissionName.equals(permission.getName()))
.filter(filterPermission(permissionName))
.map(permission -> modelToDtoMapper.map(permission, repository))
.findFirst()
.orElseThrow(NotFoundException::new)
@@ -135,6 +138,7 @@ public class PermissionRootResource {
/**
* Update a permission to the user or group managed by the repository
* ignore the user input for groupPermission and take it from the path parameter (if the group prefix (@) exists it is a group permission)
*
* @param permission permission to modify
* @param permissionName permission to modify
@@ -152,15 +156,23 @@ public class PermissionRootResource {
public Response update(@PathParam("namespace") String namespace,
@PathParam("name") String name,
@PathParam("permission-name") String permissionName,
PermissionDto permission) throws NotFoundException {
PermissionDto permission) throws NotFoundException, AlreadyExistsException {
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("the permission " + extractedPermissionName + " does not exist");
}
permission.setGroupPermission(isGroupPermission(permissionName));
if (!extractedPermissionName.equals(permission.getName())) {
checkPermissionAlreadyExists(permission, repository, "target permission " + permission.getName() + " already exists");
}
Permission existingPermission = repository.getPermissions()
.stream()
.filter(perm -> StringUtils.isNotBlank(perm.getName()) && perm.getName().equals(permissionName))
.filter(filterPermission(permissionName))
.findFirst()
.orElseThrow(() -> new NotFoundException());
.orElseThrow(NotFoundException::new);
dtoToModelMapper.modify(existingPermission, permission);
manager.modify(repository);
log.info("the permission with name: {} is updated.", permissionName);
@@ -186,11 +198,11 @@ public class PermissionRootResource {
@PathParam("name") String name,
@PathParam("permission-name") String permissionName) throws NotFoundException {
log.info("try to delete the permission with name: {}.", permissionName);
Repository repository = load(namespace, name);
Repository repository = load(namespace, name);
RepositoryPermissions.modify(repository).check();
repository.getPermissions()
.stream()
.filter(perm -> StringUtils.isNotBlank(perm.getName()) && perm.getName().equals(permissionName))
.filter(filterPermission(permissionName))
.findFirst()
.ifPresent(p -> repository.getPermissions().remove(p))
;
@@ -199,6 +211,22 @@ public class PermissionRootResource {
return Response.noContent().build();
}
Predicate<Permission> filterPermission(String permissionName) {
return permission -> getPermissionName(permissionName).equals(permission.getName())
&&
permission.isGroupPermission() == isGroupPermission(permissionName);
}
private String getPermissionName(String permissionName) {
return Optional.of(permissionName)
.filter(p -> !isGroupPermission(permissionName))
.orElse(permissionName.substring(1));
}
private boolean isGroupPermission(String permissionName) {
return permissionName.startsWith(GROUP_PREFIX);
}
/**
* check if the actual user is permitted to manage the repository permissions
@@ -219,16 +247,24 @@ 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) throws AlreadyExistsException {
boolean isPermissionAlreadyExist = repository.getPermissions()
.stream()
.anyMatch(p -> p.getName().equals(permission.getName()));
if (isPermissionAlreadyExist) {
throw new AlreadyExistsException();
private void checkPermissionAlreadyExists(PermissionDto permission, Repository repository, String errorMessage) throws AlreadyExistsException {
if (isPermissionExist(permission, repository)) {
throw new AlreadyExistsException(errorMessage);
}
}
private boolean isPermissionExist(PermissionDto permission, Repository repository) {
return repository.getPermissions()
.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.");
}
}

View File

@@ -12,9 +12,11 @@ import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermissions;
import javax.inject.Inject;
import java.util.Optional;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
import static sonia.scm.api.v2.resources.PermissionDto.GROUP_PREFIX;
@Mapper
public abstract class PermissionToPermissionDtoMapper {
@@ -39,11 +41,14 @@ public abstract class PermissionToPermissionDtoMapper {
*/
@AfterMapping
void appendLinks(@MappingTarget PermissionDto target, @Context Repository repository) {
String permissionName = Optional.of(target.getName())
.filter(p -> !target.isGroupPermission())
.orElse(GROUP_PREFIX + target.getName());
Links.Builder linksBuilder = linkingTo()
.self(resourceLinks.permission().self(repository.getNamespace(), repository.getName(), target.getName()));
.self(resourceLinks.permission().self(repository.getNamespace(), repository.getName(), permissionName));
if (RepositoryPermissions.permissionWrite(repository).isPermitted()) {
linksBuilder.single(link("update", resourceLinks.permission().update(repository.getNamespace(), repository.getName(), target.getName())));
linksBuilder.single(link("delete", resourceLinks.permission().delete(repository.getNamespace(), repository.getName(), target.getName())));
linksBuilder.single(link("update", resourceLinks.permission().update(repository.getNamespace(), repository.getName(), permissionName)));
linksBuilder.single(link("delete", resourceLinks.permission().delete(repository.getNamespace(), repository.getName(), permissionName)));
}
target.add(linksBuilder.build());
}

View File

@@ -218,8 +218,8 @@ class ResourceLinks {
tagLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, TagRootResource.class);
}
String self(String namespace, String name, String id) {
return tagLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("tags").parameters().method("get").parameters(id).href();
String self(String namespace, String name, String tagName) {
return tagLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("tags").parameters().method("get").parameters(tagName).href();
}
String all(String namespace, String name) {

View File

@@ -1,5 +1,6 @@
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;
@@ -46,7 +47,7 @@ public class SourceRootResource {
@GET
@Produces(VndMediaType.SOURCE)
@Path("{revision}/{path: .*}")
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) throws RevisionNotFoundException, RepositoryNotFoundException, IOException {
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) throws NotFoundException, IOException {
return getSource(namespace, name, path, revision);
}

View File

@@ -0,0 +1,7 @@
package sonia.scm.api.v2.resources;
import sonia.scm.NotFoundException;
public class TagNotFoundException extends NotFoundException {
}

View File

@@ -1,27 +1,100 @@
package sonia.scm.api.v2.resources;
import javax.ws.rs.DefaultValue;
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.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;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import java.io.IOException;
public class TagRootResource {
private final RepositoryServiceFactory serviceFactory;
private final TagCollectionToDtoMapper tagCollectionToDtoMapper;
private final TagToTagDtoMapper tagToTagDtoMapper;
@Inject
public TagRootResource(RepositoryServiceFactory serviceFactory,
TagCollectionToDtoMapper tagCollectionToDtoMapper,
TagToTagDtoMapper tagToTagDtoMapper) {
this.serviceFactory = serviceFactory;
this.tagCollectionToDtoMapper = tagCollectionToDtoMapper;
this.tagToTagDtoMapper = tagToTagDtoMapper;
}
@GET
@Path("")
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("10") @QueryParam("pageSize") int pageSize,
@QueryParam("sortBy") String sortBy,
@DefaultValue("false") @QueryParam("desc") boolean desc) {
throw new UnsupportedOperationException();
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the tags"),
@ResponseCode(code = 500, condition = "internal server error")
})
@Produces(VndMediaType.TAG_COLLECTION)
@TypeHint(CollectionDto.class)
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException, RepositoryNotFoundException {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
Tags tags = getTags(repositoryService);
if (tags != null && tags.getTags() != null) {
return Response.ok(tagCollectionToDtoMapper.map(namespace, name, tags.getTags())).build();
} else {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Error on getting tag from repository.")
.build();
}
}
}
@GET
@Path("{id}")
public Response get(String id) {
throw new UnsupportedOperationException();
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the tags"),
@ResponseCode(code = 404, condition = "not found, no tag available in the repository"),
@ResponseCode(code = 500, condition = "internal server error")
})
@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 {
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) {
Tags tags = getTags(repositoryService);
if (tags != null && tags.getTags() != null) {
Tag tag = tags.getTags().stream()
.filter(t -> tagName.equals(t.getName()))
.findFirst()
.orElseThrow(TagNotFoundException::new);
return Response.ok(tagToTagDtoMapper.map(tag, namespaceAndName)).build();
} else {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Error on getting tag from repository.")
.build();
}
}
}
private Tags getTags(RepositoryService repositoryService) throws IOException {
Repository repository = repositoryService.getRepository();
RepositoryPermissions.read(repository).check();
return repositoryService.getTagsCommand().getTags();
}
}

View File

@@ -11,6 +11,7 @@ import sonia.scm.repository.Tag;
import javax.inject.Inject;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
@Mapper
@@ -25,7 +26,9 @@ public abstract class TagToTagDtoMapper {
@AfterMapping
void appendLinks(@MappingTarget TagDto target, @Context NamespaceAndName namespaceAndName) {
Links.Builder linksBuilder = linkingTo()
.self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getName()));
.self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getName()))
.single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())))
.single(link("changesets", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())));
target.add(linksBuilder.build());
}
}