From cc1561d92cec6271f6edb8be679429099166e94e Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 23 Nov 2012 16:46:24 +0100 Subject: [PATCH] DATACMNS-251 - Introduced generic abstraction to determine "is new" state of entities. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduced IsNewStrategy and IsNewStrategyFactory interfaces to allow abstracting the way the is-new state of an entity can be determined. The factory allows creating a strategy instance by inspected type, whereas the actual strategy then implements the is-new decision based on a given entity. We provide a MappingContext based factory implementation that looks up a PersistentEntity for the type requested and inspects it for an @Version or @Id property. It will then return strategy implementations that inspect the detected properties and check their values against null or 0 respectively. We also add a CachingIsNewStrategyFactory wrapper to prevent the lookup of the actual strategy from being done over and over again. The @Version annotation was pulled up from the Spring Data MongoDB module. It can be used as meta annotation to allow a deprecation step on the MongoDB's @Version one. Added an IsNewAwareAuditingHandler that exposes a unifying markAudited(…) delegating to the markCreated(…) and markModified(…) depending on the outcome of the IsNewStrategy. --- .../data/annotation/Version.java | 37 ++++ .../auditing/IsNewAwareAuditingHandler.java | 66 +++++++ .../data/mapping/PersistentEntity.java | 37 +++- .../data/mapping/PersistentProperty.java | 16 ++ .../data/mapping/PreferredConstructor.java | 4 +- .../AnnotationBasedPersistentProperty.java | 19 +- .../mapping/model/BasicPersistentEntity.java | 49 ++++- .../MappingContextIsNewStrategyFactory.java | 169 ++++++++++++++++++ .../support/CachingIsNewStrategyFactory.java | 62 +++++++ .../data/support/IsNewStrategy.java | 33 ++++ .../data/support/IsNewStrategyFactory.java | 34 ++++ .../support/IsNewStrategyFactorySupport.java | 59 ++++++ .../support/PersistableIsNewStrategy.java | 42 +++++ .../auditing/AuditingHandlerUnitTests.java | 6 +- .../IsNewAwareAuditingHandlerUnitTests.java | 76 ++++++++ .../AbstractMappingContextUnitTests.java | 77 +++----- .../mapping/context/SampleMappingContext.java | 26 +++ .../context/SamplePersistentProperty.java | 37 ++++ .../AbstractPersistentPropertyUnitTests.java | 4 + ...gContextIsNewStrategyFactoryUnitTests.java | 137 ++++++++++++++ .../CachingIsNewStrategyFactoryUnitTests.java | 81 +++++++++ .../PersistableIsNewStrategyUnitTests.java | 61 +++++++ 22 files changed, 1068 insertions(+), 64 deletions(-) create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/annotation/Version.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/auditing/IsNewAwareAuditingHandler.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingContextIsNewStrategyFactory.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/support/CachingIsNewStrategyFactory.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/support/IsNewStrategy.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/support/IsNewStrategyFactory.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/support/IsNewStrategyFactorySupport.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/support/PersistableIsNewStrategy.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/auditing/IsNewAwareAuditingHandlerUnitTests.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/SampleMappingContext.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/SamplePersistentProperty.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/mapping/model/MappingContextIsNewStrategyFactoryUnitTests.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/support/CachingIsNewStrategyFactoryUnitTests.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/support/PersistableIsNewStrategyUnitTests.java diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/annotation/Version.java b/spring-data-commons-core/src/main/java/org/springframework/data/annotation/Version.java new file mode 100644 index 000000000..3a3c0cba3 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/annotation/Version.java @@ -0,0 +1,37 @@ +/* + * 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.annotation; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Demarcates a property to be used as version field to implement optimistic locking on entities. + * + * @since 1.5 + * @author Patryk Wasik + * @author Oliver Gierke + */ +@Documented +@Target({ FIELD, ANNOTATION_TYPE }) +@Retention(RUNTIME) +public @interface Version { + +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/auditing/IsNewAwareAuditingHandler.java b/spring-data-commons-core/src/main/java/org/springframework/data/auditing/IsNewAwareAuditingHandler.java new file mode 100644 index 000000000..357256111 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/auditing/IsNewAwareAuditingHandler.java @@ -0,0 +1,66 @@ +/* + * 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.auditing; + +import org.springframework.data.support.IsNewStrategy; +import org.springframework.data.support.IsNewStrategyFactory; +import org.springframework.util.Assert; + +/** + * {@link AuditingHandler} extension that uses an {@link IsNewStrategyFactory} to expose a generic + * {@link #markAudited(Object)} method that will route calls to {@link #markCreated(Object)} or + * {@link #markModified(Object)} based on the {@link IsNewStrategy} determined from the factory. + * + * @author Oliver Gierke + * @since 1.5 + */ +public class IsNewAwareAuditingHandler extends AuditingHandler { + + private final IsNewStrategyFactory isNewStrategyFactory; + + /** + * Creates a new {@link IsNewAwareAuditingHandler} using the given {@link IsNewStrategyFactory}. + * + * @param isNewStrategyFactory must not be {@literal null}. + */ + public IsNewAwareAuditingHandler(IsNewStrategyFactory isNewStrategyFactory) { + + Assert.notNull(isNewStrategyFactory, "IsNewStrategy must not be null!"); + this.isNewStrategyFactory = isNewStrategyFactory; + } + + /** + * Marks the given object created or modified based on the {@link IsNewStrategy} returned by the + * {@link IsNewStrategyFactory} configured. Will rout the calls to {@link #markCreated(Object)} and + * {@link #markModified(Object)} accordingly. + * + * @param object + */ + public void markAudited(Object object) { + + if (object == null) { + return; + } + + IsNewStrategy strategy = isNewStrategyFactory.getIsNewStrategy(object.getClass()); + + if (strategy.isNew(object)) { + markCreated(object); + } else { + markModified(object); + } + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PersistentEntity.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PersistentEntity.java index 10aab9b88..c0b773af1 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PersistentEntity.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PersistentEntity.java @@ -24,6 +24,7 @@ import org.springframework.data.util.TypeInformation; * @author Oliver Gierke * @author Graeme Rocher * @author Jon Brisbin + * @author Patryk Wasik */ public interface PersistentEntity> { @@ -51,7 +52,7 @@ public interface PersistentEntity> { * @return true if the given {@link PersistentProperty} is referred to by a constructor argument or {@literal false} * if not or {@literal null}. */ - boolean isConstructorArgument(P property); + boolean isConstructorArgument(PersistentProperty property); /** * Returns whether the given {@link PersistentProperty} is the id property of the entity. @@ -59,7 +60,15 @@ public interface PersistentEntity> { * @param property * @return */ - boolean isIdProperty(P property); + boolean isIdProperty(PersistentProperty property); + + /** + * Returns whether the given {@link PersistentProperty} is the version property of the entity. + * + * @param property + * @return + */ + boolean isVersionProperty(PersistentProperty property); /** * Returns the id property of the {@link PersistentEntity}. Can be {@literal null} in case this is an entity @@ -69,6 +78,14 @@ public interface PersistentEntity> { */ P getIdProperty(); + /** + * Returns the version property of the {@link PersistentEntity}. Can be {@literal null} in case no version property is + * available on the entity. + * + * @return the version property of the {@link PersistentEntity}. + */ + P getVersionProperty(); + /** * Obtains a {@link PersistentProperty} instance by name. * @@ -77,6 +94,22 @@ public interface PersistentEntity> { */ P getPersistentProperty(String name); + /** + * Returns whether the {@link PersistentEntity} has an id property. If this call returns {@literal true}, + * {@link #getIdProperty()} will return a non-{@literal null} value. + * + * @return + */ + boolean hasIdProperty(); + + /** + * Returns whether the {@link PersistentEntity} has a version property. If this call returns {@literal true}, + * {@link #getVersionProperty()} will return a non-{@literal null} value. + * + * @return + */ + boolean hasVersionProperty(); + /** * Returns the resolved Java type of this entity. * diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PersistentProperty.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PersistentProperty.java index c5b8f1045..378937fb4 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PersistentProperty.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PersistentProperty.java @@ -45,6 +45,11 @@ public interface PersistentProperty

> { */ Class getType(); + /** + * Returns the {@link TypeInformation} of the property. + * + * @return + */ TypeInformation getTypeInformation(); /** @@ -96,6 +101,17 @@ public interface PersistentProperty

> { */ boolean isIdProperty(); + /** + * Returns whether the current property is a potential version property of the owning + * {@link PersistentEntity}. This method is mainly used by {@link PersistentEntity} implementation to discover version + * property candidates on {@link PersistentEntity} creation you should rather call + * {@link PersistentEntity#isVersionProperty(PersistentProperty)} to determine whether the current property is the + * version property of that {@link PersistentEntity} under consideration. + * + * @return + */ + boolean isVersionProperty(); + /** * Returns whether the property is a {@link Collection}, {@link Iterable} or an array. * diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PreferredConstructor.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PreferredConstructor.java index fb58c1d49..080be75f1 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PreferredConstructor.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PreferredConstructor.java @@ -110,7 +110,7 @@ public class PreferredConstructor> { * @param property must not be {@literal null}. * @return */ - public boolean isConstructorParameter(P property) { + public boolean isConstructorParameter(PersistentProperty property) { Assert.notNull(property); @@ -238,7 +238,7 @@ public class PreferredConstructor> { * @param property * @return */ - boolean maps(P property) { + boolean maps(PersistentProperty property) { P referencedProperty = entity == null ? null : entity.getPersistentProperty(name); return property == null ? false : property.equals(referencedProperty); diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java index 30ae7385d..a675ac840 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java @@ -21,9 +21,11 @@ import java.lang.reflect.Field; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Reference; import org.springframework.data.annotation.Transient; +import org.springframework.data.annotation.Version; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; @@ -59,6 +61,7 @@ public abstract class AnnotationBasedPersistentProperty

> implements MutablePersistentEntity { @@ -46,6 +47,7 @@ public class BasicPersistentEntity> implement private final Set> associations; private P idProperty; + private P versionProperty; /** * Creates a new {@link BasicPersistentEntity} from the given {@link TypeInformation}. @@ -87,7 +89,7 @@ public class BasicPersistentEntity> implement * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#isConstructorArgument(org.springframework.data.mapping.PersistentProperty) */ - public boolean isConstructorArgument(P property) { + public boolean isConstructorArgument(PersistentProperty property) { return constructor == null ? false : constructor.isConstructorParameter(property); } @@ -95,10 +97,18 @@ public class BasicPersistentEntity> implement * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#isIdProperty(org.springframework.data.mapping.PersistentProperty) */ - public boolean isIdProperty(P property) { + public boolean isIdProperty(PersistentProperty property) { return this.idProperty == null ? false : this.idProperty.equals(property); } + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.PersistentEntity#isVersionProperty(org.springframework.data.mapping.PersistentProperty) + */ + public boolean isVersionProperty(PersistentProperty property) { + return this.versionProperty == null ? false : this.versionProperty.equals(property); + } + /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getName() @@ -115,6 +125,30 @@ public class BasicPersistentEntity> implement return idProperty; } + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.PersistentEntity#getVersionProperty() + */ + public P getVersionProperty() { + return versionProperty; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.PersistentEntity#hasIdProperty() + */ + public boolean hasIdProperty() { + return idProperty != null; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.PersistentEntity#hasVersionProperty() + */ + public boolean hasVersionProperty() { + return versionProperty != null; + } + /* * (non-Javadoc) * @see org.springframework.data.mapping.MutablePersistentEntity#addPersistentProperty(P) @@ -134,6 +168,17 @@ public class BasicPersistentEntity> implement this.idProperty = property; } + + if (property.isVersionProperty()) { + + if (this.versionProperty != null) { + throw new MappingException(String.format( + "Attempt to add version property %s but already have property %s registered " + + "as version. Check your mapping configuration!", property.getField(), versionProperty.getField())); + } + + this.versionProperty = property; + } } /* (non-Javadoc) diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingContextIsNewStrategyFactory.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingContextIsNewStrategyFactory.java new file mode 100644 index 000000000..ecece05f2 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/MappingContextIsNewStrategyFactory.java @@ -0,0 +1,169 @@ +/* + * 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.mapping.model; + +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.support.IsNewStrategy; +import org.springframework.data.support.IsNewStrategyFactory; +import org.springframework.data.support.IsNewStrategyFactorySupport; +import org.springframework.util.Assert; + +/** + * An {@link IsNewStrategyFactory} using a {@link MappingContext} to determine the {@link IsNewStrategy} to be returned + * for a particular type. It will look for a version and id property on the {@link PersistentEntity} and return a + * strategy instance that will refelctively inspect the property for {@literal null} values or {@literal null} or a + * value of 0 in case of a version property. + * + * @author Oliver Gierke + */ +public class MappingContextIsNewStrategyFactory extends IsNewStrategyFactorySupport { + + private final MappingContext, ?> context; + + /** + * Creates a new {@link MappingContextIsNewStrategyFactory} using the given {@link MappingContext}. + * + * @param context must not be {@literal null}. + */ + public MappingContextIsNewStrategyFactory(MappingContext, ?> context) { + + Assert.notNull(context, "MappingContext must not be null!"); + this.context = context; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.support.IsNewStrategyFactorySupport#getFallBackStrategy(java.lang.Class) + */ + @Override + protected IsNewStrategy doGetIsNewStrategy(Class type) { + + PersistentEntity entity = context.getPersistentEntity(type); + + if (entity == null) { + return null; + } + + if (entity.hasVersionProperty()) { + return new PropertyIsNullOrZeroNumberIsNewStrategy(entity.getVersionProperty()); + } else if (entity.hasIdProperty()) { + return new PropertyIsNullIsNewStrategy(entity.getIdProperty()); + } else { + throw new MappingException(String.format("Cannot determine IsNewStrategy for type %s!", type)); + } + } + + /** + * {@link IsNewStrategy} implementation that will inspect a given {@link PersistentProperty} and call + * {@link #decideIsNew(Object)} with the value retrieved by reflection. + * + * @author Oliver Gierke + */ + static abstract class PersistentPropertyInspectingIsNewStrategy implements IsNewStrategy { + + private final PersistentProperty property; + + /** + * Creates a new {@link PersistentPropertyInspectingIsNewStrategy} using the given {@link PersistentProperty}. + * + * @param property must not be {@literal null}. + */ + public PersistentPropertyInspectingIsNewStrategy(PersistentProperty property) { + Assert.notNull(property, "PersistentProperty must not be null!"); + this.property = property; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.IsNewStrategy#isNew(java.lang.Object) + */ + public boolean isNew(Object entity) { + + BeanWrapper, Object> wrapper = BeanWrapper.create(entity, null); + Object propertyValue = wrapper.getProperty(property); + + return decideIsNew(propertyValue); + } + + protected abstract boolean decideIsNew(Object property); + } + + /** + * {@link IsNewStrategy} that does a check against {@literal null} for the given value and considers the object new if + * the value given is {@literal null}. + * + * @author Oliver Gierke + */ + static class PropertyIsNullIsNewStrategy extends PersistentPropertyInspectingIsNewStrategy { + + /** + * Creates a new {@link PropertyIsNullIsNewStrategy} using the given {@link PersistentProperty}. + * + * @param property must not be {@literal null}. + */ + public PropertyIsNullIsNewStrategy(PersistentProperty property) { + super(property); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.MappingContextIsNewStrategyFactory.PersistentPropertyInspectingIsNewStrategy#decideIsNew(java.lang.Object) + */ + @Override + protected boolean decideIsNew(Object property) { + return property == null; + } + } + + /** + * {@link IsNewStrategy} that considers property values of {@literal null} or 0 (in case of a {@link Number}) + * implementation as indicators for the new state. + * + * @author Oliver Gierke + */ + static class PropertyIsNullOrZeroNumberIsNewStrategy extends PersistentPropertyInspectingIsNewStrategy { + + /** + * Creates a new {@link PropertyIsNullOrZeroNumberIsNewStrategy} instance using the given {@link PersistentProperty} + * . + * + * @param property must not be {@literal null}. + */ + public PropertyIsNullOrZeroNumberIsNewStrategy(PersistentProperty property) { + super(property); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.MappingContextIsNewStrategyFactory.PersistentPropertyInspectingIsNewStrategy#decideIsNew(java.lang.Object) + */ + @Override + protected boolean decideIsNew(Object property) { + + if (property == null) { + return true; + } + + if (!(property instanceof Number)) { + return false; + } + + return ((Number) property).longValue() == 0; + } + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/support/CachingIsNewStrategyFactory.java b/spring-data-commons-core/src/main/java/org/springframework/data/support/CachingIsNewStrategyFactory.java new file mode 100644 index 000000000..cc7db2c96 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/support/CachingIsNewStrategyFactory.java @@ -0,0 +1,62 @@ +/* + * 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.support; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.util.Assert; + +/** + * {@link IsNewStrategyFactory} that caches resolved {@link IsNewStrategy} instances per type to avoid re-resolving them + * on each and every request. + * + * @author Oliver Gierke + */ +public class CachingIsNewStrategyFactory implements IsNewStrategyFactory { + + private final Map, IsNewStrategy> CACHE = new ConcurrentHashMap, IsNewStrategy>(); + private final IsNewStrategyFactory delegate; + + /** + * Creates a new {@link CachingIsNewStrategyFactory} delegating to the given {@link IsNewStrategyFactory}. + * + * @param delegate must not be {@literal null}. + */ + public CachingIsNewStrategyFactory(IsNewStrategyFactory delegate) { + + Assert.notNull(delegate, "IsNewStrategyFactory delegate must not be null!"); + this.delegate = delegate; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.IsNewStrategyFactory#getIsNewStrategy(java.lang.Class) + */ + public IsNewStrategy getIsNewStrategy(Class type) { + + IsNewStrategy strategy = CACHE.get(type); + + if (strategy != null) { + return strategy; + } + + IsNewStrategy isNewStrategy = delegate.getIsNewStrategy(type); + + CACHE.put(type, isNewStrategy); + return isNewStrategy; + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/support/IsNewStrategy.java b/spring-data-commons-core/src/main/java/org/springframework/data/support/IsNewStrategy.java new file mode 100644 index 000000000..25b6ed89b --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/support/IsNewStrategy.java @@ -0,0 +1,33 @@ +/* + * 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.support; + +/** + * Strategy interface to determine whether a given entity is to be considered new. + * + * @author Oliver Gierke + * @since 1.5 + */ +public interface IsNewStrategy { + + /** + * Returns whether the given entity is new, i.e. has never been persisted before or not. + * + * @param entity can be {@literal null}. + * @return + */ + boolean isNew(Object entity); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/support/IsNewStrategyFactory.java b/spring-data-commons-core/src/main/java/org/springframework/data/support/IsNewStrategyFactory.java new file mode 100644 index 000000000..d570d4b7f --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/support/IsNewStrategyFactory.java @@ -0,0 +1,34 @@ +/* + * 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.support; + +/** + * Factory interface to create {@link IsNewStrategy} instances for a given class. + * + * @author Oliver Gierke + * @since 1.5 + */ +public interface IsNewStrategyFactory { + + /** + * Returns the {@link IsNewStrategy} to be used for the given type. + * + * @param type must not be {@literal null}. + * @return the {@link IsNewStrategy} to be used for the given type, will never be {@literal null}. + * @throws IllegalArgumentException in case no {@link IsNewStrategy} could be determined. + */ + IsNewStrategy getIsNewStrategy(Class type); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/support/IsNewStrategyFactorySupport.java b/spring-data-commons-core/src/main/java/org/springframework/data/support/IsNewStrategyFactorySupport.java new file mode 100644 index 000000000..101e3a9da --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/support/IsNewStrategyFactorySupport.java @@ -0,0 +1,59 @@ +/* + * 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.support; + +import org.springframework.data.domain.Persistable; +import org.springframework.util.Assert; + +/** + * {@link IsNewStrategyFactory} that handles {@link Persistable} implementations directly by retuning a + * {@link PersistableIsNewStrategy} and delegating to {@link #doGetIsNewStrategy(Class)} for all other types. + * + * @author Oliver Gierke + * @since 1.5 + */ +public abstract class IsNewStrategyFactorySupport implements IsNewStrategyFactory { + + /* + * (non-Javadoc) + * @see org.springframework.data.support.IsNewStrategyFactory#getIsNewStrategy(java.lang.Class) + */ + public final IsNewStrategy getIsNewStrategy(Class type) { + + Assert.notNull(type, "Type must not be null!"); + + if (Persistable.class.isAssignableFrom(type)) { + return PersistableIsNewStrategy.INSTANCE; + } + + IsNewStrategy strategy = doGetIsNewStrategy(type); + + if (strategy != null) { + return strategy; + } + + throw new IllegalArgumentException(String.format("Unsupported entity %s! Could not determine IsNewStrategy.", + type.getName())); + } + + /** + * Determine the actual {@link IsNewStrategy} to be used for the given type. + * + * @param type will never be {@literal null}. + * @return the {@link IsNewStrategy} to be used for the given type or {@literal null} in case none can be resolved. + */ + protected abstract IsNewStrategy doGetIsNewStrategy(Class type); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/support/PersistableIsNewStrategy.java b/spring-data-commons-core/src/main/java/org/springframework/data/support/PersistableIsNewStrategy.java new file mode 100644 index 000000000..51104d576 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/support/PersistableIsNewStrategy.java @@ -0,0 +1,42 @@ +/* + * 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.support; + +import org.springframework.data.domain.Persistable; + +/** + * {@link IsNewStrategy} that invokes {@link Persistable#isNew()} on the given object. + * + * @author Oliver Gierke + */ +public enum PersistableIsNewStrategy implements IsNewStrategy { + + INSTANCE; + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.IsNewStrategy#isNew(java.lang.Object) + */ + public boolean isNew(Object entity) { + + if (!(entity instanceof Persistable)) { + throw new IllegalArgumentException(String.format("Given object of type %s does not implement %s!", + entity.getClass(), Persistable.class)); + } + + return ((Persistable) entity).isNew(); + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java index 6017300b7..7a7b15db8 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java @@ -39,13 +39,17 @@ public class AuditingHandlerUnitTests { @Before public void setUp() { - handler = new AuditingHandler(); + handler = getHandler(); user = new AuditedUser(); auditorAware = mock(AuditorAware.class); when(auditorAware.getCurrentAuditor()).thenReturn(user); } + protected AuditingHandler getHandler() { + return new AuditingHandler(); + } + /** * Checks that the advice does not set auditor on the target entity if no {@code AuditorAware} was configured. */ diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/auditing/IsNewAwareAuditingHandlerUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/auditing/IsNewAwareAuditingHandlerUnitTests.java new file mode 100644 index 000000000..fce1b3d75 --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/auditing/IsNewAwareAuditingHandlerUnitTests.java @@ -0,0 +1,76 @@ +/* + * 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. + * 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.auditing; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.support.IsNewStrategy; +import org.springframework.data.support.IsNewStrategyFactory; + +/** + * Unit test for {@code AuditingHandler}. + * + * @author Oliver Gierke + * @since 1.5 + */ +@RunWith(MockitoJUnitRunner.class) +public class IsNewAwareAuditingHandlerUnitTests extends AuditingHandlerUnitTests { + + @Mock + IsNewStrategyFactory factory; + @Mock + IsNewStrategy strategy; + + @Before + public void init() { + when(factory.getIsNewStrategy(Mockito.any(Class.class))).thenReturn(strategy); + } + + @Override + protected IsNewAwareAuditingHandler getHandler() { + return new IsNewAwareAuditingHandler(factory); + } + + @Test + public void delegatesToMarkCreatedForNewEntity() { + + when(strategy.isNew(Mockito.any(Object.class))).thenReturn(true); + AuditedUser user = new AuditedUser(); + getHandler().markAudited(user); + + assertThat(user.createdDate, is(notNullValue())); + assertThat(user.modifiedDate, is(notNullValue())); + } + + @Test + public void delegatesToMarkModifiedForNonNewEntity() { + + when(strategy.isNew(Mockito.any(Object.class))).thenReturn(false); + AuditedUser user = new AuditedUser(); + getHandler().markAudited(user); + + assertThat(user.createdDate, is(nullValue())); + assertThat(user.modifiedDate, is(notNullValue())); + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java index 167484cab..777d03412 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java @@ -20,8 +20,6 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.*; import groovy.lang.MetaClass; -import java.beans.PropertyDescriptor; -import java.lang.reflect.Field; import java.util.Collections; import org.junit.Before; @@ -30,10 +28,8 @@ import org.mockito.Mockito; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.data.mapping.Association; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PropertyPath; -import org.springframework.data.mapping.model.AbstractPersistentProperty; import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mapping.model.SimpleTypeHolder; @@ -47,17 +43,17 @@ import org.springframework.data.util.TypeInformation; public class AbstractMappingContextUnitTests { final SimpleTypeHolder holder = new SimpleTypeHolder(); - DummyMappingContext context; + SampleMappingContext context; @Before public void setUp() { - context = new DummyMappingContext(); + context = new SampleMappingContext(); context.setSimpleTypeHolder(holder); } @Test public void doesNotTryToLookupPersistentEntityForLeafProperty() { - PersistentPropertyPath path = context.getPersistentPropertyPath(PropertyPath.from("name", + PersistentPropertyPath path = context.getPersistentPropertyPath(PropertyPath.from("name", Person.class)); assertThat(path, is(notNullValue())); } @@ -68,6 +64,22 @@ public class AbstractMappingContextUnitTests { @Test(expected = MappingException.class) public void doesNotAddInvalidEntity() { + context = new SampleMappingContext() { + @Override + @SuppressWarnings("unchecked") + protected BasicPersistentEntity createPersistentEntity( + TypeInformation typeInformation) { + return new BasicPersistentEntity((TypeInformation) typeInformation) { + @Override + public void verify() { + if (Unsupported.class.isAssignableFrom(getType())) { + throw new MappingException("Unsupported type!"); + } + } + }; + } + }; + try { context.getPersistentEntity(Unsupported.class); } catch (MappingException e) { @@ -82,7 +94,7 @@ public class AbstractMappingContextUnitTests { ApplicationContext context = mock(ApplicationContext.class); - DummyMappingContext mappingContext = new DummyMappingContext(); + SampleMappingContext mappingContext = new SampleMappingContext(); mappingContext.setInitialEntitySet(Collections.singleton(Person.class)); mappingContext.setApplicationContext(context); @@ -98,7 +110,7 @@ public class AbstractMappingContextUnitTests { @Test public void returnsNullPersistentEntityForSimpleTypes() { - DummyMappingContext context = new DummyMappingContext(); + SampleMappingContext context = new SampleMappingContext(); assertThat(context.getPersistentEntity(String.class), is(nullValue())); } @@ -124,10 +136,10 @@ public class AbstractMappingContextUnitTests { @Test public void doesNotCreatePersistentPropertyForGroovyMetaClass() { - DummyMappingContext mappingContext = new DummyMappingContext(); + SampleMappingContext mappingContext = new SampleMappingContext(); mappingContext.initialize(); - PersistentEntity entity = mappingContext.getPersistentEntity(Sample.class); + PersistentEntity entity = mappingContext.getPersistentEntity(Sample.class); assertThat(entity.getPersistentProperty("metaClass"), is(nullValue())); } @@ -143,47 +155,4 @@ public class AbstractMappingContextUnitTests { MetaClass metaClass; } - - class DummyMappingContext extends - AbstractMappingContext, DummyPersistenProperty> { - - @Override - @SuppressWarnings("unchecked") - protected BasicPersistentEntity createPersistentEntity( - TypeInformation typeInformation) { - return new BasicPersistentEntity((TypeInformation) typeInformation) { - - @Override - public void verify() { - if (holder.isSimpleType(getType()) || Unsupported.class.equals(getType())) { - throw new MappingException("Invalid!"); - } - } - }; - } - - @Override - protected DummyPersistenProperty createPersistentProperty(final Field field, final PropertyDescriptor descriptor, - final BasicPersistentEntity owner, final SimpleTypeHolder simpleTypeHolder) { - - return new DummyPersistenProperty(field, descriptor, owner, simpleTypeHolder); - } - } - - class DummyPersistenProperty extends AbstractPersistentProperty { - - public DummyPersistenProperty(Field field, PropertyDescriptor propertyDescriptor, - BasicPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { - super(field, propertyDescriptor, owner, simpleTypeHolder); - } - - public boolean isIdProperty() { - return false; - } - - @Override - protected Association createAssociation() { - return new Association(this, null); - } - } } diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/SampleMappingContext.java b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/SampleMappingContext.java new file mode 100644 index 000000000..7c603ea74 --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/SampleMappingContext.java @@ -0,0 +1,26 @@ +package org.springframework.data.mapping.context; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; + +import org.springframework.data.mapping.model.BasicPersistentEntity; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.util.TypeInformation; + +class SampleMappingContext extends + AbstractMappingContext, SamplePersistentProperty> { + + @Override + @SuppressWarnings("unchecked") + protected BasicPersistentEntity createPersistentEntity( + TypeInformation typeInformation) { + return new BasicPersistentEntity((TypeInformation) typeInformation); + } + + @Override + protected SamplePersistentProperty createPersistentProperty(final Field field, final PropertyDescriptor descriptor, + final BasicPersistentEntity owner, final SimpleTypeHolder simpleTypeHolder) { + + return new SamplePersistentProperty(field, descriptor, owner, simpleTypeHolder); + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/SamplePersistentProperty.java b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/SamplePersistentProperty.java new file mode 100644 index 000000000..1cba0f8ac --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/context/SamplePersistentProperty.java @@ -0,0 +1,37 @@ +/* + * 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.mapping.context; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; + +import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; +import org.springframework.data.mapping.model.BasicPersistentEntity; +import org.springframework.data.mapping.model.SimpleTypeHolder; + +class SamplePersistentProperty extends AnnotationBasedPersistentProperty { + + public SamplePersistentProperty(Field field, PropertyDescriptor propertyDescriptor, + BasicPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + super(field, propertyDescriptor, owner, simpleTypeHolder); + } + + @Override + protected Association createAssociation() { + return new Association(this, null); + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/model/AbstractPersistentPropertyUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/model/AbstractPersistentPropertyUnitTests.java index 317f789ae..e8d6f1628 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/model/AbstractPersistentPropertyUnitTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/model/AbstractPersistentPropertyUnitTests.java @@ -290,6 +290,10 @@ public class AbstractPersistentPropertyUnitTests { return false; } + public boolean isVersionProperty() { + return false; + } + @Override protected Association createAssociation() { return null; diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/model/MappingContextIsNewStrategyFactoryUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/model/MappingContextIsNewStrategyFactoryUnitTests.java new file mode 100644 index 000000000..e0f8d963b --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/model/MappingContextIsNewStrategyFactoryUnitTests.java @@ -0,0 +1,137 @@ +/* + * 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.mapping.model; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Version; +import org.springframework.data.domain.Persistable; +import org.springframework.data.mapping.MappingMetadataTests.SampleMappingContext; +import org.springframework.data.mapping.model.MappingContextIsNewStrategyFactory.PropertyIsNullIsNewStrategy; +import org.springframework.data.mapping.model.MappingContextIsNewStrategyFactory.PropertyIsNullOrZeroNumberIsNewStrategy; +import org.springframework.data.support.IsNewStrategy; +import org.springframework.data.support.IsNewStrategyFactory; + +/** + * @author Oliver Gierke + */ +public class MappingContextIsNewStrategyFactoryUnitTests { + + IsNewStrategyFactory factory; + + @Before + public void setUp() { + + SampleMappingContext context = new SampleMappingContext(); + factory = new MappingContextIsNewStrategyFactory(context); + } + + @Test + public void returnsPropertyIsNullOrZeroIsNewStrategyForVersionedEntity() { + + IsNewStrategy strategy = factory.getIsNewStrategy(VersionedEntity.class); + assertThat(strategy, is(instanceOf(PropertyIsNullOrZeroNumberIsNewStrategy.class))); + + VersionedEntity entity = new VersionedEntity(); + assertThat(strategy.isNew(entity), is(true)); + + entity.id = 1L; + assertThat(strategy.isNew(entity), is(true)); + + entity.version = 0L; + assertThat(strategy.isNew(entity), is(true)); + + entity.version = 1L; + assertThat(strategy.isNew(entity), is(false)); + } + + @Test + public void returnsPropertyIsNullOrZeroIsNewStrategyForPrimitiveVersionedEntity() { + + IsNewStrategy strategy = factory.getIsNewStrategy(VersionedEntity.class); + assertThat(strategy, is(instanceOf(PropertyIsNullOrZeroNumberIsNewStrategy.class))); + + VersionedEntity entity = new VersionedEntity(); + assertThat(strategy.isNew(entity), is(true)); + + entity.id = 1L; + assertThat(strategy.isNew(entity), is(true)); + + entity.version = 1L; + assertThat(strategy.isNew(entity), is(false)); + } + + @Test + public void returnsPropertyIsNullIsNewStrategyForEntity() { + + IsNewStrategy strategy = factory.getIsNewStrategy(Entity.class); + assertThat(strategy, is(instanceOf(PropertyIsNullIsNewStrategy.class))); + + Entity entity = new Entity(); + assertThat(strategy.isNew(entity), is(true)); + + entity.id = 1L; + assertThat(strategy.isNew(entity), is(false)); + } + + @SuppressWarnings("serial") + static class PersistableEntity implements Persistable { + + @Version + Long version; + + @Id + Long id; + + boolean isNew = true; + + public Long getId() { + return id; + } + + public boolean isNew() { + return isNew; + } + } + + static class VersionedEntity { + + @Version + Long version; + + @Id + Long id; + } + + static class PrimitveVersionedEntity { + + @Version + long version = 0; + + @Id + Long id; + } + + static class Entity { + + @Id + Long id; + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/support/CachingIsNewStrategyFactoryUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/support/CachingIsNewStrategyFactoryUnitTests.java new file mode 100644 index 000000000..1f454a47b --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/support/CachingIsNewStrategyFactoryUnitTests.java @@ -0,0 +1,81 @@ +/* + * 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.support; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * Unit tests for {@link CachingIsNewStrategyFactory}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class CachingIsNewStrategyFactoryUnitTests { + + static final IsNewStrategy REFERENCE = PersistableIsNewStrategy.INSTANCE; + + @Mock + IsNewStrategyFactory delegate; + + CachingIsNewStrategyFactory factory; + + @Before + public void setUp() { + factory = new CachingIsNewStrategyFactory(delegate); + } + + @Test + public void invokesDelegateForFirstInvocation() { + + when(delegate.getIsNewStrategy(Object.class)).thenReturn(REFERENCE); + + IsNewStrategy strategy = factory.getIsNewStrategy(Object.class); + + assertThat(strategy, is(REFERENCE)); + verify(delegate, times(1)).getIsNewStrategy(Object.class); + } + + @Test + public void usesCachedValueForSecondInvocation() { + + when(delegate.getIsNewStrategy(Mockito.any(Class.class))).thenReturn(REFERENCE); + + IsNewStrategy strategy = factory.getIsNewStrategy(Object.class); + + assertThat(strategy, is(REFERENCE)); + verify(delegate, times(1)).getIsNewStrategy(Object.class); + verify(delegate, times(0)).getIsNewStrategy(String.class); + + strategy = factory.getIsNewStrategy(Object.class); + assertThat(strategy, is(REFERENCE)); + verify(delegate, times(1)).getIsNewStrategy(Object.class); + verify(delegate, times(0)).getIsNewStrategy(String.class); + + strategy = factory.getIsNewStrategy(String.class); + assertThat(strategy, is(REFERENCE)); + verify(delegate, times(1)).getIsNewStrategy(Object.class); + verify(delegate, times(1)).getIsNewStrategy(String.class); + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/support/PersistableIsNewStrategyUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/support/PersistableIsNewStrategyUnitTests.java new file mode 100644 index 000000000..5600384d6 --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/support/PersistableIsNewStrategyUnitTests.java @@ -0,0 +1,61 @@ +/* + * 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.support; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.data.domain.Persistable; + +/** + * Unit tests for {@link PersistableIsNewStrategy}. + * + * @author Oliver Gierke + */ +public class PersistableIsNewStrategyUnitTests { + + IsNewStrategy strategy = PersistableIsNewStrategy.INSTANCE; + + @Test + public void invokesPersistableIsNewForTest() { + + PersistableEntity entity = new PersistableEntity(); + assertThat(strategy.isNew(entity), is(true)); + + entity.isNew = false; + assertThat(strategy.isNew(entity), is(false)); + } + + @Test(expected = IllegalArgumentException.class) + public void rejectsNonPersistableEntity() { + strategy.isNew(new Object()); + } + + @SuppressWarnings("serial") + static class PersistableEntity implements Persistable { + + boolean isNew = true; + + public Long getId() { + return null; + } + + public boolean isNew() { + return isNew; + } + } +}