diff --git a/config/alfresco/messages/schema-update.properties b/config/alfresco/messages/schema-update.properties index 4ddae6a8e7..e710b749c3 100644 --- a/config/alfresco/messages/schema-update.properties +++ b/config/alfresco/messages/schema-update.properties @@ -1,5 +1,6 @@ # Schema update messages +schema.update.msg.dialect_used=Schema managed by database dialect {0}. schema.update.msg.bypassing=Bypassing schema update checks. schema.update.msg.all_statements=All executed statements written to file {0}. schema.update.msg.no_changes=No changes were made to the schema. @@ -7,6 +8,8 @@ schema.update.msg.executing_generated_script=Executing database script {0} (Gene schema.update.msg.executing_copied_script=Executing database script {0} (Copied from {1}). schema.update.msg.executing_statement= Executing statement: {0} schema.update.msg.optional_statement_failed=Optional statement execution failed:\n SQL: {0}\n Error: {1}\n File: {2}\n Line: {3} +schema.update.warn.dialect_unsupported=Alfresco should not be used with database dialect {0}. +schema.update.err.previous_failed=A previous schema upgrade failed. Revert to the original database before attempting the upgrade again. schema.update.err.statement_failed=Statement execution failed:\n SQL: {0}\n Error: {1}\n File: {2}\n Line: {3} schema.update.err.update_failed=Schema auto-update failed schema.update.err.validation_failed=Schema validation failed diff --git a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java index 23d481e3cb..cb8eba5c9e 100644 --- a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java +++ b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java @@ -57,11 +57,12 @@ import org.apache.commons.logging.LogFactory; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; -import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.connection.UserSuppliedConnectionProvider; import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.MySQL5Dialect; +import org.hibernate.dialect.MySQLDialect; import org.hibernate.tool.hbm2ddl.DatabaseMetadata; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.springframework.context.ApplicationContext; @@ -82,6 +83,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean /** The placeholder for the configured Dialect class name: ${db.script.dialect} */ private static final String PLACEHOLDER_SCRIPT_DIALECT = "\\$\\{db\\.script\\.dialect\\}"; + private static final String MSG_DIALECT_USED = "schema.update.msg.dialect_used"; private static final String MSG_BYPASSING_SCHEMA_UPDATE = "schema.update.msg.bypassing"; private static final String MSG_NO_CHANGES = "schema.update.msg.no_changes"; private static final String MSG_ALL_STATEMENTS = "schema.update.msg.all_statements"; @@ -89,6 +91,8 @@ public class SchemaBootstrap extends AbstractLifecycleBean private static final String MSG_EXECUTING_COPIED_SCRIPT = "schema.update.msg.executing_copied_script"; private static final String MSG_EXECUTING_STATEMENT = "schema.update.msg.executing_statement"; private static final String MSG_OPTIONAL_STATEMENT_FAILED = "schema.update.msg.optional_statement_failed"; + private static final String WARN_DIALECT_UNSUPPORTED = "schema.update.warn.dialect_unsupported"; + private static final String ERR_PREVIOUS_FAILED_BOOTSTRAP = "schema.update.err.previous_failed"; private static final String ERR_STATEMENT_FAILED = "schema.update.err.statement_failed"; private static final String ERR_UPDATE_FAILED = "schema.update.err.update_failed"; private static final String ERR_VALIDATION_FAILED = "schema.update.err.validation_failed"; @@ -293,20 +297,15 @@ public class SchemaBootstrap extends AbstractLifecycleBean } /** - * @return Returns the number of applied patches + * @return Returns the name of the applied patch table, or null if the table doesn't exist */ - private boolean didPatchSucceed(Connection connection, String patchId) throws Exception + private String getAppliedPatchTableName(Connection connection) throws Exception { Statement stmt = connection.createStatement(); try { - ResultSet rs = stmt.executeQuery("select succeeded from alf_applied_patch where id = '" + patchId + "'"); - if (!rs.next()) - { - return false; - } - boolean succeeded = rs.getBoolean(1); - return succeeded; + stmt.executeQuery("select * from alf_applied_patch"); + return "alf_applied_patch"; } catch (Throwable e) { @@ -320,7 +319,35 @@ public class SchemaBootstrap extends AbstractLifecycleBean stmt = connection.createStatement(); try { - ResultSet rs = stmt.executeQuery("select succeeded from applied_patch where id = '" + patchId + "'"); + stmt.executeQuery("select * from applied_patch"); + return "applied_patch"; + } + catch (Throwable e) + { + // It is not there + return null; + } + finally + { + try { stmt.close(); } catch (Throwable e) {} + } + } + + /** + * @return Returns the number of applied patches + */ + private boolean didPatchSucceed(Connection connection, String patchId) throws Exception + { + String patchTableName = getAppliedPatchTableName(connection); + if (patchTableName == null) + { + // Table doesn't exist, yet + return false; + } + Statement stmt = connection.createStatement(); + try + { + ResultSet rs = stmt.executeQuery("select succeeded from " + patchTableName + " where id = '" + patchId + "'"); if (!rs.next()) { return false; @@ -334,6 +361,58 @@ public class SchemaBootstrap extends AbstractLifecycleBean } } + /** + * Records that the bootstrap process has started + */ + private synchronized void setBootstrapStarted(Connection connection) throws Exception + { + // We wait a for a minute to give other instances starting against the same database a + // chance to get through this process + for (int i = 0; i < 12; i++) + { + // Create the marker table + Statement stmt = connection.createStatement(); + try + { + stmt.executeUpdate("create table alf_bootstrap_lock (charval CHAR(1) NOT NULL)"); + // Success + return; + } + catch (Throwable e) + { + // Table exists - wait a bit + try { this.wait(5000L); } catch (InterruptedException ee) {} + } + finally + { + try { stmt.close(); } catch (Throwable e) {} + } + } + throw AlfrescoRuntimeException.create(ERR_PREVIOUS_FAILED_BOOTSTRAP); + } + + /** + * Records that the bootstrap process has finished + */ + private void setBootstrapCompleted(Connection connection) throws Exception + { + // Create the marker table + Statement stmt = connection.createStatement(); + try + { + stmt.executeUpdate("drop table alf_bootstrap_lock"); + } + catch (Throwable e) + { + // Table exists + throw AlfrescoRuntimeException.create(ERR_PREVIOUS_FAILED_BOOTSTRAP); + } + finally + { + try { stmt.close(); } catch (Throwable e) {} + } + } + /** * Builds the schema from scratch or applies the necessary patches to the schema. */ @@ -647,14 +726,23 @@ public class SchemaBootstrap extends AbstractLifecycleBean { // do everything in a transaction Session session = getSessionFactory().openSession(); - Transaction transaction = session.beginTransaction(); try { - // make sure that we don't autocommit + // make sure that we AUTO-COMMIT Connection connection = session.connection(); - connection.setAutoCommit(false); + connection.setAutoCommit(true); Configuration cfg = localSessionFactory.getConfiguration(); + + // Check and dump the dialect being used + Dialect dialect = Dialect.getDialect(cfg.getProperties()); + Class dialectClazz = dialect.getClass(); + LogUtil.info(logger, MSG_DIALECT_USED, dialectClazz.getName()); + if (dialectClazz.equals(MySQLDialect.class) || dialectClazz.equals(MySQL5Dialect.class)) + { + LogUtil.warn(logger, WARN_DIALECT_UNSUPPORTED, dialectClazz.getName()); + } + // Ensure that our static connection provider is used String defaultConnectionProviderFactoryClass = cfg.getProperty(Environment.CONNECTION_PROVIDER); cfg.setProperty(Environment.CONNECTION_PROVIDER, SchemaBootstrapConnectionProvider.class.getName()); @@ -663,6 +751,9 @@ public class SchemaBootstrap extends AbstractLifecycleBean // update the schema, if required if (updateSchema) { + // Check and record that the bootstrap has started + setBootstrapStarted(connection); + // Allocate buffer for executed statements executedStatementsThreadLocal.set(new StringBuilder(1024)); @@ -695,6 +786,9 @@ public class SchemaBootstrap extends AbstractLifecycleBean checkSchemaPatchScripts(cfg, session, connection, validateUpdateScriptPatches, false); // check scripts checkSchemaPatchScripts(cfg, session, connection, preUpdateScriptPatches, false); // check scripts checkSchemaPatchScripts(cfg, session, connection, postUpdateScriptPatches, false); // check scripts + + // Remove the flag indicating a running bootstrap + setBootstrapCompleted(connection); } else { @@ -705,12 +799,10 @@ public class SchemaBootstrap extends AbstractLifecycleBean cfg.setProperty(Environment.CONNECTION_PROVIDER, defaultConnectionProviderFactoryClass); // all done successfully - transaction.commit(); } catch (Throwable e) { LogUtil.error(logger, e, ERR_UPDATE_FAILED); - try { transaction.rollback(); } catch (Throwable ee) {} if (updateSchema) { throw new AlfrescoRuntimeException(ERR_UPDATE_FAILED, e);