MapperBuilderAssistant.java

  1. /*
  2.  *    Copyright 2009-2021 the original author or authors.
  3.  *
  4.  *    Licensed under the Apache License, Version 2.0 (the "License");
  5.  *    you may not use this file except in compliance with the License.
  6.  *    You may obtain a copy of the License at
  7.  *
  8.  *       http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  *    Unless required by applicable law or agreed to in writing, software
  11.  *    distributed under the License is distributed on an "AS IS" BASIS,
  12.  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  *    See the License for the specific language governing permissions and
  14.  *    limitations under the License.
  15.  */
  16. package org.apache.ibatis.builder;

  17. import java.util.ArrayList;
  18. import java.util.Collections;
  19. import java.util.HashMap;
  20. import java.util.HashSet;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.Properties;
  24. import java.util.Set;
  25. import java.util.StringTokenizer;

  26. import org.apache.ibatis.cache.Cache;
  27. import org.apache.ibatis.cache.decorators.LruCache;
  28. import org.apache.ibatis.cache.impl.PerpetualCache;
  29. import org.apache.ibatis.executor.ErrorContext;
  30. import org.apache.ibatis.executor.keygen.KeyGenerator;
  31. import org.apache.ibatis.mapping.CacheBuilder;
  32. import org.apache.ibatis.mapping.Discriminator;
  33. import org.apache.ibatis.mapping.MappedStatement;
  34. import org.apache.ibatis.mapping.ParameterMap;
  35. import org.apache.ibatis.mapping.ParameterMapping;
  36. import org.apache.ibatis.mapping.ParameterMode;
  37. import org.apache.ibatis.mapping.ResultFlag;
  38. import org.apache.ibatis.mapping.ResultMap;
  39. import org.apache.ibatis.mapping.ResultMapping;
  40. import org.apache.ibatis.mapping.ResultSetType;
  41. import org.apache.ibatis.mapping.SqlCommandType;
  42. import org.apache.ibatis.mapping.SqlSource;
  43. import org.apache.ibatis.mapping.StatementType;
  44. import org.apache.ibatis.reflection.MetaClass;
  45. import org.apache.ibatis.scripting.LanguageDriver;
  46. import org.apache.ibatis.session.Configuration;
  47. import org.apache.ibatis.type.JdbcType;
  48. import org.apache.ibatis.type.TypeHandler;

  49. /**
  50.  * @author Clinton Begin
  51.  */
  52. public class MapperBuilderAssistant extends BaseBuilder {

  53.   private String currentNamespace;
  54.   private final String resource;
  55.   private Cache currentCache;
  56.   private boolean unresolvedCacheRef; // issue #676

  57.   public MapperBuilderAssistant(Configuration configuration, String resource) {
  58.     super(configuration);
  59.     ErrorContext.instance().resource(resource);
  60.     this.resource = resource;
  61.   }

  62.   public String getCurrentNamespace() {
  63.     return currentNamespace;
  64.   }

  65.   public void setCurrentNamespace(String currentNamespace) {
  66.     if (currentNamespace == null) {
  67.       throw new BuilderException("The mapper element requires a namespace attribute to be specified.");
  68.     }

  69.     if (this.currentNamespace != null && !this.currentNamespace.equals(currentNamespace)) {
  70.       throw new BuilderException("Wrong namespace. Expected '"
  71.           + this.currentNamespace + "' but found '" + currentNamespace + "'.");
  72.     }

  73.     this.currentNamespace = currentNamespace;
  74.   }

  75.   public String applyCurrentNamespace(String base, boolean isReference) {
  76.     if (base == null) {
  77.       return null;
  78.     }
  79.     if (isReference) {
  80.       // is it qualified with any namespace yet?
  81.       if (base.contains(".")) {
  82.         return base;
  83.       }
  84.     } else {
  85.       // is it qualified with this namespace yet?
  86.       if (base.startsWith(currentNamespace + ".")) {
  87.         return base;
  88.       }
  89.       if (base.contains(".")) {
  90.         throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
  91.       }
  92.     }
  93.     return currentNamespace + "." + base;
  94.   }

  95.   public Cache useCacheRef(String namespace) {
  96.     if (namespace == null) {
  97.       throw new BuilderException("cache-ref element requires a namespace attribute.");
  98.     }
  99.     try {
  100.       unresolvedCacheRef = true;
  101.       Cache cache = configuration.getCache(namespace);
  102.       if (cache == null) {
  103.         throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
  104.       }
  105.       currentCache = cache;
  106.       unresolvedCacheRef = false;
  107.       return cache;
  108.     } catch (IllegalArgumentException e) {
  109.       throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
  110.     }
  111.   }

  112.   public Cache useNewCache(Class<? extends Cache> typeClass,
  113.       Class<? extends Cache> evictionClass,
  114.       Long flushInterval,
  115.       Integer size,
  116.       boolean readWrite,
  117.       boolean blocking,
  118.       Properties props) {
  119.     Cache cache = new CacheBuilder(currentNamespace)
  120.         .implementation(valueOrDefault(typeClass, PerpetualCache.class))
  121.         .addDecorator(valueOrDefault(evictionClass, LruCache.class))
  122.         .clearInterval(flushInterval)
  123.         .size(size)
  124.         .readWrite(readWrite)
  125.         .blocking(blocking)
  126.         .properties(props)
  127.         .build();
  128.     configuration.addCache(cache);
  129.     currentCache = cache;
  130.     return cache;
  131.   }

  132.   public ParameterMap addParameterMap(String id, Class<?> parameterClass, List<ParameterMapping> parameterMappings) {
  133.     id = applyCurrentNamespace(id, false);
  134.     ParameterMap parameterMap = new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings).build();
  135.     configuration.addParameterMap(parameterMap);
  136.     return parameterMap;
  137.   }

  138.   public ParameterMapping buildParameterMapping(
  139.       Class<?> parameterType,
  140.       String property,
  141.       Class<?> javaType,
  142.       JdbcType jdbcType,
  143.       String resultMap,
  144.       ParameterMode parameterMode,
  145.       Class<? extends TypeHandler<?>> typeHandler,
  146.       Integer numericScale) {
  147.     resultMap = applyCurrentNamespace(resultMap, true);

  148.     // Class parameterType = parameterMapBuilder.type();
  149.     Class<?> javaTypeClass = resolveParameterJavaType(parameterType, property, javaType, jdbcType);
  150.     TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);

  151.     return new ParameterMapping.Builder(configuration, property, javaTypeClass)
  152.         .jdbcType(jdbcType)
  153.         .resultMapId(resultMap)
  154.         .mode(parameterMode)
  155.         .numericScale(numericScale)
  156.         .typeHandler(typeHandlerInstance)
  157.         .build();
  158.   }

  159.   public ResultMap addResultMap(
  160.       String id,
  161.       Class<?> type,
  162.       String extend,
  163.       Discriminator discriminator,
  164.       List<ResultMapping> resultMappings,
  165.       Boolean autoMapping) {
  166.     id = applyCurrentNamespace(id, false);
  167.     extend = applyCurrentNamespace(extend, true);

  168.     if (extend != null) {
  169.       if (!configuration.hasResultMap(extend)) {
  170.         throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
  171.       }
  172.       ResultMap resultMap = configuration.getResultMap(extend);
  173.       List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
  174.       extendedResultMappings.removeAll(resultMappings);
  175.       // Remove parent constructor if this resultMap declares a constructor.
  176.       boolean declaresConstructor = false;
  177.       for (ResultMapping resultMapping : resultMappings) {
  178.         if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
  179.           declaresConstructor = true;
  180.           break;
  181.         }
  182.       }
  183.       if (declaresConstructor) {
  184.         extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
  185.       }
  186.       resultMappings.addAll(extendedResultMappings);
  187.     }
  188.     ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
  189.         .discriminator(discriminator)
  190.         .build();
  191.     configuration.addResultMap(resultMap);
  192.     return resultMap;
  193.   }

  194.   public Discriminator buildDiscriminator(
  195.       Class<?> resultType,
  196.       String column,
  197.       Class<?> javaType,
  198.       JdbcType jdbcType,
  199.       Class<? extends TypeHandler<?>> typeHandler,
  200.       Map<String, String> discriminatorMap) {
  201.     ResultMapping resultMapping = buildResultMapping(
  202.         resultType,
  203.         null,
  204.         column,
  205.         javaType,
  206.         jdbcType,
  207.         null,
  208.         null,
  209.         null,
  210.         null,
  211.         typeHandler,
  212.         new ArrayList<>(),
  213.         null,
  214.         null,
  215.         false);
  216.     Map<String, String> namespaceDiscriminatorMap = new HashMap<>();
  217.     for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
  218.       String resultMap = e.getValue();
  219.       resultMap = applyCurrentNamespace(resultMap, true);
  220.       namespaceDiscriminatorMap.put(e.getKey(), resultMap);
  221.     }
  222.     return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
  223.   }

  224.   public MappedStatement addMappedStatement(
  225.       String id,
  226.       SqlSource sqlSource,
  227.       StatementType statementType,
  228.       SqlCommandType sqlCommandType,
  229.       Integer fetchSize,
  230.       Integer timeout,
  231.       String parameterMap,
  232.       Class<?> parameterType,
  233.       String resultMap,
  234.       Class<?> resultType,
  235.       ResultSetType resultSetType,
  236.       boolean flushCache,
  237.       boolean useCache,
  238.       boolean resultOrdered,
  239.       KeyGenerator keyGenerator,
  240.       String keyProperty,
  241.       String keyColumn,
  242.       String databaseId,
  243.       LanguageDriver lang,
  244.       String resultSets) {

  245.     if (unresolvedCacheRef) {
  246.       throw new IncompleteElementException("Cache-ref not yet resolved");
  247.     }

  248.     id = applyCurrentNamespace(id, false);
  249.     boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

  250.     MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
  251.         .resource(resource)
  252.         .fetchSize(fetchSize)
  253.         .timeout(timeout)
  254.         .statementType(statementType)
  255.         .keyGenerator(keyGenerator)
  256.         .keyProperty(keyProperty)
  257.         .keyColumn(keyColumn)
  258.         .databaseId(databaseId)
  259.         .lang(lang)
  260.         .resultOrdered(resultOrdered)
  261.         .resultSets(resultSets)
  262.         .resultMaps(getStatementResultMaps(resultMap, resultType, id))
  263.         .resultSetType(resultSetType)
  264.         .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
  265.         .useCache(valueOrDefault(useCache, isSelect))
  266.         .cache(currentCache);

  267.     ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
  268.     if (statementParameterMap != null) {
  269.       statementBuilder.parameterMap(statementParameterMap);
  270.     }

  271.     MappedStatement statement = statementBuilder.build();
  272.     configuration.addMappedStatement(statement);
  273.     return statement;
  274.   }

  275.   /**
  276.    * Backward compatibility signature 'addMappedStatement'.
  277.    *
  278.    * @param id
  279.    *          the id
  280.    * @param sqlSource
  281.    *          the sql source
  282.    * @param statementType
  283.    *          the statement type
  284.    * @param sqlCommandType
  285.    *          the sql command type
  286.    * @param fetchSize
  287.    *          the fetch size
  288.    * @param timeout
  289.    *          the timeout
  290.    * @param parameterMap
  291.    *          the parameter map
  292.    * @param parameterType
  293.    *          the parameter type
  294.    * @param resultMap
  295.    *          the result map
  296.    * @param resultType
  297.    *          the result type
  298.    * @param resultSetType
  299.    *          the result set type
  300.    * @param flushCache
  301.    *          the flush cache
  302.    * @param useCache
  303.    *          the use cache
  304.    * @param resultOrdered
  305.    *          the result ordered
  306.    * @param keyGenerator
  307.    *          the key generator
  308.    * @param keyProperty
  309.    *          the key property
  310.    * @param keyColumn
  311.    *          the key column
  312.    * @param databaseId
  313.    *          the database id
  314.    * @param lang
  315.    *          the lang
  316.    * @return the mapped statement
  317.    */
  318.   public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
  319.       SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
  320.       String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
  321.       boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
  322.       LanguageDriver lang) {
  323.     return addMappedStatement(
  324.       id, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
  325.       parameterMap, parameterType, resultMap, resultType, resultSetType,
  326.       flushCache, useCache, resultOrdered, keyGenerator, keyProperty,
  327.       keyColumn, databaseId, lang, null);
  328.   }

  329.   private <T> T valueOrDefault(T value, T defaultValue) {
  330.     return value == null ? defaultValue : value;
  331.   }

  332.   private ParameterMap getStatementParameterMap(
  333.       String parameterMapName,
  334.       Class<?> parameterTypeClass,
  335.       String statementId) {
  336.     parameterMapName = applyCurrentNamespace(parameterMapName, true);
  337.     ParameterMap parameterMap = null;
  338.     if (parameterMapName != null) {
  339.       try {
  340.         parameterMap = configuration.getParameterMap(parameterMapName);
  341.       } catch (IllegalArgumentException e) {
  342.         throw new IncompleteElementException("Could not find parameter map " + parameterMapName, e);
  343.       }
  344.     } else if (parameterTypeClass != null) {
  345.       List<ParameterMapping> parameterMappings = new ArrayList<>();
  346.       parameterMap = new ParameterMap.Builder(
  347.           configuration,
  348.           statementId + "-Inline",
  349.           parameterTypeClass,
  350.           parameterMappings).build();
  351.     }
  352.     return parameterMap;
  353.   }

  354.   private List<ResultMap> getStatementResultMaps(
  355.       String resultMap,
  356.       Class<?> resultType,
  357.       String statementId) {
  358.     resultMap = applyCurrentNamespace(resultMap, true);

  359.     List<ResultMap> resultMaps = new ArrayList<>();
  360.     if (resultMap != null) {
  361.       String[] resultMapNames = resultMap.split(",");
  362.       for (String resultMapName : resultMapNames) {
  363.         try {
  364.           resultMaps.add(configuration.getResultMap(resultMapName.trim()));
  365.         } catch (IllegalArgumentException e) {
  366.           throw new IncompleteElementException("Could not find result map '" + resultMapName + "' referenced from '" + statementId + "'", e);
  367.         }
  368.       }
  369.     } else if (resultType != null) {
  370.       ResultMap inlineResultMap = new ResultMap.Builder(
  371.           configuration,
  372.           statementId + "-Inline",
  373.           resultType,
  374.           new ArrayList<>(),
  375.           null).build();
  376.       resultMaps.add(inlineResultMap);
  377.     }
  378.     return resultMaps;
  379.   }

  380.   public ResultMapping buildResultMapping(
  381.       Class<?> resultType,
  382.       String property,
  383.       String column,
  384.       Class<?> javaType,
  385.       JdbcType jdbcType,
  386.       String nestedSelect,
  387.       String nestedResultMap,
  388.       String notNullColumn,
  389.       String columnPrefix,
  390.       Class<? extends TypeHandler<?>> typeHandler,
  391.       List<ResultFlag> flags,
  392.       String resultSet,
  393.       String foreignColumn,
  394.       boolean lazy) {
  395.     Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
  396.     TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
  397.     List<ResultMapping> composites;
  398.     if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {
  399.       composites = Collections.emptyList();
  400.     } else {
  401.       composites = parseCompositeColumnName(column);
  402.     }
  403.     return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
  404.         .jdbcType(jdbcType)
  405.         .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
  406.         .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
  407.         .resultSet(resultSet)
  408.         .typeHandler(typeHandlerInstance)
  409.         .flags(flags == null ? new ArrayList<>() : flags)
  410.         .composites(composites)
  411.         .notNullColumns(parseMultipleColumnNames(notNullColumn))
  412.         .columnPrefix(columnPrefix)
  413.         .foreignColumn(foreignColumn)
  414.         .lazy(lazy)
  415.         .build();
  416.   }

  417.   /**
  418.    * Backward compatibility signature 'buildResultMapping'.
  419.    *
  420.    * @param resultType
  421.    *          the result type
  422.    * @param property
  423.    *          the property
  424.    * @param column
  425.    *          the column
  426.    * @param javaType
  427.    *          the java type
  428.    * @param jdbcType
  429.    *          the jdbc type
  430.    * @param nestedSelect
  431.    *          the nested select
  432.    * @param nestedResultMap
  433.    *          the nested result map
  434.    * @param notNullColumn
  435.    *          the not null column
  436.    * @param columnPrefix
  437.    *          the column prefix
  438.    * @param typeHandler
  439.    *          the type handler
  440.    * @param flags
  441.    *          the flags
  442.    * @return the result mapping
  443.    */
  444.   public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, Class<?> javaType,
  445.       JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix,
  446.       Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags) {
  447.     return buildResultMapping(
  448.       resultType, property, column, javaType, jdbcType, nestedSelect,
  449.       nestedResultMap, notNullColumn, columnPrefix, typeHandler, flags, null, null, configuration.isLazyLoadingEnabled());
  450.   }

  451.   /**
  452.    * Gets the language driver.
  453.    *
  454.    * @param langClass
  455.    *          the lang class
  456.    * @return the language driver
  457.    * @deprecated Use {@link Configuration#getLanguageDriver(Class)}
  458.    */
  459.   @Deprecated
  460.   public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
  461.     return configuration.getLanguageDriver(langClass);
  462.   }

  463.   private Set<String> parseMultipleColumnNames(String columnName) {
  464.     Set<String> columns = new HashSet<>();
  465.     if (columnName != null) {
  466.       if (columnName.indexOf(',') > -1) {
  467.         StringTokenizer parser = new StringTokenizer(columnName, "{}, ", false);
  468.         while (parser.hasMoreTokens()) {
  469.           String column = parser.nextToken();
  470.           columns.add(column);
  471.         }
  472.       } else {
  473.         columns.add(columnName);
  474.       }
  475.     }
  476.     return columns;
  477.   }

  478.   private List<ResultMapping> parseCompositeColumnName(String columnName) {
  479.     List<ResultMapping> composites = new ArrayList<>();
  480.     if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
  481.       StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false);
  482.       while (parser.hasMoreTokens()) {
  483.         String property = parser.nextToken();
  484.         String column = parser.nextToken();
  485.         ResultMapping complexResultMapping = new ResultMapping.Builder(
  486.             configuration, property, column, configuration.getTypeHandlerRegistry().getUnknownTypeHandler()).build();
  487.         composites.add(complexResultMapping);
  488.       }
  489.     }
  490.     return composites;
  491.   }

  492.   private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
  493.     if (javaType == null && property != null) {
  494.       try {
  495.         MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
  496.         javaType = metaResultType.getSetterType(property);
  497.       } catch (Exception e) {
  498.         // ignore, following null check statement will deal with the situation
  499.       }
  500.     }
  501.     if (javaType == null) {
  502.       javaType = Object.class;
  503.     }
  504.     return javaType;
  505.   }

  506.   private Class<?> resolveParameterJavaType(Class<?> resultType, String property, Class<?> javaType, JdbcType jdbcType) {
  507.     if (javaType == null) {
  508.       if (JdbcType.CURSOR.equals(jdbcType)) {
  509.         javaType = java.sql.ResultSet.class;
  510.       } else if (Map.class.isAssignableFrom(resultType)) {
  511.         javaType = Object.class;
  512.       } else {
  513.         MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
  514.         javaType = metaResultType.getGetterType(property);
  515.       }
  516.     }
  517.     if (javaType == null) {
  518.       javaType = Object.class;
  519.     }
  520.     return javaType;
  521.   }

  522. }