package er.extensions.migration; import java.io.UnsupportedEncodingException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.webobjects.eoaccess.EOAdaptorChannel; import com.webobjects.eoaccess.EOModel; import com.webobjects.eocontrol.EOEditingContext; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSBundle; import com.webobjects.foundation._NSStringUtilities; import er.extensions.foundation.ERXProperties; import er.extensions.jdbc.ERXJDBCUtilities; /** * <p>Convenience superclass for Migration classes. Checks for corresponding sql files name * "ClassnameX_Upgrade.migration" and "ClassnameX_Downgrade.migration" where * "Classname" is the classname of the migration class. The files have to to be * in a corresponding bundle. Implement "migrationBundleName" to return the * correct name of the bundle.</p> * * <p>This makes migrations easier as you only have to create a new Java class according to the * migration naming conventions, inherit from this class and put your SQL in a properly * named text file (or more than one file, if you use database specific migrations).</p> * * <p>If you need database specific migrations use: * * <blockquote><code>er.extensions.migration.ERXMigration.useDatabaseSpecificMigrations=true</code></blockquote> * * in your Properties. The default is not to use database specific migrations. A filename * for a database specific migration is then, for example, <code>ClassnameX_FrontBase_Upgrade.migration</code> or * <code>ClassnameX_Postgresql_Upgrade.migration</code>. * * <p>For the database specific part of the filename, the databaseProductName as from the JDBC * adaptor is used. So make sure, you're using the correct filename. The migration will throw * an exception if the appropriate migration textfile can't be found.</p> * * @author cug */ public abstract class ERXMigration implements IERXMigration { private static final Logger log = LoggerFactory.getLogger(ERXMigration.class); private Boolean _useDatabaseSpecificMigrations; /** * Override the global application preference on per-database migrations. * * @param useDatabaseSpecificMigrations if true, database-specific migrations will be used */ public ERXMigration(boolean useDatabaseSpecificMigrations) { _useDatabaseSpecificMigrations = Boolean.valueOf(useDatabaseSpecificMigrations); } public ERXMigration() { } /** * No dependencies */ public NSArray<ERXModelVersion> modelDependencies() { return null; } /** * Checks for a corresponding downgrade file which is performed as a raw SQL action */ public void downgrade(EOEditingContext editingContext, EOAdaptorChannel channel, EOModel model) throws Throwable { String sqlString = null; if (useDatabaseSpecificMigrations()) { sqlString = getSQLForMigration(getClass().getSimpleName() + "_" + ERXJDBCUtilities.databaseProductName(channel) + "_Downgrade.migration"); } else { sqlString = getSQLForMigration(getClass().getSimpleName() + "_Downgrade.migration"); } if (sqlString != null) { log.info("Applying migration for: {}", getClass()); ERXJDBCUtilities.executeUpdateScript(channel, sqlString); } else { if (useDatabaseSpecificMigrations()) { throw new ERXMigrationFailedException("No downgrade for migration: " + getClass().getName() + "found for database: " + ERXJDBCUtilities.databaseProductName(channel)); } throw new ERXMigrationFailedException("No downgrade for migration: " + getClass().getName()); } } /** * Checks for a corresponding upgrade file which is performed as a raw SQL action */ public void upgrade(EOEditingContext editingContext, EOAdaptorChannel channel, EOModel model) throws Throwable { String sqlString = null; if (useDatabaseSpecificMigrations()) { sqlString = getSQLForMigration(getClass().getSimpleName() + "_" + ERXJDBCUtilities.databaseProductName(channel) + "_Upgrade.migration"); } else { sqlString = getSQLForMigration(getClass().getSimpleName() + "_Upgrade.migration"); } if (sqlString != null) { log.info("Applying migration for: {}", getClass()); ERXJDBCUtilities.executeUpdateScript(channel, sqlString); } else { if (useDatabaseSpecificMigrations()) { throw new ERXMigrationFailedException("No upgrade for migration: " + getClass().getName() + " found for database: " + ERXJDBCUtilities.databaseProductName(channel)); } throw new ERXMigrationFailedException("No upgrade for migration: " + getClass().getName() + " found."); } } /** * Checks in the current bundle for migration files corresponding to this classes name * * @param migrationName * @return SQL string */ protected String getSQLForMigration(String migrationName) { NSBundle bundle; String migrationBundleName = migrationBundleName(); if (migrationBundleName == null) { bundle = NSBundle.bundleForClass(getClass()); } else { bundle = NSBundle.bundleForName(migrationBundleName()); if (bundle == null) { bundle = NSBundle._appBundleForName(migrationBundleName()); } } NSArray<String> resourcePaths = bundle.resourcePathsForResources("migration", null); if (resourcePaths != null) { for (String currentPath : resourcePaths) { if (currentPath.endsWith(migrationName)) { try { return new String(bundle.bytesForResourcePath(currentPath), _NSStringUtilities.UTF8_ENCODING); } catch (UnsupportedEncodingException e) { log.error("Could not use UTF-8 encoding.", e); } } } } return null; } /** * The name to create the NSBundle for the current bundle, defaults to the * bundle that contains the migration class. * * @return <code>null</code> */ protected String migrationBundleName() { return null; } protected boolean useDatabaseSpecificMigrations() { if (_useDatabaseSpecificMigrations == null) { _useDatabaseSpecificMigrations = Boolean.valueOf( ERXProperties.booleanForKeyWithDefault("er.extensions.migration.ERXMigration.useDatabaseSpecificMigrations", false)); } return _useDatabaseSpecificMigrations.booleanValue(); } }