diff --git a/build.gradle b/build.gradle index 9086565af..c190cda96 100644 --- a/build.gradle +++ b/build.gradle @@ -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() } diff --git a/doc/main_wiki.md b/doc/main_wiki.md index 2051b3214..7a690344f 100644 --- a/doc/main_wiki.md +++ b/doc/main_wiki.md @@ -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 + diff --git a/gradle.properties b/gradle.properties index d6c71179f..ad3d92b63 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 30bd8bdbc..cbcb63a36 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/spring-data-rest-repository/build.gradle b/spring-data-rest-repository/build.gradle index 65ec93ad3..96e4e680d 100644 --- a/spring-data-rest-repository/build.gradle +++ b/spring-data-rest-repository/build.gradle @@ -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" } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/AttributeMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/AttributeMetadata.java new file mode 100644 index 000000000..9ce188578 --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/AttributeMetadata.java @@ -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 + */ +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); + +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/EntityMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/EntityMetadata.java new file mode 100644 index 000000000..695899e30 --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/EntityMetadata.java @@ -0,0 +1,22 @@ +package org.springframework.data.rest.repository; + +import java.util.Map; + +/** + * @author Jon Brisbin + */ +public interface EntityMetadata { + + Class type(); + + Map embeddedAttributes(); + + Map linkedAttributes(); + + A idAttribute(); + + A versionAttribute(); + + A attribute(String name); + +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/JpaEntityMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/JpaEntityMetadata.java deleted file mode 100644 index 3cc3aee9a..000000000 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/JpaEntityMetadata.java +++ /dev/null @@ -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 - */ -public class JpaEntityMetadata { - - private Class targetType; - private Map embeddedAttributes = new HashMap(); - private Map fields = new HashMap(); - private Map setters = new HashMap(); - private Map getters = new HashMap(); - private Map linkedAttributes = new HashMap(); - 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) 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 embeddedAttributes() { - return Collections.unmodifiableMap(embeddedAttributes); - } - - public Map 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 doWithEmbedded(Handler handler) { - if (null == handler) { - return null; - } - V v = null; - for (Attribute attr : embeddedAttributes.values()) { - v = handler.handle(attr); - } - return v; - } - - public V doWithLinked(String name, Handler 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 doWithLinked(Handler 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); - } - } - -} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/JpaRepositoryMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/JpaRepositoryMetadata.java deleted file mode 100644 index 1ba440bb2..000000000 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/JpaRepositoryMetadata.java +++ /dev/null @@ -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 - */ -public class JpaRepositoryMetadata - implements InitializingBean, - ApplicationContextAware { - - private ApplicationContext applicationContext; - private Map, RepositoryCacheEntry> repositories = new HashMap, 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 CrudRepository repositoryFor(String name) { - if (null != name) { - for (Map.Entry, RepositoryCacheEntry> entry : repositories.entrySet()) { - if (name.equals(repositoryNameFor(entry.getValue().repository))) { - return entry.getValue().repository; - } - } - } - return null; - } - - @SuppressWarnings({"unchecked"}) - public CrudRepository repositoryFor(Class domainClass) { - RepositoryCacheEntry entry = repositories.get(domainClass); - if (null != entry) { - return entry.repository; - } - return null; - } - - @SuppressWarnings({"unchecked"}) - public EntityInformation entityInfoFor(Class domainClass) { - RepositoryCacheEntry entry = repositories.get(domainClass); - if (null != entry) { - return entry.entityInfo; - } - return null; - } - - public EntityType entityTypeFor(Class domainClass) { - return metamodel.entity(domainClass); - } - - @SuppressWarnings({"unchecked"}) - public EntityInformation entityInfoFor(CrudRepository repository) { - for (Map.Entry, RepositoryCacheEntry> entry : repositories.entrySet()) { - if (entry.getValue().repository == repository) { - return entry.getValue().entityInfo; - } - } - return null; - } - - public JpaEntityMetadata entityMetadataFor(Class 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, 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 repositoryNames() { - List names = new ArrayList(); - for (Map.Entry, RepositoryCacheEntry> entry : repositories.entrySet()) { - names.add(entry.getValue().name); - } - return names; - } - - public void setRepositories(Map repositories) { - for (Map.Entry 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 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; - } - } - -} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java new file mode 100644 index 000000000..474d86518 --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java @@ -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 + */ +public abstract class RepositoryExporter, + R extends Repository, + E extends EntityMetadata> + implements ApplicationContextAware, + InitializingBean { + + protected ApplicationContext applicationContext; + protected EntityManager entityManager; + protected Map 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 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(); + Collection 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); + } + } + } + +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporterSupport.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporterSupport.java new file mode 100644 index 000000000..0e4480a2c --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporterSupport.java @@ -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 + */ +public abstract class RepositoryExporterSupport> { + + @Autowired + protected List repositoryExporters = Collections.emptyList(); + + public List getRepositoryExporters() { + return repositoryExporters; + } + + public void setRepositoryExporters(List repositoryExporters) { + this.repositoryExporters = repositoryExporters; + } + + public List repositoryExporters() { + return repositoryExporters; + } + + @SuppressWarnings({"unchecked"}) + public S repositoryExporters(List 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()); + } + } + +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryMetadata.java new file mode 100644 index 000000000..2b4d35f78 --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryMetadata.java @@ -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 + */ +public interface RepositoryMetadata, E extends EntityMetadata> { + + String name(); + + Class domainType(); + + R repository(); + + E entityMetadata(); + + Method queryMethod(String key); + + Map queryMethods(); + +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryNotFoundException.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryNotFoundException.java new file mode 100644 index 000000000..10af0047b --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryNotFoundException.java @@ -0,0 +1,18 @@ +package org.springframework.data.rest.repository; + +import org.springframework.dao.DataAccessResourceFailureException; + +/** + * @author Jon Brisbin + */ +public class RepositoryNotFoundException extends DataAccessResourceFailureException { + + public RepositoryNotFoundException(String msg) { + super(msg); + } + + public RepositoryNotFoundException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/ValidationErrors.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/ValidationErrors.java index 9590d9245..8647abc7f 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/ValidationErrors.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/ValidationErrors.java @@ -15,11 +15,11 @@ public class ValidationErrors extends AbstractErrors { private String name; private Object entity; - private JpaEntityMetadata entityMetadata; + private EntityMetadata entityMetadata; private List globalErrors = new ArrayList(); private List fieldErrors = new ArrayList(); - 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); } } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RestPathSegment.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RestPathSegment.java new file mode 100644 index 000000000..4098b588a --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RestPathSegment.java @@ -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 + */ +@Target({ + ElementType.METHOD, + ElementType.TYPE + }) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface RestPathSegment { + + String value(); + +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AbstractRepositoryEventListener.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AbstractRepositoryEventListener.java index fe6fbd6f3..2228e4f83 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AbstractRepositoryEventListener.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AbstractRepositoryEventListener.java @@ -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 */ public abstract class AbstractRepositoryEventListener> + extends RepositoryExporterSupport implements ApplicationListener, 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 repositoryExporters) { + super.setRepositoryExporters(repositoryExporters); } @Override public final void onApplicationEvent(RepositoryEvent event) { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/ValidatingRepositoryEventListener.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/ValidatingRepositoryEventListener.java index 5df2dcbcd..73166fa67 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/ValidatingRepositoryEventListener.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/ValidatingRepositoryEventListener.java @@ -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 validators = this.validators.get(event); if (null != validators) { for (Validator v : validators) { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaAttributeMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaAttributeMetadata.java new file mode 100644 index 000000000..707d1c9a4 --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaAttributeMetadata.java @@ -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 + */ +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; + } + +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaEntityMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaEntityMetadata.java new file mode 100644 index 000000000..050c0ceb5 --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaEntityMetadata.java @@ -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 + */ +public class JpaEntityMetadata implements EntityMetadata { + + private Class type; + private JpaAttributeMetadata idAttribute; + private JpaAttributeMetadata versionAttribute; + private Map embeddedAttributes = new HashMap(); + private Map linkedAttributes = new HashMap(); + + @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 embeddedAttributes() { + return embeddedAttributes; + } + + @Override public Map 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; + } + +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryExporter.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryExporter.java new file mode 100644 index 000000000..0c5ab057a --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryExporter.java @@ -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 + */ +public class JpaRepositoryExporter extends RepositoryExporter< + JpaRepositoryMetadata>, + Repository, + JpaEntityMetadata> { + + @SuppressWarnings({"unchecked"}) + @Override + protected JpaRepositoryMetadata> createRepositoryMetadata( + Class repoClass, + Repository repo, + String name, + EntityInformation entityInfo) { + return new JpaRepositoryMetadata(new Repositories(applicationContext), + name, + repoClass, + repo, + entityInfo, + entityManager); + } + +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryMetadata.java new file mode 100644 index 000000000..a667b4d5e --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryMetadata.java @@ -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 + */ +public class JpaRepositoryMetadata> implements RepositoryMetadata { + + private final String name; + private final Class repoClass; + private final R repository; + private final EntityInformation entityInfo; + private final Map queryMethods = new HashMap(); + 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 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 queryMethods() { + return Collections.unmodifiableMap(queryMethods); + } + +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java index e06999f60..bbcf5efe2 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java @@ -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() { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java index 69229e9e7..54a37eb6b 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java @@ -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 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 typeMetaCache = new ConcurrentHashMap(); private ConversionService conversionService = new DefaultConversionService(); - private List> httpMessageConverters; - private ContentNegotiatingViewResolver viewResolver; + private List> 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) 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) 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 etags = request.getHeaders().getIfNoneMatch(); for (String etag : etags) { @@ -361,7 +309,7 @@ public class RepositoryRestController headers.set("ETag", "\"" + version.toString() + "\""); } Map 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) 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) 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) 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 entry : ((Map) 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 entry : ((Map) 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) 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 rel = new AtomicReference(); Handler entityHandler = new Handler() { - @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) 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) 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 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) linkedRepoMeta.entityMetadata() + .idAttribute() + .type()); + Object linkedEntity = linkedRepo.findOne(sChildId); + if (null != linkedEntity) { + Map 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) 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 m = (Map) typeMeta.entityMetadata.get(property, entity); - if (null != m) { - for (Map.Entry 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) 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 m = (Map) attrMeta.get(entity); + if (null != m) { + for (Map.Entry 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 model, String... pathComponents) { List links = (List) 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 idType = entityInfo.getIdType(); + Class idType = (Class) entityMeta.idAttribute().type(); Serializable serId = stringToSerializable(sId, idType); @@ -1001,58 +886,34 @@ public class RepositoryRestController } @SuppressWarnings({"unchecked"}) - private Map extractPropertiesLinkAware(final Object entity, - final JpaEntityMetadata entityMetadata, - final URI baseUri) { + private Map extractPropertiesLinkAware(Object entity, + EntityMetadata entityMetadata, + URI baseUri) { final Map entityDto = new HashMap(); - entityMetadata.doWithEmbedded(new Handler() { - @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 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() { - @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 links = (List) entityDto.get(LINKS); - if (null == links) { - links = new ArrayList(); - 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 links = (List) entityDto.get(LINKS); + if (null == links) { + links = new ArrayList(); + entityDto.put(LINKS, links); } - }); + links.add(l); + } return entityDto; } - private class TypeMetaCacheEntry { - EntityInformation entityInfo; - Class domainClass; - Class 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); - } - } - } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java index aa92eb283..5e82e754b 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java @@ -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(); } diff --git a/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/RepositoryRestControllerSpec.groovy b/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/RepositoryRestControllerSpec.groovy index ea534bb36..d8de9e4e5 100644 --- a/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/RepositoryRestControllerSpec.groovy +++ b/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/RepositoryRestControllerSpec.groovy @@ -130,6 +130,7 @@ class RepositoryRestControllerSpec extends Specification { def addrLinks = model.resource?.links then: + null != addrLinks addrLinks.size() == 1 } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java index 01a516270..b1067a599 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java @@ -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 */ +@RestPathSegment("person") public interface PersonRepository extends CrudRepository { }