AutoStoredExecutorSupport.java

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

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlInOutParameter;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.SqlReturnResultSet;

import com.github.mygreen.sqlmapper.core.SqlMapperContext;
import com.github.mygreen.sqlmapper.core.SqlMapperException;
import com.github.mygreen.sqlmapper.core.mapper.SingleColumnRowMapper;
import com.github.mygreen.sqlmapper.core.mapper.StoredResultSetRowMapper;
import com.github.mygreen.sqlmapper.core.meta.StoredParamMeta;
import com.github.mygreen.sqlmapper.core.meta.StoredPropertyMeta;
import com.github.mygreen.sqlmapper.core.type.ValueType;

import lombok.NonNull;
import lombok.RequiredArgsConstructor;

/**
 * ストアド(プロシージャ/ファンクション)のサポートクラス。
 *
 *
 * @author T.TSUCHIE
 *
 */
@RequiredArgsConstructor
public abstract class AutoStoredExecutorSupport {

    protected final SqlMapperContext context;

    /**
     * ストアド実行時のSQLパラメータタイプを組み立てます。
     * @param paramMeta パラメータ情報。
     * @return SQLのパラメータ情報
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    protected SqlParameter[] createSqlParameterTypes(final StoredParamMeta paramMeta) {

        List<SqlParameter> paramTypes = new ArrayList<>();

        if(paramMeta.isAnonymouse()) {
            paramTypes.add(new SqlParameter(paramMeta.getValueType().get().getSqlType(context.getDialect())));

        } else {
            for(StoredPropertyMeta propertyMeta : paramMeta.getAllPropertyMeta()) {
                if(propertyMeta.isIn()) {
                    ValueType<?> valueType = propertyMeta.getValueType();
                    paramTypes.add(new SqlParameter(propertyMeta.getName(), valueType.getSqlType(context.getDialect())));

                } else if(propertyMeta.isOut()) {
                    ValueType<?> valueType = propertyMeta.getValueType();
                    paramTypes.add(new SqlOutParameter(propertyMeta.getName(), valueType.getSqlType(context.getDialect()),
                            new RowMapper<Object>() {

                                @Override
                                public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
                                    return valueType.getValue(rs, rs.findColumn(propertyMeta.getName()));
                                }
                            }));

                } else if(propertyMeta.isInOut()) {
                    ValueType<?> valueType = propertyMeta.getValueType();
                    paramTypes.add(new SqlInOutParameter(propertyMeta.getName(), valueType.getSqlType(context.getDialect()),
                            new RowMapper<Object>() {

                                @Override
                                public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
                                    return valueType.getValue(rs, rs.findColumn(propertyMeta.getName()));
                                }
                            }));

                } else if(propertyMeta.isResultSet()) {
                    if(propertyMeta.isSingleValue()) {
                        paramTypes.add(new SqlReturnResultSet(propertyMeta.getName(),
                                new SingleColumnRowMapper(propertyMeta.getValueType())));
                    } else {
                        // JavaBeanの形式の場合
                        paramTypes.add(new SqlReturnResultSet(propertyMeta.getName(),
                                new StoredResultSetRowMapper(propertyMeta)));
                    }

                }
            }

        }

        return paramTypes.toArray(new SqlParameter[paramTypes.size()]);

    }

    /**
     * クエリ実行時のパラメータの値を作成します。
     * @param paramMeta パラメータ情報。
     * @param parameter パラメータオブジェクト。{@literal null} は許可しません。
     * @return クエリ実行時に渡すパラメータの値。
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    protected Object[] createParameterValues(final StoredParamMeta paramMeta, @NonNull final Object parameter) {

        List<Object> paramValues = new ArrayList<>();

        if(paramMeta.isAnonymouse()) {
            ValueType valueType = paramMeta.getValueType().get();
            paramValues.add(valueType.getSqlParameterValue(parameter));
        } else {
            for(StoredPropertyMeta propertyMeta : paramMeta.getAllPropertyMeta()) {
                if(propertyMeta.isIn() || propertyMeta.isInOut()) {
                    ValueType valueType = propertyMeta.getValueType();
                    paramValues.add(valueType.getSqlParameterValue(propertyMeta.getPropertyValue(parameter)));

                }
            }
        }

        return paramValues.toArray(new Object[paramValues.size()]);

    }

    /**
     * パラメータにOUT/INOUT/ResultSetパラメータが含まれるかどうか
     * @return
     */
    protected boolean containsResultParam(final StoredParamMeta paramMeta, final Optional<Object> parameter) {
        if(parameter.isEmpty()) {
            return false;
        }

        for(StoredPropertyMeta propertyMeta : paramMeta.getAllPropertyMeta()) {
            if(propertyMeta.isInOut() || propertyMeta.isOut() || propertyMeta.isResultSet()) {
                return true;
            }
        }

        return false;
    }

    /**
     * 戻り値を処理します。
     * @param paramMeta パラメータ情報。
     * @param parameter パラメータオブジェクト。{@literal null} は許可しません。
     * @param out クエリ実行時の結果マップ。
     */
    @SuppressWarnings({"rawtypes", "unused"})
    protected void doResultValue(final StoredParamMeta paramMeta, @NonNull final Object parameter, final Map<String, Object> out) {

        for(StoredPropertyMeta propertyMeta : paramMeta.getAllPropertyMeta()) {
            if(propertyMeta.isOut() || propertyMeta.isInOut() || propertyMeta.isResultSet()) {
                ValueType valueType = propertyMeta.getValueType();
                Object value = out.get(propertyMeta.getName());
                if(value == null) {
                    continue;
                }

                if(propertyMeta.isSingleValue()) {
                    if((value instanceof List) && !List.class.isAssignableFrom(propertyMeta.getPropertyType())) {
                        // DBの戻り値はリストだが、マッピング先はリストでない場合、1つ目の要素をマッピングする。
                        propertyMeta.setPropertyValue(parameter, ((List)value).get(0));
                    } else {
                        propertyMeta.setPropertyValue(parameter, value);
                    }
                } else if(propertyMeta.getPropertyType().isAssignableFrom(value.getClass())) {
                    // 値が、マッピング先の子クラスの場合はそのまま設定する
                    propertyMeta.setPropertyValue(parameter, value);
                } else {
                    // マッピング先のタイプと取得元のタイプが一致しない場合
                    throw new SqlMapperException(context.getMessageFormatter().create("storedResult.notMatchType")
                            .paramWithClass("paramType", paramMeta.getParamType())
                            .param("property", propertyMeta.getName())
                            .paramWithClass("propertyType", propertyMeta.getPropertyType())
                            .paramWithClass("resultSetType", value.getClass())
                            .format());
                }
            }

        }
    }

}