diff --git a/.gitignore b/.gitignore
index 5be812dbb..8c84b71be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
target/
+test-output/
.settings/
.project
.classpath
\ No newline at end of file
diff --git a/src/main/java/org/springframework/hateoas/RelProvider.java b/src/main/java/org/springframework/hateoas/RelProvider.java
new file mode 100644
index 000000000..d222a61e0
--- /dev/null
+++ b/src/main/java/org/springframework/hateoas/RelProvider.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 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;
+
+/**
+ * @author Oliver Gierke
+ */
+public interface RelProvider {
+
+ String getRelForCollectionResource(Object type);
+
+ String getRelForSingleResource(Object type);
+}
diff --git a/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java b/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java
index 13f844173..da38730c4 100644
--- a/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java
+++ b/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java
@@ -15,7 +15,8 @@
*/
package org.springframework.hateoas.config;
-import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.*;
+import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.registerBeanDefinition;
+import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.registerWithGeneratedName;
import java.util.List;
import java.util.Map;
@@ -103,12 +104,12 @@ private AbstractBeanDefinition getLinkDiscovererBeanDefinition(HypermediaType ty
AbstractBeanDefinition definition;
switch (type) {
- case HAL:
- definition = new RootBeanDefinition(HalLinkDiscoverer.class);
- break;
- case DEFAULT:
- default:
- definition = new RootBeanDefinition(DefaultLinkDiscoverer.class);
+ case HAL:
+ definition = new RootBeanDefinition(HalLinkDiscoverer.class);
+ break;
+ case DEFAULT:
+ default:
+ definition = new RootBeanDefinition(DefaultLinkDiscoverer.class);
}
definition.setSource(this);
diff --git a/src/main/java/org/springframework/hateoas/hal/AnnotationRelProvider.java b/src/main/java/org/springframework/hateoas/hal/AnnotationRelProvider.java
new file mode 100644
index 000000000..fdab68133
--- /dev/null
+++ b/src/main/java/org/springframework/hateoas/hal/AnnotationRelProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 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 org.springframework.hateoas.RelProvider;
+import org.springframework.hateoas.Resource;
+
+/**
+ * @author Oliver Gierke
+ */
+public class AnnotationRelProvider implements RelProvider {
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.hateoas.RelProvider#getRelForCollectionResource(java.lang.Object)
+ */
+ @Override
+ public String getRelForCollectionResource(Object resource) {
+ // check for hateoas wrapper type
+ if (Resource.class.isInstance(resource)) {
+ resource = ((Resource) resource).getContent();
+ }
+
+ HalRelation annotation = resource.getClass().getAnnotation(HalRelation.class);
+ if (annotation == null || HalRelation.NO_RELATION.equals(annotation.collectionRelation())) {
+ return null;
+ }
+ return annotation.value();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Object)
+ */
+ @Override
+ public String getRelForSingleResource(Object resource) {
+ // check for hateoas wrapper type
+ if (Resource.class.isInstance(resource)) {
+ resource = ((Resource) resource).getContent();
+ }
+
+ HalRelation annotation = resource.getClass().getAnnotation(HalRelation.class);
+ if (annotation == null || HalRelation.NO_RELATION.equals(annotation.value())) {
+ return null;
+ }
+ return annotation.value();
+ }
+}
diff --git a/src/main/java/org/springframework/hateoas/hal/HalRelation.java b/src/main/java/org/springframework/hateoas/hal/HalRelation.java
new file mode 100644
index 000000000..ff3a310b2
--- /dev/null
+++ b/src/main/java/org/springframework/hateoas/hal/HalRelation.java
@@ -0,0 +1,18 @@
+package org.springframework.hateoas.hal;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface HalRelation {
+
+ public static final String NO_RELATION = "";
+
+ String value() default NO_RELATION;
+
+ String collectionRelation() default NO_RELATION;
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/springframework/hateoas/hal/Jackson1HalModule.java b/src/main/java/org/springframework/hateoas/hal/Jackson1HalModule.java
index d1b97a939..47595fdeb 100644
--- a/src/main/java/org/springframework/hateoas/hal/Jackson1HalModule.java
+++ b/src/main/java/org/springframework/hateoas/hal/Jackson1HalModule.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 the original author or authors.
+ * Copyright 2012-2013 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.
@@ -17,6 +17,7 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -24,21 +25,40 @@
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.BeanProperty;
+import org.codehaus.jackson.map.ContextualDeserializer;
import org.codehaus.jackson.map.ContextualSerializer;
+import org.codehaus.jackson.map.DeserializationConfig;
+import org.codehaus.jackson.map.DeserializationContext;
+import org.codehaus.jackson.map.HandlerInstantiator;
+import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.KeyDeserializer;
+import org.codehaus.jackson.map.MapperConfig;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.map.TypeSerializer;
+import org.codehaus.jackson.map.deser.std.ContainerDeserializerBase;
+import org.codehaus.jackson.map.introspect.Annotated;
+import org.codehaus.jackson.map.jsontype.TypeIdResolver;
+import org.codehaus.jackson.map.jsontype.TypeResolverBuilder;
import org.codehaus.jackson.map.module.SimpleModule;
import org.codehaus.jackson.map.ser.std.ContainerSerializerBase;
import org.codehaus.jackson.map.ser.std.MapSerializer;
import org.codehaus.jackson.map.type.TypeFactory;
import org.codehaus.jackson.type.JavaType;
import org.springframework.hateoas.Link;
+import org.springframework.hateoas.RelProvider;
+import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
+import org.springframework.hateoas.Resources;
+import org.springframework.util.StringUtils;
/**
* Jackson 1 module implementation to render {@link Link} and {@link ResourceSupport} instances in HAL compatible JSON.
@@ -57,10 +77,12 @@ public Jackson1HalModule() {
setMixInAnnotation(Link.class, LinkMixin.class);
setMixInAnnotation(ResourceSupport.class, ResourceSupportMixin.class);
+ setMixInAnnotation(Resources.class, ResourcesMixin.class);
}
/**
- * Custom {@link JsonSerializer} to render Link instances in HAL compatible JSON.
+ * Custom {@link JsonSerializer} to render Link instances in HAL compatible JSON. Renders the list as a map, where
+ * links are sorted based on their relation.
*
* @author Alexander Baetz
* @author Oliver Gierke
@@ -113,7 +135,7 @@ public void serialize(List value, JsonGenerator jgen, SerializerProvider p
serializer.serialize(sortedLinks, jgen, provider);
}
- /*
+ /*
* (non-Javadoc)
* @see org.codehaus.jackson.map.ContextualSerializer#createContextual(org.codehaus.jackson.map.SerializationConfig, org.codehaus.jackson.map.BeanProperty)
*/
@@ -134,8 +156,95 @@ public ContainerSerializerBase> _withValueTypeSerializer(TypeSerializer vts) {
}
/**
- * Custom {@link JsonSerializer} to render Link instances in HAL compatible JSON. Renders the {@link Link} as
- * immediate object if we have a single one or as array if we have multiple ones.
+ * Custom {@link JsonSerializer} to render {@link Resource}-Lists in HAL compatible JSON. Renders the list as a Map.
+ *
+ * @author Alexander Baetz
+ * @author Oliver Gierke
+ */
+ public static class HalResourcesSerializer extends ContainerSerializerBase> implements
+ ContextualSerializer> {
+
+ private final BeanProperty property;
+ private final RelProvider relProvider;
+
+ public HalResourcesSerializer() {
+ this(null);
+ }
+
+ /**
+ * Creates a new {@link HalLinkListSerializer}.
+ */
+ public HalResourcesSerializer(RelProvider relProvider) {
+ this(null, relProvider);
+ }
+
+ public HalResourcesSerializer(BeanProperty property, RelProvider relProvider) {
+
+ super(Collection.class, false);
+ this.property = property;
+ this.relProvider = relProvider;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.codehaus.jackson.map.ser.std.SerializerBase#serialize(java.lang.Object, org.codehaus.jackson.JsonGenerator, org.codehaus.jackson.map.SerializerProvider)
+ */
+ @Override
+ public void serialize(Collection> value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
+ JsonGenerationException {
+
+ // sort resources according to their types
+ Map> sortedLinks = new HashMap>();
+
+ for (Object resource : value) {
+
+ String relation = relProvider == null ? "content" : relProvider.getRelForSingleResource(resource);
+ if (relation == null) {
+ relation = "content";
+ }
+
+ if (sortedLinks.get(relation) == null) {
+ sortedLinks.put(relation, new ArrayList