XmlMapperBuilderTest.java

/*
 *    Copyright 2009-2025 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 org.apache.ibatis.builder;

import static com.googlecode.catchexception.apis.BDDCatchException.caughtException;
import static com.googlecode.catchexception.apis.BDDCatchException.when;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.BDDAssertions.then;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;

import java.io.InputStream;
import java.util.regex.Pattern;

import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultSetType;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandler;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class XmlMapperBuilderTest {

  @Test
  void shouldSuccessfullyLoadXMLMapperFile() {
    assertDoesNotThrow(() -> {
      Configuration configuration = new Configuration();
      String resource = "org/apache/ibatis/builder/AuthorMapper.xml";
      try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
        XMLMapperBuilder builder = new XMLMapperBuilder(inputStream, configuration, resource,
            configuration.getSqlFragments());
        builder.parse();
      }
    });
  }

  @Test
  void mappedStatementWithOptions() throws Exception {
    Configuration configuration = new Configuration();
    String resource = "org/apache/ibatis/builder/AuthorMapper.xml";
    try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
      XMLMapperBuilder builder = new XMLMapperBuilder(inputStream, configuration, resource,
          configuration.getSqlFragments());
      builder.parse();

      MappedStatement mappedStatement = configuration.getMappedStatement("selectWithOptions");
      assertThat(mappedStatement.getFetchSize()).isEqualTo(200);
      assertThat(mappedStatement.getTimeout()).isEqualTo(10);
      assertThat(mappedStatement.getStatementType()).isEqualTo(StatementType.PREPARED);
      assertThat(mappedStatement.getResultSetType()).isEqualTo(ResultSetType.SCROLL_SENSITIVE);
      assertThat(mappedStatement.isFlushCacheRequired()).isFalse();
      assertThat(mappedStatement.isUseCache()).isFalse();
    }
  }

  @Test
  void mappedStatementWithoutOptionsWhenSpecifyDefaultValue() throws Exception {
    Configuration configuration = new Configuration();
    configuration.setDefaultResultSetType(ResultSetType.SCROLL_INSENSITIVE);
    String resource = "org/apache/ibatis/builder/AuthorMapper.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    XMLMapperBuilder builder = new XMLMapperBuilder(inputStream, configuration, resource,
        configuration.getSqlFragments());
    builder.parse();
    inputStream.close();

    MappedStatement mappedStatement = configuration.getMappedStatement("selectAuthor");
    assertThat(mappedStatement.getResultSetType()).isEqualTo(ResultSetType.SCROLL_INSENSITIVE);
  }

  @Test
  void parseExpression() {
    BaseBuilder builder = new BaseBuilder(new Configuration()) {
    };
    {
      Pattern pattern = builder.parseExpression("[0-9]", "[a-z]");
      assertThat(pattern.matcher("0").find()).isTrue();
      assertThat(pattern.matcher("a").find()).isFalse();
    }
    {
      Pattern pattern = builder.parseExpression(null, "[a-z]");
      assertThat(pattern.matcher("0").find()).isFalse();
      assertThat(pattern.matcher("a").find()).isTrue();
    }
  }

  @Test
  void resolveJdbcTypeWithUndefinedValue() {
    BaseBuilder builder = new BaseBuilder(new Configuration()) {
    };
    when(() -> builder.resolveJdbcType("aaa"));
    then(caughtException()).isInstanceOf(BuilderException.class)
        .hasMessageStartingWith("Error resolving JdbcType. Cause: java.lang.IllegalArgumentException: No enum")
        .hasMessageEndingWith("org.apache.ibatis.type.JdbcType.aaa");
  }

  @Test
  void resolveResultSetTypeWithUndefinedValue() {
    BaseBuilder builder = new BaseBuilder(new Configuration()) {
    };
    when(() -> builder.resolveResultSetType("bbb"));
    then(caughtException()).isInstanceOf(BuilderException.class)
        .hasMessageStartingWith("Error resolving ResultSetType. Cause: java.lang.IllegalArgumentException: No enum")
        .hasMessageEndingWith("org.apache.ibatis.mapping.ResultSetType.bbb");
  }

  @Test
  void resolveParameterModeWithUndefinedValue() {
    BaseBuilder builder = new BaseBuilder(new Configuration()) {
    };
    when(() -> builder.resolveParameterMode("ccc"));
    then(caughtException()).isInstanceOf(BuilderException.class)
        .hasMessageStartingWith("Error resolving ParameterMode. Cause: java.lang.IllegalArgumentException: No enum")
        .hasMessageEndingWith("org.apache.ibatis.mapping.ParameterMode.ccc");
  }

  @Test
  void createInstanceWithAbstractClass() {
    BaseBuilder builder = new BaseBuilder(new Configuration()) {
    };
    when(() -> builder.createInstance("org.apache.ibatis.builder.BaseBuilder"));
    then(caughtException()).isInstanceOf(BuilderException.class).hasMessage(
        "Error creating instance. Cause: java.lang.NoSuchMethodException: org.apache.ibatis.builder.BaseBuilder.<init>()");
  }

  @Test
  void resolveClassWithNotFound() {
    BaseBuilder builder = new BaseBuilder(new Configuration()) {
    };
    when(() -> builder.resolveClass("ddd"));
    then(caughtException()).isInstanceOf(BuilderException.class).hasMessage(
        "Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'ddd'.  Cause: java.lang.ClassNotFoundException: Cannot find class: ddd");
  }

  @Test
  void resolveTypeHandlerTypeHandlerAliasIsNull() {
    BaseBuilder builder = new BaseBuilder(new Configuration()) {
      {
      }
    };
    TypeHandler<?> typeHandler = builder.resolveTypeHandler(String.class, null, null, (String) null);
    assertThat(typeHandler).isNull();
  }

  @Test
  void resolveTypeHandlerNoAssignable() {
    BaseBuilder builder = new BaseBuilder(new Configuration()) {
      {
      }
    };
    when(() -> builder.resolveTypeHandler(String.class, null, null, "integer"));
    then(caughtException()).isInstanceOf(BuilderException.class).hasMessage(
        "Type java.lang.Integer is not a valid TypeHandler because it does not implement TypeHandler interface");
  }

  @Test
  void setCurrentNamespaceValueIsNull() {
    MapperBuilderAssistant builder = new MapperBuilderAssistant(new Configuration(), "resource");
    when(() -> builder.setCurrentNamespace(null));
    then(caughtException()).isInstanceOf(BuilderException.class)
        .hasMessage("The mapper element requires a namespace attribute to be specified.");
  }

  @Test
  void useCacheRefNamespaceIsNull() {
    MapperBuilderAssistant builder = new MapperBuilderAssistant(new Configuration(), "resource");
    when(() -> builder.useCacheRef(null));
    then(caughtException()).isInstanceOf(BuilderException.class)
        .hasMessage("cache-ref element requires a namespace attribute.");
  }

  @Test
  void useCacheRefNamespaceIsUndefined() {
    MapperBuilderAssistant builder = new MapperBuilderAssistant(new Configuration(), "resource");
    when(() -> builder.useCacheRef("eee"));
    then(caughtException()).hasMessage("No cache for namespace 'eee' could be found.");
  }

  @Test
  void shouldFailedLoadXMLMapperFile() throws Exception {
    Configuration configuration = new Configuration();
    String resource = "org/apache/ibatis/builder/ProblemMapper.xml";
    try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
      XMLMapperBuilder builder = new XMLMapperBuilder(inputStream, configuration, resource,
          configuration.getSqlFragments());
      Exception exception = Assertions.assertThrows(BuilderException.class, builder::parse);
      Assertions.assertTrue(exception.getMessage()
          .contains("Error parsing Mapper XML. The XML location is 'org/apache/ibatis/builder/ProblemMapper.xml'"));
    }
  }

  // @Test
  // public void shouldNotLoadTheSameNamespaceFromTwoResourcesWithDifferentNames() throws Exception {
  // Configuration configuration = new Configuration();
  // String resource = "org/apache/ibatis/builder/AuthorMapper.xml";
  // InputStream inputStream = Resources.getResourceAsStream(resource);
  // XMLMapperBuilder builder = new XMLMapperBuilder(inputStream, configuration, "name1",
  // configuration.getSqlFragments());
  // builder.parse();
  // InputStream inputStream2 = Resources.getResourceAsStream(resource);
  // XMLMapperBuilder builder2 = new XMLMapperBuilder(inputStream2, configuration, "name2",
  // configuration.getSqlFragments());
  // builder2.parse();
  // }

  @Test
  void errorResultMapLocation() throws Exception {
    Configuration configuration = new Configuration();
    String resource = "org/apache/ibatis/builder/ProblemResultMapper.xml";
    try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
      XMLMapperBuilder builder = new XMLMapperBuilder(inputStream, configuration, resource,
          configuration.getSqlFragments());
      builder.parse();
      String resultMapName = "java.lang.String";
      // namespace + "." + id
      String statementId = "org.mybatis.spring.ErrorProblemMapper.findProblemResultMapTest";
      // same as MapperBuilderAssistant.getStatementResultMaps Exception message
      String message = "Could not find result map '" + resultMapName + "' referenced from '" + statementId + "'";
      IncompleteElementException exception = Assertions.assertThrows(IncompleteElementException.class,
          () -> configuration.getMappedStatement("findProblemTypeTest"));
      assertThat(exception.getMessage()).isEqualTo(message);
    }
  }
}