Massive update that reworks the entire metadata system.
This commit is contained in:
@@ -14,8 +14,8 @@ allprojects {
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url "http://repo.springframework.org/libs-milestone" }
|
||||
maven { url "http://repo.springframework.org/libs-release" }
|
||||
maven { url "http://repo.springframework.org/milestone" }
|
||||
maven { url "http://repo.springframework.org/release" }
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
@@ -200,3 +200,6 @@ To maintain a relationship between two entities, access the resource of the rela
|
||||
You can also delete a relationship by issuing a DELETE request to the resource path that represents the relationship between parent and child entities. For example, to delete a relationship between a Profile entity with an id of 2 and a Person with an id of 1:
|
||||
|
||||
curl -v -X DELETE http://localhost:8080/data/person/1/profiles/2
|
||||
|
||||
### Handling events
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Logging
|
||||
slf4jVersion = 1.6.4
|
||||
logbackVersion = 1.0.0
|
||||
logbackVersion = 1.0.1
|
||||
|
||||
# Libraries
|
||||
springVersion = 3.1.1.RELEASE
|
||||
@@ -10,10 +10,10 @@ cglibVersion = 2.2
|
||||
groovyVersion = 1.8.6
|
||||
|
||||
# Supporting libraries
|
||||
sdCommonsVersion = 1.2.0.RELEASE
|
||||
sdJpaVersion = 1.0.1.RELEASE
|
||||
jacksonVersion = 1.9.2
|
||||
hibernateVersion = 3.5.6-Final
|
||||
sdCommonsVersion = 1.3.0.RC1
|
||||
sdJpaVersion = 1.1.0.RC1
|
||||
jacksonVersion = 1.9.5
|
||||
hibernateVersion = 4.1.1.Final
|
||||
|
||||
# Testing
|
||||
spockVersion = 0.5-groovy-1.8
|
||||
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Tue Mar 27 08:53:14 CDT 2012
|
||||
#Fri Apr 27 15:44:36 CDT 2012
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.0-milestone-9-bin.zip
|
||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.0-rc-1-bin.zip
|
||||
|
||||
@@ -17,6 +17,6 @@ dependencies {
|
||||
|
||||
// Testing
|
||||
testCompile "org.hibernate:hibernate-entitymanager:$hibernateVersion"
|
||||
testCompile "org.hsqldb:hsqldb:1.8.0.10"
|
||||
testCompile "org.hsqldb:hsqldb:2.2.8"
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public interface AttributeMetadata {
|
||||
|
||||
String name();
|
||||
|
||||
Class<?> type();
|
||||
|
||||
Class<?> elementType();
|
||||
|
||||
boolean isCollectionLike();
|
||||
|
||||
Collection<?> asCollection(Object target);
|
||||
|
||||
boolean isSetLike();
|
||||
|
||||
Set<?> asSet(Object target);
|
||||
|
||||
boolean isMapLike();
|
||||
|
||||
Map asMap(Object target);
|
||||
|
||||
Object get(Object target);
|
||||
|
||||
AttributeMetadata set(Object value, Object target);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public interface EntityMetadata<A extends AttributeMetadata> {
|
||||
|
||||
Class<?> type();
|
||||
|
||||
Map<String, A> embeddedAttributes();
|
||||
|
||||
Map<String, A> linkedAttributes();
|
||||
|
||||
A idAttribute();
|
||||
|
||||
A versionAttribute();
|
||||
|
||||
A attribute(String name);
|
||||
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.persistence.metamodel.Attribute;
|
||||
import javax.persistence.metamodel.EntityType;
|
||||
import javax.persistence.metamodel.PluralAttribute;
|
||||
import javax.persistence.metamodel.SingularAttribute;
|
||||
|
||||
import org.springframework.data.rest.core.Handler;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class JpaEntityMetadata {
|
||||
|
||||
private Class<?> targetType;
|
||||
private Map<String, Attribute> embeddedAttributes = new HashMap<String, Attribute>();
|
||||
private Map<String, Field> fields = new HashMap<String, Field>();
|
||||
private Map<String, Method> setters = new HashMap<String, Method>();
|
||||
private Map<String, Method> getters = new HashMap<String, Method>();
|
||||
private Map<String, Attribute> linkedAttributes = new HashMap<String, Attribute>();
|
||||
private final Attribute idAttribute;
|
||||
private final Attribute versionAttribute;
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public JpaEntityMetadata(EntityType entityType, JpaRepositoryMetadata repositoryMetadata) {
|
||||
targetType = entityType.getJavaType();
|
||||
|
||||
Attribute idAttribute = entityType.getId(entityType.getIdType().getJavaType());
|
||||
Attribute versionAttribute = entityType.getVersion(Long.class);
|
||||
for (Attribute attr : (Set<Attribute>) entityType.getAttributes()) {
|
||||
String name = attr.getName();
|
||||
Field f = ReflectionUtils.findField(targetType, attr.getName());
|
||||
ReflectionUtils.makeAccessible(f);
|
||||
fields.put(name, f);
|
||||
Method setter = null;
|
||||
try {
|
||||
setter = targetType.getMethod("set" + StringUtils.capitalize(name), attr.getJavaType());
|
||||
} catch (NoSuchMethodException e) {}
|
||||
if (null != setter) {
|
||||
setters.put(name, setter);
|
||||
}
|
||||
Method getter = null;
|
||||
try {
|
||||
getter = targetType.getMethod("get" + StringUtils.capitalize(name));
|
||||
} catch (NoSuchMethodException e) {}
|
||||
if (null != setter) {
|
||||
getters.put(name, getter);
|
||||
}
|
||||
|
||||
if (attr instanceof SingularAttribute) {
|
||||
SingularAttribute sattr = (SingularAttribute) attr;
|
||||
if (null != repositoryMetadata.repositoryFor(attr.getJavaType())) {
|
||||
linkedAttributes.put(name, attr);
|
||||
} else if (!sattr.isId() && !sattr.isVersion()) {
|
||||
embeddedAttributes.put(name, attr);
|
||||
}
|
||||
} else if (attr instanceof PluralAttribute) {
|
||||
PluralAttribute pattr = (PluralAttribute) attr;
|
||||
if (pattr.getElementType() instanceof EntityType
|
||||
&& null != repositoryMetadata.repositoryFor(pattr.getElementType().getJavaType())) {
|
||||
linkedAttributes.put(name, attr);
|
||||
} else {
|
||||
embeddedAttributes.put(name, attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.idAttribute = idAttribute;
|
||||
this.versionAttribute = versionAttribute;
|
||||
}
|
||||
|
||||
public Class<?> targetType() {
|
||||
return targetType;
|
||||
}
|
||||
|
||||
public Map<String, Attribute> embeddedAttributes() {
|
||||
return Collections.unmodifiableMap(embeddedAttributes);
|
||||
}
|
||||
|
||||
public Map<String, Attribute> linkedAttributes() {
|
||||
return Collections.unmodifiableMap(linkedAttributes);
|
||||
}
|
||||
|
||||
public Attribute idAttribute() {
|
||||
return idAttribute;
|
||||
}
|
||||
|
||||
public Attribute versionAttribute() {
|
||||
return versionAttribute;
|
||||
}
|
||||
|
||||
public void id(Serializable id, Object target) {
|
||||
set(idAttribute.getName(), id, target);
|
||||
}
|
||||
|
||||
public Object id(Object target) {
|
||||
return get(idAttribute.getName(), target);
|
||||
}
|
||||
|
||||
public Object version(Object target) {
|
||||
return (null != versionAttribute ? get(versionAttribute.getName(), target) : null);
|
||||
}
|
||||
|
||||
public <V> V doWithEmbedded(Handler<Attribute, V> handler) {
|
||||
if (null == handler) {
|
||||
return null;
|
||||
}
|
||||
V v = null;
|
||||
for (Attribute attr : embeddedAttributes.values()) {
|
||||
v = handler.handle(attr);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
public <V> V doWithLinked(String name, Handler<Attribute, V> handler) {
|
||||
if (null == handler) {
|
||||
return null;
|
||||
}
|
||||
V v = null;
|
||||
Attribute attr = linkedAttributes.get(name);
|
||||
if (null != attr) {
|
||||
v = handler.handle(attr);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
public <V> V doWithLinked(Handler<Attribute, V> handler) {
|
||||
if (null == handler) {
|
||||
return null;
|
||||
}
|
||||
V v = null;
|
||||
for (Attribute attr : linkedAttributes.values()) {
|
||||
v = handler.handle(attr);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
public Object get(String name, Object target) {
|
||||
try {
|
||||
Method getter = getters.get(name);
|
||||
if (null != getter) {
|
||||
return getter.invoke(target);
|
||||
} else {
|
||||
Field f = fields.get(name);
|
||||
return (null != f ? f.get(target) : null);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void set(String name, Object arg, Object target) {
|
||||
try {
|
||||
Method setter = setters.get(name);
|
||||
if (null != setter) {
|
||||
setter.invoke(target, arg);
|
||||
} else {
|
||||
Field f = fields.get(name);
|
||||
if (null != f) {
|
||||
f.set(target, arg);
|
||||
}
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.metamodel.EntityType;
|
||||
import javax.persistence.metamodel.Metamodel;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.aop.target.SingletonTargetSource;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class JpaRepositoryMetadata
|
||||
implements InitializingBean,
|
||||
ApplicationContextAware {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
private Map<Class<?>, RepositoryCacheEntry> repositories = new HashMap<Class<?>, RepositoryCacheEntry>();
|
||||
private EntityManager entityManager;
|
||||
private Metamodel metamodel;
|
||||
|
||||
@PersistenceContext
|
||||
public void setEntityManager(EntityManager entityManager) {
|
||||
this.entityManager = entityManager;
|
||||
this.metamodel = entityManager.getMetamodel();
|
||||
}
|
||||
|
||||
public EntityManager entityManager() {
|
||||
return this.entityManager;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public <T> CrudRepository<T, ? extends Serializable> repositoryFor(String name) {
|
||||
if (null != name) {
|
||||
for (Map.Entry<Class<?>, RepositoryCacheEntry> entry : repositories.entrySet()) {
|
||||
if (name.equals(repositoryNameFor(entry.getValue().repository))) {
|
||||
return entry.getValue().repository;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public <T> CrudRepository<T, ? extends Serializable> repositoryFor(Class<T> domainClass) {
|
||||
RepositoryCacheEntry entry = repositories.get(domainClass);
|
||||
if (null != entry) {
|
||||
return entry.repository;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public <T> EntityInformation<T, ? extends Serializable> entityInfoFor(Class<T> domainClass) {
|
||||
RepositoryCacheEntry entry = repositories.get(domainClass);
|
||||
if (null != entry) {
|
||||
return entry.entityInfo;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T> EntityType<T> entityTypeFor(Class<T> domainClass) {
|
||||
return metamodel.entity(domainClass);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public <T> EntityInformation<T, ? extends Serializable> entityInfoFor(CrudRepository<T, ? extends Serializable> repository) {
|
||||
for (Map.Entry<Class<?>, RepositoryCacheEntry> entry : repositories.entrySet()) {
|
||||
if (entry.getValue().repository == repository) {
|
||||
return entry.getValue().entityInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T> JpaEntityMetadata entityMetadataFor(Class<T> domainClass) {
|
||||
RepositoryCacheEntry entry = repositories.get(domainClass);
|
||||
if (null == entry.entityMetadata) {
|
||||
entry.entityMetadata = new JpaEntityMetadata(metamodel.entity(domainClass), this);
|
||||
}
|
||||
return entry.entityMetadata;
|
||||
}
|
||||
|
||||
public String repositoryNameFor(Class<?> domainClass) {
|
||||
RepositoryCacheEntry entry = repositories.get(domainClass);
|
||||
if (null != entry) {
|
||||
return entry.name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String repositoryNameFor(CrudRepository repository) {
|
||||
for (Map.Entry<Class<?>, RepositoryCacheEntry> entry : repositories.entrySet()) {
|
||||
if (entry.getValue().repository == repository) {
|
||||
return entry.getValue().name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public List<String> repositoryNames() {
|
||||
List<String> names = new ArrayList<String>();
|
||||
for (Map.Entry<Class<?>, RepositoryCacheEntry> entry : repositories.entrySet()) {
|
||||
names.add(entry.getValue().name);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
public void setRepositories(Map<String, CrudRepository> repositories) {
|
||||
for (Map.Entry<String, CrudRepository> entry : repositories.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
CrudRepository repository = entry.getValue();
|
||||
Class<?> repoClass = AopUtils.getTargetClass(repository);
|
||||
Field infoField = ReflectionUtils.findField(repoClass, "entityInformation");
|
||||
ReflectionUtils.makeAccessible(infoField);
|
||||
Method m = ReflectionUtils.findMethod(repository.getClass(), "getTargetSource");
|
||||
ReflectionUtils.makeAccessible(m);
|
||||
try {
|
||||
SingletonTargetSource targetRepo = (SingletonTargetSource) m.invoke(repository);
|
||||
EntityInformation entityInfo = (EntityInformation) infoField.get(targetRepo.getTarget());
|
||||
Class<?>[] intfs = repository.getClass().getInterfaces();
|
||||
//String name = StringUtils.uncapitalize(intfs[0].getSimpleName().replaceAll("Repository", ""));
|
||||
if (name.contains("Repository")) {
|
||||
name = name.replaceAll("Repository", "");
|
||||
}
|
||||
this.repositories.put(entityInfo.getJavaType(), new RepositoryCacheEntry(name, repository, entityInfo, null));
|
||||
} catch (Throwable t) {
|
||||
throw new IllegalStateException(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
if (this.repositories.isEmpty()) {
|
||||
ApplicationContext appCtx = applicationContext;
|
||||
while (null != appCtx) {
|
||||
Map<String, CrudRepository> beans = appCtx.getBeansOfType(CrudRepository.class);
|
||||
setRepositories(beans);
|
||||
appCtx = appCtx.getParent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RepositoryCacheEntry {
|
||||
String name;
|
||||
CrudRepository repository;
|
||||
EntityInformation entityInfo;
|
||||
JpaEntityMetadata entityMetadata;
|
||||
|
||||
private RepositoryCacheEntry(String name,
|
||||
CrudRepository repository,
|
||||
EntityInformation entityInfo,
|
||||
JpaEntityMetadata entityMetadata) {
|
||||
this.name = name;
|
||||
this.repository = repository;
|
||||
this.entityInfo = entityInfo;
|
||||
this.entityMetadata = entityMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
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.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public abstract class RepositoryExporter<M extends RepositoryMetadata<R, E>,
|
||||
R extends Repository<? extends Object, ? extends Serializable>,
|
||||
E extends EntityMetadata<? extends AttributeMetadata>>
|
||||
implements ApplicationContextAware,
|
||||
InitializingBean {
|
||||
|
||||
protected ApplicationContext applicationContext;
|
||||
protected EntityManager entityManager;
|
||||
protected Map<String, M> repositoryMetadata;
|
||||
|
||||
@PersistenceContext
|
||||
public void setEntityManager(EntityManager entityManager) {
|
||||
this.entityManager = entityManager;
|
||||
}
|
||||
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
}
|
||||
|
||||
public Set<String> repositoryNames() {
|
||||
maybeCacheRepositoryFactoryInfo();
|
||||
return repositoryMetadata.keySet();
|
||||
}
|
||||
|
||||
public boolean hasRepositoryFor(Class<?> domainType) {
|
||||
maybeCacheRepositoryFactoryInfo();
|
||||
for (M repoMeta : repositoryMetadata.values()) {
|
||||
if (repoMeta.domainType().isAssignableFrom(domainType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public M repositoryMetadataFor(Class<?> domainType) {
|
||||
maybeCacheRepositoryFactoryInfo();
|
||||
for (M repoMeta : repositoryMetadata.values()) {
|
||||
if (repoMeta.domainType().isAssignableFrom(domainType)) {
|
||||
return repoMeta;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public M repositoryMetadataFor(String name) {
|
||||
maybeCacheRepositoryFactoryInfo();
|
||||
return repositoryMetadata.get(name);
|
||||
}
|
||||
|
||||
protected abstract M createRepositoryMetadata(
|
||||
Class repoClass,
|
||||
R repo,
|
||||
String name,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public abstract class RepositoryExporterSupport<S extends RepositoryExporterSupport<? super S>> {
|
||||
|
||||
@Autowired
|
||||
protected List<RepositoryExporter> repositoryExporters = Collections.emptyList();
|
||||
|
||||
public List<RepositoryExporter> getRepositoryExporters() {
|
||||
return repositoryExporters;
|
||||
}
|
||||
|
||||
public void setRepositoryExporters(List<RepositoryExporter> repositoryExporters) {
|
||||
this.repositoryExporters = repositoryExporters;
|
||||
}
|
||||
|
||||
public List<RepositoryExporter> repositoryExporters() {
|
||||
return repositoryExporters;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public S repositoryExporters(List<RepositoryExporter> repositoryExporters) {
|
||||
this.repositoryExporters = repositoryExporters;
|
||||
return (S) this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
protected RepositoryMetadata repositoryMetadataFor(String name) {
|
||||
for (RepositoryExporter exporter : repositoryExporters) {
|
||||
RepositoryMetadata repoMeta = exporter.repositoryMetadataFor(name);
|
||||
if (null != repoMeta) {
|
||||
return repoMeta;
|
||||
}
|
||||
}
|
||||
throw new RepositoryNotFoundException("No repository found for name " + name);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
protected RepositoryMetadata repositoryMetadataFor(Class<?> domainType) {
|
||||
for (RepositoryExporter exporter : repositoryExporters) {
|
||||
RepositoryMetadata repoMeta = exporter.repositoryMetadataFor(domainType);
|
||||
if (null != repoMeta) {
|
||||
return repoMeta;
|
||||
}
|
||||
}
|
||||
throw new RepositoryNotFoundException("No repository found for type " + domainType.getName());
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
protected RepositoryMetadata repositoryMetadataFor(AttributeMetadata attrMeta) {
|
||||
if (attrMeta.isCollectionLike() || attrMeta.isMapLike()) {
|
||||
return repositoryMetadataFor(attrMeta.elementType());
|
||||
} else {
|
||||
return repositoryMetadataFor(attrMeta.type());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public interface RepositoryMetadata<R extends Repository<? extends Object, ? extends Serializable>, E extends EntityMetadata<? extends AttributeMetadata>> {
|
||||
|
||||
String name();
|
||||
|
||||
Class<? extends Object> domainType();
|
||||
|
||||
R repository();
|
||||
|
||||
E entityMetadata();
|
||||
|
||||
Method queryMethod(String key);
|
||||
|
||||
Map<String, Method> queryMethods();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class RepositoryNotFoundException extends DataAccessResourceFailureException {
|
||||
|
||||
public RepositoryNotFoundException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public RepositoryNotFoundException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,11 +15,11 @@ public class ValidationErrors extends AbstractErrors {
|
||||
|
||||
private String name;
|
||||
private Object entity;
|
||||
private JpaEntityMetadata entityMetadata;
|
||||
private EntityMetadata entityMetadata;
|
||||
private List<ObjectError> globalErrors = new ArrayList<ObjectError>();
|
||||
private List<FieldError> fieldErrors = new ArrayList<FieldError>();
|
||||
|
||||
public ValidationErrors(String name, Object entity, JpaEntityMetadata entityMetadata) {
|
||||
public ValidationErrors(String name, Object entity, EntityMetadata entityMetadata) {
|
||||
this.name = name;
|
||||
this.entity = entity;
|
||||
this.entityMetadata = entityMetadata;
|
||||
@@ -56,6 +56,6 @@ public class ValidationErrors extends AbstractErrors {
|
||||
}
|
||||
|
||||
@Override public Object getFieldValue(String field) {
|
||||
return entityMetadata.get(field, entity);
|
||||
return entityMetadata.attribute(field).get(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.springframework.data.rest.repository.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@Target({
|
||||
ElementType.METHOD,
|
||||
ElementType.TYPE
|
||||
})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface RestPathSegment {
|
||||
|
||||
String value();
|
||||
|
||||
}
|
||||
@@ -1,43 +1,32 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.data.rest.repository.JpaRepositoryMetadata;
|
||||
import org.springframework.data.rest.repository.RepositoryExporter;
|
||||
import org.springframework.data.rest.repository.RepositoryExporterSupport;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public abstract class AbstractRepositoryEventListener<T extends AbstractRepositoryEventListener<? super T>>
|
||||
extends RepositoryExporterSupport<T>
|
||||
implements ApplicationListener<RepositoryEvent>,
|
||||
ApplicationContextAware {
|
||||
|
||||
@Autowired
|
||||
protected JpaRepositoryMetadata repositoryMetadata;
|
||||
protected ApplicationContext applicationContext;
|
||||
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public JpaRepositoryMetadata getRepositoryMetadata() {
|
||||
return repositoryMetadata;
|
||||
}
|
||||
|
||||
public void setRepositoryMetadata(JpaRepositoryMetadata repositoryMetadata) {
|
||||
this.repositoryMetadata = repositoryMetadata;
|
||||
}
|
||||
|
||||
public JpaRepositoryMetadata repositoryMetadata() {
|
||||
return repositoryMetadata;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public T repositoryMetadata(JpaRepositoryMetadata repositoryMetadata) {
|
||||
this.repositoryMetadata = repositoryMetadata;
|
||||
return (T) this;
|
||||
@Autowired
|
||||
public void setRepositoryExporters(List<RepositoryExporter> repositoryExporters) {
|
||||
super.setRepositoryExporters(repositoryExporters);
|
||||
}
|
||||
|
||||
@Override public final void onApplicationEvent(RepositoryEvent event) {
|
||||
|
||||
@@ -86,9 +86,10 @@ public class ValidatingRepositoryEventListener
|
||||
private Errors validate(String event, Object entity) {
|
||||
Errors errors = null;
|
||||
if (null != entity) {
|
||||
errors = new ValidationErrors(entity.getClass().getSimpleName(),
|
||||
Class<?> domainType = entity.getClass();
|
||||
errors = new ValidationErrors(domainType.getSimpleName(),
|
||||
entity,
|
||||
repositoryMetadata.entityMetadataFor(entity.getClass()));
|
||||
repositoryMetadataFor(domainType).entityMetadata());
|
||||
Collection<Validator> validators = this.validators.get(event);
|
||||
if (null != validators) {
|
||||
for (Validator v : validators) {
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
package org.springframework.data.rest.repository.jpa;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.persistence.metamodel.Attribute;
|
||||
import javax.persistence.metamodel.EntityType;
|
||||
import javax.persistence.metamodel.PluralAttribute;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.rest.repository.AttributeMetadata;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class JpaAttributeMetadata implements AttributeMetadata {
|
||||
|
||||
private String name;
|
||||
private Attribute attribute;
|
||||
private Class<?> type;
|
||||
private Field field;
|
||||
private Method getter;
|
||||
private Method setter;
|
||||
|
||||
public JpaAttributeMetadata(EntityType<?> entityType, Attribute attribute) {
|
||||
this.attribute = attribute;
|
||||
name = attribute.getName();
|
||||
type = attribute.getJavaType();
|
||||
|
||||
field = ReflectionUtils.findField(entityType.getJavaType(), name);
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
|
||||
PropertyDescriptor property = BeanUtils.getPropertyDescriptor(entityType.getJavaType(), name);
|
||||
if (null != property) {
|
||||
getter = property.getReadMethod();
|
||||
if (null != getter)
|
||||
ReflectionUtils.makeAccessible(getter);
|
||||
|
||||
setter = property.getWriteMethod();
|
||||
if (null != setter)
|
||||
ReflectionUtils.makeAccessible(setter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override public Class<?> type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override public Class<?> elementType() {
|
||||
return (attribute instanceof PluralAttribute
|
||||
? ((PluralAttribute) attribute).getElementType().getJavaType()
|
||||
: null);
|
||||
}
|
||||
|
||||
@Override public boolean isCollectionLike() {
|
||||
if (attribute instanceof PluralAttribute) {
|
||||
PluralAttribute plattr = (PluralAttribute) attribute;
|
||||
switch (plattr.getCollectionType()) {
|
||||
case COLLECTION:
|
||||
case LIST:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public Collection<?> asCollection(Object target) {
|
||||
return (Collection<?>) get(target);
|
||||
}
|
||||
|
||||
@Override public boolean isSetLike() {
|
||||
if (attribute instanceof PluralAttribute) {
|
||||
PluralAttribute plattr = (PluralAttribute) attribute;
|
||||
switch (plattr.getCollectionType()) {
|
||||
case SET:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public Set<?> asSet(Object target) {
|
||||
return (Set<?>) get(target);
|
||||
}
|
||||
|
||||
@Override public boolean isMapLike() {
|
||||
if (attribute instanceof PluralAttribute) {
|
||||
PluralAttribute plattr = (PluralAttribute) attribute;
|
||||
switch (plattr.getCollectionType()) {
|
||||
case MAP:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public Map asMap(Object target) {
|
||||
return (Map) get(target);
|
||||
}
|
||||
|
||||
@Override public Object get(Object target) {
|
||||
try {
|
||||
if (null != getter) {
|
||||
return getter.invoke(target);
|
||||
} else {
|
||||
return field.get(target);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public AttributeMetadata set(Object value, Object target) {
|
||||
try {
|
||||
if (null != setter) {
|
||||
setter.invoke(target, value);
|
||||
} else {
|
||||
field.set(target, value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.springframework.data.rest.repository.jpa;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.persistence.metamodel.Attribute;
|
||||
import javax.persistence.metamodel.EntityType;
|
||||
import javax.persistence.metamodel.PluralAttribute;
|
||||
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.repository.EntityMetadata;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class JpaEntityMetadata implements EntityMetadata<JpaAttributeMetadata> {
|
||||
|
||||
private Class<?> type;
|
||||
private JpaAttributeMetadata idAttribute;
|
||||
private JpaAttributeMetadata versionAttribute;
|
||||
private Map<String, JpaAttributeMetadata> embeddedAttributes = new HashMap<String, JpaAttributeMetadata>();
|
||||
private Map<String, JpaAttributeMetadata> linkedAttributes = new HashMap<String, JpaAttributeMetadata>();
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public JpaEntityMetadata(Repositories repositories, EntityType<?> entityType) {
|
||||
type = entityType.getJavaType();
|
||||
idAttribute = new JpaAttributeMetadata(entityType, entityType.getId(entityType.getIdType().getJavaType()));
|
||||
if (null != entityType.getVersion(Long.class)) {
|
||||
versionAttribute = new JpaAttributeMetadata(entityType, entityType.getVersion(Long.class));
|
||||
}
|
||||
|
||||
for (Attribute attr : entityType.getAttributes()) {
|
||||
Class<?> attrType = (attr instanceof PluralAttribute
|
||||
? ((PluralAttribute) attr).getElementType().getJavaType()
|
||||
: attr.getJavaType());
|
||||
if (repositories.hasRepositoryFor(attrType)) {
|
||||
linkedAttributes.put(attr.getName(), new JpaAttributeMetadata(entityType, attr));
|
||||
} else {
|
||||
embeddedAttributes.put(attr.getName(), new JpaAttributeMetadata(entityType, attr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public Class<?> type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override public Map<String, JpaAttributeMetadata> embeddedAttributes() {
|
||||
return embeddedAttributes;
|
||||
}
|
||||
|
||||
@Override public Map<String, JpaAttributeMetadata> linkedAttributes() {
|
||||
return linkedAttributes;
|
||||
}
|
||||
|
||||
@Override public JpaAttributeMetadata idAttribute() {
|
||||
return idAttribute;
|
||||
}
|
||||
|
||||
@Override public JpaAttributeMetadata versionAttribute() {
|
||||
return versionAttribute;
|
||||
}
|
||||
|
||||
@Override public JpaAttributeMetadata attribute(String name) {
|
||||
if (idAttribute.name().equals(name)) {
|
||||
return idAttribute;
|
||||
} else if (null != versionAttribute && versionAttribute.name().equals(name)) {
|
||||
return versionAttribute;
|
||||
} else if (embeddedAttributes.containsKey(name)) {
|
||||
return embeddedAttributes.get(name);
|
||||
} else if (linkedAttributes.containsKey(name)) {
|
||||
return linkedAttributes.get(name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.springframework.data.rest.repository.jpa;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
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>
|
||||
*/
|
||||
public class JpaRepositoryExporter extends RepositoryExporter<
|
||||
JpaRepositoryMetadata<Repository<Object, Serializable>>,
|
||||
Repository<Object, Serializable>,
|
||||
JpaEntityMetadata> {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override
|
||||
protected JpaRepositoryMetadata<Repository<Object, Serializable>> createRepositoryMetadata(
|
||||
Class repoClass,
|
||||
Repository<Object, Serializable> repo,
|
||||
String name,
|
||||
EntityInformation entityInfo) {
|
||||
return new JpaRepositoryMetadata(new Repositories(applicationContext),
|
||||
name,
|
||||
repoClass,
|
||||
repo,
|
||||
entityInfo,
|
||||
entityManager);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package org.springframework.data.rest.repository.jpa;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
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.annotation.RestPathSegment;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class JpaRepositoryMetadata<R extends Repository<Object, Serializable>> implements RepositoryMetadata<R, JpaEntityMetadata> {
|
||||
|
||||
private final String name;
|
||||
private final Class<?> repoClass;
|
||||
private final R repository;
|
||||
private final EntityInformation entityInfo;
|
||||
private final Map<String, Method> queryMethods = new HashMap<String, Method>();
|
||||
private JpaEntityMetadata entityMetadata;
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public JpaRepositoryMetadata(Repositories repositories,
|
||||
String name,
|
||||
final Class<?> repoClass,
|
||||
R repository,
|
||||
EntityInformation entityInfo,
|
||||
EntityManager entityManager) {
|
||||
this.name = name;
|
||||
this.repoClass = repoClass;
|
||||
this.repository = repository;
|
||||
this.entityInfo = entityInfo;
|
||||
|
||||
ReflectionUtils.doWithMethods(
|
||||
repoClass,
|
||||
new ReflectionUtils.MethodCallback() {
|
||||
@Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
String pathSeg = AnnotationUtils.findAnnotation(method, RestPathSegment.class).value();
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
queryMethods.put(pathSeg, method);
|
||||
}
|
||||
},
|
||||
new ReflectionUtils.MethodFilter() {
|
||||
@Override public boolean matches(Method method) {
|
||||
return (!method.isSynthetic()
|
||||
&& !method.isBridge()
|
||||
&& method.getDeclaringClass() != Object.class
|
||||
&& !method.getName().contains("$")
|
||||
&& null != AnnotationUtils.findAnnotation(method, RestPathSegment.class));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
Metamodel metamodel = entityManager.getMetamodel();
|
||||
entityMetadata = new JpaEntityMetadata(repositories, metamodel.entity(entityInfo.getJavaType()));
|
||||
}
|
||||
|
||||
@Override public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override public Class<? extends Object> domainType() {
|
||||
return entityMetadata.type();
|
||||
}
|
||||
|
||||
@Override public R repository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
@Override public JpaEntityMetadata entityMetadata() {
|
||||
return entityMetadata;
|
||||
}
|
||||
|
||||
@Override public Method queryMethod(String key) {
|
||||
return queryMethods.get(key);
|
||||
}
|
||||
|
||||
@Override public Map<String, Method> queryMethods() {
|
||||
return Collections.unmodifiableMap(queryMethods);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,8 +11,8 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.rest.repository.JpaRepositoryMetadata;
|
||||
import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener;
|
||||
import org.springframework.data.rest.repository.jpa.JpaRepositoryExporter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
|
||||
@@ -28,7 +28,7 @@ public class RepositoryRestConfiguration {
|
||||
@Autowired
|
||||
EntityManagerFactory entityManagerFactory;
|
||||
@Autowired(required = false)
|
||||
JpaRepositoryMetadata jpaRepositoryMetadata;
|
||||
JpaRepositoryExporter jpaRepositoryExporter;
|
||||
@Autowired(required = false)
|
||||
ConversionService customConversionService;
|
||||
ConversionService defaultConversionService = new DefaultConversionService();
|
||||
@@ -56,18 +56,11 @@ public class RepositoryRestConfiguration {
|
||||
return httpMessageConverters;
|
||||
}
|
||||
|
||||
@Bean JpaRepositoryMetadata jpaRepositoryMetadata() throws Exception {
|
||||
if (null == jpaRepositoryMetadata) {
|
||||
jpaRepositoryMetadata = new JpaRepositoryMetadata();
|
||||
@Bean JpaRepositoryExporter jpaRepositoryExporter() {
|
||||
if (null == jpaRepositoryExporter) {
|
||||
jpaRepositoryExporter = new JpaRepositoryExporter();
|
||||
}
|
||||
return jpaRepositoryMetadata;
|
||||
}
|
||||
|
||||
@Bean ValidatingRepositoryEventListener validatingRepositoryEventListener() {
|
||||
if (null == validatingRepositoryEventListener) {
|
||||
validatingRepositoryEventListener = new ValidatingRepositoryEventListener();
|
||||
}
|
||||
return validatingRepositoryEventListener;
|
||||
return jpaRepositoryExporter;
|
||||
}
|
||||
|
||||
@Bean PersistenceAnnotationBeanPostProcessor persistenceAnnotationBeanPostProcessor() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
@@ -14,12 +15,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.persistence.metamodel.Attribute;
|
||||
import javax.persistence.metamodel.EntityType;
|
||||
import javax.persistence.metamodel.PluralAttribute;
|
||||
import javax.persistence.metamodel.SingularAttribute;
|
||||
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
@@ -29,19 +25,21 @@ import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.rest.core.Handler;
|
||||
import org.springframework.data.rest.core.Link;
|
||||
import org.springframework.data.rest.core.SimpleLink;
|
||||
import org.springframework.data.rest.core.util.UriUtils;
|
||||
import org.springframework.data.rest.repository.JpaEntityMetadata;
|
||||
import org.springframework.data.rest.repository.JpaRepositoryMetadata;
|
||||
import org.springframework.data.rest.repository.AttributeMetadata;
|
||||
import org.springframework.data.rest.repository.EntityMetadata;
|
||||
import org.springframework.data.rest.repository.RepositoryConstraintViolationException;
|
||||
import org.springframework.data.rest.repository.context.AfterLinkSaveEvent;
|
||||
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.context.AfterDeleteEvent;
|
||||
import org.springframework.data.rest.repository.context.AfterLinkSaveEvent;
|
||||
import org.springframework.data.rest.repository.context.AfterSaveEvent;
|
||||
import org.springframework.data.rest.repository.context.BeforeLinkSaveEvent;
|
||||
import org.springframework.data.rest.repository.context.BeforeDeleteEvent;
|
||||
import org.springframework.data.rest.repository.context.BeforeLinkSaveEvent;
|
||||
import org.springframework.data.rest.repository.context.BeforeSaveEvent;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
@@ -63,7 +61,6 @@ import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
@@ -71,6 +68,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
*/
|
||||
@Controller
|
||||
public class RepositoryRestController
|
||||
extends RepositoryExporterSupport<RepositoryRestController>
|
||||
implements ApplicationEventPublisherAware,
|
||||
InitializingBean {
|
||||
|
||||
@@ -85,34 +83,14 @@ public class RepositoryRestController
|
||||
|
||||
private MediaType uriListMediaType = MediaType.parseMediaType("text/uri-list");
|
||||
private MediaType jsonMediaType = MediaType.parseMediaType("application/x-spring-data+json");
|
||||
private JpaRepositoryMetadata repositoryMetadata;
|
||||
private Map<CrudRepository, TypeMetaCacheEntry> typeMetaCache = new ConcurrentHashMap<CrudRepository, TypeMetaCacheEntry>();
|
||||
private ConversionService conversionService = new DefaultConversionService();
|
||||
private List<HttpMessageConverter<?>> httpMessageConverters;
|
||||
private ContentNegotiatingViewResolver viewResolver;
|
||||
private List<HttpMessageConverter<?>> httpMessageConverters = Collections.emptyList();
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
public JpaRepositoryMetadata getRepositoryMetadata() {
|
||||
return repositoryMetadata;
|
||||
}
|
||||
|
||||
public void setRepositoryMetadata(JpaRepositoryMetadata repositoryMetadata) {
|
||||
this.repositoryMetadata = repositoryMetadata;
|
||||
}
|
||||
|
||||
public JpaRepositoryMetadata repositoryMetadata() {
|
||||
return repositoryMetadata;
|
||||
}
|
||||
|
||||
public RepositoryRestController repositoryMetadata(JpaRepositoryMetadata repositoryMetadata) {
|
||||
this.repositoryMetadata = repositoryMetadata;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConversionService getConversionService() {
|
||||
return conversionService;
|
||||
}
|
||||
@@ -147,23 +125,6 @@ public class RepositoryRestController
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContentNegotiatingViewResolver getViewResolver() {
|
||||
return viewResolver;
|
||||
}
|
||||
|
||||
public void setViewResolver(ContentNegotiatingViewResolver viewResolver) {
|
||||
this.viewResolver = viewResolver;
|
||||
}
|
||||
|
||||
public ContentNegotiatingViewResolver viewResolver() {
|
||||
return viewResolver;
|
||||
}
|
||||
|
||||
public RepositoryRestController viewResolver(ContentNegotiatingViewResolver viewResolver) {
|
||||
this.viewResolver = viewResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MediaType getUriListMediaType() {
|
||||
return uriListMediaType;
|
||||
}
|
||||
@@ -220,6 +181,7 @@ public class RepositoryRestController
|
||||
Assert.notNull(httpMessageConverters, "HttpMessageConverters cannot be null");
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/",
|
||||
method = RequestMethod.GET,
|
||||
@@ -232,8 +194,10 @@ public class RepositoryRestController
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
Links links = new Links();
|
||||
for (String name : repositoryMetadata.repositoryNames()) {
|
||||
links.add(new SimpleLink(name, buildUri(baseUri, name)));
|
||||
for (RepositoryExporter repoMeta : repositoryExporters) {
|
||||
for (String name : (Set<String>) repoMeta.repositoryNames()) {
|
||||
links.add(new SimpleLink(name, buildUri(baseUri, name)));
|
||||
}
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
@@ -253,20 +217,13 @@ public class RepositoryRestController
|
||||
Model model) {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
final CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
final TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
|
||||
Links links = new Links();
|
||||
|
||||
Iterator iter = repo.findAll().iterator();
|
||||
Iterator iter = ((CrudRepository) repoMeta.repository()).findAll().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Object o = iter.next();
|
||||
Serializable id = typeMeta.entityInfo.getId(o);
|
||||
Serializable id = (Serializable) repoMeta.entityMetadata().idAttribute().get(o);
|
||||
links.add(new SimpleLink(o.getClass().getSimpleName(), buildUri(baseUri, repository, id.toString())));
|
||||
}
|
||||
|
||||
@@ -288,16 +245,10 @@ public class RepositoryRestController
|
||||
Model model) throws IOException {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_IMPLEMENTED);
|
||||
return;
|
||||
}
|
||||
|
||||
final TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
|
||||
CrudRepository repo = (CrudRepository) repoMeta.repository();
|
||||
MediaType incomingMediaType = request.getHeaders().getContentType();
|
||||
final Object incoming = readIncoming(request, incomingMediaType, typeMeta.domainClass);
|
||||
final Object incoming = readIncoming(request, incomingMediaType, repoMeta.entityMetadata().type());
|
||||
if (null == incoming) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_ACCEPTABLE);
|
||||
} else {
|
||||
@@ -308,7 +259,7 @@ public class RepositoryRestController
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new AfterSaveEvent(savedEntity));
|
||||
}
|
||||
String sId = typeMeta.entityInfo.getId(savedEntity).toString();
|
||||
String sId = repoMeta.entityMetadata().idAttribute().get(savedEntity).toString();
|
||||
|
||||
URI selfUri = buildUri(baseUri, repository, sId);
|
||||
|
||||
@@ -335,21 +286,18 @@ public class RepositoryRestController
|
||||
Model model) {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
|
||||
Serializable serId = stringToSerializable(id,
|
||||
(Class<? extends Serializable>) repoMeta.entityMetadata()
|
||||
.idAttribute()
|
||||
.type());
|
||||
CrudRepository repo = (CrudRepository) repoMeta.repository();
|
||||
Object entity = repo.findOne(serId);
|
||||
if (null == entity) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
Object version = typeMeta.entityMetadata.version(entity);
|
||||
Object version = repoMeta.entityMetadata().versionAttribute().get(entity);
|
||||
if (null != version) {
|
||||
List<String> etags = request.getHeaders().getIfNoneMatch();
|
||||
for (String etag : etags) {
|
||||
@@ -361,7 +309,7 @@ public class RepositoryRestController
|
||||
headers.set("ETag", "\"" + version.toString() + "\"");
|
||||
}
|
||||
Map<String, Object> entityDto = extractPropertiesLinkAware(entity,
|
||||
typeMeta.entityMetadata,
|
||||
repoMeta.entityMetadata(),
|
||||
UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(repository, id)
|
||||
.build()
|
||||
@@ -398,19 +346,17 @@ public class RepositoryRestController
|
||||
InstantiationException {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_IMPLEMENTED);
|
||||
return;
|
||||
}
|
||||
|
||||
final TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
|
||||
Serializable serId = stringToSerializable(id,
|
||||
(Class<? extends Serializable>) repoMeta.entityMetadata()
|
||||
.idAttribute()
|
||||
.type());
|
||||
CrudRepository repo = (CrudRepository) repoMeta.repository();
|
||||
Object entity = null;
|
||||
Class<?> domainType = repoMeta.entityMetadata().type();
|
||||
switch (request.getMethod()) {
|
||||
case POST:
|
||||
entity = typeMeta.domainClass.newInstance();
|
||||
entity = domainType.newInstance();
|
||||
break;
|
||||
case PUT:
|
||||
entity = repo.findOne(serId);
|
||||
@@ -421,12 +367,11 @@ public class RepositoryRestController
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
final MediaType incomingMediaType = request.getHeaders().getContentType();
|
||||
final Object incoming = readIncoming(request, incomingMediaType, typeMeta.domainClass);
|
||||
final Object incoming = readIncoming(request, incomingMediaType, domainType);
|
||||
if (null == incoming) {
|
||||
throw new HttpMessageNotReadableException("Could not create an instance of " + typeMeta.domainClass
|
||||
.getSimpleName() + " from input.");
|
||||
throw new HttpMessageNotReadableException("Could not create an instance of " + domainType.getSimpleName() + " from input.");
|
||||
} else {
|
||||
typeMeta.entityMetadata.id(serId, incoming);
|
||||
repoMeta.entityMetadata().idAttribute().set(serId, incoming);
|
||||
if (request.getMethod() == HttpMethod.POST) {
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeSaveEvent(incoming));
|
||||
@@ -462,14 +407,12 @@ public class RepositoryRestController
|
||||
public void deleteEntity(@PathVariable String repository,
|
||||
@PathVariable String id,
|
||||
Model model) {
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
|
||||
Serializable serId = stringToSerializable(id,
|
||||
(Class<? extends Serializable>) repoMeta.entityMetadata()
|
||||
.idAttribute()
|
||||
.type());
|
||||
CrudRepository repo = (CrudRepository) repoMeta.repository();
|
||||
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeDeleteEvent(serId));
|
||||
@@ -499,53 +442,41 @@ public class RepositoryRestController
|
||||
Model model) {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
|
||||
Serializable serId = stringToSerializable(id,
|
||||
(Class<? extends Serializable>) repoMeta.entityMetadata()
|
||||
.idAttribute()
|
||||
.type());
|
||||
CrudRepository repo = (CrudRepository) repoMeta.repository();
|
||||
Object entity = repo.findOne(serId);
|
||||
if (null == entity) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
Attribute attr = typeMeta.entityType.getAttribute(property);
|
||||
if (null == attr) {
|
||||
AttributeMetadata attrMeta = repoMeta.entityMetadata().attribute(property);
|
||||
if (null == attrMeta) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
Class<?> childType;
|
||||
if (attr instanceof PluralAttribute) {
|
||||
childType = ((PluralAttribute) attr).getElementType().getJavaType();
|
||||
} else {
|
||||
childType = attr.getJavaType();
|
||||
}
|
||||
|
||||
CrudRepository childRepo = repositoryMetadata.repositoryFor(childType);
|
||||
if (null == childRepo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
Class<?> attrType = attrMeta.elementType();
|
||||
if (null == attrType) {
|
||||
attrType = attrMeta.type();
|
||||
}
|
||||
|
||||
RepositoryMetadata propRepoMeta = repositoryMetadataFor(attrType);
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
|
||||
TypeMetaCacheEntry childTypeMeta = typeMetaEntry(childRepo);
|
||||
|
||||
Object child = typeMeta.entityMetadata.get(property, entity);
|
||||
if (null != child) {
|
||||
Object propVal = attrMeta.get(entity);
|
||||
AttributeMetadata idAttr = propRepoMeta.entityMetadata().idAttribute();
|
||||
if (null != propVal) {
|
||||
Links links = new Links();
|
||||
if (child instanceof Collection) {
|
||||
for (Object o : (Collection) child) {
|
||||
String childId = childTypeMeta.entityInfo.getId(o).toString();
|
||||
URI uri = buildUri(baseUri, repository, id, property, childId);
|
||||
links.add(new SimpleLink(childType.getSimpleName(), uri));
|
||||
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(attrType.getSimpleName(), uri));
|
||||
}
|
||||
} else if (child instanceof Map) {
|
||||
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) child).entrySet()) {
|
||||
String childId = childTypeMeta.entityInfo.getId(entry.getValue()).toString();
|
||||
URI uri = buildUri(baseUri, repository, id, property, childId);
|
||||
} 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);
|
||||
Object oKey = entry.getKey();
|
||||
String sKey;
|
||||
if (ClassUtils.isAssignable(oKey.getClass(), String.class)) {
|
||||
@@ -556,8 +487,8 @@ public class RepositoryRestController
|
||||
links.add(new SimpleLink(sKey, uri));
|
||||
}
|
||||
} else {
|
||||
String childId = childTypeMeta.entityInfo.getId(child).toString();
|
||||
URI uri = buildUri(baseUri, repository, id, property, childId);
|
||||
String propValId = idAttr.get(propVal).toString();
|
||||
URI uri = buildUri(baseUri, repository, id, property, propValId);
|
||||
links.add(new SimpleLink(property, uri));
|
||||
}
|
||||
model.addAttribute(RESOURCE, links);
|
||||
@@ -592,72 +523,57 @@ public class RepositoryRestController
|
||||
final Model model) throws IOException {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
final TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
final RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
|
||||
Serializable serId = stringToSerializable(id,
|
||||
(Class<? extends Serializable>) repoMeta.entityMetadata()
|
||||
.idAttribute()
|
||||
.type());
|
||||
CrudRepository repo = (CrudRepository) repoMeta.repository();
|
||||
final Object entity = repo.findOne(serId);
|
||||
if (null == entity) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
final Attribute attr = typeMeta.entityMetadata.linkedAttributes().get(property);
|
||||
if (null == attr) {
|
||||
final AttributeMetadata attrMeta = repoMeta.entityMetadata().attribute(property);
|
||||
if (null == attrMeta) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
Object child = typeMeta.entityMetadata.get(attr.getName(), entity);
|
||||
Object linked = attrMeta.get(entity);
|
||||
final AtomicReference<String> rel = new AtomicReference<String>();
|
||||
Handler<Object, Void> entityHandler = new Handler<Object, Void>() {
|
||||
@Override public Void handle(Object childEntity) {
|
||||
if (attr instanceof PluralAttribute) {
|
||||
PluralAttribute plAttr = (PluralAttribute) attr;
|
||||
switch (plAttr.getCollectionType()) {
|
||||
case COLLECTION:
|
||||
case LIST: {
|
||||
Collection c = new ArrayList();
|
||||
Collection current = (Collection) typeMeta.entityMetadata.get(property, entity);
|
||||
if (request.getMethod() == HttpMethod.POST && null != current) {
|
||||
c.addAll(current);
|
||||
}
|
||||
c.add(childEntity);
|
||||
typeMeta.entityMetadata.set(property, c, entity);
|
||||
}
|
||||
break;
|
||||
case SET: {
|
||||
Set s = new HashSet();
|
||||
Set current = (Set) typeMeta.entityMetadata.get(property, entity);
|
||||
if (request.getMethod() == HttpMethod.POST && null != current) {
|
||||
s.addAll(current);
|
||||
}
|
||||
s.add(childEntity);
|
||||
typeMeta.entityMetadata.set(property, s, entity);
|
||||
}
|
||||
break;
|
||||
case MAP: {
|
||||
Map m = new HashMap();
|
||||
Map current = (Map) typeMeta.entityMetadata.get(property, entity);
|
||||
if (request.getMethod() == HttpMethod.POST && null != current) {
|
||||
m.putAll(current);
|
||||
}
|
||||
String key = rel.get();
|
||||
if (null == key) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_ACCEPTABLE);
|
||||
return null;
|
||||
} else {
|
||||
m.put(rel.get(), childEntity);
|
||||
typeMeta.entityMetadata.set(property, m, entity);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@Override public Void handle(Object linkedEntity) {
|
||||
if (attrMeta.isCollectionLike()) {
|
||||
Collection c = new ArrayList();
|
||||
Collection current = (Collection) attrMeta.get(entity);
|
||||
if (request.getMethod() == HttpMethod.POST && null != current) {
|
||||
c.addAll(current);
|
||||
}
|
||||
} else if (attr instanceof SingularAttribute) {
|
||||
typeMeta.entityMetadata.set(property, childEntity, entity);
|
||||
c.add(linkedEntity);
|
||||
attrMeta.set(c, entity);
|
||||
} else if (attrMeta.isSetLike()) {
|
||||
Set s = new HashSet();
|
||||
Set current = (Set) attrMeta.get(entity);
|
||||
if (request.getMethod() == HttpMethod.POST && null != current) {
|
||||
s.addAll(current);
|
||||
}
|
||||
s.add(linkedEntity);
|
||||
attrMeta.set(s, entity);
|
||||
} else if (attrMeta.isMapLike()) {
|
||||
Map m = new HashMap();
|
||||
Map current = (Map) attrMeta.get(entity);
|
||||
if (request.getMethod() == HttpMethod.POST && null != current) {
|
||||
m.putAll(current);
|
||||
}
|
||||
String key = rel.get();
|
||||
if (null == key) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_ACCEPTABLE);
|
||||
return null;
|
||||
} else {
|
||||
m.put(rel.get(), linkedEntity);
|
||||
attrMeta.set(m, entity);
|
||||
}
|
||||
} else {
|
||||
attrMeta.set(linkedEntity, entity);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -686,12 +602,12 @@ public class RepositoryRestController
|
||||
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeSaveEvent(entity));
|
||||
eventPublisher.publishEvent(new BeforeLinkSaveEvent(entity, child));
|
||||
eventPublisher.publishEvent(new BeforeLinkSaveEvent(entity, linked));
|
||||
}
|
||||
Object savedEntity = repo.save(entity);
|
||||
if (null != eventPublisher) {
|
||||
child = typeMeta.entityMetadata.get(attr.getName(), savedEntity);
|
||||
eventPublisher.publishEvent(new AfterLinkSaveEvent(savedEntity, child));
|
||||
linked = attrMeta.get(savedEntity);
|
||||
eventPublisher.publishEvent(new AfterLinkSaveEvent(savedEntity, linked));
|
||||
eventPublisher.publishEvent(new AfterSaveEvent(savedEntity));
|
||||
}
|
||||
|
||||
@@ -715,26 +631,23 @@ public class RepositoryRestController
|
||||
@PathVariable String id,
|
||||
@PathVariable String property,
|
||||
Model model) {
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
final TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
|
||||
Serializable serId = stringToSerializable(id,
|
||||
(Class<? extends Serializable>) repoMeta.entityMetadata()
|
||||
.idAttribute()
|
||||
.type());
|
||||
CrudRepository repo = (CrudRepository) repoMeta.repository();
|
||||
final Object entity = repo.findOne(serId);
|
||||
if (null == entity) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
final Attribute attr = typeMeta.entityMetadata.linkedAttributes().get(property);
|
||||
if (null != attr) {
|
||||
Object child = typeMeta.entityMetadata.get(property, entity);
|
||||
typeMeta.entityMetadata.set(property, null, entity);
|
||||
AttributeMetadata attrMeta = repoMeta.entityMetadata().attribute(property);
|
||||
if (null != attrMeta) {
|
||||
Object linked = attrMeta.get(entity);
|
||||
attrMeta.set(null, entity);
|
||||
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeLinkSaveEvent(entity, child));
|
||||
eventPublisher.publishEvent(new BeforeLinkSaveEvent(entity, linked));
|
||||
}
|
||||
Object savedEntity = repo.save(entity);
|
||||
if (null != eventPublisher) {
|
||||
@@ -750,7 +663,7 @@ public class RepositoryRestController
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/{repository}/{id}/{property}/{childId}",
|
||||
value = "/{repository}/{id}/{property}/{linkedId}",
|
||||
method = {
|
||||
RequestMethod.GET
|
||||
},
|
||||
@@ -758,36 +671,36 @@ public class RepositoryRestController
|
||||
"application/json"
|
||||
}
|
||||
)
|
||||
public void childEntity(UriComponentsBuilder uriBuilder,
|
||||
@PathVariable String repository,
|
||||
@PathVariable String id,
|
||||
@PathVariable String property,
|
||||
@PathVariable String childId,
|
||||
Model model) {
|
||||
public void linkedEntity(UriComponentsBuilder uriBuilder,
|
||||
@PathVariable String repository,
|
||||
@PathVariable String id,
|
||||
@PathVariable String property,
|
||||
@PathVariable String linkedId,
|
||||
Model model) {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
final TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
|
||||
Serializable serId = stringToSerializable(id,
|
||||
(Class<? extends Serializable>) repoMeta.entityMetadata()
|
||||
.idAttribute()
|
||||
.type());
|
||||
CrudRepository repo = (CrudRepository) repoMeta.repository();
|
||||
final Object entity = repo.findOne(serId);
|
||||
if (null != entity) {
|
||||
final Attribute attr = typeMeta.entityMetadata.linkedAttributes().get(property);
|
||||
if (null != attr) {
|
||||
// Find child entity
|
||||
CrudRepository childRepo = repositoryFromAttribute(attr);
|
||||
if (null != childRepo) {
|
||||
TypeMetaCacheEntry childTypeMeta = typeMetaEntry(childRepo);
|
||||
Serializable sChildId = stringToSerializable(childId, childTypeMeta.idType);
|
||||
Object childEntity = childRepo.findOne(sChildId);
|
||||
if (null != childEntity) {
|
||||
Map<String, Object> entityDto = extractPropertiesLinkAware(childEntity,
|
||||
childTypeMeta.entityMetadata,
|
||||
AttributeMetadata attrMeta = repoMeta.entityMetadata().attribute(property);
|
||||
if (null != attrMeta) {
|
||||
// Find linked entity
|
||||
RepositoryMetadata linkedRepoMeta = repositoryMetadataFor(attrMeta);
|
||||
if (null != linkedRepoMeta) {
|
||||
CrudRepository linkedRepo = (CrudRepository) linkedRepoMeta.repository();
|
||||
Serializable sChildId = stringToSerializable(linkedId,
|
||||
(Class<? extends Serializable>) linkedRepoMeta.entityMetadata()
|
||||
.idAttribute()
|
||||
.type());
|
||||
Object linkedEntity = linkedRepo.findOne(sChildId);
|
||||
if (null != linkedEntity) {
|
||||
Map<String, Object> entityDto = extractPropertiesLinkAware(linkedEntity,
|
||||
linkedRepoMeta.entityMetadata(),
|
||||
baseUri);
|
||||
URI selfUri = addSelfLink(baseUri, entityDto, repository, id);
|
||||
|
||||
@@ -807,7 +720,7 @@ public class RepositoryRestController
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/{repository}/{id}/{property}/{childId}",
|
||||
value = "/{repository}/{id}/{property}/{linkedId}",
|
||||
method = {
|
||||
RequestMethod.DELETE
|
||||
}
|
||||
@@ -815,77 +728,68 @@ public class RepositoryRestController
|
||||
public void deleteLink(@PathVariable String repository,
|
||||
@PathVariable String id,
|
||||
@PathVariable String property,
|
||||
@PathVariable String childId,
|
||||
@PathVariable String linkedId,
|
||||
Model model) {
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
final TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
|
||||
Serializable serId = stringToSerializable(id,
|
||||
(Class<? extends Serializable>) repoMeta.entityMetadata()
|
||||
.idAttribute()
|
||||
.type());
|
||||
CrudRepository repo = (CrudRepository) repoMeta.repository();
|
||||
final Object entity = repo.findOne(serId);
|
||||
if (null == entity) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
final Attribute attr = typeMeta.entityMetadata.linkedAttributes().get(property);
|
||||
if (null == attr) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
// Find child entity
|
||||
CrudRepository childRepo = repositoryFromAttribute(attr);
|
||||
if (null == childRepo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
TypeMetaCacheEntry childTypeMeta = typeMetaEntry(childRepo);
|
||||
Serializable sChildId = stringToSerializable(childId, childTypeMeta.idType);
|
||||
Object childEntity = childRepo.findOne(sChildId);
|
||||
if (null != childEntity) {
|
||||
// Remove child entity from relationship based on property type
|
||||
if (attr instanceof PluralAttribute) {
|
||||
PluralAttribute plAttr = (PluralAttribute) attr;
|
||||
switch (plAttr.getCollectionType()) {
|
||||
case COLLECTION:
|
||||
case LIST:
|
||||
Collection c = (Collection) typeMeta.entityMetadata.get(property, entity);
|
||||
if (null != c) {
|
||||
c.remove(childEntity);
|
||||
}
|
||||
break;
|
||||
case SET:
|
||||
Set s = (Set) typeMeta.entityMetadata.get(property, entity);
|
||||
if (null != s) {
|
||||
s.remove(childEntity);
|
||||
}
|
||||
break;
|
||||
case MAP:
|
||||
Object keyToRemove = null;
|
||||
Map<Object, Object> m = (Map) typeMeta.entityMetadata.get(property, entity);
|
||||
if (null != m) {
|
||||
for (Map.Entry<Object, Object> entry : m.entrySet()) {
|
||||
Object val = entry.getValue();
|
||||
if (null != val && val.equals(childEntity)) {
|
||||
keyToRemove = entry.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (null != keyToRemove) {
|
||||
m.remove(keyToRemove);
|
||||
}
|
||||
}
|
||||
break;
|
||||
AttributeMetadata attrMeta = repoMeta.entityMetadata().attribute(property);
|
||||
if (null != attrMeta) {
|
||||
// Find linked entity
|
||||
RepositoryMetadata linkedRepoMeta = repositoryMetadataFor(attrMeta);
|
||||
if (null != linkedRepoMeta) {
|
||||
CrudRepository linkedRepo = (CrudRepository) linkedRepoMeta.repository();
|
||||
Serializable sChildId = stringToSerializable(linkedId,
|
||||
(Class<? extends Serializable>) linkedRepoMeta.entityMetadata()
|
||||
.idAttribute()
|
||||
.type());
|
||||
Object linkedEntity = linkedRepo.findOne(sChildId);
|
||||
if (null != linkedEntity) {
|
||||
// Remove linked entity from relationship based on property type
|
||||
if (attrMeta.isCollectionLike()) {
|
||||
Collection c = (Collection) attrMeta.get(entity);
|
||||
if (null != c) {
|
||||
c.remove(linkedEntity);
|
||||
}
|
||||
} else if (attr instanceof SingularAttribute) {
|
||||
typeMeta.entityMetadata.set(property, childEntity, entity);
|
||||
} else if (attrMeta.isSetLike()) {
|
||||
Set s = (Set) attrMeta.get(entity);
|
||||
if (null != s) {
|
||||
s.remove(linkedEntity);
|
||||
}
|
||||
} else if (attrMeta.isMapLike()) {
|
||||
Object keyToRemove = null;
|
||||
Map<Object, Object> m = (Map) attrMeta.get(entity);
|
||||
if (null != m) {
|
||||
for (Map.Entry<Object, Object> entry : m.entrySet()) {
|
||||
Object val = entry.getValue();
|
||||
if (null != val && val.equals(linkedEntity)) {
|
||||
keyToRemove = entry.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (null != keyToRemove) {
|
||||
m.remove(keyToRemove);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
attrMeta.set(linkedEntity, entity);
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@@ -920,26 +824,6 @@ public class RepositoryRestController
|
||||
return UriComponentsBuilder.fromUri(baseUri).pathSegment(pathSegments).build().toUri();
|
||||
}
|
||||
|
||||
private TypeMetaCacheEntry typeMetaEntry(CrudRepository repo) {
|
||||
TypeMetaCacheEntry entry = typeMetaCache.get(repo);
|
||||
if (null == entry) {
|
||||
entry = new TypeMetaCacheEntry(repo);
|
||||
typeMetaCache.put(repo, entry);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private CrudRepository repositoryFromAttribute(Attribute attr) {
|
||||
CrudRepository repo;
|
||||
if (attr instanceof PluralAttribute) {
|
||||
repo = repositoryMetadata.repositoryFor(((PluralAttribute) attr).getElementType().getJavaType());
|
||||
} else {
|
||||
repo = repositoryMetadata.repositoryFor(attr.getJavaType());
|
||||
}
|
||||
return repo;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private URI addSelfLink(URI baseUri, Map<String, Object> model, String... pathComponents) {
|
||||
List<Link> links = (List<Link>) model.get(LINKS);
|
||||
@@ -972,15 +856,16 @@ public class RepositoryRestController
|
||||
String repoName = UriUtils.path(uris.get(0));
|
||||
String sId = UriUtils.path(uris.get(1));
|
||||
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repoName);
|
||||
RepositoryMetadata repoMeta = repositoryMetadataFor(repoName);
|
||||
CrudRepository repo = (CrudRepository) repoMeta.repository();
|
||||
if (null == repo) {
|
||||
return null;
|
||||
}
|
||||
EntityInformation entityInfo = repositoryMetadata.entityInfoFor(repo);
|
||||
if (null == entityInfo) {
|
||||
EntityMetadata entityMeta = repoMeta.entityMetadata();
|
||||
if (null == entityMeta) {
|
||||
return null;
|
||||
}
|
||||
Class<? extends Serializable> idType = entityInfo.getIdType();
|
||||
Class<? extends Serializable> idType = (Class<? extends Serializable>) entityMeta.idAttribute().type();
|
||||
|
||||
Serializable serId = stringToSerializable(sId, idType);
|
||||
|
||||
@@ -1001,58 +886,34 @@ public class RepositoryRestController
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private Map<String, Object> extractPropertiesLinkAware(final Object entity,
|
||||
final JpaEntityMetadata entityMetadata,
|
||||
final URI baseUri) {
|
||||
private Map<String, Object> extractPropertiesLinkAware(Object entity,
|
||||
EntityMetadata<AttributeMetadata> entityMetadata,
|
||||
URI baseUri) {
|
||||
final Map<String, Object> entityDto = new HashMap<String, Object>();
|
||||
|
||||
entityMetadata.doWithEmbedded(new Handler<Attribute, Void>() {
|
||||
@Override public Void handle(Attribute attr) {
|
||||
String name = attr.getName();
|
||||
Object val = entityMetadata.get(name, entity);
|
||||
if (null != val) {
|
||||
entityDto.put(name, val);
|
||||
}
|
||||
return null;
|
||||
for (Map.Entry<String, AttributeMetadata> attrMeta : entityMetadata.embeddedAttributes().entrySet()) {
|
||||
String name = attrMeta.getKey();
|
||||
Object val = attrMeta.getValue().get(entity);
|
||||
if (null != val) {
|
||||
entityDto.put(name, val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
entityMetadata.doWithLinked(new Handler<Attribute, Void>() {
|
||||
@Override public Void handle(Attribute attr) {
|
||||
String name = attr.getName();
|
||||
URI uri = UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(name)
|
||||
.build()
|
||||
.toUri();
|
||||
Link l = new SimpleLink(name, uri);
|
||||
List<Link> links = (List<Link>) entityDto.get(LINKS);
|
||||
if (null == links) {
|
||||
links = new ArrayList<Link>();
|
||||
entityDto.put(LINKS, links);
|
||||
}
|
||||
links.add(l);
|
||||
return null;
|
||||
for (String attrName : entityMetadata.linkedAttributes().keySet()) {
|
||||
URI uri = UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(attrName)
|
||||
.build()
|
||||
.toUri();
|
||||
Link l = new SimpleLink(attrName, uri);
|
||||
List<Link> links = (List<Link>) entityDto.get(LINKS);
|
||||
if (null == links) {
|
||||
links = new ArrayList<Link>();
|
||||
entityDto.put(LINKS, links);
|
||||
}
|
||||
});
|
||||
links.add(l);
|
||||
}
|
||||
|
||||
return entityDto;
|
||||
}
|
||||
|
||||
private class TypeMetaCacheEntry {
|
||||
EntityInformation entityInfo;
|
||||
Class<?> domainClass;
|
||||
Class<? extends Serializable> idType;
|
||||
EntityType entityType;
|
||||
JpaEntityMetadata entityMetadata;
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private TypeMetaCacheEntry(CrudRepository repo) {
|
||||
entityInfo = repositoryMetadata.entityInfoFor(repo);
|
||||
domainClass = entityInfo.getJavaType();
|
||||
idType = entityInfo.getIdType();
|
||||
entityType = repositoryMetadata.entityTypeFor(domainClass);
|
||||
entityMetadata = repositoryMetadata.entityMetadataFor(domainClass);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener;
|
||||
import org.springframework.data.rest.repository.jpa.JpaRepositoryExporter;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.View;
|
||||
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
|
||||
@@ -48,15 +50,21 @@ public class RepositoryRestMvcConfiguration {
|
||||
@Bean RepositoryRestController repositoryRestController() throws Exception {
|
||||
if (null == repositoryRestController) {
|
||||
this.repositoryRestController = new RepositoryRestController()
|
||||
.repositoryMetadata(parentConfig.jpaRepositoryMetadata())
|
||||
.conversionService(parentConfig.conversionService())
|
||||
.httpMessageConverters(parentConfig.httpMessageConverters())
|
||||
.viewResolver(contentNegotiatingViewResolver())
|
||||
.jsonMediaType("application/json");
|
||||
}
|
||||
return repositoryRestController;
|
||||
}
|
||||
|
||||
@Bean ValidatingRepositoryEventListener validatingRepositoryEventListener() {
|
||||
if (null == parentConfig.validatingRepositoryEventListener) {
|
||||
return new ValidatingRepositoryEventListener();
|
||||
}
|
||||
return parentConfig.validatingRepositoryEventListener;
|
||||
}
|
||||
|
||||
|
||||
@Bean RequestMappingHandlerMapping handlerMapping() {
|
||||
return new RequestMappingHandlerMapping();
|
||||
}
|
||||
|
||||
@@ -130,6 +130,7 @@ class RepositoryRestControllerSpec extends Specification {
|
||||
def addrLinks = model.resource?.links
|
||||
|
||||
then:
|
||||
null != addrLinks
|
||||
addrLinks.size() == 1
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.rest.repository.annotation.RestPathSegment;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@RestPathSegment("person")
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user