diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml
index a247ec35ea..b778aaabb2 100644
--- a/config/alfresco/bootstrap-context.xml
+++ b/config/alfresco/bootstrap-context.xml
@@ -100,6 +100,9 @@
classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-FK-indexes.sql
+
+ classpath:alfresco/dbscripts/create/${db.script.dialect}/Schema-Reference.xml
+
diff --git a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java
index d1f4cfdae4..2b0c3559f7 100644
--- a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java
+++ b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java
@@ -48,6 +48,7 @@ import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.sql.Types;
+import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@@ -112,6 +113,7 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.extensions.surf.util.I18NUtil;
import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
+
/**
* Bootstraps the schema and schema update. The schema is considered missing if the applied patch table
* is not present, and the schema is considered empty if the applied patch table is empty.
@@ -121,7 +123,7 @@ import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
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 PLACEHOLDER_DIALECT = "\\$\\{db\\.script\\.dialect\\}";
/** The global property containing the default batch size used by --FOREACH */
private static final String PROPERTY_DEFAULT_BATCH_SIZE = "system.upgrade.default.batchsize";
@@ -226,6 +228,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean
private boolean stopAfterSchemaBootstrap;
private List preCreateScriptUrls;
private List postCreateScriptUrls;
+ private String schemaReferenceUrl;
private List validateUpdateScriptPatches;
private List preUpdateScriptPatches;
private List postUpdateScriptPatches;
@@ -304,7 +307,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean
*
* @param postCreateScriptUrls file URLs
*
- * @see #PLACEHOLDER_SCRIPT_DIALECT
+ * @see #PLACEHOLDER_DIALECT
*/
public void setPreCreateScriptUrls(List preUpdateScriptUrls)
{
@@ -316,13 +319,28 @@ public class SchemaBootstrap extends AbstractLifecycleBean
*
* @param postCreateScriptUrls file URLs
*
- * @see #PLACEHOLDER_SCRIPT_DIALECT
+ * @see #PLACEHOLDER_DIALECT
*/
public void setPostCreateScriptUrls(List postUpdateScriptUrls)
{
this.postCreateScriptUrls = postUpdateScriptUrls;
}
+
+ /**
+ * Specifies the schema reference file that will be used to validate the repository
+ * schema whenever changes have been made. The database dialect placeholder will be
+ * resolved so that the correct reference file is loaded for the current database
+ * type (e.g. PostgreSQL)
+ *
+ * @param schemaReferenceUrl the schemaReferenceUrl to set
+ * @see #PLACEHOLDER_DIALECT
+ */
+ public void setSchemaReferenceUrl(String schemaReferenceUrl)
+ {
+ this.schemaReferenceUrl = schemaReferenceUrl;
+ }
+
/**
* Set the schema script patches that must have been applied. These will not be
* applied to the database. These can be used where the script cannot be
@@ -940,11 +958,46 @@ public class SchemaBootstrap extends AbstractLifecycleBean
try { scriptInputStream.close(); } catch (Throwable e) {} // usually a duplicate close
}
// now execute it
- String dialectScriptUrl = scriptUrl.replaceAll(PLACEHOLDER_SCRIPT_DIALECT, dialect.getClass().getName());
+ String dialectScriptUrl = scriptUrl.replaceAll(PLACEHOLDER_DIALECT, dialect.getClass().getName());
// Replace the script placeholders
executeScriptFile(cfg, connection, tempFile, dialectScriptUrl);
}
+ /**
+ * Replaces the dialect placeholder in the resource URL and attempts to find a file for
+ * it. If not found, the dialect hierarchy will be walked until a compatible resource is
+ * found. This makes it possible to have resources that are generic to all dialects.
+ *
+ * @return The Resource, otherwise null
+ */
+ private Resource getDialectResource(Class dialectClass, String resourceUrl)
+ {
+ // replace the dialect placeholder
+ String dialectResourceUrl = resourceUrl.replaceAll(PLACEHOLDER_DIALECT, dialectClass.getName());
+ // get a handle on the resource
+ Resource resource = rpr.getResource(dialectResourceUrl);
+ if (!resource.exists())
+ {
+ // it wasn't found. Get the superclass of the dialect and try again
+ Class superClass = dialectClass.getSuperclass();
+ if (Dialect.class.isAssignableFrom(superClass))
+ {
+ // we still have a Dialect - try again
+ return getDialectResource(superClass, resourceUrl);
+ }
+ else
+ {
+ // we have exhausted all options
+ return null;
+ }
+ }
+ else
+ {
+ // we have a handle to it
+ return resource;
+ }
+ }
+
/**
* Replaces the dialect placeholder in the script URL and attempts to find a file for
* it. If not found, the dialect hierarchy will be walked until a compatible script is
@@ -954,30 +1007,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean
*/
private InputStream getScriptInputStream(Class dialectClazz, String scriptUrl) throws Exception
{
- // replace the dialect placeholder
- String dialectScriptUrl = scriptUrl.replaceAll(PLACEHOLDER_SCRIPT_DIALECT, dialectClazz.getName());
- // get a handle on the resource
- Resource resource = rpr.getResource(dialectScriptUrl);
- if (!resource.exists())
- {
- // it wasn't found. Get the superclass of the dialect and try again
- Class superClazz = dialectClazz.getSuperclass();
- if (Dialect.class.isAssignableFrom(superClazz))
- {
- // we still have a Dialect - try again
- return getScriptInputStream(superClazz, scriptUrl);
- }
- else
- {
- // we have exhausted all options
- return null;
- }
- }
- else
- {
- // we have a handle to it
- return resource.getInputStream();
- }
+ return getDialectResource(dialectClazz, scriptUrl).getInputStream();
}
/**
@@ -998,6 +1028,9 @@ public class SchemaBootstrap extends AbstractLifecycleBean
StringBuilder executedStatements = executedStatementsThreadLocal.get();
if (executedStatements == null)
{
+ // Validate the schema, pre-upgrade
+ validateSchema("Alfresco-{0}-Validation-Pre-Upgrade-");
+
// Dump the normalized, pre-upgrade Alfresco schema. We keep the file for later reporting.
xmlPreSchemaOutputFile = dumpSchema(
this.dialect,
@@ -1494,11 +1527,13 @@ public class SchemaBootstrap extends AbstractLifecycleBean
setBootstrapCompleted(connection);
}
- validateSchema();
// Report normalized dumps
if (executedStatements != null)
{
+ // Validate the schema, post-upgrade
+ validateSchema("Alfresco-{0}-Validation-Post-Upgrade-");
+
// Dump the normalized, post-upgrade Alfresco schema.
File xmlPostSchemaOutputFile = dumpSchema(
this.dialect,
@@ -1598,20 +1633,16 @@ public class SchemaBootstrap extends AbstractLifecycleBean
* Collate differences and validation problems with the schema with respect to an appropriate
* reference schema.
*/
- private void validateSchema()
+ private void validateSchema(String outputFileNameTemplate)
{
Date startTime = new Date();
- String referenceFileName = dialect.getClass().getSimpleName() + "-Reference.xml";
-
- Resource referenceResource = rpr.getResource("classpath:org/alfresco/util/schemacomp/reference/"
- + referenceFileName);
+ Resource referenceResource = getDialectResource(dialect.getClass(), schemaReferenceUrl);
if (!referenceResource.exists())
{
- logger.info("No reference schema file, expected: " + referenceResource);
+ logger.debug("No reference schema file, expected: " + referenceResource);
return;
}
- logger.info("Comparing database schema with reference schema: " + referenceResource);
InputStream is = null;
try
@@ -1638,10 +1669,13 @@ public class SchemaBootstrap extends AbstractLifecycleBean
Results differences = schemaComparator.getDifferences();
List validationResults = schemaComparator.getValidationResults();
- File outputFile = TempFileProvider.createTempFile(
- "Alfresco-" + dialect.getClass().getSimpleName() + "-Validation", ".txt");
+ String outputFileName = MessageFormat.format(
+ outputFileNameTemplate,
+ new Object[] { dialect.getClass().getSimpleName() });
+
+ File outputFile = TempFileProvider.createTempFile(outputFileName, ".txt");
+
- logger.info("Writing schema validation results to: " + outputFile);
PrintWriter pw = null;
try
@@ -1660,20 +1694,27 @@ public class SchemaBootstrap extends AbstractLifecycleBean
.append(" (diff): ")
.append(difference.getWhere());
- sb.append(" reference: ");
+ sb.append(" reference path:");
if (difference.getLeft() != null)
{
sb.append(difference.getLeft().getPath());
+ sb.append(" (value: ")
+ .append(difference.getLeft().getPropertyValue())
+ .append(")");
+
}
else
{
sb.append("null");
}
- sb.append(" target: ");
+ sb.append(" target path:");
if (difference.getRight() != null)
{
sb.append(difference.getRight().getPath());
+ sb.append(" (value: ")
+ .append(difference.getRight().getPropertyValue())
+ .append(")");
}
else
{
@@ -1688,19 +1729,31 @@ public class SchemaBootstrap extends AbstractLifecycleBean
StringBuffer sb = new StringBuffer();
sb.append(validationResult.getStrength())
.append(" (validation): ")
- .append("target: ")
+ .append("target path:")
.append(validationResult.getDbProperty().getPath())
- .append(" value: ")
- .append(validationResult.getValue());
+ .append(" (value: ")
+ .append(validationResult.getValue())
+ .append(")");
pw.println(sb);
}
pw.close();
+ if (validationResults.size() == 0 && differences.size() == 0)
+ {
+ logger.info("Compared database schema with reference schema (all OK): " + referenceResource);
+ }
+ else
+ {
+ int numProblems = validationResults.size() + differences.size();
+ logger.warn("Schema validation found " + numProblems +
+ " potential problems, results written to: "
+ + outputFile);
+ }
Date endTime = new Date();
long durationMillis = endTime.getTime() - startTime.getTime();
- logger.info("Schema validation took " + durationMillis + "ms");
+ logger.debug("Schema validation took " + durationMillis + "ms");
}
/**