blob: 220b06d5242094e1a379722c6205d245c119ed26 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.empire.db.codegen;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import org.apache.empire.db.DBDatabase;
import org.apache.empire.db.DBTable;
import org.apache.empire.db.DBView;
import org.apache.empire.db.codegen.util.FileUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.log.CommonsLogLogChute;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is the entry class for generating the java persistence model based on a
* database schema. It uses the Empire DB open-source framework to build a java
* persistence layer for an application. The Apache Velocity template engine is
* used to create the output interfaces and classes.
* <p>
* The Empire DB framework doesn't try to hide the underlying database and data
* model but instead embraces its power by modeling it within java. The result
* is a persistence layer that uses a more "object-oriented, type safe" SQL to
* access persistent data.
* <p>
* NOTE: THIS VERSION HAS SEVERE RESTRICTIONS:
* <ol>
* <li>Only tables are currently modeled (we'll add views to a later version).</li>
* <li>Table indexes are not yet modeled (exception is primary key). Again, this
* will be added to later editions.</li>
* <li>It is assumed that each table has a single INTEGER auto-generated primary
* key column that has the same name for all tables.</li>
* <li>It is assumed that each table has a single TIMESTAMP optimistic locking
* column that has the same name for all tables.</li>
* </ol>
*/
public class CodeGenWriter {
private static final Logger log = LoggerFactory.getLogger(CodeGenWriter.class);
// Templates
public static final String DATABASE_TEMPLATE = "Database.vm";
public static final String BASE_TABLE_TEMPLATE = "BaseTable.vm";
public static final String TABLE_TEMPLATE = "Table.vm";
public static final String BASE_VIEW_TEMPLATE = "BaseView.vm";
public static final String VIEW_TEMPLATE = "View.vm";
public static final String BASE_RECORD_TEMPLATE = "BaseRecord.vm";
public static final String RECORD_TEMPLATE = "Record.vm";
// Services
private final WriterService writerService;
private final VelocityEngine engine;
// Properties
private final CodeGenConfig config;
private File baseDir;
private File tableDir;
private File recordDir;
private File viewDir;
/**
* Constructor
*/
public CodeGenWriter(CodeGenConfig config, WriterService writerService) {
this.writerService = writerService;
this.config = config;
this.engine = new VelocityEngine();
// we have to keep this in sync with our logging system
// http://velocity.apache.org/engine/releases/velocity-1.5/developer-guide.html#simpleexampleofacustomlogger
engine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new CommonsLogLogChute());
if(config.getTemplateFolder() == null){
engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
engine.setProperty("classpath." + RuntimeConstants.RESOURCE_LOADER + ".class", ClasspathResourceLoader.class.getName());
} else {
File templateFolder = new File(config.getTemplateFolder());
if(!templateFolder.canRead()){
throw new RuntimeException("Provided template folder missing or not readable: " + config.getTemplateFolder());
}
}
// init engine
try {
engine.init();
} catch (Exception e) {
log.error("A Exception occured on initializing the velocity-engine:", e);
throw new RuntimeException(e);
}
}
/**
* Overload using standard WriterService
* @param config
*/
public CodeGenWriter(CodeGenConfig config) {
this(config, new WriterService(config));
}
/**
* Generates the java code files for the database
*
* @param db
* the DBDatabase to generate files for
*/
public List<File> generateCodeFiles(DBDatabase db) {
List<File> generatedFiles = new ArrayList<File>();
// Prepare directories for generated source files
this.initDirectories(config);
// Create the DB class
generatedFiles.add(this.createDatabaseClass(db));
// Create base table class
if (config.getTableBaseName().equals("DBTable")==false) {
generatedFiles.add(this.createBaseTableClass(db));
}
// Create base record class
if (config.isGenerateRecords()) {
generatedFiles.add(this.createBaseRecordClass(db));
}
// Create base view class
if (config.isGenerateViews() && config.getViewBaseName().equals("DBView")==false) {
generatedFiles.add(this.createBaseViewClass(db));
}
// Create table classes, record interfaces and record classes
for (DBTable table : db.getTables()) {
if (!config.isNestTables()) {
// if table nesting is disabled, create separate table classes
generatedFiles.add(this.createTableClass(db, table));
}
if (config.isGenerateRecords()) {
// generate record
generatedFiles.add(this.createRecordClass(db, table));
}
}
// Create view classes
for (DBView view : db.getViews()) {
if (config.isGenerateViews() && !config.isNestViews()) {
// if table nesting is disabled, create separate table classes
generatedFiles.add(this.createViewClass(db, view));
}
}
return generatedFiles;
}
private void initDirectories(CodeGenConfig config) {
// Create the directory structure for the generated source code.
File targetDir = new File(config.getTargetFolder());
if (!targetDir.exists()) {
targetDir.mkdirs();
}
// Create the base package directory
this.baseDir = FileUtils.getFileFromPackage(targetDir, config.getPackageName());
// Clean out the directory so old code is wiped out.
FileUtils.cleanDirectory(this.baseDir);
boolean createTables = (!config.isNestTables() || !config.getTableBaseName().equals("DBTable"));
boolean createViews = config.isGenerateViews() && (!config.isNestViews() || !config.getViewBaseName().equals("DBView"));
boolean craeteRecords = config.isGenerateRecords();
// createViews
if (!createViews)
config.setViewPackageName(config.getPackageName());
// Create the table package directory
this.tableDir = (createTables ? FileUtils.getFileFromPackage(targetDir, config.getTablePackageName()) : null);
// Create the record package directory
this.recordDir = (craeteRecords ? FileUtils.getFileFromPackage(targetDir, config.getRecordPackageName()) : null);
// Create the record package directory
this.viewDir = (createViews ? FileUtils.getFileFromPackage(targetDir, config.getViewPackageName()) : null);
}
private File createDatabaseClass(DBDatabase db) {
File file = new File(baseDir, config.getDbClassName() + ".java");
VelocityContext context = new VelocityContext();
// TODO fall back to getPackageName() is the other names are not set
context.put("parser", writerService);
context.put("tableClassSuffix", config.getTableClassSuffix());
context.put("basePackageName", config.getPackageName());
context.put("dbClassName", config.getDbClassName());
context.put("tablePackageName", config.getTablePackageName());
context.put("viewPackageName", config.getViewPackageName());
context.put("database", db);
context.put("nestTables", config.isNestTables());
context.put("baseTableClassName", config.getTableBaseName());
context.put("nestViews", config.isNestViews());
context.put("templateFolder", config.getTemplateFolder());
context.put("baseViewClassName", config.getViewBaseName());
context.put("preserveRelationNames", config.isPreserveRelationNames());
writeFile(file, DATABASE_TEMPLATE, context);
return file;
}
private File createBaseTableClass(DBDatabase db) {
File file = new File(tableDir, config.getTableBaseName() + ".java");
VelocityContext context = new VelocityContext();
context.put("tablePackageName", config.getTablePackageName());
context.put("baseTableClassName", config.getTableBaseName());
writeFile(file, BASE_TABLE_TEMPLATE, context);
return file;
}
private File createTableClass(DBDatabase db, DBTable table) {
File file = new File(tableDir, writerService.getTableClassName(table.getName())
+ ".java");
VelocityContext context = new VelocityContext();
context.put("parser", writerService);
context.put("basePackageName", config.getPackageName());
context.put("tablePackageName", config.getTablePackageName());
context.put("baseTableClassName", config.getTableBaseName());
context.put("dbClassName", config.getDbClassName());
context.put("nestTables", config.isNestTables());
context.put("table", table);
writeFile(file, TABLE_TEMPLATE, context);
return file;
}
private File createBaseViewClass(DBDatabase db) {
File file = new File(viewDir, config.getViewBaseName() + ".java");
VelocityContext context = new VelocityContext();
context.put("viewPackageName", config.getViewPackageName());
context.put("baseViewClassName", config.getViewBaseName());
writeFile(file, BASE_VIEW_TEMPLATE, context);
return file;
}
private File createViewClass(DBDatabase db, DBView view) {
File file = new File(viewDir, writerService.getViewClassName(view.getName())
+ ".java");
VelocityContext context = new VelocityContext();
context.put("parser", writerService);
context.put("basePackageName", config.getPackageName());
context.put("viewPackageName", config.getViewPackageName());
context.put("baseViewClassName", config.getViewBaseName());
context.put("dbClassName", config.getDbClassName());
context.put("nestViews", config.isNestViews());
context.put("view", view);
writeFile(file, VIEW_TEMPLATE, context);
return file;
}
private File createBaseRecordClass(DBDatabase db) {
File file = new File(recordDir, config.getRecordBaseName() + ".java");
VelocityContext context = new VelocityContext();
context.put("baseRecordClassName", config.getRecordBaseName());
context.put("basePackageName", config.getPackageName());
context.put("tablePackageName", config.getTablePackageName());
context.put("recordPackageName", config.getRecordPackageName());
context.put("baseTableClassName", config.getTableBaseName());
writeFile(file, BASE_RECORD_TEMPLATE, context);
return file;
}
private File createRecordClass(DBDatabase db, DBTable table) {
File file = new File(recordDir, writerService.getRecordClassName(table.getName()) + ".java");
VelocityContext context = new VelocityContext();
context.put("parser", writerService);
context.put("basePackageName", config.getPackageName());
// If the tables shall be nested within the database classe, their include path for the records needs to be changed
if (config.isNestTables())
context.put("tablePackageName", config.getPackageName() + "." + config.getDbClassName());
else
context.put("tablePackageName", config.getTablePackageName());
context.put("recordPackageName", config.getRecordPackageName());
context.put("baseRecordClassName", config.getRecordBaseName());
context.put("dbClassName", config.getDbClassName());
context
.put("createRecordProperties", config
.isCreateRecordProperties());
context.put("table", table);
writeFile(file, RECORD_TEMPLATE, context);
return file;
}
private void writeFile(File file, String template,
VelocityContext context) {
String templatePath;
if(config.getTemplateFolder() == null) {
templatePath = template;
} else {
templatePath = config.getTemplateFolder()+ System.getProperty("file.separator") + template;
}
Writer writer = null;
try {
log.info("Writing " + file);
Template velocityTemplate = engine.getTemplate(templatePath);
writer = new FileWriter(file);
velocityTemplate.merge(context, writer);
} catch (IOException e) {
log.error(e.getMessage(), e);
} catch (ResourceNotFoundException e) {
log.error(e.getMessage(), e);
} catch (ParseErrorException e) {
log.error(e.getMessage(), e);
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
FileUtils.close(writer);
}
}
}