implement new link enricher api for various resource objects.

Repository, Tag, Branch, Changeset, FileObject, Group, User, Me and Index
This commit is contained in:
Sebastian Sdorra
2019-01-03 10:20:39 +01:00
parent c69c8028c9
commit 471852d360
17 changed files with 207 additions and 10 deletions

View File

@@ -15,7 +15,7 @@ import static de.otto.edison.hal.Link.linkBuilder;
import static de.otto.edison.hal.Links.linkingTo; import static de.otto.edison.hal.Links.linkingTo;
@Mapper @Mapper
public abstract class BranchToBranchDtoMapper { public abstract class BranchToBranchDtoMapper extends LinkAppenderMapper {
@Inject @Inject
private ResourceLinks resourceLinks; private ResourceLinks resourceLinks;
@@ -24,12 +24,15 @@ public abstract class BranchToBranchDtoMapper {
public abstract BranchDto map(Branch branch, @Context NamespaceAndName namespaceAndName); public abstract BranchDto map(Branch branch, @Context NamespaceAndName namespaceAndName);
@AfterMapping @AfterMapping
void appendLinks(@MappingTarget BranchDto target, @Context NamespaceAndName namespaceAndName) { void appendLinks(Branch source, @MappingTarget BranchDto target, @Context NamespaceAndName namespaceAndName) {
Links.Builder linksBuilder = linkingTo() Links.Builder linksBuilder = linkingTo()
.self(resourceLinks.branch().self(namespaceAndName, target.getName())) .self(resourceLinks.branch().self(namespaceAndName, target.getName()))
.single(linkBuilder("history", resourceLinks.branch().history(namespaceAndName, target.getName())).build()) .single(linkBuilder("history", resourceLinks.branch().history(namespaceAndName, target.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(), target.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(), target.getRevision())).build());
appendLinks(new EdisonLinkAppender(linksBuilder), source, namespaceAndName);
target.add(linksBuilder.build()); target.add(linksBuilder.build());
} }
} }

View File

@@ -23,7 +23,7 @@ import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo; import static de.otto.edison.hal.Links.linkingTo;
@Mapper @Mapper
public abstract class ChangesetToChangesetDtoMapper implements InstantAttributeMapper { public abstract class ChangesetToChangesetDtoMapper extends LinkAppenderMapper implements InstantAttributeMapper {
@Inject @Inject
private RepositoryServiceFactory serviceFactory; private RepositoryServiceFactory serviceFactory;
@@ -67,6 +67,9 @@ public abstract class ChangesetToChangesetDtoMapper implements InstantAttributeM
.self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), target.getId())) .self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), target.getId()))
.single(link("diff", resourceLinks.diff().self(namespace, name, target.getId()))) .single(link("diff", resourceLinks.diff().self(namespace, name, target.getId())))
.single(link("modifications", resourceLinks.modifications().self(namespace, name, target.getId()))); .single(link("modifications", resourceLinks.modifications().self(namespace, name, target.getId())));
appendLinks(new EdisonLinkAppender(linksBuilder), source, repository);
target.add(linksBuilder.build()); target.add(linksBuilder.build());
} }

View File

@@ -0,0 +1,18 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Link;
import de.otto.edison.hal.Links;
class EdisonLinkAppender implements LinkAppender {
private final Links.Builder builder;
EdisonLinkAppender(Links.Builder builder) {
this.builder = builder;
}
@Override
public void appendOne(String rel, String href) {
builder.single(Link.link(rel, href));
}
}

View File

@@ -18,7 +18,7 @@ import java.util.stream.Collectors;
import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Link.link;
@Mapper @Mapper
public abstract class FileObjectToFileObjectDtoMapper implements InstantAttributeMapper { public abstract class FileObjectToFileObjectDtoMapper extends LinkAppenderMapper implements InstantAttributeMapper {
@Inject @Inject
private ResourceLinks resourceLinks; private ResourceLinks resourceLinks;
@@ -39,6 +39,8 @@ public abstract class FileObjectToFileObjectDtoMapper implements InstantAttribut
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 EdisonLinkAppender(links), fileObject, namespaceAndName, revision);
dto.add(links.build()); dto.add(links.build());
} }

View File

@@ -31,6 +31,9 @@ public abstract class GroupToGroupDtoMapper extends BaseMapper<Group, GroupDto>
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(target.getName())));
} }
appendLinks(new EdisonLinkAppender(linksBuilder), group);
target.add(linksBuilder.build()); target.add(linksBuilder.build());
} }

View File

@@ -14,7 +14,7 @@ import java.util.List;
import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Link.link;
public class IndexDtoGenerator { public class IndexDtoGenerator extends LinkAppenderMapper {
private final ResourceLinks resourceLinks; private final ResourceLinks resourceLinks;
private final SCMContextProvider scmContextProvider; private final SCMContextProvider scmContextProvider;
@@ -56,6 +56,8 @@ public class IndexDtoGenerator {
builder.single(link("login", resourceLinks.authentication().jsonLogin())); builder.single(link("login", resourceLinks.authentication().jsonLogin()));
} }
appendLinks(new EdisonLinkAppender(builder), new Index());
return new IndexDto(scmContextProvider.getVersion(), builder.build()); return new IndexDto(scmContextProvider.getVersion(), builder.build());
} }
} }

View File

@@ -14,7 +14,7 @@ import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo; import static de.otto.edison.hal.Links.linkingTo;
@Mapper @Mapper
public abstract class MeToUserDtoMapper extends UserToUserDtoMapper{ public abstract class MeToUserDtoMapper extends UserToUserDtoMapper {
@Inject @Inject
private UserManager userManager; private UserManager userManager;
@@ -36,6 +36,9 @@ public abstract class MeToUserDtoMapper extends UserToUserDtoMapper{
if (userManager.isTypeDefault(user)) { if (userManager.isTypeDefault(user)) {
linksBuilder.single(link("password", resourceLinks.me().passwordChange())); linksBuilder.single(link("password", resourceLinks.me().passwordChange()));
} }
appendLinks(new EdisonLinkAppender(linksBuilder), new Me(), user);
target.add(linksBuilder.build()); target.add(linksBuilder.build());
} }

View File

@@ -67,6 +67,9 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
} }
linksBuilder.single(link("changesets", resourceLinks.changeset().all(target.getNamespace(), target.getName()))); linksBuilder.single(link("changesets", resourceLinks.changeset().all(target.getNamespace(), target.getName())));
linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(target.getNamespace(), target.getName()))); linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(target.getNamespace(), target.getName())));
appendLinks(new EdisonLinkAppender(linksBuilder), repository);
target.add(linksBuilder.build()); target.add(linksBuilder.build());
} }

View File

@@ -15,7 +15,7 @@ import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo; import static de.otto.edison.hal.Links.linkingTo;
@Mapper @Mapper
public abstract class TagToTagDtoMapper { public abstract class TagToTagDtoMapper extends LinkAppenderMapper {
@Inject @Inject
private ResourceLinks resourceLinks; private ResourceLinks resourceLinks;
@@ -24,11 +24,14 @@ public abstract class TagToTagDtoMapper {
public abstract TagDto map(Tag tag, @Context NamespaceAndName namespaceAndName); public abstract TagDto map(Tag tag, @Context NamespaceAndName namespaceAndName);
@AfterMapping @AfterMapping
void appendLinks(@MappingTarget TagDto target, @Context NamespaceAndName namespaceAndName) { void appendLinks(Tag tag, @MappingTarget TagDto target, @Context NamespaceAndName namespaceAndName) {
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(), target.getName()))
.single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision()))) .single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())))
.single(link("changeset", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision()))); .single(link("changeset", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())));
appendLinks(new EdisonLinkAppender(linksBuilder), tag, namespaceAndName);
target.add(linksBuilder.build()); target.add(linksBuilder.build());
} }
} }

View File

@@ -1,6 +1,5 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import com.google.common.annotations.VisibleForTesting;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping; import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
@@ -43,6 +42,9 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
linksBuilder.single(link("password", resourceLinks.user().passwordChange(target.getName()))); linksBuilder.single(link("password", resourceLinks.user().passwordChange(target.getName())));
} }
} }
appendLinks(new EdisonLinkAppender(linksBuilder), user);
target.add(linksBuilder.build()); target.add(linksBuilder.build());
} }

View File

@@ -0,0 +1,42 @@
package sonia.scm.api.v2.resources;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.Branch;
import sonia.scm.repository.NamespaceAndName;
import java.net.URI;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(MockitoExtension.class)
class BranchToBranchDtoMapperTest {
private final URI baseUri = URI.create("https://hitchhiker.com");
@SuppressWarnings("unused") // Is injected
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@InjectMocks
private BranchToBranchDtoMapperImpl mapper;
@Test
void shouldAppendLinks() {
LinkEnricherRegistry registry = new LinkEnricherRegistry();
registry.register(Branch.class, (ctx, appender) -> {
NamespaceAndName namespaceAndName = ctx.oneRequireByType(NamespaceAndName.class);
Branch branch = ctx.oneRequireByType(Branch.class);
appender.appendOne("ka", "http://" + namespaceAndName.logString() + "/" + branch.getName());
});
mapper.setRegistry(registry);
Branch branch = new Branch("master", "42");
BranchDto dto = mapper.map(branch, new NamespaceAndName("hitchhiker", "heart-of-gold"));
assertThat(dto.getLinks().getLinkBy("ka").get().getHref()).isEqualTo("http://hitchhiker/heart-of-gold/master");
}
}

View File

@@ -71,6 +71,24 @@ public class FileObjectToFileObjectDtoMapperTest {
assertThat(dto.getLinks().getLinkBy("self").get().getHref()).isEqualTo(expectedBaseUri.resolve("namespace/name/content/revision/foo/bar").toString()); assertThat(dto.getLinks().getLinkBy("self").get().getHref()).isEqualTo(expectedBaseUri.resolve("namespace/name/content/revision/foo/bar").toString());
} }
@Test
public void shouldAppendLinks() {
LinkEnricherRegistry registry = new LinkEnricherRegistry();
registry.register(FileObject.class, (ctx, appender) -> {
NamespaceAndName repository = ctx.oneRequireByType(NamespaceAndName.class);
FileObject fo = ctx.oneRequireByType(FileObject.class);
String rev = ctx.oneRequireByType(String.class);
appender.appendOne("hog", "http://" + repository.logString() + "/" + fo.getName() + "/" + rev);
});
mapper.setRegistry(registry);
FileObject fileObject = createFileObject();
FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("hitchhiker", "hog"), "42");
assertThat(dto.getLinks().getLinkBy("hog").get().getHref()).isEqualTo("http://hitchhiker/hog/foo/42");
}
private FileObject createDirectoryObject() { private FileObject createDirectoryObject() {
FileObject fileObject = createFileObject(); FileObject fileObject = createFileObject();
fileObject.setDirectory(true); fileObject.setDirectory(true);

View File

@@ -35,7 +35,7 @@ public class GroupToGroupDtoMapperTest {
private URI expectedBaseUri; private URI expectedBaseUri;
@Before @Before
public void init() throws URISyntaxException { public void init() {
initMocks(this); initMocks(this);
expectedBaseUri = baseUri.resolve(GroupRootResource.GROUPS_PATH_V2 + "/"); expectedBaseUri = baseUri.resolve(GroupRootResource.GROUPS_PATH_V2 + "/");
subjectThreadState.bind(); subjectThreadState.bind();
@@ -89,6 +89,21 @@ public class GroupToGroupDtoMapperTest {
assertEquals("http://example.com/base/v2/users/user0", actualMember.getLinks().getLinkBy("self").get().getHref()); assertEquals("http://example.com/base/v2/users/user0", actualMember.getLinks().getLinkBy("self").get().getHref());
} }
@Test
public void shouldAppendLinks() {
LinkEnricherRegistry registry = new LinkEnricherRegistry();
registry.register(Group.class, (ctx, appender) -> {
Group group = ctx.oneRequireByType(Group.class);
appender.appendOne("some", "http://" + group.getName());
});
mapper.setRegistry(registry);
Group group = createDefaultGroup();
GroupDto dto = mapper.map(group);
assertEquals("http://abc", dto.getLinks().getLinkBy("some").get().getHref());
}
private Group createDefaultGroup() { private Group createDefaultGroup() {
Group group = new Group(); Group group = new Group();
group.setName("abc"); group.setName("abc");

View File

@@ -11,6 +11,7 @@ import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import sonia.scm.user.User; import sonia.scm.user.User;
import sonia.scm.user.UserManager; import sonia.scm.user.UserManager;
import sonia.scm.user.UserTestData;
import java.net.URI; import java.net.URI;
@@ -124,6 +125,21 @@ public class MeToUserDtoMapperTest {
assertThat(userDto.getPassword()).as("hide password for the me resource").isBlank(); assertThat(userDto.getPassword()).as("hide password for the me resource").isBlank();
} }
@Test
public void shouldAppendLinks() {
LinkEnricherRegistry registry = new LinkEnricherRegistry();
registry.register(Me.class, (ctx, appender) -> {
User user = ctx.oneRequireByType(User.class);
appender.appendOne("profile", "http://hitchhiker.com/users/" + user.getName());
});
mapper.setRegistry(registry);
User trillian = UserTestData.createTrillian();
UserDto dto = mapper.map(trillian);
assertEquals("http://hitchhiker.com/users/trillian", dto.getLinks().getLinkBy("profile").get().getHref());
}
private User createDefaultUser() { private User createDefaultUser() {
User user = new User(); User user = new User();
user.setName("abc"); user.setName("abc");

View File

@@ -211,6 +211,19 @@ public class RepositoryToRepositoryDtoMapperTest {
assertTrue(dto.getLinks().getLinksBy("protocol").isEmpty()); assertTrue(dto.getLinks().getLinksBy("protocol").isEmpty());
} }
@Test
public void shouldAppendLinks() {
LinkEnricherRegistry registry = new LinkEnricherRegistry();
registry.register(Repository.class, (ctx, appender) -> {
Repository repository = ctx.oneRequireByType(Repository.class);
appender.appendOne("id", "http://" + repository.getId());
});
mapper.setRegistry(registry);
RepositoryDto dto = mapper.map(createTestRepository());
assertEquals("http://1", dto.getLinks().getLinkBy("id").get().getHref());
}
private ScmProtocol mockProtocol(String type, String protocol) { private ScmProtocol mockProtocol(String type, String protocol) {
return new MockScmProtocol(type, protocol); return new MockScmProtocol(type, protocol);
} }

View File

@@ -0,0 +1,37 @@
package sonia.scm.api.v2.resources;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Tag;
import java.net.URI;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(MockitoExtension.class)
class TagToTagDtoMapperTest {
@SuppressWarnings("unused") // Is injected
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("https://hitchhiker.com"));
@InjectMocks
private TagToTagDtoMapperImpl mapper;
@Test
void shouldAppendLinks() {
LinkEnricherRegistry registry = new LinkEnricherRegistry();
registry.register(Tag.class, (ctx, appender) -> {
NamespaceAndName repository = ctx.oneRequireByType(NamespaceAndName.class);
Tag tag = ctx.oneRequireByType(Tag.class);
appender.appendOne("yo", "http://" + repository.logString() + "/" + tag.getName());
});
mapper.setRegistry(registry);
TagDto dto = mapper.map(new Tag("1.0.0", "42"), new NamespaceAndName("hitchhiker", "hog"));
assertThat(dto.getLinks().getLinkBy("yo").get().getHref()).isEqualTo("http://hitchhiker/hog/1.0.0");
}
}

View File

@@ -11,6 +11,7 @@ import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import sonia.scm.user.User; import sonia.scm.user.User;
import sonia.scm.user.UserManager; import sonia.scm.user.UserManager;
import sonia.scm.user.UserTestData;
import java.net.URI; import java.net.URI;
import java.time.Instant; import java.time.Instant;
@@ -149,4 +150,17 @@ public class UserToUserDtoMapperTest {
assertEquals(expectedCreationDate, userDto.getCreationDate()); assertEquals(expectedCreationDate, userDto.getCreationDate());
assertEquals(expectedModificationDate, userDto.getLastModified()); assertEquals(expectedModificationDate, userDto.getLastModified());
} }
@Test
public void shouldAppendLink() {
User trillian = UserTestData.createTrillian();
LinkEnricherRegistry registry = new LinkEnricherRegistry();
registry.register(User.class, (ctx, appender) -> appender.appendOne("sample", "http://" + ctx.oneByType(User.class).get().getName()));
mapper.setRegistry(registry);
UserDto userDto = mapper.map(trillian);
assertEquals("http://trillian", userDto.getLinks().getLinkBy("sample").get().getHref());
}
} }