View Javadoc
1   /*
2    *    Copyright 2006-2023 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;
17  
18  import static org.mybatis.generator.internal.util.StringUtility.composeFullyQualifiedTableName;
19  import static org.mybatis.generator.internal.util.StringUtility.isTrue;
20  import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;
21  import static org.mybatis.generator.internal.util.messages.Messages.getString;
22  
23  import java.sql.Connection;
24  import java.sql.SQLException;
25  import java.util.ArrayList;
26  import java.util.List;
27  import java.util.Set;
28  
29  import org.mybatis.generator.api.CommentGenerator;
30  import org.mybatis.generator.api.ConnectionFactory;
31  import org.mybatis.generator.api.GeneratedFile;
32  import org.mybatis.generator.api.GeneratedJavaFile;
33  import org.mybatis.generator.api.GeneratedKotlinFile;
34  import org.mybatis.generator.api.GeneratedXmlFile;
35  import org.mybatis.generator.api.IntrospectedTable;
36  import org.mybatis.generator.api.JavaFormatter;
37  import org.mybatis.generator.api.JavaTypeResolver;
38  import org.mybatis.generator.api.KotlinFormatter;
39  import org.mybatis.generator.api.Plugin;
40  import org.mybatis.generator.api.ProgressCallback;
41  import org.mybatis.generator.api.XmlFormatter;
42  import org.mybatis.generator.internal.JDBCConnectionFactory;
43  import org.mybatis.generator.internal.ObjectFactory;
44  import org.mybatis.generator.internal.PluginAggregator;
45  import org.mybatis.generator.internal.db.DatabaseIntrospector;
46  
47  public class Context extends PropertyHolder {
48  
49      private String id;
50  
51      private JDBCConnectionConfiguration jdbcConnectionConfiguration;
52  
53      private ConnectionFactoryConfiguration connectionFactoryConfiguration;
54  
55      private SqlMapGeneratorConfiguration sqlMapGeneratorConfiguration;
56  
57      private JavaTypeResolverConfiguration javaTypeResolverConfiguration;
58  
59      private JavaModelGeneratorConfiguration javaModelGeneratorConfiguration;
60  
61      private JavaClientGeneratorConfiguration javaClientGeneratorConfiguration;
62  
63      private final ArrayList<TableConfiguration> tableConfigurations;
64  
65      private final ModelType defaultModelType;
66  
67      private String beginningDelimiter = "\""; //$NON-NLS-1$
68  
69      private String endingDelimiter = "\""; //$NON-NLS-1$
70  
71      private CommentGeneratorConfiguration commentGeneratorConfiguration;
72  
73      private CommentGenerator commentGenerator;
74  
75      private PluginAggregator pluginAggregator;
76  
77      private final List<PluginConfiguration> pluginConfigurations;
78  
79      private String targetRuntime;
80  
81      private String introspectedColumnImpl;
82  
83      private Boolean autoDelimitKeywords;
84  
85      private JavaFormatter javaFormatter;
86  
87      private KotlinFormatter kotlinFormatter;
88  
89      private XmlFormatter xmlFormatter;
90  
91      public Context(ModelType defaultModelType) {
92          super();
93  
94          if (defaultModelType == null) {
95              this.defaultModelType = ModelType.CONDITIONAL;
96          } else {
97              this.defaultModelType = defaultModelType;
98          }
99  
100         tableConfigurations = new ArrayList<>();
101         pluginConfigurations = new ArrayList<>();
102     }
103 
104     public void addTableConfiguration(TableConfiguration tc) {
105         tableConfigurations.add(tc);
106     }
107 
108     public JavaClientGeneratorConfiguration getJavaClientGeneratorConfiguration() {
109         return javaClientGeneratorConfiguration;
110     }
111 
112     public JavaModelGeneratorConfiguration getJavaModelGeneratorConfiguration() {
113         return javaModelGeneratorConfiguration;
114     }
115 
116     public JavaTypeResolverConfiguration getJavaTypeResolverConfiguration() {
117         return javaTypeResolverConfiguration;
118     }
119 
120     public SqlMapGeneratorConfiguration getSqlMapGeneratorConfiguration() {
121         return sqlMapGeneratorConfiguration;
122     }
123 
124     public void addPluginConfiguration(
125             PluginConfiguration pluginConfiguration) {
126         pluginConfigurations.add(pluginConfiguration);
127     }
128 
129     /**
130      * This method does a simple validate, it makes sure that all required fields have been filled in. It does not do
131      * any more complex operations such as validating that database tables exist or validating that named columns exist
132      *
133      * @param errors
134      *            the errors
135      */
136     public void validate(List<String> errors) {
137         if (!stringHasValue(id)) {
138             errors.add(getString("ValidationError.16")); //$NON-NLS-1$
139         }
140 
141         if (jdbcConnectionConfiguration == null && connectionFactoryConfiguration == null) {
142             // must specify one
143             errors.add(getString("ValidationError.10", id)); //$NON-NLS-1$
144         } else if (jdbcConnectionConfiguration != null && connectionFactoryConfiguration != null) {
145             // must not specify both
146             errors.add(getString("ValidationError.10", id)); //$NON-NLS-1$
147         } else if (jdbcConnectionConfiguration != null) {
148             jdbcConnectionConfiguration.validate(errors);
149         } else {
150             connectionFactoryConfiguration.validate(errors);
151         }
152 
153         if (javaModelGeneratorConfiguration == null) {
154             errors.add(getString("ValidationError.8", id)); //$NON-NLS-1$
155         } else {
156             javaModelGeneratorConfiguration.validate(errors, id);
157         }
158 
159         if (javaClientGeneratorConfiguration != null) {
160             javaClientGeneratorConfiguration.validate(errors, id);
161         }
162 
163         IntrospectedTable it = null;
164         try {
165             it = ObjectFactory.createIntrospectedTableForValidation(this);
166         } catch (Exception e) {
167             errors.add(getString("ValidationError.25", id)); //$NON-NLS-1$
168         }
169 
170         if (it != null && it.requiresXMLGenerator()) {
171             if (sqlMapGeneratorConfiguration == null) {
172                 errors.add(getString("ValidationError.9", id)); //$NON-NLS-1$
173             } else {
174                 sqlMapGeneratorConfiguration.validate(errors, id);
175             }
176         }
177 
178         if (tableConfigurations.isEmpty()) {
179             errors.add(getString("ValidationError.3", id)); //$NON-NLS-1$
180         } else {
181             for (int i = 0; i < tableConfigurations.size(); i++) {
182                 TableConfiguration tc = tableConfigurations.get(i);
183 
184                 tc.validate(errors, i);
185             }
186         }
187 
188         for (PluginConfiguration pluginConfiguration : pluginConfigurations) {
189             pluginConfiguration.validate(errors, id);
190         }
191     }
192 
193     public String getId() {
194         return id;
195     }
196 
197     public void setId(String id) {
198         this.id = id;
199     }
200 
201     public void setJavaClientGeneratorConfiguration(
202             JavaClientGeneratorConfiguration javaClientGeneratorConfiguration) {
203         this.javaClientGeneratorConfiguration = javaClientGeneratorConfiguration;
204     }
205 
206     public void setJavaModelGeneratorConfiguration(
207             JavaModelGeneratorConfiguration javaModelGeneratorConfiguration) {
208         this.javaModelGeneratorConfiguration = javaModelGeneratorConfiguration;
209     }
210 
211     public void setJavaTypeResolverConfiguration(
212             JavaTypeResolverConfiguration javaTypeResolverConfiguration) {
213         this.javaTypeResolverConfiguration = javaTypeResolverConfiguration;
214     }
215 
216     public void setJdbcConnectionConfiguration(
217             JDBCConnectionConfiguration jdbcConnectionConfiguration) {
218         this.jdbcConnectionConfiguration = jdbcConnectionConfiguration;
219     }
220 
221     public void setSqlMapGeneratorConfiguration(
222             SqlMapGeneratorConfiguration sqlMapGeneratorConfiguration) {
223         this.sqlMapGeneratorConfiguration = sqlMapGeneratorConfiguration;
224     }
225 
226     public ModelType getDefaultModelType() {
227         return defaultModelType;
228     }
229 
230     public String getBeginningDelimiter() {
231         return beginningDelimiter;
232     }
233 
234     public String getEndingDelimiter() {
235         return endingDelimiter;
236     }
237 
238     @Override
239     public void addProperty(String name, String value) {
240         super.addProperty(name, value);
241 
242         if (PropertyRegistry.CONTEXT_BEGINNING_DELIMITER.equals(name)) {
243             beginningDelimiter = value;
244         } else if (PropertyRegistry.CONTEXT_ENDING_DELIMITER.equals(name)) {
245             endingDelimiter = value;
246         } else if (PropertyRegistry.CONTEXT_AUTO_DELIMIT_KEYWORDS.equals(name)
247                 && stringHasValue(value)) {
248             autoDelimitKeywords = isTrue(value);
249         }
250     }
251 
252     public CommentGenerator getCommentGenerator() {
253         if (commentGenerator == null) {
254             commentGenerator = ObjectFactory.createCommentGenerator(this);
255         }
256 
257         return commentGenerator;
258     }
259 
260     public JavaFormatter getJavaFormatter() {
261         if (javaFormatter == null) {
262             javaFormatter = ObjectFactory.createJavaFormatter(this);
263         }
264 
265         return javaFormatter;
266     }
267 
268     public KotlinFormatter getKotlinFormatter() {
269         if (kotlinFormatter == null) {
270             kotlinFormatter = ObjectFactory.createKotlinFormatter(this);
271         }
272 
273         return kotlinFormatter;
274     }
275 
276     public XmlFormatter getXmlFormatter() {
277         if (xmlFormatter == null) {
278             xmlFormatter = ObjectFactory.createXmlFormatter(this);
279         }
280 
281         return xmlFormatter;
282     }
283 
284     public CommentGeneratorConfiguration getCommentGeneratorConfiguration() {
285         return commentGeneratorConfiguration;
286     }
287 
288     public void setCommentGeneratorConfiguration(
289             CommentGeneratorConfiguration commentGeneratorConfiguration) {
290         this.commentGeneratorConfiguration = commentGeneratorConfiguration;
291     }
292 
293     public Plugin getPlugins() {
294         return pluginAggregator;
295     }
296 
297     public String getTargetRuntime() {
298         return targetRuntime;
299     }
300 
301     public void setTargetRuntime(String targetRuntime) {
302         this.targetRuntime = targetRuntime;
303     }
304 
305     public String getIntrospectedColumnImpl() {
306         return introspectedColumnImpl;
307     }
308 
309     public void setIntrospectedColumnImpl(String introspectedColumnImpl) {
310         this.introspectedColumnImpl = introspectedColumnImpl;
311     }
312 
313     // methods related to code generation.
314     //
315     // Methods should be called in this order:
316     //
317     // 1. getIntrospectionSteps()
318     // 2. introspectTables()
319     // 3. getGenerationSteps()
320     // 4. generateFiles()
321     //
322 
323     private final List<IntrospectedTable> introspectedTables = new ArrayList<>();
324 
325     /**
326      * This method could be useful for users that use the library for introspection only
327      * and not for code generation.
328      *
329      * @return a list containing the results of table introspection. The list will be empty
330      *     if this method is called before introspectTables(), or if no tables are found that
331      *     match the configuration
332      */
333     public List<IntrospectedTable> getIntrospectedTables() {
334         return introspectedTables;
335     }
336 
337     public int getIntrospectionSteps() {
338         int steps = 0;
339 
340         steps++; // connect to database
341 
342         // for each table:
343         //
344         // 1. Create introspected table implementation
345 
346         steps += tableConfigurations.size();
347 
348         return steps;
349     }
350 
351     /**
352      * Introspect tables based on the configuration specified in the
353      * constructor. This method is long running.
354      *
355      * @param callback
356      *            a progress callback if progress information is desired, or
357      *            <code>null</code>
358      * @param warnings
359      *            any warning generated from this method will be added to the
360      *            List. Warnings are always Strings.
361      * @param fullyQualifiedTableNames
362      *            a set of table names to generate. The elements of the set must
363      *            be Strings that exactly match what's specified in the
364      *            configuration. For example, if table name = "foo" and schema =
365      *            "bar", then the fully qualified table name is "foo.bar". If
366      *            the Set is null or empty, then all tables in the configuration
367      *            will be used for code generation.
368      *
369      * @throws SQLException
370      *             if some error arises while introspecting the specified
371      *             database tables.
372      * @throws InterruptedException
373      *             if the progress callback reports a cancel
374      */
375     public void introspectTables(ProgressCallback callback,
376             List<String> warnings, Set<String> fullyQualifiedTableNames)
377             throws SQLException, InterruptedException {
378 
379         introspectedTables.clear();
380         JavaTypeResolver javaTypeResolver = ObjectFactory
381                 .createJavaTypeResolver(this, warnings);
382 
383         Connection connection = null;
384 
385         try {
386             callback.startTask(getString("Progress.0")); //$NON-NLS-1$
387             connection = getConnection();
388 
389             DatabaseIntrospector databaseIntrospector = new DatabaseIntrospector(
390                     this, connection.getMetaData(), javaTypeResolver, warnings);
391 
392             for (TableConfiguration tc : tableConfigurations) {
393                 String tableName = composeFullyQualifiedTableName(tc.getCatalog(), tc
394                                 .getSchema(), tc.getTableName(), '.');
395 
396                 if (fullyQualifiedTableNames != null
397                         && !fullyQualifiedTableNames.isEmpty()
398                         && !fullyQualifiedTableNames.contains(tableName)) {
399                     continue;
400                 }
401 
402                 if (!tc.areAnyStatementsEnabled()) {
403                     warnings.add(getString("Warning.0", tableName)); //$NON-NLS-1$
404                     continue;
405                 }
406 
407                 callback.startTask(getString("Progress.1", tableName)); //$NON-NLS-1$
408                 List<IntrospectedTable> tables = databaseIntrospector
409                         .introspectTables(tc);
410 
411                 if (tables != null) {
412                     introspectedTables.addAll(tables);
413                 }
414 
415                 callback.checkCancel();
416             }
417         } finally {
418             closeConnection(connection);
419         }
420     }
421 
422     public int getGenerationSteps() {
423         int steps = 0;
424 
425         for (IntrospectedTable introspectedTable : introspectedTables) {
426             steps += introspectedTable.getGenerationSteps();
427         }
428 
429         return steps;
430     }
431 
432     public void generateFiles(ProgressCallback callback,
433             List<GeneratedJavaFile> generatedJavaFiles,
434             List<GeneratedXmlFile> generatedXmlFiles,
435             List<GeneratedKotlinFile> generatedKotlinFiles,
436             List<GeneratedFile> otherGeneratedFiles,
437             List<String> warnings)
438             throws InterruptedException {
439 
440         pluginAggregator = new PluginAggregator();
441         for (PluginConfiguration pluginConfiguration : pluginConfigurations) {
442             Plugin plugin = ObjectFactory.createPlugin(this,
443                     pluginConfiguration);
444             if (plugin.validate(warnings)) {
445                 pluginAggregator.addPlugin(plugin);
446             } else {
447                 warnings.add(getString("Warning.24", //$NON-NLS-1$
448                         pluginConfiguration.getConfigurationType(), id));
449             }
450         }
451 
452         // initialize everything first before generating. This allows plugins to know about other
453         // items in the configuration.
454         for (IntrospectedTable introspectedTable : introspectedTables) {
455             callback.checkCancel();
456             introspectedTable.initialize();
457             introspectedTable.calculateGenerators(warnings, callback);
458         }
459 
460         for (IntrospectedTable introspectedTable : introspectedTables) {
461             callback.checkCancel();
462 
463             if (!pluginAggregator.shouldGenerate(introspectedTable)) {
464                 continue;
465             }
466 
467             generatedJavaFiles.addAll(introspectedTable
468                     .getGeneratedJavaFiles());
469             generatedXmlFiles.addAll(introspectedTable
470                     .getGeneratedXmlFiles());
471             generatedKotlinFiles.addAll(introspectedTable
472                     .getGeneratedKotlinFiles());
473 
474             generatedJavaFiles.addAll(pluginAggregator
475                     .contextGenerateAdditionalJavaFiles(introspectedTable));
476             generatedXmlFiles.addAll(pluginAggregator
477                     .contextGenerateAdditionalXmlFiles(introspectedTable));
478             generatedKotlinFiles.addAll(pluginAggregator
479                     .contextGenerateAdditionalKotlinFiles(introspectedTable));
480             otherGeneratedFiles.addAll(pluginAggregator
481                     .contextGenerateAdditionalFiles(introspectedTable));
482         }
483 
484         generatedJavaFiles.addAll(pluginAggregator
485                 .contextGenerateAdditionalJavaFiles());
486         generatedXmlFiles.addAll(pluginAggregator
487                 .contextGenerateAdditionalXmlFiles());
488         generatedKotlinFiles.addAll(pluginAggregator
489                 .contextGenerateAdditionalKotlinFiles());
490         otherGeneratedFiles.addAll(pluginAggregator
491                 .contextGenerateAdditionalFiles());
492     }
493 
494     /**
495      * This method creates a new JDBC connection from the values specified in the configuration file.
496      * If you call this method, then you are responsible
497      * for closing the connection (See {@link Context#closeConnection(Connection)}). If you do not
498      * close the connection, then there could be connection leaks.
499      *
500      * @return a new connection created from the values in the configuration file
501      * @throws SQLException if any error occurs while creating the connection
502      */
503     public Connection getConnection() throws SQLException {
504         ConnectionFactory connectionFactory;
505         if (jdbcConnectionConfiguration != null) {
506             connectionFactory = new JDBCConnectionFactory(jdbcConnectionConfiguration);
507         } else {
508             connectionFactory = ObjectFactory.createConnectionFactory(this);
509         }
510 
511         return connectionFactory.getConnection();
512     }
513 
514     /**
515      * This method closes a JDBC connection and ignores any errors. If the passed connection is null,
516      * then the method does nothing.
517      *
518      * @param connection a JDBC connection to close, may be null
519      */
520     public void closeConnection(Connection connection) {
521         if (connection != null) {
522             try {
523                 connection.close();
524             } catch (SQLException e) {
525                 // ignore
526             }
527         }
528     }
529 
530     public boolean autoDelimitKeywords() {
531         return autoDelimitKeywords != null
532                 && autoDelimitKeywords;
533     }
534 
535     public ConnectionFactoryConfiguration getConnectionFactoryConfiguration() {
536         return connectionFactoryConfiguration;
537     }
538 
539     public void setConnectionFactoryConfiguration(ConnectionFactoryConfiguration connectionFactoryConfiguration) {
540         this.connectionFactoryConfiguration = connectionFactoryConfiguration;
541     }
542 }