AutoUpdateImpl.java

package com.github.mygreen.sqlmapper.core.query.auto;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import org.springframework.dao.OptimisticLockingFailureException;

import com.github.mygreen.sqlmapper.core.SqlMapperContext;
import com.github.mygreen.sqlmapper.core.event.PostUpdateEvent;
import com.github.mygreen.sqlmapper.core.event.PreUpdateEvent;
import com.github.mygreen.sqlmapper.core.meta.EntityMeta;
import com.github.mygreen.sqlmapper.core.meta.PropertyMeta;
import com.github.mygreen.sqlmapper.core.meta.PropertyValueInvoker;
import com.github.mygreen.sqlmapper.core.query.IllegalOperateException;
import com.github.mygreen.sqlmapper.metamodel.EntityPath;
import com.github.mygreen.sqlmapper.metamodel.PropertyPath;

import lombok.Getter;
import lombok.NonNull;

/**
 * 更新を行うSQLを自動生成するクエリの実装です。
 *
 * @author T.TSUCHIE
 *
 * @param <T> 処理対象となるエンティティの型
 */
public class AutoUpdateImpl<T> implements AutoUpdate<T> {

    /**
     * SqlMapperの設定情報。
     */
    @Getter
    private final SqlMapperContext context;

    /**
     * 削除対象のエンティティ
     */
    @Getter
    private final T entity;

    /**
     * エンティティ情報
     */
    @Getter
    private final EntityMeta entityMeta;

    @Getter
    private Integer queryTimeout;

    /**
     * バージョンプロパティを更新対象に含めるかどうか。
     */
    @Getter
    private boolean includeVersion;

    /**
     * null値のプロパティを更新から除外する
     */
    @Getter
    private boolean excludesNull;

    /**
     * バージョンチェックを行った場合に、更新行数が0行でも{@link OptimisticLockingFailureException}スローしないなら<code>true</code>
     */
    @Getter
    private boolean suppresOptimisticLockException = false;

    /**
     * 更新対象とするプロパティ
     */
    @Getter
    private final Set<String> includesProperties = new LinkedHashSet<>();

    /**
     * 更新対象から除外するプロパティ
     */
    @Getter
    private final Set<String> excludesProperties = new LinkedHashSet<>();

    /**
     * 更新前のプロパティの状態を保持するマップ。
     * <p>
     * key=プロパティ名、value=プロパティの値。
     * </p>
     */
    @Getter
    private Map<String, Object> beforeStates = Collections.emptyMap();

    public AutoUpdateImpl(SqlMapperContext context, T entity) {
        this.context = context;
        this.entity = entity;
        this.entityMeta = context.getEntityMetaFactory().create(entity.getClass());

        // 処理対象の情報の整合性などのチェックを行う
        validateTarget();
    }

    private void validateTarget() {

        // 読み取り専用かどうかのチェック
        if(entityMeta.getTableMeta().isReadOnly()) {
            throw new IllegalOperateException(context.getMessageFormatter().create("query.readOnlyEntity")
                    .paramWithClass("entityType", entityMeta.getEntityType())
                    .format());
        }

        // 主キーを持つかどうかのチェック
        if(entityMeta.getIdPropertyMetaList().isEmpty()) {
            throw new IllegalOperateException(context.getMessageFormatter().create("query.requiredId")
                    .paramWithClass("entityType", entityMeta.getEntityType())
                    .format());
        }
    }

    @Override
    public AutoUpdateImpl<T> queryTimeout(int seconds) {
        this.queryTimeout = seconds;
        return this;
    }

    @Override
    public AutoUpdateImpl<T> includesVersion() {
        this.includeVersion = true;
        return this;
    }

    @Override
    public AutoUpdateImpl<T> excludesNull() {
        this.excludesNull = true;
        return this;
    }

    @Override
    public AutoUpdateImpl<T> suppresOptimisticLockException() {
        this.suppresOptimisticLockException = true;
        return this;
    }

    @Override
    public AutoUpdateImpl<T> includes(final PropertyPath<?>... properties) {

        for(PropertyPath<?> prop : properties) {
            String propertyName = prop.getPathMeta().getElement();

            // 挿入対象のプロパティの親のチェック
            EntityPath<?> parentPath = (EntityPath<?>)prop.getPathMeta().getParent();
            if(!entityMeta.getEntityType().equals(parentPath.getType())) {
                throw new IllegalOperateException(context.getMessageFormatter().create("query.noIncludeProperty")
                        .paramWithClass("classType", entityMeta.getEntityType())
                        .param("properyName", propertyName)
                        .format());
            }

            this.includesProperties.add(propertyName);
        }

        return this;
    }

    @Override
    public AutoUpdateImpl<T> excludes(final PropertyPath<?>... properties) {

        for(PropertyPath<?> prop : properties) {
            String propertyName = prop.getPathMeta().getElement();

            // 除外対象のプロパティの親のチェック
            EntityPath<?> parentPath = (EntityPath<?>)prop.getPathMeta().getParent();
            if(!entityMeta.getEntityType().equals(parentPath.getType())) {
                throw new IllegalOperateException(context.getMessageFormatter().create("query.noIncludeProperty")
                        .paramWithClass("classType", entityMeta.getEntityType())
                        .param("properyName", propertyName)
                        .format());

            }

            this.excludesProperties.add(propertyName);
        }

        return this;
    }

    @Override
    public AutoUpdateImpl<T> changedFrom(@NonNull final T beforeEntity) {
        this.beforeStates = new HashMap<String, Object>(entityMeta.getPropertyMetaSize());

        for(PropertyMeta propertyMeta : entityMeta.getAllColumnPropertyMeta()) {
            final String propertyName = propertyMeta.getName();
            final Object propertyValue = PropertyValueInvoker.getEmbeddedPropertyValue(propertyMeta, beforeEntity);

            this.beforeStates.put(propertyName, propertyValue);
        }

        return this;
    }

    @Override
    public AutoUpdateImpl<T> changedFrom(final Map<String, Object> beforeStates) {
        if(!beforeStates.isEmpty()) {
            this.beforeStates = Map.copyOf(beforeStates);
        }

        return this;
    }

    @Override
    public int execute() {

        context.getApplicationEventPublisher().publishEvent(new PreUpdateEvent(this, entityMeta, entity));

        final int result = new AutoUpdateExecutor(this)
                .execute();

        context.getApplicationEventPublisher().publishEvent(new PostUpdateEvent(this, entityMeta, entity));
        return result;

    }

}