DATACMNS-330, DATACMNS-331 - Advanced web integration.

Added PagedResourcesAssembler to easily convert Page instances into a PagedResource instance and automatically build the required previous/next links based on the PageableHandlerMethodArgumentResolver present in the MVC configuration. The assembler can either be injected into a Spring MVC controller or a controller method. The latter will then assume the controller methods URI to be used as pagination link base.

Added @EnableSpringDataWebSupport to automatically register HandlerMethodArgumentResolvers for Pageable and Sort as well as a DomainClassConverter that will allow the usage of domain types being managed by Spring Data repositories in controller method signatures. In case Spring HATEOAS is present on the classpath, we'll also register a PagedResourcesAssembler for injection as well as the appropriate HandlerMethodArgumentResolver for injection into controller methods.

Adapted Sonargraph configuration accordingly. Upgraded to Spring HATEOAS 0.6.0.BUILD-SNAPSHOT.
This commit is contained in:
Oliver Gierke
2013-05-08 14:07:13 +02:00
parent a741651672
commit a93c2bff94
15 changed files with 1179 additions and 295 deletions

View File

@@ -1,424 +1,430 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<context version="7.1.7.187">
<scope name="spring-data-commons" type="Project">
<context version="7.1.9.205">
<scope type="Project" name="spring-data-commons">
<architecture>
<element name="Web" type="Layer">
<element name="Assignment" type="TypeFilter">
<element name="**.web.**" type="IncludeTypePattern"/>
<element type="Layer" name="Web">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.web.**"/>
</element>
<dependency type="AllowedDependency" toName="External|External"/>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Application"/>
<dependency type="AllowedDependency" toName="External|External::Subsystem|Java Beans"/>
<dependency type="AllowedDependency" toName="External|External::Subsystem|Reflection"/>
<dependency type="AllowedDependency" toName="External|External::Subsystem|Servlet API"/>
<dependency type="AllowedDependency" toName="External|External::Subsystem|Spring"/>
<element type="Subsystem" name="Config">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.config.**"/>
</element>
<dependency toName="Project|spring-data-commons::Layer|Repositories::Subsystem|Support" type="AllowedDependency"/>
</element>
<dependency toName="External|External" type="AllowedDependency"/>
<dependency toName="Project|spring-data-commons::Layer|Application" type="AllowedDependency"/>
<dependency toName="External|External::Subsystem|Java Beans" type="AllowedDependency"/>
<dependency toName="External|External::Subsystem|Reflection" type="AllowedDependency"/>
<dependency toName="External|External::Subsystem|Servlet API" type="AllowedDependency"/>
<dependency toName="External|External::Subsystem|Spring" type="AllowedDependency"/>
</element>
<element name="Repositories" type="Layer">
<element name="Assignment" type="TypeFilter">
<element name="**.repository.**" type="IncludeTypePattern"/>
<element type="Layer" name="Repositories">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.repository.**"/>
</element>
<element name="API" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.repository.*" type="IncludeTypePattern"/>
<element type="Subsystem" name="API">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.repository.*"/>
</element>
<stereotype name="Public"/>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Application::Subsystem|Domain"/>
<dependency toName="Project|spring-data-commons::Layer|Application::Subsystem|Domain" type="AllowedDependency"/>
</element>
<element name="Core" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.util.**" type="IncludeTypePattern"/>
<element name="**.core.*" type="IncludeTypePattern"/>
<element type="Subsystem" name="Core">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.util.**"/>
<element type="IncludeTypePattern" name="**.core.*"/>
</element>
</element>
<element name="Query" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.query.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Query">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.query.**"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Application::Subsystem|Domain"/>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Repositories::Subsystem|Core"/>
<dependency toName="Project|spring-data-commons::Layer|Application::Subsystem|Domain" type="AllowedDependency"/>
<dependency toName="Project|spring-data-commons::Layer|Repositories::Subsystem|Core" type="AllowedDependency"/>
</element>
<element name="Implementation" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.core.support.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Implementation">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.core.support.**"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Application::Subsystem|Domain"/>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Repositories::Subsystem|Core"/>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Repositories::Subsystem|Query"/>
<dependency toName="Project|spring-data-commons::Layer|Application::Subsystem|Domain" type="AllowedDependency"/>
<dependency toName="Project|spring-data-commons::Layer|Repositories::Subsystem|Core" type="AllowedDependency"/>
<dependency toName="Project|spring-data-commons::Layer|Repositories::Subsystem|Query" type="AllowedDependency"/>
</element>
<element name="Support" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.support.**" type="WeakTypePattern"/>
<element type="Subsystem" name="Support">
<element type="TypeFilter" name="Assignment">
<element type="WeakTypePattern" name="**.support.**"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Repositories::Subsystem|Core"/>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Repositories::Subsystem|Implementation"/>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Repositories::Subsystem|Query"/>
<dependency toName="Project|spring-data-commons::Layer|Repositories::Subsystem|Core" type="AllowedDependency"/>
<dependency toName="Project|spring-data-commons::Layer|Repositories::Subsystem|Implementation" type="AllowedDependency"/>
<dependency toName="Project|spring-data-commons::Layer|Repositories::Subsystem|Query" type="AllowedDependency"/>
</element>
<element name="History" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.history.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="History">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.history.**"/>
</element>
</element>
<element name="CDI" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.cdi.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="CDI">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.cdi.**"/>
</element>
<dependency type="AllowedDependency" toName="External|External::Subsystem|JavaEE"/>
<dependency type="AllowedDependency" toName="External|External::Subsystem|Logging"/>
<dependency type="AllowedDependency" toName="External|External::Subsystem|Reflection"/>
<dependency type="AllowedDependency" toName="External|External::Subsystem|Spring"/>
<dependency toName="External|External::Subsystem|JavaEE" type="AllowedDependency"/>
<dependency toName="External|External::Subsystem|Logging" type="AllowedDependency"/>
<dependency toName="External|External::Subsystem|Reflection" type="AllowedDependency"/>
<dependency toName="External|External::Subsystem|Spring" type="AllowedDependency"/>
</element>
<element name="Init" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.init.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Init">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.init.**"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Repositories::Subsystem|Support"/>
<dependency toName="Project|spring-data-commons::Layer|Repositories::Subsystem|Support" type="AllowedDependency"/>
</element>
<element name="Configuration" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.config.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Configuration">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.config.**"/>
</element>
<stereotype name="Unrestricted"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Application"/>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Core"/>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Mapping"/>
<dependency toName="Project|spring-data-commons::Layer|Application" type="AllowedDependency"/>
<dependency toName="Project|spring-data-commons::Layer|Core" type="AllowedDependency"/>
<dependency toName="Project|spring-data-commons::Layer|Mapping" type="AllowedDependency"/>
</element>
<element name="Conversion" type="Layer">
<element name="Assignment" type="TypeFilter">
<element name="**.convert.**" type="IncludeTypePattern"/>
<element type="Layer" name="Conversion">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.convert.**"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Core"/>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Mapping"/>
<dependency toName="Project|spring-data-commons::Layer|Core" type="AllowedDependency"/>
<dependency toName="Project|spring-data-commons::Layer|Mapping" type="AllowedDependency"/>
</element>
<element name="Mapping" type="Layer">
<element name="Assignment" type="TypeFilter">
<element name="**.mapping.**" type="IncludeTypePattern"/>
<element type="Layer" name="Mapping">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.mapping.**"/>
</element>
<element name="Model" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.model.**" type="IncludeTypePattern"/>
<element name="**.mapping.*" type="IncludeTypePattern"/>
<element type="Subsystem" name="Model">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.model.**"/>
<element type="IncludeTypePattern" name="**.mapping.*"/>
</element>
</element>
<element name="Context" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.context.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Context">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.context.**"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Mapping::Subsystem|Model"/>
<dependency toName="Project|spring-data-commons::Layer|Mapping::Subsystem|Model" type="AllowedDependency"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Core"/>
<dependency toName="Project|spring-data-commons::Layer|Core" type="AllowedDependency"/>
</element>
<element name="Core" type="Layer">
<element name="Assignment" type="TypeFilter"/>
<element name="Types" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.util.**" type="WeakTypePattern"/>
<element type="Layer" name="Core">
<element type="TypeFilter" name="Assignment"/>
<element type="Subsystem" name="Types">
<element type="TypeFilter" name="Assignment">
<element type="WeakTypePattern" name="**.util.**"/>
</element>
<stereotype name="Public"/>
</element>
<element name="Annotations" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.annotation.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Annotations">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.annotation.**"/>
</element>
</element>
<element name="Authentication" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.authentication.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Authentication">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.authentication.**"/>
</element>
</element>
<element name="Configuration" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.config.**" type="WeakTypePattern"/>
<element type="Subsystem" name="Configuration">
<element type="TypeFilter" name="Assignment">
<element type="WeakTypePattern" name="**.config.**"/>
</element>
<stereotype name="Unrestricted"/>
</element>
<element name="History" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.history.**" type="WeakTypePattern"/>
<element type="Subsystem" name="History">
<element type="TypeFilter" name="Assignment">
<element type="WeakTypePattern" name="**.history.**"/>
</element>
</element>
<element name="Support" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.support.**" type="WeakTypePattern"/>
<element type="Subsystem" name="Support">
<element type="TypeFilter" name="Assignment">
<element type="WeakTypePattern" name="**.support.**"/>
</element>
</element>
<element name="Auditing" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.auditing.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Auditing">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.auditing.**"/>
</element>
<stereotype name="Unrestricted"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Application"/>
<dependency toName="Project|spring-data-commons::Layer|Application" type="AllowedDependency"/>
</element>
<element name="Application" type="Layer">
<element name="Assignment" type="TypeFilter"/>
<element name="Domain" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.domain.**" type="WeakTypePattern"/>
<element type="Layer" name="Application">
<element type="TypeFilter" name="Assignment"/>
<element type="Subsystem" name="Domain">
<element type="TypeFilter" name="Assignment">
<element type="WeakTypePattern" name="**.domain.**"/>
</element>
</element>
<element name="JAXB" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.domain.jaxb.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="JAXB">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.domain.jaxb.**"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Application::Subsystem|Domain"/>
<dependency type="AllowedDependency" toName="External|External::Subsystem|JAXB"/>
<dependency type="AllowedDependency" toName="External|External::Subsystem|Spring"/>
<dependency toName="Project|spring-data-commons::Layer|Application::Subsystem|Domain" type="AllowedDependency"/>
<dependency toName="External|External::Subsystem|JAXB" type="AllowedDependency"/>
<dependency toName="External|External::Subsystem|Spring" type="AllowedDependency"/>
</element>
<element name="Querydsl" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.querydsl.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Querydsl">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.querydsl.**"/>
</element>
<dependency type="AllowedDependency" toName="Project|spring-data-commons::Layer|Application::Subsystem|Domain"/>
<dependency toName="Project|spring-data-commons::Layer|Application::Subsystem|Domain" type="AllowedDependency"/>
</element>
<element name="Cross store" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.crossstore.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Cross store">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.crossstore.**"/>
</element>
</element>
</element>
</architecture>
<workspace>
<element name="src/main/java" type="JavaRootDirectory">
<element type="JavaRootDirectory" name="src/main/java">
<reference name="Project|spring-data-commons::BuildUnit|spring-data-commons"/>
</element>
<element name="target/classes" type="JavaRootDirectory">
<element type="JavaRootDirectory" name="target/classes">
<reference name="Project|spring-data-commons::BuildUnit|spring-data-commons"/>
</element>
</workspace>
<itemDefinition>
<item type="IgnoreWarningFilterItem">
<property name="AttributeId" value="NoAttachedSourceFile"/>
<property name="Comment" value="Ignore generated classes"/>
<property name="Date" value="2013-01-29 10:28:36"/>
<property name="ElementType" value="Class"/>
<property name="Pattern" value="org.springframework.data.querydsl.QSimpleEntityPathResolverUnitTests_Sample"/>
<property value="NoAttachedSourceFile" name="AttributeId"/>
<property value="Ignore generated classes" name="Comment"/>
<property value="2013-01-29 10:28:36" name="Date"/>
<property value="Class" name="ElementType"/>
<property value="org.springframework.data.querydsl.QSimpleEntityPathResolverUnitTests_Sample" name="Pattern"/>
</item>
<item type="IgnoreWarningFilterItem">
<property name="AttributeId" value="NoAttachedSourceFile"/>
<property name="Comment" value="Ignore generated classes"/>
<property name="Date" value="2013-01-29 10:28:36"/>
<property name="ElementType" value="Class"/>
<property name="Pattern" value="org.springframework.data.querydsl.QUser"/>
<property value="NoAttachedSourceFile" name="AttributeId"/>
<property value="Ignore generated classes" name="Comment"/>
<property value="2013-01-29 10:28:36" name="Date"/>
<property value="Class" name="ElementType"/>
<property value="org.springframework.data.querydsl.QUser" name="Pattern"/>
</item>
<item type="IgnoreWarningFilterItem">
<property name="AttributeId" value="NoAttachedSourceFile"/>
<property name="Comment" value="Ignore generated classes"/>
<property name="Date" value="2013-01-29 10:28:36"/>
<property name="ElementType" value="Class"/>
<property name="Pattern" value="org.springframework.data.querydsl.QSimpleEntityPathResolverUnitTests_NamedUser"/>
<property value="NoAttachedSourceFile" name="AttributeId"/>
<property value="Ignore generated classes" name="Comment"/>
<property value="2013-01-29 10:28:36" name="Date"/>
<property value="Class" name="ElementType"/>
<property value="org.springframework.data.querydsl.QSimpleEntityPathResolverUnitTests_NamedUser" name="Pattern"/>
</item>
</itemDefinition>
<physical>
<element name="spring-data-commons" type="BuildUnit"/>
<element type="BuildUnit" name="spring-data-commons"/>
</physical>
</scope>
<scope name="External" type="External">
<element name="Filter" type="TypeFilter">
<element name="**" type="IncludeTypePattern"/>
<element name="java.io.*" type="ExcludeTypePattern"/>
<element name="java.lang.*" type="ExcludeTypePattern"/>
<element name="java.lang.annotation.*" type="ExcludeTypePattern"/>
<element name="java.lang.ref.*" type="ExcludeTypePattern"/>
<element name="java.math.*" type="ExcludeTypePattern"/>
<element name="java.nio.*" type="ExcludeTypePattern"/>
<element name="java.nio.channels.*" type="ExcludeTypePattern"/>
<element name="java.nio.channels.spi.*" type="ExcludeTypePattern"/>
<element name="java.nio.charset.*" type="ExcludeTypePattern"/>
<element name="java.nio.charset.spi.*" type="ExcludeTypePattern"/>
<element name="java.text.*" type="ExcludeTypePattern"/>
<element name="java.util.*" type="ExcludeTypePattern"/>
<element name="java.util.jar.*" type="ExcludeTypePattern"/>
<element name="java.util.prefs.*" type="ExcludeTypePattern"/>
<element name="java.util.regex.*" type="ExcludeTypePattern"/>
<element name="java.util.zip.*" type="ExcludeTypePattern"/>
<element name="java.util.concurrent.**" type="ExcludeTypePattern"/>
<scope type="External" name="External">
<element type="TypeFilter" name="Filter">
<element type="IncludeTypePattern" name="**"/>
<element type="ExcludeTypePattern" name="java.io.*"/>
<element type="ExcludeTypePattern" name="java.lang.*"/>
<element type="ExcludeTypePattern" name="java.lang.annotation.*"/>
<element type="ExcludeTypePattern" name="java.lang.ref.*"/>
<element type="ExcludeTypePattern" name="java.math.*"/>
<element type="ExcludeTypePattern" name="java.nio.*"/>
<element type="ExcludeTypePattern" name="java.nio.channels.*"/>
<element type="ExcludeTypePattern" name="java.nio.channels.spi.*"/>
<element type="ExcludeTypePattern" name="java.nio.charset.*"/>
<element type="ExcludeTypePattern" name="java.nio.charset.spi.*"/>
<element type="ExcludeTypePattern" name="java.text.*"/>
<element type="ExcludeTypePattern" name="java.util.*"/>
<element type="ExcludeTypePattern" name="java.util.jar.*"/>
<element type="ExcludeTypePattern" name="java.util.prefs.*"/>
<element type="ExcludeTypePattern" name="java.util.regex.*"/>
<element type="ExcludeTypePattern" name="java.util.zip.*"/>
<element type="ExcludeTypePattern" name="java.util.concurrent.**"/>
</element>
<architecture>
<element name="Querydsl" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="com.mysema.query.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Querydsl">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="com.mysema.query.**"/>
</element>
</element>
<element name="Servlet API" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="javax.servlet.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Servlet API">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="javax.servlet.**"/>
</element>
</element>
<element name="Java Beans" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="java.beans.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Java Beans">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="java.beans.**"/>
</element>
</element>
<element name="Reflection" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="java.lang.reflect.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Reflection">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="java.lang.reflect.**"/>
</element>
</element>
<element name="JodaTime" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="**.joda.time.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="JodaTime">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="**.joda.time.**"/>
</element>
</element>
<element name="Spring" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="org.springframework.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Spring">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="org.springframework.**"/>
</element>
</element>
<element name="XML" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="org.w3c.dom.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="XML">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="org.w3c.dom.**"/>
</element>
</element>
<element name="Logging" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="org.slf4j.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Logging">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="org.slf4j.**"/>
</element>
</element>
<element name="AOP" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="org.aopalliance.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="AOP">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="org.aopalliance.**"/>
</element>
</element>
<element name="JavaEE" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="javax.enterprise.**" type="IncludeTypePattern"/>
<element name="javax.inject.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="JavaEE">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="javax.enterprise.**"/>
<element type="IncludeTypePattern" name="javax.inject.**"/>
</element>
</element>
<element name="JAXB" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="javax.xml.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="JAXB">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="javax.xml.**"/>
</element>
</element>
<element name="Jackson" type="Subsystem">
<element name="Assignment" type="TypeFilter">
<element name="org.codehaus.jackson.**" type="IncludeTypePattern"/>
<element type="Subsystem" name="Jackson">
<element type="TypeFilter" name="Assignment">
<element type="IncludeTypePattern" name="org.codehaus.jackson.**"/>
</element>
</element>
</architecture>
</scope>
<scope name="Global" type="Global">
<element name="Configuration" type="Configuration"/>
<element name="Filter" type="TypeFilter">
<element name="**" type="IncludeTypePattern"/>
<scope type="Global" name="Global">
<element type="Configuration" name="Configuration"/>
<element type="TypeFilter" name="Filter">
<element type="IncludeTypePattern" name="**"/>
</element>
<itemDefinition>
<item type="ThresholdItem">
<property name="AttributeId" value="NumberOfNotAssignedTypes"/>
<property name="LowerValue" value=""/>
<property name="NamedElementGroup" value="Project"/>
<property name="UpperValue" value="0"/>
<property value="NumberOfNotAssignedTypes" name="AttributeId"/>
<property value="" name="LowerValue"/>
<property value="Project" name="NamedElementGroup"/>
<property value="0" name="UpperValue"/>
</item>
<item type="ThresholdItem">
<property name="AttributeId" value="NumberOfParameters"/>
<property name="LowerValue" value=""/>
<property name="NamedElementGroup" value="Method"/>
<property name="UpperValue" value="7"/>
<property value="NumberOfParameters" name="AttributeId"/>
<property value="" name="LowerValue"/>
<property value="Method" name="NamedElementGroup"/>
<property value="7" name="UpperValue"/>
</item>
<item type="ThresholdItem">
<property name="AttributeId" value="NumberOfAccessibleTypes"/>
<property name="LowerValue" value=""/>
<property name="NamedElementGroup" value="Namespace"/>
<property name="UpperValue" value="30"/>
<property value="NumberOfAccessibleTypes" name="AttributeId"/>
<property value="" name="LowerValue"/>
<property value="Namespace" name="NamedElementGroup"/>
<property value="30" name="UpperValue"/>
</item>
<item type="ThresholdItem">
<property name="AttributeId" value="NormalizedCumulativeComponentDependency"/>
<property name="LowerValue" value=""/>
<property name="NamedElementGroup" value="Project"/>
<property name="UpperValue" value="7"/>
<property value="NormalizedCumulativeComponentDependency" name="AttributeId"/>
<property value="" name="LowerValue"/>
<property value="Project" name="NamedElementGroup"/>
<property value="7" name="UpperValue"/>
</item>
<item type="ThresholdItem">
<property name="AttributeId" value="LinesOfCode"/>
<property name="LowerValue" value=""/>
<property name="NamedElementGroup" value="SourceFile"/>
<property name="UpperValue" value="700"/>
<property value="LinesOfCode" name="AttributeId"/>
<property value="" name="LowerValue"/>
<property value="SourceFile" name="NamedElementGroup"/>
<property value="700" name="UpperValue"/>
</item>
<item type="ThresholdItem">
<property name="AttributeId" value="NumberOfInternalTypes"/>
<property name="LowerValue" value=""/>
<property name="NamedElementGroup" value="Namespace"/>
<property name="UpperValue" value="50"/>
<property value="NumberOfInternalTypes" name="AttributeId"/>
<property value="" name="LowerValue"/>
<property value="Namespace" name="NamedElementGroup"/>
<property value="50" name="UpperValue"/>
</item>
<item type="ThresholdItem">
<property name="AttributeId" value="NumberOfMethods"/>
<property name="LowerValue" value=""/>
<property name="NamedElementGroup" value="Type"/>
<property name="UpperValue" value="50"/>
<property value="NumberOfMethods" name="AttributeId"/>
<property value="" name="LowerValue"/>
<property value="Type" name="NamedElementGroup"/>
<property value="50" name="UpperValue"/>
</item>
<item type="ThresholdItem">
<property name="AttributeId" value="ModifiedCyclomaticComplexity"/>
<property name="LowerValue" value=""/>
<property name="NamedElementGroup" value="Method"/>
<property name="UpperValue" value="20"/>
<property value="ModifiedCyclomaticComplexity" name="AttributeId"/>
<property value="" name="LowerValue"/>
<property value="Method" name="NamedElementGroup"/>
<property value="20" name="UpperValue"/>
</item>
</itemDefinition>
</scope>
<itemDefinition>
<item type="MoveTypeItem">
<property name="AssignedTo" value=""/>
<property name="Comment" value=""/>
<property name="Date" value="2013-01-29 09:59:22"/>
<property name="Priority" value="Medium"/>
<property name="SourceScope" value="spring-data-commons"/>
<property name="TargetNamespace" value="org.springframework.data.mapping.context"/>
<property name="TargetScope" value="spring-data-commons"/>
<property name="Type" value="org.springframework.data.mapping.model.MappingContextIsNewStrategyFactory"/>
<property value="" name="AssignedTo"/>
<property value="" name="Comment"/>
<property value="2013-01-29 09:59:22" name="Date"/>
<property value="Medium" name="Priority"/>
<property value="spring-data-commons" name="SourceScope"/>
<property value="org.springframework.data.mapping.context" name="TargetNamespace"/>
<property value="spring-data-commons" name="TargetScope"/>
<property value="org.springframework.data.mapping.model.MappingContextIsNewStrategyFactory" name="Type"/>
</item>
</itemDefinition>
<attributeConfiguration type="AttributeConfiguration">
<property name="ExtendedCyclomaticComplexity" value="disabled"/>
<property name="MaxExtendedCyclomaticComplexityPerMethod" value="disabled"/>
<property name="NumberOfWarnings" value="disabled"/>
<property name="AverageCyclomaticComplexityPerMethod" value="disabled"/>
<property name="NumberOfAbstractTypes" value="disabled"/>
<property name="JdkVersion" value="disabled"/>
<property name="NumberOfCyclicDirectories" value="disabled"/>
<property name="CyclomaticComplexity" value="disabled"/>
<property name="NumberOfMethodsWithImplementation" value="disabled"/>
<property name="NumberOfIgnoredWarnings" value="disabled"/>
<property name="NumberOfNamedInterfaces" value="disabled"/>
<property name="NumberOfCyclicProjects" value="disabled"/>
<property name="NumberOfRefactorings" value="disabled"/>
<property name="DependsUpon" value="disabled"/>
<property name="NumberOfVerticalSliceGroups" value="disabled"/>
<property name="DistributedOverPathContainers" value="disabled"/>
<property name="AverageExtendedCyclomaticComplexityPerMethod" value="disabled"/>
<property name="AverageTypeDependency" value="disabled"/>
<property name="NumberOfDuplicateCodeBlocksWarnings" value="disabled"/>
<property name="RelativeAverageTypeDependency" value="disabled"/>
<property name="AverageAccumulatedExtendedCyclomaticComplexityPerType" value="disabled"/>
<property name="NumberOfLayerGroups" value="disabled"/>
<property name="NumberOfIgnoredViolations" value="disabled"/>
<property name="NumberOfFixWarnings" value="disabled"/>
<property name="NumberOfExcludedSourceFiles" value="disabled"/>
<property name="NumberOfVerticalSlices" value="disabled"/>
<property name="AverageAccumulatedCyclomaticComplexityPerType" value="disabled"/>
<property name="Timestamp" value="disabled"/>
<property name="AverageAssertionUsage" value="disabled"/>
<property name="NumberOfCyclicSubsystems" value="disabled"/>
<property name="NumberOfCyclicNamespaces" value="disabled"/>
<property name="NumberOfExcludedTargetFiles" value="disabled"/>
<property name="NumberOfCyclicVerticalSliceGroups" value="disabled"/>
<property name="AccumulatedExtendedCyclomaticComplexity" value="disabled"/>
<property name="NumberOfLayers" value="disabled"/>
<property name="NumberOfCyclicSourceFiles" value="disabled"/>
<property name="MaxAccumulatedCyclomaticComplexityPerType" value="disabled"/>
<property name="MaxAccumulatedExtendedCyclomaticComplexityPerType" value="disabled"/>
<property name="NumberOfCyclicLayerGroups" value="disabled"/>
<property name="NumberOfAssertions" value="disabled"/>
<property name="NumberOfCyclicVerticalSlices" value="disabled"/>
<property name="NormalizedCumulativeTypeDependency" value="disabled"/>
<property name="AccumulatedCyclomaticComplexity" value="disabled"/>
<property name="NumberOfMetricWarnings" value="disabled"/>
<property name="NumberOfNameParts" value="disabled"/>
<property name="NumberOfTasks" value="disabled"/>
<property name="NumberOfWorkspaceWarnings" value="disabled"/>
<property name="NumberOfAbstractMethods" value="disabled"/>
<property name="NumberOfTypesWithImplementation" value="disabled"/>
<property name="Level" value="disabled"/>
<property name="NumberOfSubsystems" value="disabled"/>
<property name="NumberOfCyclicLayers" value="disabled"/>
<property name="MaxCyclomaticComplexityPerMethod" value="disabled"/>
<property value="disabled" name="ExtendedCyclomaticComplexity"/>
<property value="disabled" name="MaxExtendedCyclomaticComplexityPerMethod"/>
<property value="disabled" name="NumberOfWarnings"/>
<property value="disabled" name="AverageCyclomaticComplexityPerMethod"/>
<property value="disabled" name="NumberOfAbstractTypes"/>
<property value="disabled" name="JdkVersion"/>
<property value="disabled" name="NumberOfCyclicDirectories"/>
<property value="disabled" name="CyclomaticComplexity"/>
<property value="disabled" name="NumberOfMethodsWithImplementation"/>
<property value="disabled" name="NumberOfIgnoredWarnings"/>
<property value="disabled" name="NumberOfNamedInterfaces"/>
<property value="disabled" name="NumberOfCyclicProjects"/>
<property value="disabled" name="NumberOfRefactorings"/>
<property value="disabled" name="DependsUpon"/>
<property value="disabled" name="NumberOfVerticalSliceGroups"/>
<property value="disabled" name="DistributedOverPathContainers"/>
<property value="disabled" name="AverageExtendedCyclomaticComplexityPerMethod"/>
<property value="disabled" name="AverageTypeDependency"/>
<property value="disabled" name="NumberOfDuplicateCodeBlocksWarnings"/>
<property value="disabled" name="RelativeAverageTypeDependency"/>
<property value="disabled" name="AverageAccumulatedExtendedCyclomaticComplexityPerType"/>
<property value="disabled" name="NumberOfLayerGroups"/>
<property value="disabled" name="NumberOfIgnoredViolations"/>
<property value="disabled" name="NumberOfFixWarnings"/>
<property value="disabled" name="NumberOfExcludedSourceFiles"/>
<property value="disabled" name="NumberOfVerticalSlices"/>
<property value="disabled" name="AverageAccumulatedCyclomaticComplexityPerType"/>
<property value="disabled" name="Timestamp"/>
<property value="disabled" name="AverageAssertionUsage"/>
<property value="disabled" name="NumberOfCyclicSubsystems"/>
<property value="disabled" name="NumberOfCyclicNamespaces"/>
<property value="disabled" name="NumberOfExcludedTargetFiles"/>
<property value="disabled" name="NumberOfCyclicVerticalSliceGroups"/>
<property value="disabled" name="AccumulatedExtendedCyclomaticComplexity"/>
<property value="disabled" name="NumberOfLayers"/>
<property value="disabled" name="NumberOfCyclicSourceFiles"/>
<property value="disabled" name="MaxAccumulatedCyclomaticComplexityPerType"/>
<property value="disabled" name="MaxAccumulatedExtendedCyclomaticComplexityPerType"/>
<property value="disabled" name="NumberOfCyclicLayerGroups"/>
<property value="disabled" name="NumberOfAssertions"/>
<property value="disabled" name="NumberOfCyclicVerticalSlices"/>
<property value="disabled" name="NormalizedCumulativeTypeDependency"/>
<property value="disabled" name="AccumulatedCyclomaticComplexity"/>
<property value="disabled" name="NumberOfMetricWarnings"/>
<property value="disabled" name="NumberOfNameParts"/>
<property value="disabled" name="NumberOfTasks"/>
<property value="disabled" name="NumberOfWorkspaceWarnings"/>
<property value="disabled" name="NumberOfAbstractMethods"/>
<property value="disabled" name="NumberOfTypesWithImplementation"/>
<property value="disabled" name="Level"/>
<property value="disabled" name="NumberOfSubsystems"/>
<property value="disabled" name="NumberOfCyclicLayers"/>
<property value="disabled" name="MaxCyclomaticComplexityPerMethod"/>
</attributeConfiguration>
</context>

View File

@@ -18,7 +18,7 @@
<properties>
<jackson>1.9.7</jackson>
<springhateoas>0.5.0.BUILD-SNAPSHOT</springhateoas>
<springhateoas>0.6.0.BUILD-SNAPSHOT</springhateoas>
<dist.key>DATACMNS</dist.key>
</properties>

View File

@@ -218,7 +218,7 @@ public class PageableHandlerMethodArgumentResolver implements HandlerMethodArgum
StringBuilder builder = new StringBuilder(prefix);
if (parameter.hasParameterAnnotation(Qualifier.class)) {
if (parameter != null && parameter.hasParameterAnnotation(Qualifier.class)) {
builder.append(parameter.getParameterAnnotation(Qualifier.class).value());
builder.append(qualifierSeparator);
}

View File

@@ -0,0 +1,172 @@
/*
* 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.data.web;
import static org.springframework.web.util.UriComponentsBuilder.*;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.PagedResources.PageMetadata;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceAssembler;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.util.Assert;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
* {@link ResourceAssembler} to easily convert {@link Page} instances into {@link PagedResources}.
*
* @since 1.6
* @author Oliver Gierke
*/
public class PagedResourcesAssembler<T> implements ResourceAssembler<Page<T>, PagedResources<Resource<T>>> {
private final PageableHandlerMethodArgumentResolver pageableResolver;
private final UriComponents baseUri;
/**
* Creates a new {@link PagedResourcesAssembler} using the given {@link PageableHandlerMethodArgumentResolver} and
* base URI. If the former is {@literal null}, a default one will be created. If the latter is {@literal null}, calls
* to {@link #toResource(Page)} will use the current request's URI to build the relevant previous and next links.
*
* @param resolver
* @param baseUri
*/
public PagedResourcesAssembler(PageableHandlerMethodArgumentResolver resolver, UriComponents baseUri) {
this.pageableResolver = resolver == null ? new PageableHandlerMethodArgumentResolver() : resolver;
this.baseUri = baseUri;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.ResourceAssembler#toResource(java.lang.Object)
*/
@Override
public PagedResources<Resource<T>> toResource(Page<T> entity) {
return toResource(entity, new SimplePagedResourceAssembler<T>());
}
/**
* Creates a new {@link PagedResources} by converting the given {@link Page} into a {@link PageMetadata} instance and
* wrapping the contained elements into {@link Resource} instances. Will add pagination links based on the given the
* self link.
*
* @param page must not be {@literal null}.
* @param selfLink must not be {@literal null}.
* @return
*/
public PagedResources<Resource<T>> toResource(Page<T> page, Link selfLink) {
return toResource(page, new SimplePagedResourceAssembler<T>(), selfLink);
}
/**
* Creates a new {@link PagedResources} by converting the given {@link Page} into a {@link PageMetadata} instance and
* using the given {@link ResourceAssembler} to turn elements of the {@link Page} into resources.
*
* @param page must not be {@literal null}.
* @param assembler must not be {@literal null}.
* @return
*/
public <R extends ResourceSupport> PagedResources<R> toResource(Page<T> page, ResourceAssembler<T, R> assembler) {
return createResource(page, assembler, null);
}
/**
* Creates a new {@link PagedResources} by converting the given {@link Page} into a {@link PageMetadata} instance and
* using the given {@link ResourceAssembler} to turn elements of the {@link Page} into resources. Will add pagination
* links based on the given the self link.
*
* @param page must not be {@literal null}.
* @param assembler must not be {@literal null}.
* @param link must not be {@literal null}.
* @return
*/
public <R extends ResourceSupport> PagedResources<R> toResource(Page<T> page, ResourceAssembler<T, R> assembler,
Link link) {
Assert.notNull(link, "Link must not be null!");
return createResource(page, assembler, link);
}
private <S, R extends ResourceSupport> PagedResources<R> createResource(Page<S> page,
ResourceAssembler<S, R> assembler, Link link) {
Assert.notNull(page, "Page must not be null!");
Assert.notNull(assembler, "ResourceAssembler must not be null!");
List<R> resources = new ArrayList<R>(page.getNumberOfElements());
for (S element : page) {
resources.add(assembler.toResource(element));
}
PagedResources<R> pagedResources = new PagedResources<R>(resources, asPageMetadata(page));
return addPaginationLinks(pagedResources, page, link == null ? getDefaultUriString().toUriString() : link.getHref());
}
private UriComponents getDefaultUriString() {
return baseUri == null ? ServletUriComponentsBuilder.fromCurrentRequest().build() : baseUri;
}
private <R extends ResourceSupport> PagedResources<R> addPaginationLinks(PagedResources<R> resources, Page<?> page,
String uri) {
if (page.hasNextPage()) {
foo(resources, page.nextPageable(), uri, Link.REL_NEXT);
}
if (page.hasPreviousPage()) {
foo(resources, page.previousPageable(), uri, Link.REL_PREVIOUS);
}
return resources;
}
private void foo(PagedResources<?> resources, Pageable pageable, String uri, String rel) {
UriComponentsBuilder builder = fromUriString(uri);
pageableResolver.enhance(builder, null, pageable);
resources.add(new Link(builder.build().toUriString(), rel));
}
/**
* Creates a new {@link PageMetadata} instance from the given {@link Page}.
*
* @param page must not be {@literal null}.
* @return
*/
private static <T> PageMetadata asPageMetadata(Page<T> page) {
Assert.notNull(page, "Page must not be null!");
return new PageMetadata(page.getSize(), page.getNumber(), page.getTotalElements(), page.getTotalPages());
}
private static class SimplePagedResourceAssembler<T> implements ResourceAssembler<T, Resource<T>> {
@Override
public Resource<T> toResource(T entity) {
return new Resource<T>(entity);
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.data.web;
import org.springframework.core.MethodParameter;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.MethodLinkBuilderFactory;
import org.springframework.hateoas.mvc.ControllerLinkBuilderFactory;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
* {@link HandlerMethodArgumentResolver} to allow injection of {@link PagedResourcesAssembler} into Spring MVC
* controller methods.
*
* @since 1.6
* @author Oliver Gierke
*/
public class PagedResourcesAssemblerArgumentResolver implements HandlerMethodArgumentResolver {
private final PageableHandlerMethodArgumentResolver resolver;
private final MethodLinkBuilderFactory<?> linkBuilderFactory;
/**
* Creates a new {@link PagedResourcesAssemblerArgumentResolver} using the given
* {@link PageableHandlerMethodArgumentResolver} and {@link MethodLinkBuilderFactory}.
*
* @param resolver can be {@literal null}.
* @param linkBuilderFactory can be {@literal null}, will be defaulted to a {@link ControllerLinkBuilderFactory}.
*/
public PagedResourcesAssemblerArgumentResolver(PageableHandlerMethodArgumentResolver resolver,
MethodLinkBuilderFactory<?> linkBuilderFactory) {
this.resolver = resolver;
this.linkBuilderFactory = linkBuilderFactory == null ? new ControllerLinkBuilderFactory() : linkBuilderFactory;
}
/*
* (non-Javadoc)
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return PagedResourcesAssembler.class.equals(parameter.getParameterType());
}
/*
* (non-Javadoc)
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory)
*/
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Link linkToMethod = linkBuilderFactory.linkTo(parameter.getMethod(), new Object[0]).withSelfRel();
UriComponents fromUriString = UriComponentsBuilder.fromUriString(linkToMethod.getHref()).build();
return new PagedResourcesAssembler<Object>(resolver, fromUriString);
}
}

View File

@@ -218,7 +218,7 @@ public class SortHandlerMethodArgumentResolver implements HandlerMethodArgumentR
StringBuilder builder = new StringBuilder();
if (parameter.hasParameterAnnotation(Qualifier.class)) {
if (parameter != null && parameter.hasParameterAnnotation(Qualifier.class)) {
builder.append(parameter.getParameterAnnotation(Qualifier.class).value()).append(qualifierDelimiter);
}

View File

@@ -0,0 +1,98 @@
/*
* 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.data.web.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.data.web.PagedResourcesAssemblerArgumentResolver;
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* Annotation to automatically register the following beans for usage with Spring MVC.
* <ul>
* <li>{@link DomainClassConverter} - to allow usage of domain types managed by Spring Data repositories as controller
* method arguments bound with {@link PathVariable} or {@link RequestParam}.</li>
* <li>{@link PageableHandlerMethodArgumentResolver} - to allow injection of {@link Pageable} instances into controller
* methods automatically created from request parameters.</li>
* <li>{@link SortHandlerMethodArgumentResolver} - to allow injection of {@link Sort} instances into controller methods
* automatically created from request parameters.</li>
* </ul>
* If Spring HATEOAS is present on the classpath we will additionall register the following beans:
* <ul>
* <li>{@link PagedResourcesAssembler} - for injection into web components</li>
* <li>{@link PagedResourcesAssemblerArgumentResolver} - for injection of {@link PagedResourcesAssembler} into
* controller methods</li>
* <ul>
*
* @since 1.6
* @see SpringDataWebConfiguration
* @see HateoasAwareSpringDataWebConfiguration
* @author Oliver Gierke
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Inherited
@Import(EnableSpringDataWebSupport.SpringDataWebConfigurationImportSelector.class)
public @interface EnableSpringDataWebSupport {
/**
* Import selector to import the appropriate configuration class depending on whether Spring HATEOAS is present on the
* classpath. We need to register the HATEOAS specific class first as apparently only the first class implementing
* {@link WebMvcConfigurationSupport} gets callbacks invoked (see https://jira.springsource.org/browse/SPR-10565).
*
* @author Oliver Gierke
*/
class SpringDataWebConfigurationImportSelector implements ImportSelector {
// Don't make final to allow test cases faking this to false
private static boolean HATEOAS_PRESENT = ClassUtils.isPresent("org.springframework.hateoas.Link", null);
/*
* (non-Javadoc)
* @see org.springframework.context.annotation.ImportSelector#selectImports(org.springframework.core.type.AnnotationMetadata)
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> configs = new ArrayList<String>();
if (HATEOAS_PRESENT) {
configs.add(HateoasAwareSpringDataWebConfiguration.class.getName());
}
configs.add(SpringDataWebConfiguration.class.getName());
return configs.toArray(new String[configs.size()]);
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.data.web.config;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.data.web.PagedResourcesAssemblerArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* JavaConfig class to register {@link PagedResourcesAssembler} and {@link PagedResourcesAssemblerArgumentResolver}.
*
* @since 1.6
* @author Oliver Gierke
*/
@Configuration
class HateoasAwareSpringDataWebConfiguration extends WebMvcConfigurationSupport {
@Autowired
SpringDataWebConfiguration config;
@Bean
public PagedResourcesAssembler<?> pagedResourcesAssembler() {
return new PagedResourcesAssembler<Object>(config.pageableResolver(), null);
}
@Bean
public PagedResourcesAssemblerArgumentResolver pagedResourcesAssemblerArgumentResolver() {
return new PagedResourcesAssemblerArgumentResolver(config.pageableResolver(), null);
}
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addArgumentResolvers(java.util.List)
*/
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
config.addArgumentResolvers(argumentResolvers);
argumentResolvers.add(pagedResourcesAssemblerArgumentResolver());
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.data.web.config;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* Configuration class to register {@link PageableHandlerMethodArgumentResolver},
* {@link SortHandlerMethodArgumentResolver} and {@link DomainClassConverter}.
*
* @since 1.6
* @author Oliver Gierke
*/
@Configuration
class SpringDataWebConfiguration extends WebMvcConfigurationSupport {
@Bean
public PageableHandlerMethodArgumentResolver pageableResolver() {
return new PageableHandlerMethodArgumentResolver();
}
@Bean
public SortHandlerMethodArgumentResolver sortResolver() {
return new SortHandlerMethodArgumentResolver();
}
@Bean
public DomainClassConverter<FormattingConversionService> mvcDomainClassConverter() {
return new DomainClassConverter<FormattingConversionService>(mvcConversionService());
}
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter#addArgumentResolvers(java.util.List)
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(pageableResolver());
argumentResolvers.add(sortResolver());
}
}

View File

@@ -0,0 +1,20 @@
/*
* 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.
*/
/**
*
* @author Oliver Gierke
*/
package org.springframework.data.web.config;

View File

@@ -0,0 +1,111 @@
/*
* 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.data.web;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.Resource;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Unit tests for {@link PagedResourcesAssembler}.
*
* @author Oliver Gierke
*/
public class PagedResourcesAssemblerUnitTests {
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
@Before
public void setUp() {
WebTestUtils.initWebTest();
}
@Test
public void addsNextLinkForFirstPage() {
PagedResourcesAssembler<Person> assembler = new PagedResourcesAssembler<Person>(resolver, null);
PagedResources<Resource<Person>> resources = assembler.toResource(createPage(0));
assertThat(resources.getLink(Link.REL_PREVIOUS), is(nullValue()));
assertThat(resources.getLink(Link.REL_NEXT), is(notNullValue()));
}
@Test
public void addsPreviousAndNextLinksForMiddlePage() {
PagedResourcesAssembler<Person> assembler = new PagedResourcesAssembler<Person>(resolver, null);
PagedResources<Resource<Person>> resources = assembler.toResource(createPage(1));
assertThat(resources.getLink(Link.REL_PREVIOUS), is(notNullValue()));
assertThat(resources.getLink(Link.REL_NEXT), is(notNullValue()));
}
@Test
public void addsPreviousLinkForLastPage() {
PagedResourcesAssembler<Person> assembler = new PagedResourcesAssembler<Person>(resolver, null);
PagedResources<Resource<Person>> resources = assembler.toResource(createPage(2));
assertThat(resources.getLink(Link.REL_PREVIOUS), is(notNullValue()));
assertThat(resources.getLink(Link.REL_NEXT), is(nullValue()));
}
@Test
public void usesBaseUriIfConfigured() {
UriComponents baseUri = UriComponentsBuilder.fromUriString("http://foo:9090").build();
PagedResourcesAssembler<Person> assembler = new PagedResourcesAssembler<Person>(resolver, baseUri);
PagedResources<Resource<Person>> resources = assembler.toResource(createPage(1));
assertThat(resources.getLink(Link.REL_PREVIOUS).getHref(), startsWith(baseUri.toUriString()));
assertThat(resources.getLink(Link.REL_NEXT).getHref(), startsWith(baseUri.toUriString()));
}
@Test
public void usesCustomLinkProvided() {
Link link = new Link("http://foo:9090", "rel");
PagedResourcesAssembler<Person> assembler = new PagedResourcesAssembler<Person>(resolver, null);
PagedResources<Resource<Person>> resources = assembler.toResource(createPage(1), link);
assertThat(resources.getLink(Link.REL_PREVIOUS).getHref(), startsWith(link.getHref()));
assertThat(resources.getLink(Link.REL_NEXT).getHref(), startsWith(link.getHref()));
}
private static Page<Person> createPage(int index) {
PageRequest request = new PageRequest(index, 1);
return new PageImpl<Person>(Arrays.asList(new Person()), request, 3);
}
static class Person {
}
}

View File

@@ -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.data.web;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
/**
* Helper methods for web integration testing.
*
* @author Oliver Gierke
*/
public class WebTestUtils {
/**
* Initializes web tests. Will register a {@link MockHttpServletRequest} for the current thread.
*/
public static void initWebTest() {
MockHttpServletRequest request = new MockHttpServletRequest();
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes);
}
/**
* Creates a {@link WebApplicationContext} from the given configuration classes.
*
* @param configClasses
* @return
*/
public static WebApplicationContext createApplicationContext(Class<?>... configClasses) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(new MockServletContext());
for (Class<?> configClass : configClasses) {
context.register(configClass);
}
context.refresh();
return context;
}
}

View File

@@ -0,0 +1,121 @@
/*
* 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.data.web.config;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.data.web.PagedResourcesAssemblerArgumentResolver;
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
import org.springframework.data.web.WebTestUtils;
import org.springframework.data.web.config.EnableSpringDataWebSupport.SpringDataWebConfigurationImportSelector;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
/**
* Integration tests for {@link EnableSpringDataWebSupport}.
*
* @author Oliver Gierke
* @see DATACMNS-330
*/
public class EnableSpringDataWebSupportIntegrationTests {
@Configuration
@EnableSpringDataWebSupport
static class SampleConfig {
}
@After
public void tearDown() {
reEnableHateoas();
}
@Test
public void registersBasicBeanDefinitions() throws Exception {
ApplicationContext context = WebTestUtils.createApplicationContext(SampleConfig.class);
List<String> names = Arrays.asList(context.getBeanDefinitionNames());
assertThat(names, hasItems("pageableResolver", "sortResolver", "mvcDomainClassConverter"));
assertResolversRegistered(context, SortHandlerMethodArgumentResolver.class,
PageableHandlerMethodArgumentResolver.class);
}
@Test
public void registersHateoasSpecificBeanDefinitions() throws Exception {
ApplicationContext context = WebTestUtils.createApplicationContext(SampleConfig.class);
List<String> names = Arrays.asList(context.getBeanDefinitionNames());
assertThat(names, hasItems("pagedResourcesAssembler", "pagedResourcesAssemblerArgumentResolver"));
assertResolversRegistered(context, PagedResourcesAssemblerArgumentResolver.class);
}
@Test
public void doesNotRegisterHateoasSpecificComponentsIfHateoasNotPresent() throws Exception {
hideHateoas();
ApplicationContext context = WebTestUtils.createApplicationContext(SampleConfig.class);
List<String> names = Arrays.asList(context.getBeanDefinitionNames());
assertThat(names, not(hasItems("pagedResourcesAssembler", "pagedResourcesAssemblerArgumentResolver")));
}
@SuppressWarnings("unchecked")
private static void assertResolversRegistered(ApplicationContext context, Class<?>... resolverTypes) {
RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class);
assertThat(adapter, is(notNullValue()));
List<HandlerMethodArgumentResolver> resolvers = adapter.getCustomArgumentResolvers();
List<Matcher<Object>> resolverMatchers = new ArrayList<Matcher<Object>>(resolverTypes.length);
for (Class<?> resolverType : resolverTypes) {
resolverMatchers.add(instanceOf(resolverType));
}
assertThat(resolvers, hasItems(resolverMatchers.toArray(new Matcher[resolverMatchers.size()])));
}
private static void hideHateoas() throws Exception {
Field field = ReflectionUtils.findField(SpringDataWebConfigurationImportSelector.class, "HATEOAS_PRESENT");
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, null, false);
}
private static void reEnableHateoas() {
Field field = ReflectionUtils.findField(SpringDataWebConfigurationImportSelector.class, "HATEOAS_PRESENT");
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, null, true);
}
}

View File

@@ -0,0 +1,96 @@
/*
* 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.data.web.config;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.data.web.WebTestUtils;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;
/**
* Integration tests for {@link PagedResourcesAssembler}.
*
* @author Oliver Gierke
*/
public class PageableResourcesAssemblerIntegrationTests {
@Configuration
@EnableSpringDataWebSupport
static class Config {
@Bean
public SampleController controller() {
return new SampleController();
}
}
@Before
public void setUp() {
WebTestUtils.initWebTest();
}
@Test
public void injectsPagedResourcesAssembler() {
WebApplicationContext context = WebTestUtils.createApplicationContext(Config.class);
SampleController controller = context.getBean(SampleController.class);
assertThat(controller.assembler, is(notNullValue()));
PagedResources<Resource<Person>> resources = controller.sample(new PageRequest(1, 1));
assertThat(resources.getLink(Link.REL_PREVIOUS), is(notNullValue()));
assertThat(resources.getLink(Link.REL_NEXT), is(notNullValue()));
}
@Controller
static class SampleController {
@Autowired
PagedResourcesAssembler<Person> assembler;
@RequestMapping("/persons")
PagedResources<Resource<Person>> sample(Pageable pageable) {
Page<Person> page = new PageImpl<Person>(Arrays.asList(new Person()), pageable, pageable.getOffset()
+ pageable.getPageSize() + 1);
return assembler.toResource(page);
}
}
static class Person {
}
}

View File

@@ -17,8 +17,7 @@ Import-Template:
org.springframework.context.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional,
org.springframework.dao.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional,
org.springframework.expression.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional,
org.springframework.expression.spel.standard.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional,
org.springframework.expression.spel.support.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional,
org.springframework.format.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional,
org.springframework.hateoas.*;version="${springhateoas:[=.=.=,+1.1.0)}";resolution:=optional,
org.springframework.oxm.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional,
org.springframework.transaction.*;version="${spring30:[=.=.=,+1.1.0)}";resolution:=optional,