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