diff --git a/src/main/java/org/springframework/hateoas/TemplateVariables.java b/src/main/java/org/springframework/hateoas/TemplateVariables.java index a481882d..eae3e448 100644 --- a/src/main/java/org/springframework/hateoas/TemplateVariables.java +++ b/src/main/java/org/springframework/hateoas/TemplateVariables.java @@ -90,6 +90,15 @@ public class TemplateVariables implements Iterable { return concat(variables.variables); } + /** + * Returns the contained {@link TemplateVariable}s as {@link List}. + * + * @return + */ + public List asList() { + return this.variables; + } + /* * (non-Javadoc) * @see java.lang.Iterable#iterator() diff --git a/src/main/java/org/springframework/hateoas/UriTemplate.java b/src/main/java/org/springframework/hateoas/UriTemplate.java index cced35b0..6e2c68bb 100644 --- a/src/main/java/org/springframework/hateoas/UriTemplate.java +++ b/src/main/java/org/springframework/hateoas/UriTemplate.java @@ -40,7 +40,7 @@ public class UriTemplate implements Iterable { private static final Pattern VARIABLE_REGEX = Pattern.compile("\\{([\\?\\&#/]?)([\\w\\,]+)\\}"); - private final List variables = new ArrayList(); + private final TemplateVariables variables;; private String baseUri; /** @@ -53,24 +53,27 @@ public class UriTemplate implements Iterable { Assert.hasText(template, "Template must not be null or empty!"); Matcher matcher = VARIABLE_REGEX.matcher(template); + int baseUriEndIndex = template.length(); + List variables = new ArrayList(); while (matcher.find()) { - if (baseUri == null) { - this.baseUri = template.substring(0, matcher.start(0)); + int start = matcher.start(0); + + if (start < baseUriEndIndex) { + baseUriEndIndex = start; } VariableType type = VariableType.from(matcher.group(1)); String[] names = matcher.group(2).split(","); for (String name : names) { - this.variables.add(new TemplateVariable(name, type)); + variables.add(new TemplateVariable(name, type)); } } - if (this.baseUri == null) { - this.baseUri = template; - } + this.variables = new TemplateVariables(variables); + this.baseUri = template.substring(0, baseUriEndIndex); } /** @@ -94,7 +97,7 @@ public class UriTemplate implements Iterable { * @return */ public List getVariables() { - return this.variables; + return this.variables.asList(); } /** @@ -162,6 +165,15 @@ public class UriTemplate implements Iterable { return this.variables.iterator(); } + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return baseUri + variables.toString(); + } + /** * Appends the value for the given {@link TemplateVariable} to the given {@link UriComponentsBuilder}. * diff --git a/src/main/java/org/springframework/hateoas/hal/CurieProvider.java b/src/main/java/org/springframework/hateoas/hal/CurieProvider.java index a98f9ad3..36905b9d 100644 --- a/src/main/java/org/springframework/hateoas/hal/CurieProvider.java +++ b/src/main/java/org/springframework/hateoas/hal/CurieProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 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. @@ -15,13 +15,17 @@ */ package org.springframework.hateoas.hal; +import java.util.Collection; + import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; /** * API to provide HAL curie information for links. * * @see http://tools.ietf.org/html/draft-kelly-json-hal#section-8.2 * @author Oliver Gierke + * @since 0.9 */ public interface CurieProvider { @@ -38,7 +42,8 @@ public interface CurieProvider { * Returns an object to render as the base curie information. Implementations have to make sure, the retunred * instances renders as defined in the spec. * + * @param links the {@link Links} that have been added to the response so far. * @return must not be {@literal null}. */ - Object getCurieInformation(); + Collection getCurieInformation(Links links); } diff --git a/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java b/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java index 3d873389..39393752 100644 --- a/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java +++ b/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java @@ -15,13 +15,20 @@ */ package org.springframework.hateoas.hal; +import java.util.Collection; +import java.util.Collections; + import org.springframework.hateoas.IanaRels; import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; +import org.springframework.hateoas.UriTemplate; import org.springframework.util.Assert; -import org.springframework.web.util.UriTemplate; /** + * Default implementation of {@link CurieProvider} rendering a single configurable {@link UriTemplate} based curie. + * * @author Oliver Gierke + * @since 0.9 */ public class DefaultCurieProvider implements CurieProvider { @@ -48,8 +55,8 @@ public class DefaultCurieProvider implements CurieProvider { * @see org.springframework.hateoas.hal.CurieProvider#getCurieInformation() */ @Override - public Curie getCurieInformation() { - return curie; + public Collection getCurieInformation(Links links) { + return Collections.singleton(curie); } /* @@ -70,8 +77,7 @@ public class DefaultCurieProvider implements CurieProvider { * * @author Oliver Gierke */ - @SuppressWarnings("unused") - private static class Curie extends Link { + protected static class Curie extends Link { private static final long serialVersionUID = 1L; diff --git a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java index 1ed455ca..d0391ad3 100644 --- a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java +++ b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java @@ -18,7 +18,6 @@ package org.springframework.hateoas.hal; import java.io.IOException; import java.lang.reflect.Type; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; @@ -28,6 +27,7 @@ import java.util.Map; import org.springframework.beans.BeanUtils; import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; import org.springframework.hateoas.RelProvider; import org.springframework.hateoas.Resource; import org.springframework.hateoas.ResourceSupport; @@ -132,6 +132,7 @@ public class Jackson2HalModule extends SimpleModule { // sort links according to their relation Map> sortedLinks = new LinkedHashMap>(); + List links = new ArrayList(); boolean prefixingRequired = curieProvider != null; boolean curiedLinkPresent = false; @@ -148,13 +149,15 @@ public class Jackson2HalModule extends SimpleModule { sortedLinks.put(rel, new ArrayList()); } + links.add(link); sortedLinks.get(rel).add(link); } if (prefixingRequired && curiedLinkPresent) { - Object curieInformation = curieProvider.getCurieInformation(); - List curies = new ArrayList(); - curies.add(Arrays.asList(curieInformation)); + + ArrayList curies = new ArrayList(); + curies.add(curieProvider.getCurieInformation(new Links(links))); + sortedLinks.put("curies", curies); } @@ -164,7 +167,7 @@ public class Jackson2HalModule extends SimpleModule { JavaType mapType = typeFactory.constructMapType(HashMap.class, keyType, valueType); MapSerializer serializer = MapSerializer.construct(new String[] {}, mapType, true, null, - provider.findKeySerializer(keyType, null), new OptionalListJackson2Serializer(property)); + provider.findKeySerializer(keyType, null), new OptionalListJackson2Serializer(property), null); serializer.serialize(sortedLinks, jgen, provider); } @@ -270,7 +273,7 @@ public class Jackson2HalModule extends SimpleModule { JavaType mapType = typeFactory.constructMapType(HashMap.class, keyType, valueType); MapSerializer serializer = MapSerializer.construct(new String[] {}, mapType, true, null, - provider.findKeySerializer(keyType, null), new OptionalListJackson2Serializer(property)); + provider.findKeySerializer(keyType, null), new OptionalListJackson2Serializer(property), null); serializer.serialize(builder.asMap(), jgen, provider); } @@ -451,6 +454,7 @@ public class Jackson2HalModule extends SimpleModule { private static final long serialVersionUID = 6420432361123210955L; + @SuppressWarnings("deprecation") public HalLinkListDeserializer() { super(List.class); } @@ -524,6 +528,7 @@ public class Jackson2HalModule extends SimpleModule { this(null, vc); } + @SuppressWarnings("deprecation") private HalResourcesDeserializer(Class type, JavaType contentType) { super(type); diff --git a/src/test/java/org/springframework/hateoas/TemplateVariablesUnitTest.java b/src/test/java/org/springframework/hateoas/TemplateVariablesUnitTest.java new file mode 100644 index 00000000..d1f8a49e --- /dev/null +++ b/src/test/java/org/springframework/hateoas/TemplateVariablesUnitTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2014 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; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.hateoas.TemplateVariable.VariableType; + +/** + * Unit tests for {@link TemplateVariables}. + * + * @author Oliver Gierke + */ +public class TemplateVariablesUnitTest { + + /** + * @see #137 + */ + @Test + public void rendersNoTempalteVariablesAsEmptyString() { + assertThat(TemplateVariables.NONE.toString(), is("")); + } + + /** + * @see #137 + */ + @Test + public void rendersSingleVariableCorrectly() { + + TemplateVariables variables = new TemplateVariables(new TemplateVariable("foo", VariableType.SEGMENT)); + assertThat(variables.toString(), is("{/foo}")); + } + + /** + * @see #137 + */ + @Test + public void combinesMultipleVariablesOfTheSameType() { + + TemplateVariable first = new TemplateVariable("foo", VariableType.REQUEST_PARAM); + TemplateVariable second = new TemplateVariable("bar", VariableType.REQUEST_PARAM); + + TemplateVariables variables = new TemplateVariables(first, second); + + assertThat(variables.toString(), is("{?foo,bar}")); + } + + /** + * @see #137 + */ + @Test + public void combinesMultipleVariablesOfTheDifferentType() { + + TemplateVariable first = new TemplateVariable("foo", VariableType.SEGMENT); + TemplateVariable second = new TemplateVariable("bar", VariableType.REQUEST_PARAM); + + TemplateVariables variables = new TemplateVariables(first, second); + + assertThat(variables.toString(), is("{/foo}{?bar}")); + } + + /** + * @see #137 + */ + @Test + public void concatsVariables() { + + TemplateVariables first = new TemplateVariables(new TemplateVariable("foo", VariableType.SEGMENT)); + TemplateVariables second = new TemplateVariables(new TemplateVariable("bar", VariableType.REQUEST_PARAM)); + + TemplateVariables variables = first.concat(second); + + assertThat(variables.toString(), is("{/foo}{?bar}")); + } + +} diff --git a/src/test/java/org/springframework/hateoas/core/MethodParametersUnitTest.java b/src/test/java/org/springframework/hateoas/core/MethodParametersUnitTest.java new file mode 100644 index 00000000..13a3f90c --- /dev/null +++ b/src/test/java/org/springframework/hateoas/core/MethodParametersUnitTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013-2014 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.core; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.MethodParameter; + +/** + * Unit tests for {@link MethodParameters}. + * + * @author Oliver Gierke + */ +public class MethodParametersUnitTest { + + @Test + public void prefersAnnotatedParameterOverDiscovered() throws Exception { + + Method method = Sample.class.getMethod("method", String.class, String.class, Object.class); + MethodParameters parameters = new MethodParameters(method, new AnnotationAttribute(Qualifier.class)); + + assertThat(parameters.getParameter("param"), is(notNullValue())); + assertThat(parameters.getParameter("foo"), is(notNullValue())); + assertThat(parameters.getParameter("another"), is(nullValue())); + } + + /** + * @see #138 + */ + @Test + public void returnsParametersOfAGivenType() throws Exception { + + Method method = Sample.class.getMethod("method", String.class, String.class, Object.class); + MethodParameters methodParameters = new MethodParameters(method); + + List objectParameters = methodParameters.getParametersOfType(Object.class); + assertThat(objectParameters, hasSize(1)); + assertThat(objectParameters.get(0).getParameterIndex(), is(2)); + } + + static class Sample { + + public void method(String param, @Qualifier("foo") String another, Object object) {} + } + +} diff --git a/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java b/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java new file mode 100644 index 00000000..8dbfc3d0 --- /dev/null +++ b/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2013-2014 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.hal; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.UriTemplate; + +/** + * Unit tests for {@link DefaultCurieProvider}. + * + * @author Oliver Gierke + */ +public class DefaultCurieProviderUnitTest { + + private static final UriTemplate URI_TEMPLATE = new UriTemplate("http://localhost:8080/rels/{rel}"); + + CurieProvider provider = new DefaultCurieProvider("acme", URI_TEMPLATE); + + @Test(expected = IllegalArgumentException.class) + public void preventsNullCurieName() { + new DefaultCurieProvider(null, URI_TEMPLATE); + } + + @Test(expected = IllegalArgumentException.class) + public void preventsEmptyCurieName() { + new DefaultCurieProvider("", URI_TEMPLATE); + } + + @Test(expected = IllegalArgumentException.class) + public void preventsNullUriTemplateName() { + new DefaultCurieProvider("acme", null); + } + + @Test(expected = IllegalArgumentException.class) + public void preventsUriTemplateWithoutVariable() { + new DefaultCurieProvider("acme", new UriTemplate("http://localhost:8080/rels")); + } + + @Test(expected = IllegalArgumentException.class) + public void preventsUriTemplateWithMoreThanOneVariable() { + new DefaultCurieProvider("acme", new UriTemplate("http://localhost:8080/rels/{rel}/{another}")); + } + + @Test + public void doesNotPrefixIanaRels() { + assertThat(provider.getNamespacedRelFrom(new Link("http://amazon.com")), is("self")); + } + + @Test + public void prefixesNormalRels() { + assertThat(provider.getNamespacedRelFrom(new Link("http://amazon.com", "book")), is("acme:book")); + } + + @Test + public void doesNotPrefixQualifiedRels() { + assertThat(provider.getNamespacedRelFrom(new Link("http://amazon.com", "custom:rel")), is("custom:rel")); + } +} diff --git a/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTests.java b/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTests.java index 0f7597e8..4f99af64 100644 --- a/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTests.java +++ b/src/test/java/org/springframework/hateoas/hal/DefaultCurieProviderUnitTests.java @@ -20,7 +20,7 @@ import static org.junit.Assert.*; import org.junit.Test; import org.springframework.hateoas.Link; -import org.springframework.web.util.UriTemplate; +import org.springframework.hateoas.UriTemplate; /** * Unit tests for {@link DefaultCurieProvider}. diff --git a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java index 66999344..acc9bcb7 100644 --- a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java @@ -19,6 +19,8 @@ import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -32,9 +34,9 @@ import org.springframework.hateoas.PagedResources.PageMetadata; import org.springframework.hateoas.Resource; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.Resources; +import org.springframework.hateoas.UriTemplate; import org.springframework.hateoas.core.AnnotationRelProvider; import org.springframework.hateoas.hal.Jackson2HalModule.HalHandlerInstantiator; -import org.springframework.web.util.UriTemplate; import com.fasterxml.jackson.databind.ObjectMapper; @@ -61,6 +63,7 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg static final Links PAGINATION_LINKS = new Links(new Link("foo", Link.REL_NEXT), new Link("bar", Link.REL_PREVIOUS)); static final String CURIED_DOCUMENT = "{\"_links\":{\"self\":{\"href\":\"foo\"},\"foo:myrel\":{\"href\":\"bar\"},\"curies\":[{\"href\":\"http://localhost:8080/rels/{rel}\",\"name\":\"foo\",\"template\":true}]}}"; + static final String MULTIPLE_CURIES_DOCUMENT = "{\"_links\":{\"default:myrel\":{\"href\":\"foo\"},\"curies\":[{\"href\":\"bar\",\"name\":\"foo\"},{\"href\":\"foo\",\"name\":\"bar\"}]}}"; static final String SINGLE_NON_CURIE_LINK = "{\"_links\":{\"self\":{\"href\":\"foo\"}}}"; static final String EMPTY_DOCUMENT = "{}"; @@ -322,6 +325,25 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg assertThat(write(support), is(LINK_TEMPLATE)); } + /** + * @see #142 + */ + @Test + public void rendersMultipleCuries() throws Exception { + + Resources resources = new Resources(Collections.emptySet()); + resources.add(new Link("foo", "myrel")); + + CurieProvider provider = new DefaultCurieProvider("default", new UriTemplate("/doc{?rel}")) { + @Override + public Collection getCurieInformation(Links links) { + return Arrays.asList(new Curie("foo", "bar"), new Curie("bar", "foo")); + } + }; + + assertThat(getCuriedObjectMapper(provider).writeValueAsString(resources), is(MULTIPLE_CURIES_DOCUMENT)); + } + private static Resources> setupAnnotatedPagedResources() { List> content = new ArrayList>(); @@ -351,11 +373,14 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg private static ObjectMapper getCuriedObjectMapper() { - CurieProvider curieProvider = new DefaultCurieProvider("foo", new UriTemplate("http://localhost:8080/rels/{rel}")); + return getCuriedObjectMapper(new DefaultCurieProvider("foo", new UriTemplate("http://localhost:8080/rels/{rel}"))); + } + + private static ObjectMapper getCuriedObjectMapper(CurieProvider provider) { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new Jackson2HalModule()); - mapper.setHandlerInstantiator(new HalHandlerInstantiator(new AnnotationRelProvider(), curieProvider)); + mapper.setHandlerInstantiator(new HalHandlerInstantiator(new AnnotationRelProvider(), provider)); return mapper; }