ConfigurationParser.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.messages.Messages.getString;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.jspecify.annotations.Nullable;
import org.mybatis.generator.codegen.XmlConstants;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.exception.XMLParserException;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
public class ConfigurationParser {
private final List<String> warnings = new ArrayList<>();
private final List<String> parseErrors = new ArrayList<>();
private final @Nullable Properties extraProperties;
public ConfigurationParser() {
this(null);
}
/**
* This constructor accepts a properties object which may be used to specify
* an additional property set. Typically, this property set will be Ant or Maven properties
* specified in the build.xml file or the POM.
*
* <p>If there are name collisions between the different property sets, they will be
* resolved in this order:
*
* <ol>
* <li>System properties take the highest precedence</li>
* <li>Properties specified in the <properties> configuration
* element are next</li>
* <li>Properties specified in this "extra" property set are
* the lowest precedence.</li>
* </ol>
*
* @param extraProperties an (optional) set of properties used to resolve property
* references in the configuration file
*/
public ConfigurationParser(@Nullable Properties extraProperties) {
this.extraProperties = extraProperties;
}
public List<String> getWarnings() {
return Collections.unmodifiableList(warnings);
}
public Configuration parseConfiguration(File inputFile) throws IOException, XMLParserException {
try (BufferedReader fr = Files.newBufferedReader(inputFile.toPath())) {
return parseConfiguration(fr);
}
}
public Configuration parseConfiguration(Reader reader) throws IOException, XMLParserException {
InputSource is = new InputSource(reader);
return parseConfiguration(is);
}
public Configuration parseConfiguration(InputStream inputStream) throws IOException, XMLParserException {
InputSource is = new InputSource(inputStream);
return parseConfiguration(is);
}
private Configuration parseConfiguration(InputSource inputSource) throws IOException, XMLParserException {
parseErrors.clear();
warnings.clear();
try {
Document document = basicParse(inputSource);
if (document == null || !parseErrors.isEmpty()) {
throw new XMLParserException(getString("RuntimeError.31"), parseErrors); //$NON-NLS-1$
}
Configuration config;
Element rootNode = document.getDocumentElement();
DocumentType docType = document.getDoctype();
if (rootNode.getNodeType() == Node.ELEMENT_NODE
&& docType.getPublicId().equals(XmlConstants.MYBATIS_GENERATOR_CONFIG_PUBLIC_ID)) {
config = parseMyBatisGeneratorConfiguration(rootNode);
} else {
throw new XMLParserException(getString("RuntimeError.5")); //$NON-NLS-1$
}
if (!parseErrors.isEmpty()) {
throw new XMLParserException(getString("RuntimeError.31"), parseErrors); //$NON-NLS-1$
}
return config;
} catch (ParserConfigurationException e) {
throw new XMLParserException(e.getMessage(), e, parseErrors);
}
}
private @Nullable Document basicParse(InputSource inputSource) throws IOException, ParserConfigurationException,
XMLParserException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); //$NON-NLS-1$
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); //$NON-NLS-1$
factory.setValidating(true);
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(new ParserEntityResolver());
ParserErrorHandler handler = new ParserErrorHandler(warnings, parseErrors);
builder.setErrorHandler(handler);
Document document = null;
try {
document = builder.parse(inputSource);
} catch (SAXParseException e) {
throw new XMLParserException(e.getMessage(), e, parseErrors);
} catch (SAXException e) {
parseErrors.add(e.getMessage());
if (e.getException() != null) {
parseErrors.add(e.getException().getMessage());
}
}
return document;
}
private Configuration parseMyBatisGeneratorConfiguration(Element rootNode) throws XMLParserException {
MyBatisGeneratorConfigurationParser parser = new MyBatisGeneratorConfigurationParser(extraProperties, warnings);
return parser.parseConfiguration(rootNode);
}
}