From 53ba957d41791ba6b44b9f4bdb6cf8d4a5875dd9 Mon Sep 17 00:00:00 2001
From: izabel <izabel>
Date: Mon, 21 Jul 2008 22:55:16 +0000
Subject: [PATCH] [LMS-520] Allow pre- and post-methods for db migration
 written in Java

SVN: 7373
---
 .../cisd/dbmigration/DBMigrationEngine.java   |  29 +++
 .../cisd/dbmigration/IDAOFactory.java         |   5 +
 .../IJavaMigrationStepExecutor.java           |  29 +++
 .../cisd/dbmigration/IMigrationStep.java      |  44 ++++
 .../JavaMigrationStepExecutor.java            | 186 +++++++++++++++++
 .../cisd/dbmigration/h2/H2DAOFactory.java     |  14 +-
 .../postgresql/PostgreSQLDAOFactory.java      |  14 +-
 .../dbmigration/DBMigrationEngineTest.java    | 193 +++++++++++++++++-
 .../JavaMigrationStepExecutorTest.java        | 114 +++++++++++
 .../MigrationStepFrom001To002.java            |  40 ++++
 .../MigrationStepFrom002To003.java            |  40 ++++
 11 files changed, 700 insertions(+), 8 deletions(-)
 create mode 100644 dbmigration/source/java/ch/systemsx/cisd/dbmigration/IJavaMigrationStepExecutor.java
 create mode 100644 dbmigration/source/java/ch/systemsx/cisd/dbmigration/IMigrationStep.java
 create mode 100644 dbmigration/source/java/ch/systemsx/cisd/dbmigration/JavaMigrationStepExecutor.java
 create mode 100644 dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/JavaMigrationStepExecutorTest.java
 create mode 100644 dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/MigrationStepFrom001To002.java
 create mode 100644 dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/MigrationStepFrom002To003.java

diff --git a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/DBMigrationEngine.java b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/DBMigrationEngine.java
index c0dfddb4006..8485a5e4167 100644
--- a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/DBMigrationEngine.java
+++ b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/DBMigrationEngine.java
@@ -24,6 +24,7 @@ import ch.systemsx.cisd.common.Script;
 import ch.systemsx.cisd.common.db.ISqlScriptExecutor;
 import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
+import ch.systemsx.cisd.common.exceptions.Status;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 
@@ -71,6 +72,8 @@ public final class DBMigrationEngine
 
     private final ISqlScriptExecutor scriptExecutor;
 
+    private final IJavaMigrationStepExecutor javaMigrationStepExecutor;
+
     /**
      * Creates an instance for the specified DAO factory and SQL script provider.
      * 
@@ -83,6 +86,7 @@ public final class DBMigrationEngine
         adminDAO = daoFactory.getDatabaseDAO();
         logDAO = daoFactory.getDatabaseVersionLogDAO();
         scriptExecutor = daoFactory.getSqlScriptExecutor();
+        javaMigrationStepExecutor = daoFactory.getJavaMigrationStepExecutor();
         this.scriptProvider = scriptProvider;
         this.shouldCreateFromScratch = shouldCreateFromScratch;
     }
@@ -251,7 +255,13 @@ public final class DBMigrationEngine
                 throw new EnvironmentFailureException(message);
             }
             final long time = System.currentTimeMillis();
+            final Status preMigrationStatusOrNull =
+                    javaMigrationStepExecutor.tryPerformPreMigration(migrationScript);
+            checkErrors(preMigrationStatusOrNull, "pre");
             scriptExecutor.execute(migrationScript, true, logDAO);
+            final Status postMigrationStatusOrNull =
+                    javaMigrationStepExecutor.tryPerformPostMigration(migrationScript);
+            checkErrors(postMigrationStatusOrNull, "post");
             if (operationLog.isInfoEnabled())
             {
                 operationLog.info("Successfully migrated from version " + version + " to "
@@ -261,6 +271,25 @@ public final class DBMigrationEngine
         } while (version.equals(toVersion) == false);
     }
 
+    private void checkErrors(final Status migrationStatusOrNull, final String prefix)
+    {
+        if (migrationStatusOrNull != null)
+        {
+            if (migrationStatusOrNull.isError())
+            {
+                final String message =
+                        String.format("Java %s-migration finnished with an error status ('%s')",
+                                prefix, migrationStatusOrNull.tryGetErrorMessage());
+                operationLog.error(message);
+                throw new EnvironmentFailureException(message);
+            } else if (operationLog.isInfoEnabled())
+            {
+                operationLog.info(String.format("Java %s-migration succesfull.", prefix));
+            }
+
+        }
+    }
+
     @Private
     final static String increment(final String version)
     {
diff --git a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/IDAOFactory.java b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/IDAOFactory.java
index 72488db1ac8..18757a584f7 100644
--- a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/IDAOFactory.java
+++ b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/IDAOFactory.java
@@ -44,4 +44,9 @@ public interface IDAOFactory
      * Returns the mass uploader used for initial data uploading.
      */
     public IMassUploader getMassUploader();
+
+    /**
+     * FIXME:
+     */
+    public IJavaMigrationStepExecutor getJavaMigrationStepExecutor();
 }
diff --git a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/IJavaMigrationStepExecutor.java b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/IJavaMigrationStepExecutor.java
new file mode 100644
index 00000000000..66925f204d7
--- /dev/null
+++ b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/IJavaMigrationStepExecutor.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed 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 ch.systemsx.cisd.dbmigration;
+
+import ch.systemsx.cisd.common.Script;
+import ch.systemsx.cisd.common.exceptions.Status;
+
+
+public interface IJavaMigrationStepExecutor
+{
+
+    public Status tryPerformPostMigration(final Script sqlScript);
+
+    public Status tryPerformPreMigration(final Script sqlScript);
+}
diff --git a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/IMigrationStep.java b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/IMigrationStep.java
new file mode 100644
index 00000000000..5ad82aad285
--- /dev/null
+++ b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/IMigrationStep.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * Licensed 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 ch.systemsx.cisd.dbmigration;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import ch.systemsx.cisd.common.exceptions.Status;
+
+/**
+ * An interface which must be implemented by all classes providing Java code that performs migration
+ * steps prior (pre) or after (post) the SQL migration script ran. Canonical name of class
+ * implementing this interface (preceded by <code>-- JAVA </code>) may be included in the first
+ * line of SQL migration script. <Example:
+ * <code> -- JAVA ch.systemsx.cisd.openbis.db.migration.MigrationStepFrom022To023</code>
+ * 
+ * @author Izabela Adamczyk
+ */
+public interface IMigrationStep
+{
+    /**
+     * Called before the SQL migration is performed.
+     */
+    public Status performPreMigration(JdbcTemplate jdbcTemplate);
+
+    /**
+     * Called after the SQL migration has been performed.
+     */
+    public Status performPostMigration(JdbcTemplate jdbcTemplate);
+
+}
diff --git a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/JavaMigrationStepExecutor.java b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/JavaMigrationStepExecutor.java
new file mode 100644
index 00000000000..ac35f7f331f
--- /dev/null
+++ b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/JavaMigrationStepExecutor.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed 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 ch.systemsx.cisd.dbmigration;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.lang.StringUtils;
+import org.springframework.jdbc.core.support.JdbcDaoSupport;
+
+import ch.systemsx.cisd.common.Script;
+import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
+import ch.systemsx.cisd.common.exceptions.Status;
+
+/**
+ * Allows to extract {@link IMigrationStep} class from migration script and run the pre- and post-
+ * migration java steps. Example of the script containing Java Migration Step definition:
+ * 
+ * <pre>
+ * -- JAVA ch.systemsx.cisd.openbis.db.migration.MigrationStepFrom022To023
+ * </pre>
+ * 
+ * @author Izabela Adamczyk
+ */
+public class JavaMigrationStepExecutor extends JdbcDaoSupport implements IJavaMigrationStepExecutor
+{
+    private static final String JAVA_MIGRATION_STEP_PREFIX = "--JAVA";
+
+    public JavaMigrationStepExecutor(final DataSource dataSource)
+    {
+        setDataSource(dataSource);
+    }
+
+    private IMigrationStep tryExtractMigrationStep(final Script sqlScript)
+
+    {
+        if (sqlScript == null || StringUtils.isBlank(sqlScript.getCode()))
+        {
+            return null;
+        }
+        final String code = sqlScript.getCode();
+        final List<String> lines = Arrays.asList(code.split("\n"));
+        IMigrationStep extratedMigrationStepOrNull = null;
+        boolean nonEmptyLineFound = false;
+        for (final String line : lines)
+        {
+            // blank lines are allowed at the beginning of the script
+            if (StringUtils.isBlank(line))
+            {
+                continue;
+            }
+            // only the first non-blank line is supposed to contain Java Migration Step
+            if (nonEmptyLineFound == false)
+            {
+                extratedMigrationStepOrNull = tryExtractMigrationStepFromLine(line);
+                nonEmptyLineFound = true;
+            } else
+            {
+                checkIfCurrentLineConsistentWithAlredyProcessed(sqlScript,
+                        extratedMigrationStepOrNull, line);
+            }
+        }
+        return extratedMigrationStepOrNull;
+    }
+
+    private void checkIfCurrentLineConsistentWithAlredyProcessed(final Script sqlScript,
+            final IMigrationStep extratedMigrationStepOrNull, final String line)
+    {
+        if (tryExtractMigrationStepFromLine(line) != null)
+        {
+            final String msg;
+            if (extratedMigrationStepOrNull != null)
+            {
+
+                msg =
+                        String
+                                .format(
+                                        "Migration script '%s' contains more than one Java Migration Steps.",
+                                        sqlScript.getName());
+
+            } else
+            {
+                msg =
+                        String
+                                .format(
+                                        "Java Migration Step should be defined in the first non-blank line of the migration script '%s'.",
+                                        sqlScript.getName());
+
+            }
+
+            throw new EnvironmentFailureException(msg);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private IMigrationStep tryExtractMigrationStepFromLine(final String lineToProcess)
+    {
+        final String line = StringUtils.deleteWhitespace(lineToProcess);
+        if (line != null && line.startsWith(JAVA_MIGRATION_STEP_PREFIX))
+        {
+            final String className = StringUtils.removeStart(line, JAVA_MIGRATION_STEP_PREFIX);
+            String msg;
+            try
+            {
+                final Class clazz = Class.forName(className);
+                final Object object = clazz.newInstance();
+                return (IMigrationStep) object;
+            } catch (final ClassNotFoundException ex)
+            {
+                msg = String.format("Class '%s' not found.", className);
+
+            } catch (final InstantiationException ex)
+            {
+                msg =
+                        String.format(
+                                "Cannot instantiate class '%s' (EnvironmentFailureException).",
+                                className);
+            } catch (final IllegalAccessException ex)
+            {
+                msg =
+                        String.format("Cannot instantiate class '%s' (IllegalAccessException).",
+                                className);
+            }
+            throw new EnvironmentFailureException(msg);
+        } else
+        {
+            return null;
+        }
+    }
+
+    /**
+     * Returns null if MigrationStep has not been found and status returned by
+     * {@link  IMigrationStep#performPreMigration(org.springframework.jdbc.core.JdbcTemplate)}
+     * otherwise.
+     */
+    public Status tryPerformPreMigration(final Script sqlScript)
+
+    {
+        final IMigrationStep migrationStep = tryExtractMigrationStep(sqlScript);
+        if (migrationStep != null)
+        {
+
+            final Status preMigrationStatus = migrationStep.performPreMigration(getJdbcTemplate());
+            return preMigrationStatus;
+        } else
+        {
+            return null;
+        }
+    }
+
+    /**
+     * Returns null if MigrationStep has not been found and status returned by
+     * {@link  IMigrationStep#performPostMigration(org.springframework.jdbc.core.JdbcTemplate)}
+     * otherwise.
+     */
+    public Status tryPerformPostMigration(final Script sqlScript)
+    {
+        final IMigrationStep migrationStep = tryExtractMigrationStep(sqlScript);
+        if (migrationStep != null)
+        {
+            final Status postMigrationStatus =
+                    migrationStep.performPostMigration(getJdbcTemplate());
+            return postMigrationStatus;
+        } else
+        {
+            return null;
+        }
+    }
+
+}
diff --git a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/h2/H2DAOFactory.java b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/h2/H2DAOFactory.java
index 78b7dc09fba..27863e57017 100644
--- a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/h2/H2DAOFactory.java
+++ b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/h2/H2DAOFactory.java
@@ -27,7 +27,9 @@ import ch.systemsx.cisd.dbmigration.DatabaseVersionLogDAO;
 import ch.systemsx.cisd.dbmigration.IDAOFactory;
 import ch.systemsx.cisd.dbmigration.IDatabaseAdminDAO;
 import ch.systemsx.cisd.dbmigration.IDatabaseVersionLogDAO;
+import ch.systemsx.cisd.dbmigration.IJavaMigrationStepExecutor;
 import ch.systemsx.cisd.dbmigration.IMassUploader;
+import ch.systemsx.cisd.dbmigration.JavaMigrationStepExecutor;
 import ch.systemsx.cisd.dbmigration.SqlScriptExecutor;
 
 /**
@@ -45,18 +47,21 @@ public class H2DAOFactory implements IDAOFactory
 
     private final IMassUploader massUploader;
 
+    private final IJavaMigrationStepExecutor javaMigrationStepExecutor;
+
     /**
      * Creates an instance based on the specified configuration context.
      */
-    public H2DAOFactory(DatabaseConfigurationContext context)
+    public H2DAOFactory(final DatabaseConfigurationContext context)
     {
         final DataSource dataSource = context.getDataSource();
         sqlScriptExecutor = new SqlScriptExecutor(dataSource, context.isScriptSingleStepMode());
+        javaMigrationStepExecutor = new JavaMigrationStepExecutor(dataSource);
         databaseVersionLogDAO = new DatabaseVersionLogDAO(dataSource, context.getLobHandler());
         try
         {
             massUploader = new H2MassUploader(dataSource, context.getSequenceNameMapper());
-        } catch (SQLException ex)
+        } catch (final SQLException ex)
         {
             throw new CheckedExceptionTunnel(ex);
         }
@@ -85,4 +90,9 @@ public class H2DAOFactory implements IDAOFactory
         return massUploader;
     }
 
+    public IJavaMigrationStepExecutor getJavaMigrationStepExecutor()
+    {
+        return javaMigrationStepExecutor;
+    }
+
 }
diff --git a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/postgresql/PostgreSQLDAOFactory.java b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/postgresql/PostgreSQLDAOFactory.java
index 98b73de749f..60e5da6698b 100644
--- a/dbmigration/source/java/ch/systemsx/cisd/dbmigration/postgresql/PostgreSQLDAOFactory.java
+++ b/dbmigration/source/java/ch/systemsx/cisd/dbmigration/postgresql/PostgreSQLDAOFactory.java
@@ -27,7 +27,9 @@ import ch.systemsx.cisd.dbmigration.DatabaseVersionLogDAO;
 import ch.systemsx.cisd.dbmigration.IDAOFactory;
 import ch.systemsx.cisd.dbmigration.IDatabaseAdminDAO;
 import ch.systemsx.cisd.dbmigration.IDatabaseVersionLogDAO;
+import ch.systemsx.cisd.dbmigration.IJavaMigrationStepExecutor;
 import ch.systemsx.cisd.dbmigration.IMassUploader;
+import ch.systemsx.cisd.dbmigration.JavaMigrationStepExecutor;
 import ch.systemsx.cisd.dbmigration.SqlScriptExecutor;
 
 /**
@@ -45,18 +47,21 @@ public class PostgreSQLDAOFactory implements IDAOFactory
 
     private final IMassUploader massUploader;
 
+    private final IJavaMigrationStepExecutor javaMigrationStepExecutor;
+
     /**
      * Creates an instance based on the specified configuration context.
      */
-    public PostgreSQLDAOFactory(DatabaseConfigurationContext context)
+    public PostgreSQLDAOFactory(final DatabaseConfigurationContext context)
     {
         final DataSource dataSource = context.getDataSource();
         sqlScriptExecutor = new SqlScriptExecutor(dataSource, context.isScriptSingleStepMode());
+        javaMigrationStepExecutor = new JavaMigrationStepExecutor(dataSource);
         databaseVersionLogDAO = new DatabaseVersionLogDAO(dataSource, context.getLobHandler());
         try
         {
             massUploader = new PostgreSQLMassUploader(dataSource, context.getSequenceNameMapper());
-        } catch (SQLException ex)
+        } catch (final SQLException ex)
         {
             throw new CheckedExceptionTunnel(ex);
         }
@@ -86,4 +91,9 @@ public class PostgreSQLDAOFactory implements IDAOFactory
         return massUploader;
     }
 
+    public IJavaMigrationStepExecutor getJavaMigrationStepExecutor()
+    {
+        return javaMigrationStepExecutor;
+    }
+
 }
diff --git a/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/DBMigrationEngineTest.java b/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/DBMigrationEngineTest.java
index 7ecede9a4c2..63e7537ad13 100644
--- a/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/DBMigrationEngineTest.java
+++ b/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/DBMigrationEngineTest.java
@@ -18,6 +18,7 @@ package ch.systemsx.cisd.dbmigration;
 
 import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertSame;
+import static org.testng.AssertJUnit.assertTrue;
 import static org.testng.AssertJUnit.fail;
 
 import java.io.File;
@@ -37,6 +38,7 @@ import ch.systemsx.cisd.common.Script;
 import ch.systemsx.cisd.common.db.ISqlScriptExecutor;
 import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
+import ch.systemsx.cisd.common.exceptions.Status;
 import ch.systemsx.cisd.common.logging.BufferedAppender;
 import ch.systemsx.cisd.common.utilities.OSUtilities;
 
@@ -55,6 +57,18 @@ public class DBMigrationEngineTest
         {
             will(returnValue(script));
             one(scriptExecutor).execute(script, honorSingleStepMode, logDAO);
+
+        }
+
+        protected void expectSuccessfulScriptExecutionWithMigrationSteps(final Script script,
+                final boolean honorSingleStepMode)
+        {
+            will(returnValue(script));
+            one(javaMigrationStepExecutor).tryPerformPreMigration(script);
+            will(returnValue(Status.OK));
+            one(scriptExecutor).execute(script, honorSingleStepMode, logDAO);
+            one(javaMigrationStepExecutor).tryPerformPostMigration(script);
+            will(returnValue(Status.OK));
         }
     }
 
@@ -70,6 +84,8 @@ public class DBMigrationEngineTest
 
     private ISqlScriptExecutor scriptExecutor;
 
+    private IJavaMigrationStepExecutor javaMigrationStepExecutor;
+
     private BufferedAppender logRecorder;
 
     @BeforeMethod
@@ -81,6 +97,7 @@ public class DBMigrationEngineTest
         adminDAO = context.mock(IDatabaseAdminDAO.class);
         logDAO = context.mock(IDatabaseVersionLogDAO.class);
         scriptExecutor = context.mock(ISqlScriptExecutor.class);
+        javaMigrationStepExecutor = context.mock(IJavaMigrationStepExecutor.class);
         logRecorder = new BufferedAppender("%-5p %c - %m%n", Level.DEBUG);
     }
 
@@ -115,6 +132,8 @@ public class DBMigrationEngineTest
                     will(returnValue(logDAO));
                     one(daoFactory).getSqlScriptExecutor();
                     will(returnValue(scriptExecutor));
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
 
                     one(adminDAO).dropDatabase();
                     one(logDAO).canConnectToDatabase();
@@ -165,6 +184,8 @@ public class DBMigrationEngineTest
                     will(returnValue(logDAO));
                     one(daoFactory).getSqlScriptExecutor();
                     will(returnValue(scriptExecutor));
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
 
                     one(adminDAO).dropDatabase();
                     one(logDAO).canConnectToDatabase();
@@ -214,6 +235,9 @@ public class DBMigrationEngineTest
                     one(daoFactory).getSqlScriptExecutor();
                     will(returnValue(scriptExecutor));
 
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
+
                     one(adminDAO).dropDatabase();
                     one(logDAO).canConnectToDatabase();
                     will(returnValue(false));
@@ -255,6 +279,8 @@ public class DBMigrationEngineTest
                     will(returnValue(logDAO));
                     one(daoFactory).getSqlScriptExecutor();
                     will(returnValue(scriptExecutor));
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
 
                     one(logDAO).canConnectToDatabase();
                     will(returnValue(false));
@@ -300,6 +326,9 @@ public class DBMigrationEngineTest
                     one(daoFactory).getSqlScriptExecutor();
                     will(returnValue(scriptExecutor));
 
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
+
                     one(logDAO).canConnectToDatabase();
                     will(returnValue(false));
                     one(adminDAO).getDatabaseName();
@@ -345,6 +374,8 @@ public class DBMigrationEngineTest
                     will(returnValue(logDAO));
                     one(daoFactory).getSqlScriptExecutor();
                     will(returnValue(scriptExecutor));
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
 
                     one(logDAO).canConnectToDatabase();
                     will(returnValue(false));
@@ -390,6 +421,8 @@ public class DBMigrationEngineTest
                     will(returnValue(logDAO));
                     one(daoFactory).getSqlScriptExecutor();
                     will(returnValue(scriptExecutor));
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
 
                     one(logDAO).canConnectToDatabase();
                     will(returnValue(true));
@@ -432,6 +465,8 @@ public class DBMigrationEngineTest
                     will(returnValue(logDAO));
                     one(daoFactory).getSqlScriptExecutor();
                     will(returnValue(scriptExecutor));
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
 
                     one(logDAO).canConnectToDatabase();
                     will(returnValue(true));
@@ -445,13 +480,15 @@ public class DBMigrationEngineTest
                     one(adminDAO).getDatabaseURL();
                     will(returnValue("my database URL"));
                     one(scriptProvider).tryGetMigrationScript(fromVersion, "100");
-                    expectSuccessfulExecution(new Script("m-099-100", "code 099 100", toVersion),
-                            true);
+                    final Script script = new Script("m-099-100", "code 099 100", toVersion);
+                    expectSuccessfulScriptExecutionWithMigrationSteps(script, true);
+
                     one(scriptProvider).tryGetMigrationScript("100", toVersion);
-                    expectSuccessfulExecution(new Script("m-100-101", "code 100 101", toVersion),
-                            true);
+                    expectSuccessfulScriptExecutionWithMigrationSteps(new Script("m-100-101",
+                            "code 100 101", toVersion), true);
                     one(adminDAO).getDatabaseName();
                     will(returnValue("my 2. database"));
+
                 }
             });
         final DBMigrationEngine migrationEngine =
@@ -464,9 +501,17 @@ public class DBMigrationEngineTest
                 "INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
                         + "Trying to migrate database 'my 1. database' from version 099 to 101."
                         + OSUtilities.LINE_SEPARATOR
+                        + "INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - Java pre-migration succesfull."
+                        + OSUtilities.LINE_SEPARATOR
+                        + "INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - Java post-migration succesfull."
+                        + OSUtilities.LINE_SEPARATOR
                         + "INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
                         + "Successfully migrated from version 099 to 100 in 0 msec"
                         + OSUtilities.LINE_SEPARATOR
+                        + "INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - Java pre-migration succesfull."
+                        + OSUtilities.LINE_SEPARATOR
+                        + "INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - Java post-migration succesfull."
+                        + OSUtilities.LINE_SEPARATOR
                         + "INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
                         + "Successfully migrated from version 100 to 101 in 0 msec"
                         + OSUtilities.LINE_SEPARATOR
@@ -479,6 +524,132 @@ public class DBMigrationEngineTest
         context.assertIsSatisfied();
     }
 
+    @Test
+    public void testMigrationStepsFailPostMigrationStep()
+    {
+        final String fromVersion = "099";
+        final String toVersion = "101";
+        context.checking(new MyExpectations()
+            {
+                {
+                    one(daoFactory).getDatabaseDAO();
+                    will(returnValue(adminDAO));
+                    one(daoFactory).getDatabaseVersionLogDAO();
+                    will(returnValue(logDAO));
+                    one(daoFactory).getSqlScriptExecutor();
+                    will(returnValue(scriptExecutor));
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
+
+                    one(logDAO).canConnectToDatabase();
+                    will(returnValue(true));
+                    one(logDAO).getLastEntry();
+                    final LogEntry logEntry = new LogEntry();
+                    logEntry.setRunStatus(LogEntry.RunStatus.SUCCESS);
+                    logEntry.setVersion(fromVersion);
+                    will(returnValue(logEntry));
+                    one(adminDAO).getDatabaseName();
+                    will(returnValue("my 1. database"));
+                    one(scriptProvider).tryGetMigrationScript(fromVersion, "100");
+                    final Script script = new Script("m-099-100", "code 099 100", toVersion);
+
+                    will(returnValue(script));
+
+                    one(javaMigrationStepExecutor).tryPerformPreMigration(script);
+                    will(returnValue(Status.OK));
+                    one(scriptExecutor).execute(script, true, logDAO);
+
+                    one(javaMigrationStepExecutor).tryPerformPostMigration(script);
+                    will(returnValue(Status.createError("Bad system user code.")));
+                }
+            });
+        final DBMigrationEngine migrationEngine =
+                new DBMigrationEngine(daoFactory, scriptProvider, false);
+
+        boolean exceptionThrown = false;
+        try
+        {
+            migrationEngine.migrateTo(toVersion);
+        } catch (final EnvironmentFailureException e)
+        {
+            exceptionThrown = true;
+            assertEquals(
+                    "Java post-migration finnished with an error status ('Bad system user code.')",
+                    e.getMessage());
+        }
+        assertTrue(exceptionThrown);
+        String logContent = logRecorder.getLogContent();
+        logContent = logContent.replaceAll("\\d+ msec", "0 msec");
+        assertEquals(
+                "INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - "
+                        + "Trying to migrate database 'my 1. database' from version 099 to 101."
+                        + OSUtilities.LINE_SEPARATOR
+                        + "INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - Java pre-migration succesfull."
+                        + OSUtilities.LINE_SEPARATOR
+                        + "ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - Java post-migration finnished with an error status ('Bad system user code.')",
+                logContent);
+
+        context.assertIsSatisfied();
+    }
+
+    @Test
+    public void testMigrationStepsFailPreMigration()
+    {
+        final String fromVersion = "099";
+        final String toVersion = "101";
+        context.checking(new MyExpectations()
+            {
+                {
+                    one(daoFactory).getDatabaseDAO();
+                    will(returnValue(adminDAO));
+                    one(daoFactory).getDatabaseVersionLogDAO();
+                    will(returnValue(logDAO));
+                    one(daoFactory).getSqlScriptExecutor();
+                    will(returnValue(scriptExecutor));
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
+
+                    one(logDAO).canConnectToDatabase();
+                    will(returnValue(true));
+                    one(logDAO).getLastEntry();
+                    final LogEntry logEntry = new LogEntry();
+                    logEntry.setRunStatus(LogEntry.RunStatus.SUCCESS);
+                    logEntry.setVersion(fromVersion);
+                    will(returnValue(logEntry));
+                    one(adminDAO).getDatabaseName();
+                    will(returnValue("my 1. database"));
+                    one(scriptProvider).tryGetMigrationScript(fromVersion, "100");
+                    final Script script = new Script("m-099-100", "code 099 100", toVersion);
+                    will(returnValue(script));
+                    one(javaMigrationStepExecutor).tryPerformPreMigration(script);
+                    will(returnValue(Status.createError("Bad system user code.")));
+                }
+            });
+        final DBMigrationEngine migrationEngine =
+                new DBMigrationEngine(daoFactory, scriptProvider, false);
+        boolean exceptionThrown = false;
+        try
+        {
+            migrationEngine.migrateTo(toVersion);
+        } catch (final EnvironmentFailureException e)
+        {
+            exceptionThrown = true;
+            assertEquals(
+                    "Java pre-migration finnished with an error status ('Bad system user code.')",
+                    e.getMessage());
+        }
+        assertTrue(exceptionThrown);
+        String logContent = logRecorder.getLogContent();
+        logContent = logContent.replaceAll("\\d+ msec", "0 msec");
+        assertEquals(
+                "INFO  OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - Trying to migrate database 'my 1. database' from version 099 to 101."
+                        + OSUtilities.LINE_SEPARATOR
+                        + "ERROR OPERATION.ch.systemsx.cisd.dbmigration.DBMigrationEngine - Java pre-migration finnished with an error status ('Bad system user code.')",
+                logContent);
+
+        context.assertIsSatisfied();
+    }
+
     @Test
     public void testMigrationScriptNotFound()
     {
@@ -493,6 +664,8 @@ public class DBMigrationEngineTest
                     will(returnValue(logDAO));
                     one(daoFactory).getSqlScriptExecutor();
                     will(returnValue(scriptExecutor));
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
 
                     one(logDAO).canConnectToDatabase();
                     will(returnValue(true));
@@ -545,6 +718,8 @@ public class DBMigrationEngineTest
                     will(returnValue(logDAO));
                     one(daoFactory).getSqlScriptExecutor();
                     will(returnValue(scriptExecutor));
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
 
                     one(logDAO).canConnectToDatabase();
                     will(returnValue(true));
@@ -588,6 +763,8 @@ public class DBMigrationEngineTest
                     will(returnValue(logDAO));
                     one(daoFactory).getSqlScriptExecutor();
                     will(returnValue(scriptExecutor));
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
 
                     one(adminDAO).dropDatabase();
                     one(logDAO).canConnectToDatabase();
@@ -633,6 +810,8 @@ public class DBMigrationEngineTest
                     will(returnValue(logDAO));
                     one(daoFactory).getSqlScriptExecutor();
                     will(returnValue(scriptExecutor));
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
 
                     one(adminDAO).dropDatabase();
                     one(logDAO).canConnectToDatabase();
@@ -682,6 +861,8 @@ public class DBMigrationEngineTest
                     will(returnValue(logDAO));
                     one(daoFactory).getSqlScriptExecutor();
                     will(returnValue(scriptExecutor));
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
 
                     one(adminDAO).dropDatabase();
                     one(logDAO).canConnectToDatabase();
@@ -728,6 +909,8 @@ public class DBMigrationEngineTest
                     will(returnValue(logDAO));
                     one(daoFactory).getSqlScriptExecutor();
                     will(returnValue(scriptExecutor));
+                    one(daoFactory).getJavaMigrationStepExecutor();
+                    will(returnValue(javaMigrationStepExecutor));
 
                     one(logDAO).canConnectToDatabase();
                     will(returnValue(true));
@@ -741,6 +924,8 @@ public class DBMigrationEngineTest
                     one(scriptProvider).tryGetMigrationScript(fromVersion, toVersion);
                     final Script script = new Script("m-1-2", "code", toVersion);
                     will(returnValue(script));
+
+                    one(javaMigrationStepExecutor).tryPerformPreMigration(script);
                     one(scriptExecutor).execute(script, true, logDAO);
                     will(throwException(exception));
                 }
diff --git a/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/JavaMigrationStepExecutorTest.java b/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/JavaMigrationStepExecutorTest.java
new file mode 100644
index 00000000000..a1f5a64dac3
--- /dev/null
+++ b/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/JavaMigrationStepExecutorTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * Licensed 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 ch.systemsx.cisd.dbmigration;
+
+import javax.sql.DataSource;
+
+import org.jmock.Mockery;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.common.Script;
+import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
+import ch.systemsx.cisd.common.exceptions.StatusFlag;
+import ch.systemsx.cisd.common.logging.LogInitializer;
+
+/**
+ * Test cases for the {@link JavaMigrationStepExecutor}.
+ * 
+ * @author Izabela Adamczyk
+ */
+public class JavaMigrationStepExecutorTest
+{
+    private Mockery context;
+
+    DataSource dataSource;
+
+    @BeforeMethod
+    public void setUp()
+    {
+        context = new Mockery();
+        dataSource = context.mock(DataSource.class);
+    }
+
+    @BeforeClass(alwaysRun = true)
+    public void beforeClass() throws Exception
+    {
+        LogInitializer.init();
+    }
+
+    @Test
+    public void testHappyCase()
+    {
+        final JavaMigrationStepExecutor javaMigrationStepExecutor =
+                new JavaMigrationStepExecutor(dataSource);
+        final Script script =
+                new Script("001To002.sql",
+                        "-- JAVA ch.systemsx.cisd.dbmigration.MigrationStepFrom001To002");
+        Assert.assertTrue(javaMigrationStepExecutor.tryPerformPreMigration(script).getFlag()
+                .equals(StatusFlag.OK));
+        Assert.assertTrue(javaMigrationStepExecutor.tryPerformPostMigration(script).getFlag()
+                .equals(StatusFlag.OK));
+
+    }
+
+    @Test
+    public void testUnhappyCase()
+    {
+        final JavaMigrationStepExecutor javaMigrationStepExecutor =
+                new JavaMigrationStepExecutor(dataSource);
+        final Script script =
+                new Script("002To003.sql",
+                        "-- JAVA ch.systemsx.cisd.dbmigration.MigrationStepFrom002To003");
+
+        Assert.assertTrue(javaMigrationStepExecutor.tryPerformPreMigration(script).getFlag()
+                .equals(StatusFlag.ERROR));
+        Assert.assertTrue(javaMigrationStepExecutor.tryPerformPreMigration(script)
+                .tryGetErrorMessage().equals("bad pre"));
+
+        Assert.assertTrue(javaMigrationStepExecutor.tryPerformPostMigration(script).getFlag()
+                .equals(StatusFlag.ERROR));
+        Assert.assertTrue(javaMigrationStepExecutor.tryPerformPostMigration(script)
+                .tryGetErrorMessage().equals("bad post"));
+
+    }
+
+    @Test
+    public void testClassNotFound()
+    {
+        final JavaMigrationStepExecutor javaMigrationStepExecutor =
+                new JavaMigrationStepExecutor(dataSource);
+        final Script script =
+                new Script("003To004.sql",
+                        "-- JAVA ch.systemsx.cisd.dbmigration.MigrationStepFrom003To003");
+        boolean exceptionThrown = false;
+        try
+        {
+            javaMigrationStepExecutor.tryPerformPreMigration(script);
+        } catch (final EnvironmentFailureException e)
+        {
+            exceptionThrown = true;
+            Assert.assertEquals(e.getMessage(),
+                    "Class 'ch.systemsx.cisd.dbmigration.MigrationStepFrom003To003' not found.");
+        }
+        Assert.assertTrue(exceptionThrown);
+
+    }
+
+}
diff --git a/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/MigrationStepFrom001To002.java b/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/MigrationStepFrom001To002.java
new file mode 100644
index 00000000000..f3d4f6051f3
--- /dev/null
+++ b/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/MigrationStepFrom001To002.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * Licensed 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 ch.systemsx.cisd.dbmigration;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import ch.systemsx.cisd.common.exceptions.Status;
+
+/**
+ * @author Izabela Adamczyk
+ */
+public class MigrationStepFrom001To002 implements IMigrationStep
+{
+
+    public Status performPostMigration(final JdbcTemplate jdbcTemplate)
+    {
+
+        return Status.OK;
+    }
+
+    public Status performPreMigration(final JdbcTemplate jdbcTemplate)
+    {
+        return Status.OK;
+    }
+
+}
diff --git a/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/MigrationStepFrom002To003.java b/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/MigrationStepFrom002To003.java
new file mode 100644
index 00000000000..0bb37c79bef
--- /dev/null
+++ b/dbmigration/sourceTest/java/ch/systemsx/cisd/dbmigration/MigrationStepFrom002To003.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * Licensed 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 ch.systemsx.cisd.dbmigration;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import ch.systemsx.cisd.common.exceptions.Status;
+
+/**
+ * @author Izabela Adamczyk
+ */
+public class MigrationStepFrom002To003 implements IMigrationStep
+{
+
+    public Status performPostMigration(final JdbcTemplate jdbcTemplate)
+    {
+
+        return Status.createError("bad post");
+    }
+
+    public Status performPreMigration(final JdbcTemplate jdbcTemplate)
+    {
+        return Status.createError("bad pre");
+    }
+
+}
-- 
GitLab