StoredParamMetaFactory.java

package com.github.mygreen.sqlmapper.core.meta;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ReflectionUtils;

import com.github.mygreen.messageformatter.MessageFormatter;
import com.github.mygreen.sqlmapper.core.type.ValueType;
import com.github.mygreen.sqlmapper.core.type.ValueTypeRegistry;

import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;

/**
 * ストアドプロシージャ/ストアドファンクションのパラメータのメタ情報を作成します。
 *
 * @since 0.3
 * @author T.TSUCHIE
 *
 */
public class StoredParamMetaFactory {

    /**
     * メッセージフォーマッタです。
     * Springのインジェクション対象です。
     */
    @Getter
    @Setter
    @Autowired
    private MessageFormatter messageFormatter;

    @Getter
    @Setter
    @Autowired
    private ValueTypeRegistry valueTypeRegistry;

    @Getter
    @Setter
    @Autowired
    private StoredPropertyMetaFactory storedPropertyMetaFactory;

    @Getter
    @Setter
    @Autowired
    private PropertyMetaFactory propertyMetaFactory;

    /**
     * パラメータのメタ情報のキャッシュ用マップです。
     * <p>key=エンティティのFQN, value=StoredParamMeta</p>
     */
    private ConcurrentHashMap<String, StoredParamMeta> paramMetaMap = new ConcurrentHashMap<>(100);

    /**
     * 作成したパラメータのメタ情報をクリアします。
     */
    public void clear() {
        this.paramMetaMap.clear();
    }

    public StoredParamMeta create(@NonNull final Class<?> paramClass) {

        if(valueTypeRegistry.isRegisteredType(paramClass)) {
            return createAnonymounseParamMeta(paramClass);
        }

        return paramMetaMap.computeIfAbsent(paramClass.getName(), s -> doParamMeta(paramClass));

    }

    public StoredParamMeta createAnonymounseParamMeta(final Class<?> paramClass) {

        StoredParamMeta paramMeta = new StoredParamMeta(paramClass, true);

        // プロパティに対する型変換を設定します。
        ValueType<?> valueType = valueTypeRegistry.findValueType(paramClass);

        //TODO:
//        // OracleなどBoolean型を純粋にサポートしていない場合は、int型に変換するタイプに変換する。
//        valueType = dialect.getValueType(valueType);
        paramMeta.setValueType(valueType);

        return paramMeta;
    }

    private StoredParamMeta doParamMeta(final Class<?> paramClass) {

        final StoredParamMeta paramMeta = new StoredParamMeta(paramClass, false);

        doPropertyMeta(paramMeta, paramClass);

        return paramMeta;

    }

    private void doPropertyMeta(final StoredParamMeta paramMeta, final Class<?> paramClass) {

        List<StoredPropertyMeta> propertyMeataList = new ArrayList<>();
        extractProperty(paramClass, propertyMeataList);

        // ネストしたプロパティを抽出する
        propertyMeataList.stream()
            .filter(p -> !p.isSingleValue())
            .forEach(p -> doNestedPropertyMeta(p));

        propertyMeataList.stream().forEach(p -> paramMeta.addPropertyMeta(p));


    }

    private void extractProperty(final Class<?> targetClass, final List<StoredPropertyMeta> propertyMeataList) {

        for(Field field : targetClass.getDeclaredFields()) {

            int modifiers = field.getModifiers();
            if(Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) {
                continue;
            }
            ReflectionUtils.makeAccessible(field);

            propertyMeataList.add(storedPropertyMetaFactory.create(field));
        }
    }

    private void doNestedPropertyMeta(final StoredPropertyMeta propertyMeta) {

        final Class<?> propertyType = propertyMeta.getPropertyType();

        final Class<?> beanType;
        if(Collection.class.isAssignableFrom(propertyType)) {
            beanType = propertyMeta.getComponentType()
                    .orElseThrow(() -> new InvalidStoredParamException(propertyMeta.getDeclaringClass(),
                            messageFormatter.create("storedParam.notDeclaredComponentType")
                            .paramWithClass("paramType", propertyMeta.getDeclaringClass())
                            .param("property", propertyMeta.getName())
                            .format()));
        } else {
            beanType = propertyType;
        }

        // プロパティを抽出する
        List<PropertyMeta> nestedPropertyMeataList = new ArrayList<>();
        for(Field field : beanType.getDeclaredFields()) {

            int modifiers = field.getModifiers();
            if(Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) {
                continue;
            }
            ReflectionUtils.makeAccessible(field);

            nestedPropertyMeataList.add(propertyMetaFactory.create(field, Optional.empty(), false));
        }

        nestedPropertyMeataList.stream().forEach(p -> propertyMeta.addNestedPropertyMeta(p));

        if(propertyMeta.getAllNestedColumnPropertyMeta().isEmpty()) {
            /*
             * マッピング対象のカラムプロパティが存在しない場合は、JavaBean形式として不正としてエラーとする。
             * または、Mapなどのサポート対象外の形式と判断する。
             */
            throw new InvalidStoredParamException(propertyMeta.getDeclaringClass(), messageFormatter.create("storedParam.notFoundColumnProperty")
                    .paramWithClass("paramType", propertyMeta.getDeclaringClass())
                    .param("property", propertyMeta.getName())
                    .paramWithClass("propertyType", propertyType)
                    .format());
        }
    }

}