org.tentackle.maven.sql.AbstractSqlMojo Maven / Gradle / Ivy
Show all versions of tentackle-sql-maven-plugin Show documentation
/**
* Tentackle - http://www.tentackle.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.tentackle.maven.sql;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.model.fileset.FileSet;
import org.apache.maven.shared.model.fileset.util.FileSetManager;
import org.tentackle.maven.AbstractTentackleMojo;
import org.tentackle.model.Entity;
import org.tentackle.model.EntityAliases;
import org.tentackle.model.Model;
import org.tentackle.model.ModelDefaults;
import org.tentackle.model.ModelException;
import org.tentackle.model.print.EntityPrinter;
import org.tentackle.model.print.PrintConfiguration;
import org.tentackle.sql.Backend;
import org.tentackle.sql.BackendFactory;
import org.tentackle.sql.BackendInfo;
import org.wurbelizer.misc.Settings;
/**
* Base tentackle sql mojo.
*
* @author harald
*/
public abstract class AbstractSqlMojo extends AbstractTentackleMojo {
/**
* The Maven Project.
*/
@Parameter(defaultValue = "${project}",
readonly = true,
required = true)
protected MavenProject mvnProject;
/**
* Directory holding the model files to be processed.
*/
@Parameter(defaultValue = "${project.build.directory}/wurbel/model",
property = "tentackle.modelDir")
protected File modelDir;
/**
* The model defaults.
*/
@Parameter(property = "tentackle.modelDefaults")
protected String modelDefaults;
/**
* The entity aliases.
*/
@Parameter(property = "tentackle.entityAliases")
protected String entityAliases;
/**
* Map schema names to flat table names.
*/
@Parameter(property = "tentackle.mapSchemas")
protected boolean mapSchemas;
/**
* Directory holding the SQL generated scripts.
*/
@Parameter(defaultValue = "${project.build.directory}/sql",
property = "tentackle.sqlDir")
protected File sqlDir;
/**
* The backend property files.
*/
@Parameter
protected List backendProperties;
/**
* The backend names.
* Separated by commas.
* "all" means generate code for all backends found in classpath.
*/
@Parameter
protected String backendNames;
/**
* Backend configuration via pom if not from property files.
*/
@Parameter
protected List backends;
/**
* Optional directory where to dump the model.
* Useful for debugging to verify that the model was interpreted correctly
* or to pretty-print the model source.
*/
@Parameter
protected File dumpDir;
/**
* Model dump should be dumped as comment blocks (default).
*/
@Parameter(defaultValue = "true")
protected boolean dumpAsComment;
/**
* Model dump should include variables (default).
*/
@Parameter(defaultValue = "true")
protected boolean dumpVariables;
/**
* Minimum number of spaces between columns in attribute section (default = 2).
*/
@Parameter(defaultValue = "2")
protected int dumpColumnGap;
/**
* Optional annotations that should be printed as attribute options.
*/
@Parameter
protected String dumpAnnotationsAsOptions;
/**
* total number of errors.
*/
protected int totalErrors;
/**
* The backends to create SQL code for.
*
* Maps the backend-name to the backend.
*/
protected Map backendInfos;
/**
* Connectable backends to create SQL code for.
*
* Maps the jdbc-Url to the backend.
*/
protected Map connectableBackendInfos;
/**
* Migration hints per jdbc-Url.
*/
protected Map migrationHints;
/**
* The created SQL file.
*/
protected File sqlFile;
/**
* Output directory for diagnostics
*/
protected String sqlDirName;
/**
* Writer to the SQL file.
*/
protected Writer sqlWriter;
/**
* Gets the created sql file name without leading path.
*
* @return the filename
*/
protected abstract String getSqlFileName();
/**
* The connection to the backend.
*/
private Connection connection;
/**
* Gets the open connection.
* Opens it if it is closed.
*
* @param backendInfo the connection info
* @return the open connection
* @throws MojoExecutionException if connection failed
*/
protected Connection getConnection(BackendInfo backendInfo) throws MojoExecutionException {
if (connection == null) {
try {
connection = backendInfo.connect();
}
catch (SQLException sqx) {
throw new MojoExecutionException("cannot connect to " + backendInfo, sqx);
}
}
return connection;
}
/**
* Gets the model defaults.
*
* @return the defaults, null if none
* @throws MojoExecutionException if parsing the model defaults failed
*/
protected ModelDefaults getModelDefaults() throws MojoExecutionException {
if (modelDefaults == null) {
return null;
}
try {
return new ModelDefaults(modelDefaults);
}
catch (ModelException mex) {
throw new MojoExecutionException(mex.getMessage(), mex);
}
}
/**
* Gets the entity aliases.
*
* @return the aliases, null if none
* @throws MojoExecutionException if parsing the aliases failed
*/
protected EntityAliases getEntityAliases() throws MojoExecutionException {
if (entityAliases == null) {
return null;
}
try {
return new EntityAliases(entityAliases);
}
catch (ModelException mex) {
throw new MojoExecutionException(mex.getMessage(), mex);
}
}
/**
* Creates the sqlFile, sqlDirName and sqlWriter.
*
* @param backendInfo the backend info
* @throws MojoExecutionException if sql file could not be created
*/
protected void createSqlFile(BackendInfo backendInfo) throws MojoExecutionException {
sqlFile = new File(new File(sqlDir, backendInfo.getBackend().getName()), getSqlFileName());
try {
// create missing directories, if any
File dir = sqlFile.getParentFile();
sqlDirName = getCanonicalPath(dir);
dir.mkdirs();
// create the SQL output file
sqlWriter = Files.newBufferedWriter(sqlFile.toPath(), Settings.encodingCharset,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING);
}
catch (IOException iox) {
throw new MojoExecutionException("cannot create sql file " + sqlFile.getAbsolutePath(), iox);
}
}
/**
* Opens the resources such as session and creates the SQL file.
*
* @param backendInfo the backend info
* @throws MojoExecutionException if sql file could not be created
*/
protected void openResources(BackendInfo backendInfo) throws MojoExecutionException {
createSqlFile(backendInfo);
}
/**
* Closes the resources.
*
* @param backendInfo the backend info
* @throws MojoExecutionException if closing failed
*/
protected void closeResources(BackendInfo backendInfo) throws MojoExecutionException {
if (connection != null) {
try {
connection.close();
connection = null;
}
catch (SQLException sqx) {
throw new MojoExecutionException(
"cannot close connection " + connection, sqx);
}
}
if (sqlWriter != null) {
try {
sqlWriter.close();
sqlWriter = null;
}
catch (IOException iox) {
throw new MojoExecutionException("cannot close sql file " + sqlFile.getAbsolutePath(), iox);
}
}
}
/**
* Process all files in a fileset.
*
* @param backendInfo the backend info
* @param fileSet the fileset
* @throws MojoExecutionException if processing failed
*/
protected abstract void processFileSet(BackendInfo backendInfo, FileSet fileSet) throws MojoExecutionException;
@Override
protected void validate() throws MojoExecutionException {
mavenProject = mvnProject; // fix for mavenProject is null, for whatever reason???
super.validate();
if (modelDir == null) {
throw new MojoExecutionException("missing tentackle.modelDir");
}
if (modelDir.getPath().contains("${")) {
throw new MojoExecutionException("undefined variable(s) in modelDir: " + modelDir.getPath());
}
if (sqlDir == null) {
throw new MojoExecutionException("missing tentackle.sqlDir");
}
if (sqlDir.getPath().contains("${")) {
throw new MojoExecutionException("undefined variable(s) in sqlDir: " + sqlDir.getPath());
}
findResourceDirs();
backendInfos = new HashMap<>();
connectableBackendInfos = new HashMap<>();
migrationHints = new HashMap<>();
if (backendNames != null) {
if ("all".equalsIgnoreCase(backendNames)) {
for (Backend backend: BackendFactory.getInstance().getAllBackends()) {
backendInfos.put(backend.getName(), new BackendInfo(backend));
}
}
else {
for (String backendName: backendNames.split(",")) {
BackendInfo backendInfo = new BackendInfo(backendName.trim());
backendInfos.put(backendInfo.getBackend().getName(), backendInfo);
}
}
}
if (backendProperties != null) {
// determine URL from property file(s)
for (FileSet fileset: backendProperties) {
FileSetManager fileSetManager = new FileSetManager(getLog(), verbosityLevel.isDebug());
List propertiesDirNames;
if (fileset.getDirectory() == null) {
propertiesDirNames = resourceDirs; // try all resource directories
if (propertiesDirNames == null || propertiesDirNames.isEmpty()) {
throw new MojoExecutionException(
"no given in of and no resource directories found");
}
}
else {
propertiesDirNames = new ArrayList<>();
propertiesDirNames.add(fileset.getDirectory()); // use given directory
}
for (String propertiesDirName: propertiesDirNames) {
fileset.setDirectory(propertiesDirName);
String[] fileNames = fileSetManager.getIncludedFiles(fileset);
if (fileNames.length > 0) {
for (String filename : fileNames) {
File propertiesFile = new File(new File(propertiesDirName), filename);
Properties props = new Properties();
try (InputStream is = new FileInputStream(propertiesFile)) {
getLog().debug("loading property file " + propertiesFile.getAbsolutePath());
props.load(is);
BackendInfo backendInfo = new BackendInfo(props);
// props infos will replace those loaded by name only
backendInfos.put(backendInfo.getBackend().getName(), backendInfo);
connectableBackendInfos.put(backendInfo.getUrl(), backendInfo);
}
catch (FileNotFoundException nfe) {
throw new MojoExecutionException("db properties file " + propertiesFile.getAbsolutePath() + " not found", nfe);
}
catch (IOException iox) {
throw new MojoExecutionException("reading " + propertiesFile.getPath() + " failed", iox);
}
}
}
}
}
}
if (backends != null && !backends.isEmpty()) {
for (BackendInfoParameter backendParameter: backends) {
BackendInfo backendInfo = backendParameter.createBackendInfo(getLog());
backendInfos.put(backendInfo.getBackend().getName(), backendInfo);
if (backendInfo.isConnectable()) {
connectableBackendInfos.put(backendInfo.getUrl(), backendInfo);
migrationHints.put(backendInfo.getUrl(),
backendParameter.loadMigrationHints(backendInfo, getLog(), verbosityLevel.isDebug(), resourceDirs));
}
}
}
}
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
// validate configuration
validate();
totalErrors = 0;
for (BackendInfo backendInfo: getBackendInfosToExecute()) {
getLog().debug("processing " + backendInfo);
openResources(backendInfo);
try {
// process files
if (filesets != null && filesets.size() > 0) {
// explicit filesets given instead of model dir
for (FileSet fileSet : filesets) {
processFileSet(backendInfo, fileSet);
}
}
else {
// all from model dir
String[] files = modelDir.isDirectory() ? modelDir.list() : null;
if (files != null && files.length > 0) {
final FileSet fs = new FileSet();
fs.setDirectory(modelDir.getPath());
processFileSet(backendInfo, fs);
}
else {
getLog().warn("no or empty modelDir " + modelDir.getAbsolutePath());
}
}
if (totalErrors > 0) {
throw new MojoFailureException(totalErrors + " model errors");
}
}
finally {
closeResources(backendInfo);
}
}
if (dumpDir != null) {
dumpDir.mkdirs();
try {
ModelDefaults defaults = modelDefaults == null ? null : new ModelDefaults(modelDefaults);
List optionAnnotations = new ArrayList<>();
if (dumpAnnotationsAsOptions != null) {
StringTokenizer stok = new StringTokenizer(dumpAnnotationsAsOptions, " ,");
while (stok.hasMoreTokens()) {
optionAnnotations.add(stok.nextToken());
}
}
PrintConfiguration configuration = new PrintConfiguration(dumpAsComment, dumpVariables, defaults,
optionAnnotations, dumpColumnGap);
for (Entity entity: Model.getInstance().getAllEntitites()) {
File dumpFile = new File(dumpDir, entity.getName() + ".txt");
try (BufferedWriter writer =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(dumpFile), Settings.encodingCharset))) {
writer.write(new EntityPrinter(entity, configuration).print());
}
}
}
catch (IOException | ModelException ex) {
throw new MojoFailureException("creating model dump failed", ex);
}
}
}
/**
* Gets the backend infos to execute.
*
* @return the backend infos
*/
protected abstract Collection getBackendInfosToExecute();
/**
* Writes the intro for a model to the SQL file.
*
* @param backendInfo the backend infor
* @param modelDirName the model directory name
* @throws MojoExecutionException if writing to SQL file failed
*/
protected void writeModelIntroComment(BackendInfo backendInfo, String modelDirName) throws MojoExecutionException {
try {
sqlWriter.append("\n-- created from ");
sqlWriter.append(getPathRelativeToBasedir(modelDirName));
sqlWriter.append(" at ");
sqlWriter.append(new Date().toString());
sqlWriter.append(" by ");
sqlWriter.append(System.getProperty("user.name"));
sqlWriter.append(" on ");
sqlWriter.append(getHostName());
sqlWriter.append("\n-- backend is ");
sqlWriter.append(backendInfo.toString());
sqlWriter.append('\n');
}
catch (IOException iox) {
throw new MojoExecutionException("cannot write to sql file " + sqlFile.getAbsolutePath(), iox);
}
}
/**
* Gets the hostname.
*
* @return the hostname
*/
private String getHostName() {
String hostName;
try {
// may fail if host is misconfigured
hostName = InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException ex) {
// return localhost
hostName = InetAddress.getLoopbackAddress().getHostAddress();
}
return hostName;
}
}