Tweaking query method support.
This commit is contained in:
@@ -16,8 +16,6 @@ allprojects {
|
||||
repositories {
|
||||
maven { url "http://repo.springsource.org/libs-milestone" }
|
||||
maven { url "http://repo.springsource.org/libs-release" }
|
||||
//mavenCentral()
|
||||
//mavenLocal()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.springframework.data.rest.core;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface Handler<T,V> {
|
||||
V handle(T t);
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.springframework.data.rest.core;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface Link {
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.springframework.data.rest.core;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class SimpleLink implements Link {
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public abstract class BeanUtils {
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class FluentBeanDeserializer extends StdDeserializer {
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import org.codehaus.jackson.map.ser.std.SerializerBase;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class FluentBeanSerializer extends SerializerBase {
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public abstract class FluentBeanUtils {
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class RestHelper<T> {
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public abstract class UriUtils {
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import org.springframework.data.rest.core.util.UriUtils
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
class UriUtilsSpec extends Specification {
|
||||
|
||||
|
||||
@@ -5,30 +5,93 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* Encapsulates necessary information about an attribute of a generic entity.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface AttributeMetadata {
|
||||
|
||||
/**
|
||||
* Name of the attribute.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* The type of this attribute.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> type();
|
||||
|
||||
/**
|
||||
* The element type of this attribute, if this attribute is a "plural"-like attribute (a Collection, Map, etc...).
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> elementType();
|
||||
|
||||
/**
|
||||
* Can this attribute look like a {@link Collection}?
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean isCollectionLike();
|
||||
|
||||
/**
|
||||
* Get the path of this attribute as a {@link Collection}.
|
||||
*
|
||||
* @param target
|
||||
* @return
|
||||
*/
|
||||
Collection<?> asCollection(Object target);
|
||||
|
||||
/**
|
||||
* Can this attribute look like a {@link Set}?
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean isSetLike();
|
||||
|
||||
/**
|
||||
* Get the path of this attribute as a {@link Set}.
|
||||
*
|
||||
* @param target
|
||||
* @return
|
||||
*/
|
||||
Set<?> asSet(Object target);
|
||||
|
||||
/**
|
||||
* Can this attribute look like a {@link Map}?
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean isMapLike();
|
||||
|
||||
/**
|
||||
* Get the path of this attribute as a {@link Map}.
|
||||
*
|
||||
* @param target
|
||||
* @return
|
||||
*/
|
||||
Map asMap(Object target);
|
||||
|
||||
/**
|
||||
* Get the path of this attribute.
|
||||
*
|
||||
* @param target
|
||||
* @return
|
||||
*/
|
||||
Object get(Object target);
|
||||
|
||||
/**
|
||||
* Set the path of this attribute.
|
||||
*
|
||||
* @param value
|
||||
* @param target
|
||||
* @return
|
||||
*/
|
||||
AttributeMetadata set(Object value, Object target);
|
||||
|
||||
}
|
||||
|
||||
@@ -3,20 +3,53 @@ package org.springframework.data.rest.repository;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* Encapsulates necessary metadata about a generic entity.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface EntityMetadata<A extends AttributeMetadata> {
|
||||
|
||||
/**
|
||||
* The class of this entity.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> type();
|
||||
|
||||
/**
|
||||
* A Map of attribute metadata keyed on the attribute's name.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Map<String, A> embeddedAttributes();
|
||||
|
||||
/**
|
||||
* A Map of linked attribute metadata keyed on the attribute's name.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Map<String, A> linkedAttributes();
|
||||
|
||||
/**
|
||||
* The {@link AttributeMetadata} representing the ID of the entity.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
A idAttribute();
|
||||
|
||||
/**
|
||||
* The {@link AttributeMetadata} representing the version of the entity, if applicable.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
A versionAttribute();
|
||||
|
||||
/**
|
||||
* Get {@link AttributeMetadata} by name.
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
A attribute(String name);
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.validation.Errors;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class RepositoryConstraintViolationException extends DataIntegrityViolationException {
|
||||
|
||||
|
||||
@@ -2,26 +2,29 @@ package org.springframework.data.rest.repository;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactoryInformation;
|
||||
import org.springframework.data.rest.repository.annotation.RestPathSegment;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.repository.annotation.RestResource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* Abstract class that contains the basic functionality that any exporter will need
|
||||
* to export a Repository implementation.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public abstract class RepositoryExporter<M extends RepositoryMetadata<R, E>,
|
||||
R extends Repository<? extends Object, ? extends Serializable>,
|
||||
@@ -30,12 +33,30 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<R, E>,
|
||||
InitializingBean {
|
||||
|
||||
protected ApplicationContext applicationContext;
|
||||
protected EntityManager entityManager;
|
||||
protected Repositories repositories;
|
||||
protected List<String> exportOnlyTheseClasses = Collections.emptyList();
|
||||
protected Map<String, M> repositoryMetadata;
|
||||
|
||||
@PersistenceContext
|
||||
public void setEntityManager(EntityManager entityManager) {
|
||||
this.entityManager = entityManager;
|
||||
/**
|
||||
* Get the list of class names of Repositories to export.
|
||||
*
|
||||
* @return a List of class names to export
|
||||
*/
|
||||
public List<String> getExportOnlyTheseClasses() {
|
||||
return exportOnlyTheseClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the class names of only those Repositories you want exported.
|
||||
* Default is to export all found Repositories.
|
||||
*
|
||||
* @param exportOnlyTheseClasses
|
||||
* @return @this
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public M setExportOnlyTheseClasses(List<String> exportOnlyTheseClasses) {
|
||||
this.exportOnlyTheseClasses = exportOnlyTheseClasses;
|
||||
return (M) this;
|
||||
}
|
||||
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
@@ -44,15 +65,45 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<R, E>,
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
repositories = new Repositories(applicationContext);
|
||||
repositoryMetadata = new HashMap<String, M>();
|
||||
Collection<RepositoryFactoryInformation> providers = BeanFactoryUtils.beansOfTypeIncludingAncestors(
|
||||
applicationContext,
|
||||
RepositoryFactoryInformation.class
|
||||
).values();
|
||||
|
||||
for (RepositoryFactoryInformation entry : providers) {
|
||||
EntityInformation entityInfo = entry.getEntityInformation();
|
||||
Class<?> repoClass = entry.getRepositoryInterface();
|
||||
String name;
|
||||
RestResource pathSeg = repoClass.getAnnotation(RestResource.class);
|
||||
if (null != pathSeg) {
|
||||
name = pathSeg.path();
|
||||
} else {
|
||||
name = StringUtils.uncapitalize(repoClass.getSimpleName().replaceAll("Repository", ""));
|
||||
}
|
||||
R repo = (R) BeanFactoryUtils.beanOfTypeIncludingAncestors(applicationContext, repoClass);
|
||||
M repoMeta = createRepositoryMetadata(repoClass, repo, name, entityInfo);
|
||||
repositoryMetadata.put(name, repoMeta);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of Repository names being exported.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<String> repositoryNames() {
|
||||
maybeCacheRepositoryFactoryInfo();
|
||||
return repositoryMetadata.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a Repository being exporter that supports this domain type?
|
||||
*
|
||||
* @param domainType
|
||||
* @return {@literal true} if a Repository is being exported, {@literal false} otherwise.
|
||||
*/
|
||||
public boolean hasRepositoryFor(Class<?> domainType) {
|
||||
maybeCacheRepositoryFactoryInfo();
|
||||
for (M repoMeta : repositoryMetadata.values()) {
|
||||
if (repoMeta.domainType().isAssignableFrom(domainType)) {
|
||||
return true;
|
||||
@@ -61,8 +112,13 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<R, E>,
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the RepositoryMetadata for the Repository responsible for this domain type.
|
||||
*
|
||||
* @param domainType
|
||||
* @return {@link RepositoryMetadata} instance
|
||||
*/
|
||||
public M repositoryMetadataFor(Class<?> domainType) {
|
||||
maybeCacheRepositoryFactoryInfo();
|
||||
for (M repoMeta : repositoryMetadata.values()) {
|
||||
if (repoMeta.domainType().isAssignableFrom(domainType)) {
|
||||
return repoMeta;
|
||||
@@ -71,8 +127,13 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<R, E>,
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link RepositoryMetadata} for the Repository exported under the given name.
|
||||
*
|
||||
* @param name
|
||||
* @return {@link RepositoryMetadata} instance
|
||||
*/
|
||||
public M repositoryMetadataFor(String name) {
|
||||
maybeCacheRepositoryFactoryInfo();
|
||||
return repositoryMetadata.get(name);
|
||||
}
|
||||
|
||||
@@ -83,30 +144,4 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<R, E>,
|
||||
EntityInformation entityInfo
|
||||
);
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private void maybeCacheRepositoryFactoryInfo() {
|
||||
if (null == repositoryMetadata) {
|
||||
repositoryMetadata = new HashMap<String, M>();
|
||||
Collection<RepositoryFactoryInformation> providers = BeanFactoryUtils.beansOfTypeIncludingAncestors(
|
||||
applicationContext,
|
||||
RepositoryFactoryInformation.class
|
||||
).values();
|
||||
|
||||
for (RepositoryFactoryInformation entry : providers) {
|
||||
EntityInformation entityInfo = entry.getEntityInformation();
|
||||
Class repoClass = entry.getRepositoryInterface();
|
||||
String name;
|
||||
RestPathSegment pathSeg = AnnotationUtils.findAnnotation(repoClass, RestPathSegment.class);
|
||||
if (null != pathSeg) {
|
||||
name = pathSeg.value();
|
||||
} else {
|
||||
name = StringUtils.uncapitalize(repoClass.getSimpleName().replaceAll("Repository", ""));
|
||||
}
|
||||
R repo = (R) BeanFactoryUtils.beanOfTypeIncludingAncestors(applicationContext, repoClass);
|
||||
M repoMeta = createRepositoryMetadata(repoClass, repo, name, entityInfo);
|
||||
repositoryMetadata.put(name, repoMeta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,25 +6,47 @@ import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* Abstract class used as a helper for those classes that need access to the exported repositories.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public abstract class RepositoryExporterSupport<S extends RepositoryExporterSupport<? super S>> {
|
||||
|
||||
@Autowired
|
||||
protected List<RepositoryExporter> repositoryExporters = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* Get a List of {@link RepositoryExporter}s.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<RepositoryExporter> getRepositoryExporters() {
|
||||
return repositoryExporters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the List of {@link RepositoryExporter}s.
|
||||
*
|
||||
* @param repositoryExporters
|
||||
*/
|
||||
public void setRepositoryExporters(List<RepositoryExporter> repositoryExporters) {
|
||||
this.repositoryExporters = repositoryExporters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a List of {@link RepositoryExporter}s.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<RepositoryExporter> repositoryExporters() {
|
||||
return repositoryExporters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the List of {@link RepositoryExporter}s.
|
||||
*
|
||||
* @param repositoryExporters
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public S repositoryExporters(List<RepositoryExporter> repositoryExporters) {
|
||||
this.repositoryExporters = repositoryExporters;
|
||||
|
||||
@@ -6,20 +6,67 @@ import java.util.Map;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* Encapsulates necessary metadata about a {@link Repository}.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface RepositoryMetadata<R extends Repository<? extends Object, ? extends Serializable>, E extends EntityMetadata<? extends AttributeMetadata>> {
|
||||
|
||||
/**
|
||||
* The name this {@link Repository} is exported under.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Get the string value to be used as part of a link {@literal rel} attribute.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String rel();
|
||||
|
||||
/**
|
||||
* The type of domain object this {@link Repository} is repsonsible for.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<? extends Object> domainType();
|
||||
|
||||
/**
|
||||
* The Class of the {@link Repository} subinterface.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<? extends Repository<? extends Object, ? extends Serializable>> repositoryClass();
|
||||
|
||||
/**
|
||||
* The {@link Repository} instance.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
R repository();
|
||||
|
||||
/**
|
||||
* The {@link EntityMetadata} associated with the domain type of this {@literal Repository}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
E entityMetadata();
|
||||
|
||||
/**
|
||||
* Get a {@link RepositoryQueryMethod} by key.
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
RepositoryQueryMethod queryMethod(String key);
|
||||
|
||||
/**
|
||||
* Get a Map of all {@link RepositoryQueryMethod}s, keyed by name.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Map<String, RepositoryQueryMethod> queryMethods();
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.springframework.data.rest.repository;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class RepositoryNotFoundException extends DataAccessResourceFailureException {
|
||||
|
||||
|
||||
@@ -7,16 +7,10 @@ import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class RepositoryQueryMethod {
|
||||
|
||||
private static final Class[] SIMPLE_TYPES = new Class[]{
|
||||
String.class,
|
||||
Integer.class,
|
||||
Long.class,
|
||||
Boolean.class
|
||||
};
|
||||
private static final LocalVariableTableParameterNameDiscoverer nameLookup = new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
private Method method;
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class ValidationErrors extends AbstractErrors {
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
||||
@@ -7,13 +7,20 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* Advertises classes annotated with this that they are event handlers.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface RepositoryEventHandler {
|
||||
|
||||
/**
|
||||
* The list of {@link org.springframework.context.ApplicationEvent} classes this event handler cares about.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* Annotate a {@link org.springframework.data.repository.Repository} with this to influence how it is exported and what
|
||||
* the value of the {@literal rel} attribute will be in links.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Target({
|
||||
ElementType.METHOD,
|
||||
@@ -15,8 +18,10 @@ import java.lang.annotation.Target;
|
||||
})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface RestPathSegment {
|
||||
public @interface RestResource {
|
||||
|
||||
String value();
|
||||
String path();
|
||||
|
||||
String rel() default "";
|
||||
|
||||
}
|
||||
@@ -11,7 +11,10 @@ import org.springframework.data.rest.repository.RepositoryExporter;
|
||||
import org.springframework.data.rest.repository.RepositoryExporterSupport;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* Abstract class that listens for generic {@link RepositoryEvent}s and dispatches them to a specific
|
||||
* method based on the event type.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public abstract class AbstractRepositoryEventListener<T extends AbstractRepositoryEventListener<? super T>>
|
||||
extends RepositoryExporterSupport<T>
|
||||
@@ -45,16 +48,48 @@ public abstract class AbstractRepositoryEventListener<T extends AbstractReposito
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal beforeSave} events.
|
||||
*
|
||||
* @param entity
|
||||
*/
|
||||
protected void onBeforeSave(Object entity) {}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal afterSave} events.
|
||||
*
|
||||
* @param entity
|
||||
*/
|
||||
protected void onAfterSave(Object entity) {}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal beforeLinkSave} events.
|
||||
*
|
||||
* @param parent
|
||||
* @param linked
|
||||
*/
|
||||
protected void onBeforeLinkSave(Object parent, Object linked) {}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal afterLinkSave} events.
|
||||
*
|
||||
* @param parent
|
||||
* @param linked
|
||||
*/
|
||||
protected void onAfterLinkSave(Object parent, Object linked) {}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal beforeDelete} events.
|
||||
*
|
||||
* @param entity
|
||||
*/
|
||||
protected void onBeforeDelete(Object entity) {}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal afterDelete} events.
|
||||
*
|
||||
* @param entity
|
||||
*/
|
||||
protected void onAfterDelete(Object entity) {}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class AfterDeleteEvent extends RepositoryEvent {
|
||||
public AfterDeleteEvent(Object source) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class AfterLinkSaveEvent extends LinkSaveEvent {
|
||||
public AfterLinkSaveEvent(Object source, Object child) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.springframework.data.rest.repository.context;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class AfterSaveEvent extends RepositoryEvent {
|
||||
public AfterSaveEvent(Object source) {
|
||||
|
||||
@@ -15,20 +15,22 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterLinkSave;
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterDelete;
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterLinkSave;
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterSave;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeLinkSave;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeDelete;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeLinkSave;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeSave;
|
||||
import org.springframework.data.rest.repository.annotation.RepositoryEventHandler;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* {@link ApplicationListener} that will dispatch {@link RepositoryEvent}s to handlers annotated with {@link
|
||||
* RepositoryEventHandler}.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class AnnotatedHandlerRepositoryEventListener
|
||||
implements ApplicationListener<RepositoryEvent>,
|
||||
@@ -43,19 +45,41 @@ public class AnnotatedHandlerRepositoryEventListener
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base package in which to search for event handlers.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getBasePackage() {
|
||||
return basePackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base package in which to search for event handlers.
|
||||
*
|
||||
* @param basePackage
|
||||
* @return
|
||||
*/
|
||||
public AnnotatedHandlerRepositoryEventListener setBasePackage(String basePackage) {
|
||||
this.basePackage = basePackage;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base package in which to search for event handlers.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String basePackage() {
|
||||
return basePackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base package in which to search for event handlers.
|
||||
*
|
||||
* @param basePackage
|
||||
* @return
|
||||
*/
|
||||
public AnnotatedHandlerRepositoryEventListener basePackage(String basePackage) {
|
||||
this.basePackage = basePackage;
|
||||
return this;
|
||||
@@ -67,7 +91,7 @@ public class AnnotatedHandlerRepositoryEventListener
|
||||
for (BeanDefinition beanDef : scanner.findCandidateComponents(basePackage)) {
|
||||
String typeName = beanDef.getBeanClassName();
|
||||
Class<?> handlerType = ClassUtils.forName(typeName, ClassUtils.getDefaultClassLoader());
|
||||
RepositoryEventHandler typeAnno = AnnotationUtils.findAnnotation(handlerType, RepositoryEventHandler.class);
|
||||
RepositoryEventHandler typeAnno = handlerType.getAnnotation(RepositoryEventHandler.class);
|
||||
Class<?>[] targetTypes = typeAnno.value();
|
||||
if (targetTypes.length == 0) {
|
||||
targetTypes = new Class<?>[]{null};
|
||||
@@ -128,7 +152,7 @@ public class AnnotatedHandlerRepositoryEventListener
|
||||
Method method,
|
||||
Class<T> annoType,
|
||||
Class<? extends RepositoryEvent> eventType) {
|
||||
T anno = AnnotationUtils.findAnnotation(method, annoType);
|
||||
T anno = method.getAnnotation(annoType);
|
||||
if (null != anno) {
|
||||
try {
|
||||
Class<?>[] targetTypes;
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.springframework.data.rest.repository.context;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class BeforeDeleteEvent extends RepositoryEvent {
|
||||
public BeforeDeleteEvent(Object source) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class BeforeLinkSaveEvent extends LinkSaveEvent {
|
||||
public BeforeLinkSaveEvent(Object source, Object linked) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.springframework.data.rest.repository.context;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class BeforeSaveEvent extends RepositoryEvent {
|
||||
public BeforeSaveEvent(Object source) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class LinkSaveEvent extends RepositoryEvent {
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.springframework.data.rest.repository.context;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public abstract class RepositoryEvent extends ApplicationEvent {
|
||||
protected RepositoryEvent(Object source) {
|
||||
|
||||
@@ -15,7 +15,10 @@ import org.springframework.validation.ValidationUtils;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* {@link org.springframework.context.ApplicationListener} implementation that dispatches {@link RepositoryEvent}s to a
|
||||
* specific {@link Validator}.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class ValidatingRepositoryEventListener
|
||||
extends AbstractRepositoryEventListener<ValidatingRepositoryEventListener>
|
||||
@@ -43,10 +46,20 @@ public class ValidatingRepositoryEventListener
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Map of {@link Validator}s that are assigned to the various {@link RepositoryEvent}s.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<String, Collection<Validator>> getValidators() {
|
||||
return validators.asMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a Map of {@link Validator}s that are assigned to the various {@link RepositoryEvent}s.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ValidatingRepositoryEventListener setValidators(Map<String, Collection<Validator>> validators) {
|
||||
for (Map.Entry<String, Collection<Validator>> entry : validators.entrySet()) {
|
||||
this.validators.replaceValues(entry.getKey(), entry.getValue());
|
||||
@@ -54,6 +67,13 @@ public class ValidatingRepositoryEventListener
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link Validator} that will be triggered on the given event.
|
||||
*
|
||||
* @param event
|
||||
* @param validator
|
||||
* @return
|
||||
*/
|
||||
public ValidatingRepositoryEventListener addValidator(String event, Validator validator) {
|
||||
validators.put(event, validator);
|
||||
return this;
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.springframework.data.rest.repository.AttributeMetadata;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class JpaAttributeMetadata implements AttributeMetadata {
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.repository.EntityMetadata;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class JpaEntityMetadata implements EntityMetadata<JpaAttributeMetadata> {
|
||||
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
package org.springframework.data.rest.repository.jpa;
|
||||
|
||||
import java.io.Serializable;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.repository.RepositoryExporter;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* Implementation of {@link RepositoryExporter} for exporting JPA {@link Repository} subinterfaces.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class JpaRepositoryExporter extends RepositoryExporter<
|
||||
JpaRepositoryMetadata<Repository<Object, Serializable>>,
|
||||
Repository<Object, Serializable>,
|
||||
JpaEntityMetadata> {
|
||||
|
||||
protected EntityManager entityManager;
|
||||
|
||||
@PersistenceContext
|
||||
public void setEntityManager(EntityManager entityManager) {
|
||||
this.entityManager = entityManager;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override
|
||||
protected JpaRepositoryMetadata<Repository<Object, Serializable>> createRepositoryMetadata(
|
||||
@@ -22,7 +32,7 @@ public class JpaRepositoryExporter extends RepositoryExporter<
|
||||
Repository<Object, Serializable> repo,
|
||||
String name,
|
||||
EntityInformation entityInfo) {
|
||||
return new JpaRepositoryMetadata(new Repositories(applicationContext),
|
||||
return new JpaRepositoryMetadata(repositories,
|
||||
name,
|
||||
repoClass,
|
||||
repo,
|
||||
|
||||
@@ -8,31 +8,33 @@ import java.util.Map;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.metamodel.Metamodel;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.repository.RepositoryMetadata;
|
||||
import org.springframework.data.rest.repository.RepositoryQueryMethod;
|
||||
import org.springframework.data.rest.repository.annotation.RestPathSegment;
|
||||
import org.springframework.data.rest.repository.annotation.RestResource;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class JpaRepositoryMetadata<R extends Repository<Object, Serializable>> implements RepositoryMetadata<R, JpaEntityMetadata> {
|
||||
|
||||
private final String name;
|
||||
private final Class<?> repoClass;
|
||||
private final Class<? extends Repository<? extends Object, ? extends Serializable>> repoClass;
|
||||
private final R repository;
|
||||
private final EntityInformation entityInfo;
|
||||
private final Map<String, RepositoryQueryMethod> queryMethods = new HashMap<String, RepositoryQueryMethod>();
|
||||
|
||||
private String rel;
|
||||
private JpaEntityMetadata entityMetadata;
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public JpaRepositoryMetadata(Repositories repositories,
|
||||
String name,
|
||||
final Class<?> repoClass,
|
||||
final Class<? extends Repository<? extends Object, ? extends Serializable>> repoClass,
|
||||
R repository,
|
||||
EntityInformation entityInfo,
|
||||
EntityManager entityManager) {
|
||||
@@ -41,11 +43,19 @@ public class JpaRepositoryMetadata<R extends Repository<Object, Serializable>> i
|
||||
this.repository = repository;
|
||||
this.entityInfo = entityInfo;
|
||||
|
||||
RestResource resourceAnno = repoClass.getAnnotation(RestResource.class);
|
||||
if (null != resourceAnno && StringUtils.hasText(resourceAnno.rel())) {
|
||||
rel = resourceAnno.rel();
|
||||
} else {
|
||||
rel = name;
|
||||
}
|
||||
|
||||
ReflectionUtils.doWithMethods(
|
||||
repoClass,
|
||||
new ReflectionUtils.MethodCallback() {
|
||||
@Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
String pathSeg = AnnotationUtils.findAnnotation(method, RestPathSegment.class).value();
|
||||
RestResource resourceAnno = method.getAnnotation(RestResource.class);
|
||||
String pathSeg = resourceAnno.path();
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
queryMethods.put(pathSeg, new RepositoryQueryMethod(method));
|
||||
}
|
||||
@@ -56,7 +66,7 @@ public class JpaRepositoryMetadata<R extends Repository<Object, Serializable>> i
|
||||
&& !method.isBridge()
|
||||
&& method.getDeclaringClass() != Object.class
|
||||
&& !method.getName().contains("$")
|
||||
&& null != AnnotationUtils.findAnnotation(method, RestPathSegment.class));
|
||||
&& null != method.getAnnotation(RestResource.class));
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -69,10 +79,18 @@ public class JpaRepositoryMetadata<R extends Repository<Object, Serializable>> i
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override public String rel() {
|
||||
return rel;
|
||||
}
|
||||
|
||||
@Override public Class<? extends Object> domainType() {
|
||||
return entityMetadata.type();
|
||||
}
|
||||
|
||||
@Override public Class<? extends Repository<? extends Object, ? extends Serializable>> repositoryClass() {
|
||||
return repoClass;
|
||||
}
|
||||
|
||||
@Override public R repository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.springframework.data.rest.repository.annotation.HandleBeforeLinkSave
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterLinkSave
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@ContextConfiguration(locations = ["/ExtensionsSpec-test.xml"])
|
||||
class ExtensionsSpec extends Specification {
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.springframework.test.context.ContextConfiguration
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@ContextConfiguration(locations = ["/JpaMetadataSpec-test.xml"])
|
||||
class JpaMetadataSpec extends Specification {
|
||||
|
||||
@@ -7,7 +7,7 @@ import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Entity
|
||||
public class Family {
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.springframework.data.rest.repository.test;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface FamilyRepository extends CrudRepository<Family, Long> {
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Entity
|
||||
public class Person {
|
||||
|
||||
@@ -3,14 +3,14 @@ package org.springframework.data.rest.repository.test;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.rest.repository.annotation.RestPathSegment;
|
||||
import org.springframework.data.rest.repository.annotation.RestResource;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
|
||||
@RestPathSegment("byName")
|
||||
@RestResource(path = "byName")
|
||||
public List<Person> findByName(String name);
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.servlet.view.AbstractView;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public class JsonView extends AbstractView {
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.springframework.data.rest.core.Link;
|
||||
import org.springframework.data.rest.core.SimpleLink;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class Links {
|
||||
|
||||
|
||||
@@ -6,10 +6,11 @@ import java.util.List;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener;
|
||||
import org.springframework.data.rest.repository.jpa.JpaRepositoryExporter;
|
||||
@@ -19,10 +20,11 @@ import org.springframework.http.converter.json.MappingJacksonHttpMessageConverte
|
||||
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* Base configuration for the Spring Data REST Exporter.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Configuration
|
||||
@ImportResource("classpath*:META-INF/spring-data-rest/**/*-export.xml")
|
||||
public class RepositoryRestConfiguration {
|
||||
|
||||
@Autowired
|
||||
@@ -31,12 +33,17 @@ public class RepositoryRestConfiguration {
|
||||
JpaRepositoryExporter jpaRepositoryExporter;
|
||||
@Autowired(required = false)
|
||||
ConversionService customConversionService;
|
||||
ConversionService defaultConversionService = new DefaultConversionService();
|
||||
ConfigurableConversionService defaultConversionService = new DefaultConversionService();
|
||||
@Autowired(required = false)
|
||||
List<HttpMessageConverter<?>> httpMessageConverters = new ArrayList<HttpMessageConverter<?>>();
|
||||
@Autowired(required = false)
|
||||
ValidatingRepositoryEventListener validatingRepositoryEventListener;
|
||||
|
||||
/**
|
||||
* Either the user's pre-configured {@link ConversionService} or the {@link DefaultConversionService}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean ConversionService conversionService() {
|
||||
if (null != customConversionService) {
|
||||
return customConversionService;
|
||||
@@ -45,6 +52,11 @@ public class RepositoryRestConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of {@link HttpMessageConverter}s to be used to read incoming data and to write outgoing responses.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean List<HttpMessageConverter<?>> httpMessageConverters() {
|
||||
if (httpMessageConverters.isEmpty()) {
|
||||
MappingJacksonHttpMessageConverter json = new MappingJacksonHttpMessageConverter();
|
||||
@@ -56,6 +68,11 @@ public class RepositoryRestConfiguration {
|
||||
return httpMessageConverters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export any JPA {@link org.springframework.data.repository.Repository} implementations we find.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean JpaRepositoryExporter jpaRepositoryExporter() {
|
||||
if (null == jpaRepositoryExporter) {
|
||||
jpaRepositoryExporter = new JpaRepositoryExporter();
|
||||
|
||||
@@ -26,6 +26,8 @@ import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.rest.core.Handler;
|
||||
@@ -39,6 +41,7 @@ import org.springframework.data.rest.repository.RepositoryExporter;
|
||||
import org.springframework.data.rest.repository.RepositoryExporterSupport;
|
||||
import org.springframework.data.rest.repository.RepositoryMetadata;
|
||||
import org.springframework.data.rest.repository.RepositoryQueryMethod;
|
||||
import org.springframework.data.rest.repository.annotation.RestResource;
|
||||
import org.springframework.data.rest.repository.context.AfterDeleteEvent;
|
||||
import org.springframework.data.rest.repository.context.AfterLinkSaveEvent;
|
||||
import org.springframework.data.rest.repository.context.AfterSaveEvent;
|
||||
@@ -55,10 +58,12 @@ import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.ui.ExtendedModelMap;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@@ -69,7 +74,7 @@ import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Controller
|
||||
public class RepositoryRestController
|
||||
@@ -90,6 +95,7 @@ public class RepositoryRestController
|
||||
private MediaType jsonMediaType = MediaType.parseMediaType("application/x-spring-data+json");
|
||||
private ConversionService conversionService = new DefaultConversionService();
|
||||
private List<HttpMessageConverter<?>> httpMessageConverters = Collections.emptyList();
|
||||
private Map<String, Handler<Object, Object>> resourceHandlers = Collections.emptyMap();
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
|
||||
@@ -130,6 +136,24 @@ public class RepositoryRestController
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, Handler<Object, Object>> getResourceHandlers() {
|
||||
return resourceHandlers;
|
||||
}
|
||||
|
||||
public RepositoryRestController setResourceHandlers(Map<String, Handler<Object, Object>> resourceHandlers) {
|
||||
this.resourceHandlers = resourceHandlers;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, Handler<Object, Object>> resourceHandlers() {
|
||||
return resourceHandlers;
|
||||
}
|
||||
|
||||
public RepositoryRestController resourceHandlers(Map<String, Handler<Object, Object>> resourceHandlers) {
|
||||
this.resourceHandlers = resourceHandlers;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MediaType getUriListMediaType() {
|
||||
return uriListMediaType;
|
||||
}
|
||||
@@ -199,9 +223,12 @@ public class RepositoryRestController
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
Links links = new Links();
|
||||
for (RepositoryExporter repoMeta : repositoryExporters) {
|
||||
for (String name : (Set<String>) repoMeta.repositoryNames()) {
|
||||
links.add(new SimpleLink(name, buildUri(baseUri, name)));
|
||||
for (RepositoryExporter repoExporter : repositoryExporters) {
|
||||
for (String name : (Set<String>) repoExporter.repositoryNames()) {
|
||||
RepositoryMetadata repoMeta = repoExporter.repositoryMetadataFor(name);
|
||||
String rel = repoMeta.rel();
|
||||
URI path = buildUri(baseUri, name);
|
||||
links.add(new SimpleLink(rel, path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,13 +256,44 @@ public class RepositoryRestController
|
||||
while (iter.hasNext()) {
|
||||
Object o = iter.next();
|
||||
Serializable id = (Serializable) repoMeta.entityMetadata().idAttribute().get(o);
|
||||
links.add(new SimpleLink(repository + "." + o.getClass().getSimpleName(),
|
||||
links.add(new SimpleLink(repoMeta.rel() + "." + o.getClass().getSimpleName(),
|
||||
buildUri(baseUri, repository, id.toString())));
|
||||
}
|
||||
links.add(new SimpleLink(repoMeta.rel() + ".search",
|
||||
buildUri(baseUri, repository, "search")));
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
model.addAttribute(RESOURCE, links);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/{repository}/search",
|
||||
method = RequestMethod.GET,
|
||||
produces = {
|
||||
"application/json"
|
||||
}
|
||||
)
|
||||
public void listQueryMethods(UriComponentsBuilder uriBuilder,
|
||||
@PathVariable String repository,
|
||||
Model model) {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
|
||||
Links links = new Links();
|
||||
|
||||
for (Map.Entry<String, RepositoryQueryMethod> entry : ((Map<String, RepositoryQueryMethod>) repoMeta.queryMethods())
|
||||
.entrySet()) {
|
||||
links.add(new SimpleLink(repository + "." + entry.getKey(),
|
||||
buildUri(baseUri, repository, "search", entry.getKey())));
|
||||
String rel = repoMeta.rel() + "." + entry.getKey();
|
||||
URI path = buildUri(baseUri, repository, "search", entry.getKey());
|
||||
RestResource resourceAnno = entry.getValue().method().getAnnotation(RestResource.class);
|
||||
if (null != resourceAnno) {
|
||||
path = buildUri(baseUri, repository, "search", resourceAnno.path());
|
||||
if (StringUtils.hasText(resourceAnno.rel())) {
|
||||
rel = repoMeta.rel() + "." + resourceAnno.rel();
|
||||
}
|
||||
}
|
||||
links.add(new SimpleLink(rel, path));
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
@@ -266,10 +324,23 @@ public class RepositoryRestController
|
||||
Object[] paramVals = new Object[paramTypes.length];
|
||||
for (int i = 0; i < paramVals.length; i++) {
|
||||
String queryVal = request.getParameter(paramNames[i]);
|
||||
if (paramTypes[i].isAssignableFrom(String.class)) {
|
||||
if (String.class.isAssignableFrom(paramTypes[i])) {
|
||||
// Param type is a String
|
||||
paramVals[i] = queryVal;
|
||||
} else {
|
||||
} else if (Pageable.class.isAssignableFrom(paramTypes[i])) {
|
||||
// Handle paging
|
||||
} else if (Sort.class.isAssignableFrom(paramTypes[i])) {
|
||||
// Handle sorting
|
||||
} else if (conversionService.canConvert(String.class, paramTypes[i])) {
|
||||
// There's a converter from String -> param type
|
||||
paramVals[i] = conversionService.convert(queryVal, paramTypes[i]);
|
||||
} else {
|
||||
// Param type isn't a "simple" type or no converter exists, try JSON
|
||||
try {
|
||||
paramVals[i] = objectMapper.readValue(queryVal, paramTypes[i]);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,8 +351,10 @@ public class RepositoryRestController
|
||||
for (Object o : (Collection) result) {
|
||||
RepositoryMetadata elemRepoMeta = repositoryMetadataFor(o.getClass());
|
||||
if (null != elemRepoMeta) {
|
||||
Map<String, Object> dto = extractPropertiesLinkAware(repository, o, elemRepoMeta.entityMetadata(), baseUri);
|
||||
coll.add(dto);
|
||||
String id = elemRepoMeta.entityMetadata().idAttribute().get(o).toString();
|
||||
String rel = elemRepoMeta.rel() + "." + elemRepoMeta.entityMetadata().type().getSimpleName();
|
||||
URI path = buildUri(baseUri, repository, id);
|
||||
coll.add(new SimpleLink(rel, path));
|
||||
} else {
|
||||
coll.add(o);
|
||||
}
|
||||
@@ -292,11 +365,11 @@ public class RepositoryRestController
|
||||
} else {
|
||||
RepositoryMetadata elemRepoMeta = repositoryMetadataFor(result.getClass());
|
||||
if (null != elemRepoMeta) {
|
||||
Map<String, Object> dto = extractPropertiesLinkAware(repository,
|
||||
result,
|
||||
elemRepoMeta.entityMetadata(),
|
||||
baseUri);
|
||||
model.addAttribute(RESOURCE, dto);
|
||||
String id = elemRepoMeta.entityMetadata().idAttribute().get(result).toString();
|
||||
String rel = elemRepoMeta.rel() + "." + elemRepoMeta.entityMetadata().type().getSimpleName();
|
||||
URI path = buildUri(baseUri, repository, id);
|
||||
Link link = new SimpleLink(rel, path);
|
||||
model.addAttribute(RESOURCE, link);
|
||||
} else {
|
||||
model.addAttribute(RESOURCE, result);
|
||||
}
|
||||
@@ -387,12 +460,10 @@ public class RepositoryRestController
|
||||
headers.set("ETag", "\"" + version.toString() + "\"");
|
||||
}
|
||||
Map<String, Object> entityDto = extractPropertiesLinkAware(repository,
|
||||
repoMeta.rel(),
|
||||
entity,
|
||||
repoMeta.entityMetadata(),
|
||||
UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(repository, id)
|
||||
.build()
|
||||
.toUri());
|
||||
buildUri(baseUri, repository, id));
|
||||
addSelfLink(baseUri, entityDto, repository, id);
|
||||
|
||||
model.addAttribute(HEADERS, headers);
|
||||
@@ -549,14 +620,14 @@ public class RepositoryRestController
|
||||
if (propVal instanceof Collection) {
|
||||
for (Object o : (Collection) propVal) {
|
||||
String propValId = idAttr.get(o).toString();
|
||||
URI uri = buildUri(baseUri, repository, id, property, propValId);
|
||||
links.add(new SimpleLink(repository + "." + entity.getClass()
|
||||
.getSimpleName() + "." + attrType.getSimpleName(), uri));
|
||||
String rel = repository + "." + entity.getClass().getSimpleName() + "." + attrType.getSimpleName();
|
||||
URI path = buildUri(baseUri, repository, id, property, propValId);
|
||||
links.add(new SimpleLink(rel, path));
|
||||
}
|
||||
} else if (propVal instanceof Map) {
|
||||
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) propVal).entrySet()) {
|
||||
String propValId = idAttr.get(entry.getValue()).toString();
|
||||
URI uri = buildUri(baseUri, repository, id, property, propValId);
|
||||
URI path = buildUri(baseUri, repository, id, property, propValId);
|
||||
Object oKey = entry.getKey();
|
||||
String sKey;
|
||||
if (ClassUtils.isAssignable(oKey.getClass(), String.class)) {
|
||||
@@ -564,14 +635,14 @@ public class RepositoryRestController
|
||||
} else {
|
||||
sKey = conversionService.convert(oKey, String.class);
|
||||
}
|
||||
links.add(new SimpleLink(repository + "." + entity.getClass()
|
||||
.getSimpleName() + "." + sKey, uri));
|
||||
String rel = repository + "." + entity.getClass().getSimpleName() + "." + sKey;
|
||||
links.add(new SimpleLink(rel, path));
|
||||
}
|
||||
} else {
|
||||
String propValId = idAttr.get(propVal).toString();
|
||||
URI uri = buildUri(baseUri, repository, id, property, propValId);
|
||||
links.add(new SimpleLink(repository + "." + entity.getClass()
|
||||
.getSimpleName() + "." + property, uri));
|
||||
String rel = repository + "." + entity.getClass().getSimpleName() + "." + property;
|
||||
URI path = buildUri(baseUri, repository, id, property, propValId);
|
||||
links.add(new SimpleLink(rel, path));
|
||||
}
|
||||
model.addAttribute(RESOURCE, links);
|
||||
} else {
|
||||
@@ -597,12 +668,12 @@ public class RepositoryRestController
|
||||
"text/uri-list"
|
||||
}
|
||||
)
|
||||
public void updateLinks(final ServerHttpRequest request,
|
||||
UriComponentsBuilder uriBuilder,
|
||||
@PathVariable String repository,
|
||||
@PathVariable String id,
|
||||
final @PathVariable String property,
|
||||
final Model model) throws IOException {
|
||||
public void updatePropertyOfEntity(final ServerHttpRequest request,
|
||||
UriComponentsBuilder uriBuilder,
|
||||
@PathVariable String repository,
|
||||
@PathVariable String id,
|
||||
final @PathVariable String property,
|
||||
final Model model) throws IOException {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
final RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
|
||||
@@ -625,7 +696,7 @@ public class RepositoryRestController
|
||||
@Override public Void handle(Object linkedEntity) {
|
||||
if (attrMeta.isCollectionLike()) {
|
||||
Collection c = new ArrayList();
|
||||
Collection current = (Collection) attrMeta.get(entity);
|
||||
Collection current = attrMeta.asCollection(entity);
|
||||
if (request.getMethod() == HttpMethod.POST && null != current) {
|
||||
c.addAll(current);
|
||||
}
|
||||
@@ -633,7 +704,7 @@ public class RepositoryRestController
|
||||
attrMeta.set(c, entity);
|
||||
} else if (attrMeta.isSetLike()) {
|
||||
Set s = new HashSet();
|
||||
Set current = (Set) attrMeta.get(entity);
|
||||
Set current = attrMeta.asSet(entity);
|
||||
if (request.getMethod() == HttpMethod.POST && null != current) {
|
||||
s.addAll(current);
|
||||
}
|
||||
@@ -641,7 +712,7 @@ public class RepositoryRestController
|
||||
attrMeta.set(s, entity);
|
||||
} else if (attrMeta.isMapLike()) {
|
||||
Map m = new HashMap();
|
||||
Map current = (Map) attrMeta.get(entity);
|
||||
Map current = attrMeta.asMap(entity);
|
||||
if (request.getMethod() == HttpMethod.POST && null != current) {
|
||||
m.putAll(current);
|
||||
}
|
||||
@@ -671,7 +742,9 @@ public class RepositoryRestController
|
||||
}
|
||||
}
|
||||
} else if (jsonMediaType.equals(incomingMediaType)) {
|
||||
final Map<String, List<Map<String, String>>> incoming = readIncoming(request, incomingMediaType, Map.class);
|
||||
final Map<String, List<Map<String, String>>> incoming = readIncoming(request,
|
||||
incomingMediaType,
|
||||
Map.class);
|
||||
for (Map<String, String> link : incoming.get(LINKS)) {
|
||||
String sLinkUri = link.get("href");
|
||||
Object o = resolveTopLevelResource(baseUri, sLinkUri);
|
||||
@@ -782,6 +855,7 @@ public class RepositoryRestController
|
||||
Object linkedEntity = linkedRepo.findOne(sChildId);
|
||||
if (null != linkedEntity) {
|
||||
Map<String, Object> entityDto = extractPropertiesLinkAware(repository,
|
||||
linkedRepoMeta.rel(),
|
||||
linkedEntity,
|
||||
linkedRepoMeta.entityMetadata(),
|
||||
baseUri);
|
||||
@@ -969,7 +1043,8 @@ public class RepositoryRestController
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private Map<String, Object> extractPropertiesLinkAware(String repository,
|
||||
private Map<String, Object> extractPropertiesLinkAware(String repoName,
|
||||
String repoRel,
|
||||
Object entity,
|
||||
EntityMetadata<AttributeMetadata> entityMetadata,
|
||||
URI baseUri) {
|
||||
@@ -988,7 +1063,7 @@ public class RepositoryRestController
|
||||
.pathSegment(attrName)
|
||||
.build()
|
||||
.toUri();
|
||||
Link l = new SimpleLink(repository + "." + entity.getClass().getSimpleName() + "." + attrName, uri);
|
||||
Link l = new SimpleLink(repoRel + "." + entity.getClass().getSimpleName() + "." + attrName, uri);
|
||||
List<Link> links = (List<Link>) entityDto.get(LINKS);
|
||||
if (null == links) {
|
||||
links = new ArrayList<Link>();
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
|
||||
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Configuration
|
||||
public class RepositoryRestMvcConfiguration {
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class ServerHttpRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.servlet.view.AbstractView;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class UriListView extends AbstractView {
|
||||
|
||||
|
||||
@@ -4,13 +4,11 @@
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
|
||||
version="2.5">
|
||||
|
||||
<context-param>
|
||||
<param-name>contextClass</param-name>
|
||||
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
|
||||
</context-param>
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>org.springframework.data.rest.webmvc.RepositoryRestConfiguration</param-value>
|
||||
<param-value>
|
||||
classpath*:META-INF/spring-data-rest/**/*-export.xml
|
||||
</param-value>
|
||||
</context-param>
|
||||
|
||||
<listener>
|
||||
@@ -26,7 +24,10 @@
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration</param-value>
|
||||
<param-value>
|
||||
org.springframework.data.rest.webmvc.RepositoryRestConfiguration
|
||||
org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration
|
||||
</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
@@ -2,12 +2,9 @@ package org.springframework.data.rest.webmvc.spec
|
||||
|
||||
import org.codehaus.jackson.map.ObjectMapper
|
||||
import org.codehaus.jackson.map.ser.CustomSerializerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext
|
||||
import org.springframework.data.rest.core.SimpleLink
|
||||
import org.springframework.data.rest.core.util.FluentBeanSerializer
|
||||
import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener
|
||||
import org.springframework.data.rest.test.webmvc.Address
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestConfiguration
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestController
|
||||
@@ -15,26 +12,25 @@ import org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.server.ServletServerHttpRequest
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.mock.web.MockServletConfig
|
||||
import org.springframework.mock.web.MockServletContext
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.ui.ExtendedModelMap
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext
|
||||
import org.springframework.web.util.UriComponentsBuilder
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@ContextConfiguration(classes = [RepositoryRestConfiguration, RepositoryRestMvcConfiguration, RepositorySpecConfig])
|
||||
class RepositoryRestControllerSpec extends Specification {
|
||||
|
||||
@Shared
|
||||
UriComponentsBuilder uriBuilder
|
||||
@Shared
|
||||
ObjectMapper mapper = new ObjectMapper()
|
||||
@Autowired
|
||||
URI baseUri
|
||||
@Autowired
|
||||
@Shared
|
||||
RepositoryRestController controller
|
||||
|
||||
MockHttpServletRequest createRequest(String method, String path) {
|
||||
@@ -46,6 +42,14 @@ class RepositoryRestControllerSpec extends Specification {
|
||||
}
|
||||
|
||||
def setupSpec() {
|
||||
def appCtx = new ClassPathXmlApplicationContext("classpath*:META-INF/spring-data-rest/**/*-export.xml")
|
||||
def webAppCtx = new AnnotationConfigWebApplicationContext()
|
||||
webAppCtx.setServletConfig(new MockServletConfig())
|
||||
webAppCtx.setServletContext(new MockServletContext())
|
||||
webAppCtx.setConfigLocations([RepositoryRestConfiguration.name, RepositoryRestMvcConfiguration.name] as String[])
|
||||
webAppCtx.setParent(appCtx)
|
||||
webAppCtx.afterPropertiesSet()
|
||||
controller = webAppCtx.getBean(RepositoryRestController)
|
||||
uriBuilder = UriComponentsBuilder.fromUriString("http://localhost:8080/data")
|
||||
def customSerializerFactory = new CustomSerializerFactory()
|
||||
customSerializerFactory.addSpecificMapping(SimpleLink, new FluentBeanSerializer(SimpleLink))
|
||||
@@ -68,40 +72,40 @@ class RepositoryRestControllerSpec extends Specification {
|
||||
|
||||
when: "adding an entity"
|
||||
model.clear()
|
||||
def req = createRequest("POST", "person")
|
||||
def req = createRequest("POST", "people")
|
||||
def data = mapper.writeValueAsBytes([name: "John Doe"])
|
||||
req.content = data
|
||||
controller.create(new ServletServerHttpRequest(req), uriBuilder, "person", model)
|
||||
controller.create(new ServletServerHttpRequest(req), uriBuilder, "people", model)
|
||||
|
||||
then:
|
||||
model.status == HttpStatus.CREATED
|
||||
|
||||
when: "getting specific entity"
|
||||
model.clear()
|
||||
req = createRequest("GET", "person/1")
|
||||
controller.entity(new ServletServerHttpRequest(req), uriBuilder, "person", "1", model)
|
||||
req = createRequest("GET", "people/1")
|
||||
controller.entity(new ServletServerHttpRequest(req), uriBuilder, "people", "1", model)
|
||||
|
||||
then:
|
||||
model.resource?.name == "John Doe"
|
||||
|
||||
when: "updating an entity"
|
||||
model.clear()
|
||||
req = createRequest("PUT", "person/1")
|
||||
req = createRequest("PUT", "people/1")
|
||||
data = mapper.writeValueAsBytes([name: "Johnnie Doe", version: 0])
|
||||
req.content = data
|
||||
controller.createOrUpdate(new ServletServerHttpRequest(req), uriBuilder, "person", "1", model)
|
||||
controller.createOrUpdate(new ServletServerHttpRequest(req), uriBuilder, "people", "1", model)
|
||||
|
||||
then:
|
||||
model.status == HttpStatus.NO_CONTENT
|
||||
|
||||
when: "listing available entities"
|
||||
model.clear()
|
||||
controller.listEntities(uriBuilder, "person", model)
|
||||
def personsLinks = model.resource?.links
|
||||
controller.listEntities(uriBuilder, "people", model)
|
||||
def peopleLinks = model.resource?.links
|
||||
|
||||
then:
|
||||
model.status == HttpStatus.OK
|
||||
personsLinks[0].href().toString() == "http://localhost:8080/data/person/1"
|
||||
peopleLinks[0].href().toString() == "http://localhost:8080/data/people/1"
|
||||
|
||||
when: "creating child entity"
|
||||
model.clear()
|
||||
@@ -115,18 +119,18 @@ class RepositoryRestControllerSpec extends Specification {
|
||||
|
||||
when: "linking child to parent entity"
|
||||
model.clear()
|
||||
req = createRequest("POST", "person/1/addresses")
|
||||
req = createRequest("POST", "people/1/addresses")
|
||||
req.contentType = "text/uri-list"
|
||||
data = "http://localhost:8080/data/address/1".bytes
|
||||
req.content = data
|
||||
controller.updateLinks(new ServletServerHttpRequest(req), uriBuilder, "person", "1", "addresses", model)
|
||||
controller.updatePropertyOfEntity(new ServletServerHttpRequest(req), uriBuilder, "people", "1", "addresses", model)
|
||||
|
||||
then:
|
||||
model.status == HttpStatus.CREATED
|
||||
|
||||
when: "getting property of entity"
|
||||
model.clear()
|
||||
controller.propertyOfEntity(uriBuilder, "person", "1", "addresses", model)
|
||||
controller.propertyOfEntity(uriBuilder, "people", "1", "addresses", model)
|
||||
def addrLinks = model.resource?.links
|
||||
|
||||
then:
|
||||
@@ -136,12 +140,3 @@ class RepositoryRestControllerSpec extends Specification {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
class RepositorySpecConfig {
|
||||
|
||||
@Bean ValidatingRepositoryEventListener validator() {
|
||||
new ValidatingRepositoryEventListener()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import org.springframework.web.client.DefaultResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class RestBuilder {
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Entity
|
||||
public class Address {
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.springframework.data.rest.test.webmvc;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface AddressRepository extends CrudRepository<Address, Long> {
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import javax.persistence.OneToMany;
|
||||
import javax.persistence.Version;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Entity
|
||||
public class Person {
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.Map;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class PersonLoader implements InitializingBean {
|
||||
|
||||
|
||||
@@ -4,15 +4,15 @@ import java.util.List;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.rest.repository.annotation.RestPathSegment;
|
||||
import org.springframework.data.rest.repository.annotation.RestResource;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@RestPathSegment("person")
|
||||
@RestResource(path = "people", rel = "peeps")
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
|
||||
@RestPathSegment("byName")
|
||||
@RestResource(path = "name", rel = "names")
|
||||
public List<Person> findByName(@Param("name") String name);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import org.springframework.validation.ValidationUtils;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class PersonValidator implements Validator {
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Entity
|
||||
public class Profile {
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.springframework.data.rest.test.webmvc;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface ProfileRepository extends CrudRepository<Profile, Long> {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user