DynamoDBAuditingRegistrar.java

/*
 * Copyright © 2018 spring-data-dynamodb (https://github.com/prasanna0586/spring-data-dynamodb)
 *
 * 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.socialsignin.spring.data.dynamodb.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.socialsignin.spring.data.dynamodb.mapping.DynamoDBMappingContext;
import org.socialsignin.spring.data.dynamodb.mapping.event.AuditingEntityCallback;
import org.socialsignin.spring.data.dynamodb.mapping.event.AuditingEventListener;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
import org.springframework.data.auditing.config.AuditingConfiguration;
import org.springframework.data.config.ParsingUtils;
import org.springframework.data.repository.config.PersistentEntitiesFactoryBean;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;

import java.lang.annotation.Annotation;

import static org.socialsignin.spring.data.dynamodb.config.BeanNames.MAPPING_CONTEXT_BEAN_NAME;
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;

/**
 * {@link org.springframework.context.annotation.ImportBeanDefinitionRegistrar} to enable {@link EnableDynamoDBAuditing}
 * annotation.
 * @author Prasanna Kumar Ramachandran
 */
class DynamoDBAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {

    private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDBAuditingRegistrar.class);

    /*
     * (non-Javadoc)
     * @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAnnotation()
     */
    @NonNull
    @Override
    protected Class<? extends Annotation> getAnnotation() {
        return EnableDynamoDBAuditing.class;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditingHandlerBeanName()
     */
    @NonNull
    @Override
    protected String getAuditingHandlerBeanName() {
        return "dynamoDBAuditingHandler";
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry)
     */
    @Override
    public void registerBeanDefinitions(@NonNull AnnotationMetadata annotationMetadata, @NonNull BeanDefinitionRegistry registry) {
        LOGGER.trace("registerBeanDefinitions");
        Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null!");
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");

        defaultDependenciesIfNecessary();
        super.registerBeanDefinitions(annotationMetadata, registry);
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditHandlerBeanDefinitionBuilder(org.springframework.data.auditing.config.AuditingConfiguration)
     */
    @NonNull
    @Override
    protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(@NonNull AuditingConfiguration configuration) {
        LOGGER.trace("getAuditHandlerBeanDefinitionBuilder");
        Assert.notNull(configuration, "AuditingConfiguration must not be null!");

        BeanDefinitionBuilder persistentEntities = rootBeanDefinition(PersistentEntitiesFactoryBean.class);
        persistentEntities.addConstructorArgReference(MAPPING_CONTEXT_BEAN_NAME);

        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class);
        builder.addConstructorArgValue(persistentEntities.getBeanDefinition());
        return configureDefaultAuditHandlerAttributes(configuration, builder);
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerAuditListenerBeanDefinition(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry)
     */
    @Override
    protected void registerAuditListenerBeanDefinition(@NonNull BeanDefinition auditingHandlerDefinition,
                                                       @NonNull BeanDefinitionRegistry registry) {
        LOGGER.trace("registerAuditListenerBeanDefinition");
        Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");

        // Register the modern callback-based auditing (for entity modification)
        BeanDefinitionBuilder callbackBeanDefinitionBuilder = BeanDefinitionBuilder
                .rootBeanDefinition(AuditingEntityCallback.class);
        callbackBeanDefinitionBuilder.addConstructorArgValue(
                ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));

        registerInfrastructureBeanWithId(callbackBeanDefinitionBuilder.getBeanDefinition(),
                AuditingEntityCallback.class.getName(), registry);

        // Also register the legacy event listener for backward compatibility
        BeanDefinitionBuilder listenerBeanDefinitionBuilder = BeanDefinitionBuilder
                .rootBeanDefinition(AuditingEventListener.class);
        listenerBeanDefinitionBuilder.addConstructorArgValue(
                ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));

        registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(),
                AuditingEventListener.class.getName(), registry);
    }

    /**
     * This method no longer creates a default {@link DynamoDBMappingContext} bean.
     * The mapping context should be provided by:
     * <p>
     * 1. @EnableDynamoDBRepositories (which creates it with the appropriate marshalling mode), or
     * 2. A user-defined @Bean
     * <p>
     * This matches the pattern used by Spring Data MongoDB's MongoAuditingRegistrar, which
     * relies on Spring's dependency injection to fail with a clear error if the required
     * mapping context bean is not available.
     */
    private void defaultDependenciesIfNecessary() {
        // No longer create a default mapping context bean.
        // The bean reference in getAuditHandlerBeanDefinitionBuilder (line 97) will cause Spring
        // to fail with a clear dependency injection error if no DynamoDBMappingContext bean exists.
        LOGGER.trace("DynamoDBAuditingRegistrar expects DynamoDBMappingContext bean '{}' to be provided by @EnableDynamoDBRepositories or user configuration",
                MAPPING_CONTEXT_BEAN_NAME);
    }
}