Simplify link building

This commit is contained in:
René Pfeuffer
2018-05-31 08:56:05 +02:00
parent 252c309a05
commit 25e25a3a94
5 changed files with 129 additions and 142 deletions

View File

@@ -0,0 +1,90 @@
package sonia.scm.api.rest.resources;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
class LinkBuilder {
private final UriInfo uriInfo;
private final Class[] classes;
private final List<Call> calls;
LinkBuilder(UriInfo uriInfo, Class... classes) {
this(uriInfo, classes, Collections.emptyList());
}
private LinkBuilder(UriInfo uriInfo, Class[] classes, List<Call> calls) {
this.uriInfo = uriInfo;
this.classes = classes;
this.calls = calls;
}
public Parameters method(String method) {
if (calls.size() >= classes.length) {
throw new IllegalStateException("no more classes for methods");
}
return new Parameters(method);
}
class Parameters {
private final String method;
private Parameters(String method) {
this.method = method;
}
public LinkBuilder parameters(String... parameters) {
return LinkBuilder.this.add(method, parameters);
}
}
private LinkBuilder add(String method, String[] parameters) {
List<Call> newCalls = new ArrayList<>(this.calls);
newCalls.add(new Call(LinkBuilder.this.classes[calls.size()], method, parameters));
return new LinkBuilder(uriInfo, classes, newCalls);
}
public Link create() {
if (calls.size() < classes.length) {
throw new IllegalStateException("not enough methods for all classes");
}
URI baseUri = uriInfo.getBaseUri();
URI relativeUri = createRelativeUri();
URI absoluteUri = baseUri.resolve(relativeUri);
return new Link(absoluteUri);
}
private URI createRelativeUri() {
UriBuilder uriBuilder = userUriBuilder();
calls.forEach(call -> uriBuilder.path(call.clazz, call.method));
String[] concatenatedParameters = calls
.stream()
.map(call -> call.parameters)
.flatMap(Arrays::stream)
.toArray(String[]::new);
return uriBuilder.build(concatenatedParameters);
}
private UriBuilder userUriBuilder() {
return UriBuilder.fromResource(classes[0]);
}
private static class Call {
private final Class clazz;
private final String method;
private final String[] parameters;
private Call(Class clazz, String method, String[] parameters) {
this.clazz = clazz;
this.method = method;
this.parameters = parameters;
}
}
}

View File

@@ -1,108 +0,0 @@
package sonia.scm.api.rest.resources;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.*;
class LinkMapBuilder {
private final UriInfo uriInfo;
private final Class[] classes;
private final Map<String, Link> links = new LinkedHashMap<>();
LinkMapBuilder(UriInfo uriInfo, Class... classes) {
this.uriInfo = uriInfo;
this.classes = classes;
}
Builder add(String linkName) {
return new ConcreteBuilder(linkName);
}
interface Builder {
Parameters method(String method);
}
interface Parameters {
Builder parameters(String... parameters);
}
private class ConcreteBuilder implements Builder {
private final String linkName;
private final List<Call> calls = new LinkedList<>();
private int callCount = 0;
ConcreteBuilder(String linkName) {
this.linkName = linkName;
}
public Parameters method(String method) {
return new ParametersImpl(method);
}
private class ParametersImpl implements Parameters {
private final String method;
ParametersImpl(String method) {
this.method = method;
}
public Builder parameters(String... parameters) {
return ConcreteBuilder.this.add(method, parameters);
}
}
private Builder add(String method, String[] parameters) {
this.calls.add(new Call(LinkMapBuilder.this.classes[callCount], method, parameters));
++callCount;
if (callCount >= classes.length) {
links.put(linkName, createLink());
return x -> {
throw new IllegalStateException("no more classes for methods");
};
}
return this;
}
private Link createLink() {
URI baseUri = uriInfo.getBaseUri();
URI relativeUri = createRelativeUri();
URI absoluteUri = baseUri.resolve(relativeUri);
return new Link(absoluteUri);
}
private URI createRelativeUri() {
UriBuilder uriBuilder = userUriBuilder();
calls.forEach(call -> uriBuilder.path(call.clazz, call.method));
String[] concatenatedParameters = calls
.stream()
.map(call -> call.parameters)
.flatMap(Arrays::stream)
.toArray(String[]::new);
return uriBuilder.build(concatenatedParameters);
}
private UriBuilder userUriBuilder() {
return UriBuilder.fromResource(classes[0]);
}
}
Map<String, Link> getLinkMap() {
return Collections.unmodifiableMap(links);
}
private static class Call {
private final Class clazz;
private final String method;
private final String[] parameters;
private Call(Class clazz, String method, String[] parameters) {
this.clazz = clazz;
this.method = method;
this.parameters = parameters;
}
}
}

View File

@@ -24,17 +24,15 @@ public abstract class User2UserDtoMapper {
@AfterMapping
void appendLinks(@MappingTarget UserDto target, @Context UriInfo uriInfo) {
LinkMapBuilder userLinkBuilder = new LinkMapBuilder(uriInfo, UserNewResource.class, UserSubResource.class);
LinkMapBuilder collectionLinkBuilder = new LinkMapBuilder(uriInfo, UserNewResource.class, UserCollectionResource.class);
userLinkBuilder.add("self").method("getUserSubResource").parameters(target.getName()).method("get").parameters();
LinkBuilder userLinkBuilder = new LinkBuilder(uriInfo, UserNewResource.class, UserSubResource.class);
LinkBuilder collectionLinkBuilder = new LinkBuilder(uriInfo, UserNewResource.class, UserCollectionResource.class);
Map<String, Link> links = new HashMap<>();
links.put("self", userLinkBuilder.method("getUserSubResource").parameters(target.getName()).method("get").parameters().create());
if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) {
userLinkBuilder.add("delete").method("getUserSubResource").parameters(target.getName()).method("delete").parameters();
userLinkBuilder.add("update").method("getUserSubResource").parameters(target.getName()).method("update").parameters();
collectionLinkBuilder.add("create").method("getUserCollectionResource").parameters().method("create").parameters();
links.put("delete", userLinkBuilder.method("getUserSubResource").parameters(target.getName()).method("delete").parameters().create());
links.put("update", userLinkBuilder.method("getUserSubResource").parameters(target.getName()).method("update").parameters().create());
links.put("create", collectionLinkBuilder.method("getUserCollectionResource").parameters().method("create").parameters().create());
}
Map<String, Link> join = new HashMap<>();
join.putAll(userLinkBuilder.getLinkMap());
join.putAll(collectionLinkBuilder.getLinkMap());
target.setLinks(join);
target.setLinks(links);
}
}

View File

@@ -73,9 +73,8 @@ public class UserCollectionResource extends AbstractManagerResource<User, UserEx
User user = dtoToUserMapper.userDtoToUser(userDto, "");
manager.create(user);
LinkMapBuilder builder = new LinkMapBuilder(uriInfo, UserNewResource.class, UserSubResource.class);
builder.add("self").method("getUserSubResource").parameters(user.getName()).method("get").parameters();
return Response.created(builder.getLinkMap().get("self").getHref()).build();
LinkBuilder builder = new LinkBuilder(uriInfo, UserNewResource.class, UserSubResource.class);
return Response.created(builder.method("getUserSubResource").parameters(user.getName()).method("get").parameters().create().getHref()).build();
}
@Override

View File

@@ -12,7 +12,7 @@ import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class LinkMapBuilderTest {
public class LinkBuilderTest {
@Path("base")
public static class Main {
@@ -41,52 +41,60 @@ public class LinkMapBuilderTest {
@Test
public void shouldBuildSimplePath() {
LinkMapBuilder builder = new LinkMapBuilder(uriInfo, Main.class);
builder
.add("link")
.method("sub")
.parameters("param_x");
LinkBuilder builder = new LinkBuilder(uriInfo, Main.class);
URI actual = builder.getLinkMap().get("link").getHref();
URI actual = builder
.method("sub")
.parameters("param_x")
.create()
.getHref();
assertEquals("http://example.com/base/main/param_x", actual.toString());
}
@Test
public void shouldBuildPathOverSubResources() {
LinkMapBuilder builder = new LinkMapBuilder(uriInfo, Main.class, Sub.class);
builder
.add("link")
LinkBuilder builder = new LinkBuilder(uriInfo, Main.class, Sub.class);
URI actual = builder
.method("sub")
.parameters("param_x")
.method("x")
.parameters("param_y", "param_z");
URI actual = builder.getLinkMap().get("link").getHref();
.parameters("param_y", "param_z")
.create()
.getHref();
assertEquals("http://example.com/base/main/param_x/sub/param_y/param_z", actual.toString());
}
@Test
public void shouldBuildPathWithoutParameters() {
LinkMapBuilder builder = new LinkMapBuilder(uriInfo, NoParam.class);
builder
.add("link")
.method("get")
.parameters();
LinkBuilder builder = new LinkBuilder(uriInfo, NoParam.class);
URI actual = builder.getLinkMap().get("link").getHref();
URI actual = builder
.method("get")
.parameters()
.create()
.getHref();
assertEquals("http://example.com/base", actual.toString());
}
@Test(expected = IllegalStateException.class)
public void shouldFailForTooManyMethods() {
LinkMapBuilder builder = new LinkMapBuilder(uriInfo, Main.class);
LinkBuilder builder = new LinkBuilder(uriInfo, Main.class);
builder
.add("link")
.method("sub")
.parameters("param_x")
.method("x");
}
@Test(expected = IllegalStateException.class)
public void shouldFailForTooFewMethods() {
LinkBuilder builder = new LinkBuilder(uriInfo, Main.class, Sub.class);
builder
.method("sub")
.parameters("param_x")
.create();
}
@Before
public void setBaseUri() throws URISyntaxException {
when(uriInfo.getBaseUri()).thenReturn(new URI("http://example.com/"));