package squill.mgen;
import static java.lang.String.format;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.resources.FileResource;
import squill.mgen.ClassInspectionUtil.ClassInfo;
import squill.mgen.naming.CamelCaseNaming;
import squill.mgen.naming.NamingStrategy;
import squill.mgen.naming.PropertyOverrideNamingStrategy;
import squill.util.FileUtil;
public class SquillMappingsTask extends Task implements MessageLogger {
private static final String NAMING_STRATEGY_LOOKUP_PACKAGE = "squill.mgen.naming.";
/**
* Database JDBC URL.
*/
private String url;
/**
* Database driver.
*/
private String driver;
/**
* Database user name.
*/
private String user;
/**
* Database password
*/
private String password;
/**
* Database schema
*/
private String schema;
/**
* Package name for model objects
*/
private String packageName;
/**
* Path to directory where to output files into
*/
private String outputPath;
/**
* Path to the Velocity template file used for generating classes
*/
private String templateFile;
/**
* The strategy for translating database table, view or column names into
* names of corresponding Squill mappings.
*/
private String namingStrategy;
/**
* Path to a properties file which contains an optional mapping from the
* table/column names to class/filed names.
*/
private File namingOverride;
/**
* Path to a properties file which contains exclude rules to exclude some
* tables/columns from generating mappings to them
*/
private File excludeRules;
private Map<String, File> modelClassFiles = new HashMap<String, File>();
public static URL[] classpathURLs;
private static final String DEFAULT_STRATEGY_CLASS = CamelCaseNaming.class.getName();
public void addConfiguredFileSet(FileSet fs) {
for (Iterator<FileResource> i = fs.iterator(); i.hasNext();) {
File file = i.next().getFile();
if (file.getName().endsWith(".class")) {
modelClassFiles.put(file.getName().substring(0, file.getName().length() - 6), file);
}
}
}
public static void main(String[] args) {
SquillMappingsTask task = new SquillMappingsTask();
task.setDriver("org.hsqldb.jdbcDriver");
task.setUrl("jdbc:hsqldb:hsql://localhost/squilldemodb");
task.setSchema("PUBLIC");
task.setUser("SA");
task.setPackageName("simple.data");
task.setOutputPath("src");
task.perform();
}
@Override
public void execute() throws BuildException {
info("Starting to generate Squill mappings out of database");
try {
long time = trace(null, 0L);
DatabaseInfoReader databaseInfoReader = new DatabaseInfoReader(driver, url, user, password, schema, this);
Collection<DbTable> tables = databaseInfoReader.gatherDatabaseInfo();
time = trace("Gathering database information", time);
if (tables.size() > 0) {
generateMappings(tables, loadNamingStrategy(), loadExcludeRules());
trace("Generating mappings", time);
}
} catch (Exception e) {
e.printStackTrace();
throw new BuildException("Error creating mapping files..." + e, e);
}
info("Finished mappings task");
}
private Set<String> loadExcludeRules() {
if (getExcludeRules() == null) {
return null;
}
FileInputStream inStream = null;
try {
Properties exludeRulesProperties = new Properties();
inStream = new FileInputStream(getExcludeRules());
exludeRulesProperties.load(inStream);
// Properties.stringPropertyNames() is not used to provide
// compatibility with Java5
Set<String> stringPropertyNames = new HashSet<String>();
Enumeration<?> propertyNames = exludeRulesProperties.propertyNames();
while (propertyNames.hasMoreElements()) {
String name = (String) propertyNames.nextElement();
stringPropertyNames.add(name.toLowerCase());
}
return stringPropertyNames;
} catch (IOException e) {
throw new RuntimeException("Could not load exclude rules properties from '" + getExcludeRules() + "'", e);
} finally {
try {
if (inStream != null) {
inStream.close();
}
} catch (IOException e) {
throw new IllegalStateException("Error closing stream ", e);
}
}
}
private long trace(final String msg, final long time) {
if (msg != null && time != 0L) {
final long delta = System.nanoTime() - time;
log(format("%s took %.2f ms.", msg, (double) delta / (1000 * 1000)), Project.MSG_INFO);
}
return System.nanoTime();
}
/**
* @return Class that provides conversion between database names and type
* names
*/
@SuppressWarnings("unchecked")
private NamingStrategy loadNamingStrategy() {
String strategyClassName = getStrategyClassName();
return strategyInstance(strategyClassName);
}
@SuppressWarnings("unchecked")
private NamingStrategy strategyInstance(final String strategyClassName) {
try {
final Class<? extends NamingStrategy> strategyClass = (Class<? extends NamingStrategy>) Class.forName(strategyClassName);
return addOverrides(strategyClass.newInstance());
} catch (Exception e) {
error("Error", e);
throw new BuildException(format("Problem loading naming strategy '%s' from class '%s'.", getNamingStrategy(), strategyClassName), e);
}
}
private String getStrategyClassName() {
final String strategyName = getNamingStrategy();
String strategyClassName = DEFAULT_STRATEGY_CLASS;
if (strategyName != null && strategyName.trim().length() > 0) {
strategyClassName = strategyName.contains(".") ? strategyName : NAMING_STRATEGY_LOOKUP_PACKAGE + strategyName;
}
return strategyClassName;
}
private NamingStrategy addOverrides(NamingStrategy strategy) {
if (getNamingOverride() == null) {
return strategy;
}
FileInputStream inStream = null;
try {
Properties namingOverrideProperties = new Properties();
inStream = new FileInputStream(getNamingOverride());
namingOverrideProperties.load(inStream);
return new PropertyOverrideNamingStrategy(strategy, namingOverrideProperties);
} catch (IOException e) {
throw new RuntimeException("Could not load naming override properties from '" + getNamingOverride() + "'", e);
} finally {
try {
if (inStream != null) {
inStream.close();
}
} catch (IOException e) {
throw new IllegalStateException("Error closing stream ", e);
}
}
}
/**
* Generate mapping files based on the database information
*
* @param dbTableList List of database tables/views
* @param excludeRules Set of exclude rules
*/
public void generateMappings(Collection<DbTable> dbTableList, NamingStrategy namingStrategy, Set<String> excludeRules) {
info("Using naming strategy: " + namingStrategy.getClass());
TableCodeGenerator codeGenerator = new VelocityTableCodeGenerator(getTemplateFile());
final String packageName = getPackageName();
for (DbTable curTable : dbTableList) {
info("Processing table: " + curTable.getName());
if (excludeRules == null || !excludeRules.contains(curTable.getName().toLowerCase())) {
long time = trace("generate java code", 0L);
ClassInfo modelClass = null;
String modelTypeName = namingStrategy.getTypeName(curTable.getName());
info("Searching for the model " + modelTypeName);
if (modelClassFiles.containsKey(modelTypeName)) {
modelClass = ClassInspectionUtil.inspectClass(modelClassFiles, modelTypeName);
info("Found corresponding model file: " + modelClass.getName());
}
MapTable mapTable = new MapTable(curTable, namingStrategy, modelClass, excludeRules, this); // Mappings
// container
String tableJavaCode = codeGenerator.generateJavaTableCode(packageName, mapTable);
time = trace("generate java code", time);
FileUtil.writeFile(tableJavaCode, FileUtil.javaFile(getOutputPath(), getPackageName(), mapTable.getJavaName()));
trace("write file", time);
} else {
info("Skipping table " + curTable.getName() + " because of the exclude rules.");
}
}
}
public String getTemplateFile() {
if (templateFile != null) {
return templateFile;
}
return FileUtil.path(getClass()) + "Model";
}
public void setTemplateFile(final String templateFile) {
this.templateFile = templateFile;
}
public void setUrl(String url) {
this.url = url;
}
public void setDriver(String driver) {
this.driver = driver;
}
public void setUser(String user) {
this.user = user;
}
public void setPassword(String password) {
this.password = password;
}
public void setSchema(String schema) {
this.schema = schema;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public void setOutputPath(String outputPath) {
this.outputPath = outputPath;
}
public String getOutputPath() {
String lastChar = outputPath.substring(outputPath.length() - 1, outputPath.length());
if ("${output.path}".equalsIgnoreCase(outputPath)) {
return "output/";
} else if ("/".equals(lastChar) || "\\".equals(lastChar)) {
return outputPath;
}
return outputPath + "/";
}
public String getPackageName() {
if ("${java.package}".equals(packageName) || (this.packageName != null && this.packageName.trim().length() == 0)) {
return null;
} else {
return packageName;
}
}
public void warning(String message) {
log(message, Project.MSG_WARN);
}
public void info(String message) {
log(message, Project.MSG_INFO);
}
public void error(String message, Throwable t) {
log(message, t, Project.MSG_ERR);
}
public String getNamingStrategy() {
return namingStrategy;
}
public void setNamingStrategy(String namingStrategy) {
this.namingStrategy = namingStrategy;
}
public File getNamingOverride() {
return namingOverride;
}
public void setNamingOverride(File namingOverride) {
this.namingOverride = namingOverride;
}
public void setExcludeRules(File excludeRules) {
this.excludeRules = excludeRules;
}
public File getExcludeRules() {
return excludeRules;
}
}