/*
* Copyright 2014-2017 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.time.LocalDateTime;
import java.time.temporal.TemporalAccessor;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
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.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.util.Optionals;
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
* @author Christoph Strobl
* @since 1.8
*/
public class MappingAuditableBeanWrapperFactory extends DefaultAuditableBeanWrapperFactory {
private final PersistentEntities entities;
private final Map<Class<?>, MappingAuditingMetadata> metadataCache;
/**
* Creates a new {@link MappingAuditableBeanWrapperFactory} using the given {@link PersistentEntities}.
*
* @param entities must not be {@literal null}.
*/
public MappingAuditableBeanWrapperFactory(PersistentEntities entities) {
Assert.notNull(entities, "PersistentEntities must not be null!");
this.entities = entities;
this.metadataCache = new HashMap<>();
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.AuditableBeanWrapperFactory#getBeanWrapperFor(java.lang.Object)
*/
@Override
public Optional<AuditableBeanWrapper> getBeanWrapperFor(Object source) {
return Optional.of(source).flatMap(it -> {
if (it instanceof Auditable) {
return super.getBeanWrapperFor(source);
}
Class<?> type = it.getClass();
return entities.getPersistentEntity(type).map(entity -> {
MappingAuditingMetadata metadata = metadataCache.computeIfAbsent(type,
key -> new MappingAuditingMetadata(entity));
return Optional.<AuditableBeanWrapper> ofNullable(metadata.isAuditable()
? new MappingMetadataAuditableBeanWrapper(entity.getPropertyAccessor(it), metadata) : null);
}).orElseGet(() -> super.getBeanWrapperFor(source));
});
}
/**
* Captures {@link PersistentProperty} instances equipped with auditing annotations.
*
* @author Oliver Gierke
* @since 1.8
*/
static class MappingAuditingMetadata {
private final Optional<? extends 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<?, ? extends PersistentProperty<?>> 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 Optionals.isAnyPresent(createdByProperty, createdDateProperty, lastModifiedByProperty,
lastModifiedDateProperty);
}
}
/**
* {@link AuditableBeanWrapper} using {@link MappingAuditingMetadata} and a {@link PersistentPropertyAccessor} to set
* values on auditing properties.
*
* @author Oliver Gierke
* @since 1.8
*/
static class MappingMetadataAuditableBeanWrapper extends DateConvertingAuditableBeanWrapper {
private final PersistentPropertyAccessor accessor;
private final MappingAuditingMetadata metadata;
/**
* Creates a new {@link MappingMetadataAuditableBeanWrapper} for the given target and
* {@link MappingAuditingMetadata}.
*
* @param target must not be {@literal null}.
* @param metadata must not be {@literal null}.
*/
public MappingMetadataAuditableBeanWrapper(PersistentPropertyAccessor accessor, MappingAuditingMetadata metadata) {
Assert.notNull(accessor, "Target object must not be null!");
Assert.notNull(metadata, "Auditing metadata must not be null!");
this.accessor = accessor;
this.metadata = metadata;
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedBy(java.util.Optional)
*/
@Override
public Object setCreatedBy(Object value) {
metadata.createdByProperty.ifPresent(it -> this.accessor.setProperty(it, Optional.of(value)));
return value;
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedDate(java.util.Optional)
*/
@Override
public TemporalAccessor setCreatedDate(TemporalAccessor value) {
return setDateProperty(metadata.createdDateProperty, value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedBy(java.util.Optional)
*/
@Override
public Object setLastModifiedBy(Object value) {
return setProperty(metadata.lastModifiedByProperty, value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.AuditableBeanWrapper#getLastModifiedDate()
*/
@Override
public Optional<TemporalAccessor> getLastModifiedDate() {
return getAsTemporalAccessor(metadata.lastModifiedDateProperty.flatMap(accessor::getProperty),
LocalDateTime.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedDate(java.util.Optional)
*/
@Override
public TemporalAccessor setLastModifiedDate(TemporalAccessor value) {
return setDateProperty(metadata.lastModifiedDateProperty, value);
}
private <T, P extends PersistentProperty<?>> T setProperty(Optional<P> property, T value) {
property.ifPresent(it -> this.accessor.setProperty(it, Optional.of(value)));
return value;
}
private <P extends PersistentProperty<?>> TemporalAccessor setDateProperty(Optional<P> property,
TemporalAccessor value) {
property.ifPresent(
it -> this.accessor.setProperty(it, Optional.of(getDateValueToSet(value, it.getType(), accessor.getBean()))));
return value;
}
}
}