diff --git a/pom.xml b/pom.xml index f85b7573..88d51e06 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,7 @@ 3.1.2.RELEASE 1.9.7 + 1.0 true @@ -88,7 +89,14 @@ ${jackson.version} true - + + + javax.ws.rs + jsr311-api + ${jaxrs.version} + true + + org.codehaus.jackson jackson-mapper-asl diff --git a/src/main/java/org/springframework/hateoas/LinkBuilderFactory.java b/src/main/java/org/springframework/hateoas/LinkBuilderFactory.java index 536f6ab4..9b273e83 100644 --- a/src/main/java/org/springframework/hateoas/LinkBuilderFactory.java +++ b/src/main/java/org/springframework/hateoas/LinkBuilderFactory.java @@ -20,7 +20,7 @@ package org.springframework.hateoas; * * @author Ricardo Gladwell */ -public interface LinkBuilderFactory { +public interface LinkBuilderFactory { /** * Creates a new {@link LinkBuilder} with a base of the mapping annotated to the given target clas (controller, @@ -29,7 +29,7 @@ public interface LinkBuilderFactory { * @param target must not be {@literal null}. * @return */ - LinkBuilder linkTo(Class target); + T linkTo(Class target); /** * Creates a new {@link LinkBuilder} with a base of the mapping annotated to the given target class (controller, @@ -40,5 +40,5 @@ public interface LinkBuilderFactory { * @param parameters must not be {@literal null}. * @return */ - LinkBuilder linkTo(Class target, Object... parameters); + T linkTo(Class target, Object... parameters); } diff --git a/src/main/java/org/springframework/hateoas/jaxrs/JaxRsLinkBuilder.java b/src/main/java/org/springframework/hateoas/jaxrs/JaxRsLinkBuilder.java new file mode 100644 index 00000000..9c0144ad --- /dev/null +++ b/src/main/java/org/springframework/hateoas/jaxrs/JaxRsLinkBuilder.java @@ -0,0 +1,90 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.jaxrs; + +import javax.ws.rs.Path; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.hateoas.LinkBuilder; +import org.springframework.hateoas.mvc.UriComponentsLinkBuilder; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.web.util.UriTemplate; + +/** + * {@link LinkBuilder} to derive URI mappings from a JAX-RS {@link Path} annotation. + * + * @author Oliver Gierke + */ +public class JaxRsLinkBuilder extends UriComponentsLinkBuilder { + + /** + * Creates a new {@link JaxRsLinkBuilder} from the given {@link UriComponentsBuilder}. + * + * @param builder must not be {@literal null}. + */ + private JaxRsLinkBuilder(UriComponentsBuilder builder) { + super(builder); + } + + /** + * Creates a {@link JaxRsLinkBuilder} instance to link to the {@link Path} mapping tied to the given class. + * + * @param service the class to discover the annotation on, must not be {@literal null}. + * @return + */ + public static JaxRsLinkBuilder linkTo(Class service) { + return linkTo(service, new Object[0]); + } + + /** + * Creates a new {@link JaxRsLinkBuilder} instance to link to the {@link Path} mapping tied to the given class binding + * the given parameters to the URI template. + * + * @param service the class to discover the annotation on, must not be {@literal null}. + * @param parameters additional parameters to bind to the URI template declared in the annotation, must not be + * {@literal null}. + * @return + */ + public static JaxRsLinkBuilder linkTo(Class service, Object... parameters) { + + Path annotation = AnnotationUtils.findAnnotation(service, Path.class); + String path = (String) AnnotationUtils.getValue(annotation); + + JaxRsLinkBuilder builder = new JaxRsLinkBuilder(ServletUriComponentsBuilder.fromCurrentServletMapping()); + + UriTemplate template = new UriTemplate(path); + return builder.slash(template.expand(parameters)); + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.UriComponentsLinkBuilder#getThis() + */ + @Override + protected JaxRsLinkBuilder getThis() { + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.UriComponentsLinkBuilder#createNewInstance(org.springframework.web.util.UriComponentsBuilder) + */ + @Override + protected JaxRsLinkBuilder createNewInstance(UriComponentsBuilder builder) { + return new JaxRsLinkBuilder(builder); + } +} diff --git a/src/main/java/org/springframework/hateoas/jaxrs/JaxRsLinkBuilderFactory.java b/src/main/java/org/springframework/hateoas/jaxrs/JaxRsLinkBuilderFactory.java new file mode 100644 index 00000000..7a7f70da --- /dev/null +++ b/src/main/java/org/springframework/hateoas/jaxrs/JaxRsLinkBuilderFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.jaxrs; + +import org.springframework.hateoas.LinkBuilder; +import org.springframework.hateoas.LinkBuilderFactory; + +/** + * Factory for {@link LinkBuilder} instances based on the path mapping annotated on the given JAX-RS service. + * + * @author Ricardo Gladwell + * @author Oliver Gierke + */ +public class JaxRsLinkBuilderFactory implements LinkBuilderFactory { + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkBuilderFactory#linkTo(java.lang.Class) + */ + public JaxRsLinkBuilder linkTo(Class service) { + return JaxRsLinkBuilder.linkTo(service); + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkBuilderFactory#linkTo(java.lang.Class, java.lang.Object[]) + */ + @Override + public JaxRsLinkBuilder linkTo(Class service, Object... parameters) { + return JaxRsLinkBuilder.linkTo(service, parameters); + } +} diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java index 4d8dd0af..a0cae63a 100755 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java @@ -15,17 +15,11 @@ */ package org.springframework.hateoas.mvc; -import java.net.URI; - import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.hateoas.Identifiable; import org.springframework.hateoas.Link; -import org.springframework.hateoas.LinkBuilder; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; -import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriTemplate; @@ -34,24 +28,21 @@ import org.springframework.web.util.UriTemplate; * * @author Oliver Gierke */ -public class ControllerLinkBuilder implements LinkBuilder { - - private final UriComponents uriComponents; +public class ControllerLinkBuilder extends UriComponentsLinkBuilder { /** - * Creates a new {@link ControllerLinkBuilder}. + * Creates a new {@link ControllerLinkBuilder} using the given {@link UriComponentsBuilder}. * - * @param uriComponents must not be {@literal null}. + * @param builder must not be {@literal null}. */ private ControllerLinkBuilder(UriComponentsBuilder builder) { - Assert.notNull(builder); - this.uriComponents = builder.build(); + super(builder); } /** * Creates a new {@link ControllerLinkBuilder} with a base of the mapping annotated to the given controller class. * - * @param controller must not be {@literal null}. + * @param controller the class to discover the annotation on, must not be {@literal null}. * @return */ public static ControllerLinkBuilder linkTo(Class controller) { @@ -62,8 +53,9 @@ public class ControllerLinkBuilder implements LinkBuilder { * Creates a new {@link ControllerLinkBuilder} with a base of the mapping annotated to the given controller class. The * additional parameters are used to fill up potentially available path variables in the class scop request mapping. * - * @param controller must not be {@literal null}. - * @param parameters + * @param controller the class to discover the annotation on, must not be {@literal null}. + * @param parameters additional parameters to bind to the URI template declared in the annotation, must not be + * {@literal null}. * @return */ public static ControllerLinkBuilder linkTo(Class controller, Object... parameters) { @@ -87,63 +79,21 @@ public class ControllerLinkBuilder implements LinkBuilder { return builder.slash(template.expand(parameters)); } - /* + /* * (non-Javadoc) - * @see org.springframework.hateoas.LinkBuilder#slash(java.lang.Object) - */ - public ControllerLinkBuilder slash(Object object) { - - if (object == null) { - return this; - } - - String[] segments = StringUtils.tokenizeToStringArray(object.toString(), "/"); - return new ControllerLinkBuilder(UriComponentsBuilder.fromUri(uriComponents.toUri()).pathSegment(segments)); - } - - /* - * (non-Javadoc) - * @see org.springframework.hateoas.LinkBuilder#slash(org.springframework.hateoas.Identifiable) - */ - public ControllerLinkBuilder slash(Identifiable identifyable) { - - if (identifyable == null) { - return this; - } - - return slash(identifyable.getId()); - } - - /* - * (non-Javadoc) - * @see org.springframework.hateoas.LinkBuilder#toUri() - */ - public URI toUri() { - return uriComponents.encode().toUri(); - } - - /* - * (non-Javadoc) - * @see org.springframework.hateoas.LinkBuilder#withRel(java.lang.String) - */ - public Link withRel(String rel) { - return new Link(this.toString(), rel); - } - - /* - * (non-Javadoc) - * @see org.springframework.hateoas.LinkBuilder#withSelfRel() - */ - public Link withSelfRel() { - return new Link(this.toString()); - } - - /* - * (non-Javadoc) - * @see java.lang.Object#toString() + * @see org.springframework.hateoas.UriComponentsLinkBuilder#getThis() */ @Override - public String toString() { - return toUri().normalize().toASCIIString(); + protected ControllerLinkBuilder getThis() { + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.UriComponentsLinkBuilder#createNewInstance(org.springframework.web.util.UriComponentsBuilder) + */ + @Override + protected ControllerLinkBuilder createNewInstance(UriComponentsBuilder builder) { + return new ControllerLinkBuilder(builder); } } diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java index 720862a3..44027230 100644 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java @@ -18,12 +18,13 @@ package org.springframework.hateoas.mvc; import org.springframework.hateoas.LinkBuilderFactory; /** - * Factory for {@link ControllerLinkBuilder} instances. + * Factory for {@link UriComponentsLinkBuilder} instances based on the request mapping annotated on the given + * controller. * * @author Ricardo Gladwell * @author Oliver Gierke */ -public class ControllerLinkBuilderFactory implements LinkBuilderFactory { +public class ControllerLinkBuilderFactory implements LinkBuilderFactory { /* * (non-Javadoc) diff --git a/src/main/java/org/springframework/hateoas/mvc/UriComponentsLinkBuilder.java b/src/main/java/org/springframework/hateoas/mvc/UriComponentsLinkBuilder.java new file mode 100644 index 00000000..51e0ac27 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/mvc/UriComponentsLinkBuilder.java @@ -0,0 +1,123 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.mvc; + +import java.net.URI; + +import org.springframework.hateoas.Identifiable; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.LinkBuilder; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * Base class to implement {@link LinkBuilder}s based on a Spring MVC {@link UriComponentsBuilder}. + * + * @author Ricardo Gladwell + * @author Oliver Gierke + */ +public abstract class UriComponentsLinkBuilder implements LinkBuilder { + + private final UriComponents uriComponents; + + /** + * Creates a new {@link UriComponentsLinkBuilder} using the given {@link UriComponentsBuilder}. + * + * @param builder must not be {@literal null}. + */ + public UriComponentsLinkBuilder(UriComponentsBuilder builder) { + + Assert.notNull(builder); + this.uriComponents = builder.build(); + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkBuilder#slash(java.lang.Object) + */ + public T slash(Object object) { + + if (object == null) { + return getThis(); + } + + String[] segments = StringUtils.tokenizeToStringArray(object.toString(), "/"); + return createNewInstance(UriComponentsBuilder.fromUri(uriComponents.toUri()).pathSegment(segments)); + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkBuilder#slash(org.springframework.hateoas.Identifiable) + */ + public LinkBuilder slash(Identifiable identifyable) { + + if (identifyable == null) { + return this; + } + + return slash(identifyable.getId()); + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkBuilder#toUri() + */ + public URI toUri() { + return uriComponents.encode().toUri(); + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkBuilder#withRel(java.lang.String) + */ + public Link withRel(String rel) { + return new Link(this.toString(), rel); + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.LinkBuilder#withSelfRel() + */ + public Link withSelfRel() { + return new Link(this.toString()); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return toUri().normalize().toASCIIString(); + } + + /** + * Returns the current concrete instance. + * + * @return + */ + protected abstract T getThis(); + + /** + * Creates a new instance of the sub-class. + * + * @param builder will never be {@literal null}. + * @return + */ + protected abstract T createNewInstance(UriComponentsBuilder builder); +} diff --git a/src/test/java/org/springframework/hateoas/TestUtils.java b/src/test/java/org/springframework/hateoas/TestUtils.java index 697358ad..ac1c8338 100644 --- a/src/test/java/org/springframework/hateoas/TestUtils.java +++ b/src/test/java/org/springframework/hateoas/TestUtils.java @@ -26,6 +26,7 @@ import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; /** + * Utility class to ease tesing. * * @author Oliver Gierke */ diff --git a/src/test/java/org/springframework/hateoas/jaxrs/JaxRsLinkBuilderFactoryUnitTest.java b/src/test/java/org/springframework/hateoas/jaxrs/JaxRsLinkBuilderFactoryUnitTest.java new file mode 100644 index 00000000..f65447ee --- /dev/null +++ b/src/test/java/org/springframework/hateoas/jaxrs/JaxRsLinkBuilderFactoryUnitTest.java @@ -0,0 +1,53 @@ +package org.springframework.hateoas.jaxrs; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import javax.ws.rs.Path; + +import org.junit.Test; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.TestUtils; + +/** + * Unit test for {@link JaxRsLinkBuilderFactory}. + * + * @author Ricardo Gladwell + * @author Oliver Gierke + */ +public class JaxRsLinkBuilderFactoryUnitTest extends TestUtils { + + JaxRsLinkBuilderFactory factory = new JaxRsLinkBuilderFactory(); + + @Test + public void createsLinkToServiceRoot() { + + Link link = factory.linkTo(PersonServiceImpl.class).withSelfRel(); + + assertThat(link.getRel(), is(Link.REL_SELF)); + assertThat(link.getHref(), endsWith("/people")); + } + + @Test + public void createsLinkToParameterizedServiceRoot() { + + Link link = factory.linkTo(PersonsAddressesService.class, 15).withSelfRel(); + + assertThat(link.getRel(), is(Link.REL_SELF)); + assertThat(link.getHref(), endsWith("/people/15/addresses")); + } + + @Path("/people") + interface PersonService { + + } + + class PersonServiceImpl implements PersonService { + + } + + @Path("/people/{id}/addresses") + class PersonsAddressesService { + + } +} diff --git a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java index 4a939438..ab935f70 100644 --- a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java @@ -20,7 +20,6 @@ import static org.junit.Assert.*; import org.junit.Test; import org.springframework.hateoas.Link; -import org.springframework.hateoas.LinkBuilderFactory; import org.springframework.hateoas.TestUtils; import org.springframework.hateoas.mvc.ControllerLinkBuilderUnitTest.PersonControllerImpl; import org.springframework.hateoas.mvc.ControllerLinkBuilderUnitTest.PersonsAddressesController; @@ -33,7 +32,7 @@ import org.springframework.hateoas.mvc.ControllerLinkBuilderUnitTest.PersonsAddr */ public class ControllerLinkBuilderFactoryUnitTest extends TestUtils { - LinkBuilderFactory factory = new ControllerLinkBuilderFactory(); + ControllerLinkBuilderFactory factory = new ControllerLinkBuilderFactory(); @Test public void createsLinkToControllerRoot() { diff --git a/src/test/java/org/springframework/hateoas/mvc/ResourceAssemblerSupportUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/ResourceAssemblerSupportUnitTest.java index bc55855f..5f1915b8 100644 --- a/src/test/java/org/springframework/hateoas/mvc/ResourceAssemblerSupportUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/ResourceAssemblerSupportUnitTest.java @@ -26,6 +26,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.hateoas.Identifiable; import org.springframework.hateoas.Link; +import org.springframework.hateoas.LinkBuilder; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.TestUtils; import org.springframework.web.bind.annotation.RequestMapping; @@ -86,7 +87,7 @@ public class ResourceAssemblerSupportUnitTest extends TestUtils { List result = assembler.toResources(Arrays.asList(first, second)); - ControllerLinkBuilder builder = linkTo(PersonController.class); + LinkBuilder builder = linkTo(PersonController.class); PersonResource firstResource = new PersonResource(); firstResource.add(builder.slash(1L).withSelfRel()); diff --git a/template.mf b/template.mf index 96ded0c6..727f7cdc 100644 --- a/template.mf +++ b/template.mf @@ -5,4 +5,5 @@ Bundle-ManifestVersion: 2 Import-Template: javax.xml.bind.*;version="0", org.springframework.*;version="${spring.version:[=.=.=,+1.0.0)}";resolution:=optional, - org.codehaus.jackson.*;version="${jackson.version:[=.=.=,+1.0.0)}";resolution:=optional + org.codehaus.jackson.*;version="${jackson.version:[=.=.=,+1.0.0)}";resolution:=optional, + javax.ws.rs.*;version="${jaxrs.version:[=.=.=,+1.0.0)}";resolution:=optional