ALF-11256: SchemaBootstrap must compare running schema against reference dump

Reference schemas (e.g. classpath:org/alfresco/util/schemacomp/reference/PostgreSQLDialect-Reference.xml) are loaded (if present) and the database is compared to that reference schema.



git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@31991 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Matt Ward 2011-11-15 15:03:22 +00:00
parent 8bb6a9a372
commit f5b08ab073
20 changed files with 357 additions and 161 deletions

View File

@ -18,13 +18,17 @@
*/
package org.alfresco.repo.domain.schema;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Writer;
import java.sql.Array;
import java.sql.Blob;
@ -45,6 +49,7 @@ import java.sql.Statement;
import java.sql.Struct;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -66,6 +71,13 @@ import org.alfresco.repo.domain.hibernate.dialect.AlfrescoSybaseAnywhereDialect;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.util.LogUtil;
import org.alfresco.util.TempFileProvider;
import org.alfresco.util.schemacomp.Difference;
import org.alfresco.util.schemacomp.ExportDb;
import org.alfresco.util.schemacomp.Results;
import org.alfresco.util.schemacomp.SchemaComparator;
import org.alfresco.util.schemacomp.ValidationResult;
import org.alfresco.util.schemacomp.XMLToSchema;
import org.alfresco.util.schemacomp.model.Schema;
import org.alfresco.util.schemadump.Main;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -91,6 +103,8 @@ import org.hibernate.engine.ActionQueue;
import org.hibernate.tool.hbm2ddl.DatabaseMetadata;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.core.Conventions;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
@ -1480,6 +1494,8 @@ public class SchemaBootstrap extends AbstractLifecycleBean
setBootstrapCompleted(connection);
}
validateSchema();
// Report normalized dumps
if (executedStatements != null)
{
@ -1578,6 +1594,115 @@ public class SchemaBootstrap extends AbstractLifecycleBean
}
}
/**
* Collate differences and validation problems with the schema with respect to an appropriate
* reference schema.
*/
private void validateSchema()
{
Date startTime = new Date();
String referenceFileName = dialect.getClass().getSimpleName() + "-Reference.xml";
Resource referenceResource = rpr.getResource("classpath:org/alfresco/util/schemacomp/reference/"
+ referenceFileName);
if (!referenceResource.exists())
{
logger.info("No reference schema file, expected: " + referenceResource);
return;
}
logger.info("Comparing database schema with reference schema: " + referenceResource);
InputStream is = null;
try
{
is = new BufferedInputStream(referenceResource.getInputStream());
}
catch (IOException e)
{
throw new RuntimeException("Unable to open schema reference file: " + referenceResource);
}
XMLToSchema xmlToSchema = new XMLToSchema(is);
xmlToSchema.parse();
Schema reference = xmlToSchema.getSchema();
ExportDb exporter = new ExportDb(dataSource, dialect);
exporter.execute();
Schema target = exporter.getSchema();
SchemaComparator schemaComparator = new SchemaComparator(reference, target, dialect);
schemaComparator.validateAndCompare();
Results differences = schemaComparator.getDifferences();
List<ValidationResult> validationResults = schemaComparator.getValidationResults();
File outputFile = TempFileProvider.createTempFile(
"Alfresco-" + dialect.getClass().getSimpleName() + "-Validation", ".txt");
logger.info("Writing schema validation results to: " + outputFile);
PrintWriter pw = null;
try
{
pw = new PrintWriter(outputFile);
}
catch (FileNotFoundException error)
{
throw new RuntimeException("Unable to open file for writing: " + outputFile);
}
for (Difference difference : differences)
{
StringBuffer sb = new StringBuffer();
sb.append(difference.getStrength())
.append(" (diff): ")
.append(difference.getWhere());
sb.append(" reference: ");
if (difference.getLeft() != null)
{
sb.append(difference.getLeft().getPath());
}
else
{
sb.append("null");
}
sb.append(" target: ");
if (difference.getRight() != null)
{
sb.append(difference.getRight().getPath());
}
else
{
sb.append("null");
}
pw.println(sb);
}
for (ValidationResult validationResult : validationResults)
{
StringBuffer sb = new StringBuffer();
sb.append(validationResult.getStrength())
.append(" (validation): ")
.append("target: ")
.append(validationResult.getDbProperty().getPath())
.append(" value: ")
.append(validationResult.getValue());
pw.println(sb);
}
pw.close();
Date endTime = new Date();
long durationMillis = endTime.getTime() - startTime.getTime();
logger.info("Schema validation took " + durationMillis + "ms");
}
/**
* @return Returns the file that was written to or <tt>null</tt> if it failed
*/

View File

@ -29,7 +29,6 @@ import org.alfresco.util.schemacomp.model.PrimaryKey;
import org.alfresco.util.schemacomp.model.Schema;
import org.alfresco.util.schemacomp.model.Table;
import org.alfresco.util.schemacomp.validator.DbValidator;
import org.apache.poi.hssf.record.DVALRecord;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
@ -91,12 +90,12 @@ public class DbObjectXMLTransformer
* @param validators
* @throws SAXException
*/
private void transformValidators(List<DbValidator<? extends DbObject>> validators) throws SAXException
private void transformValidators(List<DbValidator> validators) throws SAXException
{
if (validators.size() > 0)
{
simpleStartTag(XML.EL_VALIDATORS);
for (DbValidator<? extends DbObject> dbv : validators)
for (DbValidator dbv : validators)
{
final AttributesImpl attribs = new AttributesImpl();
attribs.addAttribute("", "", XML.ATTR_CLASS, "CDATA", dbv.getClass().getName());

View File

@ -34,7 +34,6 @@ import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
@ -47,7 +46,6 @@ import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import org.alfresco.util.schemacomp.model.Column;
import org.alfresco.util.schemacomp.model.DbObject;
import org.alfresco.util.schemacomp.model.ForeignKey;
import org.alfresco.util.schemacomp.model.Index;
import org.alfresco.util.schemacomp.model.PrimaryKey;
@ -268,7 +266,7 @@ public class DbObjectXMLTransformerTest
NameValidator nameValidator = new NameValidator();
nameValidator.setPattern(Pattern.compile("match_me_if_you_can"));
List<DbValidator<? extends DbObject>> validators = new ArrayList<DbValidator<? extends DbObject>>();
List<DbValidator> validators = new ArrayList<DbValidator>();
validators.add(nameValidator);
table.setValidators(validators);

View File

@ -58,11 +58,12 @@ public class ExportDb
private Dialect dialect;
private String namePrefix = "%";
/** Only top-level tables starting with namePrefix will be exported, set to empty string for all objects */
private String namePrefix = "alf_";
public ExportDb(ApplicationContext context) throws Exception
public ExportDb(ApplicationContext context)
{
this((DataSource) context.getBean("dataSource"),
(Dialect) context.getBean("dialect"));
@ -75,7 +76,7 @@ public class ExportDb
* @param connection the database connection to use for metadata queries
* @param dialect the Hibernate dialect
*/
public ExportDb(final DataSource dataSource, final Dialect dialect) throws Exception
public ExportDb(final DataSource dataSource, final Dialect dialect)
{
this.dataSource = dataSource;
this.dialect = dialect;
@ -83,12 +84,41 @@ public class ExportDb
}
private void init()
{
try
{
attemptInit();
}
catch (SecurityException error)
{
throw new RuntimeException("Unable to generate type map using hibernate.", error);
}
catch (IllegalArgumentException error)
{
throw new RuntimeException("Unable to generate type map using hibernate.", error);
}
catch (NoSuchFieldException error)
{
throw new RuntimeException("Unable to generate type map using hibernate.", error);
}
catch (IllegalAccessException error)
{
throw new RuntimeException("Unable to generate type map using hibernate.", error);
}
}
/**
* Initializes the fields ready to perform the database metadata reading
* @param dialect the Hibernate dialect
* @throws NoSuchFieldException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
@SuppressWarnings("unchecked")
private void init() throws Exception
private void attemptInit() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException
{
final Field typeNamesField = Dialect.class.getDeclaredField("typeNames");
typeNamesField.setAccessible(true);
@ -205,7 +235,7 @@ public class ExportDb
String nullableString = columns.getString("IS_NULLABLE");
column.setNullable(parseBoolean(nullableString));
column.setParent(table);
table.getColumns().add(column);
}
columns.close();
@ -231,6 +261,7 @@ public class ExportDb
// If this table has a primary key, add it.
if (pk != null)
{
pk.setParent(table);
table.setPrimaryKey(pk);
}
@ -259,6 +290,7 @@ public class ExportDb
{
index = new Index(indexName);
index.setUnique(!indexes.getBoolean("NON_UNIQUE"));
index.setParent(table);
table.getIndexes().add(index);
lastIndexName = indexName;
}
@ -283,6 +315,7 @@ public class ExportDb
if (!keyName.equals(lastKeyName))
{
fk = new ForeignKey(keyName);
fk.setParent(table);
table.getForeignKeys().add(fk);
lastKeyName = keyName;
}

View File

@ -21,6 +21,8 @@ package org.alfresco.util.schemacomp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
import java.util.Iterator;
@ -30,6 +32,7 @@ import org.alfresco.util.schemacomp.model.Column;
import org.alfresco.util.schemacomp.model.DbObject;
import org.alfresco.util.schemacomp.model.ForeignKey;
import org.alfresco.util.schemacomp.model.Index;
import org.alfresco.util.schemacomp.model.PrimaryKey;
import org.alfresco.util.schemacomp.model.Schema;
import org.alfresco.util.schemacomp.model.Sequence;
import org.alfresco.util.schemacomp.model.Table;
@ -55,7 +58,6 @@ public class ExportDbTest
exporter = new ExportDb(ctx);
}
@Ignore
@Test
public void exportDb() throws Exception
@ -64,6 +66,8 @@ public class ExportDbTest
exporter.execute();
Schema schema = exporter.getSchema();
assertNull("Schema shouldn't have a parent", schema.getParent());
System.out.println(schema);
Table appliedPatchTable = null;
@ -87,10 +91,10 @@ public class ExportDbTest
}
checkResultsFiltered(schema, "alf_");
checkAppliedPatchTable(appliedPatchTable);
checkQNameTable(qNameTable);
checkAppliedPatchTable(schema, appliedPatchTable);
checkQNameTable(schema, qNameTable);
// TODO: what to do about sequences? They can't easily be retrieved with JDBC's DatabaseMetaData
//checkAuthoritySequence(authoritySeq);
//checkAuthoritySequence(schema, authoritySeq);
}
@ -116,7 +120,7 @@ public class ExportDbTest
/**
* @param qNameTable
*/
private void checkQNameTable(Table qNameTable)
private void checkQNameTable(Schema schema, Table qNameTable)
{
/*
CREATE TABLE alf_qname
@ -133,24 +137,30 @@ public class ExportDbTest
*/
assertNotNull("Couldn't find table alf_qname", qNameTable);
assertSame("Incorrect parent or no parent set", schema, qNameTable.getParent());
Iterator<Column> colIt = qNameTable.getColumns().iterator();
Column col = colIt.next();
assertSame("Incorrect parent or no parent set", qNameTable, col.getParent());
assertEquals("id", col.getName());
assertEquals("int8", col.getType());
assertEquals(false, col.isNullable());
col = colIt.next();
assertSame("Incorrect parent or no parent set", qNameTable, col.getParent());
assertEquals("version", col.getName());
assertEquals("int8", col.getType());
assertEquals(false, col.isNullable());
col = colIt.next();
assertSame("Incorrect parent or no parent set", qNameTable, col.getParent());
assertEquals("ns_id", col.getName());
assertEquals("int8", col.getType());
assertEquals(false, col.isNullable());
col = colIt.next();
assertSame("Incorrect parent or no parent set", qNameTable, col.getParent());
assertSame("Incorrect parent or no parent set", qNameTable, col.getParent());
assertEquals("local_name", col.getName());
assertEquals("varchar(200)", col.getType());
assertEquals(false, col.isNullable());
@ -159,6 +169,7 @@ public class ExportDbTest
Iterator<Index> indexIt = qNameTable.getIndexes().iterator();
Index index = indexIt.next();
assertSame("Incorrect parent or no parent set", qNameTable, index.getParent());
assertEquals("ns_id", index.getName());
assertEquals(true, index.isUnique());
assertEquals(2, index.getColumnNames().size());
@ -166,14 +177,18 @@ public class ExportDbTest
assertEquals("local_name", index.getColumnNames().get(1));
index = indexIt.next();
assertSame("Incorrect parent or no parent set", qNameTable, index.getParent());
assertEquals("fk_alf_qname_ns", index.getName());
assertEquals(1, index.getColumnNames().size());
assertEquals("ns_id", index.getColumnNames().get(0));
assertEquals("id", qNameTable.getPrimaryKey().getColumnNames().get(0));
PrimaryKey pk = qNameTable.getPrimaryKey();
assertSame("Incorrect parent or no parent set", qNameTable, pk.getParent());
assertEquals("id", pk.getColumnNames().get(0));
assertEquals(1, qNameTable.getForeignKeys().size());
ForeignKey fk = qNameTable.getForeignKeys().get(0);
assertSame("Incorrect parent or no parent set", qNameTable, fk.getParent());
assertEquals("fk_alf_qname_ns", fk.getName());
assertEquals("ns_id", fk.getLocalColumn());
assertEquals("alf_namespace", fk.getTargetTable());
@ -184,7 +199,7 @@ public class ExportDbTest
/**
* @param appliedPatch
*/
private void checkAppliedPatchTable(Table appliedPatch)
private void checkAppliedPatchTable(Schema schema, Table appliedPatch)
{
/*
CREATE TABLE alf_applied_patch
@ -205,73 +220,88 @@ public class ExportDbTest
*/
assertNotNull("Couldn't find alf_applied_patch", appliedPatch);
assertSame("Incorrect parent or no parent set", schema, appliedPatch.getParent());
assertEquals("alf_applied_patch", appliedPatch.getName());
Iterator<Column> colIt = appliedPatch.getColumns().iterator();
Column col = colIt.next();
assertSame("Incorrect parent or no parent set", appliedPatch, col.getParent());
assertEquals("id", col.getName());
assertEquals("varchar(64)", col.getType());
assertEquals(false, col.isNullable());
col = colIt.next();
assertSame("Incorrect parent or no parent set", appliedPatch, col.getParent());
assertEquals("description", col.getName());
assertEquals("varchar(1024)", col.getType());
assertEquals(true, col.isNullable());
col = colIt.next();
assertSame("Incorrect parent or no parent set", appliedPatch, col.getParent());
assertEquals("fixes_from_schema", col.getName());
assertEquals("int4", col.getType());
assertEquals(true, col.isNullable());
col = colIt.next();
assertSame("Incorrect parent or no parent set", appliedPatch, col.getParent());
assertEquals("fixes_to_schema", col.getName());
assertEquals("int4", col.getType());
assertEquals(true, col.isNullable());
col = colIt.next();
assertSame("Incorrect parent or no parent set", appliedPatch, col.getParent());
assertEquals("applied_to_schema", col.getName());
assertEquals("int4", col.getType());
assertEquals(true, col.isNullable());
col = colIt.next();
assertSame("Incorrect parent or no parent set", appliedPatch, col.getParent());
assertEquals("target_schema", col.getName());
assertEquals("int4", col.getType());
assertEquals(true, col.isNullable());
col = colIt.next();
assertSame("Incorrect parent or no parent set", appliedPatch, col.getParent());
assertEquals("applied_on_date", col.getName());
assertEquals("timestamp", col.getType());
assertEquals(true, col.isNullable());
col = colIt.next();
assertSame("Incorrect parent or no parent set", appliedPatch, col.getParent());
assertEquals("applied_to_server", col.getName());
assertEquals("varchar(64)", col.getType());
assertEquals(true, col.isNullable());
col = colIt.next();
assertSame("Incorrect parent or no parent set", appliedPatch, col.getParent());
assertEquals("was_executed", col.getName());
assertEquals("bool", col.getType());
assertEquals(true, col.isNullable());
col = colIt.next();
assertSame("Incorrect parent or no parent set", appliedPatch, col.getParent());
assertEquals("succeeded", col.getName());
assertEquals("bool", col.getType());
assertEquals(true, col.isNullable());
col = colIt.next();
assertSame("Incorrect parent or no parent set", appliedPatch, col.getParent());
assertEquals("report", col.getName());
assertEquals("varchar(1024)", col.getType());
assertEquals(true, col.isNullable());
assertEquals("id", appliedPatch.getPrimaryKey().getColumnNames().get(0));
PrimaryKey pk = appliedPatch.getPrimaryKey();
assertSame("Incorrect parent or no parent set", appliedPatch, pk.getParent());
assertEquals("id", pk.getColumnNames().get(0));
}
public void checkAuthoritySequence(Sequence seq)
public void checkAuthoritySequence(Schema schema, Sequence seq)
{
/*
CREATE SEQUENCE alf_authority_seq START WITH 1 INCREMENT BY 1;
*/
assertNotNull("Couldn't find sequence alf_authority_seq", seq);
assertSame("Incorrect parent or no parent set", schema, seq.getParent());
assertEquals("alf_authority_seq", seq.getName());
}
}

View File

@ -26,34 +26,35 @@ import org.alfresco.util.schemacomp.model.Schema;
import org.hibernate.dialect.Dialect;
/**
* Compares two abstract database schemas, a 'left' schema and a 'right' schema. The left schema is the primary
* schema and all objects in the right schema are judged relative to it.
* Compares two abstract database schemas, a reference schema and a target schema (the schema to check for validity).
* The reference schema is the primary schema and all objects in the right schema are judged relative to it.
*
* @author Matt Ward
*/
public class SchemaComparator
{
private final Schema leftSchema;
private final Schema rightSchema;
private final Schema referenceSchema;
private final Schema targetSchema;
private final DiffContext ctx;
/**
* Construct a comparator to compare schemas left and right.
* Construct a comparator to compare a target schema against a reference schema. Validators supplied
* by the reference schema will be used to validate the target schema.
*
* @param left
* @param right
* @param referenceSchema
* @param targetSchema
*/
public SchemaComparator(Schema left, Schema right, Dialect dialect)
public SchemaComparator(Schema referenceSchema, Schema targetSchema, Dialect dialect)
{
this.leftSchema = left;
this.rightSchema = right;
this.ctx = new DiffContext(dialect, new Results(), new ArrayList<ValidationResult>(), leftSchema, rightSchema);
this.referenceSchema = referenceSchema;
this.targetSchema = targetSchema;
this.ctx = new DiffContext(dialect, new Results(), new ArrayList<ValidationResult>(), referenceSchema, targetSchema);
}
public void validateAndCompare()
{
validate();
validateTargetSchema();
compare();
}
@ -63,18 +64,17 @@ public class SchemaComparator
*/
private void compare()
{
leftSchema.diff(rightSchema, ctx, Strength.ERROR);
referenceSchema.diff(targetSchema, ctx, Strength.ERROR);
}
/**
* Validate both schemas.
* Validate the target schema against the reference schema using the reference schema's validators.
*/
private void validate()
private void validateTargetSchema()
{
ValidatingVisitor validatingVisitor = new ValidatingVisitor(ctx);
leftSchema.accept(validatingVisitor);
rightSchema.accept(validatingVisitor);
referenceSchema.accept(validatingVisitor);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
* Copytarget (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@ -50,15 +50,15 @@ import org.junit.Test;
public class SchemaComparatorTest
{
private SchemaComparator comparator;
private Schema left;
private Schema right;
private Schema reference;
private Schema target;
private Dialect dialect;
@Before
public void setup()
{
left = new Schema("left_schema");
right = new Schema("right_schema");
reference = new Schema("reference_schema");
target = new Schema("target_schema");
dialect = new MySQL5InnoDBDialect();
}
@ -66,24 +66,24 @@ public class SchemaComparatorTest
@Test
public void canPerformDiff()
{
// Left hand side's database objects.
left.add(new Table(left, "tbl_no_diff", columns("id NUMBER(10)", "nodeRef VARCHAR2(200)", "name VARCHAR2(150)"),
// Reference schema's database objects.
reference.add(new Table(reference, "tbl_no_diff", columns("id NUMBER(10)", "nodeRef VARCHAR2(200)", "name VARCHAR2(150)"),
pk("pk_tbl_no_diff", "id"), fkeys(fk("fk_tbl_no_diff", "nodeRef", "node", "nodeRef")),
indexes("idx_node id nodeRef")));
left.add(table("table_in_left"));
left.add(new Table(left, "tbl_has_diff_pk", columns("id NUMBER(10)", "nodeRef VARCHAR2(200)"),
reference.add(table("table_in_reference"));
reference.add(new Table(reference, "tbl_has_diff_pk", columns("id NUMBER(10)", "nodeRef VARCHAR2(200)"),
pk("pk_is_diff", "id"), fkeys(), indexes("idx_one id nodeRef", "idx_two id")));
// Right hand side's database objects.
right.add(new Table(right, "tbl_no_diff", columns("id NUMBER(10)", "nodeRef VARCHAR2(200)", "name VARCHAR2(150)"),
// Target schema's database objects.
target.add(new Table(target, "tbl_no_diff", columns("id NUMBER(10)", "nodeRef VARCHAR2(200)", "name VARCHAR2(150)"),
pk("pk_tbl_no_diff", "id"), fkeys(fk("fk_tbl_no_diff", "nodeRef", "node", "nodeRef")),
indexes("idx_node id nodeRef")));
right.add(new Table(right, "tbl_has_diff_pk", columns("id NUMBER(10)", "nodeRef VARCHAR2(200)"),
target.add(new Table(target, "tbl_has_diff_pk", columns("id NUMBER(10)", "nodeRef VARCHAR2(200)"),
pk("pk_is_diff", "nodeRef"), fkeys(), indexes("idx_one id nodeRef", "idx_two [unique] id")));
right.add(table("table_in_right"));
target.add(table("table_in_target"));
comparator = new SchemaComparator(left, right, dialect);
comparator = new SchemaComparator(reference, target, dialect);
comparator.validateAndCompare();
// See stdout for diagnostics dump...
@ -95,59 +95,59 @@ public class SchemaComparatorTest
Iterator<Difference> it = differences.iterator();
// Schema names are different ("left_schema" vs "right_schema")
// Schema names are different ("reference_schema" vs "target_schema")
Difference diff = it.next();
assertEquals(Where.IN_BOTH_BUT_DIFFERENCE, diff.getWhere());
assertEquals("left_schema.name", diff.getLeft().getPath());
assertEquals("right_schema.name", diff.getRight().getPath());
assertSame(left, diff.getLeft().getDbObject());
assertSame(right, diff.getRight().getDbObject());
assertEquals("reference_schema.name", diff.getLeft().getPath());
assertEquals("target_schema.name", diff.getRight().getPath());
assertSame(reference, diff.getLeft().getDbObject());
assertSame(target, diff.getRight().getDbObject());
assertEquals("name", diff.getLeft().getPropertyName());
assertEquals("left_schema", diff.getLeft().getPropertyValue());
assertEquals("reference_schema", diff.getLeft().getPropertyValue());
assertEquals("name", diff.getRight().getPropertyName());
assertEquals("right_schema", diff.getRight().getPropertyValue());
assertEquals("target_schema", diff.getRight().getPropertyValue());
// Table table_in_left only appears in the left schema
// Table table_in_reference only appears in the reference schema
diff = it.next();
assertEquals(Where.ONLY_IN_LEFT, diff.getWhere());
assertEquals("left_schema.table_in_left", diff.getLeft().getPath());
assertEquals("reference_schema.table_in_reference", diff.getLeft().getPath());
assertEquals(null, diff.getRight());
assertEquals(null, diff.getLeft().getPropertyName());
assertEquals(null, diff.getLeft().getPropertyValue());
// Table tbl_has_diff_pk has PK of "id" in left and "nodeRef" in right
// Table tbl_has_diff_pk has PK of "id" in reference and "nodeRef" in target
diff = it.next();
assertEquals(Where.ONLY_IN_LEFT, diff.getWhere());
assertEquals("left_schema.tbl_has_diff_pk.pk_is_diff.columnNames[0]", diff.getLeft().getPath());
assertEquals("right_schema.tbl_has_diff_pk.pk_is_diff.columnNames", diff.getRight().getPath());
assertEquals("reference_schema.tbl_has_diff_pk.pk_is_diff.columnNames[0]", diff.getLeft().getPath());
assertEquals("target_schema.tbl_has_diff_pk.pk_is_diff.columnNames", diff.getRight().getPath());
assertEquals("columnNames[0]", diff.getLeft().getPropertyName());
assertEquals("id", diff.getLeft().getPropertyValue());
assertEquals("columnNames", diff.getRight().getPropertyName());
assertEquals(Arrays.asList("nodeRef"), diff.getRight().getPropertyValue());
// Table tbl_has_diff_pk has PK of "id" in left and "nodeRef" in right
// Table tbl_has_diff_pk has PK of "id" in reference and "nodeRef" in target
diff = it.next();
assertEquals(Where.ONLY_IN_RIGHT, diff.getWhere());
assertEquals("left_schema.tbl_has_diff_pk.pk_is_diff.columnNames", diff.getLeft().getPath());
assertEquals("right_schema.tbl_has_diff_pk.pk_is_diff.columnNames[0]", diff.getRight().getPath());
assertEquals("reference_schema.tbl_has_diff_pk.pk_is_diff.columnNames", diff.getLeft().getPath());
assertEquals("target_schema.tbl_has_diff_pk.pk_is_diff.columnNames[0]", diff.getRight().getPath());
assertEquals("columnNames", diff.getLeft().getPropertyName());
assertEquals(Arrays.asList("id"), diff.getLeft().getPropertyValue());
assertEquals("columnNames[0]", diff.getRight().getPropertyName());
assertEquals("nodeRef", diff.getRight().getPropertyValue());
// idx_two is unique in the righ_schema but not in the left
// idx_two is unique in the righ_schema but not in the reference
diff = it.next();
assertEquals("left_schema.tbl_has_diff_pk.idx_two.unique", diff.getLeft().getPath());
assertEquals("right_schema.tbl_has_diff_pk.idx_two.unique", diff.getRight().getPath());
assertEquals("reference_schema.tbl_has_diff_pk.idx_two.unique", diff.getLeft().getPath());
assertEquals("target_schema.tbl_has_diff_pk.idx_two.unique", diff.getRight().getPath());
assertEquals("unique", diff.getLeft().getPropertyName());
assertEquals(false, diff.getLeft().getPropertyValue());
assertEquals("unique", diff.getRight().getPropertyName());
assertEquals(true, diff.getRight().getPropertyValue());
// Table table_in_right does not exist in the left schema
// Table table_in_target does not exist in the reference schema
diff = it.next();
assertEquals(Where.ONLY_IN_RIGHT, diff.getWhere());
assertEquals("right_schema.table_in_right", diff.getRight().getPath());
assertEquals("target_schema.table_in_target", diff.getRight().getPath());
assertEquals(null, diff.getLeft());
assertEquals(null, diff.getRight().getPropertyName());
assertEquals(null, diff.getRight().getPropertyValue());

View File

@ -18,14 +18,10 @@
*/
package org.alfresco.util.schemacomp;
import java.util.ArrayList;
import java.util.List;
import org.alfresco.util.schemacomp.model.DbObject;
import org.alfresco.util.schemacomp.model.Index;
import org.alfresco.util.schemacomp.validator.DbValidator;
import org.alfresco.util.schemacomp.validator.NameValidator;
import org.alfresco.util.schemacomp.validator.NullValidator;
/**
* Invokes the correct validator for a given DbObject.
@ -35,39 +31,38 @@ import org.alfresco.util.schemacomp.validator.NullValidator;
public class ValidatingVisitor implements DbObjectVisitor
{
private DiffContext ctx;
protected NameValidator indexNameValidator = new NameValidator();
protected NullValidator defaultValidator = new NullValidator();
protected ComparisonUtils comparisonUtils = new DefaultComparisonUtils();
private ComparisonUtils comparisonUtils = new DefaultComparisonUtils();
public ValidatingVisitor(DiffContext ctx)
{
this.ctx = ctx;
}
protected DbValidator getValidatorFor(Class<? extends DbObject> c)
{
if (c.equals(Index.class))
{
return indexNameValidator;
}
else
{
return defaultValidator;
}
}
@Override
public void visit(DbObject referenceObj)
{
DbValidator validator = getValidatorFor(referenceObj.getClass());
List<DbObject> matches = comparisonUtils.findEquivalentObjects(ctx.getTargetSchema(), referenceObj);
// TODO: if matches.size() > 1 then warn of possible redundant database objects
for (DbObject target : matches)
// Validate each matching target object against the reference object
// using each of the available validators for that reference object.
for (DbValidator validator : referenceObj.getValidators())
{
validator.validate(referenceObj, target, ctx);
for (DbObject target : matches)
{
validator.validate(referenceObj, target, ctx);
}
}
}
/**
* @param comparisonUtils the comparisonUtils to set
*/
public void setComparisonUtils(ComparisonUtils comparisonUtils)
{
this.comparisonUtils = comparisonUtils;
}
}

View File

@ -19,21 +19,15 @@
package org.alfresco.util.schemacomp;
import static org.junit.Assert.assertSame;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.alfresco.util.schemacomp.model.Column;
import org.alfresco.util.schemacomp.model.DbObject;
import org.alfresco.util.schemacomp.model.ForeignKey;
import org.alfresco.util.schemacomp.model.Index;
import org.alfresco.util.schemacomp.model.PrimaryKey;
import org.alfresco.util.schemacomp.model.Schema;
import org.alfresco.util.schemacomp.model.Sequence;
import org.alfresco.util.schemacomp.model.Table;
import org.alfresco.util.schemacomp.validator.DbValidator;
import org.alfresco.util.schemacomp.validator.NameValidator;
import org.hibernate.dialect.MySQL5InnoDBDialect;
import org.junit.Before;
import org.junit.Test;
@ -48,47 +42,59 @@ public class ValidatingVisitorTest
{
private DiffContext ctx;
private ValidatingVisitor visitor;
private Table table;
private Table refTable;
private Table targetTable;
private Schema refSchema;
private Schema targetSchema;
private Index index;
private Index refIndex;
private Index targetIndex1;
private Index targetIndex2;
private Index targetIndex3;
private List<DbValidator> validators;
private ComparisonUtils comparisonUtils;
@Before
public void setUp() throws Exception
{
index = new Index(table, "index_name", new ArrayList<String>());
refTable = new Table("reference_table");
refIndex = new Index(refTable, "index_name", Arrays.asList("a", "b", "c"));
ctx = new DiffContext(new MySQL5InnoDBDialect(), new Results(),
new ArrayList<ValidationResult>(), refSchema, targetSchema);
visitor = new ValidatingVisitor(ctx);
}
validators = new ArrayList<DbValidator>();
validators.add(Mockito.mock(DbValidator.class));
validators.add(Mockito.mock(DbValidator.class));
refIndex.setValidators(validators);
@Test
public void canGetCorrectValidator()
{
// Get references to the validator instances to test for
DbValidator nullValidator = visitor.defaultValidator;
DbValidator nameValidator = visitor.indexNameValidator;
assertSame(nullValidator, visitor.getValidatorFor(Column.class));
assertSame(nullValidator, visitor.getValidatorFor(ForeignKey.class));
assertSame(nameValidator, visitor.getValidatorFor(Index.class));
assertSame(nullValidator, visitor.getValidatorFor(PrimaryKey.class));
assertSame(nullValidator, visitor.getValidatorFor(Schema.class));
assertSame(nullValidator, visitor.getValidatorFor(Sequence.class));
assertSame(nullValidator, visitor.getValidatorFor(Table.class));
targetTable = new Table("target_table");
targetIndex1 = new Index(targetTable, "index_name", Arrays.asList("a", "b", "c"));
targetIndex2 = new Index(targetTable, "another_index", Arrays.asList("a", "b", "c"));
targetIndex3 = new Index(targetTable, "index_name", Arrays.asList("e", "f"));
comparisonUtils = Mockito.mock(ComparisonUtils.class);
visitor.setComparisonUtils(comparisonUtils);
}
@Test
public void canValidate()
{
visitor.indexNameValidator = Mockito.mock(NameValidator.class);
visitor.comparisonUtils = Mockito.mock(ComparisonUtils.class);
Mockito.when(visitor.comparisonUtils.findEquivalentObjects(refSchema, index)).
thenReturn(Arrays.asList((DbObject)index));
Mockito.when(comparisonUtils.findEquivalentObjects(refSchema, refIndex)).
thenReturn(Arrays.asList((DbObject) targetIndex1, targetIndex2, targetIndex3));
// Validate all instances of the target schema's indexes that are equivalent to this index
visitor.visit(index);
visitor.visit(refIndex);
Mockito.verify(visitor.indexNameValidator).validate(index, index, ctx);
Mockito.verify(validators.get(0)).validate(refIndex, targetIndex1, ctx);
Mockito.verify(validators.get(0)).validate(refIndex, targetIndex2, ctx);
Mockito.verify(validators.get(0)).validate(refIndex, targetIndex3, ctx);
Mockito.verify(validators.get(1)).validate(refIndex, targetIndex1, ctx);
Mockito.verify(validators.get(1)).validate(refIndex, targetIndex2, ctx);
Mockito.verify(validators.get(1)).validate(refIndex, targetIndex3, ctx);
}
}

View File

@ -20,8 +20,6 @@ package org.alfresco.util.schemacomp;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import javax.xml.parsers.SAXParser;
@ -107,6 +105,7 @@ public class XMLToSchema extends DefaultHandler
{
Column column = (Column) stack.pop();
Table table = (Table) stack.peek();
column.setParent(table);
table.getColumns().add(column);
}
else if (qName.equals(XML.EL_PRIMARY_KEY))
@ -119,34 +118,31 @@ public class XMLToSchema extends DefaultHandler
{
ForeignKey fk = (ForeignKey) stack.pop();
Table table = (Table) stack.peek();
fk.setParent(table);
table.getForeignKeys().add(fk);
}
else if (qName.equals(XML.EL_INDEX))
{
Index index = (Index) stack.pop();
Table table = (Table) stack.peek();
index.setParent(table);
table.getIndexes().add(index);
}
else if (qName.equals(XML.EL_SEQUENCE))
{
Sequence seq = (Sequence) stack.pop();
seq.setParent(schema);
schema.add(seq);
}
else if (qName.equals(XML.EL_VALIDATOR))
{
@SuppressWarnings("unchecked")
DbValidator<? extends DbObject> validator = (DbValidator<? extends DbObject>) stack.pop();
DbValidator validator = (DbValidator) stack.pop();
DbObject dbo = (DbObject) stack.peek();
dbo.getValidators().add(validator);
}
else if (qName.equals(XML.EL_PROPERTY))
{
//stack.pop();
}
}
@SuppressWarnings("unchecked")
@Override
public void startElement(String uri, String localName, String qName, Attributes atts)
throws SAXException
@ -187,10 +183,10 @@ public class XMLToSchema extends DefaultHandler
else if (qName.equals(XML.EL_VALIDATOR))
{
String className = atts.getValue(XML.ATTR_CLASS);
DbValidator<? extends DbObject> validator = null;
DbValidator validator = null;
try
{
validator = (DbValidator<? extends DbObject>) Class.forName(className).newInstance();
validator = (DbValidator) Class.forName(className).newInstance();
}
catch (Throwable e)
{
@ -258,7 +254,7 @@ public class XMLToSchema extends DefaultHandler
if (stack.peek() instanceof DbValidator)
{
@SuppressWarnings("unchecked")
DbValidator<? extends DbObject> validator = (DbValidator<? extends DbObject>) stack.peek();
DbValidator validator = (DbValidator) stack.peek();
validator.setProperty(propName, propValue);
}
}

View File

@ -22,6 +22,8 @@ package org.alfresco.util.schemacomp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertNull;
import java.io.BufferedInputStream;
import java.io.InputStream;
@ -61,31 +63,38 @@ public class XMLToSchemaTest
Schema schema = xmlToSchema.getSchema();
assertNotNull("A null Schema object was returned", schema);
assertNull("Schema isn't meant to have a parent", schema.getParent());
assertEquals("alfresco", schema.getName());
Iterator<DbObject> objects = schema.iterator();
Table table = (Table) objects.next();
assertSame("Wrong or no parent set on table", schema, table.getParent());
assertEquals("node", table.getName());
assertEquals(3, table.getColumns().size());
assertSame("Wrong or no parent set", table, table.getColumns().get(0).getParent());
assertEquals("id", table.getColumns().get(0).getName());
assertEquals("NUMBER(10)", table.getColumns().get(0).getType());
assertEquals(false, table.getColumns().get(0).isNullable());
assertSame("Wrong or no parent set", table, table.getColumns().get(1).getParent());
assertEquals("nodeRef", table.getColumns().get(1).getName());
assertEquals("VARCHAR2(200)", table.getColumns().get(1).getType());
assertEquals(false, table.getColumns().get(1).isNullable());
assertSame("Wrong or no parent set", table, table.getColumns().get(2).getParent());
assertEquals("name", table.getColumns().get(2).getName());
assertEquals("VARCHAR2(150)", table.getColumns().get(2).getType());
assertEquals(true, table.getColumns().get(2).isNullable());
assertSame("Wrong or no parent set", table, table.getPrimaryKey().getParent());
assertEquals("pk_node", table.getPrimaryKey().getName());
assertEquals(1, table.getPrimaryKey().getColumnNames().size());
assertEquals("id", table.getPrimaryKey().getColumnNames().get(0));
assertEquals(1, table.getForeignKeys().size());
assertSame("Wrong or no parent set", table, table.getForeignKeys().get(0).getParent());
assertEquals("fk_node_noderef", table.getForeignKeys().get(0).getName());
assertEquals("nodeRef", table.getForeignKeys().get(0).getLocalColumn());
assertEquals("node", table.getForeignKeys().get(0).getTargetTable());
@ -93,20 +102,29 @@ public class XMLToSchemaTest
assertEquals(1, table.getIndexes().size());
Index index = table.getIndexes().get(0);
assertSame("Wrong or no parent set on index", table, index.getParent());
assertEquals("idx_node_by_id", index.getName());
assertEquals(true, index.isUnique());
assertEquals(2, index.getColumnNames().size());
assertEquals("id", index.getColumnNames().get(0));
assertEquals("nodeRef", index.getColumnNames().get(1));
assertEquals(1, index.getValidators().size());
DbValidator<? extends DbObject> validator = index.getValidators().get(0);
DbValidator validator = index.getValidators().get(0);
assertEquals(NameValidator.class, validator.getClass());
assertEquals(1, validator.getPropertyNames().size());
assertEquals("idx_.+", validator.getProperty("pattern"));
assertEquals("node_seq", ((Sequence) objects.next()).getName());
assertEquals("person_seq", ((Sequence) objects.next()).getName());
assertEquals("content_seq", ((Sequence) objects.next()).getName());
Sequence seq = (Sequence) objects.next();
assertSame("Wrong or no parent set", schema, seq.getParent());
assertEquals("node_seq", seq.getName());
seq = (Sequence) objects.next();
assertSame("Wrong or no parent set", schema, seq.getParent());
assertEquals("person_seq", seq.getName());
seq = (Sequence) objects.next();
assertSame("Wrong or no parent set", schema, seq.getParent());
assertEquals("content_seq", seq.getName());
assertFalse("Should be no more DB objects", objects.hasNext());
}

View File

@ -42,7 +42,7 @@ public abstract class AbstractDbObject implements DbObject
/** How differences in the name field should be reported */
private Strength nameStrength = Strength.ERROR;
protected ComparisonUtils comparisonUtils = new DefaultComparisonUtils();
private List<DbValidator<?>> validators = new ArrayList<DbValidator<?>>();
private List<DbValidator> validators = new ArrayList<DbValidator>();
/**
@ -212,7 +212,7 @@ public abstract class AbstractDbObject implements DbObject
@Override
public List<DbValidator<? extends DbObject>> getValidators()
public List<DbValidator> getValidators()
{
return validators;
}
@ -222,7 +222,7 @@ public abstract class AbstractDbObject implements DbObject
* @param validators the validators to set
*/
@Override
public void setValidators(List<DbValidator<? extends DbObject>> validators)
public void setValidators(List<DbValidator> validators)
{
if (validators == null)
{

View File

@ -113,11 +113,10 @@ public class AbstractDbObjectTest
}
@SuppressWarnings("unchecked")
@Test
public void canGetValidators()
{
List<DbValidator<? extends DbObject>> validators = dbObject.getValidators();
List<DbValidator> validators = dbObject.getValidators();
assertEquals(0, validators.size());
dbObject.setValidators(null);
@ -166,13 +165,13 @@ public class AbstractDbObjectTest
}
private List<DbValidator<? extends DbObject>> validatorList(DbValidator<? extends DbObject>... validators)
private List<DbValidator> validatorList(DbValidator... validators)
{
return Arrays.asList(validators);
}
private static class TestValidator extends AbstractDbValidator<DbObject>
private static class TestValidator extends AbstractDbValidator
{
@Override
public void validate(DbObject reference, DbObject target, DiffContext ctx)

View File

@ -18,13 +18,12 @@
*/
package org.alfresco.util.schemacomp.model;
import java.util.Collection;
import java.util.List;
import org.alfresco.util.schemacomp.DbObjectVisitor;
import org.alfresco.util.schemacomp.DiffContext;
import org.alfresco.util.schemacomp.Results;
import org.alfresco.util.schemacomp.Result.Strength;
import org.alfresco.util.schemacomp.Results;
import org.alfresco.util.schemacomp.validator.DbValidator;
/**
@ -98,12 +97,12 @@ public interface DbObject
* @see DbValidator
* @return DbValidator List
*/
List<DbValidator<? extends DbObject>> getValidators();
List<DbValidator> getValidators();
/**
* Set/override the validators associated with this database object.
*
* @param validators
*/
void setValidators(List<DbValidator<? extends DbObject>> validators);
void setValidators(List<DbValidator> validators);
}

View File

@ -25,6 +25,8 @@ import org.alfresco.util.schemacomp.DbObjectVisitor;
import org.alfresco.util.schemacomp.DbProperty;
import org.alfresco.util.schemacomp.DiffContext;
import org.alfresco.util.schemacomp.Result.Strength;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Represents an index on a table.
@ -35,6 +37,7 @@ public class Index extends AbstractDbObject
{
private final List<String> columnNames = new ArrayList<String>();
private boolean unique;
private static Log logger = LogFactory.getLog(Index.class);
public Index(String name)
{

View File

@ -22,14 +22,12 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.alfresco.util.schemacomp.model.DbObject;
/**
* Base class providing DbValidator support.
*
* @author Matt Ward
*/
public abstract class AbstractDbValidator<T extends DbObject> implements DbValidator<T>
public abstract class AbstractDbValidator implements DbValidator
{
private final Map<String, String> properties = new HashMap<String, String>();

View File

@ -29,9 +29,9 @@ import org.alfresco.util.schemacomp.model.DbObject;
*
* @author Matt Ward
*/
public interface DbValidator<T extends DbObject>
public interface DbValidator
{
void validate(T reference, T target, DiffContext ctx);
void validate(DbObject reference, DbObject target, DiffContext ctx);
void setProperty(String name, String value);

View File

@ -36,7 +36,7 @@ import org.hibernate.dialect.Dialect;
*
* @author Matt Ward
*/
public class NameValidator implements DbValidator<DbObject>
public class NameValidator implements DbValidator
{
private Pattern pattern;

View File

@ -22,10 +22,7 @@ package org.alfresco.util.schemacomp.validator;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.alfresco.util.schemacomp.DiffContext;

View File

@ -26,7 +26,7 @@ import org.alfresco.util.schemacomp.model.DbObject;
*
* @author Matt Ward
*/
public class NullValidator extends AbstractDbValidator<DbObject>
public class NullValidator extends AbstractDbValidator
{
@Override
public void validate(DbObject reference, DbObject target, DiffContext ctx)