diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BranchesCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/BranchesCommandBuilder.java index 3deaf491d8..784c9534fd 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/BranchesCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/BranchesCommandBuilder.java @@ -35,10 +35,8 @@ package sonia.scm.repository.api; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.base.Objects; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; import sonia.scm.repository.Branch; @@ -49,10 +47,10 @@ import sonia.scm.repository.RepositoryException; import sonia.scm.repository.spi.BlameCommand; import sonia.scm.repository.spi.BranchesCommand; -//~--- JDK imports ------------------------------------------------------------ - import java.io.IOException; +//~--- JDK imports ------------------------------------------------------------ + /** * The branches command list all repository branches.
*
@@ -139,10 +137,7 @@ public final class BranchesCommandBuilder branches = getBranchesFromCommand(); - if (branches != null) - { - cache.put(key, branches); - } + cache.put(key, branches); } else if (logger.isDebugEnabled()) { diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index 7c764685f1..afac955f21 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -15,9 +15,11 @@ public class VndMediaType { public static final String USER = PREFIX + "user" + SUFFIX; public static final String GROUP = PREFIX + "group" + SUFFIX; public static final String REPOSITORY = PREFIX + "repository" + SUFFIX; + public static final String BRANCH = PREFIX + "branch" + SUFFIX; public static final String USER_COLLECTION = PREFIX + "userCollection" + SUFFIX; public static final String GROUP_COLLECTION = PREFIX + "groupCollection" + SUFFIX; public static final String REPOSITORY_COLLECTION = PREFIX + "repositoryCollection" + SUFFIX; + public static final String BRANCH_COLLECTION = PREFIX + "branchCollection" + SUFFIX; public static final String CONFIG = PREFIX + "config" + SUFFIX; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchCollectionResource.java index d64016b475..069f72a0c9 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchCollectionResource.java @@ -1,18 +1,65 @@ 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.Branches; +import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.RepositoryException; +import sonia.scm.repository.RepositoryManager; +import sonia.scm.repository.RepositoryNotFoundException; +import sonia.scm.repository.api.CommandNotSupportedException; +import sonia.scm.repository.api.RepositoryService; +import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.util.IOUtil; +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 BranchCollectionResource { + + private final RepositoryManager manager; + private final RepositoryServiceFactory servicefactory; + private final BranchCollectionToDtoMapper branchCollectionToDtoMapper; + + @Inject + public BranchCollectionResource(RepositoryManager manager, RepositoryServiceFactory servicefactory, BranchCollectionToDtoMapper branchCollectionToDtoMapper) { + this.manager = manager; + this.servicefactory = servicefactory; + this.branchCollectionToDtoMapper = branchCollectionToDtoMapper; + } + @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(); + @Produces(VndMediaType.BRANCH_COLLECTION) + @TypeHint(CollectionDto.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group\" privilege"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException, RepositoryException { + RepositoryService repositoryService; + try { + repositoryService = servicefactory.create(new NamespaceAndName(namespace, name)); + } catch (RepositoryNotFoundException e) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + try { + Branches branches = repositoryService.getBranchesCommand().getBranches(); + return Response.ok(branchCollectionToDtoMapper.map(namespace, name, branches.getBranches())).build(); + } catch (CommandNotSupportedException ex) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } finally { + IOUtil.close(repositoryService); + } } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchCollectionToDtoMapper.java new file mode 100644 index 0000000000..9e574b3a7b --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchCollectionToDtoMapper.java @@ -0,0 +1,46 @@ +package sonia.scm.api.v2.resources; + +import com.google.inject.Inject; +import de.otto.edison.hal.Embedded; +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import sonia.scm.repository.Branch; +import sonia.scm.repository.NamespaceAndName; + +import java.util.Collection; +import java.util.List; + +import static de.otto.edison.hal.Embedded.embeddedBuilder; +import static de.otto.edison.hal.Links.linkingTo; +import static java.util.stream.Collectors.toList; + +public class BranchCollectionToDtoMapper { + + private final ResourceLinks resourceLinks; + private final BranchToBranchDtoMapper branchToDtoMapper; + + @Inject + public BranchCollectionToDtoMapper(BranchToBranchDtoMapper branchToDtoMapper, ResourceLinks resourceLinks) { + this.resourceLinks = resourceLinks; + this.branchToDtoMapper = branchToDtoMapper; + } + + public HalRepresentation map(String namespace, String name, Collection branches) { + List dtos = branches.stream().map(branch -> branchToDtoMapper.map(branch, new NamespaceAndName(namespace, name))).collect(toList()); + return new HalRepresentation(createLinks(namespace, name), embedDtos(dtos)); + } + + private Links createLinks(String namespace, String name) { + String baseUrl = resourceLinks.branchCollection().self(namespace, name); + + Links.Builder linksBuilder = linkingTo() + .with(Links.linkingTo().self(baseUrl).build()); + return linksBuilder.build(); + } + + private Embedded embedDtos(List dtos) { + return embeddedBuilder() + .with("branches", dtos) + .build(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java new file mode 100644 index 0000000000..f5bdc850ab --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java @@ -0,0 +1,19 @@ +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; + +@Getter @Setter @NoArgsConstructor +public class BranchDto extends HalRepresentation { + + private String name; + private String revision; + + @Override + protected HalRepresentation add(Links links) { + return super.add(links); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchResource.java new file mode 100644 index 0000000000..f76363f88e --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchResource.java @@ -0,0 +1,30 @@ +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.web.VndMediaType; + +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 BranchResource { + + @GET + @Path("") + @Produces(VndMediaType.BRANCH) + @TypeHint(BranchDto.class) + @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 branch"), + @ResponseCode(code = 404, condition = "not found, no branch with the specified name for the repository available"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("branch") String branch) { + return null; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java index ee50fdcd1e..a656aa36a4 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRootResource.java @@ -7,10 +7,17 @@ import javax.ws.rs.Path; public class BranchRootResource { private final Provider branchCollectionResource; + private final Provider branchResource; @Inject - public BranchRootResource(Provider branchCollectionResource) { + public BranchRootResource(Provider branchCollectionResource, Provider branchResource) { this.branchCollectionResource = branchCollectionResource; + this.branchResource = branchResource; + } + + @Path("{branch}") + public BranchResource getBranchResource() { + return branchResource.get(); } @Path("") diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapper.java new file mode 100644 index 0000000000..edced65c44 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchToBranchDtoMapper.java @@ -0,0 +1,30 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Links; +import org.mapstruct.AfterMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import sonia.scm.repository.Branch; +import sonia.scm.repository.NamespaceAndName; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Links.linkingTo; + +@Mapper +public abstract class BranchToBranchDtoMapper { + + @Inject + private ResourceLinks resourceLinks; + + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + public abstract BranchDto map(Branch branch, @Context NamespaceAndName namespaceAndName); + + @AfterMapping + void appendLinks(@MappingTarget BranchDto target, @Context NamespaceAndName namespaceAndName) { + Links.Builder linksBuilder = linkingTo().self(resourceLinks.branch().self(namespaceAndName, target.getName())); + target.add(linksBuilder.build()); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java index 69430bf43b..2779ae4d5a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -21,6 +21,8 @@ public class MapperModule extends AbstractModule { bind(RepositoryToRepositoryDtoMapper.class).to(Mappers.getMapper(RepositoryToRepositoryDtoMapper.class).getClass()); bind(RepositoryDtoToRepositoryMapper.class).to(Mappers.getMapper(RepositoryDtoToRepositoryMapper.class).getClass()); + bind(BranchToBranchDtoMapper.class).to(Mappers.getMapper(BranchToBranchDtoMapper.class).getClass()); + bind(UriInfoStore.class).in(ServletScopes.REQUEST); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java index 255f08f295..8fdaa00a19 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java @@ -136,7 +136,7 @@ public class RepositoryResource { } @Path("branches/") - public BranchRootResource branches() { + public BranchRootResource branches(@PathParam("namespace") String namespace, @PathParam("name") String name) { return branchRootResource.get(); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index d02747d2af..31e7d45007 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -1,5 +1,7 @@ package sonia.scm.api.v2.resources; +import sonia.scm.repository.NamespaceAndName; + import javax.inject.Inject; import javax.ws.rs.core.UriInfo; @@ -181,6 +183,22 @@ class ResourceLinks { } } + public BranchLinks branch() { + return new BranchLinks(uriInfoStore.get()); + } + + static class BranchLinks { + private final LinkBuilder branchLinkBuilder; + + BranchLinks(UriInfo uriInfo) { + branchLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, BranchRootResource.class, BranchResource.class); + } + + String self(NamespaceAndName namespaceAndName, String branch) { + return branchLinkBuilder.method("getRepositoryResource").parameters(namespaceAndName.getNamespace(), namespaceAndName.getName()).method("branches").parameters().method("getBranchResource").parameters().method("get").parameters(branch).href(); + } + } + public BranchCollectionLinks branchCollection() { return new BranchCollectionLinks(uriInfoStore.get()); }