ALF-11516: Load schema ref. file using same location and mechanism as for db create scripts

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@32049 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Matt Ward
2011-11-17 11:28:07 +00:00
parent d80f8c136a
commit d111a38a56
2 changed files with 101 additions and 45 deletions

View File

@@ -100,6 +100,9 @@
<value>classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-FK-indexes.sql</value> <value>classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-FK-indexes.sql</value>
</list> </list>
</property> </property>
<property name="schemaReferenceUrl">
<value>classpath:alfresco/dbscripts/create/${db.script.dialect}/Schema-Reference.xml</value>
</property>
<property name="validateUpdateScriptPatches"> <property name="validateUpdateScriptPatches">
<list> <list>
</list> </list>

View File

@@ -48,6 +48,7 @@ import java.sql.Savepoint;
import java.sql.Statement; import java.sql.Statement;
import java.sql.Struct; import java.sql.Struct;
import java.sql.Types; import java.sql.Types;
import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap; 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.extensions.surf.util.I18NUtil;
import org.springframework.orm.hibernate3.LocalSessionFactoryBean; import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
/** /**
* Bootstraps the schema and schema update. The schema is considered missing if the applied patch table * 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. * 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 public class SchemaBootstrap extends AbstractLifecycleBean
{ {
/** The placeholder for the configured <code>Dialect</code> class name: <b>${db.script.dialect}</b> */ /** The placeholder for the configured <code>Dialect</code> class name: <b>${db.script.dialect}</b> */
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 */ /** The global property containing the default batch size used by --FOREACH */
private static final String PROPERTY_DEFAULT_BATCH_SIZE = "system.upgrade.default.batchsize"; 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 boolean stopAfterSchemaBootstrap;
private List<String> preCreateScriptUrls; private List<String> preCreateScriptUrls;
private List<String> postCreateScriptUrls; private List<String> postCreateScriptUrls;
private String schemaReferenceUrl;
private List<SchemaUpgradeScriptPatch> validateUpdateScriptPatches; private List<SchemaUpgradeScriptPatch> validateUpdateScriptPatches;
private List<SchemaUpgradeScriptPatch> preUpdateScriptPatches; private List<SchemaUpgradeScriptPatch> preUpdateScriptPatches;
private List<SchemaUpgradeScriptPatch> postUpdateScriptPatches; private List<SchemaUpgradeScriptPatch> postUpdateScriptPatches;
@@ -304,7 +307,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean
* *
* @param postCreateScriptUrls file URLs * @param postCreateScriptUrls file URLs
* *
* @see #PLACEHOLDER_SCRIPT_DIALECT * @see #PLACEHOLDER_DIALECT
*/ */
public void setPreCreateScriptUrls(List<String> preUpdateScriptUrls) public void setPreCreateScriptUrls(List<String> preUpdateScriptUrls)
{ {
@@ -316,13 +319,28 @@ public class SchemaBootstrap extends AbstractLifecycleBean
* *
* @param postCreateScriptUrls file URLs * @param postCreateScriptUrls file URLs
* *
* @see #PLACEHOLDER_SCRIPT_DIALECT * @see #PLACEHOLDER_DIALECT
*/ */
public void setPostCreateScriptUrls(List<String> postUpdateScriptUrls) public void setPostCreateScriptUrls(List<String> postUpdateScriptUrls)
{ {
this.postCreateScriptUrls = 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 * 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 <u>cannot</u> be * applied to the database. These can be used where the script <u>cannot</u> be
@@ -940,11 +958,46 @@ public class SchemaBootstrap extends AbstractLifecycleBean
try { scriptInputStream.close(); } catch (Throwable e) {} // usually a duplicate close try { scriptInputStream.close(); } catch (Throwable e) {} // usually a duplicate close
} }
// now execute it // 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 // Replace the script placeholders
executeScriptFile(cfg, connection, tempFile, dialectScriptUrl); 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 * 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 * 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 private InputStream getScriptInputStream(Class dialectClazz, String scriptUrl) throws Exception
{ {
// replace the dialect placeholder return getDialectResource(dialectClazz, scriptUrl).getInputStream();
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();
}
} }
/** /**
@@ -998,6 +1028,9 @@ public class SchemaBootstrap extends AbstractLifecycleBean
StringBuilder executedStatements = executedStatementsThreadLocal.get(); StringBuilder executedStatements = executedStatementsThreadLocal.get();
if (executedStatements == null) 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. // Dump the normalized, pre-upgrade Alfresco schema. We keep the file for later reporting.
xmlPreSchemaOutputFile = dumpSchema( xmlPreSchemaOutputFile = dumpSchema(
this.dialect, this.dialect,
@@ -1494,11 +1527,13 @@ public class SchemaBootstrap extends AbstractLifecycleBean
setBootstrapCompleted(connection); setBootstrapCompleted(connection);
} }
validateSchema();
// Report normalized dumps // Report normalized dumps
if (executedStatements != null) if (executedStatements != null)
{ {
// Validate the schema, post-upgrade
validateSchema("Alfresco-{0}-Validation-Post-Upgrade-");
// Dump the normalized, post-upgrade Alfresco schema. // Dump the normalized, post-upgrade Alfresco schema.
File xmlPostSchemaOutputFile = dumpSchema( File xmlPostSchemaOutputFile = dumpSchema(
this.dialect, this.dialect,
@@ -1598,20 +1633,16 @@ public class SchemaBootstrap extends AbstractLifecycleBean
* Collate differences and validation problems with the schema with respect to an appropriate * Collate differences and validation problems with the schema with respect to an appropriate
* reference schema. * reference schema.
*/ */
private void validateSchema() private void validateSchema(String outputFileNameTemplate)
{ {
Date startTime = new Date(); 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()) if (!referenceResource.exists())
{ {
logger.info("No reference schema file, expected: " + referenceResource); logger.debug("No reference schema file, expected: " + referenceResource);
return; return;
} }
logger.info("Comparing database schema with reference schema: " + referenceResource);
InputStream is = null; InputStream is = null;
try try
@@ -1638,10 +1669,13 @@ public class SchemaBootstrap extends AbstractLifecycleBean
Results differences = schemaComparator.getDifferences(); Results differences = schemaComparator.getDifferences();
List<ValidationResult> validationResults = schemaComparator.getValidationResults(); List<ValidationResult> validationResults = schemaComparator.getValidationResults();
File outputFile = TempFileProvider.createTempFile( String outputFileName = MessageFormat.format(
"Alfresco-" + dialect.getClass().getSimpleName() + "-Validation", ".txt"); outputFileNameTemplate,
new Object[] { dialect.getClass().getSimpleName() });
File outputFile = TempFileProvider.createTempFile(outputFileName, ".txt");
logger.info("Writing schema validation results to: " + outputFile);
PrintWriter pw = null; PrintWriter pw = null;
try try
@@ -1660,20 +1694,27 @@ public class SchemaBootstrap extends AbstractLifecycleBean
.append(" (diff): ") .append(" (diff): ")
.append(difference.getWhere()); .append(difference.getWhere());
sb.append(" reference: "); sb.append(" reference path:");
if (difference.getLeft() != null) if (difference.getLeft() != null)
{ {
sb.append(difference.getLeft().getPath()); sb.append(difference.getLeft().getPath());
sb.append(" (value: ")
.append(difference.getLeft().getPropertyValue())
.append(")");
} }
else else
{ {
sb.append("null"); sb.append("null");
} }
sb.append(" target: "); sb.append(" target path:");
if (difference.getRight() != null) if (difference.getRight() != null)
{ {
sb.append(difference.getRight().getPath()); sb.append(difference.getRight().getPath());
sb.append(" (value: ")
.append(difference.getRight().getPropertyValue())
.append(")");
} }
else else
{ {
@@ -1688,19 +1729,31 @@ public class SchemaBootstrap extends AbstractLifecycleBean
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
sb.append(validationResult.getStrength()) sb.append(validationResult.getStrength())
.append(" (validation): ") .append(" (validation): ")
.append("target: ") .append("target path:")
.append(validationResult.getDbProperty().getPath()) .append(validationResult.getDbProperty().getPath())
.append(" value: ") .append(" (value: ")
.append(validationResult.getValue()); .append(validationResult.getValue())
.append(")");
pw.println(sb); pw.println(sb);
} }
pw.close(); 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(); Date endTime = new Date();
long durationMillis = endTime.getTime() - startTime.getTime(); long durationMillis = endTime.getTime() - startTime.getTime();
logger.info("Schema validation took " + durationMillis + "ms"); logger.debug("Schema validation took " + durationMillis + "ms");
} }
/** /**