diff --git a/Spring Data Commons.sonargraph b/Spring Data Commons.sonargraph index f21de9ad7..ae6bbda82 100644 --- a/Spring Data Commons.sonargraph +++ b/Spring Data Commons.sonargraph @@ -99,6 +99,20 @@ + + + + + + + + + + + + + + @@ -135,12 +149,6 @@ - - - - - - @@ -151,18 +159,24 @@ - - - - - - + + + + + + + + + + + + diff --git a/src/main/java/org/springframework/data/auditing/AuditableBeanWrapperFactory.java b/src/main/java/org/springframework/data/auditing/AuditableBeanWrapperFactory.java index 84e17353d..54b1bb506 100644 --- a/src/main/java/org/springframework/data/auditing/AuditableBeanWrapperFactory.java +++ b/src/main/java/org/springframework/data/auditing/AuditableBeanWrapperFactory.java @@ -22,7 +22,6 @@ import org.joda.time.DateTime; import org.joda.time.LocalDateTime; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; -import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.data.domain.Auditable; import org.springframework.data.util.ReflectionUtils; import org.springframework.format.support.DefaultFormattingConversionService; @@ -37,9 +36,6 @@ import org.springframework.util.ClassUtils; */ class AuditableBeanWrapperFactory { - private static boolean IS_JODA_TIME_PRESENT = ClassUtils.isPresent("org.joda.time.DateTime", - ReflectionAuditingBeanWrapper.class.getClassLoader()); - /** * Returns an {@link AuditableBeanWrapper} if the given object is capable of being equipped with auditing information. * @@ -112,14 +108,69 @@ class AuditableBeanWrapperFactory { } } + /** + * Base class for {@link AuditableBeanWrapper} implementations that might need to convert {@link Calendar} values into + * compatible types when setting date/time information. + * + * @author Oliver Gierke + * @since 1.8 + */ + static abstract class DateConvertingAuditableBeanWrapper implements AuditableBeanWrapper { + + private static boolean IS_JODA_TIME_PRESENT = ClassUtils.isPresent("org.joda.time.DateTime", + ReflectionAuditingBeanWrapper.class.getClassLoader()); + + private final ConversionService conversionService; + + /** + * Creates a new {@link DateConvertingAuditableBeanWrapper}. + */ + public DateConvertingAuditableBeanWrapper() { + + DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); + + if (IS_JODA_TIME_PRESENT) { + conversionService.addConverter(CalendarToDateTimeConverter.INSTANCE); + conversionService.addConverter(CalendarToLocalDateTimeConverter.INSTANCE); + } + + this.conversionService = conversionService; + } + + /** + * Returns the {@link Calendar} in a type, compatible to the given field. + * + * @param value can be {@literal null}. + * @param targetType must not be {@literal null}. + * @param source must not be {@literal null}. + * @return + */ + protected Object getDateValueToSet(Calendar value, Class targetType, Object source) { + + if (value == null) { + return null; + } + + if (Calendar.class.equals(targetType)) { + return value; + } + + if (conversionService.canConvert(Calendar.class, targetType)) { + return conversionService.convert(value, targetType); + } + + throw new IllegalArgumentException(String.format("Invalid date type for member %s! Supported types are %s.", + source, AnnotationAuditingMetadata.SUPPORTED_DATE_TYPES)); + } + } + /** * An {@link AuditableBeanWrapper} implementation that sets values on the target object using refelction. * * @author Oliver Gierke */ - static class ReflectionAuditingBeanWrapper implements AuditableBeanWrapper { + static class ReflectionAuditingBeanWrapper extends DateConvertingAuditableBeanWrapper { - private final ConversionService conversionService; private final AnnotationAuditingMetadata metadata; private final Object target; @@ -134,15 +185,6 @@ class AuditableBeanWrapperFactory { this.metadata = AnnotationAuditingMetadata.getMetadata(target.getClass()); this.target = target; - - ConfigurableConversionService conversionService = new DefaultFormattingConversionService(); - - if (IS_JODA_TIME_PRESENT) { - conversionService.addConverter(CalendarToDateTimeConverter.INSTANCE); - conversionService.addConverter(CalendarToLocalDateTimeConverter.INSTANCE); - } - - this.conversionService = conversionService; } /* @@ -202,38 +244,11 @@ class AuditableBeanWrapperFactory { return; } - ReflectionUtils.setField(field, target, getDateValueToSet(value, field)); - } - - /** - * Returns the {@link DateTime} in a type compatible to the given field. - * - * @param value - * @param field must not be {@literal null}. - * @return - */ - private Object getDateValueToSet(Calendar value, Field field) { - - if (value == null) { - return null; - } - - Class targetType = field.getType(); - - if (Calendar.class.equals(targetType)) { - return value; - } - - if (conversionService.canConvert(Calendar.class, targetType)) { - return conversionService.convert(value, targetType); - } - - throw new IllegalArgumentException(String.format("Invalid date type for field %s! Supported types are %s.", - field, AnnotationAuditingMetadata.SUPPORTED_DATE_TYPES)); + ReflectionUtils.setField(field, target, getDateValueToSet(value, field.getType(), field)); } } - static enum CalendarToDateTimeConverter implements Converter { + private static enum CalendarToDateTimeConverter implements Converter { INSTANCE; @@ -243,7 +258,7 @@ class AuditableBeanWrapperFactory { } } - static enum CalendarToLocalDateTimeConverter implements Converter { + private static enum CalendarToLocalDateTimeConverter implements Converter { INSTANCE; diff --git a/src/main/java/org/springframework/data/auditing/AuditingHandler.java b/src/main/java/org/springframework/data/auditing/AuditingHandler.java index 10f29858c..7912359e7 100644 --- a/src/main/java/org/springframework/data/auditing/AuditingHandler.java +++ b/src/main/java/org/springframework/data/auditing/AuditingHandler.java @@ -23,6 +23,9 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.data.domain.Auditable; import org.springframework.data.domain.AuditorAware; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.context.MappingContext; import org.springframework.util.Assert; /** @@ -35,16 +38,41 @@ public class AuditingHandler implements InitializingBean { private static final Logger LOGGER = LoggerFactory.getLogger(AuditingHandler.class); - private final AuditableBeanWrapperFactory factory = new AuditableBeanWrapperFactory(); + private final AuditableBeanWrapperFactory factory; + private DateTimeProvider dateTimeProvider = CurrentDateTimeProvider.INSTANCE; private AuditorAware auditorAware; private boolean dateTimeForNow = true; private boolean modifyOnCreation = true; + /** + * Creates a new {@link AuditingHandler}. + * + * @deprecated use the constructor taking a {@link MappingContext}. + */ + @Deprecated + public AuditingHandler() { + this.factory = new AuditableBeanWrapperFactory(); + } + + /** + * Creates a new {@link AuditableBeanWrapper} using the given {@link MappingContext} when looking up auditing metadata + * via reflection. + * + * @param mappingContext must not be {@literal null}. + * @since 1.8 + */ + public AuditingHandler( + MappingContext, ? extends PersistentProperty> mappingContext) { + + Assert.notNull(mappingContext, "MappingContext must not be null!"); + this.factory = new MappingAuditableBeanWrapperFactory(mappingContext); + } + /** * Setter to inject a {@code AuditorAware} component to retrieve the current auditor. * - * @param auditorAware the auditorAware to set + * @param auditorAware must not be {@literal null}. */ public void setAuditorAware(final AuditorAware auditorAware) { diff --git a/src/main/java/org/springframework/data/auditing/IsNewAwareAuditingHandler.java b/src/main/java/org/springframework/data/auditing/IsNewAwareAuditingHandler.java index fc8837fbf..84c397962 100644 --- a/src/main/java/org/springframework/data/auditing/IsNewAwareAuditingHandler.java +++ b/src/main/java/org/springframework/data/auditing/IsNewAwareAuditingHandler.java @@ -15,6 +15,10 @@ */ package org.springframework.data.auditing; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.context.MappingContextIsNewStrategyFactory; import org.springframework.data.support.IsNewStrategy; import org.springframework.data.support.IsNewStrategyFactory; import org.springframework.util.Assert; @@ -31,14 +35,31 @@ public class IsNewAwareAuditingHandler extends AuditingHandler { private final IsNewStrategyFactory isNewStrategyFactory; + /** + * Creates a new {@link IsNewAwareAuditingHandler} for the given {@link MappingContext}. + * + * @param mappingContext must not be {@literal null}. + * @since 1.8 + */ + public IsNewAwareAuditingHandler( + MappingContext, ? extends PersistentProperty> mappingContext) { + + super(mappingContext); + + Assert.notNull(mappingContext, "MappingContext must not be null!"); + this.isNewStrategyFactory = new MappingContextIsNewStrategyFactory(mappingContext); + } + /** * Creates a new {@link IsNewAwareAuditingHandler} using the given {@link IsNewStrategyFactory}. * * @param isNewStrategyFactory must not be {@literal null}. + * @deprecated use constructor taking a {@link MappingContext} directly. Will be removed in 1.9. */ + @Deprecated public IsNewAwareAuditingHandler(IsNewStrategyFactory isNewStrategyFactory) { - Assert.notNull(isNewStrategyFactory, "IsNewStrategy must not be null!"); + Assert.notNull(isNewStrategyFactory, "IsNewStrategyFactory must not be null!"); this.isNewStrategyFactory = isNewStrategyFactory; } diff --git a/src/main/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactory.java b/src/main/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactory.java new file mode 100644 index 000000000..4ae837e54 --- /dev/null +++ b/src/main/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactory.java @@ -0,0 +1,206 @@ +/* + * Copyright 2014 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 java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.domain.Auditable; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.BeanWrapper; +import org.springframework.util.Assert; + +/** + * {@link AuditableBeanWrapperFactory} that will create am {@link AuditableBeanWrapper} using mapping information + * obtained from a {@link MappingContext} to detect auditing configuration and eventually invoking setting the auditing + * values. + * + * @author Oliver Gierke + * @since 1.8 + */ +class MappingAuditableBeanWrapperFactory extends AuditableBeanWrapperFactory { + + private final MappingContext, ? extends PersistentProperty> mappingContext; + private final Map, MappingAuditingMetadata> metadataCache; + + /** + * Creates a new {@link MappingAuditableBeanWrapperFactory} using the given {@link MappingContext}. + * + * @param mappingContext must not be {@literal null}. + */ + public MappingAuditableBeanWrapperFactory( + MappingContext, ? extends PersistentProperty> mappingContext) { + + Assert.notNull(mappingContext, "MappingContext must not be null!"); + + this.mappingContext = mappingContext; + this.metadataCache = new HashMap, MappingAuditingMetadata>(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.auditing.AuditableBeanWrapperFactory#getBeanWrapperFor(java.lang.Object) + */ + @Override + public AuditableBeanWrapper getBeanWrapperFor(Object source) { + + if (source instanceof Auditable) { + return super.getBeanWrapperFor(source); + } + + Class type = source.getClass(); + PersistentEntity entity = mappingContext.getPersistentEntity(type); + + if (entity == null) { + return super.getBeanWrapperFor(source); + } + + MappingAuditingMetadata metadata = metadataCache.get(type); + + if (metadata == null) { + metadata = new MappingAuditingMetadata(entity); + metadataCache.put(type, metadata); + } + + return metadata.isAuditable() ? new MappingMetadataAuditableBeanWrapper(source, metadata) : null; + } + + /** + * Captures {@link PersistentProperty} instances equipped with auditing annotations. + * + * @author Oliver Gierke + * @since 1.8 + */ + static class MappingAuditingMetadata { + + private final PersistentProperty createdByProperty, createdDateProperty, lastModifiedByProperty, + lastModifiedDateProperty; + + /** + * Creates a new {@link MappingAuditingMetadata} instance from the given {@link PersistentEntity}. + * + * @param entity must not be {@literal null}. + */ + public MappingAuditingMetadata(PersistentEntity> entity) { + + Assert.notNull(entity, "PersistentEntity must not be null!"); + + this.createdByProperty = entity.getPersistentProperty(CreatedBy.class); + this.createdDateProperty = entity.getPersistentProperty(CreatedDate.class); + this.lastModifiedByProperty = entity.getPersistentProperty(LastModifiedBy.class); + this.lastModifiedDateProperty = entity.getPersistentProperty(LastModifiedDate.class); + } + + /** + * Returns whether the {@link PersistentEntity} is auditable at all (read: any of the auditing annotations is + * present). + * + * @return + */ + public boolean isAuditable() { + return createdByProperty != null || createdDateProperty != null || lastModifiedByProperty != null + || lastModifiedDateProperty != null; + } + } + + /** + * {@link AuditableBeanWrapper} using {@link MappingAuditingMetadata} and a {@link BeanWrapper} to set values on + * auditing properties. + * + * @author Oliver Gierke + * @since 1.8 + */ + static class MappingMetadataAuditableBeanWrapper extends DateConvertingAuditableBeanWrapper { + + private final BeanWrapper wrapper; + private final MappingAuditingMetadata metadata; + + /** + * Creates a new {@link MappingMetadataAuditableBeanWrapper} for the given taregt and + * {@link MappingAuditingMetadata}. + * + * @param target must not be {@literal null}. + * @param metadata must not be {@literal null}. + */ + public MappingMetadataAuditableBeanWrapper(Object target, MappingAuditingMetadata metadata) { + + Assert.notNull(target, "Target object must not be null!"); + Assert.notNull(metadata, "Auditing metadata must not be null!"); + + this.wrapper = BeanWrapper.create(target, null); + this.metadata = metadata; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedBy(java.lang.Object) + */ + @Override + public void setCreatedBy(Object value) { + + if (metadata.createdByProperty != null) { + this.wrapper.setProperty(metadata.createdByProperty, value); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedDate(java.util.Calendar) + */ + @Override + public void setCreatedDate(Calendar value) { + + PersistentProperty property = metadata.createdDateProperty; + + if (property != null) { + this.wrapper.setProperty(property, getDateValueToSet(value, property.getType(), property)); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedBy(java.lang.Object) + */ + @Override + public void setLastModifiedBy(Object value) { + + if (metadata.lastModifiedByProperty != null) { + this.wrapper.setProperty(metadata.lastModifiedByProperty, value); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedDate(java.util.Calendar) + */ + @Override + public void setLastModifiedDate(Calendar value) { + + PersistentProperty property = metadata.lastModifiedDateProperty; + + if (property != null) { + this.wrapper.setProperty(property, getDateValueToSet(value, property.getType(), property)); + } + } + } +} diff --git a/src/main/java/org/springframework/data/config/AuditingHandlerBeanDefinitionParser.java b/src/main/java/org/springframework/data/auditing/config/AuditingHandlerBeanDefinitionParser.java similarity index 64% rename from src/main/java/org/springframework/data/config/AuditingHandlerBeanDefinitionParser.java rename to src/main/java/org/springframework/data/auditing/config/AuditingHandlerBeanDefinitionParser.java index 99f879925..1348d148d 100644 --- a/src/main/java/org/springframework/data/config/AuditingHandlerBeanDefinitionParser.java +++ b/src/main/java/org/springframework/data/auditing/config/AuditingHandlerBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 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. @@ -13,17 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.config; +package org.springframework.data.auditing.config; import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; import org.springframework.aop.framework.ProxyFactoryBean; import org.springframework.aop.target.LazyInitTargetSource; +import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; import org.springframework.data.auditing.AuditingHandler; +import org.springframework.data.config.ParsingUtils; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.w3c.dom.Element; @@ -37,6 +43,30 @@ public class AuditingHandlerBeanDefinitionParser extends AbstractSingleBeanDefin private static final String AUDITOR_AWARE_REF = "auditor-aware-ref"; + private final String mappingContextBeanName; + private String resolvedBeanName; + + /** + * Creates a new {@link AuditingHandlerBeanDefinitionParser} to point to a {@link MappingContext} with the given bean + * name. + * + * @param mappingContextBeanName must not be {@literal null} or empty. + */ + public AuditingHandlerBeanDefinitionParser(String mappingContextBeanName) { + + Assert.hasText(mappingContextBeanName, "MappingContext bean name must not be null!"); + this.mappingContextBeanName = mappingContextBeanName; + } + + /** + * Returns the name of the bean definition the {@link AuditingHandler} was registered under. + * + * @return the resolvedBeanName + */ + public String getResolvedBeanName() { + return resolvedBeanName; + } + /* * (non-Javadoc) * @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#getBeanClass(org.w3c.dom.Element) @@ -62,6 +92,8 @@ public class AuditingHandlerBeanDefinitionParser extends AbstractSingleBeanDefin @Override protected void doParse(Element element, BeanDefinitionBuilder builder) { + builder.addConstructorArgReference(mappingContextBeanName); + String auditorAwareRef = element.getAttribute(AUDITOR_AWARE_REF); if (StringUtils.hasText(auditorAwareRef)) { @@ -73,6 +105,18 @@ public class AuditingHandlerBeanDefinitionParser extends AbstractSingleBeanDefin ParsingUtils.setPropertyValue(builder, element, "modify-on-creation", "modifyOnCreation"); } + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#resolveId(org.w3c.dom.Element, org.springframework.beans.factory.support.AbstractBeanDefinition, org.springframework.beans.factory.xml.ParserContext) + */ + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + + this.resolvedBeanName = super.resolveId(element, definition, parserContext); + return resolvedBeanName; + } + private BeanDefinition createLazyInitTargetSourceBeanDefinition(String auditorAwareRef) { BeanDefinitionBuilder targetSourceBuilder = rootBeanDefinition(LazyInitTargetSource.class); diff --git a/src/main/java/org/springframework/data/auditing/config/IsNewAwareAuditingHandlerBeanDefinitionParser.java b/src/main/java/org/springframework/data/auditing/config/IsNewAwareAuditingHandlerBeanDefinitionParser.java new file mode 100644 index 000000000..fc7c55a21 --- /dev/null +++ b/src/main/java/org/springframework/data/auditing/config/IsNewAwareAuditingHandlerBeanDefinitionParser.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2014 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.config; + +import org.springframework.data.auditing.IsNewAwareAuditingHandler; +import org.springframework.data.mapping.context.MappingContext; +import org.w3c.dom.Element; + +/** + * {@link AuditingHandlerBeanDefinitionParser} that will register am {@link IsNewAwareAuditingHandler}. Needs to get the + * bean id of the {@link MappingContext} it shall refer to. + * + * @author Oliver Gierke + * @since 1.5 + */ +public class IsNewAwareAuditingHandlerBeanDefinitionParser extends AuditingHandlerBeanDefinitionParser { + + /** + * Creates a new {@link IsNewAwareAuditingHandlerBeanDefinitionParser}. + * + * @param mappingContextBeanName must not be {@literal null} or empty. + */ + public IsNewAwareAuditingHandlerBeanDefinitionParser(String mappingContextBeanName) { + super(mappingContextBeanName); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.config.AuditingHandlerBeanDefinitionParser#getBeanClass(org.w3c.dom.Element) + */ + @Override + protected Class getBeanClass(Element element) { + return IsNewAwareAuditingHandler.class; + } +} diff --git a/src/main/java/org/springframework/data/config/IsNewAwareAuditingHandlerBeanDefinitionParser.java b/src/main/java/org/springframework/data/config/IsNewAwareAuditingHandlerBeanDefinitionParser.java deleted file mode 100644 index 15fcd594b..000000000 --- a/src/main/java/org/springframework/data/config/IsNewAwareAuditingHandlerBeanDefinitionParser.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2012-2014 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.config; - -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.data.auditing.IsNewAwareAuditingHandler; -import org.springframework.util.Assert; -import org.w3c.dom.Element; - -/** - * {@link AuditingHandlerBeanDefinitionParser} that will register am {@link IsNewAwareAuditingHandler}. Needs to get the - * bean id of the - * - * @author Oliver Gierke - */ -public class IsNewAwareAuditingHandlerBeanDefinitionParser extends AuditingHandlerBeanDefinitionParser { - - private final String isNewStrategyFactoryBeanId; - private String resolvedBeanName; - - /** - * Creates a new {@link IsNewAwareAuditingHandlerBeanDefinitionParser}. - * - * @param isNewStrategyFactoryBeanId must not be {@literal null} or empty. - */ - public IsNewAwareAuditingHandlerBeanDefinitionParser(String isNewStrategyFactoryBeanId) { - - Assert.hasText(isNewStrategyFactoryBeanId); - this.isNewStrategyFactoryBeanId = isNewStrategyFactoryBeanId; - } - - /** - * Returns the bean name that was used to register the {@link IsNewAwareAuditingHandler}. - * - * @return the resolvedBeanName - */ - public String getResolvedBeanName() { - return resolvedBeanName; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.config.AuditingHandlerBeanDefinitionParser#getBeanClass(org.w3c.dom.Element) - */ - @Override - protected Class getBeanClass(Element element) { - return IsNewAwareAuditingHandler.class; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.config.AuditingHandlerBeanDefinitionParser#doParse(org.w3c.dom.Element, org.springframework.beans.factory.support.BeanDefinitionBuilder) - */ - @Override - protected void doParse(Element element, BeanDefinitionBuilder builder) { - - builder.addConstructorArgReference(isNewStrategyFactoryBeanId); - super.doParse(element, builder); - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#resolveId(org.w3c.dom.Element, org.springframework.beans.factory.support.AbstractBeanDefinition, org.springframework.beans.factory.xml.ParserContext) - */ - @Override - protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) - throws BeanDefinitionStoreException { - - this.resolvedBeanName = super.resolveId(element, definition, parserContext); - return resolvedBeanName; - } -} diff --git a/src/main/java/org/springframework/data/mapping/PersistentEntity.java b/src/main/java/org/springframework/data/mapping/PersistentEntity.java index bb033b218..543fe39b7 100644 --- a/src/main/java/org/springframework/data/mapping/PersistentEntity.java +++ b/src/main/java/org/springframework/data/mapping/PersistentEntity.java @@ -96,6 +96,15 @@ public interface PersistentEntity> { */ P getPersistentProperty(String name); + /** + * Returns the property equipped with an annotation of the given type. + * + * @param annotationType must not be {@literal null}. + * @return + * @since 1.8 + */ + P getPersistentProperty(Class annotationType); + /** * Returns whether the {@link PersistentEntity} has an id property. If this call returns {@literal true}, * {@link #getIdProperty()} will return a non-{@literal null} value. @@ -158,6 +167,7 @@ public interface PersistentEntity> { * * @param annotationType must not be {@literal null}. * @return + * @since 1.8 */ A findAnnotation(Class annotationType); } diff --git a/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java b/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java index 73c25d209..e0e4a4244 100644 --- a/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java +++ b/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java @@ -228,6 +228,33 @@ public class BasicPersistentEntity> implement return propertyCache.get(name); } + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.PersistentEntity#getPersistentProperty(java.lang.Class) + */ + @Override + public P getPersistentProperty(Class annotationType) { + + Assert.notNull(annotationType, "Annotation type must not be null!"); + + for (P property : properties) { + if (property.isAnnotationPresent(annotationType)) { + return property; + } + } + + for (Association

association : associations) { + + P property = association.getInverse(); + + if (property.isAnnotationPresent(annotationType)) { + return property; + } + } + + return null; + } + /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getType() diff --git a/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java b/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java index b32f80663..c42ee0c08 100644 --- a/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java +++ b/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java @@ -46,6 +46,7 @@ public class AuditingHandlerUnitTests { when(auditorAware.getCurrentAuditor()).thenReturn(user); } + @SuppressWarnings("deprecation") protected AuditingHandler getHandler() { return new AuditingHandler(); } diff --git a/src/test/java/org/springframework/data/auditing/IsNewAwareAuditingHandlerUnitTests.java b/src/test/java/org/springframework/data/auditing/IsNewAwareAuditingHandlerUnitTests.java index eed7c6b49..a15a8da93 100644 --- a/src/test/java/org/springframework/data/auditing/IsNewAwareAuditingHandlerUnitTests.java +++ b/src/test/java/org/springframework/data/auditing/IsNewAwareAuditingHandlerUnitTests.java @@ -25,6 +25,10 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.context.SampleMappingContext; import org.springframework.data.support.IsNewStrategy; import org.springframework.data.support.IsNewStrategyFactory; @@ -46,6 +50,7 @@ public class IsNewAwareAuditingHandlerUnitTests extends AuditingHandlerUnitTests } @Override + @SuppressWarnings("deprecation") protected IsNewAwareAuditingHandler getHandler() { return new IsNewAwareAuditingHandler(factory); } @@ -71,4 +76,21 @@ public class IsNewAwareAuditingHandlerUnitTests extends AuditingHandlerUnitTests assertThat(user.createdDate, is(nullValue())); assertThat(user.modifiedDate, is(notNullValue())); } + + /** + * @see DATACMNS-365 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsNullMappingContext() { + new IsNewAwareAuditingHandler( + (MappingContext, ? extends PersistentProperty>) null); + } + + /** + * @see DATACMNS-365 + */ + @Test + public void setsUpHandlerWithMappingContext() { + new IsNewAwareAuditingHandler(new SampleMappingContext()); + } } diff --git a/src/test/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactoryUnitTests.java b/src/test/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactoryUnitTests.java new file mode 100644 index 000000000..34ef05606 --- /dev/null +++ b/src/test/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactoryUnitTests.java @@ -0,0 +1,126 @@ +/* + * Copyright 2014 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 java.util.GregorianCalendar; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.auditing.AuditableBeanWrapperFactory.AuditableInterfaceBeanWrapper; +import org.springframework.data.domain.Auditable; +import org.springframework.data.mapping.context.SampleMappingContext; + +/** + * Unit tests for {@link MappingAuditableBeanWrapperFactory}. + * + * @author Oliver Gierke + * @since 1.8 + */ +public class MappingAuditableBeanWrapperFactoryUnitTests { + + AuditableBeanWrapperFactory factory; + + @Before + public void setUp() { + factory = new MappingAuditableBeanWrapperFactory(new SampleMappingContext()); + } + + /** + * @see DATACMNS-365 + */ + @Test + public void discoversAuditingPropertyOnField() { + + Sample sample = new Sample(); + AuditableBeanWrapper wrapper = factory.getBeanWrapperFor(sample); + + assertThat(wrapper, is(notNullValue())); + + wrapper.setCreatedBy("Me!"); + assertThat(sample.createdBy, is(notNullValue())); + } + + /** + * @see DATACMNS-365 + */ + @Test + public void discoversAuditingPropertyOnAccessor() { + + Sample sample = new Sample(); + AuditableBeanWrapper wrapper = factory.getBeanWrapperFor(sample); + + assertThat(wrapper, is(notNullValue())); + + wrapper.setLastModifiedBy("Me, too!"); + assertThat(sample.lastModifiedBy, is(notNullValue())); + } + + /** + * @see DATACMNS-365 + */ + @Test + public void settingInavailablePropertyIsNoop() { + + Sample sample = new Sample(); + AuditableBeanWrapper wrapper = factory.getBeanWrapperFor(sample); + + wrapper.setLastModifiedDate(new GregorianCalendar()); + } + + /** + * @see DATACMNS-365 + */ + @Test + public void doesNotReturnWrapperForEntityNotUsingAuditing() { + assertThat(factory.getBeanWrapperFor(new NoAuditing()), is(nullValue())); + } + + /** + * @see DATACMNS-365 + */ + @Test + public void returnsAuditableWrapperForAuditable() { + + assertThat(factory.getBeanWrapperFor(mock(ExtendingAuditable.class)), + is(instanceOf(AuditableInterfaceBeanWrapper.class))); + } + + static class Sample { + + @CreatedBy private Object createdBy; + private Object lastModifiedBy; + + @LastModifiedBy + public Object getLastModifiedBy() { + return lastModifiedBy; + } + } + + static class NoAuditing { + + } + + @SuppressWarnings("serial") + static abstract class ExtendingAuditable implements Auditable { + + } +} diff --git a/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java b/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java index 6f39ca517..ff93584c6 100644 --- a/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java @@ -1,3 +1,18 @@ +/* + * Copyright 2011-2014 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.*; @@ -8,16 +23,23 @@ import java.util.Comparator; import java.util.Iterator; import java.util.SortedSet; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; import org.springframework.data.annotation.TypeAlias; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentEntitySpec; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.Person; +import org.springframework.data.mapping.context.SampleMappingContext; +import org.springframework.data.mapping.context.SamplePersistentProperty; import org.springframework.data.util.ClassTypeInformation; import org.springframework.test.util.ReflectionTestUtils; @@ -29,8 +51,9 @@ import org.springframework.test.util.ReflectionTestUtils; @RunWith(MockitoJUnitRunner.class) public class BasicPersistentEntityUnitTests> { - @Mock - T property; + @Rule public ExpectedException exception = ExpectedException.none(); + + @Mock T property; @Test public void assertInvariants() { @@ -122,13 +145,28 @@ public class BasicPersistentEntityUnitTests> { when(property.isIdProperty()).thenReturn(true); entity.addPersistentProperty(property); + exception.expect(MappingException.class); + entity.addPersistentProperty(property); + } - try { - entity.addPersistentProperty(property); - fail("Expected MappingException!"); - } catch (MappingException e) { - // expected - } + /** + * @see DATACMNS-365 + */ + @Test + public void detectsPropertyWithAnnotation() { + + SampleMappingContext context = new SampleMappingContext(); + PersistentEntity entity = context.getPersistentEntity(Entity.class); + + PersistentProperty property = entity.getPersistentProperty(LastModifiedBy.class); + assertThat(property, is(notNullValue())); + assertThat(property.getName(), is("field")); + + property = entity.getPersistentProperty(CreatedBy.class); + assertThat(property, is(notNullValue())); + assertThat(property.getName(), is("property")); + + assertThat(entity.getPersistentProperty(CreatedDate.class), is(nullValue())); } private BasicPersistentEntity createEntity(Comparator comparator) { @@ -142,5 +180,15 @@ public class BasicPersistentEntityUnitTests> { static class Entity { + @LastModifiedBy String field; + String property; + + /** + * @return the property + */ + @CreatedBy + public String getProperty() { + return property; + } } }