org.springframework.data.auditing.MappingAuditableBeanWrapperFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.data.auditing.MappingAuditableBeanWrapperFactory.java

Source

/*
 * Copyright 2014-2019 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
 *
 *      https://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.lang.annotation.Annotation;
import java.time.temporal.TemporalAccessor;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;

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.PersistentPropertyPaths;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.util.Lazy;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;

/**
 * {@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 ConcurrentReferenceHashMap<>();
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.auditing.AuditableBeanWrapperFactory#getBeanWrapperFor(java.lang.Object)
     */
    @Override
    public <T> Optional<AuditableBeanWrapper<T>> getBeanWrapperFor(T source) {

        return Optional.of(source).flatMap(it -> {

            if (it instanceof Auditable) {
                return super.getBeanWrapperFor(source);
            }

            return entities.mapOnContext(it.getClass(), (context, entity) -> {

                MappingAuditingMetadata metadata = metadataCache.computeIfAbsent(it.getClass(),
                        key -> new MappingAuditingMetadata(context, it.getClass()));

                return Optional.<AuditableBeanWrapper<T>>ofNullable(metadata.isAuditable() //
                        ? new MappingMetadataAuditableBeanWrapper<T>(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 PersistentPropertyPaths<?, ? extends PersistentProperty<?>> createdByPaths, createdDatePaths,
                lastModifiedByPaths, lastModifiedDatePaths;

        private final Lazy<Boolean> isAuditable;

        /**
         * Creates a new {@link MappingAuditingMetadata} instance from the given {@link PersistentEntity}.
         *
         * @param entity must not be {@literal null}.
         */
        public <P> MappingAuditingMetadata(MappingContext<?, ? extends PersistentProperty<?>> context,
                Class<?> type) {

            Assert.notNull(type, "Type must not be null!");

            this.createdByPaths = context.findPersistentPropertyPaths(type, withAnnotation(CreatedBy.class));
            this.createdDatePaths = context.findPersistentPropertyPaths(type, withAnnotation(CreatedDate.class));
            this.lastModifiedByPaths = context.findPersistentPropertyPaths(type,
                    withAnnotation(LastModifiedBy.class));
            this.lastModifiedDatePaths = context.findPersistentPropertyPaths(type,
                    withAnnotation(LastModifiedDate.class));

            this.isAuditable = Lazy.of( //
                    () -> Arrays
                            .asList(createdByPaths, createdDatePaths, lastModifiedByPaths, lastModifiedDatePaths) //
                            .stream() //
                            .anyMatch(it -> !it.isEmpty())//
            );
        }

        private static Predicate<PersistentProperty<?>> withAnnotation(Class<? extends Annotation> type) {
            return t -> t.findAnnotation(type) != null;
        }

        /**
         * Returns whether the {@link PersistentEntity} is auditable at all (read: any of the auditing annotations is
         * present).
         *
         * @return
         */
        public boolean isAuditable() {
            return isAuditable.get();
        }
    }

    /**
     * {@link AuditableBeanWrapper} using {@link MappingAuditingMetadata} and a {@link PersistentPropertyAccessor} to set
     * values on auditing properties.
     *
     * @author Oliver Gierke
     * @since 1.8
     */
    static class MappingMetadataAuditableBeanWrapper<T> extends DateConvertingAuditableBeanWrapper<T> {

        private final PersistentPropertyAccessor<T> accessor;
        private final MappingAuditingMetadata metadata;

        /**
         * Creates a new {@link MappingMetadataAuditableBeanWrapper} for the given target and
         * {@link MappingAuditingMetadata}.
         *
         * @param accessor must not be {@literal null}.
         * @param metadata must not be {@literal null}.
         */
        public MappingMetadataAuditableBeanWrapper(PersistentPropertyAccessor<T> accessor,
                MappingAuditingMetadata metadata) {

            Assert.notNull(accessor, "PersistentPropertyAccessor 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.createdByPaths.forEach(it -> this.accessor.setProperty(it, value));

            return value;
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedDate(java.util.Optional)
         */
        @Override
        public TemporalAccessor setCreatedDate(TemporalAccessor value) {
            return setDateProperty(metadata.createdDatePaths, value);
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedBy(java.util.Optional)
         */
        @Override
        public Object setLastModifiedBy(Object value) {
            return setProperty(metadata.lastModifiedByPaths, value);
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.auditing.AuditableBeanWrapper#getLastModifiedDate()
         */
        @Override
        public Optional<TemporalAccessor> getLastModifiedDate() {

            Optional<Object> firstValue = metadata.lastModifiedDatePaths.getFirst() //
                    .map(accessor::getProperty);

            return getAsTemporalAccessor(firstValue, TemporalAccessor.class);
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedDate(java.util.Optional)
         */
        @Override
        public TemporalAccessor setLastModifiedDate(TemporalAccessor value) {
            return setDateProperty(metadata.lastModifiedDatePaths, value);
        }

        /* 
         * (non-Javadoc)
         * @see org.springframework.data.auditing.AuditableBeanWrapper#getBean()
         */
        @Override
        public T getBean() {
            return accessor.getBean();
        }

        private <S, P extends PersistentProperty<?>> S setProperty(
                PersistentPropertyPaths<?, ? extends PersistentProperty<?>> paths, S value) {

            paths.forEach(it -> this.accessor.setProperty(it, value));

            return value;
        }

        private <P extends PersistentProperty<?>> TemporalAccessor setDateProperty(
                PersistentPropertyPaths<?, ? extends PersistentProperty<?>> property, TemporalAccessor value) {

            property.forEach(it -> this.accessor.setProperty(it,
                    getDateValueToSet(value, it.getRequiredLeafProperty().getType(), accessor.getBean())));

            return value;
        }
    }
}