Added @Relation annotation to be able to define the relations that shall be exposed. The annotation can be used on domain classes or ResourceSupport subtypes to define the relations that shall be used to build links pointing to them. Introduced RelProvider implementations that use the plain domain class name plus an appended List by default (DefaultRelProvider, fallback) as well as an AnnotationRelProvider that will inspect @Relation on the domain type. Added a DelegatingRelProvider that is based on a Spring Plugin PluginRegistry to allow adding RelProvider implementations transparently. Polished AnnotationRelProvider (added annotation caching) and corrected ControllerRelProvider to lookup relation types from Controller classes. Added BeanDefinition setup to HypermediaSupportBeanDefinitionRegistrar to setup a PluginRegistry of RelProviders and the wrapping DelegatingRelProvider to become the primary auto wiring candidate. Moved RelProvider registration into Jackson HandlerInstantiator implementations. Made the BeanPostProcessors for Jackson ObjectMapper customization aware of the BeanFactory to lookup the DelegatingRelProvider instance to hand them into the Jackson HandlerInstantiators. TODOs: - More tests - Correctly register ControllerRelProviders
This commit is contained in:
@@ -15,12 +15,14 @@
|
||||
*/
|
||||
package org.springframework.hateoas;
|
||||
|
||||
import org.springframework.plugin.core.Plugin;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface RelProvider {
|
||||
public interface RelProvider extends Plugin<Class<?>> {
|
||||
|
||||
String getRelForCollectionResource(Class<?> type);
|
||||
String getSingleResourceRelFor(Class<?> type);
|
||||
|
||||
String getRelForSingleResource(Class<?> type);
|
||||
String getCollectionResourceRelFor(Class<?> type);
|
||||
}
|
||||
|
||||
@@ -21,10 +21,13 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -32,14 +35,20 @@ import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.hateoas.EntityLinks;
|
||||
import org.springframework.hateoas.LinkDiscoverer;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
|
||||
import org.springframework.hateoas.core.AnnotationRelProvider;
|
||||
import org.springframework.hateoas.core.DefaultLinkDiscoverer;
|
||||
import org.springframework.hateoas.core.DefaultRelProvider;
|
||||
import org.springframework.hateoas.core.DelegatingRelProvider;
|
||||
import org.springframework.hateoas.hal.HalLinkDiscoverer;
|
||||
import org.springframework.hateoas.hal.Jackson1HalModule;
|
||||
import org.springframework.hateoas.hal.Jackson2HalModule;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.plugin.core.support.PluginRegistryFactoryBean;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
@@ -56,12 +65,15 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
private static final String LINK_DISCOVERER_BEAN_NAME = "_linkDiscoverer";
|
||||
private static final String DELEGATING_REL_PROVIDER_BEAN_NAME = "_relProvider";
|
||||
|
||||
private static final boolean JACKSON1_PRESENT = ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", null);
|
||||
private static final boolean JACKSON2_PRESENT = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
|
||||
null);
|
||||
private static final boolean JSONPATH_PRESENT = ClassUtils.isPresent("com.jayway.jsonpath.JsonPath", null);
|
||||
|
||||
private final ImportBeanDefinitionRegistrar linkBuilderBeanDefinitionRegistrar = new LinkBuilderBeanDefinitionRegistrar();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry)
|
||||
@@ -69,7 +81,7 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
|
||||
new LinkBuilderBeanDefinitionRegistrar().registerBeanDefinitions(importingClassMetadata, registry);
|
||||
linkBuilderBeanDefinitionRegistrar.registerBeanDefinitions(importingClassMetadata, registry);
|
||||
|
||||
Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableHypermediaSupport.class
|
||||
.getName());
|
||||
@@ -90,6 +102,38 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe
|
||||
registerWithGeneratedName(new RootBeanDefinition(Jackson1ModuleRegisteringBeanPostProcessor.class), registry);
|
||||
}
|
||||
}
|
||||
|
||||
registerRelProviderPluginRegistryAndDelegate(registry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers bean definitions for a {@link PluginRegistry} to capture {@link RelProvider} instances. Wraps the
|
||||
* registry into a {@link DelegatingRelProvider} bean definition backed by the registry.
|
||||
*
|
||||
* @param registry
|
||||
*/
|
||||
private static void registerRelProviderPluginRegistryAndDelegate(BeanDefinitionRegistry registry) {
|
||||
|
||||
RootBeanDefinition defaultRelProviderBeanDefinition = new RootBeanDefinition(DefaultRelProvider.class);
|
||||
registry.registerBeanDefinition("defaultRelProvider", defaultRelProviderBeanDefinition);
|
||||
|
||||
RootBeanDefinition annotationRelProviderBeanDefinition = new RootBeanDefinition(AnnotationRelProvider.class);
|
||||
registry.registerBeanDefinition("annotationRelProvider", annotationRelProviderBeanDefinition);
|
||||
|
||||
BeanDefinitionBuilder registryFactoryBeanBuilder = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(PluginRegistryFactoryBean.class);
|
||||
registryFactoryBeanBuilder.addPropertyValue("type", RelProvider.class);
|
||||
registryFactoryBeanBuilder.addPropertyValue("exclusions", DelegatingRelProvider.class);
|
||||
|
||||
AbstractBeanDefinition registryBeanDefinition = registryFactoryBeanBuilder.getBeanDefinition();
|
||||
registry.registerBeanDefinition("relProviderPluginRegistry", registryBeanDefinition);
|
||||
|
||||
BeanDefinitionBuilder delegateBuilder = BeanDefinitionBuilder.rootBeanDefinition(DelegatingRelProvider.class);
|
||||
delegateBuilder.addConstructorArgValue(registryBeanDefinition);
|
||||
|
||||
AbstractBeanDefinition beanDefinition = delegateBuilder.getBeanDefinition();
|
||||
beanDefinition.setPrimary(true);
|
||||
registry.registerBeanDefinition(DELEGATING_REL_PROVIDER_BEAN_NAME, beanDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,7 +165,17 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class Jackson2ModuleRegisteringBeanPostProcessor implements BeanPostProcessor {
|
||||
private static class Jackson2ModuleRegisteringBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
|
||||
|
||||
private BeanFactory factory;
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
|
||||
*/
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.factory = beanFactory;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -164,7 +218,12 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe
|
||||
}
|
||||
|
||||
private void registerModule(Object objectMapper) {
|
||||
((ObjectMapper) objectMapper).registerModule(new Jackson2HalModule(null));
|
||||
|
||||
RelProvider provider = factory.getBean(DELEGATING_REL_PROVIDER_BEAN_NAME, RelProvider.class);
|
||||
|
||||
ObjectMapper mapper = (ObjectMapper) objectMapper;
|
||||
mapper.registerModule(new Jackson2HalModule());
|
||||
mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(provider));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +233,18 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class Jackson1ModuleRegisteringBeanPostProcessor implements BeanPostProcessor {
|
||||
private static class Jackson1ModuleRegisteringBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
|
||||
*/
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -216,8 +286,13 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe
|
||||
}
|
||||
}
|
||||
|
||||
private void registerModule(Object mapper) {
|
||||
((org.codehaus.jackson.map.ObjectMapper) mapper).registerModule(new Jackson1HalModule(null));
|
||||
private void registerModule(Object objectMapper) {
|
||||
|
||||
RelProvider relProvider = beanFactory.getBean(DELEGATING_REL_PROVIDER_BEAN_NAME, RelProvider.class);
|
||||
|
||||
org.codehaus.jackson.map.ObjectMapper mapper = (org.codehaus.jackson.map.ObjectMapper) objectMapper;
|
||||
mapper.registerModule(new Jackson1HalModule());
|
||||
mapper.setHandlerInstantiator(new Jackson1HalModule.HalHandlerInstantiator(relProvider));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Alexander Baetz
|
||||
*/
|
||||
@Order(100)
|
||||
public class AnnotationRelProvider implements RelProvider {
|
||||
|
||||
private final Map<Class<?>, Relation> annotationCache = new HashMap<Class<?>, Relation>();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForCollectionResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public String getCollectionResourceRelFor(Class<?> type) {
|
||||
|
||||
Relation annotation = lookupAnnotation(type);
|
||||
|
||||
if (annotation == null || Relation.NO_RELATION.equals(annotation.collectionRelation())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return annotation.collectionRelation();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public String getSingleResourceRelFor(Class<?> type) {
|
||||
|
||||
Relation annotation = lookupAnnotation(type);
|
||||
|
||||
if (annotation == null || Relation.NO_RELATION.equals(annotation.value())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return annotation.value();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> delimiter) {
|
||||
return lookupAnnotation(delimiter) != null;
|
||||
}
|
||||
|
||||
private Relation lookupAnnotation(Class<?> type) {
|
||||
|
||||
Relation relation = annotationCache.get(type);
|
||||
|
||||
if (relation != null) {
|
||||
return relation;
|
||||
}
|
||||
|
||||
relation = AnnotationUtils.getAnnotation(type, Relation.class);
|
||||
|
||||
if (relation != null) {
|
||||
annotationCache.put(type, relation);
|
||||
}
|
||||
|
||||
return relation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link RelProvider} to simply use the uncapitalized version of the given type's name as
|
||||
* single resource rel as well as an appended {@code List} for the collection resource rel.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public class DefaultRelProvider implements RelProvider {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> delimiter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForCollectionResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public String getCollectionResourceRelFor(Class<?> type) {
|
||||
return StringUtils.uncapitalize(type.getSimpleName()) + "List";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public String getSingleResourceRelFor(Class<?> type) {
|
||||
return StringUtils.uncapitalize(type.getSimpleName());
|
||||
}
|
||||
}
|
||||
@@ -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.core;
|
||||
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DelegatingRelProvider implements RelProvider {
|
||||
|
||||
private final PluginRegistry<RelProvider, Class<?>> providers;
|
||||
|
||||
public DelegatingRelProvider(PluginRegistry<RelProvider, Class<?>> providers) {
|
||||
|
||||
Assert.notNull(providers, "RelProviders must not be null!");
|
||||
this.providers = providers;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public String getSingleResourceRelFor(Class<?> type) {
|
||||
return providers.getPluginFor(type).getSingleResourceRelFor(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForCollectionResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public String getCollectionResourceRelFor(java.lang.Class<?> type) {
|
||||
return providers.getPluginFor(type).getCollectionResourceRelFor(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(java.lang.Class<?> delimiter) {
|
||||
return providers.hasPluginFor(delimiter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import org.springframework.hateoas.Resource;
|
||||
|
||||
/**
|
||||
* Simple helper class.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class ObjectUtils {
|
||||
|
||||
/**
|
||||
* Returns the resource type of the given object. In case a {@link Resource} is given, it will return the
|
||||
* {@link Resource} content's type.
|
||||
*
|
||||
* @param object can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static Class<?> getResourceType(Object object) {
|
||||
|
||||
if (object instanceof Resource) {
|
||||
return nullSafeType(((Resource<?>) object).getContent());
|
||||
}
|
||||
|
||||
return nullSafeType(object);
|
||||
}
|
||||
|
||||
private static Class<?> nullSafeType(Object object) {
|
||||
return object == null ? null : object.getClass();
|
||||
}
|
||||
}
|
||||
52
src/main/java/org/springframework/hateoas/core/Relation.java
Normal file
52
src/main/java/org/springframework/hateoas/core/Relation.java
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.Resources;
|
||||
|
||||
/**
|
||||
* Annotation to configure the relation to be used when embedding objects in HAL representations of {@link Resource}s
|
||||
* and {@link Resources}.
|
||||
*
|
||||
* @author Alexander Baetz
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Relation {
|
||||
|
||||
static final String NO_RELATION = "";
|
||||
|
||||
/**
|
||||
* Defines the relation to be used when referring to a single resource.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String value() default NO_RELATION;
|
||||
|
||||
/**
|
||||
* Defines the relation to be used when referring to a collection of resources.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String collectionRelation() default NO_RELATION;
|
||||
}
|
||||
@@ -35,24 +35,32 @@ 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.module.SimpleSerializers;
|
||||
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.beans.BeanUtils;
|
||||
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.hateoas.core.ObjectUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -66,18 +74,13 @@ public class Jackson1HalModule extends SimpleModule {
|
||||
/**
|
||||
* Creates a new {@link Jackson1HalModule}.
|
||||
*/
|
||||
public Jackson1HalModule(RelProvider relProvider) {
|
||||
public Jackson1HalModule() {
|
||||
|
||||
super("json-hal-module", new Version(1, 0, 0, null));
|
||||
|
||||
setMixInAnnotation(Link.class, LinkMixin.class);
|
||||
setMixInAnnotation(ResourceSupport.class, ResourceSupportMixin.class);
|
||||
setMixInAnnotation(Resources.class, ResourcesMixin.class);
|
||||
|
||||
SimpleSerializers serializers = new SimpleSerializers();
|
||||
serializers.addSerializer(new HalResourcesSerializer(relProvider));
|
||||
|
||||
setSerializers(serializers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,7 +201,12 @@ public class Jackson1HalModule extends SimpleModule {
|
||||
|
||||
for (Object resource : value) {
|
||||
|
||||
String relation = relProvider == null ? "content" : relProvider.getRelForSingleResource(value.getClass());
|
||||
Class<?> type = ObjectUtils.getResourceType(resource);
|
||||
String relation = relProvider == null ? "content" : relProvider.getSingleResourceRelFor(type);
|
||||
|
||||
if (relation == null) {
|
||||
relation = "content";
|
||||
}
|
||||
|
||||
if (sortedLinks.get(relation) == null) {
|
||||
sortedLinks.put(relation, new ArrayList<Object>());
|
||||
@@ -454,4 +462,59 @@ public class Jackson1HalModule extends SimpleModule {
|
||||
return des;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HalHandlerInstantiator extends HandlerInstantiator {
|
||||
|
||||
private final Map<Class<?>, Object> instanceMap = new HashMap<Class<?>, Object>();
|
||||
|
||||
public HalHandlerInstantiator(RelProvider relProvider) {
|
||||
|
||||
Assert.notNull(relProvider, "RelProvider must not be null!");
|
||||
this.instanceMap.put(HalResourcesSerializer.class, new HalResourcesSerializer(null, relProvider));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonDeserializer<?> deserializerInstance(DeserializationConfig config, Annotated annotated,
|
||||
Class<? extends JsonDeserializer<?>> deserClass) {
|
||||
return (JsonDeserializer<?>) findInstance(deserClass, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated,
|
||||
Class<? extends KeyDeserializer> keyDeserClass) {
|
||||
return (KeyDeserializer) findInstance(keyDeserClass, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonSerializer<?> serializerInstance(SerializationConfig config, Annotated annotated,
|
||||
Class<? extends JsonSerializer<?>> serClass) {
|
||||
// there is a know bug in jackson that will not create a serializer instance if the handler instantiator returns
|
||||
// null!
|
||||
return (JsonSerializer<?>) findInstance(serClass, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeResolverBuilder<?> typeResolverBuilderInstance(MapperConfig<?> config, Annotated annotated,
|
||||
Class<? extends TypeResolverBuilder<?>> builderClass) {
|
||||
return (TypeResolverBuilder<?>) findInstance(builderClass, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeIdResolver typeIdResolverInstance(MapperConfig<?> config, Annotated annotated,
|
||||
Class<? extends TypeIdResolver> resolverClass) {
|
||||
return (TypeIdResolver) findInstance(resolverClass, false);
|
||||
}
|
||||
|
||||
private Object findInstance(Class<?> type, boolean createInstance) {
|
||||
|
||||
Object result = instanceMap.get(type);
|
||||
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return createInstance ? BeanUtils.instantiateClass(type) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,14 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
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.hateoas.core.ObjectUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerationException;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
@@ -37,17 +40,24 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.core.Version;
|
||||
import com.fasterxml.jackson.databind.BeanProperty;
|
||||
import com.fasterxml.jackson.databind.DeserializationConfig;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.KeyDeserializer;
|
||||
import com.fasterxml.jackson.databind.SerializationConfig;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
|
||||
import com.fasterxml.jackson.databind.cfg.MapperConfig;
|
||||
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
|
||||
import com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase;
|
||||
import com.fasterxml.jackson.databind.introspect.Annotated;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.module.SimpleSerializers;
|
||||
import com.fasterxml.jackson.databind.ser.ContainerSerializer;
|
||||
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
|
||||
import com.fasterxml.jackson.databind.ser.std.MapSerializer;
|
||||
@@ -63,17 +73,13 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
|
||||
private static final long serialVersionUID = 7806951456457932384L;
|
||||
|
||||
public Jackson2HalModule(RelProvider relProvider) {
|
||||
public Jackson2HalModule() {
|
||||
|
||||
super("json-hal-module", new Version(1, 0, 0, null, "org.springframework.hateoas", "spring-hateoas"));
|
||||
|
||||
setMixInAnnotation(Link.class, LinkMixin.class);
|
||||
setMixInAnnotation(ResourceSupport.class, ResourceSupportMixin.class);
|
||||
setMixInAnnotation(Resources.class, ResourcesMixin.class);
|
||||
|
||||
SimpleSerializers serializers = new SimpleSerializers();
|
||||
serializers.addSerializer(new HalResourcesSerializer(relProvider));
|
||||
setSerializers(serializers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,6 +204,8 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
*/
|
||||
public static class HalResourcesSerializer extends ContainerSerializer<Collection<?>> implements ContextualSerializer {
|
||||
|
||||
private static final String DEFAULT_REL = "content";
|
||||
|
||||
private final BeanProperty property;
|
||||
private final RelProvider relProvider;
|
||||
|
||||
@@ -234,12 +242,16 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
|
||||
for (Object resource : value) {
|
||||
|
||||
// TODO: do something fancy to get the relation name
|
||||
String relation = relProvider == null ? "content" : relProvider.getRelForSingleResource(value.getClass());
|
||||
Class<?> type = ObjectUtils.getResourceType(resource);
|
||||
String relation = relProvider == null ? DEFAULT_REL : relProvider.getSingleResourceRelFor(type);
|
||||
|
||||
if (relation == null) {
|
||||
relation = DEFAULT_REL;
|
||||
}
|
||||
|
||||
if (sortedLinks.get(relation) == null) {
|
||||
sortedLinks.put(relation, new ArrayList<Object>());
|
||||
}
|
||||
|
||||
sortedLinks.get(relation).add(resource);
|
||||
}
|
||||
|
||||
@@ -257,7 +269,7 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
@Override
|
||||
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
|
||||
throws JsonMappingException {
|
||||
return new HalResourcesSerializer(property, null);
|
||||
return new HalResourcesSerializer(property, relProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -564,4 +576,69 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
return des;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HalHandlerInstantiator extends HandlerInstantiator {
|
||||
|
||||
private final Map<Class<?>, Object> instanceMap = new HashMap<Class<?>, Object>();
|
||||
|
||||
public HalHandlerInstantiator(RelProvider resolver) {
|
||||
|
||||
Assert.notNull(resolver, "RelProvider must not be null!");
|
||||
this.instanceMap.put(HalResourcesSerializer.class, new HalResourcesSerializer(null, resolver));
|
||||
}
|
||||
|
||||
private Object findInstance(Class<?> type) {
|
||||
|
||||
Object result = instanceMap.get(type);
|
||||
return result != null ? result : BeanUtils.instantiateClass(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#deserializerInstance(com.fasterxml.jackson.databind.DeserializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public JsonDeserializer<?> deserializerInstance(DeserializationConfig config, Annotated annotated,
|
||||
Class<?> deserClass) {
|
||||
return (JsonDeserializer<?>) findInstance(deserClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#keyDeserializerInstance(com.fasterxml.jackson.databind.DeserializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated,
|
||||
Class<?> keyDeserClass) {
|
||||
return (KeyDeserializer) findInstance(keyDeserClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#serializerInstance(com.fasterxml.jackson.databind.SerializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public JsonSerializer<?> serializerInstance(SerializationConfig config, Annotated annotated, Class<?> serClass) {
|
||||
return (JsonSerializer<?>) findInstance(serClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#typeResolverBuilderInstance(com.fasterxml.jackson.databind.cfg.MapperConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public TypeResolverBuilder<?> typeResolverBuilderInstance(MapperConfig<?> config, Annotated annotated,
|
||||
Class<?> builderClass) {
|
||||
return (TypeResolverBuilder<?>) findInstance(builderClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#typeIdResolverInstance(com.fasterxml.jackson.databind.cfg.MapperConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public TypeIdResolver typeIdResolverInstance(MapperConfig<?> config, Annotated annotated, Class<?> resolverClass) {
|
||||
return (TypeIdResolver) findInstance(resolverClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,26 +18,28 @@ package org.springframework.hateoas.mvc;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.hateoas.ExposesResourceFor;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class ControllerRelProvider implements RelProvider {
|
||||
|
||||
private final Class<?> controllerType;
|
||||
private final Class<?> entityType;
|
||||
private final String collectionResourceRel;
|
||||
private final String singleResourceRel;
|
||||
private final PluginRegistry<RelProvider, Class<?>> providers;
|
||||
|
||||
public ControllerRelProvider(Class<?> controller) {
|
||||
public ControllerRelProvider(Class<?> controller, PluginRegistry<RelProvider, Class<?>> providers) {
|
||||
|
||||
Assert.notNull(controller);
|
||||
|
||||
ExposesResourceFor annotation = AnnotationUtils.findAnnotation(controller, ExposesResourceFor.class);
|
||||
Assert.notNull(annotation);
|
||||
|
||||
this.controllerType = controller;
|
||||
this.entityType = annotation.value();
|
||||
this.singleResourceRel = StringUtils.uncapitalize(entityType.getSimpleName());
|
||||
this.collectionResourceRel = singleResourceRel + "List";
|
||||
this.providers = providers;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -45,8 +47,8 @@ public class ControllerRelProvider implements RelProvider {
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForCollectionResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public String getRelForCollectionResource(Class<?> type) {
|
||||
return collectionResourceRel;
|
||||
public String getCollectionResourceRelFor(Class<?> resource) {
|
||||
return providers.getPluginFor(entityType).getCollectionResourceRelFor(resource);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -54,7 +56,16 @@ public class ControllerRelProvider implements RelProvider {
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public String getRelForSingleResource(Class<?> type) {
|
||||
return singleResourceRel;
|
||||
public String getSingleResourceRelFor(Class<?> resource) {
|
||||
return providers.getPluginFor(entityType).getSingleResourceRelFor(resource);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> delimiter) {
|
||||
return controllerType.equals(delimiter);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user