MappedStatementConfig.java

/*
 * Copyright 2004-2026 the original author or authors.
 *
 * 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
 *
 *    https://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 com.ibatis.sqlmap.engine.config;

import com.ibatis.common.beans.Probe;
import com.ibatis.common.beans.ProbeFactory;
import com.ibatis.common.resources.Resources;
import com.ibatis.sqlmap.client.SqlMapException;
import com.ibatis.sqlmap.engine.cache.CacheModel;
import com.ibatis.sqlmap.engine.impl.SqlMapClientImpl;
import com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate;
import com.ibatis.sqlmap.engine.mapping.parameter.InlineParameterMapParser;
import com.ibatis.sqlmap.engine.mapping.parameter.ParameterMap;
import com.ibatis.sqlmap.engine.mapping.result.AutoResultMap;
import com.ibatis.sqlmap.engine.mapping.result.ResultMap;
import com.ibatis.sqlmap.engine.mapping.sql.Sql;
import com.ibatis.sqlmap.engine.mapping.sql.SqlText;
import com.ibatis.sqlmap.engine.mapping.sql.dynamic.DynamicSql;
import com.ibatis.sqlmap.engine.mapping.sql.simple.SimpleDynamicSql;
import com.ibatis.sqlmap.engine.mapping.sql.stat.StaticSql;
import com.ibatis.sqlmap.engine.mapping.statement.CachingStatement;
import com.ibatis.sqlmap.engine.mapping.statement.InsertStatement;
import com.ibatis.sqlmap.engine.mapping.statement.MappedStatement;
import com.ibatis.sqlmap.engine.mapping.statement.SelectKeyStatement;
import com.ibatis.sqlmap.engine.scope.ErrorContext;
import com.ibatis.sqlmap.engine.type.TypeHandlerFactory;

import java.sql.ResultSet;
import java.util.Arrays;
import java.util.List;

/**
 * The Class MappedStatementConfig.
 */
public class MappedStatementConfig {

  /** The Constant PROBE. */
  private static final Probe PROBE = ProbeFactory.getProbe();

  /** The Constant PARAM_PARSER. */
  private static final InlineParameterMapParser PARAM_PARSER = new InlineParameterMapParser();

  /** The error context. */
  private ErrorContext errorContext;

  /** The client. */
  private SqlMapClientImpl client;

  /** The type handler factory. */
  private TypeHandlerFactory typeHandlerFactory;

  /** The mapped statement. */
  private MappedStatement mappedStatement;

  /** The root statement. */
  private MappedStatement rootStatement;

  /**
   * Instantiates a new mapped statement config.
   *
   * @param config
   *          the config
   * @param id
   *          the id
   * @param statement
   *          the statement
   * @param processor
   *          the processor
   * @param parameterMapName
   *          the parameter map name
   * @param parameterClass
   *          the parameter class
   * @param resultMapName
   *          the result map name
   * @param additionalResultMapNames
   *          the additional result map names
   * @param resultClass
   *          the result class
   * @param additionalResultClasses
   *          the additional result classes
   * @param cacheModelName
   *          the cache model name
   * @param resultSetType
   *          the result set type
   * @param fetchSize
   *          the fetch size
   * @param allowRemapping
   *          the allow remapping
   * @param timeout
   *          the timeout
   * @param defaultStatementTimeout
   *          the default statement timeout
   * @param xmlResultName
   *          the xml result name
   */
  MappedStatementConfig(SqlMapConfiguration config, String id, MappedStatement statement, SqlSource processor,
      String parameterMapName, Class parameterClass, String resultMapName, String[] additionalResultMapNames,
      Class resultClass, Class[] additionalResultClasses, String cacheModelName, String resultSetType,
      Integer fetchSize, boolean allowRemapping, Integer timeout, Integer defaultStatementTimeout,
      String xmlResultName) {
    this.errorContext = config.getErrorContext();
    this.client = config.getClient();
    SqlMapExecutorDelegate delegate = client.getDelegate();
    this.typeHandlerFactory = config.getTypeHandlerFactory();
    errorContext.setActivity("parsing a mapped statement");
    errorContext.setObjectId(id + " statement");
    errorContext.setMoreInfo("Check the result map name.");
    if (resultMapName != null) {
      statement.setResultMap(client.getDelegate().getResultMap(resultMapName));
      if (additionalResultMapNames != null) {
        for (String additionalResultMapName : additionalResultMapNames) {
          statement.addResultMap(client.getDelegate().getResultMap(additionalResultMapName));
        }
      }
    }
    errorContext.setMoreInfo("Check the parameter map name.");
    if (parameterMapName != null) {
      statement.setParameterMap(client.getDelegate().getParameterMap(parameterMapName));
    }
    statement.setId(id);
    statement.setResource(errorContext.getResource());
    if (resultSetType != null) {
      switch (resultSetType) {
        case "FORWARD_ONLY":
          statement.setResultSetType(Integer.valueOf(ResultSet.TYPE_FORWARD_ONLY));
          break;
        case "SCROLL_INSENSITIVE":
          statement.setResultSetType(Integer.valueOf(ResultSet.TYPE_SCROLL_INSENSITIVE));
          break;
        case "SCROLL_SENSITIVE":
          statement.setResultSetType(Integer.valueOf(ResultSet.TYPE_SCROLL_SENSITIVE));
          break;
        default:
          break;
      }
    }
    if (fetchSize != null) {
      statement.setFetchSize(fetchSize);
    }

    // set parameter class either from attribute or from map (make sure to match)
    ParameterMap parameterMap = statement.getParameterMap();
    if (parameterMap == null) {
      statement.setParameterClass(parameterClass);
    } else {
      statement.setParameterClass(parameterMap.getParameterClass());
    }

    // process SQL statement, including inline parameter maps
    errorContext.setMoreInfo("Check the SQL statement.");
    Sql sql = processor.getSql();
    setSqlForStatement(statement, sql);

    // set up either null result map or automatic result mapping
    ResultMap resultMap = statement.getResultMap();
    if (resultMap == null && resultClass == null) {
      statement.setResultMap(null);
    } else if (resultMap == null) {
      resultMap = buildAutoResultMap(allowRemapping, statement, resultClass, xmlResultName);
      statement.setResultMap(resultMap);
      if (additionalResultClasses != null) {
        for (Class additionalResultClass : additionalResultClasses) {
          statement.addResultMap(buildAutoResultMap(allowRemapping, statement, additionalResultClass, xmlResultName));
        }
      }

    }
    statement.setTimeout(defaultStatementTimeout);
    if (timeout != null) {
      try {
        statement.setTimeout(timeout);
      } catch (NumberFormatException e) {
        throw new SqlMapException(
            "Specified timeout value for statement " + statement.getId() + " is not a valid integer");
      }
    }
    errorContext.setMoreInfo(null);
    errorContext.setObjectId(null);
    statement.setSqlMapClient(client);
    if (cacheModelName != null && !cacheModelName.isEmpty() && client.getDelegate().isCacheModelsEnabled()) {
      CacheModel cacheModel = client.getDelegate().getCacheModel(cacheModelName);
      mappedStatement = new CachingStatement(statement, cacheModel);
    } else {
      mappedStatement = statement;
    }
    rootStatement = statement;
    delegate.addMappedStatement(mappedStatement);
  }

  /**
   * Sets the select key statement.
   *
   * @param processor
   *          the processor
   * @param resultClassName
   *          the result class name
   * @param keyPropName
   *          the key prop name
   * @param runAfterSQL
   *          the run after SQL
   * @param type
   *          the type
   */
  public void setSelectKeyStatement(SqlSource processor, String resultClassName, String keyPropName,
      boolean runAfterSQL, String type) {
    if (!(rootStatement instanceof InsertStatement)) {
      throw new SqlMapException("You cant set a select key statement on statement named " + rootStatement.getId()
          + " because it is not an InsertStatement.");
    }
    InsertStatement insertStatement = (InsertStatement) rootStatement;
    Class parameterClass = insertStatement.getParameterClass();
    errorContext.setActivity("parsing a select key");
    SelectKeyStatement selectKeyStatement = new SelectKeyStatement();
    resultClassName = typeHandlerFactory.resolveAlias(resultClassName);
    Class resultClass = null;

    // get parameter and result maps
    selectKeyStatement.setSqlMapClient(client);
    selectKeyStatement.setId(insertStatement.getId() + "-SelectKey");
    selectKeyStatement.setResource(errorContext.getResource());
    selectKeyStatement.setKeyProperty(keyPropName);
    selectKeyStatement.setRunAfterSQL(runAfterSQL);
    // process the type (pre or post) attribute
    if (type != null) {
      selectKeyStatement.setRunAfterSQL("post".equals(type));
    }
    try {
      if (resultClassName != null) {
        errorContext.setMoreInfo("Check the select key result class.");
        resultClass = Resources.classForName(resultClassName);
      } else if (keyPropName != null && parameterClass != null) {
        resultClass = PROBE.getPropertyTypeForSetter(parameterClass, selectKeyStatement.getKeyProperty());
      }
    } catch (ClassNotFoundException e) {
      throw new SqlMapException("Error.  Could not set result class.  Cause: " + e, e);
    }
    if (resultClass == null) {
      resultClass = Object.class;
    }

    // process SQL statement, including inline parameter maps
    errorContext.setMoreInfo("Check the select key SQL statement.");
    Sql sql = processor.getSql();
    setSqlForStatement(selectKeyStatement, sql);
    ResultMap resultMap = new AutoResultMap(client.getDelegate(), false);
    resultMap.setId(selectKeyStatement.getId() + "-AutoResultMap");
    resultMap.setResultClass(resultClass);
    resultMap.setResource(selectKeyStatement.getResource());
    selectKeyStatement.setResultMap(resultMap);
    errorContext.setMoreInfo(null);
    insertStatement.setSelectKeyStatement(selectKeyStatement);
  }

  /**
   * Sets the sql for statement.
   *
   * @param statement
   *          the statement
   * @param sql
   *          the sql
   */
  private void setSqlForStatement(MappedStatement statement, Sql sql) {
    if (sql instanceof DynamicSql) {
      statement.setSql(sql);
    } else {
      applyInlineParameterMap(statement, sql.getSql(null, null));
    }
  }

  /**
   * Apply inline parameter map.
   *
   * @param statement
   *          the statement
   * @param sqlStatement
   *          the sql statement
   */
  private void applyInlineParameterMap(MappedStatement statement, String sqlStatement) {
    String newSql = sqlStatement;
    errorContext.setActivity("building an inline parameter map");
    ParameterMap parameterMap = statement.getParameterMap();
    errorContext.setMoreInfo("Check the inline parameters.");
    if (parameterMap == null) {
      ParameterMap map;
      map = new ParameterMap(client.getDelegate());
      map.setId(statement.getId() + "-InlineParameterMap");
      map.setParameterClass(statement.getParameterClass());
      map.setResource(statement.getResource());
      statement.setParameterMap(map);
      SqlText sqlText = PARAM_PARSER.parseInlineParameterMap(client.getDelegate().getTypeHandlerFactory(), newSql,
          statement.getParameterClass());
      newSql = sqlText.getText();
      List mappingList = Arrays.asList(sqlText.getParameterMappings());
      map.setParameterMappingList(mappingList);
    }
    Sql sql;
    if (SimpleDynamicSql.isSimpleDynamicSql(newSql)) {
      sql = new SimpleDynamicSql(client.getDelegate(), newSql);
    } else {
      sql = new StaticSql(newSql);
    }
    statement.setSql(sql);

  }

  /**
   * Builds the auto result map.
   *
   * @param allowRemapping
   *          the allow remapping
   * @param statement
   *          the statement
   * @param firstResultClass
   *          the first result class
   * @param xmlResultName
   *          the xml result name
   *
   * @return the result map
   */
  private ResultMap buildAutoResultMap(boolean allowRemapping, MappedStatement statement, Class firstResultClass,
      String xmlResultName) {
    ResultMap resultMap = new AutoResultMap(client.getDelegate(), allowRemapping);
    resultMap.setId(statement.getId() + "-AutoResultMap");
    resultMap.setResultClass(firstResultClass);
    resultMap.setXmlName(xmlResultName);
    resultMap.setResource(statement.getResource());
    return resultMap;
  }

  /**
   * Gets the mapped statement.
   *
   * @return the mapped statement
   */
  public MappedStatement getMappedStatement() {
    return mappedStatement;
  }
}