added appendEmbedded method to HalAppender

With this change we could not longer use @AfterMapping from MapStruct.
We should use @ObjectFactory instead and create Links and Embedded before.
This commit is contained in:
Sebastian Sdorra
2019-02-04 16:18:47 +01:00
parent 462cccb443
commit 21441aed45
24 changed files with 196 additions and 161 deletions

View File

@@ -1,5 +1,7 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
/** /**
* The {@link HalAppender} can be used within an {@link HalEnricher} to append hateoas links to a json response. * The {@link HalAppender} can be used within an {@link HalEnricher} to append hateoas links to a json response.
* *
@@ -24,6 +26,13 @@ public interface HalAppender {
*/ */
LinkArrayBuilder linkArrayBuilder(String rel); LinkArrayBuilder linkArrayBuilder(String rel);
/**
* Appends one embedded to the json response.
*
* @param rel name of relation
* @param embeddedItem embedded object
*/
void appendEmbedded(String rel, HalRepresentation embeddedItem);
/** /**
* Builder for link arrays. * Builder for link arrays.

View File

@@ -14,7 +14,7 @@ public class HalAppenderMapper {
this.registry = registry; this.registry = registry;
} }
protected void appendLinks(HalAppender appender, Object source, Object... contextEntries) { protected void applyEnrichers(HalAppender appender, Object source, Object... contextEntries) {
// null check is only their to not break existing tests // null check is only their to not break existing tests
if (registry != null) { if (registry != null) {

View File

@@ -30,7 +30,7 @@ class HalAppenderMapperTest {
void shouldAppendSimpleLink() { void shouldAppendSimpleLink() {
registry.register(String.class, (ctx, appender) -> appender.appendLink("42", "https://hitchhiker.com")); registry.register(String.class, (ctx, appender) -> appender.appendLink("42", "https://hitchhiker.com"));
mapper.appendLinks(appender, "hello"); mapper.applyEnrichers(appender, "hello");
verify(appender).appendLink("42", "https://hitchhiker.com"); verify(appender).appendLink("42", "https://hitchhiker.com");
} }
@@ -40,7 +40,7 @@ class HalAppenderMapperTest {
registry.register(String.class, (ctx, appender) -> appender.appendLink("42", "https://hitchhiker.com")); registry.register(String.class, (ctx, appender) -> appender.appendLink("42", "https://hitchhiker.com"));
registry.register(String.class, (ctx, appender) -> appender.appendLink("21", "https://scm.hitchhiker.com")); registry.register(String.class, (ctx, appender) -> appender.appendLink("21", "https://scm.hitchhiker.com"));
mapper.appendLinks(appender, "hello"); mapper.applyEnrichers(appender, "hello");
verify(appender).appendLink("42", "https://hitchhiker.com"); verify(appender).appendLink("42", "https://hitchhiker.com");
verify(appender).appendLink("21", "https://scm.hitchhiker.com"); verify(appender).appendLink("21", "https://scm.hitchhiker.com");
@@ -53,7 +53,7 @@ class HalAppenderMapperTest {
appender.appendLink(rel.get(), "https://hitchhiker.com"); appender.appendLink(rel.get(), "https://hitchhiker.com");
}); });
mapper.appendLinks(appender, "42"); mapper.applyEnrichers(appender, "42");
verify(appender).appendLink("42", "https://hitchhiker.com"); verify(appender).appendLink("42", "https://hitchhiker.com");
} }
@@ -66,7 +66,7 @@ class HalAppenderMapperTest {
appender.appendLink(String.valueOf(rel.get()), href.get()); appender.appendLink(String.valueOf(rel.get()), href.get());
}); });
mapper.appendLinks(appender, Integer.valueOf(42), "https://hitchhiker.com"); mapper.applyEnrichers(appender, Integer.valueOf(42), "https://hitchhiker.com");
verify(appender).appendLink("42", "https://hitchhiker.com"); verify(appender).appendLink("42", "https://hitchhiker.com");
} }

View File

@@ -1,5 +1,6 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import lombok.Getter; import lombok.Getter;
@@ -12,8 +13,7 @@ public class BranchDto extends HalRepresentation {
private String name; private String name;
private String revision; private String revision;
@Override BranchDto(Links links, Embedded embedded) {
protected HalRepresentation add(Links links) { super(links, embedded);
return super.add(links);
} }
} }

View File

@@ -1,11 +1,11 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Context; import org.mapstruct.Context;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.ObjectFactory;
import sonia.scm.repository.Branch; import sonia.scm.repository.Branch;
import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.NamespaceAndName;
@@ -23,16 +23,17 @@ public abstract class BranchToBranchDtoMapper extends HalAppenderMapper {
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
public abstract BranchDto map(Branch branch, @Context NamespaceAndName namespaceAndName); public abstract BranchDto map(Branch branch, @Context NamespaceAndName namespaceAndName);
@AfterMapping @ObjectFactory
void appendLinks(Branch source, @MappingTarget BranchDto target, @Context NamespaceAndName namespaceAndName) { BranchDto createDto(@Context NamespaceAndName namespaceAndName, Branch branch) {
Links.Builder linksBuilder = linkingTo() Links.Builder linksBuilder = linkingTo()
.self(resourceLinks.branch().self(namespaceAndName, target.getName())) .self(resourceLinks.branch().self(namespaceAndName, branch.getName()))
.single(linkBuilder("history", resourceLinks.branch().history(namespaceAndName, target.getName())).build()) .single(linkBuilder("history", resourceLinks.branch().history(namespaceAndName, branch.getName())).build())
.single(linkBuilder("changeset", resourceLinks.changeset().changeset(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build()) .single(linkBuilder("changeset", resourceLinks.changeset().changeset(namespaceAndName.getNamespace(), namespaceAndName.getName(), branch.getRevision())).build())
.single(linkBuilder("source", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build()); .single(linkBuilder("source", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), branch.getRevision())).build());
appendLinks(new EdisonHalAppender(linksBuilder), source, namespaceAndName); Embedded.Builder embeddedBuilder = Embedded.embeddedBuilder();
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), branch, namespaceAndName);
target.add(linksBuilder.build()); return new BranchDto(linksBuilder.build(), embeddedBuilder.build());
} }
} }

View File

@@ -1,5 +1,6 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import lombok.Getter; import lombok.Getter;
@@ -7,7 +8,6 @@ import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import java.time.Instant; import java.time.Instant;
import java.util.List;
@Getter @Getter
@Setter @Setter
@@ -34,16 +34,7 @@ public class ChangesetDto extends HalRepresentation {
*/ */
private String description; private String description;
@Override public ChangesetDto(Links links, Embedded embedded) {
@SuppressWarnings("squid:S1185") // We want to have this method available in this package super(links, embedded);
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<? extends HalRepresentation> halRepresentations) {
return super.withEmbedded(rel, halRepresentations);
}
} }

View File

@@ -1,11 +1,11 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Context; import org.mapstruct.Context;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.ObjectFactory;
import sonia.scm.repository.Branch; import sonia.scm.repository.Branch;
import sonia.scm.repository.Changeset; import sonia.scm.repository.Changeset;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
@@ -19,6 +19,7 @@ import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo; import static de.otto.edison.hal.Links.linkingTo;
@@ -31,7 +32,6 @@ public abstract class ChangesetToChangesetDtoMapper extends HalAppenderMapper im
@Inject @Inject
private ResourceLinks resourceLinks; private ResourceLinks resourceLinks;
@Inject @Inject
private BranchCollectionToDtoMapper branchCollectionToDtoMapper; private BranchCollectionToDtoMapper branchCollectionToDtoMapper;
@@ -46,31 +46,34 @@ public abstract class ChangesetToChangesetDtoMapper extends HalAppenderMapper im
public abstract ChangesetDto map(Changeset changeset, @Context Repository repository); public abstract ChangesetDto map(Changeset changeset, @Context Repository repository);
@AfterMapping @ObjectFactory
void appendLinks(Changeset source, @MappingTarget ChangesetDto target, @Context Repository repository) { ChangesetDto createDto(@Context Repository repository, Changeset source) {
String namespace = repository.getNamespace(); String namespace = repository.getNamespace();
String name = repository.getName(); String name = repository.getName();
Embedded.Builder embeddedBuilder = embeddedBuilder();
try (RepositoryService repositoryService = serviceFactory.create(repository)) { try (RepositoryService repositoryService = serviceFactory.create(repository)) {
if (repositoryService.isSupported(Command.TAGS)) { if (repositoryService.isSupported(Command.TAGS)) {
target.withEmbedded("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name, embeddedBuilder.with("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name,
getListOfObjects(source.getTags(), tagName -> new Tag(tagName, source.getId())))); getListOfObjects(source.getTags(), tagName -> new Tag(tagName, source.getId()))));
} }
if (repositoryService.isSupported(Command.BRANCHES)) { if (repositoryService.isSupported(Command.BRANCHES)) {
target.withEmbedded("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name, embeddedBuilder.with("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name,
getListOfObjects(source.getBranches(), branchName -> new Branch(branchName, source.getId())))); getListOfObjects(source.getBranches(), branchName -> new Branch(branchName, source.getId()))));
} }
} }
target.withEmbedded("parents", getListOfObjects(source.getParents(), parent -> changesetToParentDtoMapper.map(new Changeset(parent, 0L, null), repository))); embeddedBuilder.with("parents", getListOfObjects(source.getParents(), parent -> changesetToParentDtoMapper.map(new Changeset(parent, 0L, null), repository)));
Links.Builder linksBuilder = linkingTo() Links.Builder linksBuilder = linkingTo()
.self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), target.getId())) .self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), source.getId()))
.single(link("diff", resourceLinks.diff().self(namespace, name, target.getId()))) .single(link("diff", resourceLinks.diff().self(namespace, name, source.getId())))
.single(link("modifications", resourceLinks.modifications().self(namespace, name, target.getId()))); .single(link("modifications", resourceLinks.modifications().self(namespace, name, source.getId())));
appendLinks(new EdisonHalAppender(linksBuilder), source, repository);
target.add(linksBuilder.build()); applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), source, repository);
return new ChangesetDto(linksBuilder.build(), embeddedBuilder.build());
} }
private <T> List<T> getListOfObjects(List<String> list, Function<String, T> mapFunction) { private <T> List<T> getListOfObjects(List<String> list, Function<String, T> mapFunction) {

View File

@@ -1,5 +1,7 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Link; import de.otto.edison.hal.Link;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
@@ -8,20 +10,27 @@ import java.util.List;
class EdisonHalAppender implements HalAppender { class EdisonHalAppender implements HalAppender {
private final Links.Builder builder; private final Links.Builder linkBuilder;
private final Embedded.Builder embeddedBuilder;
EdisonHalAppender(Links.Builder builder) { EdisonHalAppender(Links.Builder linkBuilder, Embedded.Builder embeddedBuilder) {
this.builder = builder; this.linkBuilder = linkBuilder;
this.embeddedBuilder = embeddedBuilder;
} }
@Override @Override
public void appendLink(String rel, String href) { public void appendLink(String rel, String href) {
builder.single(Link.link(rel, href)); linkBuilder.single(Link.link(rel, href));
} }
@Override @Override
public LinkArrayBuilder linkArrayBuilder(String rel) { public LinkArrayBuilder linkArrayBuilder(String rel) {
return new EdisonLinkArrayBuilder(builder, rel); return new EdisonLinkArrayBuilder(linkBuilder, rel);
}
@Override
public void appendEmbedded(String rel, HalRepresentation embedded) {
embeddedBuilder.with(rel, embedded);
} }
private static class EdisonLinkArrayBuilder implements LinkArrayBuilder { private static class EdisonLinkArrayBuilder implements LinkArrayBuilder {

View File

@@ -1,6 +1,7 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import lombok.Getter; import lombok.Getter;
@@ -27,10 +28,8 @@ public class FileObjectDto extends HalRepresentation {
@JsonInclude(JsonInclude.Include.NON_EMPTY) @JsonInclude(JsonInclude.Include.NON_EMPTY)
private String revision; private String revision;
@Override public FileObjectDto(Links links, Embedded embedded) {
@SuppressWarnings("squid:S1185") // We want to have this method available in this package super(links, embedded);
protected HalRepresentation add(Links links) {
return super.add(links);
} }
public void setChildren(List<FileObjectDto> children) { public void setChildren(List<FileObjectDto> children) {

View File

@@ -1,17 +1,18 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Context; import org.mapstruct.Context;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.ObjectFactory;
import sonia.scm.repository.FileObject; import sonia.scm.repository.FileObject;
import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.SubRepository; import sonia.scm.repository.SubRepository;
import javax.inject.Inject; import javax.inject.Inject;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Link.link;
@Mapper @Mapper
@@ -25,20 +26,21 @@ public abstract class FileObjectToFileObjectDtoMapper extends HalAppenderMapper
abstract SubRepositoryDto mapSubrepository(SubRepository subRepository); abstract SubRepositoryDto mapSubrepository(SubRepository subRepository);
@AfterMapping @ObjectFactory
void addLinks(FileObject fileObject, @MappingTarget FileObjectDto dto, @Context NamespaceAndName namespaceAndName, @Context String revision) { FileObjectDto createDto(@Context NamespaceAndName namespaceAndName, @Context String revision, FileObject fileObject) {
String path = removeFirstSlash(fileObject.getPath()); String path = removeFirstSlash(fileObject.getPath());
Links.Builder links = Links.linkingTo(); Links.Builder links = Links.linkingTo();
if (dto.isDirectory()) { if (fileObject.isDirectory()) {
links.self(resourceLinks.source().sourceWithPath(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path)); links.self(resourceLinks.source().sourceWithPath(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path));
} else { } else {
links.self(resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path)); links.self(resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path));
links.single(link("history", resourceLinks.fileHistory().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path))); links.single(link("history", resourceLinks.fileHistory().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path)));
} }
appendLinks(new EdisonHalAppender(links), fileObject, namespaceAndName, revision); Embedded.Builder embeddedBuilder = embeddedBuilder();
applyEnrichers(new EdisonHalAppender(links, embeddedBuilder), fileObject, namespaceAndName, revision);
dto.add(links.build()); return new FileObjectDto(links.build(), embeddedBuilder.build());
} }
private String removeFirstSlash(String source) { private String removeFirstSlash(String source) {

View File

@@ -1,6 +1,7 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import lombok.Getter; import lombok.Getter;
@@ -28,13 +29,7 @@ public class GroupDto extends HalRepresentation {
private Map<String, String> properties; private Map<String, String> properties;
private List<String> members; private List<String> members;
@Override GroupDto(Links links, Embedded embedded) {
@SuppressWarnings("squid:S1185") // We want to have this method available in this package super(links, embedded);
protected HalRepresentation add(Links links) {
return super.add(links);
}
public HalRepresentation withMembers(List<MemberDto> members) {
return super.withEmbedded("members", members);
} }
} }

View File

@@ -1,9 +1,9 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget; import org.mapstruct.ObjectFactory;
import sonia.scm.group.Group; import sonia.scm.group.Group;
import sonia.scm.group.GroupPermissions; import sonia.scm.group.GroupPermissions;
import sonia.scm.security.PermissionPermissions; import sonia.scm.security.PermissionPermissions;
@@ -12,6 +12,7 @@ import javax.inject.Inject;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo; import static de.otto.edison.hal.Links.linkingTo;
@@ -23,28 +24,26 @@ public abstract class GroupToGroupDtoMapper extends BaseMapper<Group, GroupDto>
@Inject @Inject
private ResourceLinks resourceLinks; private ResourceLinks resourceLinks;
@AfterMapping @ObjectFactory
void appendLinks(Group group, @MappingTarget GroupDto target) { GroupDto createDto(Group group) {
Links.Builder linksBuilder = linkingTo().self(resourceLinks.group().self(target.getName())); Links.Builder linksBuilder = linkingTo().self(resourceLinks.group().self(group.getName()));
if (GroupPermissions.delete(group).isPermitted()) { if (GroupPermissions.delete(group).isPermitted()) {
linksBuilder.single(link("delete", resourceLinks.group().delete(target.getName()))); linksBuilder.single(link("delete", resourceLinks.group().delete(group.getName())));
} }
if (GroupPermissions.modify(group).isPermitted()) { if (GroupPermissions.modify(group).isPermitted()) {
linksBuilder.single(link("update", resourceLinks.group().update(target.getName()))); linksBuilder.single(link("update", resourceLinks.group().update(group.getName())));
} }
if (PermissionPermissions.read().isPermitted()) { if (PermissionPermissions.read().isPermitted()) {
linksBuilder.single(link("permissions", resourceLinks.groupPermissions().permissions(target.getName()))); linksBuilder.single(link("permissions", resourceLinks.groupPermissions().permissions(group.getName())));
} }
appendLinks(new EdisonHalAppender(linksBuilder), group); Embedded.Builder embeddedBuilder = embeddedBuilder();
target.add(linksBuilder.build());
}
@AfterMapping
void mapMembers(Group group, @MappingTarget GroupDto target) {
List<MemberDto> memberDtos = group.getMembers().stream().map(this::createMember).collect(Collectors.toList()); List<MemberDto> memberDtos = group.getMembers().stream().map(this::createMember).collect(Collectors.toList());
target.withMembers(memberDtos); embeddedBuilder.with("members", memberDtos);
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), group);
return new GroupDto(linksBuilder.build(), embeddedBuilder.build());
} }
private MemberDto createMember(String name) { private MemberDto createMember(String name) {

View File

@@ -1,5 +1,6 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import lombok.Getter; import lombok.Getter;
@@ -9,8 +10,8 @@ public class IndexDto extends HalRepresentation {
private final String version; private final String version;
IndexDto(String version, Links links) { IndexDto(Links links, Embedded embedded, String version) {
super(links); super(links, embedded);
this.version = version; this.version = version;
} }
} }

View File

@@ -1,6 +1,7 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.Link; import de.otto.edison.hal.Link;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.SecurityUtils;
@@ -13,6 +14,7 @@ import sonia.scm.user.UserPermissions;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.List; import java.util.List;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Link.link;
public class IndexDtoGenerator extends HalAppenderMapper { public class IndexDtoGenerator extends HalAppenderMapper {
@@ -61,8 +63,9 @@ public class IndexDtoGenerator extends HalAppenderMapper {
builder.single(link("login", resourceLinks.authentication().jsonLogin())); builder.single(link("login", resourceLinks.authentication().jsonLogin()));
} }
appendLinks(new EdisonHalAppender(builder), new Index()); Embedded.Builder embeddedBuilder = embeddedBuilder();
applyEnrichers(new EdisonHalAppender(builder, embeddedBuilder), new Index());
return new IndexDto(scmContextProvider.getVersion(), builder.build()); return new IndexDto(builder.build(), embeddedBuilder.build(), scmContextProvider.getVersion());
} }
} }

View File

@@ -1,5 +1,6 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import lombok.Getter; import lombok.Getter;
@@ -18,9 +19,7 @@ public class MeDto extends HalRepresentation {
private String mail; private String mail;
private List<String> groups; private List<String> groups;
@Override MeDto(Links links, Embedded embedded) {
@SuppressWarnings("squid:S1185") // We want to have this method available in this package super(links, embedded);
protected HalRepresentation add(Links links) {
return super.add(links);
} }
} }

View File

@@ -1,6 +1,7 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.PrincipalCollection;
@@ -13,6 +14,7 @@ import sonia.scm.user.UserPermissions;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.Collections; import java.util.Collections;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo; import static de.otto.edison.hal.Links.linkingTo;
@@ -29,15 +31,11 @@ public class MeDtoFactory extends HalAppenderMapper {
public MeDto create() { public MeDto create() {
PrincipalCollection principals = getPrincipalCollection(); PrincipalCollection principals = getPrincipalCollection();
MeDto dto = new MeDto();
User user = principals.oneByType(User.class); User user = principals.oneByType(User.class);
MeDto dto = createDto(user);
mapUserProperties(user, dto); mapUserProperties(user, dto);
mapGroups(principals, dto); mapGroups(principals, dto);
appendLinks(user, dto);
return dto; return dto;
} }
@@ -61,21 +59,22 @@ public class MeDtoFactory extends HalAppenderMapper {
} }
private void appendLinks(User user, MeDto target) { private MeDto createDto(User user) {
Links.Builder linksBuilder = linkingTo().self(resourceLinks.me().self()); Links.Builder linksBuilder = linkingTo().self(resourceLinks.me().self());
if (UserPermissions.delete(user).isPermitted()) { if (UserPermissions.delete(user).isPermitted()) {
linksBuilder.single(link("delete", resourceLinks.me().delete(target.getName()))); linksBuilder.single(link("delete", resourceLinks.me().delete(user.getName())));
} }
if (UserPermissions.modify(user).isPermitted()) { if (UserPermissions.modify(user).isPermitted()) {
linksBuilder.single(link("update", resourceLinks.me().update(target.getName()))); linksBuilder.single(link("update", resourceLinks.me().update(user.getName())));
} }
if (userManager.isTypeDefault(user) && UserPermissions.changePassword(user).isPermitted()) { if (userManager.isTypeDefault(user) && UserPermissions.changePassword(user).isPermitted()) {
linksBuilder.single(link("password", resourceLinks.me().passwordChange())); linksBuilder.single(link("password", resourceLinks.me().passwordChange()));
} }
appendLinks(new EdisonHalAppender(linksBuilder), new Me(), user); Embedded.Builder embeddedBuilder = embeddedBuilder();
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), new Me(), user);
target.add(linksBuilder.build()); return new MeDto(linksBuilder.build(), embeddedBuilder.build());
} }
} }

View File

@@ -1,9 +1,11 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty; import org.hibernate.validator.constraints.NotEmpty;
@@ -13,7 +15,7 @@ import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@Getter @Setter @Getter @Setter @NoArgsConstructor
public class RepositoryDto extends HalRepresentation { public class RepositoryDto extends HalRepresentation {
@Email @Email
@@ -31,9 +33,7 @@ public class RepositoryDto extends HalRepresentation {
private String type; private String type;
protected Map<String, String> properties; protected Map<String, String> properties;
@Override RepositoryDto(Links links, Embedded embedded) {
@SuppressWarnings("squid:S1185") // We want to have this method available in this package super(links, embedded);
protected HalRepresentation add(Links links) {
return super.add(links);
} }
} }

View File

@@ -1,11 +1,11 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import com.google.inject.Inject; import com.google.inject.Inject;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.Link; import de.otto.edison.hal.Link;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget; import org.mapstruct.ObjectFactory;
import sonia.scm.repository.Feature; import sonia.scm.repository.Feature;
import sonia.scm.repository.HealthCheckFailure; import sonia.scm.repository.HealthCheckFailure;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
@@ -17,6 +17,7 @@ import sonia.scm.repository.api.ScmProtocol;
import java.util.List; import java.util.List;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo; import static de.otto.edison.hal.Links.linkingTo;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
@@ -33,17 +34,17 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
abstract HealthCheckFailureDto toDto(HealthCheckFailure failure); abstract HealthCheckFailureDto toDto(HealthCheckFailure failure);
@AfterMapping @ObjectFactory
void appendLinks(Repository repository, @MappingTarget RepositoryDto target) { RepositoryDto createDto(Repository repository) {
Links.Builder linksBuilder = linkingTo().self(resourceLinks.repository().self(target.getNamespace(), target.getName())); Links.Builder linksBuilder = linkingTo().self(resourceLinks.repository().self(repository.getNamespace(), repository.getName()));
if (RepositoryPermissions.delete(repository).isPermitted()) { if (RepositoryPermissions.delete(repository).isPermitted()) {
linksBuilder.single(link("delete", resourceLinks.repository().delete(target.getNamespace(), target.getName()))); linksBuilder.single(link("delete", resourceLinks.repository().delete(repository.getNamespace(), repository.getName())));
} }
if (RepositoryPermissions.modify(repository).isPermitted()) { if (RepositoryPermissions.modify(repository).isPermitted()) {
linksBuilder.single(link("update", resourceLinks.repository().update(target.getNamespace(), target.getName()))); linksBuilder.single(link("update", resourceLinks.repository().update(repository.getNamespace(), repository.getName())));
} }
if (RepositoryPermissions.permissionRead(repository).isPermitted()) { if (RepositoryPermissions.permissionRead(repository).isPermitted()) {
linksBuilder.single(link("permissions", resourceLinks.repositoryPermission().all(target.getNamespace(), target.getName()))); linksBuilder.single(link("permissions", resourceLinks.repositoryPermission().all(repository.getNamespace(), repository.getName())));
} }
try (RepositoryService repositoryService = serviceFactory.create(repository)) { try (RepositoryService repositoryService = serviceFactory.create(repository)) {
if (RepositoryPermissions.pull(repository).isPermitted()) { if (RepositoryPermissions.pull(repository).isPermitted()) {
@@ -53,26 +54,27 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
linksBuilder.array(protocolLinks); linksBuilder.array(protocolLinks);
} }
if (repositoryService.isSupported(Command.TAGS)) { if (repositoryService.isSupported(Command.TAGS)) {
linksBuilder.single(link("tags", resourceLinks.tag().all(target.getNamespace(), target.getName()))); linksBuilder.single(link("tags", resourceLinks.tag().all(repository.getNamespace(), repository.getName())));
} }
if (repositoryService.isSupported(Command.BRANCHES)) { if (repositoryService.isSupported(Command.BRANCHES)) {
linksBuilder.single(link("branches", resourceLinks.branchCollection().self(target.getNamespace(), target.getName()))); linksBuilder.single(link("branches", resourceLinks.branchCollection().self(repository.getNamespace(), repository.getName())));
} }
if (repositoryService.isSupported(Feature.INCOMING_REVISION)) { if (repositoryService.isSupported(Feature.INCOMING_REVISION)) {
linksBuilder.single(link("incomingChangesets", resourceLinks.incoming().changesets(target.getNamespace(), target.getName()))); linksBuilder.single(link("incomingChangesets", resourceLinks.incoming().changesets(repository.getNamespace(), repository.getName())));
linksBuilder.single(link("incomingDiff", resourceLinks.incoming().diff(target.getNamespace(), target.getName()))); linksBuilder.single(link("incomingDiff", resourceLinks.incoming().diff(repository.getNamespace(), repository.getName())));
} }
if (repositoryService.isSupported(Command.MERGE)) { if (repositoryService.isSupported(Command.MERGE)) {
linksBuilder.single(link("merge", resourceLinks.merge().merge(target.getNamespace(), target.getName()))); linksBuilder.single(link("merge", resourceLinks.merge().merge(repository.getNamespace(), repository.getName())));
linksBuilder.single(link("mergeDryRun", resourceLinks.merge().dryRun(target.getNamespace(), target.getName()))); linksBuilder.single(link("mergeDryRun", resourceLinks.merge().dryRun(repository.getNamespace(), repository.getName())));
} }
} }
linksBuilder.single(link("changesets", resourceLinks.changeset().all(target.getNamespace(), target.getName()))); linksBuilder.single(link("changesets", resourceLinks.changeset().all(repository.getNamespace(), repository.getName())));
linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(target.getNamespace(), target.getName()))); linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(repository.getNamespace(), repository.getName())));
appendLinks(new EdisonHalAppender(linksBuilder), repository); Embedded.Builder embeddedBuilder = embeddedBuilder();
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), repository);
target.add(linksBuilder.build()); return new RepositoryDto(linksBuilder.build(), embeddedBuilder.build());
} }
private Link createProtocolLink(ScmProtocol protocol) { private Link createProtocolLink(ScmProtocol protocol) {

View File

@@ -1,5 +1,6 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import lombok.Getter; import lombok.Getter;
@@ -15,10 +16,8 @@ public class TagDto extends HalRepresentation {
private String revision; private String revision;
@Override TagDto(Links links, Embedded embedded) {
@SuppressWarnings("squid:S1185") // We want to have this method available in this package super(links, embedded);
protected HalRepresentation add(Links links) {
return super.add(links);
} }
} }

View File

@@ -1,16 +1,17 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Context; import org.mapstruct.Context;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.ObjectFactory;
import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Tag; import sonia.scm.repository.Tag;
import javax.inject.Inject; import javax.inject.Inject;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo; import static de.otto.edison.hal.Links.linkingTo;
@@ -23,15 +24,16 @@ public abstract class TagToTagDtoMapper extends HalAppenderMapper {
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
public abstract TagDto map(Tag tag, @Context NamespaceAndName namespaceAndName); public abstract TagDto map(Tag tag, @Context NamespaceAndName namespaceAndName);
@AfterMapping @ObjectFactory
void appendLinks(Tag tag, @MappingTarget TagDto target, @Context NamespaceAndName namespaceAndName) { TagDto createDto(@Context NamespaceAndName namespaceAndName, Tag tag) {
Links.Builder linksBuilder = linkingTo() Links.Builder linksBuilder = linkingTo()
.self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getName())) .self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getName()))
.single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision()))) .single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getRevision())))
.single(link("changeset", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision()))); .single(link("changeset", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getRevision())));
appendLinks(new EdisonHalAppender(linksBuilder), tag, namespaceAndName); Embedded.Builder embeddedBuilder = embeddedBuilder();
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), tag, namespaceAndName);
target.add(linksBuilder.build()); return new TagDto(linksBuilder.build(), embeddedBuilder.build());
} }
} }

View File

@@ -1,6 +1,7 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import lombok.Getter; import lombok.Getter;
@@ -33,9 +34,7 @@ public class UserDto extends HalRepresentation {
private String type; private String type;
private Map<String, String> properties; private Map<String, String> properties;
@Override UserDto(Links links, Embedded embedded) {
@SuppressWarnings("squid:S1185") // We want to have this method available in this package super(links, embedded);
protected HalRepresentation add(Links links) {
return super.add(links);
} }
} }

View File

@@ -1,10 +1,10 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget; import org.mapstruct.ObjectFactory;
import sonia.scm.security.PermissionPermissions; import sonia.scm.security.PermissionPermissions;
import sonia.scm.user.User; import sonia.scm.user.User;
import sonia.scm.user.UserManager; import sonia.scm.user.UserManager;
@@ -12,6 +12,7 @@ import sonia.scm.user.UserPermissions;
import javax.inject.Inject; import javax.inject.Inject;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo; import static de.otto.edison.hal.Links.linkingTo;
@@ -31,25 +32,26 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
@Inject @Inject
private ResourceLinks resourceLinks; private ResourceLinks resourceLinks;
@AfterMapping @ObjectFactory
protected void appendLinks(User user, @MappingTarget UserDto target) { UserDto createDto(User user) {
Links.Builder linksBuilder = linkingTo().self(resourceLinks.user().self(target.getName())); Links.Builder linksBuilder = linkingTo().self(resourceLinks.user().self(user.getName()));
if (UserPermissions.delete(user).isPermitted()) { if (UserPermissions.delete(user).isPermitted()) {
linksBuilder.single(link("delete", resourceLinks.user().delete(target.getName()))); linksBuilder.single(link("delete", resourceLinks.user().delete(user.getName())));
} }
if (UserPermissions.modify(user).isPermitted()) { if (UserPermissions.modify(user).isPermitted()) {
linksBuilder.single(link("update", resourceLinks.user().update(target.getName()))); linksBuilder.single(link("update", resourceLinks.user().update(user.getName())));
if (userManager.isTypeDefault(user)) { if (userManager.isTypeDefault(user)) {
linksBuilder.single(link("password", resourceLinks.user().passwordChange(target.getName()))); linksBuilder.single(link("password", resourceLinks.user().passwordChange(user.getName())));
} }
} }
if (PermissionPermissions.read().isPermitted()) { if (PermissionPermissions.read().isPermitted()) {
linksBuilder.single(link("permissions", resourceLinks.userPermissions().permissions(target.getName()))); linksBuilder.single(link("permissions", resourceLinks.userPermissions().permissions(user.getName())));
} }
appendLinks(new EdisonHalAppender(linksBuilder), user); Embedded.Builder embeddedBuilder = embeddedBuilder();
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), user);
target.add(linksBuilder.build()); return new UserDto(linksBuilder.build(), embeddedBuilder.build());
} }
} }

View File

@@ -1,5 +1,7 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Link; import de.otto.edison.hal.Link;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@@ -7,25 +9,28 @@ import org.junit.jupiter.api.Test;
import java.util.List; import java.util.List;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Links.linkingTo; import static de.otto.edison.hal.Links.linkingTo;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class EdisonHalAppenderTest { class EdisonHalAppenderTest {
private Links.Builder builder; private Links.Builder linksBuilder;
private Embedded.Builder embeddedBuilder;
private EdisonHalAppender appender; private EdisonHalAppender appender;
@BeforeEach @BeforeEach
void prepare() { void prepare() {
builder = linkingTo(); linksBuilder = linkingTo();
appender = new EdisonHalAppender(builder); embeddedBuilder = embeddedBuilder();
appender = new EdisonHalAppender(linksBuilder, embeddedBuilder);
} }
@Test @Test
void shouldAppendOneLink() { void shouldAppendOneLink() {
appender.appendLink("self", "https://scm.hitchhiker.com"); appender.appendLink("self", "https://scm.hitchhiker.com");
Links links = builder.build(); Links links = linksBuilder.build();
assertThat(links.getLinkBy("self").get().getHref()).isEqualTo("https://scm.hitchhiker.com"); assertThat(links.getLinkBy("self").get().getHref()).isEqualTo("https://scm.hitchhiker.com");
} }
@@ -36,8 +41,21 @@ class EdisonHalAppenderTest {
.append("two", "http://two") .append("two", "http://two")
.build(); .build();
List<Link> items = builder.build().getLinksBy("items"); List<Link> items = linksBuilder.build().getLinksBy("items");
assertThat(items).hasSize(2); assertThat(items).hasSize(2);
} }
@Test
void shouldAppendEmbedded() {
HalRepresentation one = new HalRepresentation();
appender.appendEmbedded("one", one);
HalRepresentation two = new HalRepresentation();
appender.appendEmbedded("two", new HalRepresentation());
Embedded embedded = embeddedBuilder.build();
assertThat(embedded.getItemsBy("one")).containsOnly(one);
assertThat(embedded.getItemsBy("two")).containsOnly(two);
}
} }

View File

@@ -136,10 +136,13 @@ public class GitLfsITCase {
} }
private void createUser(User user) { private void createUser(User user) {
UserDto dto = new UserToUserDtoMapperImpl(){ UserDto dto = new UserDto();
@Override dto.setName(user.getName());
protected void appendLinks(User user, UserDto target) {} dto.setMail(user.getMail());
}.map(user); dto.setDisplayName(user.getDisplayName());
dto.setType(user.getType());
dto.setActive(user.isActive());
dto.setAdmin(user.isAdmin());
dto.setPassword(user.getPassword()); dto.setPassword(user.getPassword());
createResource(adminClient, "users") createResource(adminClient, "users")
.accept("*/*") .accept("*/*")