From 258af43397d9a80ee45a731b62b5d9298556701e Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 28 Nov 2012 18:15:49 +0100 Subject: [PATCH] DATAJPA-274 - Introduce JPA Metamodel based MappingContext implementation. Implemented rudimentary MappingContext to expose a JpaPersistentEntity through RepositoryFactoryInformation. --- .../mapping/JpaMetamodelMappingContext.java | 84 +++++++++++ .../data/jpa/mapping/JpaPersistentEntity.java | 28 ++++ .../jpa/mapping/JpaPersistentEntityImpl.java | 41 +++++ .../jpa/mapping/JpaPersistentProperty.java | 28 ++++ .../mapping/JpaPersistentPropertyImpl.java | 141 ++++++++++++++++++ .../support/JpaRepositoryFactory.java | 9 +- .../support/JpaRepositoryFactoryBean.java | 3 +- ...tamodelMappingContextIntegrationTests.java | 87 +++++++++++ .../JpaRepositoryFactoryBeanUnitTests.java | 4 + 9 files changed, 418 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java create mode 100644 src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntity.java create mode 100644 src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java create mode 100644 src/main/java/org/springframework/data/jpa/mapping/JpaPersistentProperty.java create mode 100644 src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java create mode 100644 src/test/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContextIntegrationTests.java diff --git a/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java b/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java new file mode 100644 index 000000000..d93fd4ff0 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.mapping; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; + +import javax.persistence.metamodel.Metamodel; + +import org.springframework.data.mapping.context.AbstractMappingContext; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.Assert; + +/** + * {@link MappingContext} implementation based on a Jpa {@link Metamodel}. + * + * @author Oliver Gierke + * @since 1.3 + */ +public class JpaMetamodelMappingContext extends + AbstractMappingContext, JpaPersistentProperty> { + + private final Metamodel model; + + /** + * Creates a new JPA {@link Metamodel} based {@link MappingContext}. + * + * @param model must not be {@literal null}. + */ + public JpaMetamodelMappingContext(Metamodel model) { + + Assert.notNull(model, "JPA Metamodel must not be null!"); + this.model = model; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation) + */ + @Override + protected JpaPersistentEntityImpl createPersistentEntity(TypeInformation typeInformation) { + return new JpaPersistentEntityImpl(typeInformation, null); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentProperty(java.lang.reflect.Field, java.beans.PropertyDescriptor, org.springframework.data.mapping.model.MutablePersistentEntity, org.springframework.data.mapping.model.SimpleTypeHolder) + */ + @Override + protected JpaPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, + JpaPersistentEntityImpl owner, SimpleTypeHolder simpleTypeHolder) { + return new JpaPersistentPropertyImpl(model, field, descriptor, owner, simpleTypeHolder); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.AbstractMappingContext#shouldCreatePersistentEntityFor(org.springframework.data.util.TypeInformation) + */ + @Override + protected boolean shouldCreatePersistentEntityFor(TypeInformation type) { + + try { + model.managedType(type.getType()); + return true; + } catch (IllegalArgumentException o_O) { + return false; + } + } +} diff --git a/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntity.java b/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntity.java new file mode 100644 index 000000000..790429de2 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntity.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.mapping; + +import org.springframework.data.mapping.PersistentEntity; + +/** + * Interface for a JPA-specific entity. + * + * @author Oliver Gierke + * @since 1.3 + */ +public interface JpaPersistentEntity extends PersistentEntity { + +} diff --git a/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java b/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java new file mode 100644 index 000000000..d91bccfb2 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.mapping; + +import java.util.Comparator; + +import org.springframework.data.mapping.model.BasicPersistentEntity; +import org.springframework.data.util.TypeInformation; + +/** + * Implementation of {@link JpaPersistentEntity}. + * + * @author Oliver Gierke + * @since 1.3 + */ +class JpaPersistentEntityImpl extends BasicPersistentEntity implements + JpaPersistentEntity { + + /** + * Creates a new {@link JpaPersistentEntityImpl} using the given {@link TypeInformation} and {@link Comparator}. + * + * @param information must not be {@literal null}. + * @param comparator must not be {@literal null}. + */ + public JpaPersistentEntityImpl(TypeInformation information, Comparator comparator) { + super(information, comparator); + } +} diff --git a/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentProperty.java b/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentProperty.java new file mode 100644 index 000000000..198db44fc --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentProperty.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.mapping; + +import org.springframework.data.mapping.PersistentProperty; + +/** + * Interface for a JPA-specific {@link PersistentProperty}. + * + * @author Oliver Gierke + * @since 1.3 + */ +public interface JpaPersistentProperty extends PersistentProperty { + +} diff --git a/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java b/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java new file mode 100644 index 000000000..15ce436fd --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java @@ -0,0 +1,141 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.mapping; + +import java.beans.PropertyDescriptor; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.EmbeddedId; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.metamodel.EmbeddableType; +import javax.persistence.metamodel.ManagedType; +import javax.persistence.metamodel.Metamodel; + +import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; +import org.springframework.data.mapping.model.SimpleTypeHolder; + +/** + * {@link JpaPersistentProperty} implementation usind a JPA {@link Metamodel}. + * + * @author Oliver Gierke + * @since 1.3 + */ +class JpaPersistentPropertyImpl extends AnnotationBasedPersistentProperty implements + JpaPersistentProperty { + + private static final Collection> ASSOCIATION_ANNOTATIONS; + private static final Collection> ID_ANNOTATIONS; + + static { + + Set> annotations = new HashSet>(); + annotations.add(OneToMany.class); + annotations.add(ManyToMany.class); + annotations.add(ManyToOne.class); + + ASSOCIATION_ANNOTATIONS = Collections.unmodifiableSet(annotations); + + annotations = new HashSet>(); + annotations.add(Id.class); + annotations.add(EmbeddedId.class); + + ID_ANNOTATIONS = annotations; + } + + private final Metamodel metamodel; + + /** + * Creates a new {@link JpaPersistentPropertyImpl} + * + * @param metamodel must not be {@literal null}. + * @param field must not be {@literal null}. + * @param propertyDescriptor can be {@literal null}. + * @param owner must not be {@literal null}. + * @param simpleTypeHolder must not be {@literal null}. + */ + public JpaPersistentPropertyImpl(Metamodel metamodel, Field field, PropertyDescriptor propertyDescriptor, + PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + + super(field, propertyDescriptor, owner, simpleTypeHolder); + this.metamodel = metamodel; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.AnnotationBasedPersistentProperty#isIdProperty() + */ + @Override + public boolean isIdProperty() { + + for (Class annotation : ID_ANNOTATIONS) { + if (field.getAnnotation(annotation) != null) { + return true; + } + } + + return false; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.AbstractPersistentProperty#isEntity() + */ + @Override + public boolean isEntity() { + + try { + ManagedType type = metamodel.managedType(getType()); + return !(type instanceof EmbeddableType); + } catch (IllegalArgumentException o_O) { + return false; + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.AnnotationBasedPersistentProperty#isAssociation() + */ + @Override + public boolean isAssociation() { + + for (Class annotationType : ASSOCIATION_ANNOTATIONS) { + if (field.getAnnotation(annotationType) != null) { + return true; + } + } + + return false; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.AbstractPersistentProperty#createAssociation() + */ + @Override + protected Association createAssociation() { + return new Association(this, null); + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java index da1dc3e9a..c60efef7c 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2011 the original author or authors. + * Copyright 2008-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,6 +50,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport { public JpaRepositoryFactory(EntityManager entityManager) { Assert.notNull(entityManager); + this.entityManager = entityManager; this.extractor = PersistenceProvider.fromEntityManager(entityManager); this.lockModePostProcessor = LockModeRepositoryPostProcessor.INSTANCE; @@ -59,14 +60,10 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport { /* * (non-Javadoc) - * - * @see - * org.springframework.data.repository.support.RepositoryFactorySupport# - * getTargetRepository(java.lang.Class) + * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.core.RepositoryMetadata) */ @Override protected Object getTargetRepository(RepositoryMetadata metadata) { - return getTargetRepository(metadata, entityManager); } diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java index 365c8c9f5..4593c4b55 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java @@ -20,6 +20,7 @@ import java.io.Serializable; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport; @@ -45,8 +46,8 @@ public class JpaRepositoryFactoryBean, S, ID extends */ @PersistenceContext public void setEntityManager(EntityManager entityManager) { - this.entityManager = entityManager; + setMappingContext(new JpaMetamodelMappingContext(entityManager.getMetamodel())); } /* diff --git a/src/test/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContextIntegrationTests.java b/src/test/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContextIntegrationTests.java new file mode 100644 index 000000000..fe1a62107 --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContextIntegrationTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.mapping; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.data.jpa.domain.sample.User; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Integration tests for {@link JpaMetamodelMappingContext}. + * + * @author Oliver Gierke + * @since 1.3 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:infrastructure.xml") +public class JpaMetamodelMappingContextIntegrationTests { + + JpaMetamodelMappingContext context; + + @PersistenceContext + EntityManager em; + + @Before + public void setUp() { + context = new JpaMetamodelMappingContext(em.getMetamodel()); + } + + @Test + public void setsUpMappingContextCorrectly() { + + JpaPersistentEntityImpl entity = context.getPersistentEntity(User.class); + assertThat(entity, is(notNullValue())); + } + + @Test + public void detectsIdProperty() { + + JpaPersistentEntityImpl entity = context.getPersistentEntity(User.class); + assertThat(entity.getIdProperty(), is(notNullValue())); + } + + @Test + public void detectsAssociation() { + + JpaPersistentEntityImpl entity = context.getPersistentEntity(User.class); + assertThat(entity, is(notNullValue())); + + JpaPersistentProperty property = entity.getPersistentProperty("manager"); + assertThat(property.isAssociation(), is(true)); + } + + @Test + public void detectsPropertyIsEntity() { + + JpaPersistentEntityImpl entity = context.getPersistentEntity(User.class); + assertThat(entity, is(notNullValue())); + + JpaPersistentProperty property = entity.getPersistentProperty("manager"); + assertThat(property.isEntity(), is(true)); + + property = entity.getPersistentProperty("lastname"); + assertThat(property.isEntity(), is(false)); + } +} diff --git a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java index 56db715b9..75b69d46b 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.Map; import javax.persistence.EntityManager; +import javax.persistence.metamodel.Metamodel; import org.junit.Before; import org.junit.Test; @@ -60,6 +61,8 @@ public class JpaRepositoryFactoryBeanUnitTests { PersistenceExceptionTranslator translator; @Mock Repository repository; + @Mock + Metamodel metamodel; @Before @SuppressWarnings("unchecked") @@ -70,6 +73,7 @@ public class JpaRepositoryFactoryBeanUnitTests { when(beanFactory.getBeansOfType(eq(PersistenceExceptionTranslator.class), anyBoolean(), anyBoolean())).thenReturn( beans); when(factory.getRepository(any(Class.class), any(Object.class))).thenReturn(repository); + when(entityManager.getMetamodel()).thenReturn(metamodel); // Setup standard factory configuration factoryBean = new DummyJpaRepositoryFactoryBean();