DATACMNS-251 - Introduced generic abstraction to determine "is new" state of entities.

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.
This commit is contained in:
Oliver Gierke
2012-11-23 16:46:24 +01:00
parent 8a01a55046
commit cc1561d92c
22 changed files with 1068 additions and 64 deletions

View File

@@ -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 {
}

View File

@@ -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<T> extends AuditingHandler<T> {
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);
}
}
}

View File

@@ -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<T, P extends PersistentProperty<P>> {
@@ -51,7 +52,7 @@ public interface PersistentEntity<T, P extends PersistentProperty<P>> {
* @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<T, P extends PersistentProperty<P>> {
* @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<T, P extends PersistentProperty<P>> {
*/
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<T, P extends PersistentProperty<P>> {
*/
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.
*

View File

@@ -45,6 +45,11 @@ public interface PersistentProperty<P extends PersistentProperty<P>> {
*/
Class<?> getType();
/**
* Returns the {@link TypeInformation} of the property.
*
* @return
*/
TypeInformation<?> getTypeInformation();
/**
@@ -96,6 +101,17 @@ public interface PersistentProperty<P extends PersistentProperty<P>> {
*/
boolean isIdProperty();
/**
* Returns whether the current property is a <em>potential</em> 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.
*

View File

@@ -110,7 +110,7 @@ public class PreferredConstructor<T, P extends PersistentProperty<P>> {
* @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<T, P extends PersistentProperty<P>> {
* @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);

View File

@@ -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<P extends PersistentProp
*
* @see org.springframework.data.mapping.model.AbstractPersistentProperty#getSpelExpression()
*/
@Override
public String getSpelExpression() {
return value == null ? null : value.value();
}
@@ -69,6 +72,7 @@ public abstract class AnnotationBasedPersistentProperty<P extends PersistentProp
*
* @see org.springframework.data.mapping.BasicPersistentProperty#isTransient()
*/
@Override
public boolean isTransient() {
boolean isTransient = super.isTransient() || field.isAnnotationPresent(Transient.class);
@@ -76,11 +80,20 @@ public abstract class AnnotationBasedPersistentProperty<P extends PersistentProp
return isTransient || field.isAnnotationPresent(Value.class) || field.isAnnotationPresent(Autowired.class);
}
/**
* Regards the property as ID if there is an {@link Id} annotation found on it.
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentProperty#isIdProperty()
*/
public boolean isIdProperty() {
return field.isAnnotationPresent(Id.class);
return AnnotationUtils.getAnnotation(field, Id.class) != null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentProperty#isVersionProperty()
*/
public boolean isVersionProperty() {
return AnnotationUtils.getAnnotation(field, Version.class) != null;
}
/**

View File

@@ -37,6 +37,7 @@ import org.springframework.util.StringUtils;
*
* @author Oliver Gierke
* @author Jon Brisbin
* @author Patryk Wasik
*/
public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implements MutablePersistentEntity<T, P> {
@@ -46,6 +47,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
private final Set<Association<P>> associations;
private P idProperty;
private P versionProperty;
/**
* Creates a new {@link BasicPersistentEntity} from the given {@link TypeInformation}.
@@ -87,7 +89,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> 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<T, P extends PersistentProperty<P>> 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<T, P extends PersistentProperty<P>> 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<T, P extends PersistentProperty<P>> 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)

View File

@@ -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<? extends PersistentEntity<?, ?>, ?> context;
/**
* Creates a new {@link MappingContextIsNewStrategyFactory} using the given {@link MappingContext}.
*
* @param context must not be {@literal null}.
*/
public MappingContextIsNewStrategyFactory(MappingContext<? extends PersistentEntity<?, ?>, ?> 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<PersistentEntity<Object, ?>, 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;
}
}
}

View File

@@ -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<Class<?>, IsNewStrategy> CACHE = new ConcurrentHashMap<Class<?>, 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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -39,13 +39,17 @@ public class AuditingHandlerUnitTests {
@Before
public void setUp() {
handler = new AuditingHandler<AuditedUser>();
handler = getHandler();
user = new AuditedUser();
auditorAware = mock(AuditorAware.class);
when(auditorAware.getCurrentAuditor()).thenReturn(user);
}
protected AuditingHandler<AuditedUser> getHandler() {
return new AuditingHandler<AuditedUser>();
}
/**
* Checks that the advice does not set auditor on the target entity if no {@code AuditorAware} was configured.
*/

View File

@@ -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<AuditedUser> getHandler() {
return new IsNewAwareAuditingHandler<AuditedUser>(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()));
}
}

View File

@@ -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<DummyPersistenProperty> path = context.getPersistentPropertyPath(PropertyPath.from("name",
PersistentPropertyPath<SamplePersistentProperty> 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 <S> BasicPersistentEntity<Object, SamplePersistentProperty> createPersistentEntity(
TypeInformation<S> typeInformation) {
return new BasicPersistentEntity<Object, SamplePersistentProperty>((TypeInformation<Object>) 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<Object, DummyPersistenProperty> entity = mappingContext.getPersistentEntity(Sample.class);
PersistentEntity<Object, SamplePersistentProperty> entity = mappingContext.getPersistentEntity(Sample.class);
assertThat(entity.getPersistentProperty("metaClass"), is(nullValue()));
}
@@ -143,47 +155,4 @@ public class AbstractMappingContextUnitTests {
MetaClass metaClass;
}
class DummyMappingContext extends
AbstractMappingContext<BasicPersistentEntity<Object, DummyPersistenProperty>, DummyPersistenProperty> {
@Override
@SuppressWarnings("unchecked")
protected <S> BasicPersistentEntity<Object, DummyPersistenProperty> createPersistentEntity(
TypeInformation<S> typeInformation) {
return new BasicPersistentEntity<Object, DummyPersistenProperty>((TypeInformation<Object>) 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<Object, DummyPersistenProperty> owner, final SimpleTypeHolder simpleTypeHolder) {
return new DummyPersistenProperty(field, descriptor, owner, simpleTypeHolder);
}
}
class DummyPersistenProperty extends AbstractPersistentProperty<DummyPersistenProperty> {
public DummyPersistenProperty(Field field, PropertyDescriptor propertyDescriptor,
BasicPersistentEntity<?, DummyPersistenProperty> owner, SimpleTypeHolder simpleTypeHolder) {
super(field, propertyDescriptor, owner, simpleTypeHolder);
}
public boolean isIdProperty() {
return false;
}
@Override
protected Association<DummyPersistenProperty> createAssociation() {
return new Association<DummyPersistenProperty>(this, null);
}
}
}

View File

@@ -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<BasicPersistentEntity<Object, SamplePersistentProperty>, SamplePersistentProperty> {
@Override
@SuppressWarnings("unchecked")
protected <S> BasicPersistentEntity<Object, SamplePersistentProperty> createPersistentEntity(
TypeInformation<S> typeInformation) {
return new BasicPersistentEntity<Object, SamplePersistentProperty>((TypeInformation<Object>) typeInformation);
}
@Override
protected SamplePersistentProperty createPersistentProperty(final Field field, final PropertyDescriptor descriptor,
final BasicPersistentEntity<Object, SamplePersistentProperty> owner, final SimpleTypeHolder simpleTypeHolder) {
return new SamplePersistentProperty(field, descriptor, owner, simpleTypeHolder);
}
}

View File

@@ -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<SamplePersistentProperty> {
public SamplePersistentProperty(Field field, PropertyDescriptor propertyDescriptor,
BasicPersistentEntity<?, SamplePersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
super(field, propertyDescriptor, owner, simpleTypeHolder);
}
@Override
protected Association<SamplePersistentProperty> createAssociation() {
return new Association<SamplePersistentProperty>(this, null);
}
}

View File

@@ -290,6 +290,10 @@ public class AbstractPersistentPropertyUnitTests {
return false;
}
public boolean isVersionProperty() {
return false;
}
@Override
protected Association<SamplePersistentProperty> createAssociation() {
return null;

View File

@@ -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<Long> {
@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;
}
}

View File

@@ -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);
}
}

View File

@@ -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<Long> {
boolean isNew = true;
public Long getId() {
return null;
}
public boolean isNew() {
return isNew;
}
}
}