SqlMapConfiguration.java

/*
 * Copyright 2004-2023 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.ClassInfo;
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.client.extensions.TypeHandlerCallback;
import com.ibatis.sqlmap.engine.accessplan.AccessPlanFactory;
import com.ibatis.sqlmap.engine.cache.CacheController;
import com.ibatis.sqlmap.engine.cache.CacheModel;
import com.ibatis.sqlmap.engine.cache.fifo.FifoCacheController;
import com.ibatis.sqlmap.engine.cache.lru.LruCacheController;
import com.ibatis.sqlmap.engine.cache.memory.MemoryCacheController;
import com.ibatis.sqlmap.engine.datasource.DbcpDataSourceFactory;
import com.ibatis.sqlmap.engine.datasource.JndiDataSourceFactory;
import com.ibatis.sqlmap.engine.datasource.SimpleDataSourceFactory;
import com.ibatis.sqlmap.engine.impl.SqlMapClientImpl;
import com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate;
import com.ibatis.sqlmap.engine.mapping.result.Discriminator;
import com.ibatis.sqlmap.engine.mapping.result.ResultMap;
import com.ibatis.sqlmap.engine.mapping.result.ResultObjectFactory;
import com.ibatis.sqlmap.engine.mapping.statement.MappedStatement;
import com.ibatis.sqlmap.engine.scope.ErrorContext;
import com.ibatis.sqlmap.engine.transaction.TransactionManager;
import com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig;
import com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransactionConfig;
import com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig;
import com.ibatis.sqlmap.engine.type.*;

import java.util.Iterator;
import java.util.Map;

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

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

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

  /** The delegate. */
  private SqlMapExecutorDelegate delegate;

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

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

  /** The default statement timeout. */
  private Integer defaultStatementTimeout;

  /**
   * Instantiates a new sql map configuration.
   */
  public SqlMapConfiguration() {
    errorContext = new ErrorContext();
    delegate = new SqlMapExecutorDelegate();
    typeHandlerFactory = delegate.getTypeHandlerFactory();
    client = new SqlMapClientImpl(delegate);
    registerDefaultTypeAliases();
  }

  /**
   * Gets the type handler factory.
   *
   * @return the type handler factory
   */
  public TypeHandlerFactory getTypeHandlerFactory() {
    return typeHandlerFactory;
  }

  /**
   * Gets the error context.
   *
   * @return the error context
   */
  public ErrorContext getErrorContext() {
    return errorContext;
  }

  /**
   * Gets the client.
   *
   * @return the client
   */
  public SqlMapClientImpl getClient() {
    return client;
  }

  /**
   * Gets the delegate.
   *
   * @return the delegate
   */
  public SqlMapExecutorDelegate getDelegate() {
    return delegate;
  }

  /**
   * Sets the class info cache enabled.
   *
   * @param classInfoCacheEnabled
   *          the new class info cache enabled
   */
  public void setClassInfoCacheEnabled(boolean classInfoCacheEnabled) {
    errorContext.setActivity("setting class info cache enabled/disabled");
    ClassInfo.setCacheEnabled(classInfoCacheEnabled);
  }

  /**
   * Sets the lazy loading enabled.
   *
   * @param lazyLoadingEnabled
   *          the new lazy loading enabled
   */
  public void setLazyLoadingEnabled(boolean lazyLoadingEnabled) {
    errorContext.setActivity("setting lazy loading enabled/disabled");
    client.getDelegate().setLazyLoadingEnabled(lazyLoadingEnabled);
  }

  /**
   * Sets the statement caching enabled.
   *
   * @param statementCachingEnabled
   *          the new statement caching enabled
   */
  public void setStatementCachingEnabled(boolean statementCachingEnabled) {
    errorContext.setActivity("setting statement caching enabled/disabled");
    client.getDelegate().setStatementCacheEnabled(statementCachingEnabled);
  }

  /**
   * Sets the cache models enabled.
   *
   * @param cacheModelsEnabled
   *          the new cache models enabled
   */
  public void setCacheModelsEnabled(boolean cacheModelsEnabled) {
    errorContext.setActivity("setting cache models enabled/disabled");
    client.getDelegate().setCacheModelsEnabled(cacheModelsEnabled);
  }

  /**
   * Sets the enhancement enabled.
   *
   * @param enhancementEnabled
   *          the new enhancement enabled
   */
  public void setEnhancementEnabled(boolean enhancementEnabled) {
    errorContext.setActivity("setting enhancement enabled/disabled");
    try {
      enhancementEnabled = enhancementEnabled && Resources.classForName("net.sf.cglib.proxy.InvocationHandler") != null;
    } catch (ClassNotFoundException e) {
      enhancementEnabled = false;
    }
    client.getDelegate().setEnhancementEnabled(enhancementEnabled);
    AccessPlanFactory.setBytecodeEnhancementEnabled(enhancementEnabled);
  }

  /**
   * Sets the use column label.
   *
   * @param useColumnLabel
   *          the new use column label
   */
  public void setUseColumnLabel(boolean useColumnLabel) {
    client.getDelegate().setUseColumnLabel(useColumnLabel);
  }

  /**
   * Sets the force multiple result set support.
   *
   * @param forceMultipleResultSetSupport
   *          the new force multiple result set support
   */
  public void setForceMultipleResultSetSupport(boolean forceMultipleResultSetSupport) {
    client.getDelegate().setForceMultipleResultSetSupport(forceMultipleResultSetSupport);
  }

  /**
   * Sets the default statement timeout.
   *
   * @param defaultTimeout
   *          the new default statement timeout
   */
  public void setDefaultStatementTimeout(Integer defaultTimeout) {
    errorContext.setActivity("setting default timeout");
    if (defaultTimeout != null) {
      try {
        defaultStatementTimeout = defaultTimeout;
      } catch (NumberFormatException e) {
        throw new SqlMapException("Specified defaultStatementTimeout is not a valid integer");
      }
    }
  }

  /**
   * Sets the transaction manager.
   *
   * @param txManager
   *          the new transaction manager
   */
  public void setTransactionManager(TransactionManager txManager) {
    delegate.setTxManager(txManager);
  }

  /**
   * Sets the result object factory.
   *
   * @param rof
   *          the new result object factory
   */
  public void setResultObjectFactory(ResultObjectFactory rof) {
    delegate.setResultObjectFactory(rof);
  }

  /**
   * New type handler.
   *
   * @param javaType
   *          the java type
   * @param jdbcType
   *          the jdbc type
   * @param callback
   *          the callback
   */
  public void newTypeHandler(Class javaType, String jdbcType, Object callback) {
    try {
      errorContext.setActivity("building a building custom type handler");
      TypeHandlerFactory typeHandlerFactory = client.getDelegate().getTypeHandlerFactory();
      TypeHandler typeHandler;
      if (callback instanceof TypeHandlerCallback) {
        typeHandler = new CustomTypeHandler((TypeHandlerCallback) callback);
      } else if (callback instanceof TypeHandler) {
        typeHandler = (TypeHandler) callback;
      } else {
        throw new RuntimeException(
            "The object '" + callback + "' is not a valid implementation of TypeHandler or TypeHandlerCallback");
      }
      errorContext.setMoreInfo("Check the javaType attribute '" + javaType + "' (must be a classname) or the jdbcType '"
          + jdbcType + "' (must be a JDBC type name).");
      if (jdbcType != null && jdbcType.length() > 0) {
        typeHandlerFactory.register(javaType, jdbcType, typeHandler);
      } else {
        typeHandlerFactory.register(javaType, typeHandler);
      }
    } catch (Exception e) {
      throw new SqlMapException("Error registering occurred.  Cause: " + e, e);
    }
    errorContext.setMoreInfo(null);
    errorContext.setObjectId(null);
  }

  /**
   * New cache model config.
   *
   * @param id
   *          the id
   * @param controller
   *          the controller
   * @param readOnly
   *          the read only
   * @param serialize
   *          the serialize
   *
   * @return the cache model config
   */
  public CacheModelConfig newCacheModelConfig(String id, CacheController controller, boolean readOnly,
      boolean serialize) {
    return new CacheModelConfig(this, id, controller, readOnly, serialize);
  }

  /**
   * New parameter map config.
   *
   * @param id
   *          the id
   * @param parameterClass
   *          the parameter class
   *
   * @return the parameter map config
   */
  public ParameterMapConfig newParameterMapConfig(String id, Class parameterClass) {
    return new ParameterMapConfig(this, id, parameterClass);
  }

  /**
   * New result map config.
   *
   * @param id
   *          the id
   * @param resultClass
   *          the result class
   * @param groupBy
   *          the group by
   * @param extended
   *          the extended
   * @param xmlName
   *          the xml name
   *
   * @return the result map config
   */
  public ResultMapConfig newResultMapConfig(String id, Class resultClass, String groupBy, String extended,
      String xmlName) {
    return new ResultMapConfig(this, id, resultClass, groupBy, extended, xmlName);
  }

  /**
   * New mapped statement 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 resultSetType
   *          the result set type
   * @param fetchSize
   *          the fetch size
   * @param allowRemapping
   *          the allow remapping
   * @param timeout
   *          the timeout
   * @param cacheModelName
   *          the cache model name
   * @param xmlResultName
   *          the xml result name
   *
   * @return the mapped statement config
   */
  public MappedStatementConfig newMappedStatementConfig(String id, MappedStatement statement, SqlSource processor,
      String parameterMapName, Class parameterClass, String resultMapName, String[] additionalResultMapNames,
      Class resultClass, Class[] additionalResultClasses, String resultSetType, Integer fetchSize,
      boolean allowRemapping, Integer timeout, String cacheModelName, String xmlResultName) {
    return new MappedStatementConfig(this, id, statement, processor, parameterMapName, parameterClass, resultMapName,
        additionalResultMapNames, resultClass, additionalResultClasses, cacheModelName, resultSetType, fetchSize,
        allowRemapping, timeout, defaultStatementTimeout, xmlResultName);
  }

  /**
   * Finalize sql map config.
   */
  public void finalizeSqlMapConfig() {
    wireUpCacheModels();
    bindResultMapDiscriminators();
  }

  /**
   * Resolve type handler.
   *
   * @param typeHandlerFactory
   *          the type handler factory
   * @param clazz
   *          the clazz
   * @param propertyName
   *          the property name
   * @param javaType
   *          the java type
   * @param jdbcType
   *          the jdbc type
   *
   * @return the type handler
   */
  TypeHandler resolveTypeHandler(TypeHandlerFactory typeHandlerFactory, Class clazz, String propertyName,
      Class javaType, String jdbcType) {
    return resolveTypeHandler(typeHandlerFactory, clazz, propertyName, javaType, jdbcType, false);
  }

  /**
   * Resolve type handler.
   *
   * @param typeHandlerFactory
   *          the type handler factory
   * @param clazz
   *          the clazz
   * @param propertyName
   *          the property name
   * @param javaType
   *          the java type
   * @param jdbcType
   *          the jdbc type
   * @param useSetterToResolve
   *          the use setter to resolve
   *
   * @return the type handler
   */
  TypeHandler resolveTypeHandler(TypeHandlerFactory typeHandlerFactory, Class clazz, String propertyName,
      Class javaType, String jdbcType, boolean useSetterToResolve) {
    TypeHandler handler;
    if (clazz == null) {
      // Unknown
      handler = typeHandlerFactory.getUnkownTypeHandler();
    } else if (DomTypeMarker.class.isAssignableFrom(clazz)) {
      // DOM
      handler = typeHandlerFactory.getTypeHandler(String.class, jdbcType);
    } else if (java.util.Map.class.isAssignableFrom(clazz)) {
      // Map
      if (javaType == null) {
        handler = typeHandlerFactory.getUnkownTypeHandler(); // BUG 1012591 -
                                                             // typeHandlerFactory.getTypeHandler(java.lang.Object.class,
                                                             // jdbcType);
      } else {
        handler = typeHandlerFactory.getTypeHandler(javaType, jdbcType);
      }
    } else if (typeHandlerFactory.getTypeHandler(clazz, jdbcType) != null) {
      // Primitive
      handler = typeHandlerFactory.getTypeHandler(clazz, jdbcType);
    } else {
      // JavaBean
      if (javaType == null) {
        if (useSetterToResolve) {
          Class type = PROBE.getPropertyTypeForSetter(clazz, propertyName);
          handler = typeHandlerFactory.getTypeHandler(type, jdbcType);
        } else {
          Class type = PROBE.getPropertyTypeForGetter(clazz, propertyName);
          handler = typeHandlerFactory.getTypeHandler(type, jdbcType);
        }
      } else {
        handler = typeHandlerFactory.getTypeHandler(javaType, jdbcType);
      }
    }
    return handler;
  }

  /**
   * Register default type aliases.
   */
  private void registerDefaultTypeAliases() {
    // TRANSACTION ALIASES
    typeHandlerFactory.putTypeAlias("JDBC", JdbcTransactionConfig.class.getName());
    typeHandlerFactory.putTypeAlias("JTA", JtaTransactionConfig.class.getName());
    typeHandlerFactory.putTypeAlias("EXTERNAL", ExternalTransactionConfig.class.getName());

    // DATA SOURCE ALIASES
    typeHandlerFactory.putTypeAlias("SIMPLE", SimpleDataSourceFactory.class.getName());
    typeHandlerFactory.putTypeAlias("DBCP", DbcpDataSourceFactory.class.getName());
    typeHandlerFactory.putTypeAlias("JNDI", JndiDataSourceFactory.class.getName());

    // CACHE ALIASES
    typeHandlerFactory.putTypeAlias("FIFO", FifoCacheController.class.getName());
    typeHandlerFactory.putTypeAlias("LRU", LruCacheController.class.getName());
    typeHandlerFactory.putTypeAlias("MEMORY", MemoryCacheController.class.getName());

    // TYPE ALIASEs
    typeHandlerFactory.putTypeAlias("dom", DomTypeMarker.class.getName());
    typeHandlerFactory.putTypeAlias("domCollection", DomCollectionTypeMarker.class.getName());
    typeHandlerFactory.putTypeAlias("xml", XmlTypeMarker.class.getName());
    typeHandlerFactory.putTypeAlias("xmlCollection", XmlCollectionTypeMarker.class.getName());
  }

  /**
   * Wire up cache models.
   */
  private void wireUpCacheModels() {
    // Wire Up Cache Models
    Iterator cacheNames = client.getDelegate().getCacheModelNames();
    while (cacheNames.hasNext()) {
      String cacheName = (String) cacheNames.next();
      CacheModel cacheModel = client.getDelegate().getCacheModel(cacheName);
      Iterator statementNames = cacheModel.getFlushTriggerStatementNames();
      while (statementNames.hasNext()) {
        String statementName = (String) statementNames.next();
        MappedStatement statement = client.getDelegate().getMappedStatement(statementName);
        if (statement != null) {
          statement.addExecuteListener(cacheModel);
        } else {
          throw new RuntimeException("Could not find statement named '" + statementName
              + "' for use as a flush trigger for the cache model named '" + cacheName + "'.");
        }
      }
    }
  }

  /**
   * Bind result map discriminators.
   */
  private void bindResultMapDiscriminators() {
    // Bind discriminators
    Iterator names = delegate.getResultMapNames();
    while (names.hasNext()) {
      String name = (String) names.next();
      ResultMap rm = delegate.getResultMap(name);
      Discriminator disc = rm.getDiscriminator();
      if (disc != null) {
        disc.bindSubMaps();
      }
    }
  }

}