[ACS-1455] Schema validation report expansion with patch specific problems told apart (#386)

* Update schema validation report

- Introduced changes to tell apart problems due to optional unapplied
  patches within schema validation report

* Update SchemaDifferenceHelper

- Now populating optionalUpgradePatches list during the schema bootstrap
  registration to conform with patch registration mechanising already in use
- Added tests

* Make minor corrections

Co-authored-by: Nana Insaidoo <nana.insaidoo@meterian.com>
This commit is contained in:
Nana Insaidoo
2021-04-27 09:57:47 +01:00
committed by GitHub
parent 6c9b05d289
commit 5ce179f209
15 changed files with 3482 additions and 19 deletions

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -40,7 +40,8 @@ public class SchemaUpgradeScriptPatch extends AbstractPatch
{
private static final String MSG_NOT_EXECUTED = "patch.schemaUpgradeScript.err.not_executed";
private String scriptUrl;
private String scriptUrl;
private String problemsPatternFileUrl;
public SchemaUpgradeScriptPatch()
{
@@ -52,8 +53,13 @@ public class SchemaUpgradeScriptPatch extends AbstractPatch
public String getScriptUrl()
{
return scriptUrl;
}
}
public String getProblemPatternsFileUrl()
{
return problemsPatternFileUrl;
}
/**
* Set the URL of the upgrade scriptUrl to execute. This is the full URL of the
* file, e.g. <b>classpath:alfresco/patch/scripts/upgrade-1.4/${hibernate.dialect.class}/patchAlfrescoSchemaUpdate-1.4-2.sql</b>
@@ -65,12 +71,25 @@ public class SchemaUpgradeScriptPatch extends AbstractPatch
public void setScriptUrl(String script)
{
this.scriptUrl = script;
}
}
/**
* Set the URL of the problems pattern file to accompany the upgrade script. This is the full URL of the
* file, e.g. <b>classpath:alfresco/patch/scripts/upgrade-1.4/${hibernate.dialect.class}/patchAlfrescoSchemaUpdate-1.4-2-problems.txt</b>
* where the <b>${hibernate.dialect.class}</b> placeholder will be substituted with the Hibernate
* <code>Dialect</code> as configured for the system.
*
* @param problemsFile the problems file
*/
public void setProblemsPatternFileUrl(String problemsFile)
{
this.problemsPatternFileUrl = problemsFile;
}
protected void checkProperties()
{
super.checkProperties();
checkPropertyNotNull(scriptUrl, "scriptUrl");
checkPropertyNotNull(scriptUrl, "scriptUrl");
}
/**
@@ -79,6 +98,6 @@ public class SchemaUpgradeScriptPatch extends AbstractPatch
@Override
protected String applyInternal() throws Exception
{
throw new PatchException(MSG_NOT_EXECUTED, scriptUrl);
}
throw new PatchException(MSG_NOT_EXECUTED, scriptUrl);
}
}

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -85,12 +85,14 @@ import org.alfresco.util.DialectUtil;
import org.alfresco.util.LogUtil;
import org.alfresco.util.PropertyCheck;
import org.alfresco.util.TempFileProvider;
import org.alfresco.util.schemacomp.Difference;
import org.alfresco.util.schemacomp.ExportDb;
import org.alfresco.util.schemacomp.MultiFileDumper;
import org.alfresco.util.schemacomp.MultiFileDumper.DbToXMLFactory;
import org.alfresco.util.schemacomp.Result;
import org.alfresco.util.schemacomp.Results;
import org.alfresco.util.schemacomp.SchemaComparator;
import org.alfresco.util.schemacomp.SchemaDifferenceHelper;
import org.alfresco.util.schemacomp.XMLToSchema;
import org.alfresco.util.schemacomp.model.Schema;
import org.apache.commons.logging.Log;
@@ -124,6 +126,7 @@ 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 MSG_OPTIONAL_PATCH_RUN_SUGGESTION = "system.schema_comp.patch_run_suggestion";
private static final String ERR_FORCED_STOP = "schema.update.err.forced_stop";
private static final String ERR_MULTIPLE_SCHEMAS = "schema.update.err.found_multiple";
private static final String ERR_PREVIOUS_FAILED_BOOTSTRAP = "schema.update.err.previous_failed";
@@ -153,7 +156,8 @@ public class SchemaBootstrap extends AbstractLifecycleBean
private static volatile int maxStringLength = DEFAULT_MAX_STRING_LENGTH;
private Dialect dialect;
private SchemaDifferenceHelper differenceHelper;
private ResourcePatternResolver rpr = new PathMatchingResourcePatternResolver(this.getClass().getClassLoader());
/**
@@ -233,6 +237,11 @@ public class SchemaBootstrap extends AbstractLifecycleBean
this.dialect = dialect;
}
public void setDifferenceHelper(SchemaDifferenceHelper differenceHelper)
{
this.differenceHelper = differenceHelper;
}
private static Log logger = LogFactory.getLog(SchemaBootstrap.class);
private DescriptorService descriptorService;
@@ -1815,7 +1824,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean
// Return number of problems found across all reference files.
return totalProblems;
}
private int validateSchema(Resource referenceResource, String outputFileNameTemplate, PrintWriter out)
{
try
@@ -1916,11 +1925,42 @@ public class SchemaBootstrap extends AbstractLifecycleBean
pw = out;
}
Map<String, List<String>> optionalPatchMessages = new HashMap<>();
// Populate the file with details of the comparison's results.
for (Result result : results)
{
pw.print(result.describe());
String optionalPatchId = findPatchCausingDifference(result, target);
String differenceMessage = result.describe();
if (optionalPatchId == null)
{
pw.print(differenceMessage);
pw.print(SchemaComparator.LINE_SEPARATOR);
}
else
{
if (optionalPatchMessages.containsKey(optionalPatchId))
{
optionalPatchMessages.get(optionalPatchId).add(differenceMessage);
}
else
{
List<String> newResults = new ArrayList<>();
newResults.add(differenceMessage);
optionalPatchMessages.put(optionalPatchId, newResults);
}
}
}
for (String optionalPatchId: optionalPatchMessages.keySet())
{
pw.print(SchemaComparator.LINE_SEPARATOR);
pw.print(I18NUtil.getMessage(MSG_OPTIONAL_PATCH_RUN_SUGGESTION, optionalPatchId));
pw.print(SchemaComparator.LINE_SEPARATOR);
for (String optionalPatchMessage: optionalPatchMessages.get(optionalPatchId))
{
pw.print(optionalPatchMessage);
pw.print(SchemaComparator.LINE_SEPARATOR);
}
}
}
finally
@@ -1946,7 +1986,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean
}
else
{
LogUtil.warn(logger, WARN_SCHEMA_COMP_PROBLEMS_FOUND, numProblems, outputFile);
LogUtil.warn(logger, WARN_SCHEMA_COMP_PROBLEMS_FOUND, numProblems, outputFile);
}
}
Date endTime = new Date();
@@ -1956,6 +1996,17 @@ public class SchemaBootstrap extends AbstractLifecycleBean
return results.size();
}
private String findPatchCausingDifference(Result result, Schema currentDb)
{
// In new installations of the system the schema validation is run twice. Since none of the alf_ tables is present there is no need to seek for unapplied patches.
if (!currentDb.containsByName("alf_applied_patch"))
{
return null;
}
return differenceHelper.findPatchCausingDifference((Difference)result);
}
/**
* Produces schema dump in XML format: this is performed pre- and post-upgrade (i.e. if
* changes are made to the schema) and can made upon demand via JMX.

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -29,7 +29,8 @@ import java.util.Collections;
import java.util.List;
import org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch;
import org.alfresco.util.PropertyCheck;
import org.alfresco.util.PropertyCheck;
import org.alfresco.util.schemacomp.SchemaDifferenceHelper;
/**
* Registers a list of create scripts.
@@ -44,7 +45,8 @@ public class SchemaBootstrapRegistration
private List<String> postCreateScriptUrls;
private List<SchemaUpgradeScriptPatch> preUpdateScriptPatches;
private List<SchemaUpgradeScriptPatch> postUpdateScriptPatches;
private List<SchemaUpgradeScriptPatch> updateActivitiScriptPatches;
private List<SchemaUpgradeScriptPatch> updateActivitiScriptPatches;
private SchemaDifferenceHelper differenceHelper;
public SchemaBootstrapRegistration()
{
@@ -61,6 +63,14 @@ public class SchemaBootstrapRegistration
public void setSchemaBootstrap(SchemaBootstrap schemaBootstrap)
{
this.schemaBootstrap = schemaBootstrap;
}
/**
* @param differenceHelper the component with which to register upgrade script pacthes
*/
public void setDifferenceHelper(SchemaDifferenceHelper differenceHelper)
{
this.differenceHelper = differenceHelper;
}
/**
@@ -139,7 +149,8 @@ public class SchemaBootstrapRegistration
}
for (SchemaUpgradeScriptPatch postUpdateScriptPatch : postUpdateScriptPatches)
{
schemaBootstrap.addPostUpdateScriptPatch(postUpdateScriptPatch);
schemaBootstrap.addPostUpdateScriptPatch(postUpdateScriptPatch);
differenceHelper.addUpgradeScriptPatch(postUpdateScriptPatch);
}
for (SchemaUpgradeScriptPatch updateActivitiScriptPatch : updateActivitiScriptPatches)
{

View File

@@ -0,0 +1,170 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.util.schemacomp;
import static java.util.Locale.ENGLISH;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.alfresco.repo.admin.patch.PatchService;
import org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch;
import org.alfresco.repo.domain.dialect.Dialect;
import org.alfresco.util.DialectUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.extensions.surf.util.I18NUtil;
public class SchemaDifferenceHelper
{
private static Log logger = LogFactory.getLog(SchemaDifferenceHelper.class);
private Dialect dialect;
private PatchService patchService;
private List<SchemaUpgradeScriptPatch> optionalUpgradePatches;
private ResourcePatternResolver rpr = new PathMatchingResourcePatternResolver(this.getClass().getClassLoader());
public SchemaDifferenceHelper(Dialect dialect, PatchService patchService)
{
this.dialect = dialect;
this.patchService = patchService;
this.optionalUpgradePatches = new ArrayList<SchemaUpgradeScriptPatch>(4);
}
public SchemaDifferenceHelper(Dialect dialect, PatchService patchService,
List<SchemaUpgradeScriptPatch> upgradePatches)
{
this.dialect = dialect;
this.patchService = patchService;
this.optionalUpgradePatches = upgradePatches;
}
public void addUpgradeScriptPatch(SchemaUpgradeScriptPatch patch)
{
if (patch.isIgnored())
{
this.optionalUpgradePatches.add(patch);
}
}
public String findPatchCausingDifference(Difference difference)
{
for (SchemaUpgradeScriptPatch patch: optionalUpgradePatches)
{
if (!isPatchApplied(patch))
{
List<String> problemPatterns = getProblemsPatterns(patch);
for (String problemPattern: problemPatterns)
{
if (describe(difference).matches(problemPattern))
{
return patch.getId();
}
}
}
}
return null;
}
private boolean isPatchApplied(SchemaUpgradeScriptPatch patch)
{
return patchService.getPatch(patch.getId()) != null;
}
protected Resource getDialectResource(String resourceUrl)
{
if(resourceUrl == null)
{
return null;
}
return DialectUtil.getDialectResource(rpr, dialect.getClass(), resourceUrl);
}
private List<String> getProblemsPatterns(SchemaUpgradeScriptPatch patch)
{
List<String> optionalProblems = new ArrayList<>();
String problemFileUrl = patch.getProblemPatternsFileUrl();
Resource problemFile = getDialectResource(problemFileUrl);
if (problemFile != null)
{
try (BufferedReader reader = new BufferedReader(new InputStreamReader(problemFile.getInputStream(), StandardCharsets.UTF_8)))
{
String line = reader.readLine();
while (line != null)
{
optionalProblems.add(line);
line = reader.readLine();
}
}
catch (Exception ex)
{
logger.error("Error while parsing problems patterns for patch " + patch.getId() + ex);
}
}
return optionalProblems;
}
protected String describe(Difference difference)
{
if (difference.getLeft() == null)
{
return I18NUtil.getMessage(
"system.schema_comp.diff.target_only",
ENGLISH,
difference.getRight().getDbObject().getTypeName(),
difference.getRight().getPath(),
difference.getRight().getPropertyValue());
}
if (difference.getRight() == null)
{
return I18NUtil.getMessage(
"system.schema_comp.diff.ref_only",
ENGLISH,
difference.getLeft().getDbObject().getTypeName(),
difference.getLeft().getPath(),
difference.getLeft().getPropertyValue());
}
return I18NUtil.getMessage(
"system.schema_comp.diff",
ENGLISH,
difference.getLeft().getDbObject().getTypeName(),
difference.getLeft().getPath(),
difference.getLeft().getPropertyValue(),
difference.getRight().getPath(),
difference.getRight().getPropertyValue());
}
}

View File

@@ -96,6 +96,14 @@
<value>classpath:alfresco/dbscripts/create/${db.script.dialect}/Schema-Reference-ACT.xml</value>
</list>
</property>
<property name="differenceHelper">
<ref bean="differenceHelper" />
</property>
</bean>
<bean id="differenceHelper" class="org.alfresco.util.schemacomp.SchemaDifferenceHelper">
<constructor-arg ref="dialect"/>
<constructor-arg ref="patchComponent"/>
</bean>
<bean id="encryptionChecker" class="org.alfresco.encryption.EncryptionChecker">

View File

@@ -29,6 +29,7 @@
<bean id="schema.upgrade.core" class="org.alfresco.repo.domain.schema.SchemaBootstrapRegistration" init-method="register">
<property name="schemaBootstrap" ref="schemaBootstrap" />
<property name="differenceHelper" ref="differenceHelper" />
<property name="preUpdateScriptPatches">
<list>
<ref bean="patch.db-V4.1-update-activiti-nullable-columns" />

View File

@@ -37,6 +37,8 @@ system.schema_comp.name_validator=name must match pattern ''{0}''
system.schema_comp.index_columns_validator=Number of columns in index doesn''t match. Was {0}, but expected {1}
system.schema_comp.column_names_validator=Column types do not match. Was {0}, but expected {1}
system.schema_comp.schema_version_validator=version must be at least ''{0}''
# Optional long running patch messages...
system.schema_comp.patch_run_suggestion=The following problems will be resolved once the long running patch {0} has been run
# Clustering
system.cluster.license.not_enabled=License does not permit clustering: clustering is disabled.

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2020 Alfresco Software Limited
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -233,7 +233,9 @@ import org.junit.runners.Suite;
org.alfresco.repo.rendition2.TransformationOptionsConverterTest.class,
org.alfresco.transform.client.registry.TransformServiceRegistryConfigTest.class,
org.alfresco.repo.event2.RepoEvent2UnitSuite.class
org.alfresco.repo.event2.RepoEvent2UnitSuite.class,
org.alfresco.util.schemacomp.SchemaDifferenceHelperUnitTest.class
})
public class AllUnitTestsSuite
{

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -85,6 +85,7 @@ import org.junit.runners.Suite;
org.alfresco.util.schemacomp.DbToXMLTest.class,
org.alfresco.util.schemacomp.ExportDbTest.class,
org.alfresco.util.schemacomp.SchemaReferenceFileTest.class,
org.alfresco.util.schemacomp.SchemaBootstrapTest.class,
org.alfresco.repo.module.ModuleComponentHelperTest.class,
org.alfresco.repo.node.getchildren.GetChildrenCannedQueryTest.class,

View File

@@ -0,0 +1,86 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.util.schemacomp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch;
import org.alfresco.repo.domain.schema.SchemaBootstrap;
import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.test.junitrules.ApplicationContextInit;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.RuleChain;
@Category({OwnJVMTestsCategory.class})
public class SchemaBootstrapTest
{
private static final String BOOTSTRAP_TEST_CONTEXT = "classpath*:alfresco/dbscripts/test-bootstrap-context.xml";
private static final List<String> TEST_SCHEMA_REFERENCE_URLS = Arrays.asList(
"classpath:alfresco/dbscripts/create/${db.script.dialect}/Test-Schema-Reference-ALF.xml",
"classpath:alfresco/dbscripts/create/${db.script.dialect}/Schema-Reference-ACT.xml");
private static ApplicationContextInit APP_CONTEXT_INIT = ApplicationContextInit.createStandardContextWithOverrides(BOOTSTRAP_TEST_CONTEXT);
@ClassRule
public static RuleChain staticRuleChain = RuleChain.outerRule(APP_CONTEXT_INIT);
private SchemaBootstrap schemaBootstrap;
private SchemaUpgradeScriptPatch optionalPatch;
@Before
public void setUp() throws Exception
{
schemaBootstrap = (SchemaBootstrap) APP_CONTEXT_INIT.getApplicationContext().getBean("schemaBootstrap");
schemaBootstrap.setSchemaReferenceUrls(TEST_SCHEMA_REFERENCE_URLS);
optionalPatch = (SchemaUpgradeScriptPatch) APP_CONTEXT_INIT.getApplicationContext().getBean("patchDbVOAddIndexTest");
}
@Test
public void shouldSchemaValidationReportProblemsCausedByUnappliedOptionalPatch()
{
ByteArrayOutputStream buff = new ByteArrayOutputStream();
PrintWriter out = new PrintWriter(buff);
int numProblems = schemaBootstrap.validateSchema(null, out);
out.flush();
assertEquals(1, numProblems);
String problems = buff.toString();
assertTrue("Missing optional patch-specific problems report: \n" + problems,
problems.contains("The following problems will be resolved once the long running patch "
+ optionalPatch.getId() + " has been run"));
}
}

View File

@@ -0,0 +1,207 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.util.schemacomp;
import static org.alfresco.util.schemacomp.Difference.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import org.alfresco.repo.admin.patch.AppliedPatch;
import org.alfresco.repo.admin.patch.PatchService;
import org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch;
import org.alfresco.repo.domain.dialect.Dialect;
import org.alfresco.util.schemacomp.model.Index;
import org.alfresco.util.schemacomp.model.Schema;
import org.alfresco.util.schemacomp.model.Table;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
public class SchemaDifferenceHelperUnitTest
{
private static final String TEST_PATCH_ID = "patch.db-V1.0-test";
private static final String BASE_PROBLEM_PATTERN = ".*missing %s.*%s";
private SchemaDifferenceHelper differenceHelper;
private PatchService patchService;
private Dialect dialect;
@Rule
public TemporaryFolder testFolder = new TemporaryFolder();
@Before
public void setup()
{
dialect = mock(Dialect.class);
patchService = mock(PatchService.class);
}
@Test
public void shouldNotFindPatchWhenThereAreNoUpgradePatches()
{
Difference diff = createDifference();
differenceHelper = createHelper(Arrays.asList());
assertNull(differenceHelper.findPatchCausingDifference(diff));
}
@Test
public void shouldNotFindPatchWhenUpgradePatchHasBeenApplied() throws IOException
{
Difference diff = createDifference();
SchemaUpgradeScriptPatch upgradeScript = createUpgradeScript(TEST_PATCH_ID);
when(patchService.getPatch(TEST_PATCH_ID)).thenReturn(new AppliedPatch());
differenceHelper = createHelper(Arrays.asList(upgradeScript));
String result = differenceHelper.findPatchCausingDifference(diff);
assertNull(result);
}
@Test
public void shouldNotFindPatchWhenUpgradePatchDoesNotProvideAnyProblemPatternsFile() throws IOException
{
Difference diff = createDifference();
SchemaUpgradeScriptPatch upgradeScript = createUpgradeScript(TEST_PATCH_ID);
upgradeScript.setProblemsPatternFileUrl(null);
when(patchService.getPatch(TEST_PATCH_ID)).thenReturn(null);
differenceHelper = createHelper(Arrays.asList(upgradeScript));
String result = differenceHelper.findPatchCausingDifference(diff);
assertNull(result);
}
@Test
public void shouldFindPatchWhenDifferenceCausedByUpgradePatchIsDetected() throws IOException
{
Index index = createTableIndex("alf_node");
Difference diff = new Difference(Where.ONLY_IN_REFERENCE, new DbProperty(index), null);
SchemaUpgradeScriptPatch upgradeScript = createUpgradeScript(TEST_PATCH_ID,
String.format(BASE_PROBLEM_PATTERN, index.getTypeName(), "idx_alf_node_test"));
when(patchService.getPatch(TEST_PATCH_ID)).thenReturn(null);
differenceHelper = createHelper(Arrays.asList(upgradeScript));
String result = differenceHelper.findPatchCausingDifference(diff);
assertEquals(TEST_PATCH_ID, result);
}
private Difference createDifference()
{
Difference difference = new Difference(Where.IN_BOTH_BUT_DIFFERENCE, mock(DbProperty.class), mock(DbProperty.class));
return difference;
}
private Index createTableIndex(String tableName)
{
Table table = new Table(tableName);
table.setParent(new Schema(""));
return new Index(table, "idx_alf_node_test", Arrays.asList("col_a", "col_b"));
}
private SchemaUpgradeScriptPatch createUpgradeScript(String id, String problemPattern) throws IOException
{
SchemaUpgradeScriptPatch upgradeScript = new SchemaUpgradeScriptPatch();
upgradeScript.setId(id);
Path file = createTempFile(problemPattern);
upgradeScript.setProblemsPatternFileUrl(file.toAbsolutePath().toString());
return upgradeScript;
}
private SchemaUpgradeScriptPatch createUpgradeScript(String id) throws IOException
{
return createUpgradeScript(id, "");
}
private Path createTempFile() throws IOException
{
return Files.createTempFile(testFolder.getRoot().toPath(), null, "txt");
}
private Path createTempFile(String content) throws IOException
{
Path tempFile = createTempFile();
Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8));
return tempFile;
}
private SchemaDifferenceHelper createHelper(List<SchemaUpgradeScriptPatch> upgradePatches)
{
return new SchemaDifferenceHelper(dialect, patchService, upgradePatches) {
@Override
protected String describe(Difference difference)
{
if (difference.getLeft() == null)
{
return String.format("Difference: unexpected %s found in database with path: %s",
difference.getRight().getDbObject().getTypeName(),
difference.getRight().getPath());
}
if(difference.getRight() == null)
{
return String.format("Difference: missing %s from database, expected at path: %s",
difference.getLeft().getDbObject().getTypeName(),
difference.getLeft().getPath());
}
return String.format("Difference: expected %s %s=\"%s\", but was %s=\"%s\"",
difference.getLeft().getDbObject().getTypeName(),
difference.getLeft().getPath(),
difference.getLeft().getPropertyValue(),
difference.getRight().getPath(),
difference.getRight().getPropertyValue());
}
@Override
protected Resource getDialectResource(String resourceUrl)
{
try
{
return new InputStreamResource(new FileInputStream(resourceUrl));
}
catch (Exception e)
{
return null;
}
}
};
}
}

View File

@@ -0,0 +1,60 @@
<?xml version='1.0' encoding='UTF-8'?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.5.xsd">
<bean id="schema.upgrade.core" class="org.alfresco.repo.domain.schema.SchemaBootstrapRegistration" init-method="register">
<property name="schemaBootstrap" ref="schemaBootstrap" />
<property name="differenceHelper" ref="differenceHelper" />
<property name="preUpdateScriptPatches">
<list>
<ref bean="patch.db-V4.1-update-activiti-nullable-columns" />
</list>
</property>
<property name="postUpdateScriptPatches">
<list>
<ref bean="patch.db-V4.0-SolrTracking" />
<ref bean="patch.db-V4.0-AclChangeSet2" />
<ref bean="patch.db-V4.0-TenantTables" />
<ref bean="patch.db-V4.1-NodeDeleted" />
<ref bean="patch.db-V4.1-drop-alfqname-fk-indexes" />
<ref bean="patch.db-V4.2-remove-index-acl_id" />
<ref bean="patch.db-V4.1-drop-activiti-feed-format" />
<ref bean="patch.db-V4.2-metadata-query-indexes" />
<ref bean="patch.db-V4.1-fix-Repo-seqs-order" />
<ref bean="patch.db-V4.1-ChildAssoc-OrderBy" />
<ref bean="patch.db-V4.1-createIdxAlfNodeTQN" />
<ref bean="patch.db-V4.2-restructure-idx_alf_nprop_s-MSSQL" />
<ref bean="patch.db-V4.2-migrate-locale-multilingual" />
<ref bean="patch.db-V4.1-AuthorizationTables" />
<ref bean="patch.db-V5.0-ContentUrlEncryptionTables" />
<ref bean="patch.db-V5.1-metadata-query-indexes" />
<ref bean="patch.db-V5.2-remove-jbpm-tables-from-db" />
<ref bean="patch.db-V6.0-change-set-indexes" />
<ref bean="patch.db-V6.3-add-indexes-node-transaction" />
<ref bean="patch.db-V0-add-index-test" />
</list>
</property>
</bean>
<alias name="patch.db-V0-add-index-test" alias="patchDbVOAddIndexTest" />
<bean id="patch.db-V0-add-index-test" class="org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch" parent="basePatch">
<property name="id"><value>patch.db-V0-add-index-test</value></property>
<property name="description"><value>patch.db-V0-add-index-test.description</value></property>
<property name="fixesFromSchema"><value>0</value></property>
<property name="fixesToSchema"><value>15000</value></property>
<property name="targetSchema"><value>15001</value></property>
<property name="ignored"><value>${system.new-node-transaction-indexes.ignored}</value></property>
<property name="scriptUrl">
<value>classpath:alfresco/dbscripts/upgrade/0/${db.script.dialect}/add-index-test.sql</value>
</property>
<property name="problemsPatternFileUrl">
<value>classpath:alfresco/dbscripts/upgrade/0/${db.script.dialect}/add-index-test-problem-patterns.txt</value>
</property>
</bean>
</beans>

View File

@@ -0,0 +1,15 @@
CREATE INDEX idx_alf_node_test ON alf_node (acl_id, audit_creator); --(optional)
--
-- Record script finish
--
DELETE FROM alf_applied_patch WHERE id = 'patch.db-V0-add-index-test';
INSERT INTO alf_applied_patch
(id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report)
VALUES
(
'patch.db-V0-add-index-test', 'Manually executed script upgrade V0: Added new index test',
0, 15000, -1, 15001, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed'
);