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());
}