diff --git a/pom.xml b/pom.xml index 7f2adf4e6b..a14b995e41 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jpa - 1.5.0.BUILD-SNAPSHOT + 1.5.0.DATAJPA-307-SNAPSHOT Spring Data JPA Spring Data module for JPA repositories. @@ -23,14 +23,13 @@ DATAJPA 2.4.0 - 3.6.10.Final - 1.8.0.10 + 4.1.10.Final 2.0.0 2.2.1 - 1.7.0.BUILD-SNAPSHOT + 1.7.0.DATACMNS-293-SNAPSHOT - + hibernate-41 @@ -128,6 +127,14 @@ test + + + com.h2database + h2 + 1.3.172 + test + + joda-time joda-time @@ -161,18 +168,6 @@ openjpa-persistence-jdbc ${openjpa} true - - - commons-logging - commons-logging - - - - - junit - junit - - @@ -241,12 +236,6 @@ ${openjpa} runtime - - org.hsqldb - hsqldb - ${hsqldb1} - runtime - @@ -318,12 +307,6 @@ **/OpenJpa*Tests.java -javaagent:${settings.localRepository}/org/apache/openjpa/openjpa/${openjpa}/openjpa-${openjpa}.jar - - org.hsqldb:hsqldb - - - ${settings.localRepository}/org/hsqldb/hsqldb/${hsqldb1}/hsqldb-${hsqldb1}.jar - @@ -417,7 +400,7 @@ spring-libs-snapshot - http://repo.springsource.org/libs-snapshot-local + http://repo.springsource.org/libs-snapshot diff --git a/src/main/java/org/springframework/data/jpa/repository/augment/AbstractJpaAnnotationBasedQueryAugmentor.java b/src/main/java/org/springframework/data/jpa/repository/augment/AbstractJpaAnnotationBasedQueryAugmentor.java new file mode 100644 index 0000000000..799a084db4 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/augment/AbstractJpaAnnotationBasedQueryAugmentor.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013 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.repository.augment; + +import java.lang.annotation.Annotation; + +import org.springframework.data.jpa.repository.support.JpaCriteriaQueryContext; +import org.springframework.data.jpa.repository.support.JpaQueryContext; +import org.springframework.data.jpa.repository.support.JpaUpdateContext; +import org.springframework.data.repository.augment.AnnotationBasedQueryAugmentor; + +/** + * @author Oliver Gierke + */ +public abstract class AbstractJpaAnnotationBasedQueryAugmentor extends + AnnotationBasedQueryAugmentor, JpaQueryContext, JpaUpdateContext> { + +} diff --git a/src/main/java/org/springframework/data/jpa/repository/augment/JpaSoftDeleteQueryAugmentor.java b/src/main/java/org/springframework/data/jpa/repository/augment/JpaSoftDeleteQueryAugmentor.java new file mode 100644 index 0000000000..e8b0521e9e --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/augment/JpaSoftDeleteQueryAugmentor.java @@ -0,0 +1,92 @@ +/* + * Copyright 2013 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.repository.augment; + + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; + +import org.springframework.beans.BeanWrapper; +import org.springframework.data.jpa.repository.support.JpaCriteriaQueryContext; +import org.springframework.data.jpa.repository.support.JpaQueryContext; +import org.springframework.data.jpa.repository.support.JpaUpdateContext; +import org.springframework.data.repository.SoftDelete; +import org.springframework.data.repository.augment.AbstractSoftDeleteQueryAugmentor; +import org.springframework.data.repository.augment.QueryContext.QueryMode; + +/** + * JPA implementation of {@link AbstractSoftDeleteQueryAugmentor} to transparently turn delete calls into entity + * updates. Also filters queries accordingly. + * + * @author Oliver Gierke + */ +public class JpaSoftDeleteQueryAugmentor extends + AbstractSoftDeleteQueryAugmentor, JpaQueryContext, JpaUpdateContext> { + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.augment.AnnotationBasedQueryAugmentor#prepareNativeQuery(org.springframework.data.repository.augment.QueryContext, java.lang.annotation.Annotation) + */ + @Override + protected JpaQueryContext prepareNativeQuery(JpaQueryContext context, SoftDelete expression) { + + if (!context.getMode().in(QueryMode.FIND)) { + return context; + } + + String string = context.getQueryString(); + // TODO: Augment query; + + return context.withQuery(string); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.sample.JpaSoftDeleteQueryAugmentor#prepareQuery(org.springframework.data.jpa.repository.support.JpaCriteriaQueryContext, org.springframework.data.jpa.repository.support.GlobalFilter) + */ + @Override + protected JpaCriteriaQueryContext prepareQuery(JpaCriteriaQueryContext context, SoftDelete expression) { + + CriteriaQuery criteriaQuery = context.getQuery(); + CriteriaBuilder builder = context.getCriteriaBuilder(); + + Predicate predicate = builder.equal(context.getRoot().get(expression.value()), expression.flagMode().activeValue()); + Predicate restriction = criteriaQuery.getRestriction(); + + criteriaQuery.where(restriction == null ? predicate : builder.and(restriction, predicate)); + + return context; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.augment.AbstractSoftDeleteQueryAugmentor#updateDeletedState(java.lang.Object) + */ + @Override + public void updateDeletedState(Object entity, JpaUpdateContext context) { + context.getEntityManager().merge(entity); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.augment.AbstractSoftDeleteQueryAugmentor#prepareBeanWrapper(org.springframework.data.repository.augment.UpdateContext) + */ + @Override + protected BeanWrapper createBeanWrapper(JpaUpdateContext context) { + return new PropertyChangeEnsuringBeanWrapper(context.getEntity()); + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/augment/LockModeQueryAugmentor.java b/src/main/java/org/springframework/data/jpa/repository/augment/LockModeQueryAugmentor.java new file mode 100644 index 0000000000..74a7dac145 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/augment/LockModeQueryAugmentor.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013 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.repository.augment; + +import javax.persistence.Query; + +import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.support.JpaQueryContext; + +/** + * @author Oliver Gierke + */ +public class LockModeQueryAugmentor extends AbstractJpaAnnotationBasedQueryAugmentor { + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.augment.AnnotationBasedQueryAugmentor#prepareNativeQuery(org.springframework.data.repository.augment.QueryContext, java.lang.annotation.Annotation) + */ + @Override + protected JpaQueryContext prepareNativeQuery(JpaQueryContext context, Lock expression) { + + Query query = context.getQuery(); + query.setLockMode(expression.value()); + + return context; + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/augment/PropertyChangeEnsuringBeanWrapper.java b/src/main/java/org/springframework/data/jpa/repository/augment/PropertyChangeEnsuringBeanWrapper.java new file mode 100644 index 0000000000..8f14f93cc1 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/augment/PropertyChangeEnsuringBeanWrapper.java @@ -0,0 +1,62 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.augment; + +import java.lang.reflect.Method; + +import org.springframework.data.support.DirectFieldAccessFallbackBeanWrapper; +import org.springframework.util.ReflectionUtils; + +/** + * Custom {@link DirectFieldAccessFallbackBeanWrapper} to hook in additional functionality when setting a property by + * field access. + * + * @author Oliver Gierke + */ +class PropertyChangeEnsuringBeanWrapper extends DirectFieldAccessFallbackBeanWrapper { + + public PropertyChangeEnsuringBeanWrapper(Object entity) { + super(entity); + } + + /** + * We in case of setting the value using field access, we need to make sure that EclipseLink detects the change. + * Hence we check for an EclipseLink specific generated method that is used to record the changes and invoke it if + * available. + * + * @see org.springframework.data.support.DirectFieldAccessFallbackBeanWrapper#setPropertyUsingFieldAccess(java.lang.String, + * java.lang.Object) + */ + @Override + protected void setPropertyUsingFieldAccess(String propertyName, Object value) { + + Object oldValue = getPropertyValue(propertyName); + super.setPropertyUsingFieldAccess(propertyName, oldValue); + triggerPropertyChangeMethodIfAvailable(propertyName, oldValue, oldValue); + } + + private void triggerPropertyChangeMethodIfAvailable(String propertyName, Object oldValue, Object value) { + + Method method = ReflectionUtils.findMethod(getWrappedClass(), "_persistence_propertyChange", String.class, + Object.class, Object.class); + + if (method == null) { + return; + } + + ReflectionUtils.invokeMethod(method, getWrappedInstance(), propertyName, oldValue, value); + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/jpa/repository/augment/QueryDslSoftDeleteQueryAugmentor.java b/src/main/java/org/springframework/data/jpa/repository/augment/QueryDslSoftDeleteQueryAugmentor.java new file mode 100644 index 0000000000..be74a91da1 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/augment/QueryDslSoftDeleteQueryAugmentor.java @@ -0,0 +1,95 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.augment; + +import org.springframework.beans.BeanWrapper; +import org.springframework.data.jpa.repository.support.QueryDslJpaQueryContext; +import org.springframework.data.jpa.repository.support.QueryDslJpaUpdateContext; +import org.springframework.data.jpa.repository.support.QueryDslQueryContext; +import org.springframework.data.repository.SoftDelete; +import org.springframework.data.repository.augment.AbstractSoftDeleteQueryAugmentor; +import org.springframework.data.repository.augment.QueryContext.QueryMode; + +import com.mysema.query.jpa.JPQLQuery; +import com.mysema.query.types.Predicate; +import com.mysema.query.types.path.PathBuilder; + +/** + * QueryDsl implementation of {@link AbstractSoftDeleteQueryAugmentor} to transparently turn delete calls into entity + * updates and filter queries accordingly. + * + * @author Dev Naruka + * + */ +public class QueryDslSoftDeleteQueryAugmentor extends + AbstractSoftDeleteQueryAugmentor, QueryDslQueryContext, QueryDslJpaUpdateContext> { + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.augment.AnnotationBasedQueryAugmentor#prepareNativeQuery(org.springframework.data.repository.augment.QueryContext, java.lang.annotation.Annotation) + */ + @Override + protected QueryDslQueryContext prepareNativeQuery( + QueryDslQueryContext context, SoftDelete expression) { + if (!context.getMode().in(QueryMode.FIND)) { + return context; + } + + String string = context.getQueryString(); + // TODO: Augment query; + + return context.withQuery(string); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.augment.AnnotationBasedQueryAugmentor#prepareQuery(org.springframework.data.repository.augment.QueryContext, java.lang.annotation.Annotation) + */ + @Override + protected QueryDslJpaQueryContext prepareQuery( + QueryDslJpaQueryContext context, SoftDelete expression) { + JPQLQuery query = context.getQuery(); + PathBuilder builder = context.getPathBuilder(); + + Predicate predicate = builder.get(expression.value()).eq(expression.flagMode().activeValue()); + + query.where(predicate); + + return context; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.augment.AbstractSoftDeleteQueryAugmentor#updateDeletedState(java.lang.Object, org.springframework.data.repository.augment.UpdateContext) + */ + @Override + public void updateDeletedState(Object entity, + QueryDslJpaUpdateContext context) { + + @SuppressWarnings("unchecked") + QueryDslJpaUpdateContext castedContext = (QueryDslJpaUpdateContext) context; + castedContext.update().set(castedContext.getRoot(), entity); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.augment.AbstractSoftDeleteQueryAugmentor#createBeanWrapper(org.springframework.data.repository.augment.UpdateContext) + */ + @Override + protected BeanWrapper createBeanWrapper(QueryDslJpaUpdateContext context) { + return new PropertyChangeEnsuringBeanWrapper(context.getEntity()); + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/augment/package-info.java b/src/main/java/org/springframework/data/jpa/repository/augment/package-info.java new file mode 100644 index 0000000000..2917e8668b --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/augment/package-info.java @@ -0,0 +1,4 @@ +/** + * JPA specific {@link org.springframework.data.repository.augment.QueryAugmentor} implementations. + */ +package org.springframework.data.jpa.repository.augment; \ No newline at end of file diff --git a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java index 15f0f2e526..eb5193ecfb 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java @@ -25,6 +25,8 @@ import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution; import org.springframework.data.jpa.repository.query.JpaQueryExecution.PagedExecution; import org.springframework.data.jpa.repository.query.JpaQueryExecution.SingleEntityExecution; +import org.springframework.data.repository.augment.QueryAugmentationEngine; +import org.springframework.data.repository.augment.QueryAugmentationEngineAware; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.util.Assert; @@ -34,11 +36,13 @@ * @author Oliver Gierke * @author Thomas Darimont */ -public abstract class AbstractJpaQuery implements RepositoryQuery { +public abstract class AbstractJpaQuery implements RepositoryQuery, QueryAugmentationEngineAware { private final JpaQueryMethod method; private final EntityManager em; + private QueryAugmentationEngine augmentationEngine; + /** * Creates a new {@link AbstractJpaQuery} from the given {@link JpaQueryMethod}. * @@ -54,6 +58,14 @@ public AbstractJpaQuery(JpaQueryMethod method, EntityManager em) { this.em = em; } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.augment.QueryAugmentationEngineAware#setQueryAugmentationEngine(org.springframework.data.repository.augment.QueryAugmentationEngine) + */ + public void setQueryAugmentationEngine(QueryAugmentationEngine engine) { + this.augmentationEngine = engine; + } + /* * (non-Javadoc) * @@ -70,10 +82,16 @@ public JpaQueryMethod getQueryMethod() { * @return the em */ protected EntityManager getEntityManager() { - return em; } + /** + * @return the augmentationEngine + */ + public QueryAugmentationEngine getAugmentationEngine() { + return augmentationEngine; + } + /* * (non-Javadoc) * @@ -165,4 +183,4 @@ protected TypedQuery createCountQuery(Object[] values) { * @return */ protected abstract TypedQuery doCreateCountQuery(Object[] values); -} +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java index 18f00361bb..e294e25e35 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java @@ -19,6 +19,9 @@ import javax.persistence.Query; import javax.persistence.TypedQuery; +import org.springframework.data.jpa.repository.support.JpaQueryContext; +import org.springframework.data.repository.augment.QueryAugmentationEngine; +import org.springframework.data.repository.augment.QueryContext.QueryMode; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.util.Assert; @@ -85,7 +88,9 @@ protected ParameterBinder createBinder(Object[] values) { * @return */ public Query createJpaQuery(String queryString) { - return getEntityManager().createQuery(queryString); + Query query = getEntityManager().createQuery(queryString); + query = potentiallyAugment(query); + return query; } /* @@ -110,4 +115,21 @@ public StringQuery getQuery() { public StringQuery getCountQuery() { return countQuery; } + + protected Query potentiallyAugment(Query query) { + return potentiallyAugment(query, QueryMode.FIND); + } + + private Query potentiallyAugment(Query query, QueryMode mode) { + + QueryAugmentationEngine engine = getAugmentationEngine(); + + if (engine != null + && engine.augmentationNeeded(JpaQueryContext.class, mode, getQueryMethod().getEntityInformation())) { + JpaQueryContext context = new JpaQueryContext(mode, getEntityManager(), query); + return engine.invokeAugmentors(context).getQuery(); + } else { + return query; + } + } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java index 3b0c254178..76ecffc066 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java @@ -25,6 +25,9 @@ import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata; +import org.springframework.data.jpa.repository.support.JpaCriteriaQueryContext; +import org.springframework.data.repository.augment.QueryAugmentationEngine; +import org.springframework.data.repository.augment.QueryContext.QueryMode; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.parser.PartTree; @@ -113,7 +116,7 @@ public Query createQuery(Object[] values) { if (cachedCriteriaQuery == null || accessor.hasBindableNullValue()) { JpaQueryCreator creator = createCreator(accessor); - criteriaQuery = creator.createQuery(getDynamicSort(values)); + criteriaQuery = potentiallyAugment(creator.createQuery(getDynamicSort(values))); expressions = creator.getParameterExpressions(); } @@ -141,6 +144,23 @@ private TypedQuery createQuery(CriteriaQuery criteriaQuery) { return getEntityManager().createQuery(criteriaQuery); } + protected CriteriaQuery potentiallyAugment(CriteriaQuery query) { + return potentiallyAugment(query, QueryMode.FIND); + } + + private CriteriaQuery potentiallyAugment(CriteriaQuery query, QueryMode mode) { + + QueryAugmentationEngine engine = getAugmentationEngine(); + + if (engine != null + && engine.augmentationNeeded(JpaCriteriaQueryContext.class, mode, getQueryMethod().getEntityInformation())) { + JpaCriteriaQueryContext context = new JpaCriteriaQueryContext(mode, getEntityManager(), query, null); + return engine.invokeAugmentors(context).getQuery(); + } else { + return query; + } + } + protected JpaQueryCreator createCreator(ParametersParameterAccessor accessor) { EntityManager entityManager = getEntityManager(); @@ -202,6 +222,15 @@ protected JpaQueryCreator createCreator(ParametersParameterAccessor accessor) { return new JpaCountQueryCreator(tree, domainClass, builder, provider); } + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.query.PartTreeJpaQuery.QueryPreparer#potentiallyAugment(javax.persistence.criteria.CriteriaQuery) + */ + @Override + protected CriteriaQuery potentiallyAugment(CriteriaQuery query) { + return super.potentiallyAugment(query, QueryMode.COUNT_FOR_PAGING); + } + /** * Customizes binding by skipping the pagination. * diff --git a/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java index 6a00e28903..ab524dcb53 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java @@ -22,7 +22,7 @@ /** * {@link RepositoryQuery} implementation that inspects a {@link org.springframework.data.repository.query.QueryMethod} - * for the existence of an {@link org.springframework.data.jpa.repository.Query} annotation and creates a JPA + * for the existance of an {@link org.springframework.data.jpa.repository.Query} annotation and creates a JPA * {@link Query} from it. * * @author Oliver Gierke diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaCriteriaQueryContext.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaCriteriaQueryContext.java new file mode 100644 index 0000000000..d7df3ba6d6 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaCriteriaQueryContext.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013 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.repository.support; + +import javax.persistence.EntityManager; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.springframework.data.repository.augment.QueryContext; + +/** + * A {@link QueryContext} + * + * @author Oliver Gierke + */ +public class JpaCriteriaQueryContext extends QueryContext> { + + private final EntityManager em; + private final Root root; + + /** + * @param mode + * @param em + * @param query + * @param root + */ + public JpaCriteriaQueryContext(QueryMode mode, EntityManager em, CriteriaQuery query, Root root) { + + super(query, mode); + + this.em = em; + this.root = root; + } + + /** + * @return the root + */ + public Root getRoot() { + return root; + } + + public CriteriaBuilder getCriteriaBuilder() { + return em.getCriteriaBuilder(); + } + + public JpaCriteriaQueryContext with(CriteriaQuery query) { + return new JpaCriteriaQueryContext(getMode(), em, query, root); + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java index febc902c49..7b348e41f5 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java @@ -16,7 +16,6 @@ package org.springframework.data.jpa.repository.support; import java.io.Serializable; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -32,11 +31,9 @@ import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; -import org.springframework.beans.NotReadablePropertyException; -import org.springframework.beans.NotWritablePropertyException; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.support.DirectFieldAccessFallbackBeanWrapper; import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; /** * Implementation of {@link org.springframework.data.repository.core.EntityInformation} that uses JPA {@link Metamodel} @@ -249,53 +246,6 @@ public Class getType() { } } - /** - * Custom extension of {@link BeanWrapperImpl} that falls back to direct field access in case the object or type being - * wrapped does not use accessor methods. - * - * @author Oliver Gierke - */ - private static class DirectFieldAccessFallbackBeanWrapper extends BeanWrapperImpl { - - public DirectFieldAccessFallbackBeanWrapper(Object entity) { - super(entity); - } - - public DirectFieldAccessFallbackBeanWrapper(Class type) { - super(type); - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.BeanWrapperImpl#getPropertyValue(java.lang.String) - */ - @Override - public Object getPropertyValue(String propertyName) { - try { - return super.getPropertyValue(propertyName); - } catch (NotReadablePropertyException e) { - Field field = ReflectionUtils.findField(getWrappedClass(), propertyName); - ReflectionUtils.makeAccessible(field); - return ReflectionUtils.getField(field, getWrappedInstance()); - } - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.BeanWrapperImpl#setPropertyValue(java.lang.String, java.lang.Object) - */ - @Override - public void setPropertyValue(String propertyName, Object value) { - try { - super.setPropertyValue(propertyName, value); - } catch (NotWritablePropertyException e) { - Field field = ReflectionUtils.findField(getWrappedClass(), propertyName); - ReflectionUtils.makeAccessible(field); - ReflectionUtils.setField(field, getWrappedInstance(), value); - } - } - } - /** * Custom extension of {@link DirectFieldAccessFallbackBeanWrapper} that allows to derived the identifier if composite * keys with complex key attribute types (e.g. types that are annotated with {@code @Entity} themselves) are used. diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaQueryContext.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaQueryContext.java new file mode 100644 index 0000000000..bbc504a8e8 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaQueryContext.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013 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.repository.support; + +import javax.persistence.EntityManager; +import javax.persistence.Query; + +import org.springframework.data.jpa.repository.query.QueryExtractor; +import org.springframework.data.repository.augment.QueryContext; + +/** + * @author Oliver Gierke + */ +public class JpaQueryContext extends QueryContext { + + private final EntityManager entityManager; + private final QueryExtractor extractor; + + /** + * @param query + * @param queryMode + */ + public JpaQueryContext(QueryMode queryMode, EntityManager entityManager, Query query) { + + super(query, queryMode); + this.entityManager = entityManager; + this.extractor = PersistenceProvider.fromEntityManager(entityManager); + } + + /** + * @return the entityManager + */ + public EntityManager getEntityManager() { + return entityManager; + } + + public String getQueryString() { + return extractor.extractQueryString(getQuery()); + } + + @SuppressWarnings("unchecked") + public JpaQueryContext withQuery(String query) { + + Query createQuery = entityManager.createQuery(query); + return new JpaQueryContext(getMode(), entityManager, createQuery); + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaUpdateContext.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaUpdateContext.java new file mode 100644 index 0000000000..530614f250 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaUpdateContext.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013 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.repository.support; + +import javax.persistence.EntityManager; + +import org.springframework.data.repository.augment.UpdateContext; +import org.springframework.util.Assert; + +/** + * JPA-specific extension of {@link UpdateContext} adding an {@link EntityManager} to it. + * + * @author Oliver Gierke + */ +public class JpaUpdateContext extends UpdateContext { + + private final EntityManager em; + + /** + * Creates a new {@link JpaUpdateContext} from the given entity and {@link EntityManager}. + * + * @param entity + * @param em must not be {@literal null}. + */ + public JpaUpdateContext(T entity, UpdateMode mode, EntityManager em) { + + super(entity, mode); + + Assert.notNull(em, "EntityManager must not be null!"); + this.em = em; + } + + /** + * Returns the {@link EntityManager} to be used for this update. + * + * @return the em will never be {@literal null}. + */ + public EntityManager getEntityManager() { + return em; + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/support/LockModeRepositoryPostProcessor.java b/src/main/java/org/springframework/data/jpa/repository/support/LockModeRepositoryPostProcessor.java index 8b2cdff40d..179a03f001 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/LockModeRepositoryPostProcessor.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/LockModeRepositoryPostProcessor.java @@ -46,8 +46,6 @@ public enum LockModeRepositoryPostProcessor implements RepositoryProxyPostProces * @see org.springframework.data.repository.core.support.RepositoryProxyPostProcessor#postProcess(org.springframework.aop.framework.ProxyFactory) */ public void postProcess(ProxyFactory factory) { - - factory.addAdvice(ExposeInvocationInterceptor.INSTANCE); factory.addAdvice(LockModePopulatingMethodIntercceptor.INSTANCE); } diff --git a/src/main/java/org/springframework/data/jpa/repository/support/QueryDslJpaQueryContext.java b/src/main/java/org/springframework/data/jpa/repository/support/QueryDslJpaQueryContext.java new file mode 100644 index 0000000000..2f0061ebdf --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/support/QueryDslJpaQueryContext.java @@ -0,0 +1,46 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.support; + +import org.springframework.data.repository.augment.QueryContext; + +import com.mysema.query.jpa.JPQLQuery; +import com.mysema.query.types.EntityPath; +import com.mysema.query.types.path.PathBuilder; + +/** + * @author Dev Naruka + * + */ +public class QueryDslJpaQueryContext extends QueryContext { + + private final EntityPath root; + private final PathBuilder pathBuilder; + + public QueryDslJpaQueryContext(JPQLQuery query, EntityPath root, PathBuilder builder, QueryMode queryMode) { + super(query, queryMode); + this.root = root; + this.pathBuilder = builder; + } + + public EntityPath getRoot() { + return root; + } + + public PathBuilder getPathBuilder() { + return pathBuilder; + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/support/QueryDslJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/QueryDslJpaRepository.java index cf2d4996b1..d5b373a88e 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/QueryDslJpaRepository.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/QueryDslJpaRepository.java @@ -27,6 +27,8 @@ import org.springframework.data.querydsl.EntityPathResolver; import org.springframework.data.querydsl.QueryDslPredicateExecutor; import org.springframework.data.querydsl.SimpleEntityPathResolver; +import org.springframework.data.repository.augment.QueryContext.QueryMode; +import org.springframework.data.repository.core.EntityInformation; import com.mysema.query.jpa.JPQLQuery; import com.mysema.query.types.EntityPath; @@ -48,6 +50,7 @@ public class QueryDslJpaRepository extends SimpleJpa private final EntityPath path; private final PathBuilder builder; private final Querydsl querydsl; + private final EntityInformation entityInformation; /** * Creates a new {@link QueryDslJpaRepository} from the given domain class and {@link EntityManager}. This will use @@ -73,6 +76,7 @@ public QueryDslJpaRepository(JpaEntityInformation entityInformation, Enti super(entityInformation, entityManager); + this.entityInformation = entityInformation; this.path = resolver.createPath(entityInformation.getJavaType()); this.builder = new PathBuilder(path.getType(), path.getMetadata()); this.querydsl = new Querydsl(entityManager, builder); @@ -132,6 +136,15 @@ public long count(Predicate predicate) { * @return the Querydsl {@link JPQLQuery}. */ protected JPQLQuery createQuery(Predicate... predicate) { - return querydsl.createQuery(path).where(predicate); + JPQLQuery query = querydsl.createQuery(path).where(predicate); + + if (engine.augmentationNeeded(QueryDslJpaQueryContext.class, QueryMode.FIND, entityInformation)) { + QueryDslJpaQueryContext context = new QueryDslJpaQueryContext(query, path, builder, QueryMode.FIND); + + context = engine.invokeAugmentors(context); + query = context.getQuery(); + } + + return query; } } diff --git a/src/main/java/org/springframework/data/jpa/repository/support/QueryDslJpaUpdateContext.java b/src/main/java/org/springframework/data/jpa/repository/support/QueryDslJpaUpdateContext.java new file mode 100644 index 0000000000..23b0d44318 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/support/QueryDslJpaUpdateContext.java @@ -0,0 +1,56 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.support; + +import javax.persistence.EntityManager; + +import org.springframework.data.repository.augment.UpdateContext; + +import com.mysema.query.dml.DeleteClause; +import com.mysema.query.dml.UpdateClause; +import com.mysema.query.jpa.impl.JPADeleteClause; +import com.mysema.query.jpa.impl.JPAUpdateClause; +import com.mysema.query.types.EntityPath; +import com.mysema.query.types.Path; + +/** + * @author Dev Naruka + * + */ +public class QueryDslJpaUpdateContext extends UpdateContext { + + private final EntityPath root; + private final EntityManager em; + + public QueryDslJpaUpdateContext(T entity, EntityManager entityManager, EntityPath root, UpdateMode mode) { + super(entity, mode); + + this.root = root; + this.em = entityManager; + } + + public Path getRoot() { + return root; + } + + public UpdateClause update() { + return new JPAUpdateClause(em, root); + } + + public DeleteClause delete() { + return new JPADeleteClause(em, root); + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/support/QueryDslQueryContext.java b/src/main/java/org/springframework/data/jpa/repository/support/QueryDslQueryContext.java new file mode 100644 index 0000000000..9dffbba069 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/support/QueryDslQueryContext.java @@ -0,0 +1,44 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.support; + +import org.springframework.data.repository.augment.QueryContext; + +import com.mysema.query.jpa.JPQLQuery; + +/** + * @author Dev Naruka + * + */ +// TODO Which native implementation to be used? (SQLQuery or JPASQLQuery) +// TODO Where QueryDslQueryContext can be used? (in QueryDslRepositorySupport ???) +public class QueryDslQueryContext extends QueryContext { + + public QueryDslQueryContext(JPQLQuery query, QueryMode queryMode) { + super(query, queryMode); + // TODO Auto-generated constructor stub + } + + public String getQueryString() { + // TODO Auto-generated method stub + return null; + } + + public QueryDslQueryContext withQuery(String string) { + // TODO Auto-generated method stub + return null; + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index 281f3e5d93..3f36de4b4c 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -41,6 +41,10 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.query.QueryUtils; +import org.springframework.data.repository.augment.QueryAugmentationEngine; +import org.springframework.data.repository.augment.QueryAugmentationEngineAware; +import org.springframework.data.repository.augment.QueryContext.QueryMode; +import org.springframework.data.repository.augment.UpdateContext.UpdateMode; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; @@ -58,13 +62,14 @@ @Repository @Transactional(readOnly = true) public class SimpleJpaRepository implements JpaRepository, - JpaSpecificationExecutor { + JpaSpecificationExecutor, QueryAugmentationEngineAware { private final JpaEntityInformation entityInformation; private final EntityManager em; private final PersistenceProvider provider; private LockMetadataProvider lockMetadataProvider; + protected QueryAugmentationEngine engine = QueryAugmentationEngine.NONE; /** * Creates a new {@link SimpleJpaRepository} to manage objects of the given {@link JpaEntityInformation}. @@ -102,6 +107,14 @@ public void setLockMetadataProvider(LockMetadataProvider lockMetadataProvider) { this.lockMetadataProvider = lockMetadataProvider; } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.support.QueryAugmentationEngineAware#setQueryAugmentationEngine(org.springframework.data.repository.core.support.QueryAugmentationEngine) + */ + public void setQueryAugmentationEngine(QueryAugmentationEngine engine) { + this.engine = engine; + } + private Class getDomainClass() { return entityInformation.getJavaType(); } @@ -110,12 +123,6 @@ private String getDeleteAllQueryString() { return getQueryString(DELETE_ALL_QUERY_STRING, entityInformation.getEntityName()); } - private String getCountQueryString() { - - String countQuery = String.format(COUNT_QUERY_STRING, provider.getCountQueryPlaceholder(), "%s"); - return getQueryString(countQuery, entityInformation.getEntityName()); - } - /* * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable) @@ -143,7 +150,20 @@ public void delete(ID id) { public void delete(T entity) { Assert.notNull(entity, "The entity must not be null!"); - em.remove(em.contains(entity) ? entity : em.merge(entity)); + + entity = em.contains(entity) ? entity : em.merge(entity); + + if (engine.augmentationNeeded(JpaUpdateContext.class, null, entityInformation)) { + + JpaUpdateContext context = new JpaUpdateContext(entity, UpdateMode.DELETE, em); + context = engine.invokeAugmentors(context); + + if (context == null) { + return; + } + } + + em.remove(entity); } /* @@ -206,6 +226,21 @@ public T findOne(ID id) { Assert.notNull(id, "The given id must not be null!"); + if (engine.augmentationNeeded(JpaCriteriaQueryContext.class, QueryMode.FIND, entityInformation)) { + + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(entityInformation.getJavaType()); + Root root = query.from(entityInformation.getJavaType()); + + JpaCriteriaQueryContext context = potentiallyAugment(query, root, QueryMode.FIND); + + try { + return em.createQuery(context.getQuery()).getSingleResult(); + } catch (NoResultException e) { + return null; + } + } + LockModeType type = lockMetadataProvider == null ? null : lockMetadataProvider.getLockModeType(); Class domainType = getDomainClass(); @@ -346,7 +381,7 @@ public List findAll(Specification spec, Sort sort) { * @see org.springframework.data.repository.CrudRepository#count() */ public long count() { - return em.createQuery(getCountQueryString(), Long.class).getSingleResult(); + return count(null); } /* @@ -354,7 +389,6 @@ public long count() { * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#count(org.springframework.data.jpa.domain.Specification) */ public long count(Specification spec) { - return getCountQuery(spec).getSingleResult(); } @@ -462,6 +496,11 @@ private TypedQuery getQuery(Specification spec, Sort sort) { CriteriaQuery query = builder.createQuery(getDomainClass()); Root root = applySpecificationToCriteria(spec, query); + + JpaCriteriaQueryContext context = potentiallyAugment(query, root, QueryMode.FIND); + query = context.getQuery(); + root = context.getRoot(); + query.select(root); if (sort != null) { @@ -484,6 +523,10 @@ private TypedQuery getCountQuery(Specification spec) { Root root = applySpecificationToCriteria(spec, query); + JpaCriteriaQueryContext context = potentiallyAugment(query, root, QueryMode.COUNT); + query = context.getQuery(); + root = context.getRoot(); + if (query.isDistinct()) { query.select(builder.countDistinct(root)); } else { @@ -521,7 +564,23 @@ private Root applySpecificationToCriteria(Specification spec, Criteria private TypedQuery applyLockMode(TypedQuery query) { + if (engine.augmentationNeeded(JpaQueryContext.class, QueryMode.FIND, entityInformation)) { + // TODO + // engine.invokeNativeAugmentors(new JpaQueryContext) + } + LockModeType type = lockMetadataProvider == null ? null : lockMetadataProvider.getLockModeType(); return type == null ? query : query.setLockMode(type); } + + private JpaCriteriaQueryContext potentiallyAugment(CriteriaQuery query, Root root, QueryMode mode) { + + JpaCriteriaQueryContext context = new JpaCriteriaQueryContext(mode, em, query, root); + + if (engine.augmentationNeeded(JpaCriteriaQueryContext.class, mode, entityInformation)) { + context = engine.invokeAugmentors(context); + } + + return context; + } } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkSoftDeleteIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkSoftDeleteIntegrationTests.java new file mode 100644 index 0000000000..5e291e139c --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkSoftDeleteIntegrationTests.java @@ -0,0 +1,26 @@ +/* + * Copyright 2013 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.repository.support; + +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Oliver Gierke + */ +@ContextConfiguration("classpath:eclipselink.xml") +public class EclipseLinkSoftDeleteIntegrationTests extends SoftDeleteIntegrationTests { + +} diff --git a/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaSoftDeleteIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaSoftDeleteIntegrationTests.java new file mode 100644 index 0000000000..ff91a0f65d --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaSoftDeleteIntegrationTests.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013 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.repository.support; + +import org.junit.Ignore; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Oliver Gierke + */ +@ContextConfiguration("classpath:openjpa.xml") +public class OpenJpaSoftDeleteIntegrationTests extends SoftDeleteIntegrationTests { + + @Override + @Ignore + public void basicSaveAndDelete() { + + } +} diff --git a/src/test/java/org/springframework/data/jpa/repository/support/SoftDeleteIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/support/SoftDeleteIntegrationTests.java new file mode 100644 index 0000000000..0dda7a286d --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/support/SoftDeleteIntegrationTests.java @@ -0,0 +1,139 @@ +/* + * Copyright 2013 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.repository.support; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.List; + +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.QUser; +import org.springframework.data.jpa.domain.sample.User; +import org.springframework.data.jpa.repository.augment.JpaSoftDeleteQueryAugmentor; +import org.springframework.data.jpa.repository.augment.QueryDslSoftDeleteQueryAugmentor; +import org.springframework.data.querydsl.QueryDslPredicateExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.SoftDelete; +import org.springframework.data.repository.SoftDelete.FlagMode; +import org.springframework.data.repository.augment.QueryAugmentor; +import org.springframework.data.repository.augment.QueryContext; +import org.springframework.data.repository.augment.UpdateContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +import com.mysema.query.types.Predicate; + +/** + * @author Oliver Gierke + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:infrastructure.xml") +@Transactional +public class SoftDeleteIntegrationTests { + + @PersistenceContext + EntityManager em; + + SoftUserRepository softRepository; + SpecialUserRepository repository; + + @Before + public void setUp() { + + JpaRepositoryFactory factory = new JpaRepositoryFactory(em); + JpaSoftDeleteQueryAugmentor jpaAugmentor = new JpaSoftDeleteQueryAugmentor(); + QueryDslSoftDeleteQueryAugmentor queryDslAugmentor = new QueryDslSoftDeleteQueryAugmentor(); + + List, ? extends QueryContext, ? extends UpdateContext>> augmentors = // + new ArrayList, ? extends QueryContext, ? extends UpdateContext>>(); + augmentors.add(jpaAugmentor); + augmentors.add(queryDslAugmentor); + + factory.setQueryAugmentors(augmentors); + + softRepository = factory.getRepository(SoftUserRepository.class); + repository = factory.getRepository(SpecialUserRepository.class); + } + + @Test + public void basicSaveAndDelete() { + + User user = new User("Foo", "Bar", "foo@bar.de"); + user = softRepository.save(user); + + assertThat(repository.findAll(), hasItem(user)); + assertThat(softRepository.findAll(), hasItem(user)); + + softRepository.delete(user); + + assertThat(softRepository.findAll(), is(emptyIterable())); + assertThat(softRepository.count(), is(0L)); + assertThat(softRepository.findOne(user.getId()), is(nullValue())); + + assertThat(repository.count(), is(1L)); + assertThat(repository.findAll(), hasItem(user)); + assertThat(repository.findOne(user.getId()), is(notNullValue())); + + Predicate predicate = QUser.user.firstname.eq("Foo"); + assertThat(softRepository.findAll(predicate), is(emptyIterable())); + assertThat(softRepository.count(predicate), is(0L)); + assertThat(softRepository.findOne(predicate), is(nullValue())); + + assertThat(repository.count(predicate), is(1L)); + assertThat(repository.findAll(predicate), hasItem(user)); + assertThat(repository.findOne(predicate), is(notNullValue())); + } + + @Test + public void basicSaveAndDeleteWithQueryDslPredicate() { + + User user = new User("Tony", "Stark", "tony@stark.com"); + user = softRepository.save(user); + + assertThat(repository.findAll(), hasItem(user)); + assertThat(softRepository.findAll(), hasItem(user)); + + softRepository.delete(user); + + Predicate predicate = QUser.user.firstname.eq("Tony"); + assertThat(softRepository.findAll(predicate), is(emptyIterable())); + assertThat(softRepository.count(predicate), is(0L)); + assertThat(softRepository.findOne(predicate), is(nullValue())); + + assertThat(repository.count(predicate), is(1L)); + assertThat(repository.findAll(predicate), hasItem(user)); + assertThat(repository.findOne(predicate), is(notNullValue())); + } + + @SoftDelete(value = "active", flagMode = FlagMode.ACTIVE) + interface SoftUserRepository extends CrudRepository, QueryDslPredicateExecutor { + + List findByLastname(); + } + + interface SpecialUserRepository extends CrudRepository, QueryDslPredicateExecutor { + + List findAll(); + } +} diff --git a/src/test/resources/openjpa.xml b/src/test/resources/openjpa.xml index b84c743ada..b48f11b3e4 100644 --- a/src/test/resources/openjpa.xml +++ b/src/test/resources/openjpa.xml @@ -2,16 +2,20 @@ - + none + +