diff --git a/build.gradle b/build.gradle index 6b022b0e0..b25ec7519 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ ext { // Spring springVersion = "3.1.3.RELEASE" - //springVersion = "3.2.0.RELEASE" + spring32Version = "3.2.1.RELEASE" hateoasVersion = "0.4.0.BUILD-SNAPSHOT" springPluginVersion = "0.8.0.BUILD-SNAPSHOT" springSecurityVersion = "3.1.3.RELEASE" @@ -76,13 +76,22 @@ configure(allprojects) { compile "org.slf4j:slf4j-api:$slf4jVersion" runtime "org.slf4j:jcl-over-slf4j:$slf4jVersion" + // Spring + compile("org.springframework:spring-aop:$springVersion") { force = true } + compile("org.springframework:spring-beans:$springVersion") { force = true } + compile("org.springframework:spring-context:$springVersion") { force = true } + compile("org.springframework:spring-core:$springVersion") { force = true } + compile("org.springframework:spring-expression:$springVersion") { force = true } + compile("org.springframework:spring-web:$springVersion") { force = true } + runtime "cglib:cglib-nodep:$cglibVersion" + // Testing testCompile "junit:junit-dep:$junitVersion" testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion" testCompile "org.jmock:jmock-junit4:$jmockVersion" testCompile "org.jmock:jmock-legacy:$jmockVersion" - testCompile "org.springframework:spring-test:$springVersion" - testRuntime "org.springframework:spring-context-support:$springVersion" + testCompile("org.springframework:spring-test:$springVersion") { force = true } + testRuntime("org.springframework:spring-context-support:$springVersion") { force = true } testRuntime "ch.qos.logback:logback-classic:$logbackVersion" } } @@ -124,11 +133,6 @@ project("spring-data-rest-core") { // Google Guava compile "com.google.guava:guava:$guavaVersion" - // Spring - compile("org.springframework:spring-core:$springVersion") { force = true } - compile("org.springframework:spring-web:$springVersion") { force = true } - runtime "cglib:cglib-nodep:$cglibVersion" - // Spring HATEOAS compile("org.springframework.hateoas:spring-hateoas:$hateoasVersion") { exclude module: "spring-webmvc" @@ -205,6 +209,20 @@ project("spring-data-rest-webmvc") { testCompile "org.eclipse.jetty:jetty-servlet:$jettyVersion" testCompile "org.eclipse.jetty:jetty-webapp:$jettyVersion" testCompile "org.mozilla:rhino:1.7R4" + // Spring Data + testCompile "org.springframework.data:spring-data-jpa:$sdJpaVersion" + testCompile "org.springframework.data:spring-data-mongodb:$sdMongoVersion" + testCompile "org.springframework.data:spring-data-gemfire:$sdGemfireVersion" + // JODA + testCompile "joda-time:joda-time:$jodaVersion" + // Jackson + testCompile "com.fasterxml.jackson.datatype:jackson-datatype-joda:$jacksonVersion" + testCompile "com.fasterxml.jackson.datatype:jackson-datatype-hibernate4:$jacksonVersion" + // Hibernate + testRuntime "org.hibernate:hibernate-entitymanager:$hibernateVersion" + testRuntime "org.hibernate:hibernate-validator:$hibernateValidatorVersion" + // HSQL + testRuntime "org.hsqldb:hsqldb:2.2.8" } } @@ -227,8 +245,8 @@ project("spring-data-rest-example") { compile "org.springframework.data:spring-data-gemfire:$sdGemfireVersion" // Spring Security - compile "org.springframework.security:spring-security-config:$springSecurityVersion" - compile "org.springframework.security:spring-security-web:$springSecurityVersion" + //compile "org.springframework.security:spring-security-config:$springSecurityVersion" + //compile "org.springframework.security:spring-security-web:$springSecurityVersion" // JODA compile "joda-time:joda-time:$jodaVersion" diff --git a/spring-data-rest-example/src/main/java/org/springframework/data/rest/example/RestExporterExampleRestConfig.java b/spring-data-rest-example/src/main/java/org/springframework/data/rest/example/RestExporterExampleRestConfig.java index e4e43dbf8..cfaddb733 100644 --- a/spring-data-rest-example/src/main/java/org/springframework/data/rest/example/RestExporterExampleRestConfig.java +++ b/spring-data-rest-example/src/main/java/org/springframework/data/rest/example/RestExporterExampleRestConfig.java @@ -1,10 +1,14 @@ package org.springframework.data.rest.example; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; import org.springframework.data.rest.config.RepositoryRestConfiguration; import org.springframework.data.rest.example.jpa.Person; import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.Resource; +import org.springframework.hateoas.ResourceProcessor; /** * @author Jon Brisbin @@ -23,13 +27,17 @@ public class RestExporterExampleRestConfig extends RepositoryRestMvcConfiguratio .setPath("siblings"); } - // @Bean public ResourceProcessor> globalResourceProcessor() { - // return new ResourceProcessor>() { - // @Override public Resource process(Resource resource) { - // resource.add(new Link("href", "rel")); - // return resource; - // } - // }; - // } + @Bean public ResourceProcessor> personResourceProcessor() { + return new ResourceProcessor>() { + RepositoryRestConfiguration config = config(); + + @Override public Resource process(Resource resource) { + System.out.println("processing " + resource); + System.out.println("url: " + config.getBaseUri().toString()); + resource.add(new Link("http://host:port/path", "myrel")); + return resource; + } + }; + } } diff --git a/spring-data-rest-example/src/main/java/org/springframework/data/rest/example/RestExporterWebInitializer.java b/spring-data-rest-example/src/main/java/org/springframework/data/rest/example/RestExporterWebInitializer.java index f5584b8a3..d1b18428d 100644 --- a/spring-data-rest-example/src/main/java/org/springframework/data/rest/example/RestExporterWebInitializer.java +++ b/spring-data-rest-example/src/main/java/org/springframework/data/rest/example/RestExporterWebInitializer.java @@ -29,12 +29,12 @@ public class RestExporterWebInitializer implements WebApplicationInitializer { ); servletContext.addListener(new ContextLoaderListener(rootCtx)); - servletContext.addFilter("springSecurity", DelegatingFilterProxy.class); - servletContext.getFilterRegistration("springSecurity").addMappingForUrlPatterns( - EnumSet.of(DispatcherType.REQUEST), - false, - "/*" - ); + // servletContext.addFilter("springSecurity", DelegatingFilterProxy.class); + // servletContext.getFilterRegistration("springSecurity").addMappingForUrlPatterns( + // EnumSet.of(DispatcherType.REQUEST), + // false, + // "/*" + // ); AnnotationConfigWebApplicationContext webCtx = new AnnotationConfigWebApplicationContext(); webCtx.register(RestExporterExampleRestConfig.class); diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/BaseUriAwareResource.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/BaseUriAwareResource.java index be39a50f9..1aafcc28e 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/BaseUriAwareResource.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/BaseUriAwareResource.java @@ -42,7 +42,8 @@ public class BaseUriAwareResource extends Resource { String baseUriStr = baseUri.toString(); List links = new ArrayList(); for(Link l : super.getLinks()) { - if(!l.getHref().startsWith(baseUriStr)) { + if(!l.getHref().startsWith(baseUriStr) + && !l.getHref().startsWith("http")) { links.add(new Link(buildUri(baseUri, l.getHref()).toString(), l.getRel())); } else { links.add(l); @@ -56,7 +57,8 @@ public class BaseUriAwareResource extends Resource { if(null == l) { return null; } - if(!l.getHref().startsWith(baseUri.toString())) { + if(!l.getHref().startsWith(baseUri.toString()) + && !l.getHref().startsWith("http")) { return new Link(buildUri(baseUri, l.getHref()).toString(), l.getRel()); } else { return l; diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/PersistentEntityResourceProcessor.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/PersistentEntityResourceProcessor.java new file mode 100644 index 000000000..fe82fc662 --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/PersistentEntityResourceProcessor.java @@ -0,0 +1,63 @@ +package org.springframework.data.rest.repository.context; + +import static org.springframework.util.ClassUtils.*; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.repository.support.Repositories; +import org.springframework.data.rest.repository.PersistentEntityResource; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; +import org.springframework.hateoas.Resource; +import org.springframework.hateoas.ResourceProcessor; + +/** + * @author Jon Brisbin + */ +public class PersistentEntityResourceProcessor implements ResourceProcessor> { + + private final List resourceProcessors = new ArrayList(); + + @Autowired + public PersistentEntityResourceProcessor(Repositories repositories, + List>> resourceProcessors) { + if(null != resourceProcessors) { + for(ResourceProcessor> rp : resourceProcessors) { + TypeInformation typeInfo = ClassTypeInformation.from(rp.getClass()); + TypeInformation domainType = typeInfo.getTypeArguments().get(0); + if(null != repositories.getPersistentEntity(domainType.getType())) { + this.resourceProcessors.add(new DomainTypeResourceProcessor(domainType.getType(), rp)); + } + } + } + } + + @Override public PersistentEntityResource process(PersistentEntityResource resource) { + Object content = resource.getContent(); + if(null == content) { + return resource; + } + + Class domainType = content.getClass(); + for(DomainTypeResourceProcessor rp : resourceProcessors) { + if(isAssignable(domainType, rp.domainType)) { + rp.resourceProcessor.process(resource); + } + } + + return resource; + } + + private static class DomainTypeResourceProcessor { + final Class domainType; + final ResourceProcessor> resourceProcessor; + + private DomainTypeResourceProcessor(Class domainType, ResourceProcessor> resourceProcessor) { + this.domainType = domainType; + this.resourceProcessor = resourceProcessor; + } + } + +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUriMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUriMethodArgumentResolver.java index 3e1af82c0..0240895a0 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUriMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BaseUriMethodArgumentResolver.java @@ -21,6 +21,13 @@ public class BaseUriMethodArgumentResolver implements HandlerMethodArgumentResol @Autowired private RepositoryRestConfiguration config; + public BaseUriMethodArgumentResolver() { + } + + public BaseUriMethodArgumentResolver(RepositoryRestConfiguration config) { + this.config = config; + } + @Override public boolean supportsParameter(MethodParameter parameter) { return (null != parameter.getParameterAnnotation(BaseURI.class) && parameter.getParameterType() == URI.class); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PagingAndSortingMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PagingAndSortingMethodArgumentResolver.java index 083ea071a..869deec73 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PagingAndSortingMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PagingAndSortingMethodArgumentResolver.java @@ -31,6 +31,13 @@ public class PagingAndSortingMethodArgumentResolver implements HandlerMethodArgu @Autowired private RepositoryRestConfiguration config; + public PagingAndSortingMethodArgumentResolver() { + } + + public PagingAndSortingMethodArgumentResolver(RepositoryRestConfiguration config) { + this.config = config; + } + @Override public boolean supportsParameter(MethodParameter parameter) { return ClassUtils.isAssignable(parameter.getParameterType(), PagingAndSorting.class); } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResourceProcessorReturnValueHandler.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResourceProcessorReturnValueHandler.java new file mode 100644 index 000000000..5ed58b421 --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResourceProcessorReturnValueHandler.java @@ -0,0 +1,133 @@ +package org.springframework.data.rest.webmvc; + +import static org.springframework.data.util.ClassTypeInformation.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.springframework.core.MethodParameter; +import org.springframework.data.rest.webmvc.support.JsonpResponse; +import org.springframework.data.util.TypeInformation; +import org.springframework.hateoas.Resource; +import org.springframework.hateoas.ResourceProcessor; +import org.springframework.hateoas.Resources; +import org.springframework.http.ResponseEntity; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.method.support.ModelAndViewContainer; + +/** + * @author Jon Brisbin + */ +public class PersistentEntityResourceProcessorReturnValueHandler implements HandlerMethodReturnValueHandler { + + private final HandlerMethodReturnValueHandler delegate; + private final List processors = new ArrayList(); + + @SuppressWarnings({"unchecked"}) + public PersistentEntityResourceProcessorReturnValueHandler(HandlerMethodReturnValueHandler delegate, + List> processors) { + this.delegate = delegate; + for(ResourceProcessor rp : processors) { + TypeInformation componentType = from(rp.getClass()) + .getSuperTypeInformation(ResourceProcessor.class) + .getComponentType(); + if(Resources.class.isAssignableFrom(componentType.getType())) { + this.processors.add(new ResourcesProcessorWrapper(componentType.getComponentType().getType(), + (ResourceProcessor>)rp)); + } else if(Resource.class.isAssignableFrom(componentType.getType())) { + this.processors.add(new ResourceProcessorWrapper(componentType.getComponentType().getType(), + (ResourceProcessor>)rp)); + } + } + } + + @Override public boolean supportsReturnType(MethodParameter returnType) { + Class controller = returnType.getMethod().getDeclaringClass(); + return RepositoryController.class.isAssignableFrom(controller) + || RepositoryEntityController.class.isAssignableFrom(controller) + || RepositoryPropertyReferenceController.class.isAssignableFrom(controller) + || RepositorySearchController.class.isAssignableFrom(controller); + } + + @SuppressWarnings({"unchecked"}) + @Override + public void handleReturnValue(Object returnValue, + MethodParameter methodParam, + ModelAndViewContainer mavContainer, + NativeWebRequest nativeRequest) throws Exception { + Class returnValueType = returnValue.getClass(); + Class entityType = null; + + if(JsonpResponse.class.isAssignableFrom(returnValueType)) { + entityType = ((JsonpResponse)returnValue).getResponseEntity().getBody().getClass(); + } else if(ResponseEntity.class.isAssignableFrom(returnValueType)) { + entityType = ((ResponseEntity)returnValue).getBody().getClass(); + } else if(Resources.class.isAssignableFrom(returnValueType)) { + Collection c = ((Resources)returnValue).getContent(); + Object o; + if(null != c && !c.isEmpty() && null != (o = c.iterator().next())) { + entityType = o.getClass(); + } else { + if(delegate.supportsReturnType(methodParam)) { + delegate.handleReturnValue(returnValue, + methodParam, + mavContainer, + nativeRequest); + } + return; + } + } else if(Resource.class.isAssignableFrom(returnValueType)) { + entityType = ((Resource)returnValue).getContent().getClass(); + } + + for(Wrapper w : processors) { + if(w.type.isAssignableFrom(entityType)) { + if(ResourcesProcessorWrapper.class.isAssignableFrom(w.getClass()) + && Resources.class.isAssignableFrom(returnValueType)) { + ((ResourcesProcessorWrapper)w).processor.process((Resources)returnValue); + } else if(ResourceProcessorWrapper.class.isAssignableFrom(w.getClass()) + && Resource.class.isAssignableFrom(returnValueType)) { + ((ResourceProcessorWrapper)w).processor.process((Resource)returnValue); + } + } + } + + if(delegate.supportsReturnType(methodParam)) { + delegate.handleReturnValue(returnValue, + methodParam, + mavContainer, + nativeRequest); + } + } + + static class Wrapper { + Class type; + TypeInformation typeInfo; + + Wrapper(Class type) { + this.type = type; + this.typeInfo = from(type); + } + } + + static class ResourcesProcessorWrapper extends Wrapper { + ResourceProcessor> processor; + + ResourcesProcessorWrapper(Class type, ResourceProcessor> processor) { + super(type); + this.processor = processor; + } + } + + static class ResourceProcessorWrapper extends Wrapper { + ResourceProcessor> processor; + + ResourceProcessorWrapper(Class type, ResourceProcessor> processor) { + super(type); + this.processor = processor; + } + } + +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceProcessorInvokingHandlerAdapter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceProcessorInvokingHandlerAdapter.java index f68f9b178..ad8ebaa50 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceProcessorInvokingHandlerAdapter.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceProcessorInvokingHandlerAdapter.java @@ -30,60 +30,64 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl /** * Special {@link RequestMappingHandlerAdapter} that tweaks the {@link HandlerMethodReturnValueHandlerComposite} to be - * proxied by a {@link ResourceProcessorHandlerMethodReturnValueHandler} which will invoke the {@link ResourceProcessor}s + * proxied by a {@link ResourceProcessorHandlerMethodReturnValueHandler} which will invoke the {@link + * ResourceProcessor}s * found in the application context and eventually delegate to the originally configured * {@link HandlerMethodReturnValueHandler}. - *

- * This is a separate component as it might make sense to deploy it in a standalone SpringMVC application to enable post + *

+ * This is a separate component as it might make sense to deploy it in a standalone SpringMVC application to enable + * post * processing. It would actually make most sense in Spring HATEOAS project. - * + * * @author Oliver Gierke */ public class ResourceProcessorInvokingHandlerAdapter extends RequestMappingHandlerAdapter { - @Autowired(required = false) - private List> resourcesProcessors = new ArrayList>(); + @Autowired(required = false) + private List> resourcesProcessors = new ArrayList>(); - /** - * Empty constructor to setup a {@link ResourceProcessorInvokingHandlerAdapter}. - */ - public ResourceProcessorInvokingHandlerAdapter() { + /** + * Empty constructor to setup a {@link ResourceProcessorInvokingHandlerAdapter}. + */ + public ResourceProcessorInvokingHandlerAdapter() { - } + } - /** - * Copy constructor to copy configuration of {@link HttpMessageConverter}s, {@link WebBindingInitializer}, custom - * {@link HandlerMethodArgumentResolver}s and custom {@link HandlerMethodReturnValueHandler}s. - * - * @param original must not be {@literal null}. - */ - public ResourceProcessorInvokingHandlerAdapter(RequestMappingHandlerAdapter original) { - - Assert.notNull(original); + /** + * Copy constructor to copy configuration of {@link HttpMessageConverter}s, {@link WebBindingInitializer}, custom + * {@link HandlerMethodArgumentResolver}s and custom {@link HandlerMethodReturnValueHandler}s. + * + * @param original + * must not be {@literal null}. + */ + public ResourceProcessorInvokingHandlerAdapter(RequestMappingHandlerAdapter original) { - setMessageConverters(original.getMessageConverters()); - setWebBindingInitializer(original.getWebBindingInitializer()); - setCustomArgumentResolvers(original.getCustomArgumentResolvers()); - setCustomReturnValueHandlers(original.getCustomReturnValueHandlers()); - } + Assert.notNull(original); - /* - * (non-Javadoc) - * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet() - */ - @Override - public void afterPropertiesSet() { + setMessageConverters(original.getMessageConverters()); + setWebBindingInitializer(original.getWebBindingInitializer()); + setCustomArgumentResolvers(original.getCustomArgumentResolvers()); + setCustomReturnValueHandlers(original.getCustomReturnValueHandlers()); + } - super.afterPropertiesSet(); + /* + * (non-Javadoc) + * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet() + */ + @Override + public void afterPropertiesSet() { - // Retrieve actual handlers to use as delegate - HandlerMethodReturnValueHandlerComposite oldHandlers = getReturnValueHandlers(); + super.afterPropertiesSet(); - // Set up ResourceProcessingHandlerMethodResolver to delegate to originally configured ones - List newHandlers = new ArrayList(); - newHandlers.add(new ResourceProcessorHandlerMethodReturnValueHandler(oldHandlers, resourcesProcessors)); + // Retrieve actual handlers to use as delegate + HandlerMethodReturnValueHandlerComposite oldHandlers = getReturnValueHandlers(); - // Configure the new handler to be used - this.setReturnValueHandlers(newHandlers); - } + // Set up ResourceProcessingHandlerMethodResolver to delegate to originally configured ones + List newHandlers = new ArrayList(); + newHandlers.add(new PersistentEntityResourceProcessorReturnValueHandler(oldHandlers, resourcesProcessors)); + newHandlers.add(new ResourceProcessorHandlerMethodReturnValueHandler(oldHandlers, resourcesProcessors)); + + // Configure the new handler to be used + this.setReturnValueHandlers(newHandlers); + } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractJMockTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractJMockTests.java new file mode 100644 index 000000000..3d23ef560 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractJMockTests.java @@ -0,0 +1,20 @@ +package org.springframework.data.rest.webmvc; + +import org.jmock.integration.junit4.JMock; +import org.jmock.integration.junit4.JUnit4Mockery; +import org.jmock.lib.legacy.ClassImposteriser; +import org.junit.runner.RunWith; + +/** + * Abstract base classes for JUnit tests that use JMock. + * + * @author Jon Brisbin + */ +@RunWith(JMock.class) +public abstract class AbstractJMockTests { + + protected JUnit4Mockery context = new JUnit4Mockery() {{ + setImposteriser(ClassImposteriser.INSTANCE); + }}; + +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/CustomMethodArgumentResolverTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/CustomMethodArgumentResolverTests.java new file mode 100644 index 000000000..4b53b0c86 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/CustomMethodArgumentResolverTests.java @@ -0,0 +1,106 @@ +package org.springframework.data.rest.webmvc; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +import java.net.URI; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.MethodParameter; +import org.springframework.data.rest.config.RepositoryRestConfiguration; +import org.springframework.data.rest.webmvc.annotation.BaseURI; +import org.springframework.data.rest.webmvc.support.PagingAndSorting; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; + +/** + * @author Jon Brisbin + */ +public class CustomMethodArgumentResolverTests extends AbstractJMockTests { + + static final MethodParameter BASE_URI; + static final MethodParameter PAGE_SORT; + + static { + try { + BASE_URI = MethodParameter.forMethodOrConstructor( + Methods.class.getDeclaredMethod("baseUri", URI.class), + 0 + ); + PAGE_SORT = MethodParameter.forMethodOrConstructor( + Methods.class.getDeclaredMethod("pagingAndSorting", PagingAndSorting.class), + 0 + ); + } catch(NoSuchMethodException e) { + throw new IllegalStateException(e); + } + } + + private final RepositoryRestConfiguration config = + new RepositoryRestConfiguration() + .setBaseUri(URI.create("http://localhost:8080")); + private final BaseUriMethodArgumentResolver baseUriResolver = + new BaseUriMethodArgumentResolver(config); + private final PagingAndSortingMethodArgumentResolver pageSortResolver = + new PagingAndSortingMethodArgumentResolver(config); + private ModelAndViewContainer mavContainer; + private WebDataBinderFactory webDataBinderFactory; + + @Before + public void setup() { + mavContainer = new ModelAndViewContainer(); + webDataBinderFactory = context.mock(WebDataBinderFactory.class); + } + + @Test + public void baseUriMethodArgumentResolver() throws Exception { + assertThat("Finds @BaseURI-annotated java.net.URI parameter", + baseUriResolver.supportsParameter(BASE_URI), + is(true)); + + // Resolve the base URI + URI baseUri = (URI)baseUriResolver.resolveArgument( + BASE_URI, + mavContainer, + new ServletWebRequest(Requests.ROOT_REQUEST), + webDataBinderFactory + ); + + assertThat("Base URI should be 'http://localhost:8080'", + baseUri.toString(), + is("http://localhost:8080")); + } + + @Test + public void pagingAndSortingMethodArgumentResolver() throws Exception { + assertThat("Finds PagingAndSorting parameter", + pageSortResolver.supportsParameter(PAGE_SORT), + is(true)); + + // Resolve Page and Sort information + PagingAndSorting pageSort = (PagingAndSorting)pageSortResolver.resolveArgument( + PAGE_SORT, + mavContainer, + new ServletWebRequest(Requests.PAGE_REQUEST), + webDataBinderFactory + ); + + assertThat("Finds page parameter value", + pageSort.getPageNumber(), + is(1)); + assertThat("Finds limit parameter value", + pageSort.getPageSize(), + is(5)); + } + + static class Methods { + void baseUri(@BaseURI URI baseUri) { + } + + void pagingAndSorting(PagingAndSorting pageSort) { + } + } + +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryRestMvcTestConfig.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryRestMvcTestConfig.java new file mode 100644 index 000000000..7b108bba0 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryRestMvcTestConfig.java @@ -0,0 +1,20 @@ +package org.springframework.data.rest.webmvc; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration; +import org.springframework.data.rest.webmvc.gemfire.GemfireRepositoryConfig; +import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig; +import org.springframework.data.rest.webmvc.mongodb.MongoDbRepositoryConfig; + +/** + * @author Jon Brisbin + */ +@Configuration +@Import({ + JpaRepositoryConfig.class, + MongoDbRepositoryConfig.class, + GemfireRepositoryConfig.class + }) +public class RepositoryRestMvcTestConfig extends RepositoryRestMvcConfiguration { +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/Requests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/Requests.java new file mode 100644 index 000000000..229d3eac7 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/Requests.java @@ -0,0 +1,21 @@ +package org.springframework.data.rest.webmvc; + +import org.springframework.mock.web.MockHttpServletRequest; + +/** + * @author Jon Brisbin + */ +public abstract class Requests { + + public static MockHttpServletRequest ROOT_REQUEST = new MockHttpServletRequest("GET", "http://localhost:8080/"); + public static MockHttpServletRequest PAGE_REQUEST = new MockHttpServletRequest("GET", "http://localhost:8080/"); + + static { + PAGE_REQUEST.setParameter("page", "2"); + PAGE_REQUEST.setParameter("limit", "5"); + } + + private Requests() { + } + +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/GemfireRepositoryConfig.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/GemfireRepositoryConfig.java new file mode 100644 index 000000000..8f5f9c08a --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/GemfireRepositoryConfig.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.gemfire; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportResource; +import org.springframework.data.gemfire.repository.config.EnableGemfireRepositories; + +/** + * Spring JavaConfig configuration class to setup a Spring container and infrastructure components. + * + * @author Oliver Gierke + * @author David Turanski + */ +@Configuration +@ImportResource("classpath:META-INF/spring/cache-config.xml") +@EnableGemfireRepositories +public class GemfireRepositoryConfig { + +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/AbstractPersistentEntity.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/AbstractPersistentEntity.java new file mode 100644 index 000000000..8eb8f6b6f --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/AbstractPersistentEntity.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.gemfire.core; + +import org.springframework.data.annotation.Id; + +/** + * Base class for persistent classes. + * + * @author Oliver Gierke + * @author David Turanski + */ +public class AbstractPersistentEntity { + + @Id + private final Long id; + + /** + * Returns the identifier of the entity. + * + * @return the id + */ + public Long getId() { + return id; + } + + protected AbstractPersistentEntity(Long id) { + this.id = id; + } + + protected AbstractPersistentEntity() { + this.id = null; + } + + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + + if (this.id == null || obj == null || !(this.getClass().equals(obj.getClass()))) { + return false; + } + + AbstractPersistentEntity that = (AbstractPersistentEntity) obj; + + return this.id.equals(that.getId()); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return id == null ? 0 : id.hashCode(); + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/Address.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/Address.java new file mode 100644 index 000000000..2a4306d95 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/Address.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.gemfire.core; + +import org.springframework.util.Assert; + +/** + * An address. + * + * @author Oliver Gierke + */ +public class Address { + + private final String street, city, country; + + /** + * Creates a new {@link Address} from the given street, city and country. + * + * @param street must not be {@literal null} or empty. + * @param city must not be {@literal null} or empty. + * @param country must not be {@literal null} or empty. + */ + public Address(String street, String city, String country) { + + Assert.hasText(street, "Street must not be null or empty!"); + Assert.hasText(city, "City must not be null or empty!"); + Assert.hasText(country, "Country must not be null or empty!"); + + this.street = street; + this.city = city; + this.country = country; + } + + /** + * Returns a copy of the current {@link Address} instance which is a new entity in terms of persistence. + * + * @return + */ + public Address getCopy() { + return new Address(this.street, this.city, this.country); + } + + /** + * Returns the street. + * + * @return + */ + public String getStreet() { + return street; + } + + /** + * Returns the city. + * + * @return + */ + public String getCity() { + return city; + } + + /** + * Returns the country. + * + * @return + */ + public String getCountry() { + return country; + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/Customer.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/Customer.java new file mode 100644 index 000000000..36f07f8f9 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/Customer.java @@ -0,0 +1,138 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.gemfire.core; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.data.gemfire.mapping.Region; +import org.springframework.util.Assert; + + +/** + * A customer. + * + * @author Oliver Gierke + * @author David Turanski + */ +@Region +public class Customer extends AbstractPersistentEntity { + private EmailAddress emailAddress; + private String firstname, lastname; + private Set

addresses = new HashSet
(); + + /** + * Creates a new {@link Customer} from the given parameters. + * + * @param id + * the unique id; + * @param emailAddress + * must not be {@literal null} or empty. + * @param firstname + * must not be {@literal null} or empty. + * @param lastname + * must not be {@literal null} or empty. + */ + public Customer(Long id, EmailAddress emailAddress, String firstname, String lastname) { + super(id); + Assert.hasText(firstname); + Assert.hasText(lastname); + Assert.notNull(emailAddress); + + this.firstname = firstname; + this.lastname = lastname; + this.emailAddress = emailAddress; + } + + protected Customer() { + } + + /** + * Adds the given {@link Address} to the {@link Customer}. + * + * @param address + * must not be {@literal null}. + */ + public void add(Address address) { + + Assert.notNull(address); + this.addresses.add(address); + } + + /** + * Returns the firstname of the {@link Customer}. + * + * @return + */ + public String getFirstname() { + return firstname; + } + + /** + * Sets the firstname of the {@link Customer}. + * + * @param firstname + */ + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + /** + * Returns the lastname of the {@link Customer}. + * + * @return + */ + public String getLastname() { + return lastname; + } + + /** + * Sets the lastname of the {@link Customer}. + * + * @param lastname + */ + public void setLastname(String lastname) { + this.lastname = lastname; + } + + /** + * Returns the {@link EmailAddress} of the {@link Customer}. + * + * @return + */ + public EmailAddress getEmailAddress() { + return emailAddress; + } + + /** + * Sets the emailAddress of the {@link Customer}. + * + * @param emailAddress + */ + public void setEmailAddress(EmailAddress emailAddress) { + this.emailAddress = emailAddress; + } + + /** + * Return the {@link Customer}'s addresses. + * + * @return + */ + public Set
getAddresses() { + return Collections.unmodifiableSet(addresses); + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/CustomerRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/CustomerRepository.java new file mode 100644 index 000000000..9e02d14ca --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/CustomerRepository.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.gemfire.core; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; + +/** + * Repository interface to access {@link Customer}s. + * + * @author Oliver Gierke + * @author David Turanski + */ + +public interface CustomerRepository extends CrudRepository { + + /** + * Finds all {@link Customer}s with the given lastname. + * + * @param lastname + * + * @return + */ + List findByLastname(@Param("lastname") String lastname); + + /** + * Finds the Customer with the given {@link EmailAddress}. + * + * @param emailAddress + * + * @return + */ + Customer findByEmailAddress(@Param("email") EmailAddress emailAddress); + +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/EmailAddress.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/EmailAddress.java new file mode 100644 index 000000000..692fd4629 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/EmailAddress.java @@ -0,0 +1,126 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.gemfire.core; + +import java.util.regex.Pattern; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Value object to represent email addresses. + * + * @author Oliver Gierke + */ +@JsonSerialize(using = ToStringSerializer.class) +public final class EmailAddress { + + private static final String EMAIL_REGEX = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; + private static final Pattern PATTERN = Pattern.compile(EMAIL_REGEX); + private final String value; + + /** + * Creates a new {@link EmailAddress} from the given {@link String} representation. + * + * @param emailAddress + * must not be {@literal null} or empty. + */ + @JsonCreator + public EmailAddress(String emailAddress) { + Assert.isTrue(isValid(emailAddress), "Invalid email address!"); + this.value = emailAddress; + } + + /** + * Returns whether the given {@link String} is a valid {@link EmailAddress} which means you can safely instantiate + * the + * class. + * + * @param candidate + * + * @return + */ + public static boolean isValid(String candidate) { + return candidate == null ? false : PATTERN.matcher(candidate).matches(); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return value; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + + if(this == obj) { + return true; + } + + if(!(obj instanceof EmailAddress)) { + return false; + } + + EmailAddress that = (EmailAddress)obj; + return this.value.equals(that.value); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return value.hashCode(); + } + + @Component + static class EmailAddressToStringConverter implements Converter { + + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) + */ + @Override + public String convert(EmailAddress source) { + return source == null ? null : source.value; + } + } + + @Component + static class StringToEmailAddressConverter implements Converter { + + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) + */ + public EmailAddress convert(String source) { + return StringUtils.hasText(source) ? new EmailAddress(source) : null; + } + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/Product.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/Product.java new file mode 100644 index 000000000..6c033c765 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/Product.java @@ -0,0 +1,133 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.gemfire.core; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.gemfire.mapping.Region; +import org.springframework.util.Assert; + + +/** + * A product. + * + * @author Oliver Gierke + * @author David Turanski + */ +@Region +public class Product extends AbstractPersistentEntity { + + private String name, description; + private BigDecimal price; + private Map attributes = new HashMap(); + + /** + * Creates a new {@link Product} with the given name. + * + * @param id + * a unique Id + * @param name + * must not be {@literal null} or empty. + * @param price + * must not be {@literal null} or less than or equal to zero. + */ + public Product(Long id, String name, BigDecimal price) { + this(id, name, price, null); + } + + /** + * Creates a new {@link Product} from the given name and description. + * + * @param id + * a unique Id + * @param name + * must not be {@literal null} or empty. + * @param price + * must not be {@literal null} or less than or equal to zero. + * @param description + */ + @PersistenceConstructor + public Product(Long id, String name, BigDecimal price, String description) { + super(id); + Assert.hasText(name, "Name must not be null or empty!"); + Assert.isTrue(BigDecimal.ZERO.compareTo(price) < 0, "Price must be greater than zero!"); + + this.name = name; + this.price = price; + this.description = description; + } + + protected Product() { + } + + /** + * Sets the attribute with the given name to the given value. + * + * @param name + * must not be {@literal null} or empty. + * @param value + */ + public void setAttribute(String name, String value) { + + Assert.hasText(name); + + if(value == null) { + this.attributes.remove(value); + } else { + this.attributes.put(name, value); + } + } + + /** + * Returns the {@link Product}'s name. + * + * @return + */ + public String getName() { + return name; + } + + /** + * Returns the {@link Product}'s description. + * + * @return + */ + public String getDescription() { + return description; + } + + /** + * Returns all the custom attributes of the {@link Product}. + * + * @return + */ + public Map getAttributes() { + return Collections.unmodifiableMap(attributes); + } + + /** + * Returns the price of the {@link Product}. + * + * @return + */ + public BigDecimal getPrice() { + return price; + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/ProductRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/ProductRepository.java new file mode 100644 index 000000000..383bc667c --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/core/ProductRepository.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.gemfire.core; + +import java.util.List; + +import org.springframework.data.gemfire.repository.Query; +import org.springframework.data.repository.CrudRepository; + + +/** + * Repository interface to access {@link Product}s. + * + * @author Oliver Gierke + * @author David TuranskiGem + */ +public interface ProductRepository extends CrudRepository { + + /** + * Returns a list of {@link Product}s having a description which contains the given snippet. + * @param the search string + * @return + */ + + List findByDescriptionContaining(String description); + + /** + * Returns all {@link Product}s having the given attribute value. + * @param attribute + * @param value + * @return + */ + @Query("SELECT * FROM /Product where attributes[$1] = $2") + List findByAttributes(String key, String value); + + List findByName(String name); +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/order/LineItem.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/order/LineItem.java new file mode 100644 index 000000000..9d582fe8a --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/order/LineItem.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.gemfire.order; + +import java.math.BigDecimal; + +import org.springframework.data.rest.webmvc.gemfire.core.Product; +import org.springframework.util.Assert; + +/** + * @author Oliver Gierke + */ +public class LineItem { + + private BigDecimal price; + private int amount; + private Long productId; + + /** + * Creates a new {@link LineItem} for the given {@link Product}. + * + * @param product + * must not be {@literal null}. + */ + public LineItem(Product product) { + this(product, 1); + } + + /** + * Creates a new {@link LineItem} for the given {@link Product} and amount. + * + * @param product + * must not be {@literal null}. + * @param amount + */ + public LineItem(Product product, int amount) { + Assert.notNull(product, "The given Product must not be null!"); + Assert.isTrue(amount > 0, "The amount of Products to be bought must be greater than 0!"); + + this.productId = product.getId(); + this.amount = amount; + this.price = product.getPrice(); + } + + protected LineItem() { + } + + /** + * Returns the id of the {@link Product} the {@link LineItem} refers to. + * + * @return + */ + public Long getProductId() { + return productId; + } + + /** + * Returns the amount of {@link Product}s to be ordered. + * + * @return + */ + public int getAmount() { + return amount; + } + + /** + * Returns the price a single unit of the {@link LineItem}'s product. + * + * @return the price + */ + public BigDecimal getUnitPrice() { + return price; + } + + /** + * Returns the total for the {@link LineItem}. + * + * @return + */ + public BigDecimal getTotal() { + return price.multiply(BigDecimal.valueOf(amount)); + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/order/Order.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/order/Order.java new file mode 100644 index 000000000..e5000fd07 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/order/Order.java @@ -0,0 +1,123 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.gemfire.order; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.data.gemfire.mapping.Region; +import org.springframework.data.rest.webmvc.gemfire.core.AbstractPersistentEntity; +import org.springframework.data.rest.webmvc.gemfire.core.Address; +import org.springframework.util.Assert; + +/** + * @author Oliver Gierke + * @author David Turanski + */ +@Region +public class Order extends AbstractPersistentEntity { + + private Long customerId; + private Address billingAddress; + private Address shippingAddress; + private Set lineItems = new HashSet(); + + /** + * Creates a new {@link Order} for the given {@link org.springframework.data.rest.webmvc.gemfire.core.Customer}. + * + * @param id + * order ID + * @param customerId + * must not be {@literal null}. + * @param shippingAddress + * must not be {@literal null}. + */ + public Order(Long id, Long customerId, Address shippingAddress) { + super(id); + Assert.notNull(customerId); + Assert.notNull(shippingAddress); + + this.customerId = customerId; + this.shippingAddress = shippingAddress; + } + + protected Order() { + } + + /** + * Adds the given {@link LineItem} to the {@link Order}. + * + * @param lineItem + */ + public void add(LineItem lineItem) { + this.lineItems.add(lineItem); + } + + /** + * Returns the id of the {@link org.springframework.data.rest.webmvc.gemfire.core.Customer} who placed the {@link + * Order}. + * + * @return + */ + public Long getCustomerId() { + return customerId; + } + + /** + * Returns the billing {@link Address} for this order. + * + * @return + */ + public Address getBillingAddress() { + return billingAddress != null ? billingAddress : shippingAddress; + } + + /** + * Returns the shipping {@link Address} for this order; + * + * @return + */ + public Address getShippingAddress() { + return shippingAddress; + } + + /** + * Returns all {@link LineItem}s currently belonging to the {@link Order}. + * + * @return + */ + public Set getLineItems() { + return Collections.unmodifiableSet(lineItems); + } + + /** + * Returns the total of the {@link Order}. + * + * @return + */ + public BigDecimal getTotal() { + + BigDecimal total = BigDecimal.ZERO; + + for(LineItem item : lineItems) { + total = total.add(item.getTotal()); + } + + return total; + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/order/OrderRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/order/OrderRepository.java new file mode 100644 index 000000000..9360673f7 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/gemfire/order/OrderRepository.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.gemfire.order; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +/** + * @author Oliver Gierke + * @author David Turanski + */ +public interface OrderRepository extends CrudRepository { + List findByCustomerId(Long customerId); +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java new file mode 100644 index 000000000..0a22b8ee4 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaRepositoryConfig.java @@ -0,0 +1,60 @@ +package org.springframework.data.rest.webmvc.jpa; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.orm.jpa.JpaDialect; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.Database; +import org.springframework.orm.jpa.vendor.HibernateJpaDialect; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + * @author Jon Brisbin + */ +@Configuration +@ComponentScan(basePackageClasses = JpaRepositoryConfig.class) +@EnableJpaRepositories +@EnableTransactionManagement +public class JpaRepositoryConfig { + + @Bean public DataSource dataSource() { + EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); + return builder.setType(EmbeddedDatabaseType.HSQL).build(); + } + + @Bean public EntityManagerFactory entityManagerFactory() { + HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); + vendorAdapter.setDatabase(Database.HSQL); + vendorAdapter.setGenerateDdl(true); + + LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); + factory.setJpaVendorAdapter(vendorAdapter); + factory.setPackagesToScan(getClass().getPackage().getName()); + factory.setDataSource(dataSource()); + + factory.afterPropertiesSet(); + + return factory.getObject(); + } + + @Bean public JpaDialect jpaDialect() { + return new HibernateJpaDialect(); + } + + @Bean public PlatformTransactionManager transactionManager() { + JpaTransactionManager txManager = new JpaTransactionManager(); + txManager.setEntityManagerFactory(entityManagerFactory()); + return txManager; + } + +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/Person.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/Person.java new file mode 100644 index 000000000..352fdc9b9 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/Person.java @@ -0,0 +1,106 @@ +package org.springframework.data.rest.webmvc.jpa; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.PrePersist; +import javax.validation.constraints.NotNull; + +import org.springframework.data.rest.repository.annotation.Description; + +/** + * An entity that represents a person. + * + * @author Jon Brisbin + */ +@Entity +public class Person { + + private Long id; + @Description("A person's first name") + private String firstName; + @Description("A person's last name") + private String lastName; + @Description("A person's siblings") + private List siblings = Collections.emptyList(); + private Person father; + @Description("Timestamp this person object was created") + private Date created; + + public Person() { + } + + public Person(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + @Id @GeneratedValue public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + @NotNull + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Person addSibling(Person p) { + if(siblings == Collections.EMPTY_LIST) { + siblings = new ArrayList(); + } + siblings.add(p); + return this; + } + + @ManyToMany public List getSiblings() { + return siblings; + } + + public void setSiblings(List siblings) { + this.siblings = siblings; + } + + @ManyToOne public Person getFather() { + return father; + } + + public void setFather(Person father) { + this.father = father; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + } + + @PrePersist + private void prePersist() { + this.created = Calendar.getInstance().getTime(); + } + +} \ No newline at end of file diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/PersonLoader.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/PersonLoader.java new file mode 100644 index 000000000..9a84ef3c3 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/PersonLoader.java @@ -0,0 +1,31 @@ +package org.springframework.data.rest.webmvc.jpa; + +import java.util.Arrays; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @author Jon Brisbin + */ +@Component +public class PersonLoader implements InitializingBean { + + @Autowired + PersonRepository people; + + @Override public void afterPropertiesSet() throws Exception { + Person billyBob = people.save(new Person("Billy Bob", "Thornton")); + + Person john = new Person("John", "Doe"); + Person jane = new Person("Jane", "Doe"); + john.addSibling(jane); + john.setFather(billyBob); + jane.addSibling(john); + jane.setFather(billyBob); + + people.save(Arrays.asList(john, jane)); + } + +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/PersonRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/PersonRepository.java new file mode 100644 index 000000000..2f0fb2e9e --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/PersonRepository.java @@ -0,0 +1,36 @@ +package org.springframework.data.rest.webmvc.jpa; + +import java.util.Date; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.convert.ISO8601DateConverter; +import org.springframework.data.rest.repository.annotation.ConvertWith; +import org.springframework.data.rest.repository.annotation.RestResource; + +/** + * A repository to manage {@link Person}s. + * + * @author Jon Brisbin + */ +@RestResource(rel = "people", path = "people") +public interface PersonRepository extends PagingAndSortingRepository { + + @RestResource(rel = "firstname", path = "firstname") + public Page findByFirstName(@Param("firstName") String firstName, Pageable pageable); + + public Person findFirstPersonByFirstName(@Param("firstName") String firstName); + + public Page findByCreatedGreaterThan(@Param("date") Date date, Pageable pageable); + + @Query("select p from Person p where p.created > :date") + public Page findByCreatedUsingISO8601Date(@Param("date") + @ConvertWith( + ISO8601DateConverter.class) + Date date, + Pageable pageable); + +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/MongoDbRepositoryConfig.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/MongoDbRepositoryConfig.java new file mode 100644 index 000000000..ccb5b7d4f --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/MongoDbRepositoryConfig.java @@ -0,0 +1,30 @@ +package org.springframework.data.rest.webmvc.mongodb; + +import java.net.UnknownHostException; + +import com.mongodb.Mongo; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoDbFactory; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +/** + * @author Jon Brisbin + */ +@Configuration +@ComponentScan(basePackageClasses = MongoDbRepositoryConfig.class) +@EnableMongoRepositories +public class MongoDbRepositoryConfig { + + @Bean public MongoDbFactory mongoDbFactory() throws UnknownHostException { + return new SimpleMongoDbFactory(new Mongo("localhost"), "spring-data-rest-example"); + } + + @Bean public MongoTemplate mongoTemplate() throws UnknownHostException { + return new MongoTemplate(mongoDbFactory()); + } + +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/Profile.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/Profile.java new file mode 100644 index 000000000..dc5931e4c --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/Profile.java @@ -0,0 +1,44 @@ +package org.springframework.data.rest.webmvc.mongodb; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +/** + * @author Jon Brisbin + */ +@Document +public class Profile { + + @Id + private String id; + private Long person; + private String type; + + public String getId() { + return id; + } + + public Profile setId(String id) { + this.id = id; + return this; + } + + public Long getPerson() { + return person; + } + + public Profile setPerson(Long person) { + this.person = person; + return this; + } + + public String getType() { + return type; + } + + public Profile setType(String type) { + this.type = type; + return this; + } + +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/ProfileRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/ProfileRepository.java new file mode 100644 index 000000000..c9f09e133 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/ProfileRepository.java @@ -0,0 +1,9 @@ +package org.springframework.data.rest.webmvc.mongodb; + +import org.springframework.data.repository.CrudRepository; + +/** + * @author Jon Brisbin + */ +public interface ProfileRepository extends CrudRepository { +} diff --git a/spring-data-rest-webmvc/src/test/resources/META-INF/persistence.xml b/spring-data-rest-webmvc/src/test/resources/META-INF/persistence.xml new file mode 100644 index 000000000..a92748d1c --- /dev/null +++ b/spring-data-rest-webmvc/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-data-rest-webmvc/src/test/resources/META-INF/spring/cache-config.xml b/spring-data-rest-webmvc/src/test/resources/META-INF/spring/cache-config.xml new file mode 100644 index 000000000..a991aebfc --- /dev/null +++ b/spring-data-rest-webmvc/src/test/resources/META-INF/spring/cache-config.xml @@ -0,0 +1,14 @@ + + + + + + + + + +