diff --git a/scm-core/src/main/java/sonia/scm/repository/Changeset.java b/scm-core/src/main/java/sonia/scm/repository/Changeset.java index fecfd74cc9..ca2c9ac5db 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Changeset.java +++ b/scm-core/src/main/java/sonia/scm/repository/Changeset.java @@ -33,27 +33,20 @@ package sonia.scm.repository; -//~--- non-JDK imports -------------------------------------------------------- - import com.google.common.base.Objects; - import sonia.scm.BasicPropertiesAware; -import sonia.scm.Validateable; +import sonia.scm.ModelObject; import sonia.scm.util.Util; import sonia.scm.util.ValidationUtil; -//~--- JDK imports ------------------------------------------------------------ - -import java.io.Serializable; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + /** * Represents a changeset/commit of a repository. @@ -62,43 +55,58 @@ import javax.xml.bind.annotation.XmlRootElement; */ @XmlRootElement(name = "changeset") @XmlAccessorType(XmlAccessType.FIELD) -public class Changeset extends BasicPropertiesAware - implements Validateable, Serializable -{ +public class Changeset extends BasicPropertiesAware implements ModelObject { - /** Field description */ private static final long serialVersionUID = -8373308448928993039L; - //~--- constructors --------------------------------------------------------- + /** + * The author of the changeset + */ + private Person author; /** - * Constructs a new instance of changeset. - * + * The name of the branches on which the changeset was committed. */ + private List branches; + + /** + * The date when the changeset was committed + */ + private Long date; + + /** + * The text of the changeset description + */ + private String description; + + /** + * The changeset identification string + */ + private String id; + + /** + * List of files changed by this changeset + */ + @XmlElement(name = "modifications") + private Modifications modifications; + + /** + * parent changeset ids + */ + private List parents; + + /** + * The tags associated with the changeset + */ + private List tags; + public Changeset() {} - /** - * Constructs a new instance of changeset. - * - * - * @param id id of the changeset - * @param date date of the changeset - * @param author author of the changeset - */ public Changeset(String id, Long date, Person author) { this(id, date, author, null); } - /** - * Constructs a new instance of changeset. - * - * - * @param id id of the changeset - * @param date date of the changeset - * @param author author of the changeset - * @param description description of the changeset - */ public Changeset(String id, Long date, Person author, String description) { this.id = id; @@ -107,16 +115,6 @@ public class Changeset extends BasicPropertiesAware this.description = description; } - //~--- methods -------------------------------------------------------------- - - /** - * {@inheritDoc} - * - * - * @param obj - * - * @return - */ @Override public boolean equals(Object obj) { @@ -125,15 +123,14 @@ public class Changeset extends BasicPropertiesAware return false; } - if (getClass() != obj.getClass()) - { + if (getClass() != obj.getClass()) { return false; } final Changeset other = (Changeset) obj; //J- - return Objects.equal(id, other.id) + return Objects.equal(id, other.id) && Objects.equal(date, other.date) && Objects.equal(author, other.author) && Objects.equal(description, other.description) @@ -195,7 +192,21 @@ public class Changeset extends BasicPropertiesAware return out.toString(); } - //~--- get methods ---------------------------------------------------------- + + /** + * Returns a timestamp of the creation date of the {@link Changeset}. + * + * @return a timestamp of the creation date of the {@link Changeset} + */ + public Long getCreationDate() { + return getDate(); + } + + @Override + public void setCreationDate(Long timestamp) { + this.setDate(timestamp); + } + /** * Returns the author of the changeset. @@ -203,14 +214,13 @@ public class Changeset extends BasicPropertiesAware * * @return author of the changeset */ - public Person getAuthor() - { + public Person getAuthor() { return author; } /** - * Returns the branches of the changeset. In the most cases a changeset is - * only related to one branch, but in the case of receive hooks it is possible + * Returns the branches of the changeset. In the most cases a changeset is + * only related to one branch, but in the case of receive hooks it is possible * that a changeset is related to more than a branch. * * @@ -254,11 +264,27 @@ public class Changeset extends BasicPropertiesAware * * @return id of the changeset */ - public String getId() - { + @Override + public String getId() { return id; } + @Override + public void setLastModified(Long timestamp) { + this.setDate(timestamp); + } + + @Override + public Long getLastModified() { + return getCreationDate(); + } + + @Override + public String getType() { + return "Changeset"; + } + + /** * Returns the file modifications, which was done with this changeset. * @@ -321,8 +347,6 @@ public class Changeset extends BasicPropertiesAware && (date != null); } - //~--- set methods ---------------------------------------------------------- - /** * Sets the author of the changeset. * @@ -412,30 +436,4 @@ public class Changeset extends BasicPropertiesAware this.tags = tags; } - //~--- fields --------------------------------------------------------------- - - /** The author of the changeset */ - private Person author; - - /** The name of the branches on which the changeset was committed. */ - private List branches; - - /** The date when the changeset was committed */ - private Long date; - - /** The text of the changeset description */ - private String description; - - /** The changeset identification string */ - private String id; - - /** List of files changed by this changeset */ - @XmlElement(name = "modifications") - private Modifications modifications; - - /** parent changeset ids */ - private List parents; - - /** The tags associated with the changeset */ - private List tags; } diff --git a/scm-core/src/main/java/sonia/scm/repository/Person.java b/scm-core/src/main/java/sonia/scm/repository/Person.java index b3bf15c49a..8b5e66f61a 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Person.java +++ b/scm-core/src/main/java/sonia/scm/repository/Person.java @@ -36,18 +36,16 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.base.Objects; - import sonia.scm.Validateable; import sonia.scm.util.Util; import sonia.scm.util.ValidationUtil; -//~--- JDK imports ------------------------------------------------------------ - -import java.io.Serializable; - import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +//~--- JDK imports ------------------------------------------------------------ /** * The {@link Person} (author) of a changeset. @@ -251,9 +249,11 @@ public class Person implements Validateable, Serializable //~--- fields --------------------------------------------------------------- - /** name of the person */ + /** mail address of the person */ private String mail; - /** mail address of the person */ + /** + * name of the person + */ private String name; } 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 afb1417670..7541dd9766 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -17,6 +17,8 @@ public class VndMediaType { public static final String GROUP = PREFIX + "group" + SUFFIX; public static final String REPOSITORY = PREFIX + "repository" + SUFFIX; public static final String PERMISSION = PREFIX + "permission" + SUFFIX; + public static final String CHANGESET = PREFIX + "changeset" + SUFFIX; + public static final String CHANGESET_COLLECTION = PREFIX + "changesetCollection" + 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; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BasicCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BasicCollectionToDtoMapper.java index 4700d22841..4787452003 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BasicCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BasicCollectionToDtoMapper.java @@ -11,6 +11,7 @@ import sonia.scm.PageResult; import javax.inject.Inject; import java.util.EnumSet; import java.util.List; +import java.util.function.Function; import static com.damnhandy.uri.template.UriTemplate.fromTemplate; import static de.otto.edison.hal.Embedded.embeddedBuilder; @@ -19,25 +20,28 @@ import static de.otto.edison.hal.Links.linkingTo; import static de.otto.edison.hal.paging.NumberedPaging.zeroBasedNumberedPaging; import static java.util.stream.Collectors.toList; -abstract class BasicCollectionToDtoMapper { +abstract class BasicCollectionToDtoMapper> { private final String collectionName; - private final BaseMapper entityToDtoMapper; + + protected final M entityToDtoMapper; @Inject - public BasicCollectionToDtoMapper(String collectionName, BaseMapper entityToDtoMapper) { + public BasicCollectionToDtoMapper(String collectionName, M entityToDtoMapper) { this.collectionName = collectionName; this.entityToDtoMapper = entityToDtoMapper; } public CollectionDto map(int pageNumber, int pageSize, PageResult pageResult) { - NumberedPaging paging = zeroBasedNumberedPaging(pageNumber, pageSize, pageResult.getOverallCount()); - List dtos = pageResult.getEntities().stream().map(entityToDtoMapper::map).collect(toList()); + return map(pageNumber, pageSize, pageResult, entityToDtoMapper::map, createSelfLink()); + } + public CollectionDto map(int pageNumber, int pageSize, PageResult pageResult, Function mapper, String selfLink) { + NumberedPaging paging = zeroBasedNumberedPaging(pageNumber, pageSize, pageResult.getOverallCount()); + List dtos = pageResult.getEntities().stream().map(mapper).collect(toList()); CollectionDto collectionDto = new CollectionDto( - createLinks(paging), - embedDtos(dtos) - ); + createLinks(paging, selfLink), + embedDtos(dtos)); collectionDto.setPage(pageNumber); collectionDto.setPageTotal(computePageTotal(pageSize, pageResult)); return collectionDto; @@ -51,9 +55,7 @@ abstract class BasicCollectionToDtoMapper branches) { - List dtos = branches.stream().map(branch -> branchToDtoMapper.map(branch, new NamespaceAndName(namespace, name))).collect(toList()); - return new HalRepresentation(createLinks(namespace, name), embedDtos(dtos)); + return new HalRepresentation(createLinks(namespace, name), embedDtos(getBranchDtoList(namespace, name, branches))); + } + + public List getBranchDtoList(String namespace, String name, Collection branches) { + return branches.stream().map(branch -> branchToDtoMapper.map(branch, new NamespaceAndName(namespace, name))).collect(toList()); } private Links createLinks(String namespace, String name) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapper.java new file mode 100644 index 0000000000..93eb372fe9 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetCollectionToDtoMapper.java @@ -0,0 +1,42 @@ +package sonia.scm.api.v2.resources; + +import sonia.scm.PageResult; +import sonia.scm.repository.Changeset; +import sonia.scm.repository.Repository; + +import javax.inject.Inject; + +public class ChangesetCollectionToDtoMapper extends BasicCollectionToDtoMapper { + + private final ResourceLinks resourceLinks; + + @Inject + public ChangesetCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) { + super("changesets", changesetToChangesetDtoMapper); + this.resourceLinks = resourceLinks; + } + + public CollectionDto map(int pageNumber, int pageSize, PageResult pageResult, Repository repository) { + return super.map(pageNumber, pageSize, pageResult, changeset -> super.entityToDtoMapper.map(changeset, repository), createSelfLink(repository)); + } + + + @Override + String createCreateLink() { + return null; + } + + @Override + String createSelfLink() { + return null; + } + + private String createSelfLink(Repository repository) { + return resourceLinks.changeset().all(repository.getNamespace(), repository.getName()); + } + + @Override + boolean isCreatePermitted() { + return false; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java new file mode 100644 index 0000000000..162d5d6699 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetDto.java @@ -0,0 +1,49 @@ +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; + +import java.time.Instant; +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +public class ChangesetDto extends HalRepresentation { + + /** + * The changeset identification string + */ + private String id; + + /** + * The author of the changeset + */ + private PersonDto author; + + /** + * The date when the changeset was committed + */ + private Instant date; + + /** + * The text of the changeset description + */ + private String description; + + @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package + protected HalRepresentation add(Links links) { + return super.add(links); + } + + @SuppressWarnings("squid:S1185") // We want to have this method available in this package + protected HalRepresentation withEmbedded(String rel, List halRepresentations) { + return super.withEmbedded(rel, halRepresentations); + } + + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java index 42ffbdd6c1..aa3aa5924b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetRootResource.java @@ -1,25 +1,84 @@ 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 lombok.extern.slf4j.Slf4j; +import sonia.scm.PageResult; +import sonia.scm.repository.Changeset; +import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryException; +import sonia.scm.repository.RepositoryNotFoundException; +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.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; +import java.io.IOException; + +@Slf4j public class ChangesetRootResource { + private final RepositoryServiceFactory serviceFactory; + + private final ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper; + + @Inject + public ChangesetRootResource(RepositoryServiceFactory serviceFactory, ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper) { + this.serviceFactory = serviceFactory; + this.changesetCollectionToDtoMapper = changesetCollectionToDtoMapper; + } + @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 changeset"), + @ResponseCode(code = 404, condition = "not found, no changeset with the specified name available in the repository"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @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) { + try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) { + ChangesetPagingResult changesets; + Repository repository = repositoryService.getRepository(); + changesets = repositoryService.getLogCommand() + .setPagingStart(page) + .setPagingLimit(pageSize) + .getChangesets(); + if (changesets != null) { + PageResult pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal()); + return Response.ok(changesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository)).build(); + } else { + return Response.ok().build(); + } + } catch (RepositoryNotFoundException e) { + log.debug("Not found in repository {}/{}", namespace, name, e); + return Response.status(Response.Status.NOT_FOUND).build(); + } catch (RepositoryException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return Response.ok().build(); } @GET - @Path("{revision}") - public Response get() { + @Path("{id}") + public Response get(@PathParam("id") String id) { throw new UnsupportedOperationException(); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java new file mode 100644 index 0000000000..f047e3bbaa --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToChangesetDtoMapper.java @@ -0,0 +1,78 @@ +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.Changeset; +import sonia.scm.repository.Repository; +import sonia.scm.repository.Tag; +import sonia.scm.repository.api.Command; +import sonia.scm.repository.api.RepositoryService; +import sonia.scm.repository.api.RepositoryServiceFactory; + +import javax.inject.Inject; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static de.otto.edison.hal.Link.link; +import static de.otto.edison.hal.Links.linkingTo; + +@Mapper +public abstract class ChangesetToChangesetDtoMapper extends BaseMapper { + + @Inject + private RepositoryServiceFactory serviceFactory; + + @Inject + private ResourceLinks resourceLinks; + + + @Inject + private BranchCollectionToDtoMapper branchCollectionToDtoMapper; + + @Inject + private ChangesetToParentDtoMapper changesetToParentDtoMapper; + + @Inject + private TagCollectionToDtoMapper tagCollectionToDtoMapper; + + + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + public abstract ChangesetDto map(Changeset changeset, @Context Repository repository); + + + @AfterMapping + void appendLinks(Changeset source, @MappingTarget ChangesetDto target, @Context Repository repository) { + String namespace = repository.getNamespace(); + String name = repository.getName(); + + try (RepositoryService repositoryService = serviceFactory.create(repository)) { + if (repositoryService.isSupported(Command.TAGS)) { + target.withEmbedded("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name, + getListOfObjects(source.getTags(), tagName -> new Tag(tagName, source.getId())))); + } + if (repositoryService.isSupported(Command.BRANCHES)) { + target.withEmbedded("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name, + getListOfObjects(source.getBranches(), branchName -> new Branch(branchName, source.getId())))); + } + } + target.withEmbedded("parents", getListOfObjects(source.getParents(), parent -> changesetToParentDtoMapper.map(new Changeset(parent, 0L, null), repository))); + + Links.Builder linksBuilder = linkingTo() + .self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), target.getId())) + .single(link("diff", resourceLinks.diff().self(namespace, name, target.getId()))); + target.add(linksBuilder.build()); + } + + private List getListOfObjects(List list, Function mapFunction) { + return list + .stream() + .map(mapFunction) + .collect(Collectors.toList()); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToParentDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToParentDtoMapper.java new file mode 100644 index 0000000000..611f5e6cbb --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ChangesetToParentDtoMapper.java @@ -0,0 +1,38 @@ +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.Changeset; +import sonia.scm.repository.Repository; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Link.link; +import static de.otto.edison.hal.Links.linkingTo; + +@Mapper +public abstract class ChangesetToParentDtoMapper extends BaseMapper { + + @Inject + private ResourceLinks resourceLinks; + + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + public abstract ParentChangesetDto map(Changeset changeset, @Context Repository repository); + + + @AfterMapping + void appendLinks(@MappingTarget ParentChangesetDto target, @Context Repository repository) { + String namespace = repository.getNamespace(); + String name = repository.getName(); + Links.Builder linksBuilder = linkingTo() + .self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), target.getId())) + .single(link("diff", resourceLinks.diff().self(namespace, name, target.getId()))); + target.add(linksBuilder.build()); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java new file mode 100644 index 0000000000..176a86dda7 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DiffRootResource.java @@ -0,0 +1,27 @@ +package sonia.scm.api.v2.resources; + +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +public class DiffRootResource { + + + @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(); + } + + @GET + @Path("{id}") + public Response get(String id) { + throw new UnsupportedOperationException(); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionToDtoMapper.java index 415f04bfaf..8d13662b10 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionToDtoMapper.java @@ -5,7 +5,7 @@ import sonia.scm.group.GroupPermissions; import javax.inject.Inject; -public class GroupCollectionToDtoMapper extends BasicCollectionToDtoMapper { +public class GroupCollectionToDtoMapper extends BasicCollectionToDtoMapper { private final ResourceLinks resourceLinks; 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 0605d943e7..9c7a390597 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 @@ -28,6 +28,11 @@ public class MapperModule extends AbstractModule { bind(PermissionDtoToPermissionMapper.class).to(Mappers.getMapper(PermissionDtoToPermissionMapper.class).getClass()); bind(PermissionToPermissionDtoMapper.class).to(Mappers.getMapper(PermissionToPermissionDtoMapper.class).getClass()); + bind(ChangesetToChangesetDtoMapper.class).to(Mappers.getMapper(ChangesetToChangesetDtoMapper.class).getClass()); + bind(ChangesetToParentDtoMapper.class).to(Mappers.getMapper(ChangesetToParentDtoMapper.class).getClass()); + + bind(TagToTagDtoMapper.class).to(Mappers.getMapper(TagToTagDtoMapper.class).getClass()); + bind(UriInfoStore.class).in(ServletScopes.REQUEST); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ParentChangesetDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ParentChangesetDto.java new file mode 100644 index 0000000000..17dbc0df22 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ParentChangesetDto.java @@ -0,0 +1,27 @@ +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; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class ParentChangesetDto extends HalRepresentation { + + /** + * the id of the parent changeset + */ + private String id; + + @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package + protected HalRepresentation add(Links links) { + return super.add(links); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PersonDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PersonDto.java new file mode 100644 index 0000000000..ce35b507c8 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PersonDto.java @@ -0,0 +1,22 @@ +package sonia.scm.api.v2.resources; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class PersonDto { + + /** + * mail address of the person + */ + private String mail; + + /** + * name of the person + */ + private String name; + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionToDtoMapper.java index a1cf0218e4..9a5fbb1429 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionToDtoMapper.java @@ -7,7 +7,7 @@ import javax.inject.Inject; // Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. @SuppressWarnings("squid:S3306") -public class RepositoryCollectionToDtoMapper extends BasicCollectionToDtoMapper { +public class RepositoryCollectionToDtoMapper extends BasicCollectionToDtoMapper { private final ResourceLinks resourceLinks; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java index 0263b81048..84ad8f57bc 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java @@ -40,13 +40,13 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper tags) { + return new HalRepresentation(createLinks(namespace, name), embedDtos(getTagDtoList(namespace, name, tags))); + } + + public List getTagDtoList(String namespace, String name, Collection tags) { + return tags.stream().map(tag -> tagToTagDtoMapper.map(tag, new NamespaceAndName(namespace, name))).collect(toList()); + } + + private Links createLinks(String namespace, String name) { + return + linkingTo() + .self(resourceLinks.tag().all(namespace, name)) + .build(); + } + + private Embedded embedDtos(List dtos) { + return embeddedBuilder() + .with("tags", dtos) + .build(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java new file mode 100644 index 0000000000..8af036f5a3 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java @@ -0,0 +1,24 @@ +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 TagDto extends HalRepresentation { + + private String name; + + private String revision; + + @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package + protected HalRepresentation add(Links links) { + return super.add(links); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java index 29a2a922ca..10b4607fbc 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagRootResource.java @@ -1,20 +1,27 @@ package sonia.scm.api.v2.resources; -import javax.inject.Inject; -import javax.inject.Provider; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; public class TagRootResource { - private final Provider tagCollectionResource; - - @Inject - public TagRootResource(Provider tagCollectionResource) { - this.tagCollectionResource = tagCollectionResource; - } + @GET @Path("") - public TagCollectionResource getTagCollectionResource() { - return tagCollectionResource.get(); + 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(); } + + @GET + @Path("{id}") + public Response get(String id) { + throw new UnsupportedOperationException(); + } + } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java new file mode 100644 index 0000000000..ada0fa2887 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java @@ -0,0 +1,31 @@ +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.NamespaceAndName; +import sonia.scm.repository.Tag; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Links.linkingTo; + +@Mapper +public abstract class TagToTagDtoMapper { + + @Inject + private ResourceLinks resourceLinks; + + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + public abstract TagDto map(Tag tag, @Context NamespaceAndName namespaceAndName); + + @AfterMapping + void appendLinks(@MappingTarget TagDto target, @Context NamespaceAndName namespaceAndName) { + Links.Builder linksBuilder = linkingTo() + .self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getName())); + target.add(linksBuilder.build()); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapper.java index db2a9afc9f..01517d63e7 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapper.java @@ -7,7 +7,7 @@ import javax.inject.Inject; // Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. @SuppressWarnings("squid:S3306") -public class UserCollectionToDtoMapper extends BasicCollectionToDtoMapper { +public class UserCollectionToDtoMapper extends BasicCollectionToDtoMapper { private final ResourceLinks resourceLinks; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java index 1b23b18f27..6ace303b21 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchRootResourceTest.java @@ -45,7 +45,7 @@ public class BranchRootResourceTest { public void prepareEnvironment() throws Exception { BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks); BranchRootResource branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper); - RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider.of(new RepositoryResource(null, null, null, null, MockProvider.of(branchRootResource), null, null, null, null)), null); + RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider.of(new RepositoryResource(null, null, null, null, MockProvider.of(branchRootResource), null, null, null, null, null)), null); dispatcher.getRegistry().addSingletonResource(repositoryRootResource); when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java index 3736f99301..8f7e40425a 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java @@ -28,7 +28,11 @@ import org.junit.jupiter.api.TestFactory; import org.mockito.InjectMocks; import org.mockito.Mock; import sonia.scm.api.rest.AuthorizationExceptionMapper; -import sonia.scm.repository.*; +import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.Permission; +import sonia.scm.repository.PermissionType; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryManager; import sonia.scm.web.VndMediaType; import java.io.IOException; @@ -46,7 +50,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; @Slf4j @@ -129,7 +136,7 @@ public class PermissionRootResourceTest { permissionCollectionToDtoMapper = new PermissionCollectionToDtoMapper(permissionToPermissionDtoMapper, resourceLinks); permissionRootResource = new PermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, permissionCollectionToDtoMapper, resourceLinks, repositoryManager); RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider - .of(new RepositoryResource(null, null, null, null, null, null, null, null, MockProvider.of(permissionRootResource))), null); + .of(new RepositoryResource(null, null, null, null, null, null, null, null, MockProvider.of(permissionRootResource), null)), null); subjectThreadState.bind(); ThreadContext.bind(subject); dispatcher.getRegistry().addSingletonResource(repositoryRootResource); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java index 31cacf2d72..e7912c55bc 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java @@ -80,7 +80,7 @@ public class RepositoryRootResourceTest { @Before public void prepareEnvironment() { initMocks(this); - RepositoryResource repositoryResource = new RepositoryResource(repositoryToDtoMapper, dtoToRepositoryMapper, repositoryManager, null, null, null, null, null, null); + RepositoryResource repositoryResource = new RepositoryResource(repositoryToDtoMapper, dtoToRepositoryMapper, repositoryManager, null, null, null, null, null, null, null); RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks); RepositoryCollectionResource repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks); RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider.of(repositoryResource), MockProvider.of(repositoryCollectionResource)); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java index 6df5ac1a7a..6be360c585 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java @@ -19,7 +19,7 @@ public class ResourceLinksMock { when(resourceLinks.groupCollection()).thenReturn(new ResourceLinks.GroupCollectionLinks(uriInfo)); when(resourceLinks.repository()).thenReturn(new ResourceLinks.RepositoryLinks(uriInfo)); when(resourceLinks.repositoryCollection()).thenReturn(new ResourceLinks.RepositoryCollectionLinks(uriInfo)); - when(resourceLinks.tagCollection()).thenReturn(new ResourceLinks.TagCollectionLinks(uriInfo)); + when(resourceLinks.tag()).thenReturn(new ResourceLinks.TagCollectionLinks(uriInfo)); when(resourceLinks.branchCollection()).thenReturn(new ResourceLinks.BranchCollectionLinks(uriInfo)); when(resourceLinks.changeset()).thenReturn(new ResourceLinks.ChangesetLinks(uriInfo)); when(resourceLinks.source()).thenReturn(new ResourceLinks.SourceLinks(uriInfo)); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java index 000e628f26..b65b516902 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java @@ -105,7 +105,7 @@ public class ResourceLinksTest { @Test public void shouldCreateCorrectTagCollectionUrl() { - String url = resourceLinks.tagCollection().self("space", "repo"); + String url = resourceLinks.tag().all("space", "repo"); assertEquals(BASE_URL + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/tags/", url); } @@ -141,7 +141,7 @@ public class ResourceLinksTest { @Test public void shouldCreateCorrectChangesetCollectionUrl() { - String url = resourceLinks.changeset().self("space", "repo"); + String url = resourceLinks.changeset().all("space", "repo"); assertEquals(BASE_URL + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/changesets/", url); }