package com.goodworkalan.addendum; import java.util.Map; import com.goodworkalan.danger.Danger; /** * The root builder for an individual migration definition. * * @author Alan Gutierrez */ public class Addendum { /** A list of updates to perform. */ private final Patch patch; /** An entity name already exists in the addendum. */ public final static String ADDENDUM_ENTITY_EXISTS = "403"; /** The entity cannot be found in the addendum. */ public final static String ADDENDUM_ENTITY_MISSING = "413"; /** A table name already exists in the addendum. */ public final static String ADDENDUM_TABLE_EXISTS = "404"; /** The table cannot be found in the addendum. */ public final static String ADDENDUM_TABLE_MISSING = "414"; /** Unable to add a column. */ public final static String CANNOT_ADD_COLUMN = "504"; /** Unable to alter a column. */ public final static String CANNOT_ALTER_COLUMN = "503"; /** Unable to create the table for an entity due to an SQL exception. */ public final static String CANNOT_CREATE_TABLE = "501"; /** Unable to drop a column. */ public final static String CANNOT_DROP_COLUMN = "505"; /** Unable to execute arbitrary SQL statements. */ public final static String CANNOT_EXECUTE_SQL = "502"; /** Unable to insert values. */ public final static String CANNOT_INSERT = "506"; /** Unable to rename a table. */ public final static String CANNOT_RENAME_TABLE = "507"; /** A column already exists in the table in the schema. */ public final static String COLUMN_EXISTS = "408"; /** A column does not exist the entity. */ public final static String COLUMN_MISSING = "416"; /** The requested generator type is not supported by the SQL dialect. */ public final static String DIALECT_DOES_NOT_SUPPORT_GENERATOR = "101"; /** The dialog does not support a specific SQL type. */ public final static String DIALECT_DOES_NOT_SUPPORT_TYPE = "102"; /** An entity name already exists in the schema. */ public final static String ENTITY_EXISTS = "405"; /** The entity cannot be found in the schema. */ public final static String ENTITY_MISSING = "410"; /** Insert statement DSL values count does not match column count. */ public final static String INSERT_VALUES = "401"; /** Unable to open an SQL connection due to a JNI naming error. */ public final static String NAMING_EXCEPTION = "201"; /** A primary key property does not exist. */ public final static String PRIMARY_KEY_COLUMN_MISSING = "417"; /** The primary key has already been specified for the entity. */ public final static String PRIMARY_KEY_EXISTS = "409"; /** A property already exists in the entity. */ public final static String PROPERTY_EXISTS = "407"; /** A property does not exist the entity. */ public final static String PROPERTY_MISSING = "415"; /** Unable to determine the maximum value of the applied updates. */ public final static String SQL_ADDENDA_COUNT = "303"; /** Unable to update the addenda table with a new update. */ public final static String SQL_ADDENDUM = "304"; /** Unable to close a JDBC data source. */ public final static String SQL_CLOSE = "399"; /** Unable to connect to a JDBC data source. */ public final static String SQL_CONNECT = "301"; /** Unable to create the addenda table to track updates. */ public final static String SQL_CREATE_ADDENDA = "302"; /** Unable to execute a SQL migration statement. */ public final static String SQL_EXECUTION = "308"; /** Unable to create database dialect. */ public final static String SQL_GET_DIALECT = "309"; /** A table name already exists in the schema. */ public final static String TABLE_EXISTS = "406"; /** The table cannot be found in the schema. */ public final static String TABLE_MISSING = "411"; /** Unable to determine an SQL type for a Java class. */ public final static String UNMAPPABLE_TYPE = "412"; /** * Create a new addendum using the given patch to record the addendum * properties. * * @param patch * The database migration patch. */ Addendum(Patch patch) { this.patch = patch; } /** * Apply the given definition class against this addendum. Definition * classes are used to capture entity definitions generated from reflecting * on an object-relational mapping. * * @param definition * A definition. * @return This addendum builder to continue construction. */ public Addendum apply(Definition definition) { definition.define(this); return this; } /** * Define an entity with the given entity name and the given table name in * the database. * * @param name * The entity name. * @param tableName * The name of the table in the database. * @return An entity definition builder. */ public DefineEntity define(String name, String tableName) { if (patch.aliases.put(name, tableName) != null) { throw new Danger(Addendum.class, ADDENDUM_ENTITY_EXISTS, name); } Entity entity = new Entity(name); if (patch.entities.put(tableName, entity) != null) { throw new Danger(Addendum.class, ADDENDUM_TABLE_EXISTS, tableName); } return new DefineEntity(this, entity); } /** * Define an entity with the given entity name which will also be used as * the table name in the database. * * @param name * The entity name. * @return An entity definition builder. */ public DefineEntity define(String name) { return define(name, name); } /** * Create entities defined in this addendum that do not already exist in the * tracking schema. * * @return This addendum builder to continue construction. */ public Addendum createIfAbsent() { for (Map.Entry<String, String> entry : patch.aliases.entrySet()) { String entityName = entry.getKey(); if (!patch.schema.aliases.containsKey(entityName)) { String tableName = entry.getValue(); if (patch.schema.entities.containsKey(tableName)) { throw new Danger(Addendum.class, TABLE_EXISTS, tableName); } patch.add(new TableCreate(entityName, patch.entities.get(tableName))); } } return this; } /** * Create entities in the schema using the entities defined in this addendum * given as a list of addendum entity names. If the entity does not exist in * the addendum an exception is raised. If the entity or the table already * exists in the schema an exception is raised. * * @param names * The list of addendum defined entities to create. * @return This addendum builder to continue construction. */ public Addendum createDefinitions(String...names) { for (String name : names) { patch.add(new TableCreate(name, patch.getEntity(name))); } return this; } /** * Create an entity with the given entity name and the given table name in * the database. * * @param name * The entity name. * @param tableName * The name of the table in the database. * @return A create entity builder. */ public CreateEntity create(String name, String tableName) { if (patch.schema.aliases.containsKey(name)) { throw new Danger(Addendum.class, ENTITY_EXISTS, name); } if (patch.schema.entities.containsKey(tableName)) { throw new Danger(Addendum.class, TABLE_EXISTS, tableName); } return new CreateEntity(this, new Entity(tableName), name, patch); } /** * Create an entity with the given entity name which will also be used as * the table name in the database. * * @param name * The entity name. * @return A create entity builder. */ public CreateEntity create(String name) { return create(name, name); } /** * Rename the entity with the given name. This method returns a rename * builder that will be used to specify the new entity name. * <p> * If the table for the renamed entity has the same name as the entity, the * table is renamed to the new entity name. If the table has a different * name than the entity name, only the entity is renamed. * * @param from * The name of the entity to rename. * @param to * The name to rename the entity to. * @return This addendum builder to continue construction. */ public Addendum rename(String from, String to) { if (!patch.schema.aliases.containsKey(from)) { throw new Danger(Addendum.class, ENTITY_MISSING, from); } patch.schema.rename(from, to); Schema schema = patch.schema; Entity entity = schema.entities.get(schema.aliases.get(to)); if (entity.tableName.equals(from)) { patch.add(new TableRename(to, from, to)); } return this; } /** * Sets the association between the given table name to the given entity * name, replacing the current entity name to table name association. If the * entity name is already in use, and is not associated with the given table * name, an exception is raised. If the table name is not defined an * exception is raised. * <p> * This method is useful when first mapping a legacy database with SQL like * table names to Java objects. * * @param tableName * The table name. * @param entityName * The entity name. * @return This addendum builder to continue construction. * @exception Addendum * If the entity name is already in use or if the table name * cannot be found. */ public Addendum alias(String tableName, String entityName) { // Didn't bother to create a schema update for this, since there is // never a database operation and no need to reuse. String existingEntityName = patch.schema.aliases.get(entityName); if (existingEntityName == null) { if (!patch.schema.entities.containsKey(tableName)) { throw new Danger(Addendum.class, TABLE_MISSING, tableName); } patch.schema.aliases.remove(patch.schema.getEntityName(tableName)); patch.schema.aliases.put(entityName, tableName); } else if (!existingEntityName.equals(tableName)) { throw new Danger(Addendum.class, ENTITY_EXISTS, entityName); } return this; } /** * Alter the entity with the given name. This method returns an entity * alteration builder that can be used to change the underlying table, alter * existing properties, add new properties, or drop properties. * * @param name * The name of the entity to alter. * @return An entity alteration builder. */ public AlterEntity alter(String name) { return new AlterEntity(this, patch.schema.getEntity(name), patch); } /** * Performs updates using application specific SQL statements. * * @param executable * An {@link Executable} to execute. * @return This schema element to continue the domain-specific language * statement. */ public Addendum execute(Executable executable) { Execution execution = new Execution(executable); patch.add(execution); return this; } /** * Create an insert statement that will insert values into the database. * * @param table * The name of the table to update. * @return An insert element to define the insert statement. */ public Insert insert(String table) { Insertion insertion = new Insertion(table); patch.add(insertion); return new Insert(this, insertion); } /** * Terminates the addendum specification statement in the domain specific * language. */ public void commit() { } }