MyBatisGeneratorConfigurationParser.java
/*
* Copyright 2006-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 org.mybatis.generator.config.xml;
import static org.mybatis.generator.internal.util.StringUtility.isTrue;
import static org.mybatis.generator.internal.util.StringUtility.parseNullableBoolean;
import static org.mybatis.generator.internal.util.StringUtility.trimToNull;
import static org.mybatis.generator.internal.util.messages.Messages.getString;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import org.jspecify.annotations.Nullable;
import org.mybatis.generator.config.ClientGeneratorConfiguration;
import org.mybatis.generator.config.ColumnOverride;
import org.mybatis.generator.config.ColumnRenamingRule;
import org.mybatis.generator.config.CommentGeneratorConfiguration;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.ConnectionFactoryConfiguration;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.DomainObjectRenamingRule;
import org.mybatis.generator.config.GeneratedKey;
import org.mybatis.generator.config.IgnoredColumn;
import org.mybatis.generator.config.IgnoredColumnException;
import org.mybatis.generator.config.IgnoredColumnPattern;
import org.mybatis.generator.config.JDBCConnectionConfiguration;
import org.mybatis.generator.config.JavaTypeResolverConfiguration;
import org.mybatis.generator.config.ModelGeneratorConfiguration;
import org.mybatis.generator.config.ModelType;
import org.mybatis.generator.config.NullableProperties;
import org.mybatis.generator.config.PluginConfiguration;
import org.mybatis.generator.config.Property;
import org.mybatis.generator.config.SqlMapGeneratorConfiguration;
import org.mybatis.generator.config.TableConfiguration;
import org.mybatis.generator.exception.XMLParserException;
import org.mybatis.generator.internal.ObjectFactory;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* This class parses configuration files into the new Configuration API.
*
* @author Jeff Butler
*/
public class MyBatisGeneratorConfigurationParser {
private final Properties extraProperties;
private final Properties configurationProperties;
private final List<String> warnings;
public MyBatisGeneratorConfigurationParser(@Nullable Properties extraProperties, List<String> warnings) {
this.extraProperties = Objects.requireNonNullElseGet(extraProperties, Properties::new);
configurationProperties = new Properties();
this.warnings = warnings;
}
public Configuration parseConfiguration(Element rootNode) throws XMLParserException {
Configuration configuration = new Configuration();
NodeList nodeList = rootNode.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node childNode = nodeList.item(i);
if (childNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
switch (childNode.getNodeName()) {
case "properties" -> //$NON-NLS-1$
parsePropertiesElement(childNode);
case "classPathEntry" -> //$NON-NLS-1$
configuration.addClasspathEntry(parseClassPathEntry(childNode));
case "context" -> //$NON-NLS-1$
configuration.addContext(parseContext(childNode));
default -> {
// Ignore unrecognized elements
}
}
}
return configuration;
}
protected void parsePropertiesElement(Node node) throws XMLParserException {
NullableProperties attributes = parseAttributes(node);
String resource = attributes.getProperty("resource"); //$NON-NLS-1$
String url = attributes.getProperty("url"); //$NON-NLS-1$
if (resource == null && url == null) {
throw new XMLParserException(getString("RuntimeError.14")); //$NON-NLS-1$
}
if (resource != null && url != null) {
throw new XMLParserException(getString("RuntimeError.14")); //$NON-NLS-1$
}
if (resource != null) {
loadPropertiesFromResource(resource);
} else {
loadPropertiesFromURL(url);
}
}
private void loadPropertiesFromResource(String resource) throws XMLParserException {
try {
URL resourceUrl = ObjectFactory.getResource(resource)
.orElseThrow(() -> new XMLParserException(getString("RuntimeError.15", resource)));
InputStream inputStream = resourceUrl.openConnection().getInputStream();
configurationProperties.load(inputStream);
inputStream.close();
} catch (IOException e) {
throw new XMLParserException(getString("RuntimeError.16", resource)); //$NON-NLS-1$
}
}
private void loadPropertiesFromURL(String url) throws XMLParserException {
try {
URL resourceUrl = URI.create(url).toURL();
InputStream inputStream = resourceUrl.openConnection().getInputStream();
configurationProperties.load(inputStream);
inputStream.close();
} catch (IOException e) {
throw new XMLParserException(getString("RuntimeError.17", url)); //$NON-NLS-1$
}
}
private Context parseContext(Node node) {
NullableProperties attributes = parseAttributes(node);
String defaultModelType = attributes.getProperty("defaultModelType"); //$NON-NLS-1$
String targetRuntime = attributes.getProperty("targetRuntime"); //$NON-NLS-1$
String introspectedColumnImpl = attributes.getProperty("introspectedColumnImpl"); //$NON-NLS-1$
String id = attributes.getProperty("id"); //$NON-NLS-1$
assert id != null;
ModelType dmt =
defaultModelType == null ? null : ModelType.getModelType(defaultModelType);
Context.Builder builder = new Context.Builder()
.withId(id)
.withDefaultModelType(dmt)
.withIntrospectedColumnImpl(introspectedColumnImpl)
.withTargetRuntime(targetRuntime);
NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node childNode = nodeList.item(i);
if (childNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
switch (childNode.getNodeName()) {
case "property" -> //$NON-NLS-1$
parseProperty(childNode).ifPresent(builder::withProperty);
case "plugin" -> //$NON-NLS-1$
builder.withPluginConfiguration(parsePlugin(childNode));
case "commentGenerator" -> //$NON-NLS-1$
builder.withCommentGeneratorConfiguration(parseCommentGenerator(childNode));
case "jdbcConnection" -> //$NON-NLS-1$
builder.withJdbcConnectionConfiguration(parseJdbcConnection(childNode));
case "connectionFactory" -> //$NON-NLS-1$
builder.withConnectionFactoryConfiguration(parseConnectionFactory(childNode));
case "modelGenerator" -> //$NON-NLS-1$
builder.withModelGeneratorConfiguration(parseModelGenerator(childNode));
case "javaModelGenerator" -> { //$NON-NLS-1$
warnings.add(getString("Warning.33")); //$NON-NLS-1$
builder.withModelGeneratorConfiguration(parseModelGenerator(childNode));
}
case "javaTypeResolver" -> //$NON-NLS-1$
builder.withJavaTypeResolverConfiguration(parseJavaTypeResolver(childNode));
case "sqlMapGenerator" -> //$NON-NLS-1$
builder.withSqlMapGeneratorConfiguration(parseSqlMapGenerator(childNode));
case "clientGenerator" -> //$NON-NLS-1$
builder.withClientGeneratorConfiguration(parseClientGenerator(childNode, id));
case "javaClientGenerator" -> { //$NON-NLS-1$
warnings.add(getString("Warning.34")); //$NON-NLS-1$
builder.withClientGeneratorConfiguration(parseClientGenerator(childNode, id));
}
case "table" -> //$NON-NLS-1$
builder.withTableConfiguration(parseTable(childNode));
default -> {
// Ignore unrecognized elements
}
}
}
return builder.build();
}
protected SqlMapGeneratorConfiguration parseSqlMapGenerator(Node node) {
NullableProperties attributes = parseAttributes(node);
String targetPackage = attributes.getProperty("targetPackage"); //$NON-NLS-1$
String targetProject = attributes.getProperty("targetProject"); //$NON-NLS-1$
Properties properties = parseProperties(node.getChildNodes());
return new SqlMapGeneratorConfiguration.Builder()
.withTargetPackage(targetPackage)
.withTargetProject(targetProject)
.withProperties(properties)
.build();
}
protected TableConfiguration parseTable(Node node) {
NullableProperties attributes = parseAttributes(node);
String catalog = attributes.getProperty("catalog"); //$NON-NLS-1$
String schema = attributes.getProperty("schema"); //$NON-NLS-1$
String tableName = attributes.getProperty("tableName"); //$NON-NLS-1$
String domainObjectName = attributes.getProperty("domainObjectName"); //$NON-NLS-1$
String alias = attributes.getProperty("alias"); //$NON-NLS-1$
String modelType = attributes.getProperty("modelType"); //$NON-NLS-1$
String mapperName = attributes.getProperty("mapperName"); //$NON-NLS-1$
String sqlProviderName = attributes.getProperty("sqlProviderName"); //$NON-NLS-1$
TableConfiguration.Builder builder = new TableConfiguration.Builder()
.withModelType(modelType)
.withCatalog(catalog)
.withSchema(schema)
.withTableName(tableName)
.withDomainObjectName(domainObjectName)
.withAlias(alias)
.withMapperName(mapperName)
.withSqlProviderName(sqlProviderName);
String enableInsert = attributes.getProperty("enableInsert"); //$NON-NLS-1$
if (enableInsert != null) {
builder.withInsertStatementEnabled(isTrue(enableInsert));
}
String enableSelectByPrimaryKey = attributes.getProperty("enableSelectByPrimaryKey"); //$NON-NLS-1$
if (enableSelectByPrimaryKey != null) {
builder.withSelectByPrimaryKeyStatementEnabled(isTrue(enableSelectByPrimaryKey));
}
String enableSelectByExample = attributes.getProperty("enableSelectByExample"); //$NON-NLS-1$
if (enableSelectByExample != null) {
builder.withSelectByExampleStatementEnabled(isTrue(enableSelectByExample));
}
String enableUpdateByPrimaryKey = attributes.getProperty("enableUpdateByPrimaryKey"); //$NON-NLS-1$
if (enableUpdateByPrimaryKey != null) {
builder.withUpdateByPrimaryKeyStatementEnabled(isTrue(enableUpdateByPrimaryKey));
}
String enableDeleteByPrimaryKey = attributes.getProperty("enableDeleteByPrimaryKey"); //$NON-NLS-1$
if (enableDeleteByPrimaryKey != null) {
builder.withDeleteByPrimaryKeyStatementEnabled(isTrue(enableDeleteByPrimaryKey));
}
String enableDeleteByExample = attributes.getProperty("enableDeleteByExample"); //$NON-NLS-1$
if (enableDeleteByExample != null) {
builder.withDeleteByExampleStatementEnabled(isTrue(enableDeleteByExample));
}
String enableCountByExample = attributes.getProperty("enableCountByExample"); //$NON-NLS-1$
if (enableCountByExample != null) {
builder.withCountByExampleStatementEnabled(isTrue(enableCountByExample));
}
String enableUpdateByExample = attributes.getProperty("enableUpdateByExample"); //$NON-NLS-1$
if (enableUpdateByExample != null) {
builder.withUpdateByExampleStatementEnabled(isTrue(enableUpdateByExample));
}
String escapeWildcards = attributes.getProperty("escapeWildcards"); //$NON-NLS-1$
if (escapeWildcards != null) {
builder.withWildcardEscapingEnabled(isTrue(escapeWildcards));
}
String delimitIdentifiers = attributes.getProperty("delimitIdentifiers"); //$NON-NLS-1$
if (delimitIdentifiers != null) {
builder.withDelimitIdentifiers(isTrue(delimitIdentifiers));
}
String delimitAllColumns = attributes.getProperty("delimitAllColumns"); //$NON-NLS-1$
if (delimitAllColumns != null) {
builder.withAllColumnDelimitingEnabled(isTrue(delimitAllColumns));
}
NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node childNode = nodeList.item(i);
if (childNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
switch (childNode.getNodeName()) {
case "property" -> //$NON-NLS-1$
parseProperty(childNode).ifPresent(builder::withProperty);
case "columnOverride" -> //$NON-NLS-1$
builder.withColumnOverride(parseColumnOverride(childNode));
case "ignoreColumn" -> //$NON-NLS-1$
builder.withIgnoredColumn(parseIgnoreColumn(childNode));
case "ignoreColumnsByRegex" -> //$NON-NLS-1$
builder.withIgnoredColumnPattern(parseIgnoreColumnByRegex(childNode));
case "generatedKey" -> //$NON-NLS-1$
builder.withGeneratedKey(parseGeneratedKey(childNode));
case "domainObjectRenamingRule" -> //$NON-NLS-1$
builder.withDomainObjectRenamingRule(parseDomainObjectRenamingRule(childNode));
case "columnRenamingRule" -> //$NON-NLS-1$
builder.withColumnRenamingRule(parseColumnRenamingRule(childNode));
default -> {
// Ignore unrecognized elements
}
}
}
return builder.build();
}
private ColumnOverride parseColumnOverride(Node node) {
NullableProperties attributes = parseAttributes(node);
String column = attributes.getProperty("column"); //$NON-NLS-1$
String javaProperty = attributes.getProperty("property"); //$NON-NLS-1$
String javaType = attributes.getProperty("javaType"); //$NON-NLS-1$
String jdbcType = attributes.getProperty("jdbcType"); //$NON-NLS-1$
String typeHandler = attributes.getProperty("typeHandler"); //$NON-NLS-1$
String delimitedColumnName = attributes.getProperty("delimitedColumnName"); //$NON-NLS-1$
String isGeneratedAlways = attributes.getProperty("isGeneratedAlways"); //$NON-NLS-1$
Properties properties = parseProperties(node.getChildNodes());
return new ColumnOverride.Builder()
.withColumnName(column)
.withJavaProperty(javaProperty)
.withJavaType(javaType)
.withJdbcType(jdbcType)
.withTypeHandler(typeHandler)
.withColumnNameDelimited(parseNullableBoolean(delimitedColumnName))
.withGeneratedAlways(isTrue(isGeneratedAlways))
.withProperties(properties)
.build();
}
private GeneratedKey parseGeneratedKey(Node node) {
NullableProperties attributes = parseAttributes(node);
String column = attributes.getProperty("column"); //$NON-NLS-1$
boolean identity = isTrue(attributes.getProperty("identity")); //$NON-NLS-1$
String sqlStatement = attributes.getProperty("sqlStatement"); //$NON-NLS-1$
return new GeneratedKey(column, sqlStatement, identity);
}
private IgnoredColumn parseIgnoreColumn(Node node) {
NullableProperties attributes = parseAttributes(node);
String column = attributes.getProperty("column"); //$NON-NLS-1$
String delimitedColumnName = attributes.getProperty("delimitedColumnName"); //$NON-NLS-1$
return new IgnoredColumn(column, isTrue(delimitedColumnName));
}
private IgnoredColumnPattern parseIgnoreColumnByRegex(Node node) {
NullableProperties attributes = parseAttributes(node);
String pattern = attributes.getProperty("pattern"); //$NON-NLS-1$
IgnoredColumnPattern.Builder builder = new IgnoredColumnPattern.Builder().withPattern(pattern);
NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node childNode = nodeList.item(i);
if (childNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
if ("except".equals(childNode.getNodeName())) {
builder.addException(parseException(childNode));
}
}
return builder.build();
}
private IgnoredColumnException parseException(Node node) {
NullableProperties attributes = parseAttributes(node);
String column = attributes.getProperty("column"); //$NON-NLS-1$
String delimitedColumnName = attributes.getProperty("delimitedColumnName"); //$NON-NLS-1$
return new IgnoredColumnException(column, isTrue(delimitedColumnName));
}
private DomainObjectRenamingRule parseDomainObjectRenamingRule(Node node) {
NullableProperties attributes = parseAttributes(node);
String searchString = attributes.getProperty("searchString"); //$NON-NLS-1$
String replaceString = attributes.getProperty("replaceString"); //$NON-NLS-1$
return new DomainObjectRenamingRule(searchString, replaceString);
}
private ColumnRenamingRule parseColumnRenamingRule(Node node) {
NullableProperties attributes = parseAttributes(node);
String searchString = attributes.getProperty("searchString"); //$NON-NLS-1$
String replaceString = attributes.getProperty("replaceString"); //$NON-NLS-1$
return new ColumnRenamingRule(searchString, replaceString);
}
protected JavaTypeResolverConfiguration parseJavaTypeResolver(Node node) {
NullableProperties attributes = parseAttributes(node);
String type = attributes.getProperty("type"); //$NON-NLS-1$
Properties properties = parseProperties(node.getChildNodes());
return new JavaTypeResolverConfiguration.Builder()
.withConfigurationType(type)
.withProperties(properties)
.build();
}
private PluginConfiguration parsePlugin(Node node) {
NullableProperties attributes = parseAttributes(node);
String type = attributes.getProperty("type"); //$NON-NLS-1$
Properties properties = parseProperties(node.getChildNodes());
return new PluginConfiguration.Builder()
.withConfigurationType(type)
.withProperties(properties)
.build();
}
protected ModelGeneratorConfiguration parseModelGenerator(Node node) {
NullableProperties attributes = parseAttributes(node);
String targetPackage = attributes.getProperty("targetPackage"); //$NON-NLS-1$
String targetProject = attributes.getProperty("targetProject"); //$NON-NLS-1$
Properties properties = parseProperties(node.getChildNodes());
return new ModelGeneratorConfiguration.Builder()
.withTargetPackage(targetPackage)
.withTargetProject(targetProject)
.withProperties(properties)
.build();
}
private ClientGeneratorConfiguration parseClientGenerator(Node node, String contextId) {
NullableProperties attributes = parseAttributes(node);
String type = attributes.getProperty("type"); //$NON-NLS-1$
String targetPackage = attributes.getProperty("targetPackage"); //$NON-NLS-1$
String targetProject = attributes.getProperty("targetProject"); //$NON-NLS-1$
Properties properties = parseProperties(node.getChildNodes());
ClientGeneratorConfiguration.LegacyClientType legacyClientType = null;
if (type != null) {
legacyClientType = ClientGeneratorConfiguration.LegacyClientType.getByAlias(type);
if (legacyClientType == null) {
warnings.add(getString("ValidationError.31", type, contextId)); //$NON-NLS-1$
}
}
return new ClientGeneratorConfiguration.Builder()
.withLegacyClientType(legacyClientType)
.withTargetPackage(targetPackage)
.withTargetProject(targetProject)
.withProperties(properties)
.build();
}
protected JDBCConnectionConfiguration parseJdbcConnection(Node node) {
NullableProperties attributes = parseAttributes(node);
String driverClass = attributes.getProperty("driverClass"); //$NON-NLS-1$
String connectionURL = attributes.getProperty("connectionURL"); //$NON-NLS-1$
String userId = attributes.getProperty("userId"); //$NON-NLS-1$
String password = attributes.getProperty("password"); //$NON-NLS-1$
Properties properties = parseProperties(node.getChildNodes());
return new JDBCConnectionConfiguration.Builder()
.withDriverClass(driverClass)
.withConnectionURL(connectionURL)
.withUserId(userId)
.withPassword(password)
.withProperties(properties)
.build();
}
protected @Nullable String parseClassPathEntry(Node node) {
NullableProperties attributes = parseAttributes(node);
return attributes.getProperty("location"); //$NON-NLS-1$
}
protected Properties parseProperties(NodeList nodeList) {
Properties properties = new Properties();
for (int i = 0; i < nodeList.getLength(); i++) {
Node childNode = nodeList.item(i);
if (childNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
if ("property".equals(childNode.getNodeName())) { //$NON-NLS-1$
parseProperty(childNode).ifPresent(p -> properties.setProperty(p.name(), p.value()));
}
}
return properties;
}
protected Optional<Property> parseProperty(Node node) {
NullableProperties attributes = parseAttributes(node);
String name = attributes.getProperty("name"); //$NON-NLS-1$
String value = attributes.getProperty("value"); //$NON-NLS-1$
if (name == null || value == null) {
return Optional.empty();
} else {
return Optional.of(new Property(name, value));
}
}
/**
* Parses node attributes.
*
* <p>Any attribute with an empty value (defined as missing, or blank string) will be dropped.
*
* @param node the node
* @return properties containing all non-empty attributes
*/
protected NullableProperties parseAttributes(Node node) {
NullableProperties attributes = new NullableProperties();
NamedNodeMap nnm = node.getAttributes();
for (int i = 0; i < nnm.getLength(); i++) {
Node attribute = nnm.item(i);
String value = parsePropertyTokens(attribute.getNodeValue());
attributes.put(attribute.getNodeName(), trimToNull(value));
}
return attributes;
}
String parsePropertyTokens(String s) {
final String OPEN = "${"; //$NON-NLS-1$
final String CLOSE = "}"; //$NON-NLS-1$
int currentIndex = 0;
List<String> answer = new ArrayList<>();
int markerStartIndex = s.indexOf(OPEN);
if (markerStartIndex < 0) {
// no parameter markers
answer.add(s);
currentIndex = s.length();
}
while (markerStartIndex > -1) {
if (markerStartIndex > currentIndex) {
// add the characters before the next parameter marker
answer.add(s.substring(currentIndex, markerStartIndex));
currentIndex = markerStartIndex;
}
int markerEndIndex = s.indexOf(CLOSE, currentIndex);
int nestedStartIndex = s.indexOf(OPEN, markerStartIndex + OPEN.length());
while (nestedStartIndex > -1 && markerEndIndex > -1 && nestedStartIndex < markerEndIndex) {
nestedStartIndex = s.indexOf(OPEN, nestedStartIndex + OPEN.length());
markerEndIndex = s.indexOf(CLOSE, markerEndIndex + CLOSE.length());
}
if (markerEndIndex < 0) {
// no closing delimiter, just move to the end of the string
answer.add(s.substring(markerStartIndex));
currentIndex = s.length();
break;
}
// we have a valid property marker...
String property = s.substring(markerStartIndex + OPEN.length(), markerEndIndex);
String propertyValue = resolveProperty(parsePropertyTokens(property));
if (propertyValue == null) {
// add the property marker back into the stream
answer.add(s.substring(markerStartIndex, markerEndIndex + 1));
} else {
answer.add(propertyValue);
}
currentIndex = markerEndIndex + CLOSE.length();
markerStartIndex = s.indexOf(OPEN, currentIndex);
}
if (currentIndex < s.length()) {
answer.add(s.substring(currentIndex));
}
return String.join("", answer);
}
protected CommentGeneratorConfiguration parseCommentGenerator(Node node) {
NullableProperties attributes = parseAttributes(node);
String type = attributes.getProperty("type"); //$NON-NLS-1$
Properties properties = parseProperties(node.getChildNodes());
return new CommentGeneratorConfiguration.Builder()
.withConfigurationType(type)
.withProperties(properties)
.build();
}
protected ConnectionFactoryConfiguration parseConnectionFactory(Node node) {
NullableProperties attributes = parseAttributes(node);
String type = attributes.getProperty("type"); //$NON-NLS-1$
Properties properties = parseProperties(node.getChildNodes());
return new ConnectionFactoryConfiguration.Builder()
.withConfigurationType(type)
.withProperties(properties)
.build();
}
/**
* This method resolves a property from one of the three sources: system properties,
* properties loaded from the <properties> configuration element, and
* "extra" properties that may be supplied by the Maven or Ant environments.
*
* <p>If there is a name collision, system properties take precedence, followed by
* configuration properties, followed by extra properties.
*
* @param key property key
* @return the resolved property. This method will return null if the property is
* undefined in any of the sources.
*/
private @Nullable String resolveProperty(String key) {
String property = System.getProperty(key);
if (property == null) {
property = configurationProperties.getProperty(key);
}
if (property == null) {
property = extraProperties.getProperty(key);
}
return property;
}
}