/*
* Copyright 2004-2015 the Seasar Foundation and the Others.
*
* 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.seasar.extension.jdbc.query;
import java.lang.reflect.Field;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import javax.persistence.Lob;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.seasar.extension.jdbc.JdbcContext;
import org.seasar.extension.jdbc.ModuleCall;
import org.seasar.extension.jdbc.ParamType;
import org.seasar.extension.jdbc.ResultSetHandler;
import org.seasar.extension.jdbc.ValueType;
import org.seasar.extension.jdbc.annotation.InOut;
import org.seasar.extension.jdbc.annotation.Out;
import org.seasar.extension.jdbc.exception.FieldNotGenericsRuntimeException;
import org.seasar.extension.jdbc.handler.BeanListResultSetHandler;
import org.seasar.extension.jdbc.handler.BeanResultSetHandler;
import org.seasar.extension.jdbc.handler.ObjectListResultSetHandler;
import org.seasar.extension.jdbc.handler.ObjectResultSetHandler;
import org.seasar.extension.jdbc.manager.JdbcManagerImplementor;
import org.seasar.extension.jdbc.parameter.LobParameter;
import org.seasar.extension.jdbc.parameter.TemporalParameter;
import org.seasar.extension.jdbc.query.AbstractModuleCall.ParamDesc.ParameterType;
import org.seasar.extension.jdbc.types.ValueTypes;
import org.seasar.framework.exception.SQLRuntimeException;
import org.seasar.framework.util.ClassUtil;
import org.seasar.framework.util.Disposable;
import org.seasar.framework.util.DisposableUtil;
import org.seasar.framework.util.FieldUtil;
import org.seasar.framework.util.ModifierUtil;
import org.seasar.framework.util.StatementUtil;
import org.seasar.framework.util.tiger.CollectionsUtil;
import org.seasar.framework.util.tiger.ReflectionUtil;
/**
* 永続格納モジュール(ストアドプロシージャまたはストアドファンクション)を呼び出す抽象クラスです。
*
* @author koichik
* @param <S>
* <code>ModuleCall</code>のサブタイプです。
*/
public abstract class AbstractModuleCall<S extends ModuleCall<S>> extends
AbstractQuery<S> implements ModuleCall<S> {
/** {@link DisposableUtil} に登録済みなら<code>true</code> */
protected static boolean initialized;
/** プロシージャまたはファンクションの引数として使用されるDTOのフィールドを表す{@link ParamDesc}のキャッシュ */
protected static final ConcurrentMap<Class<?>, ParamDesc[]> paramDescCache = CollectionsUtil
.newConcurrentHashMap();
/** 最大行数 */
protected int maxRows;
/** フェッチ数 */
protected int fetchSize;
/** パラメータ */
protected Object parameter;
/** MS SQLServerのような結果セットを<code>OUT</code>パラメータにマッピングしない場合のパラメータのリスト */
protected List<Param> nonParamList = new ArrayList<Param>();
/** ファンクション呼び出しの場合は<code>true</code> */
protected boolean functionCall;
/**
* インスタンスを構築します。
*
* @param jdbcManager
* 内部的なJDBCマネージャ
* @param functionCall
* ファンクション呼び出しの場合は<code>true</code>
*/
public AbstractModuleCall(final JdbcManagerImplementor jdbcManager,
final boolean functionCall) {
super(jdbcManager);
this.functionCall = functionCall;
}
@SuppressWarnings("unchecked")
public S maxRows(final int maxRows) {
this.maxRows = maxRows;
return (S) this;
}
@SuppressWarnings("unchecked")
public S fetchSize(final int fetchSize) {
this.fetchSize = fetchSize;
return (S) this;
}
/**
* パラメータの準備をします。
*/
protected void prepareParameter() {
if (parameter == null) {
return;
}
final Class<?> paramClass = parameter.getClass();
if (ValueTypes.isSimpleType(paramClass)
|| TemporalParameter.class == paramClass
|| LobParameter.class == paramClass) {
addParam(parameter, paramClass);
return;
}
final boolean needsParameterForResultSet = jdbcManager.getDialect()
.needsParameterForResultSet();
for (final ParamDesc paramDesc : getParamDescs(paramClass)) {
final Class<?> clazz = paramDesc.paramClass;
final ValueType valueType = paramDesc.valueType;
switch (paramDesc.paramType) {
case RESULT_SET:
if (needsParameterForResultSet) {
addParam(paramDesc.field, clazz, valueType, ParamType.OUT);
} else {
addNonParam(paramDesc.field);
}
break;
case IN:
addParam(FieldUtil.get(paramDesc.field, parameter), clazz,
valueType);
break;
case OUT:
addParam(paramDesc.field, clazz, valueType, ParamType.OUT);
break;
case IN_OUT:
addParam(paramDesc.field, clazz, valueType, ParamType.IN_OUT);
break;
}
}
}
/**
* パラメータを追加します。
*
* @param field
* パラメータとなるフィールド
* @param paramClass
* パラメータのクラス
* @param valueType
* 値タイプ
* @param paramType
* パラメータのタイプ
*/
protected void addParam(final Field field, final Class<?> paramClass,
final ValueType valueType, final ParamType paramType) {
final Object value;
if (field != null && paramType != ParamType.OUT) {
value = FieldUtil.get(field, parameter);
} else {
value = null;
}
final Param p = addParam(value, paramClass, valueType);
p.paramType = paramType;
p.field = field;
}
/**
* 呼び出し可能なステートメントを返します。
*
* @param jdbcContext
* JDBCコンテキスト
* @return 呼び出し可能なステートメント
*/
protected CallableStatement getCallableStatement(
final JdbcContext jdbcContext) {
final CallableStatement ps = jdbcContext
.getCallableStatement(executedSql);
setupCallableStatement(ps);
return ps;
}
/**
* 呼び出し可能なステートメントをセットアップします。
*
* @param cs
* 呼び出し可能なステートメント
*/
protected void setupCallableStatement(final CallableStatement cs) {
if (maxRows > 0) {
StatementUtil.setMaxRows(cs, maxRows);
}
if (fetchSize > 0) {
StatementUtil.setFetchSize(cs, fetchSize);
}
if (queryTimeout > 0) {
StatementUtil.setQueryTimeout(cs, queryTimeout);
}
prepareInParams(cs);
prepareOutParams(cs);
}
/**
* <code>OUT</code>パラメータを準備します。
*
* @param cs
* 呼び出し可能なステートメント
*/
protected void prepareOutParams(final CallableStatement cs) {
final int size = getParamSize();
try {
for (int i = 0; i < size; i++) {
final Param param = getParam(i);
if (param.paramType == ParamType.IN) {
continue;
}
param.valueType.registerOutParameter(cs, i + 1);
}
} catch (final SQLException e) {
throw new SQLRuntimeException(e);
}
}
/**
* <code>OUT</code>パラメータにマッピングされない結果セットを処理します。
* <p>
* このメソッドは{@link #handleOutParams(CallableStatement)}よりも前に呼び出さなくてはなりません。
* </p>
*
* @param cs
* 呼び出し可能なステートメント
* @param resultSetGettable
* 呼び出し可能なステートメントから結果セットを取得可能な場合{@code true}
*/
protected void handleNonParamResultSets(final CallableStatement cs,
final boolean resultSetGettable) {
try {
if (!resultSetGettable) {
cs.getMoreResults();
}
for (int i = 0; i < nonParamList.size(); i++) {
final ResultSet rs = getResultSet(cs);
if (rs == null) {
break;
}
final Param param = nonParamList.get(i);
final Object value = handleResultSet(param.field, cs
.getResultSet());
FieldUtil.set(param.field, parameter, value);
cs.getMoreResults();
}
} catch (final SQLException e) {
throw new SQLRuntimeException(e);
}
}
/**
* <code>OUT/INOUT</code>パラメータとは別に戻される結果セットを返します。
*
* @param cs
* 呼び出し可能なステートメント
* @return 結果セット
*/
protected ResultSet getResultSet(final CallableStatement cs) {
try {
for (;;) {
final ResultSet rs = cs.getResultSet();
if (rs != null) {
return rs;
}
if (cs.getUpdateCount() == -1) {
return null;
}
cs.getMoreResults();
}
} catch (final SQLException e) {
throw new SQLRuntimeException(e);
}
}
/**
* <code>OUT</code>パラメータを処理します。
*
* @param cs
* 呼び出し可能なステートメント
*/
protected void handleOutParams(final CallableStatement cs) {
try {
final int start = functionCall ? 1 : 0;
for (int i = start; i < getParamSize(); i++) {
final Param param = getParam(i);
if (param.paramType == ParamType.IN) {
continue;
}
Object value = param.valueType.getValue(cs, i + 1);
if (value instanceof ResultSet) {
value = handleResultSet(param.field, (ResultSet) value);
}
FieldUtil.set(param.field, parameter, value);
}
} catch (final SQLException e) {
throw new SQLRuntimeException(e);
}
}
/**
* 結果セットを処理します。
*
* @param field
* フィールド
* @param rs
* 結果セット
* @return 処理した結果
*/
protected Object handleResultSet(final Field field, final ResultSet rs) {
if (!List.class.isAssignableFrom(field.getType())) {
return handleSingleResult(field.getType(), rs);
}
final Class<?> elementClass = ReflectionUtil
.getElementTypeOfListFromFieldType(field);
if (elementClass == null) {
logger.log("ESSR0709", new Object[] { callerClass.getName(),
callerMethodName });
throw new FieldNotGenericsRuntimeException(field);
}
return handleResultList(elementClass, rs);
}
/**
* 1行だけの結果セットを処理します。
*
* @param resultClass
* 結果のクラス
* @param rs
* 結果セット
* @return 処理した結果
*/
protected Object handleSingleResult(final Class<?> resultClass,
final ResultSet rs) {
final ResultSetHandler handler;
if (ValueTypes.isSimpleType(resultClass)) {
handler = new ObjectResultSetHandler(ValueTypes
.getValueType(resultClass), null);
} else {
handler = new BeanResultSetHandler(resultClass, jdbcManager
.getDialect(), jdbcManager.getPersistenceConvention(), null);
}
return handleResultSet(handler, rs);
}
/**
* 複数の行を持つ結果セットを処理します。
*
* @param elementClass
* 結果となるリストの要素型
* @param rs
* 結果セット
* @return 処理した結果のリスト
*/
protected Object handleResultList(final Class<?> elementClass,
final ResultSet rs) {
final ResultSetHandler handler;
if (ValueTypes.isSimpleType(elementClass)) {
handler = new ObjectListResultSetHandler(ValueTypes
.getValueType(elementClass));
} else {
handler = new BeanListResultSetHandler(elementClass, jdbcManager
.getDialect(), jdbcManager.getPersistenceConvention(), null);
}
return handleResultSet(handler, rs);
}
/**
* 直接パラメータでは指定しないパラメータの数を返します。
*
* @return 直接パラメータでは指定しないパラメータの数
*/
protected int getNonParamSize() {
return nonParamList.size();
}
/**
* 直接パラメータでは指定しないパラメータを返します。
*
* @param index
* インデックス
* @return 直接パラメータでは指定しないパラメータ
*/
protected Param getNonParam(final int index) {
return nonParamList.get(index);
}
/**
* 直接パラメータでは指定しないパラメータを追加します。
*
* @param field
* フィールド
* @return 追加されたパラメータ
*/
protected Param addNonParam(final Field field) {
final Param param = new Param();
param.field = field;
param.paramType = ParamType.OUT;
nonParamList.add(param);
return param;
}
/**
* フェッチ数を返します。
*
* @return フェッチ数
*/
public int getFetchSize() {
return fetchSize;
}
/**
* 最大行数を返します。
*
* @return 最大行数
*/
public int getMaxRows() {
return maxRows;
}
/**
* パラメータを返します。
*
* @return パラメータ
*/
public Object getParameter() {
return parameter;
}
/**
* 初期化します。
*/
protected synchronized void initialize() {
if (initialized) {
return;
}
DisposableUtil.add(new Disposable() {
public void dispose() {
paramDescCache.clear();
initialized = false;
}
});
initialized = true;
}
/**
* パラメータとして渡されたDTOのフィールドを表す{@link ParamDesc}の配列を返します。
*
* @param dtoClass
* パラメータとして渡されたDTOのクラス
* @return パラメータとして渡されたDTOのフィールドを表す{@link ParamDesc}の配列
*/
protected ParamDesc[] getParamDescs(final Class<?> dtoClass) {
initialize();
final ParamDesc[] paramDesc = paramDescCache.get(dtoClass);
if (paramDesc != null) {
return paramDesc;
}
return createParamDesc(dtoClass);
}
/**
* パラメータとして渡されたDTOのフィールドを表す{@link ParamDesc}の配列を作成して返します。
*
* @param dtoClass
* パラメータとして渡されたDTOのクラス
* @return パラメータとして渡されたDTOのフィールドを表す{@link ParamDesc}の配列
*/
protected ParamDesc[] createParamDesc(final Class<?> dtoClass) {
final Field[] fields = ClassUtil.getDeclaredFields(dtoClass);
final List<ParamDesc> list = CollectionsUtil
.newArrayList(fields.length);
for (int i = 0; i < fields.length; ++i) {
final Field field = fields[i];
if (!ModifierUtil.isInstanceField(field)) {
continue;
}
field.setAccessible(true);
final ParamDesc paramDesc = new ParamDesc();
paramDesc.field = field;
paramDesc.name = field.getName();
paramDesc.paramClass = field.getType();
final boolean lob = field.getAnnotation(Lob.class) != null;
final Temporal temporal = field.getAnnotation(Temporal.class);
final TemporalType temporalType = temporal != null ? temporal
.value() : null;
paramDesc.valueType = getValueType(paramDesc.paramClass, lob,
temporalType);
if (field
.getAnnotation(org.seasar.extension.jdbc.annotation.ResultSet.class) != null) {
paramDesc.paramType = ParameterType.RESULT_SET;
} else if (field.getAnnotation(Out.class) != null) {
paramDesc.paramType = ParameterType.OUT;
} else if (field.getAnnotation(InOut.class) != null) {
paramDesc.paramType = ParameterType.IN_OUT;
} else {
paramDesc.paramType = ParameterType.IN;
}
list.add(paramDesc);
}
final ParamDesc[] result = list.toArray(new ParamDesc[list.size()]);
return CollectionsUtil.putIfAbsent(paramDescCache, dtoClass, result);
}
/**
* パラメータとして渡されたDTOのフィールドを表すクラスです。
*
* @author koichik
*/
public static class ParamDesc {
/**
* パラメータのタイプを表す列挙です。
*/
public enum ParameterType {
/** <code>IN</code>パラメータを表します。 */
IN,
/** <code>INOUT</code>パラメータを表します。 */
IN_OUT,
/** <code>OUT</code>パラメータを表します。 */
OUT,
/** ストアドのパラメータとは別に戻される結果セットを表します。 */
RESULT_SET
}
/** パラメータを表すフィールド */
public Field field;
/** パラメータ名 */
public String name;
/** パラメータのクラス */
public Class<?> paramClass;
/** パラメータのタイプ */
public ParameterType paramType;
/** パラメータのS2JDBCでの型 */
public ValueType valueType;
}
}