View Javadoc
1   /*
2    *    Copyright 2006-2026 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    *       https://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.mybatis.generator.config.xml;
17  
18  import static org.mybatis.generator.internal.util.messages.Messages.getString;
19  
20  import java.io.BufferedReader;
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.Reader;
25  import java.nio.file.Files;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.List;
29  import java.util.Properties;
30  import javax.xml.XMLConstants;
31  import javax.xml.parsers.DocumentBuilder;
32  import javax.xml.parsers.DocumentBuilderFactory;
33  import javax.xml.parsers.ParserConfigurationException;
34  
35  import org.jspecify.annotations.Nullable;
36  import org.mybatis.generator.codegen.XmlConstants;
37  import org.mybatis.generator.config.Configuration;
38  import org.mybatis.generator.exception.XMLParserException;
39  import org.w3c.dom.Document;
40  import org.w3c.dom.DocumentType;
41  import org.w3c.dom.Element;
42  import org.w3c.dom.Node;
43  import org.xml.sax.InputSource;
44  import org.xml.sax.SAXException;
45  import org.xml.sax.SAXParseException;
46  
47  public class ConfigurationParser {
48      private final List<String> warnings = new ArrayList<>();
49      private final List<String> parseErrors = new ArrayList<>();
50      private final @Nullable Properties extraProperties;
51  
52      public ConfigurationParser() {
53          this(null);
54      }
55  
56      /**
57       * This constructor accepts a properties object which may be used to specify
58       * an additional property set.  Typically, this property set will be Ant or Maven properties
59       * specified in the build.xml file or the POM.
60       *
61       * <p>If there are name collisions between the different property sets, they will be
62       * resolved in this order:
63       *
64       * <ol>
65       *   <li>System properties take the highest precedence</li>
66       *   <li>Properties specified in the &lt;properties&gt; configuration
67       *       element are next</li>
68       *   <li>Properties specified in this "extra" property set are
69       *       the lowest precedence.</li>
70       * </ol>
71       *
72       * @param extraProperties an (optional) set of properties used to resolve property
73       *     references in the configuration file
74       */
75      public ConfigurationParser(@Nullable Properties extraProperties) {
76          this.extraProperties = extraProperties;
77      }
78  
79      public List<String> getWarnings() {
80          return Collections.unmodifiableList(warnings);
81      }
82  
83      public Configuration parseConfiguration(File inputFile) throws IOException, XMLParserException {
84          try (BufferedReader fr = Files.newBufferedReader(inputFile.toPath())) {
85              return parseConfiguration(fr);
86          }
87      }
88  
89      public Configuration parseConfiguration(Reader reader) throws IOException, XMLParserException {
90          InputSource is = new InputSource(reader);
91          return parseConfiguration(is);
92      }
93  
94      public Configuration parseConfiguration(InputStream inputStream) throws IOException, XMLParserException {
95          InputSource is = new InputSource(inputStream);
96          return parseConfiguration(is);
97      }
98  
99      private Configuration parseConfiguration(InputSource inputSource) throws IOException, XMLParserException {
100         parseErrors.clear();
101         warnings.clear();
102 
103         try {
104             Document document = basicParse(inputSource);
105 
106             if (document == null || !parseErrors.isEmpty()) {
107                 throw new XMLParserException(getString("RuntimeError.31"), parseErrors); //$NON-NLS-1$
108             }
109 
110             Configuration config;
111             Element rootNode = document.getDocumentElement();
112             DocumentType docType = document.getDoctype();
113             if (rootNode.getNodeType() == Node.ELEMENT_NODE
114                     && docType.getPublicId().equals(XmlConstants.MYBATIS_GENERATOR_CONFIG_PUBLIC_ID)) {
115                 config = parseMyBatisGeneratorConfiguration(rootNode);
116             } else {
117                 throw new XMLParserException(getString("RuntimeError.5")); //$NON-NLS-1$
118             }
119 
120             if (!parseErrors.isEmpty()) {
121                 throw new XMLParserException(getString("RuntimeError.31"), parseErrors); //$NON-NLS-1$
122             }
123 
124             return config;
125         } catch (ParserConfigurationException e) {
126             throw new XMLParserException(e.getMessage(), e, parseErrors);
127         }
128     }
129 
130     private @Nullable Document basicParse(InputSource inputSource) throws IOException, ParserConfigurationException,
131             XMLParserException {
132         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
133         factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); //$NON-NLS-1$
134         factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); //$NON-NLS-1$
135         factory.setValidating(true);
136 
137         factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
138         DocumentBuilder builder = factory.newDocumentBuilder();
139         builder.setEntityResolver(new ParserEntityResolver());
140 
141         ParserErrorHandler handler = new ParserErrorHandler(warnings, parseErrors);
142         builder.setErrorHandler(handler);
143 
144         Document document = null;
145         try {
146             document = builder.parse(inputSource);
147         } catch (SAXParseException e) {
148             throw new XMLParserException(e.getMessage(), e, parseErrors);
149         } catch (SAXException e) {
150             parseErrors.add(e.getMessage());
151             if (e.getException() != null) {
152                 parseErrors.add(e.getException().getMessage());
153             }
154         }
155 
156         return document;
157     }
158 
159     private Configuration parseMyBatisGeneratorConfiguration(Element rootNode) throws XMLParserException {
160         MyBatisGeneratorConfigurationParser parser = new MyBatisGeneratorConfigurationParser(extraProperties, warnings);
161         return parser.parseConfiguration(rootNode);
162     }
163 }