1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.mybatis.generator.config.xml;
17
18 import static org.mybatis.generator.internal.util.StringUtility.isTrue;
19 import static org.mybatis.generator.internal.util.StringUtility.parseNullableBoolean;
20 import static org.mybatis.generator.internal.util.StringUtility.trimToNull;
21 import static org.mybatis.generator.internal.util.messages.Messages.getString;
22
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.net.URI;
26 import java.net.URL;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.Objects;
30 import java.util.Optional;
31 import java.util.Properties;
32
33 import org.jspecify.annotations.Nullable;
34 import org.mybatis.generator.config.ClientGeneratorConfiguration;
35 import org.mybatis.generator.config.ColumnOverride;
36 import org.mybatis.generator.config.ColumnRenamingRule;
37 import org.mybatis.generator.config.CommentGeneratorConfiguration;
38 import org.mybatis.generator.config.Configuration;
39 import org.mybatis.generator.config.ConnectionFactoryConfiguration;
40 import org.mybatis.generator.config.Context;
41 import org.mybatis.generator.config.DomainObjectRenamingRule;
42 import org.mybatis.generator.config.GeneratedKey;
43 import org.mybatis.generator.config.IgnoredColumn;
44 import org.mybatis.generator.config.IgnoredColumnException;
45 import org.mybatis.generator.config.IgnoredColumnPattern;
46 import org.mybatis.generator.config.JDBCConnectionConfiguration;
47 import org.mybatis.generator.config.JavaTypeResolverConfiguration;
48 import org.mybatis.generator.config.ModelGeneratorConfiguration;
49 import org.mybatis.generator.config.ModelType;
50 import org.mybatis.generator.config.NullableProperties;
51 import org.mybatis.generator.config.PluginConfiguration;
52 import org.mybatis.generator.config.Property;
53 import org.mybatis.generator.config.SqlMapGeneratorConfiguration;
54 import org.mybatis.generator.config.TableConfiguration;
55 import org.mybatis.generator.exception.XMLParserException;
56 import org.mybatis.generator.internal.ObjectFactory;
57 import org.w3c.dom.Element;
58 import org.w3c.dom.NamedNodeMap;
59 import org.w3c.dom.Node;
60 import org.w3c.dom.NodeList;
61
62
63
64
65
66
67 public class MyBatisGeneratorConfigurationParser {
68 private final Properties extraProperties;
69 private final Properties configurationProperties;
70 private final List<String> warnings;
71
72 public MyBatisGeneratorConfigurationParser(@Nullable Properties extraProperties, List<String> warnings) {
73 this.extraProperties = Objects.requireNonNullElseGet(extraProperties, Properties::new);
74 configurationProperties = new Properties();
75 this.warnings = warnings;
76 }
77
78 public Configuration parseConfiguration(Element rootNode) throws XMLParserException {
79 Configuration configuration = new Configuration();
80
81 NodeList nodeList = rootNode.getChildNodes();
82 for (int i = 0; i < nodeList.getLength(); i++) {
83 Node childNode = nodeList.item(i);
84
85 if (childNode.getNodeType() != Node.ELEMENT_NODE) {
86 continue;
87 }
88
89 switch (childNode.getNodeName()) {
90 case "properties" ->
91 parsePropertiesElement(childNode);
92 case "classPathEntry" ->
93 configuration.addClasspathEntry(parseClassPathEntry(childNode));
94 case "context" ->
95 configuration.addContext(parseContext(childNode));
96 default -> {
97
98 }
99 }
100 }
101
102 return configuration;
103 }
104
105 protected void parsePropertiesElement(Node node) throws XMLParserException {
106 NullableProperties attributes = parseAttributes(node);
107 String resource = attributes.getProperty("resource");
108 String url = attributes.getProperty("url");
109
110 if (resource == null && url == null) {
111 throw new XMLParserException(getString("RuntimeError.14"));
112 }
113
114 if (resource != null && url != null) {
115 throw new XMLParserException(getString("RuntimeError.14"));
116 }
117
118 if (resource != null) {
119 loadPropertiesFromResource(resource);
120 } else {
121 loadPropertiesFromURL(url);
122 }
123 }
124
125 private void loadPropertiesFromResource(String resource) throws XMLParserException {
126 try {
127 URL resourceUrl = ObjectFactory.getResource(resource)
128 .orElseThrow(() -> new XMLParserException(getString("RuntimeError.15", resource)));
129 InputStream inputStream = resourceUrl.openConnection().getInputStream();
130 configurationProperties.load(inputStream);
131 inputStream.close();
132 } catch (IOException e) {
133 throw new XMLParserException(getString("RuntimeError.16", resource));
134 }
135 }
136
137 private void loadPropertiesFromURL(String url) throws XMLParserException {
138 try {
139 URL resourceUrl = URI.create(url).toURL();
140 InputStream inputStream = resourceUrl.openConnection().getInputStream();
141 configurationProperties.load(inputStream);
142 inputStream.close();
143 } catch (IOException e) {
144 throw new XMLParserException(getString("RuntimeError.17", url));
145 }
146 }
147
148 private Context parseContext(Node node) {
149 NullableProperties attributes = parseAttributes(node);
150 String defaultModelType = attributes.getProperty("defaultModelType");
151 String targetRuntime = attributes.getProperty("targetRuntime");
152 String introspectedColumnImpl = attributes.getProperty("introspectedColumnImpl");
153 String id = attributes.getProperty("id");
154 assert id != null;
155 ModelType dmt =
156 defaultModelType == null ? null : ModelType.getModelType(defaultModelType);
157
158 Context.Builder builder = new Context.Builder()
159 .withId(id)
160 .withDefaultModelType(dmt)
161 .withIntrospectedColumnImpl(introspectedColumnImpl)
162 .withTargetRuntime(targetRuntime);
163
164 NodeList nodeList = node.getChildNodes();
165 for (int i = 0; i < nodeList.getLength(); i++) {
166 Node childNode = nodeList.item(i);
167
168 if (childNode.getNodeType() != Node.ELEMENT_NODE) {
169 continue;
170 }
171
172 switch (childNode.getNodeName()) {
173 case "property" ->
174 parseProperty(childNode).ifPresent(builder::withProperty);
175 case "plugin" ->
176 builder.withPluginConfiguration(parsePlugin(childNode));
177 case "commentGenerator" ->
178 builder.withCommentGeneratorConfiguration(parseCommentGenerator(childNode));
179 case "jdbcConnection" ->
180 builder.withJdbcConnectionConfiguration(parseJdbcConnection(childNode));
181 case "connectionFactory" ->
182 builder.withConnectionFactoryConfiguration(parseConnectionFactory(childNode));
183 case "modelGenerator" ->
184 builder.withModelGeneratorConfiguration(parseModelGenerator(childNode));
185 case "javaModelGenerator" -> {
186 warnings.add(getString("Warning.33"));
187 builder.withModelGeneratorConfiguration(parseModelGenerator(childNode));
188 }
189 case "javaTypeResolver" ->
190 builder.withJavaTypeResolverConfiguration(parseJavaTypeResolver(childNode));
191 case "sqlMapGenerator" ->
192 builder.withSqlMapGeneratorConfiguration(parseSqlMapGenerator(childNode));
193 case "clientGenerator" ->
194 builder.withClientGeneratorConfiguration(parseClientGenerator(childNode, id));
195 case "javaClientGenerator" -> {
196 warnings.add(getString("Warning.34"));
197 builder.withClientGeneratorConfiguration(parseClientGenerator(childNode, id));
198 }
199 case "table" ->
200 builder.withTableConfiguration(parseTable(childNode));
201 default -> {
202
203 }
204 }
205 }
206
207 return builder.build();
208 }
209
210 protected SqlMapGeneratorConfiguration parseSqlMapGenerator(Node node) {
211 NullableProperties attributes = parseAttributes(node);
212 String targetPackage = attributes.getProperty("targetPackage");
213 String targetProject = attributes.getProperty("targetProject");
214 Properties properties = parseProperties(node.getChildNodes());
215 return new SqlMapGeneratorConfiguration.Builder()
216 .withTargetPackage(targetPackage)
217 .withTargetProject(targetProject)
218 .withProperties(properties)
219 .build();
220 }
221
222 protected TableConfiguration parseTable(Node node) {
223 NullableProperties attributes = parseAttributes(node);
224 String catalog = attributes.getProperty("catalog");
225 String schema = attributes.getProperty("schema");
226 String tableName = attributes.getProperty("tableName");
227 String domainObjectName = attributes.getProperty("domainObjectName");
228 String alias = attributes.getProperty("alias");
229 String modelType = attributes.getProperty("modelType");
230 String mapperName = attributes.getProperty("mapperName");
231 String sqlProviderName = attributes.getProperty("sqlProviderName");
232
233 TableConfiguration.Builder builder = new TableConfiguration.Builder()
234 .withModelType(modelType)
235 .withCatalog(catalog)
236 .withSchema(schema)
237 .withTableName(tableName)
238 .withDomainObjectName(domainObjectName)
239 .withAlias(alias)
240 .withMapperName(mapperName)
241 .withSqlProviderName(sqlProviderName);
242
243 String enableInsert = attributes.getProperty("enableInsert");
244 if (enableInsert != null) {
245 builder.withInsertStatementEnabled(isTrue(enableInsert));
246 }
247
248 String enableSelectByPrimaryKey = attributes.getProperty("enableSelectByPrimaryKey");
249 if (enableSelectByPrimaryKey != null) {
250 builder.withSelectByPrimaryKeyStatementEnabled(isTrue(enableSelectByPrimaryKey));
251 }
252
253 String enableSelectByExample = attributes.getProperty("enableSelectByExample");
254 if (enableSelectByExample != null) {
255 builder.withSelectByExampleStatementEnabled(isTrue(enableSelectByExample));
256 }
257
258 String enableUpdateByPrimaryKey = attributes.getProperty("enableUpdateByPrimaryKey");
259 if (enableUpdateByPrimaryKey != null) {
260 builder.withUpdateByPrimaryKeyStatementEnabled(isTrue(enableUpdateByPrimaryKey));
261 }
262
263 String enableDeleteByPrimaryKey = attributes.getProperty("enableDeleteByPrimaryKey");
264 if (enableDeleteByPrimaryKey != null) {
265 builder.withDeleteByPrimaryKeyStatementEnabled(isTrue(enableDeleteByPrimaryKey));
266 }
267
268 String enableDeleteByExample = attributes.getProperty("enableDeleteByExample");
269 if (enableDeleteByExample != null) {
270 builder.withDeleteByExampleStatementEnabled(isTrue(enableDeleteByExample));
271 }
272
273 String enableCountByExample = attributes.getProperty("enableCountByExample");
274 if (enableCountByExample != null) {
275 builder.withCountByExampleStatementEnabled(isTrue(enableCountByExample));
276 }
277
278 String enableUpdateByExample = attributes.getProperty("enableUpdateByExample");
279 if (enableUpdateByExample != null) {
280 builder.withUpdateByExampleStatementEnabled(isTrue(enableUpdateByExample));
281 }
282
283 String escapeWildcards = attributes.getProperty("escapeWildcards");
284 if (escapeWildcards != null) {
285 builder.withWildcardEscapingEnabled(isTrue(escapeWildcards));
286 }
287
288 String delimitIdentifiers = attributes.getProperty("delimitIdentifiers");
289 if (delimitIdentifiers != null) {
290 builder.withDelimitIdentifiers(isTrue(delimitIdentifiers));
291 }
292
293 String delimitAllColumns = attributes.getProperty("delimitAllColumns");
294 if (delimitAllColumns != null) {
295 builder.withAllColumnDelimitingEnabled(isTrue(delimitAllColumns));
296 }
297
298 NodeList nodeList = node.getChildNodes();
299 for (int i = 0; i < nodeList.getLength(); i++) {
300 Node childNode = nodeList.item(i);
301
302 if (childNode.getNodeType() != Node.ELEMENT_NODE) {
303 continue;
304 }
305
306 switch (childNode.getNodeName()) {
307 case "property" ->
308 parseProperty(childNode).ifPresent(builder::withProperty);
309 case "columnOverride" ->
310 builder.withColumnOverride(parseColumnOverride(childNode));
311 case "ignoreColumn" ->
312 builder.withIgnoredColumn(parseIgnoreColumn(childNode));
313 case "ignoreColumnsByRegex" ->
314 builder.withIgnoredColumnPattern(parseIgnoreColumnByRegex(childNode));
315 case "generatedKey" ->
316 builder.withGeneratedKey(parseGeneratedKey(childNode));
317 case "domainObjectRenamingRule" ->
318 builder.withDomainObjectRenamingRule(parseDomainObjectRenamingRule(childNode));
319 case "columnRenamingRule" ->
320 builder.withColumnRenamingRule(parseColumnRenamingRule(childNode));
321 default -> {
322
323 }
324 }
325 }
326
327 return builder.build();
328 }
329
330 private ColumnOverride parseColumnOverride(Node node) {
331 NullableProperties attributes = parseAttributes(node);
332 String column = attributes.getProperty("column");
333 String javaProperty = attributes.getProperty("property");
334 String javaType = attributes.getProperty("javaType");
335 String jdbcType = attributes.getProperty("jdbcType");
336 String typeHandler = attributes.getProperty("typeHandler");
337 String delimitedColumnName = attributes.getProperty("delimitedColumnName");
338 String isGeneratedAlways = attributes.getProperty("isGeneratedAlways");
339 Properties properties = parseProperties(node.getChildNodes());
340
341 return new ColumnOverride.Builder()
342 .withColumnName(column)
343 .withJavaProperty(javaProperty)
344 .withJavaType(javaType)
345 .withJdbcType(jdbcType)
346 .withTypeHandler(typeHandler)
347 .withColumnNameDelimited(parseNullableBoolean(delimitedColumnName))
348 .withGeneratedAlways(isTrue(isGeneratedAlways))
349 .withProperties(properties)
350 .build();
351 }
352
353 private GeneratedKey parseGeneratedKey(Node node) {
354 NullableProperties attributes = parseAttributes(node);
355 String column = attributes.getProperty("column");
356 boolean identity = isTrue(attributes.getProperty("identity"));
357 String sqlStatement = attributes.getProperty("sqlStatement");
358 return new GeneratedKey(column, sqlStatement, identity);
359 }
360
361 private IgnoredColumn parseIgnoreColumn(Node node) {
362 NullableProperties attributes = parseAttributes(node);
363 String column = attributes.getProperty("column");
364 String delimitedColumnName = attributes.getProperty("delimitedColumnName");
365 return new IgnoredColumn(column, isTrue(delimitedColumnName));
366 }
367
368 private IgnoredColumnPattern parseIgnoreColumnByRegex(Node node) {
369 NullableProperties attributes = parseAttributes(node);
370 String pattern = attributes.getProperty("pattern");
371
372 IgnoredColumnPattern.Builder builder = new IgnoredColumnPattern.Builder().withPattern(pattern);
373
374 NodeList nodeList = node.getChildNodes();
375 for (int i = 0; i < nodeList.getLength(); i++) {
376 Node childNode = nodeList.item(i);
377
378 if (childNode.getNodeType() != Node.ELEMENT_NODE) {
379 continue;
380 }
381
382 if ("except".equals(childNode.getNodeName())) {
383 builder.addException(parseException(childNode));
384 }
385 }
386
387 return builder.build();
388 }
389
390 private IgnoredColumnException parseException(Node node) {
391 NullableProperties attributes = parseAttributes(node);
392 String column = attributes.getProperty("column");
393 String delimitedColumnName = attributes.getProperty("delimitedColumnName");
394 return new IgnoredColumnException(column, isTrue(delimitedColumnName));
395 }
396
397 private DomainObjectRenamingRule parseDomainObjectRenamingRule(Node node) {
398 NullableProperties attributes = parseAttributes(node);
399 String searchString = attributes.getProperty("searchString");
400 String replaceString = attributes.getProperty("replaceString");
401 return new DomainObjectRenamingRule(searchString, replaceString);
402 }
403
404 private ColumnRenamingRule parseColumnRenamingRule(Node node) {
405 NullableProperties attributes = parseAttributes(node);
406 String searchString = attributes.getProperty("searchString");
407 String replaceString = attributes.getProperty("replaceString");
408 return new ColumnRenamingRule(searchString, replaceString);
409 }
410
411 protected JavaTypeResolverConfiguration parseJavaTypeResolver(Node node) {
412 NullableProperties attributes = parseAttributes(node);
413 String type = attributes.getProperty("type");
414 Properties properties = parseProperties(node.getChildNodes());
415 return new JavaTypeResolverConfiguration.Builder()
416 .withConfigurationType(type)
417 .withProperties(properties)
418 .build();
419 }
420
421 private PluginConfiguration parsePlugin(Node node) {
422 NullableProperties attributes = parseAttributes(node);
423 String type = attributes.getProperty("type");
424 Properties properties = parseProperties(node.getChildNodes());
425 return new PluginConfiguration.Builder()
426 .withConfigurationType(type)
427 .withProperties(properties)
428 .build();
429 }
430
431 protected ModelGeneratorConfiguration parseModelGenerator(Node node) {
432 NullableProperties attributes = parseAttributes(node);
433 String targetPackage = attributes.getProperty("targetPackage");
434 String targetProject = attributes.getProperty("targetProject");
435 Properties properties = parseProperties(node.getChildNodes());
436 return new ModelGeneratorConfiguration.Builder()
437 .withTargetPackage(targetPackage)
438 .withTargetProject(targetProject)
439 .withProperties(properties)
440 .build();
441 }
442
443 private ClientGeneratorConfiguration parseClientGenerator(Node node, String contextId) {
444 NullableProperties attributes = parseAttributes(node);
445 String type = attributes.getProperty("type");
446 String targetPackage = attributes.getProperty("targetPackage");
447 String targetProject = attributes.getProperty("targetProject");
448 Properties properties = parseProperties(node.getChildNodes());
449 ClientGeneratorConfiguration.LegacyClientType legacyClientType = null;
450 if (type != null) {
451 legacyClientType = ClientGeneratorConfiguration.LegacyClientType.getByAlias(type);
452 if (legacyClientType == null) {
453 warnings.add(getString("ValidationError.31", type, contextId));
454 }
455 }
456
457 return new ClientGeneratorConfiguration.Builder()
458 .withLegacyClientType(legacyClientType)
459 .withTargetPackage(targetPackage)
460 .withTargetProject(targetProject)
461 .withProperties(properties)
462 .build();
463 }
464
465 protected JDBCConnectionConfiguration parseJdbcConnection(Node node) {
466 NullableProperties attributes = parseAttributes(node);
467 String driverClass = attributes.getProperty("driverClass");
468 String connectionURL = attributes.getProperty("connectionURL");
469 String userId = attributes.getProperty("userId");
470 String password = attributes.getProperty("password");
471 Properties properties = parseProperties(node.getChildNodes());
472 return new JDBCConnectionConfiguration.Builder()
473 .withDriverClass(driverClass)
474 .withConnectionURL(connectionURL)
475 .withUserId(userId)
476 .withPassword(password)
477 .withProperties(properties)
478 .build();
479 }
480
481 protected @Nullable String parseClassPathEntry(Node node) {
482 NullableProperties attributes = parseAttributes(node);
483 return attributes.getProperty("location");
484 }
485
486 protected Properties parseProperties(NodeList nodeList) {
487 Properties properties = new Properties();
488 for (int i = 0; i < nodeList.getLength(); i++) {
489 Node childNode = nodeList.item(i);
490
491 if (childNode.getNodeType() != Node.ELEMENT_NODE) {
492 continue;
493 }
494
495 if ("property".equals(childNode.getNodeName())) {
496 parseProperty(childNode).ifPresent(p -> properties.setProperty(p.name(), p.value()));
497 }
498 }
499 return properties;
500 }
501
502 protected Optional<Property> parseProperty(Node node) {
503 NullableProperties attributes = parseAttributes(node);
504 String name = attributes.getProperty("name");
505 String value = attributes.getProperty("value");
506
507 if (name == null || value == null) {
508 return Optional.empty();
509 } else {
510 return Optional.of(new Property(name, value));
511 }
512 }
513
514
515
516
517
518
519
520
521
522 protected NullableProperties parseAttributes(Node node) {
523 NullableProperties attributes = new NullableProperties();
524 NamedNodeMap nnm = node.getAttributes();
525 for (int i = 0; i < nnm.getLength(); i++) {
526 Node attribute = nnm.item(i);
527 String value = parsePropertyTokens(attribute.getNodeValue());
528 attributes.put(attribute.getNodeName(), trimToNull(value));
529 }
530
531 return attributes;
532 }
533
534 String parsePropertyTokens(String s) {
535 final String OPEN = "${";
536 final String CLOSE = "}";
537 int currentIndex = 0;
538
539 List<String> answer = new ArrayList<>();
540
541 int markerStartIndex = s.indexOf(OPEN);
542 if (markerStartIndex < 0) {
543
544 answer.add(s);
545 currentIndex = s.length();
546 }
547
548 while (markerStartIndex > -1) {
549 if (markerStartIndex > currentIndex) {
550
551 answer.add(s.substring(currentIndex, markerStartIndex));
552 currentIndex = markerStartIndex;
553 }
554
555 int markerEndIndex = s.indexOf(CLOSE, currentIndex);
556 int nestedStartIndex = s.indexOf(OPEN, markerStartIndex + OPEN.length());
557 while (nestedStartIndex > -1 && markerEndIndex > -1 && nestedStartIndex < markerEndIndex) {
558 nestedStartIndex = s.indexOf(OPEN, nestedStartIndex + OPEN.length());
559 markerEndIndex = s.indexOf(CLOSE, markerEndIndex + CLOSE.length());
560 }
561
562 if (markerEndIndex < 0) {
563
564 answer.add(s.substring(markerStartIndex));
565 currentIndex = s.length();
566 break;
567 }
568
569
570 String property = s.substring(markerStartIndex + OPEN.length(), markerEndIndex);
571 String propertyValue = resolveProperty(parsePropertyTokens(property));
572 if (propertyValue == null) {
573
574 answer.add(s.substring(markerStartIndex, markerEndIndex + 1));
575 } else {
576 answer.add(propertyValue);
577 }
578
579 currentIndex = markerEndIndex + CLOSE.length();
580 markerStartIndex = s.indexOf(OPEN, currentIndex);
581 }
582
583 if (currentIndex < s.length()) {
584 answer.add(s.substring(currentIndex));
585 }
586
587 return String.join("", answer);
588 }
589
590 protected CommentGeneratorConfiguration parseCommentGenerator(Node node) {
591 NullableProperties attributes = parseAttributes(node);
592 String type = attributes.getProperty("type");
593 Properties properties = parseProperties(node.getChildNodes());
594 return new CommentGeneratorConfiguration.Builder()
595 .withConfigurationType(type)
596 .withProperties(properties)
597 .build();
598 }
599
600 protected ConnectionFactoryConfiguration parseConnectionFactory(Node node) {
601 NullableProperties attributes = parseAttributes(node);
602 String type = attributes.getProperty("type");
603 Properties properties = parseProperties(node.getChildNodes());
604 return new ConnectionFactoryConfiguration.Builder()
605 .withConfigurationType(type)
606 .withProperties(properties)
607 .build();
608 }
609
610
611
612
613
614
615
616
617
618
619
620
621
622 private @Nullable String resolveProperty(String key) {
623 String property = System.getProperty(key);
624
625 if (property == null) {
626 property = configurationProperties.getProperty(key);
627 }
628
629 if (property == null) {
630 property = extraProperties.getProperty(key);
631 }
632
633 return property;
634 }
635 }