Merge with 2.0.0-m3

This commit is contained in:
René Pfeuffer
2018-08-08 15:57:05 +02:00
174 changed files with 7559 additions and 4653 deletions

View File

@@ -2,14 +2,13 @@ package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import org.mapstruct.Mapping;
import sonia.scm.ModelObject;
import java.time.Instant;
abstract class BaseMapper<T extends ModelObject, D extends HalRepresentation> {
abstract class BaseMapper<T, D extends HalRepresentation> {
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
public abstract D map(T modelObject);
public abstract D map(T object);
Instant mapTime(Long epochMilli) {
return epochMilli == null? null: Instant.ofEpochMilli(epochMilli);

View File

@@ -0,0 +1,32 @@
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

@@ -1,13 +1,9 @@
package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.databind.JsonNode;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import sonia.scm.group.Group;
import java.util.stream.Collectors;
@Mapper
public abstract class GroupDtoToGroupMapper {
@@ -16,15 +12,4 @@ public abstract class GroupDtoToGroupMapper {
@Mapping(target = "lastModified", ignore = true)
public abstract Group map(GroupDto groupDto);
@AfterMapping
void mapMembers(GroupDto dto, @MappingTarget Group target) {
target.setMembers(
dto
.getEmbedded()
.getItemsBy("members")
.stream()
.map(m -> m.getAttribute("name"))
.map(JsonNode::asText)
.collect(Collectors.toList()));
}
}

View File

@@ -21,6 +21,9 @@ public class MapperModule extends AbstractModule {
bind(RepositoryToRepositoryDtoMapper.class).to(Mappers.getMapper(RepositoryToRepositoryDtoMapper.class).getClass());
bind(RepositoryDtoToRepositoryMapper.class).to(Mappers.getMapper(RepositoryDtoToRepositoryMapper.class).getClass());
bind(RepositoryTypeToRepositoryTypeDtoMapper.class).to(Mappers.getMapper(RepositoryTypeToRepositoryTypeDtoMapper.class).getClass());
bind(RepositoryTypeCollectionToDtoMapper.class);
bind(BranchToBranchDtoMapper.class).to(Mappers.getMapper(BranchToBranchDtoMapper.class).getClass());
bind(UriInfoStore.class).in(ServletScopes.REQUEST);

View File

@@ -3,6 +3,7 @@ package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RepositoryIsNotArchivedException;
@@ -165,7 +166,7 @@ public class RepositoryResource {
}
private Supplier<Optional<Repository>> loadBy(String namespace, String name) {
return () -> manager.getByNamespace(namespace, name);
return () -> Optional.ofNullable(manager.get(new NamespaceAndName(namespace, name)));
}
private Predicate<Repository> nameAndNamespaceStaysTheSame(String namespace, String name) {

View File

@@ -25,6 +25,7 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
@AfterMapping
void appendLinks(Repository repository, @MappingTarget RepositoryDto target) {
Links.Builder linksBuilder = linkingTo().self(resourceLinks.repository().self(target.getNamespace(), target.getName()));
linksBuilder.single(link("httpProtocol", resourceLinks.repository().clone(target.getType(), target.getNamespace(), target.getName())));
if (RepositoryPermissions.delete(repository).isPermitted()) {
linksBuilder.single(link("delete", resourceLinks.repository().delete(target.getNamespace(), target.getName())));
}

View File

@@ -0,0 +1,36 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import de.otto.edison.hal.HalRepresentation;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
public class RepositoryTypeCollectionResource {
private RepositoryManager repositoryManager;
private RepositoryTypeCollectionToDtoMapper mapper;
@Inject
public RepositoryTypeCollectionResource(RepositoryManager repositoryManager, RepositoryTypeCollectionToDtoMapper mapper) {
this.repositoryManager = repositoryManager;
this.mapper = mapper;
}
@GET
@Path("")
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 500, condition = "internal server error")
})
@Produces(VndMediaType.REPOSITORY_TYPE_COLLECTION)
public HalRepresentation getAll() {
return mapper.map(repositoryManager.getConfiguredTypes());
}
}

View File

@@ -0,0 +1,21 @@
package sonia.scm.api.v2.resources;
import sonia.scm.repository.RepositoryType;
import javax.inject.Inject;
public class RepositoryTypeCollectionToDtoMapper extends CollectionToDtoMapper<RepositoryType, RepositoryTypeDto> {
private final ResourceLinks resourceLinks;
@Inject
public RepositoryTypeCollectionToDtoMapper(RepositoryTypeToRepositoryTypeDtoMapper mapper, ResourceLinks resourceLinks) {
super("repositoryTypes", mapper);
this.resourceLinks = resourceLinks;
}
@Override
protected String createSelfLink() {
return resourceLinks.repositoryTypeCollection().self();
}
}

View File

@@ -0,0 +1,22 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@NoArgsConstructor
@Getter
@Setter
public class RepositoryTypeDto extends HalRepresentation {
private String name;
private String displayName;
@Override
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
protected HalRepresentation add(Links links) {
return super.add(links);
}
}

View File

@@ -0,0 +1,53 @@
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.RepositoryManager;
import sonia.scm.repository.RepositoryType;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
public class RepositoryTypeResource {
private RepositoryManager repositoryManager;
private RepositoryTypeToRepositoryTypeDtoMapper mapper;
@Inject
public RepositoryTypeResource(RepositoryManager repositoryManager, RepositoryTypeToRepositoryTypeDtoMapper mapper) {
this.repositoryManager = repositoryManager;
this.mapper = mapper;
}
/**
* Returns the specified repository type.
*
* <strong>Note:</strong> This method requires "group" privilege.
*
* @param name of the requested repository type
*/
@GET
@Path("")
@Produces(VndMediaType.REPOSITORY_TYPE)
@TypeHint(RepositoryTypeDto.class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 404, condition = "not found, no repository type with the specified name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response get(@PathParam("name") String name) {
for (RepositoryType type : repositoryManager.getConfiguredTypes()) {
if (name.equalsIgnoreCase(type.getName())) {
return Response.ok(mapper.map(type)).build();
}
}
return Response.status(Response.Status.NOT_FOUND).build();
}
}

View File

@@ -0,0 +1,35 @@
package sonia.scm.api.v2.resources;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
/**
* RESTful Web Service Resource to get available repository types.
*/
@Path(RepositoryTypeRootResource.PATH)
public class RepositoryTypeRootResource {
static final String PATH = "v2/repositoryTypes/";
private Provider<RepositoryTypeCollectionResource> collectionResourceProvider;
private Provider<RepositoryTypeResource> resourceProvider;
@Inject
public RepositoryTypeRootResource(Provider<RepositoryTypeCollectionResource> collectionResourceProvider, Provider<RepositoryTypeResource> resourceProvider) {
this.collectionResourceProvider = collectionResourceProvider;
this.resourceProvider = resourceProvider;
}
@Path("")
public RepositoryTypeCollectionResource getRepositoryTypeCollectionResource() {
return collectionResourceProvider.get();
}
@Path("{name}")
public RepositoryTypeResource getRepositoryTypeResource() {
return resourceProvider.get();
}
}

View File

@@ -0,0 +1,25 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import sonia.scm.repository.RepositoryType;
import javax.inject.Inject;
import static de.otto.edison.hal.Links.linkingTo;
@Mapper
public abstract class RepositoryTypeToRepositoryTypeDtoMapper extends BaseMapper<RepositoryType, RepositoryTypeDto> {
@Inject
private ResourceLinks resourceLinks;
@AfterMapping
void appendLinks(RepositoryType repositoryType, @MappingTarget RepositoryTypeDto target) {
Links.Builder linksBuilder = linkingTo().self(resourceLinks.repositoryType().self(repositoryType.getName()));
target.add(linksBuilder.build());
}
}

View File

@@ -4,6 +4,7 @@ import sonia.scm.repository.NamespaceAndName;
import javax.inject.Inject;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
class ResourceLinks {
@@ -129,15 +130,21 @@ class ResourceLinks {
static class RepositoryLinks {
private final LinkBuilder repositoryLinkBuilder;
private final UriInfo uriInfo;
RepositoryLinks(UriInfo uriInfo) {
repositoryLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class);
this.uriInfo = uriInfo;
}
String self(String namespace, String name) {
return repositoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("get").parameters().href();
}
String clone(String type, String namespace, String name) {
return uriInfo.getBaseUri().resolve(URI.create("../../" + type + "/" + namespace + "/" + name)).toASCIIString();
}
String delete(String namespace, String name) {
return repositoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("delete").parameters().href();
}
@@ -167,6 +174,39 @@ class ResourceLinks {
}
}
public RepositoryTypeLinks repositoryType() {
return new RepositoryTypeLinks(uriInfoStore.get());
}
static class RepositoryTypeLinks {
private final LinkBuilder repositoryTypeLinkBuilder;
RepositoryTypeLinks(UriInfo uriInfo) {
repositoryTypeLinkBuilder = new LinkBuilder(uriInfo, RepositoryTypeRootResource.class, RepositoryTypeResource.class);
}
String self(String name) {
return repositoryTypeLinkBuilder.method("getRepositoryTypeResource").parameters(name).method("get").parameters().href();
}
}
public RepositoryTypeCollectionLinks repositoryTypeCollection() {
return new RepositoryTypeCollectionLinks(uriInfoStore.get());
}
static class RepositoryTypeCollectionLinks {
private final LinkBuilder collectionLinkBuilder;
RepositoryTypeCollectionLinks(UriInfo uriInfo) {
collectionLinkBuilder = new LinkBuilder(uriInfo, RepositoryTypeRootResource.class, RepositoryTypeCollectionResource.class);
}
String self() {
return collectionLinkBuilder.method("getRepositoryTypeCollectionResource").parameters().method("getAll").parameters().href();
}
}
public TagCollectionLinks tagCollection() {
return new TagCollectionLinks(uriInfoStore.get());
}