View Javadoc
1   package com.wakaleo.schemaspy;
2   
3   import java.io.File;
4   import java.util.ArrayList;
5   import java.util.List;
6   import java.util.Locale;
7   import org.apache.maven.plugins.annotations.LifecyclePhase;
8   import org.apache.maven.plugins.annotations.Mojo;
9   import org.apache.maven.plugins.annotations.Parameter;
10  import org.apache.maven.reporting.AbstractMavenReport;
11  import org.apache.maven.reporting.MavenReportException;
12  
13  /**
14   * The SchemaSpy Maven plugin report.
15   *
16   * <p>This plugin is designed to generate SchemaSpy report for a Maven website. SchemaSpy also may
17   * need the Graphviz tool (https://www.graphviz.org/) in order to generate graphical representations
18   * of the table/view relationships, so this needs to be installed on your machine.
19   *
20   * <p>The SchemaSpy goal invokes the SchemaSpy command-line tool. SchemaSpy generates a graphical
21   * and HTML report describing a given relational database.
22   *
23   * @author John Smart
24   * @author mprins
25   */
26  @Mojo(name = "schemaspy", defaultPhase = LifecyclePhase.SITE)
27  public class SchemaSpyReport extends AbstractMavenReport {
28  
29    @Parameter(property = "vizjs", defaultValue = "true")
30    protected boolean vizjs = false;
31  
32    /**
33     * Limit the degree of separation (1 shows less, 2 is default), 1 is a good option for large databases with
34     * lots of relationships.
35     */
36    @Parameter(property = "degree", defaultValue = "2")
37    protected int degree = 2;
38  
39    /**
40     * Whether to create the report only on the execution root of a multi-module project.
41     *
42     * @since 5.0.4
43     */
44    @Parameter(property = "runOnExecutionRoot", defaultValue = "false")
45    protected boolean runOnExecutionRoot = false;
46  
47    /** The output directory for the intermediate report. */
48    @Parameter(property = "targetDirectory", defaultValue = "${project.build.directory}")
49    private File targetDirectory;
50  
51    /** The name of the database being analysed. */
52    @Parameter(property = "database", required = true)
53    private String database;
54  
55    /** The host address of the database being analysed. */
56    @Parameter(property = "host")
57    private String host;
58  
59    /** The port, required by some drivers. */
60    @Parameter(property = "port")
61    private String port;
62  
63    /** The type of database being analysed — defaults to ora. */
64    @Parameter(property = "databaseType")
65    private String databaseType;
66  
67    /** Connect to the database with this user id. */
68    @Parameter(property = "user")
69    private String user;
70  
71    /** Database schema to use — defaults to the specified user. */
72    @Parameter(property = "schema")
73    private String schema;
74  
75    /** Database password to use — defaults to none. */
76    @Parameter(property = "password")
77    private String password;
78  
79    /**
80     * If specified, SchemaSpy will look for JDBC drivers on this path, rather than using the
81     * application classpath. Useful if your database has a non-O/S driver not bundled with the
82     * plugin.
83     */
84    @Parameter(property = "pathToDrivers")
85    private String pathToDrivers;
86  
87    /**
88     * Schema description. Displays the specified textual description on summary pages. If your
89     * description includes an equals sign then escape it with a backslash. NOTE: This field doesn't
90     * seem to be used by SchemaSpy in the current version.
91     */
92    @Parameter(property = "schemaDescription")
93    private String schemaDescription;
94  
95    /**
96     * Only include matching tables/views. This is a regular expression that's used to determine which
97     * tables/views to include. For example: -i "(.*book.*)|(library.*)" includes only those
98     * tables/views with 'book' in their names or that start with 'library'. You might want to use
99     * "description" with this option to describe the subset of tables.
100    */
101   @Parameter(property = "includeTableNamesRegex")
102   private String includeTableNamesRegex;
103 
104   /**
105    * Exclude matching columns from relationship analysis to simplify the generated graphs. This is a
106    * regular expression that's used to determine which columns to exclude. It must match table name,
107    * followed by a dot, followed by column name. For example: -x "(book.isbn)|(borrower.address)"
108    * Note that each column name regular expression must be surrounded by ()'s and separated from other
109    * column names by a |.
110    */
111   @Parameter(property = "excludeColumnNamesRegex")
112   private String excludeColumnNamesRegex;
113 
114   /**
115    * Allow HTML In Comments. Any HTML embedded in comments normally gets encoded so that it's
116    * rendered as text. This option allows it to be rendered as HTML.
117    */
118   @Parameter(property = "allowHtmlInComments")
119   private Boolean allowHtmlInComments;
120 
121   /**
122    * Comments Initially Displayed. Column comments are normally hidden by default. This option
123    * displays them by default.
124    *
125    * @deprecated this seems to no longer be a supported option in SchemaSpy
126    */
127   @Deprecated
128   @Parameter(property = "commentsInitiallyDisplayed")
129   private Boolean commentsInitiallyDisplayed;
130 
131   /** Don't include implied foreign key relationships in the generated table details. */
132   @Parameter(property = "noImplied")
133   private Boolean noImplied;
134 
135   /** Only generate files needed for insertion/deletion of data (e.g. for scripts). */
136   @Parameter(property = "noHtml")
137   private Boolean noHtml;
138 
139   /** Detail of execution logging. */
140   @Parameter(property = "logLevel")
141   private String logLevel;
142 
143   /**
144    * Some databases, like Derby, will crash if you use the old driver object to establish a
145    * connection (eg "connection = driver.connect(...)"). In this case, set useDriverManager to true
146    * to use the DriverManager.getConnection() method instead (eg "connection =
147    * java.sql.DriverManager.getConnection(...)"). Other databases (e.g. MySQL) seem to only work with
148    * the first method, so don't use this parameter unless you have to.
149    */
150   @Parameter(property = "useDriverManager")
151   private Boolean useDriverManager;
152 
153   /** The CSS Stylesheet. Allows you to override the default SchemaSpyCSS stylesheet. */
154   @Parameter(property = "cssStylesheet")
155   private String cssStylesheet;
156 
157   /**
158    * Single Sign-On. Don't require a user to be specified with -user to simplify configuration when
159    * running in a single sign-on environment.
160    */
161   @Parameter(property = "singleSignOn")
162   private Boolean singleSignOn;
163 
164   /**
165    * Generate lower-quality diagrams. Various installations of Graphviz (depending on OS and/or
166    * version) will default to generating either higher or lower quality images. That is, some might
167    * not have the "lower quality" libraries and others might not have the "higher quality"
168    * libraries.
169    */
170   @Parameter(property = "lowQuality")
171   private Boolean lowQuality;
172 
173   /**
174    * Generate higher-quality diagrams. Various installations of Graphviz (depending on OS and/or
175    * version) will default to generating either higher or lower quality images. That is, some might
176    * not have the "lower quality" libraries and others might not have the "higher quality"
177    * libraries.
178    */
179   @Parameter(property = "highQuality")
180   private Boolean highQuality;
181 
182   /**
183    * Evaluate all schemas in a database. Generates a high-level index of the schemas evaluated and
184    * allows for traversal of cross-schema foreign key relationships. Use with -schemaSpec
185    * "schemaRegularExpression" to narrow-down the schemas to include.
186    */
187   @Parameter(property = "showAllSchemas")
188   private Boolean showAllSchemas;
189 
190   /**
191    * Evaluate specified schemas. Similar to -showAllSchemas, but explicitly specifies which schema
192    * to evaluate without interrogating the database's metadata. Can be used with databases like
193    * MySQL where a database isn't composed of multiple schemas.
194    */
195   @Parameter(property = "schemas")
196   private String schemas;
197 
198   /** No schema required for this database (e.g. derby). */
199   @Parameter(property = "noSchema")
200   private Boolean noSchema;
201 
202   /** Don't query or display row counts. */
203   @Parameter(property = "noRows")
204   private Boolean noRows;
205 
206   /** Don't query or display row counts. */
207   @Parameter(property = "noViews")
208   private Boolean noViews;
209 
210   /**
211    * Specifies additional properties to be used when connecting to the database. Specify the entries
212    * directly, escaping the ='s with \= and separating each key\=value pair with a ;.
213    *
214    * <p>May also be a file name, useful for hiding connection properties from public logs.
215    */
216   @Parameter(property = "connprops")
217   private String connprops;
218 
219   /**
220    * Don't generate ads in reports.
221    *
222    * @deprecated will be removed
223    */
224   @Deprecated
225   @Parameter(property = "noAds", defaultValue = "true")
226   private Boolean noAds;
227 
228   /**
229    * Don't generate sourceforge logos in reports.
230    *
231    * @deprecated will be removed
232    */
233   @Deprecated
234   @Parameter(property = "noLogo", defaultValue = "true")
235   private Boolean noLogo;
236 
237   @Parameter(property = "catalog", defaultValue = "%")
238   private String catalog;
239 
240   /**
241    * The SchemaSpy analyser that generates the actual report. Can be overridden for testing
242    * purposes.
243    */
244   private MavenSchemaAnalyzer analyzer;
245 
246   protected void setSchemaAnalyzer(final MavenSchemaAnalyzer analyzer) {
247     this.analyzer = analyzer;
248   }
249 
250   /**
251    * Convenience method used to build the SchemaSpy command line parameters.
252    *
253    * @param argList the current list of SchemaSpy parameter options.
254    * @param parameter a new parameter to add
255    * @param value the value for this parameter
256    */
257   private void addToArguments(
258       final List<String> argList, final String parameter, final Boolean value) {
259     if (value != null && value) {
260       argList.add(parameter);
261       argList.add(String.valueOf(true));
262     }
263   }
264 
265   /**
266    * Convenience method used to build the SchemaSpy command line parameters.
267    *
268    * @param argList the current list of SchemaSpy parameter options.
269    * @param parameter a new parameter to add
270    * @param value the value for this parameter
271    */
272   private void addFlagToArguments(
273       final List<String> argList, final String parameter, final Boolean value) {
274     if (value != null && value) {
275       argList.add(parameter);
276     }
277   }
278 
279   /**
280    * Convenience method used to build the SchemaSpy command line parameters.
281    *
282    * @param argList the current list of SchemaSpy parameter options.
283    * @param parameter a new parameter to add
284    * @param value the value for this parameter
285    */
286   private void addToArguments(
287       final List<String> argList, final String parameter, final String value) {
288     if (value != null) {
289       argList.add(parameter);
290       argList.add(value);
291     }
292   }
293 
294   /**
295    * Convenience method used to build the SchemaSpy command line parameters.
296    *
297    * @param argList the current list of SchemaSpy parameter options.
298    * @param parameter a new parameter to add
299    * @param value the value for this parameter
300    */
301   private void addToArguments(
302           final List<String> argList, final String parameter, final int value, final int defaultValue) {
303     if (value != defaultValue) {
304       argList.add(parameter);
305       argList.add(String.valueOf(value));
306     }
307   }
308 
309   /**
310    * Generate the SchemaSpy report.
311    *
312    * @throws MavenReportException if SchemaSpy crashes
313    * @param locale the language of the report — currently ignored.
314    */
315   @Override
316   protected void executeReport(final Locale locale) throws MavenReportException {
317     File outputDir;
318     if (outputDirectory == null) {
319       // targetDirectory should be set by the maven framework. This is just for unit testing
320       // purposes
321       if (targetDirectory == null) {
322         targetDirectory = new File("target");
323       }
324       File siteDir = new File(targetDirectory, "site");
325       outputDir = new File(siteDir, "schemaspy");
326     } else {
327       outputDir = new File(outputDirectory, "schemaspy");
328     }
329     outputDir.mkdirs();
330 
331     String schemaSpyDirectory = outputDir.getAbsolutePath();
332     getLog().debug("SchemaSpy output directory: " + schemaSpyDirectory);
333 
334     List<String> argList = new ArrayList<>();
335 
336     addToArguments(argList, "-dp", pathToDrivers);
337     addToArguments(argList, "-db", database);
338     addToArguments(argList, "-host", host);
339     addToArguments(argList, "-port", port);
340     addToArguments(argList, "-t", databaseType);
341     addToArguments(argList, "-u", user);
342     addToArguments(argList, "-p", password);
343     if (null != schema && schema.contains(",")) {
344       addToArguments(argList, "-schemas", schema);
345     } else {
346       addToArguments(argList, "-s", schema);
347     }
348     addToArguments(argList, "-o", schemaSpyDirectory);
349     addToArguments(argList, "-desc", schemaDescription);
350     addToArguments(argList, "-i", includeTableNamesRegex);
351     addToArguments(argList, "-x", excludeColumnNamesRegex);
352     addFlagToArguments(argList, "-ahic", allowHtmlInComments);
353     addFlagToArguments(argList, "-noimplied", noImplied);
354     addFlagToArguments(argList, "-nohtml", noHtml);
355     addToArguments(argList, "-loglevel", logLevel);
356     addFlagToArguments(argList, "-norows", noRows);
357     addFlagToArguments(argList, "-noviews", noViews);
358     addFlagToArguments(argList, "-noschema", noSchema);
359     addFlagToArguments(argList, "-all", showAllSchemas);
360     addToArguments(argList, "-schemas", schemas);
361     addToArguments(argList, "-useDriverManager", useDriverManager);
362     addToArguments(argList, "-css", cssStylesheet);
363     addFlagToArguments(argList, "-sso", singleSignOn);
364     addFlagToArguments(argList, "-lq", lowQuality);
365     addFlagToArguments(argList, "-hq", highQuality);
366     addToArguments(argList, "-connprops", connprops);
367     addFlagToArguments(argList, "-cid", commentsInitiallyDisplayed);
368     addFlagToArguments(argList, "-noads", noAds);
369     addFlagToArguments(argList, "-nologo", noLogo);
370     addToArguments(argList, "-cat", catalog);
371     addFlagToArguments(argList, "-vizjs", vizjs);
372     addToArguments(argList, "-degree", degree, 2);
373     if (getLog().isDebugEnabled()) {
374       addFlagToArguments(argList, "-debug", true);
375     }
376     getLog().debug("SchemaSpy arguments: " + argList);
377 
378     try {
379       if (analyzer == null) {
380         analyzer = new MavenSchemaAnalyzer();
381       }
382       analyzer.applyConfiguration(argList);
383       analyzer.analyze();
384     } catch (Exception e) {
385       throw new MavenReportException(e.getMessage(), e);
386     }
387   }
388 
389   @Override
390   public boolean canGenerateReport() {
391     return !runOnExecutionRoot || project.isExecutionRoot();
392   }
393 
394   @Override
395   public String getDescription(final Locale locale) {
396     return "SchemaSpy database documentation";
397   }
398 
399   @Override
400   public String getName(final Locale locale) {
401     return "SchemaSpy";
402   }
403 
404   /**
405    * @deprecated use {@link #getOutputPath()} instead.
406    * @return the output path of the report
407    */
408   @Override
409   @Deprecated (since = "4.0.0")
410   public String getOutputName() {
411     return this.getOutputPath();
412   }
413 
414   @Override
415   public String getOutputPath() {
416     return "schemaspy/index";
417   }
418 
419   /**
420    * Always return {@code true} as we're using the report generated by SchemaSpy rather than
421    * creating our own report.
422    *
423    * @return {@code true}
424    */
425   @Override
426   public boolean isExternalReport() {
427     return true;
428   }
429 }