package com.github.wwadge.hbnpojogen;
import com.github.wwadge.hbnpojogen.obj.Clazz;
import com.github.wwadge.hbnpojogen.obj.GeneratorEnum;
import org.apache.maven.plugin.logging.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* Hibernate Pojo Generator.
* <p>
* Usage: Edit the input xml (see sample.xml for a sample) and pass it as an arg
*
* @author wallacew
*/
public class HbnPojoGen {
/**
* Config file setting
*/
static String jdbcConnection;
/**
* Config file setting
*/
static String driver;
private static Log outputLogger;
private static Set<String> errors = new HashSet<String>();
public static URL[] getSkeletonURL(String source) throws IOException {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Enumeration[] e = new Enumeration[]{
cl.getResources(source),
};
Set all = new LinkedHashSet();
URL url;
URLConnection conn;
JarFile jarFile = null;
for (int i = 0, s = e.length; i < s; ++i) {
while (e[i].hasMoreElements()) {
url = (URL) e[i].nextElement();
conn = url.openConnection();
conn.setUseCaches(false);
conn.setDefaultUseCaches(false);
if (conn instanceof JarURLConnection) {
jarFile = ((JarURLConnection) conn).getJarFile();
}
if (jarFile != null) {
searchJar(cl, all, jarFile);
} else {
searchDir(all, new File(URLDecoder.decode(url.getFile(), "UTF-8")));
}
}
}
URL[] urlArray = (URL[]) all.toArray(new URL[all.size()]);
return urlArray;
}
private static boolean searchDir(Set result, File file)
throws IOException {
if (file.exists() && file.isDirectory()) {
File[] fc = file.listFiles();
String path;
URL src;
for (int i = 0; i < fc.length; i++) {
path = fc[i].getAbsolutePath();
if (fc[i].isDirectory()) {
result.add(fc[i].toURI().toURL());
searchDir(result, fc[i]);
}
result.add(fc[i].toURI().toURL());
}
return true;
}
return false;
}
private static void searchJar(ClassLoader cl, Set result, JarFile file) throws IOException {
Enumeration e = file.entries();
JarEntry entry;
String name;
while (e.hasMoreElements()) {
try {
entry = (JarEntry) e.nextElement();
} catch (Throwable t) {
continue;
}
name = entry.getName();
Enumeration e2 = cl.getResources(name);
while (e2.hasMoreElements()) {
result.add(e2.nextElement());
}
}
}
/**
* This is the core function
*
* @param targetFolder Where files are to be created
* @param dbmd DB Connection meta data
* @param dbCatalog Database catalog
* @param connection Connection to use
* @throws ClassNotFoundException
* @throws SQLException
* @throws IOException
* @throws Exception
*/
private static void sync(final String targetFolder, final DatabaseMetaData dbmd, final String dbCatalog, final Connection connection)
throws ClassNotFoundException, SQLException, IOException, Exception {
long startTime = System.currentTimeMillis();
log("Stage 1: Copying skeletons");
URL[] urls = getSkeletonURL("skeleton");
SyncUtils.copyDirectory(urls, targetFolder);
BufferedReader br = new BufferedReader(new InputStreamReader(HbnPojoGen.class.getResource("/synchronizer.version").openStream()));
State.getInstance().setSynchronizerVersion(br.readLine());
br.close();
//SyncUtils.copyDirectory(new File("skeleton"), targetFolder);
// Fetch the commit order of the db, this makes creation of test cases easier later on
// as dependencies would have already been resolved.
LinkedList<String> commitOrder = new LinkedList<String>();
LinkedList<String> tmp = null;
tmp = new LinkedList<String>();
switch (State.getInstance().schemaRestrict) {
case 0: // RESTRICT
log("Stage 2: Getting commit order in 'RESTRICT' strategy");
State.getInstance().commitResult = SyncUtils.getCommitOrder(connection, true, false);
tmp.addAll(State.getInstance().commitResult.getCommitList());
break;
case 1: // PARTIAL
log("Stage 2: Getting commit order in 'PARTIAL' strategy");
State.getInstance().commitResult = SyncUtils.getCommitOrder(connection, false, false);
tmp.addAll(State.getInstance().commitResult.getCommitList());
break;
case 2: // FULL
log("Stage 2: Getting commit order in 'FULL' strategy");
State.getInstance().commitResult = SyncUtils.getCommitOrder(connection, false, true);
tmp.addAll(State.getInstance().commitResult.getCommitList());
break;
case 3: // ALL
log("Stage 2: Getting commit order in 'ALL' strategy");
tmp = new LinkedList<String>();
State.getInstance().commitResult = SyncUtils.getCompleteCommitOrder(connection);
tmp.addAll(State.getInstance().commitResult.getCommitList());
break;
default:
State.getInstance().commitResult = new SyncUtils.CommitResults();
HbnPojoGen.logE("Stage 2: Getting commit order in [Error: Unknown commit order.]");
break;
}
// log(State.getInstance().commitResult);
// find loops
State.getInstance().commitResult.buildCycleList();
int ignoredTables = 0;
for (String table : tmp) {
if (!State.getInstance().ignoreTableList.contains(table.toUpperCase()) &&
(!State.getInstance().ignoreTableList.contains(SyncUtils.getTableCatalog(table).toUpperCase() + ".*")) &&
(!State.getInstance().ignoreTableList.contains("*." + SyncUtils.getTableName(table).toUpperCase()))) {
commitOrder.add(table);
} else {
ignoredTables++;
}
State.getInstance().catalogs.add(SyncUtils.getTableCatalog(table));
if (State.getInstance().dbMode == 2) { // postgresql
State.getInstance().schemas.add(SyncUtils.getTableSchema(table));
} else {
State.getInstance().schemas.add(SyncUtils.getTableCatalog(table));
}
}
// Create all target folders
String srcFolder = targetFolder + "/" + State.getInstance().getSrcFolder() + "/" + State.getInstance().topLevel.replaceAll("\\.", "/");
// Read in our table model
log("Stage 3: Parsing tables");
Core.parseTables(dbmd, dbCatalog, connection, commitOrder);
log("Stage 4: Building object model");
Core.buildObjectModel(State.getInstance().classes, commitOrder);
int tmpCount = 0;
for (byte b : State.getInstance().getProjectName().getBytes()) {
tmpCount++;
VelocityWriters.serialCount += 10 * tmpCount * b; //
}
// Write all the classes
log("Stage 5: Writing classes");
VelocityWriters.writeClasses(targetFolder, State.getInstance().classes);
log("Stage 6: Writing interfaces" + (State.getInstance().isSkipModelInterfaces() ? " [disabled]" : ""));
if (!State.getInstance().isSkipModelInterfaces()) {
VelocityWriters.writeInterfaceClasses(targetFolder, State.getInstance().classes);
}
// Dump the enums
log("Stage 7: Writing enums");
VelocityWriters.writeEnums(SyncUtils.getConfigPackage("DEFAULT", PackageTypeEnum.ENUM_TARGET_BASE));
VelocityWriters.writeSubtypeEnums(SyncUtils.getConfigPackage("DEFAULT", PackageTypeEnum.ENUM_TARGET_BASE));
log("Stage 8: Writing DAO Factory classes");
VelocityWriters.writeOutDaoFactoryClass(State.getInstance().classes, targetFolder, State.getInstance().schemas);
log("Stage 9: Writing Spring, EhCache configs, etc");
if (!State.getInstance().isDisableApplicationContext()) {
VelocityWriters.writeSpringApplicationContext(targetFolder, State.getInstance().classes, dbCatalog);
}
// VelocityWriters.writeAntBuildFile(targetFolder, dbCatalog);
VelocityWriters.writeEHCache(targetFolder);
VelocityWriters.writeUtils(targetFolder);
if (!State.getInstance().isPropertyPlaceholderConfigurerSuppressBean()) {
VelocityWriters.writeSpringOverrideFile(targetFolder);
}
log("Stage 10: Writing DAO Test classes");
VelocityWriters.writeOutDaoTestClass(targetFolder, State.getInstance().classes, commitOrder, srcFolder);
log("Stage 11: Writing Data pool Factory classes");
VelocityWriters.writeOutDataPoolFactoryClass(State.getInstance().classes, targetFolder, State.getInstance().schemas);
log("Stage 12: Writing Data layer helpers");
VelocityWriters.writeOutDataLayerHelpers(targetFolder, State.getInstance().classes, State.getInstance().schemas);
if (!State.getInstance().isMavenEnabled() || !State.getInstance().isMavenPomEnabled()) {
log("Stage 13: Writing Maven pom.xml [Disabled]");
} else {
log("Stage 13: Writing Maven pom.xml");
VelocityWriters.writeMavenPom(targetFolder);
}
log("Stage 14: Writing DB Version Check helpers");
if (State.getInstance().isVersionCheckEnabled()) {
VelocityWriters.writeOutDBVersionCheck(targetFolder, State.getInstance().classes, State.getInstance().schemas);
}
if (State.getInstance().isEnableMockitoBeans()) {
log("Stage 15: Writing mockito test context file");
VelocityWriters.writeOutMockitoBean(targetFolder, State.getInstance().classes, State.getInstance().schemas);
}
printStatistics(State.getInstance().classes, ignoredTables);
long endTime = System.currentTimeMillis() - startTime;
log("");
log("All done. Time taken: " + endTime / 1000 + " sec");
// Save what we found out, for easier re-use in other libraries.
if (State.getInstance().isEnableStateSave()) {
log("Dumping state to disk as requested by config file");
State.getInstance().serializeState(targetFolder + File.separator + State.getInstance().getResourceFolder() + File.separator + "synchronizer.state");
}
log("Output written to : " + targetFolder);
}
/**
* @param classes
* @param ignoredTables
*/
private static void printStatistics(TreeMap<String, Clazz> classes, int ignoredTables) {
int embeddable = 0;
int subclasses = 0;
int superclasses = 0;
int jointables = 0;
int hiddenJoinTables = 0;
int nameClashed = 0;
int properties = 0;
int fields = 0;
int abstractClasses = 0;
for (Clazz clazz : classes.values()) {
if (clazz.isEmbeddable()) {
embeddable++;
}
if (clazz.isSubclass()) {
subclasses++;
}
if (clazz.isSuperclass()) {
superclasses++;
}
if (clazz.isJoinTable()) {
jointables++;
}
if (clazz.isHiddenJoinTable()) {
hiddenJoinTables++;
}
if (clazz.isNameAmbiguityPossible()) {
nameClashed++;
}
if (clazz.isAbstractClass()) {
abstractClasses++;
}
fields += clazz.getTableObj().getFields().size();
properties += clazz.getProperties().size();
}
log(String.format("Tables parsed : %d", State.getInstance().tables.size()));
log(String.format("Classes written : %d", classes.size()));
log(String.format("Table Fields : %d", fields));
log(String.format("Properties : %d", properties));
log(String.format("Embeddable classes : %d", embeddable));
log(String.format("Sub Classes : %d", subclasses));
log(String.format("Super Classes : %d", superclasses));
log(String.format("Join Tables : %d", jointables));
log(String.format("Hidden Join Tables : %d", hiddenJoinTables));
log(String.format("Class Name clashes : %d", nameClashed));
log(String.format("Tables ignored : %d", ignoredTables));
log(String.format("Abstract Classes : %d", abstractClasses));
if (!State.getInstance().noOutPutForSchemaList.isEmpty()) {
log("Warning: Config file specifies no output wanted for schemas: " + State.getInstance().noOutPutForSchemaList);
log("Warning: Compilation errors may result");
}
if (!State.getInstance().noOutPutForExceptSchemaList.isEmpty()) {
log("Warning: Config file specifies no output wanted for any schemas except: " + State.getInstance().noOutPutForExceptSchemaList);
log("Warning: Compilation errors may result");
}
}
/**
* @param generator
* @return enum equivalent
*/
static GeneratorEnum mapGeneratorType(String generator) {
if (generator.equalsIgnoreCase("AUTO")) {
return GeneratorEnum.AUTO;
}
if (generator.equalsIgnoreCase("UUID")) {
return GeneratorEnum.UUID;
}
if (generator.equalsIgnoreCase("UUID-WITHOUTDASHES")) {
return GeneratorEnum.UUIDWithoutDashes;
}
if (generator.equalsIgnoreCase("GUID")) {
return GeneratorEnum.GUID;
}
if (generator.equalsIgnoreCase("CUSTOM")) {
return GeneratorEnum.CUSTOM;
}
if (generator.equalsIgnoreCase("IDAWARE")) {
return GeneratorEnum.IDAWARE;
}
if (generator.equalsIgnoreCase("PKS")) {
return GeneratorEnum.PKS;
}
return null;
}
/**
* @param generator
* @return true/false
*/
static boolean isValidGenerator(String generator) {
return mapGeneratorType(generator) != null;
}
/**
* @param log
*/
public static void setLog(Log log) {
outputLogger = log;
}
public static void log(String s) {
if (outputLogger == null) {
System.out.println(s);
} else {
outputLogger.info(s);
}
}
public static void logE(String s) {
if (!errors.contains(s)) {
if (outputLogger == null) {
System.err.println(s);
} else {
outputLogger.error(s);
}
errors.add(s);
}
}
public static void run(String config, String overridePath, String overrideIP) {
try {
String path = Config.parseConfig(config, overridePath, overrideIP);
log("Hibernate POJO Generator\n");
log("Reading from config: " + new File(config).getAbsolutePath());
// Setup the DB connection
Class.forName(driver);
Properties props = new Properties();
props.put("user", State.getInstance().dbUsername);
props.put("password", State.getInstance().dbPassword);
props.put("useInformationSchema", "false");
Connection connection = DriverManager.getConnection(jdbcConnection, props);
State.getInstance().dbCatalog = connection.getCatalog();
DatabaseMetaData dbmd = connection.getMetaData();
sync(path, dbmd, State.getInstance().dbCatalog, connection);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Main method
*
* @param args
*/
public static void main(String[] args) {
if (args.length < 1) {
log("This will generate: Source files (1 per table - including the annotations), enums, dao, test cases");
log("Syntax: hbnSync configfile.xml [target-dir (override)] [ipaddress to use as source (override)] ");
log("Eg: java -jar hbnSync.jar config.xml /var/tmp 10.0.0.2");
log("Edit the templates (templates/*) and regenerate if the style is not to your liking");
System.exit(1);
}
System.setProperty("line.separator", "\n");
String overridePath = null;
String overrideIP = null;
String config = args[0];
if (args.length > 1) {
overridePath = args[1];
}
if (args.length > 2) {
overrideIP = args[2];
}
run(config, overridePath, overrideIP);
}
}