DATAJPA-19 - Added Hades extensions module.

Added DomainClassPropertyEditor and DomainClassConverter to automatically bind domain classes to Spring MVC controller methods. Same applies to PageableArgumentResolver. Polished up some generics.

Introduced RepositoryFactoryInformation interface being implemented by RepositoryFactorySupport that allows the extension components to get access to the raw factories, access EntityInformation of it and then find out about the repository interface to lookup the actual repository instance.
This commit is contained in:
Oliver Gierke
2011-03-11 15:59:15 +01:00
parent dd6e56d013
commit 80359c4d07
24 changed files with 1829 additions and 22 deletions

View File

@@ -12,6 +12,7 @@
<module>spring-data-commons-parent</module>
<module>spring-data-commons-core</module>
<module>spring-data-commons-aspects</module>
<module>spring-data-commons-extensions</module>
</modules>
<developers>

View File

@@ -89,7 +89,7 @@ public class QueryMethod {
}
public EntityMetadata<?> getEntityMetadata() {
public EntityMetadata<?> getEntityInformation() {
return new EntityMetadata() {

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.repository.support;
import java.io.Serializable;
import org.springframework.util.Assert;
@@ -24,8 +26,8 @@ import org.springframework.util.Assert;
*
* @author Oliver Gierke
*/
public abstract class AbstractEntityInformation<T> implements
EntityInformation<T> {
public abstract class AbstractEntityInformation<T, ID extends Serializable> implements
EntityInformation<T, ID> {
private final Class<T> domainClass;

View File

@@ -15,13 +15,15 @@
*/
package org.springframework.data.repository.support;
import java.io.Serializable;
/**
* Extension of {@link EntityMetadata} to add functionality to query information
* of entity instances.
*
* @author Oliver Gierke
*/
public interface EntityInformation<T> extends EntityMetadata<T> {
public interface EntityInformation<T, ID extends Serializable> extends EntityMetadata<T> {
/**
* Returns whether the given entity is considered to be new.
@@ -38,5 +40,12 @@ public interface EntityInformation<T> extends EntityMetadata<T> {
* @param entity must never be {@literal null}
* @return
*/
Object getId(T entity);
ID getId(T entity);
/**
* Returns the type of the id of the entity.
*
* @return
*/
Class<ID> getIdType();
}

View File

@@ -15,6 +15,9 @@
*/
package org.springframework.data.repository.support;
import java.io.Serializable;
import org.springframework.core.GenericTypeResolver;
import org.springframework.data.domain.Persistable;
@@ -25,18 +28,21 @@ import org.springframework.data.domain.Persistable;
*
* @author Oliver Gierke
*/
@SuppressWarnings("rawtypes")
public class PersistableEntityInformation<T extends Persistable> extends
AbstractEntityInformation<T> {
public class PersistableEntityInformation<T extends Persistable<ID>, ID extends Serializable> extends
AbstractEntityInformation<T, ID> {
private Class<ID> idClass;
/**
* Creates a new {@link PersistableEntityInformation}.
*
* @param domainClass
*/
@SuppressWarnings("unchecked")
public PersistableEntityInformation(Class<T> domainClass) {
super(domainClass);
this.idClass = (Class<ID>) GenericTypeResolver.resolveTypeArgument(domainClass, Persistable.class);
}
@@ -61,8 +67,15 @@ public class PersistableEntityInformation<T extends Persistable> extends
* org.springframework.data.repository.support.IdAware#getId(java.lang.Object
* )
*/
public Object getId(T entity) {
public ID getId(T entity) {
return entity.getId();
}
/* (non-Javadoc)
* @see org.springframework.data.repository.support.EntityInformation#getIdType()
*/
public Class<ID> getIdType() {
return this.idClass;
}
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.repository.support;
import java.io.Serializable;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Required;
@@ -31,8 +33,8 @@ import org.springframework.util.Assert;
* @author Oliver Gierke
* @param <T> the type of the repository
*/
public abstract class RepositoryFactoryBeanSupport<T extends Repository<?, ?>>
implements FactoryBean<T>, InitializingBean {
public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>, S, ID extends Serializable>
implements InitializingBean, RepositoryFactoryInformation<S, ID>, FactoryBean<T> {
private RepositoryFactorySupport factory;
@@ -75,6 +77,25 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<?, ?>>
this.customImplementation = customImplementation;
}
/* (non-Javadoc)
* @see org.springframework.data.repository.support.EntityMetadataProvider#getEntityMetadata()
*/
public EntityInformation<S, ID> getEntityInformation() {
RepositoryMetadata repositoryMetadata = factory.getRepositoryMetadata(repositoryInterface);
return (EntityInformation<S, ID>) factory.getEntityInformation(repositoryMetadata.getDomainClass());
}
/* (non-Javadoc)
* @see org.springframework.data.repository.support.RepositoryFactoryInformation#getRepositoryInterface()
*/
@Override
public Class<? extends T> getRepositoryInterface() {
return repositoryInterface;
}
/*
* (non-Javadoc)

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2011 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.repository.support;
import java.io.Serializable;
import org.springframework.data.repository.Repository;
/**
* Interface for components that can provide {@link EntityInformation} this
* interface
*
* @author Oliver Gierke
*/
public interface RepositoryFactoryInformation<T, ID extends Serializable> {
/**
* Returns {@link EntityInformation} the repository factory is using.
*
* @return
*/
EntityInformation<T, ID> getEntityInformation();
/**
* Returns the interface of the {@link Repository} the factory will create.
*
* @return
*/
Class<? extends Repository<T, ID>> getRepositoryInterface();
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.repository.support;
import static org.springframework.util.ReflectionUtils.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
@@ -124,8 +125,7 @@ public abstract class RepositoryFactorySupport {
Object customImplementation) {
RepositoryMetadata metadata =
new DefaultRepositoryMetadata(repositoryInterface,
getRepositoryBaseClass(repositoryInterface));
getRepositoryMetadata(repositoryInterface);
validate(metadata, customImplementation);
@@ -145,6 +145,29 @@ public abstract class RepositoryFactorySupport {
return (T) result.getProxy();
}
/**
* Returns the {@link RepositoryMetadata} for the given repository interface.
*
* @param repositoryInterface
* @return
*/
protected RepositoryMetadata getRepositoryMetadata(Class<?> repositoryInterface) {
return new DefaultRepositoryMetadata(repositoryInterface, getRepositoryBaseClass(repositoryInterface));
}
/**
* Returns the {@link EntityInformation} for the given domain class.
*
* @param <T> the entity type
* @param <ID> the id type
* @param domainClass
* @return
*/
public abstract <T, ID extends Serializable> EntityInformation<T, ID> getEntityInformation(Class<T> domainClass);
/**

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.repository.support;
import java.io.Serializable;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ListableBeanFactory;
@@ -32,8 +34,8 @@ import org.springframework.util.Assert;
*
* @author Oliver Gierke
*/
public abstract class TransactionalRepositoryFactoryBeanSupport<T extends Repository<?, ?>>
extends RepositoryFactoryBeanSupport<T> implements BeanFactoryAware {
public abstract class TransactionalRepositoryFactoryBeanSupport<T extends Repository<S, ID>, S, ID extends Serializable>
extends RepositoryFactoryBeanSupport<T, S, ID> implements BeanFactoryAware {
private String transactionManagerName = TxUtils.DEFAULT_TRANSACTION_MANAGER;
private RepositoryProxyPostProcessor txPostProcessor;

View File

@@ -18,6 +18,8 @@ package org.springframework.data.repository.support;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.io.Serializable;
import org.junit.Test;
@@ -38,14 +40,14 @@ public class AbstractEntityInformationUnitTests {
@Test
public void considersEntityNewIfGetIdReturnsNull() throws Exception {
EntityInformation<Object> metadata =
EntityInformation<Object, Serializable> metadata =
new DummyAbstractEntityInformation(Object.class);
assertThat(metadata.isNew(null), is(true));
assertThat(metadata.isNew(new Object()), is(false));
}
private static class DummyAbstractEntityInformation extends
AbstractEntityInformation<Object> {
AbstractEntityInformation<Object, Serializable> {
public DummyAbstractEntityInformation(Class<Object> domainClass) {
@@ -60,9 +62,18 @@ public class AbstractEntityInformationUnitTests {
* org.springframework.data.repository.support.EntityMetadata#getId(
* java.lang.Object)
*/
public Object getId(Object entity) {
public Serializable getId(Object entity) {
return entity == null ? null : entity.toString();
}
/* (non-Javadoc)
* @see org.springframework.data.repository.support.EntityInformation#getIdType()
*/
@Override
public Class<Serializable> getIdType() {
return Serializable.class;
}
}
}

View File

@@ -35,8 +35,8 @@ import org.springframework.data.domain.Persistable;
public class PersistableEntityInformationUnitTests {
@SuppressWarnings("rawtypes")
static final PersistableEntityInformation<Persistable> metadata =
new PersistableEntityInformation<Persistable>(Persistable.class);
static final PersistableEntityInformation metadata =
new PersistableEntityInformation(Persistable.class);
@Mock
Persistable<Long> persistable;
@@ -64,8 +64,8 @@ public class PersistableEntityInformationUnitTests {
@Test
public void returnsGivenClassAsEntityType() throws Exception {
PersistableEntityInformation<PersistableEntity> info =
new PersistableEntityInformation<PersistableEntity>(
PersistableEntityInformation<PersistableEntity, Long> info =
new PersistableEntityInformation<PersistableEntity, Long>(
PersistableEntity.class);
assertEquals(PersistableEntity.class, info.getJavaType());

View File

@@ -62,6 +62,16 @@ public class RepositoryFactorySupportUnitTests {
}
class DummyRepositoryFactory extends RepositoryFactorySupport {
/* (non-Javadoc)
* @see org.springframework.data.repository.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class)
*/
@Override
public <T, ID extends Serializable> EntityInformation<T, ID> getEntityInformation(
Class<T> domainClass) {
return mock(EntityInformation.class);
}
@Override
protected Object getTargetRepository(RepositoryMetadata metadata) {

View File

@@ -0,0 +1,98 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons-parent</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>../spring-data-commons-parent/pom.xml</relativePath>
</parent>
<artifactId>spring-data-commons-extensions</artifactId>
<packaging>jar</packaging>
<name>Spring Data Commons Extensions</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-data-commons-core</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>1.6</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.springsource.bundlor</groupId>
<artifactId>com.springsource.bundlor.maven</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,180 @@
/*
* Copyright 2008-2011 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.extensions.beans;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorSupport;
import java.io.Serializable;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.support.EntityInformation;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Generic {@link PropertyEditor} to map entities handled by a
* {@link Repository} to their id's and vice versa.
*
* @author Oliver Gierke
*/
public class DomainClassPropertyEditor<T, ID extends Serializable> extends
PropertyEditorSupport {
private final Repository<T, ID> repository;
private final EntityInformation<T, ID> information;
private final PropertyEditorRegistry registry;
/**
* Creates a new {@link DomainClassPropertyEditor} for the given
* {@link Repository}.
*
* @param repository
* @param registry
*/
public DomainClassPropertyEditor(Repository<T, ID> repository,
EntityInformation<T, ID> information,
PropertyEditorRegistry registry) {
Assert.notNull(repository);
Assert.notNull(registry);
this.repository = repository;
this.information = information;
this.registry = registry;
}
/*
* (non-Javadoc)
*
* @see java.beans.PropertyEditorSupport#setAsText(java.lang.String)
*/
@Override
public void setAsText(String idAsString) throws IllegalArgumentException {
if (!StringUtils.hasText(idAsString)) {
setValue(null);
return;
}
setValue(repository.findById(getId(idAsString)));
}
/*
* (non-Javadoc)
*
* @see java.beans.PropertyEditorSupport#getAsText()
*/
@Override
@SuppressWarnings("unchecked")
public String getAsText() {
T entity = (T) getValue();
if (null == entity) {
return null;
}
Object id = getId(entity);
return id == null ? null : id.toString();
}
/**
* Looks up the id of the given entity using one of the
* {@link org.synyx.hades.dao.orm.GenericDaoSupport.IdAware} implementations
* of Hades.
*
* @param entity
* @return
*/
private ID getId(T entity) {
return information.getId(entity);
}
/**
* Returns the actual typed id. Looks up an available customly registered
* {@link PropertyEditor} from the {@link PropertyEditorRegistry} before
* falling back on a {@link SimpleTypeConverter} to translate the
* {@link String} id into the type one.
*
* @param idAsString
* @return
*/
@SuppressWarnings("unchecked")
private ID getId(String idAsString) {
Class<ID> idClass = information.getIdType();
PropertyEditor idEditor = registry.findCustomEditor(idClass, null);
if (idEditor != null) {
idEditor.setAsText(idAsString);
return (ID) idEditor.getValue();
}
return new SimpleTypeConverter()
.convertIfNecessary(idAsString, idClass);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
DomainClassPropertyEditor<?, ?> that =
(DomainClassPropertyEditor<?, ?>) obj;
return this.repository.equals(that.repository)
&& this.registry.equals(that.registry)
&& this.information.equals(that.information);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int hashCode = 17;
hashCode += repository.hashCode() * 32;
hashCode += information.hashCode() * 32;
hashCode += registry.hashCode() * 32;
return hashCode;
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2008-2011 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.extensions.beans;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.support.EntityInformation;
import org.springframework.data.repository.support.RepositoryFactoryInformation;
/**
* Simple helper class to use Hades DAOs to provide
* {@link java.beans.PropertyEditor}s for domain classes. To get this working
* configure a
* {@link org.springframework.web.bind.support.ConfigurableWebBindingInitializer}
* for your
* {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter}
* and register the {@link DomainClassPropertyEditorRegistrar} there: <code>
* &lt;bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"&gt;
* &lt;property name="webBindingInitializer"&gt;
* &lt;bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer"&gt;
* &lt;property name="propertyEditorRegistrars"&gt;
* &lt;bean class="org.springframework.data.extensions.beans.DomainClassPropertyEditorRegistrar" /&gt;
* &lt;/property&gt;
* &lt;/bean&gt;
* &lt;/property&gt;
* &lt;/bean&gt;
* </code> Make sure this bean declaration is in the {@link ApplicationContext}
* created by the {@link DispatcherServlet} whereas the repositories need to be
* declared in the root
* {@link org.springframework.web.context.WebApplicationContext}.
*
* @author Oliver Gierke
*/
public class DomainClassPropertyEditorRegistrar implements
PropertyEditorRegistrar, ApplicationContextAware {
private final Map<EntityInformation<Object, Serializable>, Repository<Object, Serializable>> repositories =
new HashMap<EntityInformation<Object, Serializable>, Repository<Object, Serializable>>();
/*
* (non-Javadoc)
*
* @see
* org.springframework.beans.PropertyEditorRegistrar#registerCustomEditors
* (org.springframework.beans.PropertyEditorRegistry)
*/
public void registerCustomEditors(PropertyEditorRegistry registry) {
for (Entry<EntityInformation<Object, Serializable>, Repository<Object, Serializable>> entry : repositories
.entrySet()) {
EntityInformation<Object, Serializable> metadata = entry.getKey();
Repository<Object, Serializable> repository = entry.getValue();
DomainClassPropertyEditor<Object, Serializable> editor =
new DomainClassPropertyEditor<Object, Serializable>(
repository, metadata, registry);
registry.registerCustomEditor(metadata.getJavaType(), editor);
}
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.context.ApplicationContextAware#setApplicationContext
* (org.springframework.context.ApplicationContext)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public void setApplicationContext(ApplicationContext context) {
Collection<RepositoryFactoryInformation> providers =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context,
RepositoryFactoryInformation.class).values();
for (RepositoryFactoryInformation information : providers) {
EntityInformation<Object, Serializable> metadata =
information.getEntityInformation();
Class<Repository<Object, Serializable>> objectType =
information.getRepositoryInterface();
Repository<Object, Serializable> repository =
BeanFactoryUtils.beanOfType(context, objectType);
this.repositories.put(metadata, repository);
}
}
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright 2008-2011 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.extensions.converter;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.support.EntityInformation;
import org.springframework.data.repository.support.RepositoryFactoryInformation;
/**
* {@link org.springframework.core.convert.converter.Converter} to convert
* arbitrary input into domain classes managed by Spring Data {@link Repository}
* s. The implementation uses a {@link ConversionService} in turn to convert the
* source type into the domain class' id type which is then converted into a
* domain class object by using a {@link Repository}.
*
* @author Oliver Gierke
*/
public class DomainClassConverter implements ConditionalGenericConverter,
ApplicationContextAware {
private final Map<EntityInformation<?, Serializable>, Repository<?, Serializable>> repositories =
new HashMap<EntityInformation<?, Serializable>, Repository<?, Serializable>>();
private final ConversionService service;
/**
* Creates a new {@link DomainClassConverter}.
*
* @param service
*/
public DomainClassConverter(ConversionService service) {
this.service = service;
}
/*
* (non-Javadoc)
*
* @see org.springframework.core.convert.converter.GenericConverter#
* getConvertibleTypes()
*/
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object.class,
Object.class));
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.core.convert.converter.GenericConverter#convert(java
* .lang.Object, org.springframework.core.convert.TypeDescriptor,
* org.springframework.core.convert.TypeDescriptor)
*/
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
EntityInformation<?, Serializable> info =
getRepositoryForDomainType(targetType.getType());
Repository<?, Serializable> repository = repositories.get(info);
Serializable id = service.convert(source, info.getIdType());
return repository.findById(id);
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.core.convert.converter.ConditionalGenericConverter
* #matches(org.springframework.core.convert.TypeDescriptor,
* org.springframework.core.convert.TypeDescriptor)
*/
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
EntityInformation<?, ?> info =
getRepositoryForDomainType(targetType.getType());
if (info == null) {
return false;
}
return service.canConvert(sourceType.getType(), info.getIdType());
}
private EntityInformation<?, Serializable> getRepositoryForDomainType(
Class<?> domainType) {
for (EntityInformation<?, Serializable> information : repositories
.keySet()) {
if (domainType.equals(information.getJavaType())) {
return information;
}
}
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.context.ApplicationContextAware#setApplicationContext
* (org.springframework.context.ApplicationContext)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public void setApplicationContext(ApplicationContext context) {
Collection<RepositoryFactoryInformation> providers =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context,
RepositoryFactoryInformation.class).values();
for (RepositoryFactoryInformation entry : providers) {
EntityInformation<Object, Serializable> metadata =
entry.getEntityInformation();
Class<Repository<Object, Serializable>> objectType =
entry.getRepositoryInterface();
Repository<Object, Serializable> repository =
BeanFactoryUtils.beanOfType(context, objectType);
this.repositories.put(metadata, repository);
}
}
}

View File

@@ -0,0 +1,327 @@
/*
* Copyright 2008-2011 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.extensions.web;
import java.beans.PropertyEditorSupport;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletRequest;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.validation.DataBinder;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.ServletRequestParameterPropertyValues;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Extracts paging information from web requests and thus allows injecting
* {@link Pageable} instances into controller methods. Request properties to be
* parsed can be configured. Default configuration uses request properties
* beginning with {@link #DEFAULT_PREFIX}{@link #DEFAULT_SEPARATOR}.
*
* @author Oliver Gierke
*/
public class PageableArgumentResolver implements WebArgumentResolver {
private static final Pageable DEFAULT_PAGE_REQUEST = new PageRequest(0, 10);
private static final String DEFAULT_PREFIX = "page";
private static final String DEFAULT_SEPARATOR = ".";
private Pageable fallbackPagable = DEFAULT_PAGE_REQUEST;
private String prefix = DEFAULT_PREFIX;
private String separator = DEFAULT_SEPARATOR;
/**
* Setter to configure a fallback instance of {@link Pageable} that is being
* used to back missing parameters. Defaults to
* {@value #DEFAULT_PAGE_REQUEST}.
*
* @param fallbackPagable the fallbackPagable to set
*/
public void setFallbackPagable(Pageable fallbackPagable) {
this.fallbackPagable =
null == fallbackPagable ? DEFAULT_PAGE_REQUEST
: fallbackPagable;
}
/**
* Setter to configure the prefix of request parameters to be used to
* retrieve paging information. Defaults to {@link #DEFAULT_PREFIX}.
*
* @param prefix the prefix to set
*/
public void setPrefix(String prefix) {
this.prefix = null == prefix ? DEFAULT_PREFIX : prefix;
}
/**
* Setter to configure the separator between prefix and actual property
* value. Defaults to {@link #DEFAULT_SEPARATOR}.
*
* @param separator the separator to set
*/
public void setSeparator(String separator) {
this.separator = null == separator ? DEFAULT_SEPARATOR : separator;
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.web.bind.support.WebArgumentResolver#resolveArgument
* (org.springframework.core.MethodParameter,
* org.springframework.web.context.request.NativeWebRequest)
*/
public Object resolveArgument(MethodParameter methodParameter,
NativeWebRequest webRequest) {
if (methodParameter.getParameterType().equals(Pageable.class)) {
assertPageableUniqueness(methodParameter);
Pageable request =
getDefaultFromAnnotationOrFallback(methodParameter);
ServletRequest servletRequest =
(ServletRequest) webRequest.getNativeRequest();
PropertyValues propertyValues =
new ServletRequestParameterPropertyValues(servletRequest,
getPrefix(methodParameter), separator);
DataBinder binder = new ServletRequestDataBinder(request);
binder.initDirectFieldAccess();
binder.registerCustomEditor(Sort.class, new SortPropertyEditor(
"sort.dir", propertyValues));
binder.bind(propertyValues);
if (request.getPageNumber() > 0) {
request =
new PageRequest(request.getPageNumber() - 1,
request.getPageSize(), request.getSort());
}
return request;
}
return UNRESOLVED;
}
private Pageable getDefaultFromAnnotationOrFallback(
MethodParameter methodParameter) {
// search for PageableDefaults annotation
for (Annotation annotation : methodParameter.getParameterAnnotations()) {
if (annotation instanceof PageableDefaults) {
PageableDefaults defaults = (PageableDefaults) annotation;
// +1 is because we substract 1 later
return new PageRequest(defaults.pageNumber() + 1,
defaults.value());
}
}
// Construct request with fallback request to ensure sensible
// default values. Create fresh copy as Spring will manipulate the
// instance under the covers
return new PageRequest(fallbackPagable.getPageNumber(),
fallbackPagable.getPageSize(), fallbackPagable.getSort());
}
/**
* Resolves the prefix to use to bind properties from. Will prepend a
* possible {@link Qualifier} if available or return the configured prefix
* otherwise.
*
* @param parameter
* @return
*/
private String getPrefix(MethodParameter parameter) {
for (Annotation annotation : parameter.getParameterAnnotations()) {
if (annotation instanceof Qualifier) {
return new StringBuilder(((Qualifier) annotation).value())
.append("_").append(prefix).toString();
}
}
return prefix;
}
/**
* Asserts uniqueness of all {@link Pageable} parameters of the method of
* the given {@link MethodParameter}.
*
* @param parameter
*/
private void assertPageableUniqueness(MethodParameter parameter) {
Method method = parameter.getMethod();
if (containsMoreThanOnePageableParameter(method)) {
Annotation[][] annotations = method.getParameterAnnotations();
assertQualifiersFor(method.getParameterTypes(), annotations);
}
}
/**
* Returns whether the given {@link Method} has more than one
* {@link Pageable} parameter.
*
* @param method
* @return
*/
private boolean containsMoreThanOnePageableParameter(Method method) {
boolean pageableFound = false;
for (Class<?> type : method.getParameterTypes()) {
if (pageableFound && type.equals(Pageable.class)) {
return true;
}
if (type.equals(Pageable.class)) {
pageableFound = true;
}
}
return false;
}
/**
* Asserts that every {@link Pageable} parameter of the given parameters
* carries an {@link Qualifier} annotation to distinguish them from each
* other.
*
* @param parameterTypes
* @param annotations
*/
private void assertQualifiersFor(Class<?>[] parameterTypes,
Annotation[][] annotations) {
Set<String> values = new HashSet<String>();
for (int i = 0; i < annotations.length; i++) {
if (Pageable.class.equals(parameterTypes[i])) {
Qualifier qualifier = findAnnotation(annotations[i]);
if (null == qualifier) {
throw new IllegalStateException(
"Ambiguous Pageable arguments in handler method. If you use multiple parameters of type Pageable you need to qualify them with @Qualifier");
}
if (values.contains(qualifier.value())) {
throw new IllegalStateException(
"Values of the user Qualifiers must be unique!");
}
values.add(qualifier.value());
}
}
}
/**
* Returns a {@link Qualifier} annotation from the given array of
* {@link Annotation}s. Returns {@literal null} if the array does not
* contain a {@link Qualifier} annotation.
*
* @param annotations
* @return
*/
private Qualifier findAnnotation(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation instanceof Qualifier) {
return (Qualifier) annotation;
}
}
return null;
}
/**
* {@link java.beans.PropertyEditor} to create {@link Sort} instances from
* textual representations. The implementation interprets the string as a
* comma separated list where the first entry is the sort direction (
* {@code asc}, {@code desc}) followed by the properties to sort by.
*
* @author Oliver Gierke
*/
private static class SortPropertyEditor extends PropertyEditorSupport {
private final String orderProperty;
private final PropertyValues values;
/**
* Creates a new {@link SortPropertyEditor}.
*
* @param orderProperty
* @param values
*/
public SortPropertyEditor(String orderProperty, PropertyValues values) {
this.orderProperty = orderProperty;
this.values = values;
}
/*
* (non-Javadoc)
*
* @see java.beans.PropertyEditorSupport#setAsText(java.lang.String)
*/
@Override
public void setAsText(String text) throws IllegalArgumentException {
PropertyValue rawOrder = values.getPropertyValue(orderProperty);
Direction order =
null == rawOrder ? Direction.ASC : Direction
.fromString(rawOrder.getValue().toString());
setValue(new Sort(order, text));
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2008-2011 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.extensions.web;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.data.domain.Pageable;
/**
* Annotation to set defaults when injecting a {@link Pageable} into a
* controller method.
*
* @author Oliver Gierke
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface PageableDefaults {
/**
* The default-size the injected
* {@link org.springframework.data.domain.Pageable} should get if no
* corresponding parameter defined in request (default is 10).
*/
int value() default 10;
/**
* The default-pagenumber the injected
* {@link org.synyx.hades.domain.Pageable} should get if no corresponding
* parameter defined in request (default is 0).
*/
int pageNumber() default 0;
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright 2008-2011 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.extensions.beans;
import static org.hamcrest.Matchers.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.support.EntityInformation;
import org.springframework.data.repository.support.RepositoryFactoryInformation;
/**
* Unit test for {@link DomainClassPropertyEditorRegistrar}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class DomainClassPropertyEditorRegistrarUnitTests {
DomainClassPropertyEditorRegistrar registrar =
new DomainClassPropertyEditorRegistrar();
@Mock
ApplicationContext context;
@Mock
PropertyEditorRegistry registry;
@Mock
EntityRepository repository;
@Mock
EntityInformation<Entity, Long> information;
@Mock
RepositoryFactoryInformation<Entity, Long> provider;
DomainClassPropertyEditor<Entity, Long> reference;
@Before
@SuppressWarnings({ "unchecked", "rawtypes" })
public void setup() {
when(information.getJavaType()).thenReturn(Entity.class);
when(provider.getEntityInformation()).thenReturn(information);
when(provider.getRepositoryInterface()).thenReturn(
(Class) EntityRepository.class);
Map<String, EntityRepository> map = getBeanAsMap(repository);
when(context.getBeansOfType(EntityRepository.class)).thenReturn(map);
reference =
new DomainClassPropertyEditor<Entity, Long>(repository,
information, registry);
}
@Test
public void addsRepositoryForEntityIfAvailableInAppContext()
throws Exception {
letContextContain(provider);
registrar.setApplicationContext(context);
registrar.registerCustomEditors(registry);
verify(registry).registerCustomEditor(eq(Entity.class), eq(reference));
}
@Test
public void doesNotAddDaoAtAllIfNoDaosFound() throws Exception {
letContextContain(provider);
registrar.registerCustomEditors(registry);
verify(registry, never()).registerCustomEditor(eq(Entity.class),
eq(reference));
}
private void letContextContain(Object bean) {
Map<String, Object> beanMap = getBeanAsMap(bean);
when(context.getBeansOfType(argThat(is(subtypeOf(bean.getClass())))))
.thenReturn(beanMap);
}
private <T> Map<String, T> getBeanAsMap(T bean) {
Map<String, T> beanMap = new HashMap<String, T>();
beanMap.put(bean.getClass().getName(), bean);
return beanMap;
}
@SuppressWarnings("serial")
private static class Entity implements Serializable {
}
private static interface EntityRepository extends Repository<Entity, Long> {
}
private static <T> TypeSafeMatcher<Class<T>> subtypeOf(
final Class<? extends T> type) {
return new TypeSafeMatcher<Class<T>>() {
public void describeTo(Description arg0) {
arg0.appendText("not a subtype of");
}
@Override
public boolean matchesSafely(Class<T> arg0) {
return arg0.isAssignableFrom(type);
}
};
}
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright 2008-2011 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.extensions.beans;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.beans.PropertyEditor;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.data.domain.Persistable;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.support.EntityInformation;
/**
* Unit test for {@link DomainClassPropertyEditor}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class DomainClassPropertyEditorUnitTests {
DomainClassPropertyEditor<User, Integer> editor;
@Mock
PropertyEditorRegistry registry;
@Mock
UserRepository userRepository;
@Mock
EntityInformation<User, Integer> information;
@Before
public void setUp() {
when(information.getIdType()).thenReturn(Integer.class);
editor =
new DomainClassPropertyEditor<User, Integer>(userRepository,
information, registry);
}
@Test
public void convertsPlainIdTypeCorrectly() throws Exception {
User user = new User(1);
when(information.getId(user)).thenReturn(user.getId());
when(userRepository.findById(1)).thenReturn(user);
editor.setAsText("1");
verify(userRepository, times(1)).findById(1);
}
@Test
public void convertsEntityToIdCorrectly() throws Exception {
User user = new User(1);
editor.setValue(user);
when(information.getId(user)).thenReturn(user.getId());
assertThat(editor.getAsText(), is("1"));
}
@Test
public void usesCustomEditorIfConfigured() throws Exception {
PropertyEditor customEditor = mock(PropertyEditor.class);
when(customEditor.getValue()).thenReturn(1);
when(registry.findCustomEditor(Integer.class, null)).thenReturn(
customEditor);
convertsPlainIdTypeCorrectly();
verify(customEditor, times(1)).setAsText("1");
}
@Test
public void returnsNullIdIfNoEntitySet() throws Exception {
editor.setValue(null);
assertThat(editor.getAsText(), is(nullValue()));
}
@Test
public void resetsValueToNullAfterEmptyStringConversion() throws Exception {
assertValueResetToNullAfterConverting("");
}
@Test
public void resetsValueToNullAfterNullStringConversion() throws Exception {
assertValueResetToNullAfterConverting(null);
}
private void assertValueResetToNullAfterConverting(String source)
throws Exception {
convertsPlainIdTypeCorrectly();
assertThat(editor.getValue(), is(notNullValue()));
editor.setAsText(source);
assertThat(editor.getValue(), is(nullValue()));
}
/**
* Sample entity.
*
* @author Oliver Gierke
*/
@SuppressWarnings("serial")
private static class User implements Persistable<Integer> {
private Integer id;
public User(Integer id) {
this.id = id;
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.domain.Persistable#getId()
*/
@Override
public Integer getId() {
return id;
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.domain.Persistable#isNew()
*/
@Override
public boolean isNew() {
return getId() != null;
}
}
/**
* Sample generic DAO interface.
*
* @author Oliver Gierke
*/
private static interface UserRepository extends Repository<User, Integer> {
}
}

View File

@@ -0,0 +1,192 @@
/*
* Copyright 2008-2011 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.extensions.conversion;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.util.HashMap;
import java.util.Map;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.extensions.converter.DomainClassConverter;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.support.EntityInformation;
import org.springframework.data.repository.support.RepositoryFactoryInformation;
/**
* Unit test for {@link DomainClassConverter}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class DomainClassConverterUnitTests {
static final User USER = new User();
DomainClassConverter converter;
TypeDescriptor sourceDescriptor;
TypeDescriptor targetDescriptor;
Map<String, RepositoryFactoryInformation> providers;
@Mock
ApplicationContext context;
@Mock
UserRepository repository;
@Mock
ConversionService service;
@Mock
EntityInformation<User, Long> information;
@Mock
RepositoryFactoryInformation<User, Long> provider;
@Before
@SuppressWarnings({ "unchecked", "rawtypes" })
public void setUp() {
converter = new DomainClassConverter(service);
providers = new HashMap<String, RepositoryFactoryInformation>();
sourceDescriptor = TypeDescriptor.valueOf(String.class);
targetDescriptor = TypeDescriptor.valueOf(User.class);
Map<String, UserRepository> map = getBeanAsMap(repository);
when(context.getBeansOfType(UserRepository.class)).thenReturn(map);
when(context.getBeansOfType(RepositoryFactoryInformation.class))
.thenReturn(providers);
when(provider.getEntityInformation()).thenReturn(information);
when(provider.getRepositoryInterface()).thenReturn(
(Class) UserRepository.class);
when(information.getJavaType()).thenReturn(User.class);
when(information.getIdType()).thenReturn(Long.class);
}
@Test
public void matchFailsIfNoDaoAvailable() throws Exception {
converter.setApplicationContext(context);
assertMatches(false);
}
@Test
public void matchesIfConversionInBetweenIsPossible() throws Exception {
letContextContain(provider);
converter.setApplicationContext(context);
when(service.canConvert(String.class, Long.class)).thenReturn(true);
assertMatches(true);
}
@Test
public void matchFailsIfNoIntermediateConversionIsPossible()
throws Exception {
letContextContain(provider);
converter.setApplicationContext(context);
when(service.canConvert(String.class, Long.class)).thenReturn(false);
assertMatches(false);
}
private void assertMatches(boolean matchExpected) {
assertThat(converter.matches(sourceDescriptor, targetDescriptor),
is(matchExpected));
}
@Test
public void convertsStringToUserCorrectly() throws Exception {
letContextContain(provider);
converter.setApplicationContext(context);
when(service.canConvert(String.class, Long.class)).thenReturn(true);
when(service.convert(anyString(), eq(Long.class))).thenReturn(1L);
when(repository.findById(1L)).thenReturn(USER);
Object user =
converter.convert("1", sourceDescriptor, targetDescriptor);
assertThat(user, is(instanceOf(User.class)));
assertThat(user, is((Object) USER));
}
private void letContextContain(Object bean) {
Map<String, Object> beanMap = getBeanAsMap(bean);
when(context.getBeansOfType(argThat(is(subtypeOf(bean.getClass())))))
.thenReturn(beanMap);
}
private <T> Map<String, T> getBeanAsMap(T bean) {
Map<String, T> beanMap = new HashMap<String, T>();
beanMap.put(bean.getClass().getName(), bean);
return beanMap;
}
private static <T> TypeSafeMatcher<Class<T>> subtypeOf(
final Class<? extends T> type) {
return new TypeSafeMatcher<Class<T>>() {
public void describeTo(Description arg0) {
arg0.appendText("not a subtype of");
}
@Override
public boolean matchesSafely(Class<T> arg0) {
return arg0.isAssignableFrom(type);
}
};
}
private static class User {
}
private static interface UserRepository extends Repository<User, Long> {
}
}

View File

@@ -0,0 +1,193 @@
/*
* Copyright 2008-2011 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.extensions.web;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
/**
* Unit test for {@link PageableArgumentResolver}.
*
* @author Oliver Gierke - gierke@synyx.de
*/
public class PageableArgumentResolverUnitTests {
Method correctMethod;
Method failedMethod;
Method invalidQualifiers;
Method defaultsMethod;
MockHttpServletRequest request;
@Before
public void setUp() throws SecurityException, NoSuchMethodException {
correctMethod =
SampleController.class.getMethod("correctMethod",
Pageable.class, Pageable.class);
failedMethod =
SampleController.class.getMethod("failedMethod",
Pageable.class, Pageable.class);
invalidQualifiers =
SampleController.class.getMethod("invalidQualifiers",
Pageable.class, Pageable.class);
defaultsMethod =
SampleController.class.getMethod("defaultsMethod",
Pageable.class);
request = new MockHttpServletRequest();
// Add pagination info for foo table
request.addParameter("foo_page.size", "50");
request.addParameter("foo_page.sort", "foo");
request.addParameter("foo_page.sort.dir", "asc");
// Add pagination info for bar table
request.addParameter("bar_page.size", "60");
}
@Test
public void testname() throws Exception {
assertSizeForPrefix(50, new Sort(Direction.ASC, "foo"), 0);
assertSizeForPrefix(60, null, 1);
}
@Test(expected = IllegalStateException.class)
public void rejectsInvalidlyMappedPageables() throws Exception {
MethodParameter parameter = new MethodParameter(failedMethod, 0);
NativeWebRequest webRequest = new ServletWebRequest(request);
new PageableArgumentResolver().resolveArgument(parameter, webRequest);
}
@Test(expected = IllegalStateException.class)
public void rejectsInvalidQualifiers() throws Exception {
MethodParameter parameter = new MethodParameter(invalidQualifiers, 0);
NativeWebRequest webRequest = new ServletWebRequest(request);
new PageableArgumentResolver().resolveArgument(parameter, webRequest);
}
@Test
public void assertDefaults() throws Exception {
MethodParameter parameter = new MethodParameter(defaultsMethod, 0);
NativeWebRequest webRequest =
new ServletWebRequest(new MockHttpServletRequest());
PageableArgumentResolver resolver = new PageableArgumentResolver();
Object argument = resolver.resolveArgument(parameter, webRequest);
assertTrue(argument instanceof Pageable);
Pageable pageable = (Pageable) argument;
assertEquals(SampleController.DEFAULT_PAGESIZE, pageable.getPageSize());
assertEquals(SampleController.DEFAULT_PAGENUMBER,
pageable.getPageNumber());
}
@Test
public void assertOverridesDefaults() throws Exception {
Integer sizeParam = 5;
MethodParameter parameter = new MethodParameter(defaultsMethod, 0);
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
mockRequest.addParameter("page.page", sizeParam.toString());
NativeWebRequest webRequest = new ServletWebRequest(mockRequest);
PageableArgumentResolver resolver = new PageableArgumentResolver();
Object argument = resolver.resolveArgument(parameter, webRequest);
assertTrue(argument instanceof Pageable);
Pageable pageable = (Pageable) argument;
assertEquals(SampleController.DEFAULT_PAGESIZE, pageable.getPageSize());
assertEquals(sizeParam - 1, pageable.getPageNumber());
}
private void assertSizeForPrefix(int size, Sort sort, int index)
throws Exception {
MethodParameter parameter = new MethodParameter(correctMethod, index);
NativeWebRequest webRequest = new ServletWebRequest(request);
PageableArgumentResolver resolver = new PageableArgumentResolver();
Object argument = resolver.resolveArgument(parameter, webRequest);
assertTrue(argument instanceof Pageable);
Pageable pageable = (Pageable) argument;
assertEquals(size, pageable.getPageSize());
if (null != sort) {
assertEquals(sort, pageable.getSort());
}
}
@SuppressWarnings("unused")
private class SampleController {
static final int DEFAULT_PAGESIZE = 198;
static final int DEFAULT_PAGENUMBER = 42;
public void defaultsMethod(
@PageableDefaults(value = DEFAULT_PAGESIZE, pageNumber = DEFAULT_PAGENUMBER) Pageable pageable) {
}
public void correctMethod(@Qualifier("foo") Pageable first,
@Qualifier("bar") Pageable second) {
}
public void failedMethod(Pageable first, Pageable second) {
}
public void invalidQualifiers(@Qualifier("foo") Pageable first,
@Qualifier("foo") Pageable second) {
}
}
}

View File

@@ -0,0 +1,16 @@
Bundle-SymbolicName: org.springframework.data.extensions
Bundle-Name: Spring Data Commons Extensions
Bundle-Vendor: SpringSource
Bundle-ManifestVersion: 2
Export-Template:
org.springframework.data.extensions.*;version="${project.version:[=.=.=,+1.0.0)}"
Import-Template:
org.springframework.data.*;version="${project.version:[=.=.=,+1.0.0)}",
org.springframework.core.*;version="${org.springframework.version:[=.=.=,+1.0.0)}",
org.springframework.beans.*;version="${org.springframework.version:[=.=.=,+1.0.0)}",
org.springframework.context.*;version="${org.springframework.version:[=.=.=,+1.0.0)}",
org.springframework.util.*;version="${org.springframework.version:[=.=.=,+1.0.0)}",
org.springframework.validation.*;version="${org.springframework.version:[=.=.=,+1.0.0)}",
org.springframework.web.*;version="${org.springframework.version:[=.=.=,+1.0.0)}",
javax.servlet.*;version="[2.5.0, 3.0.0)"

View File

@@ -111,6 +111,16 @@
<artifactId>spring-tx</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>