diff --git a/config/alfresco/policy-context.xml b/config/alfresco/policy-context.xml index aa742b787e..7997d3b7bb 100644 --- a/config/alfresco/policy-context.xml +++ b/config/alfresco/policy-context.xml @@ -64,7 +64,8 @@ - + + ${spaces.store} diff --git a/source/java/org/alfresco/repo/admin/RepoAdminServiceImplTest.java b/source/java/org/alfresco/repo/admin/RepoAdminServiceImplTest.java new file mode 100644 index 0000000000..1758285dcf --- /dev/null +++ b/source/java/org/alfresco/repo/admin/RepoAdminServiceImplTest.java @@ -0,0 +1,430 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.admin; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; + +import junit.framework.TestCase; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.cmr.admin.RepoAdminService; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationContext; + +/** + * @see RepoAdminServiceImpl + * + * @author janv + */ +public class RepoAdminServiceImplTest extends TestCase +{ + private static Log logger = LogFactory.getLog(RepoAdminServiceImplTest.class); + + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private RepoAdminService repoAdminService; + private DictionaryService dictionaryService; + private TransactionService transactionService; + + final String modelPrefix = "model-"; + final static String MKR = "{MKR}"; + + public static final String MODEL_MKR_XML = + "" + + + " Test model "+MKR+"" + + " Alfresco" + + " 2005-05-30" + + " 1.0" + + + " " + + " " + + " " + + " " + + + " " + + " " + + " " + + + " " + + + " " + + " Base" + + " The Base Type" + + " cm:content" + + " " + + " " + + " d:text" + + " " + + " " + + " " + + + " " + + + ""; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + repoAdminService = (RepoAdminService) ctx.getBean("RepoAdminService"); + dictionaryService = (DictionaryService) ctx.getBean("DictionaryService"); + transactionService = (TransactionService) ctx.getBean("TransactionService"); + + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + } + + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + } + + public void testSetup() throws Exception + { + // NOOP + } + + // + // Test custom model management + // + + public void testSimpleDynamicModel() throws Exception + { + final int X = 0; + final String modelFileName = modelPrefix+X+".xml"; + final QName typeName = QName.createQName("{http://www.alfresco.org/test/testmodel"+X+"/1.0}base"); + final QName modelName = QName.createQName("{http://www.alfresco.org/test/testmodel"+X+"/1.0}testModel"+X); + + try + { + if (isModelDeployed(modelFileName)) + { + // undeploy model + repoAdminService.undeployModel(modelFileName); + } + + assertFalse(isModelDeployed(modelFileName)); + + assertNull(dictionaryService.getClass(typeName)); + + final int defaultModelCnt = dictionaryService.getAllModels().size(); + + // deploy custom model + String model = MODEL_MKR_XML.replace(MKR, X+""); + InputStream modelStream = new ByteArrayInputStream(model.getBytes("UTF-8")); + + repoAdminService.deployModel(modelStream, modelFileName); + + assertTrue(isModelDeployed(modelFileName)); + assertEquals(defaultModelCnt+1, dictionaryService.getAllModels().size()); + + ClassDefinition myType = dictionaryService.getClass(typeName); + assertNotNull(myType); + assertEquals(modelName, myType.getModel().getName()); + + // can re-deploy a deployed model (note: re-deploying the same model is a valid incremental update) + modelStream = new ByteArrayInputStream(model.getBytes("UTF-8")); + repoAdminService.deployModel(modelStream, modelFileName); + + // deactivate model + repoAdminService.deactivateModel(modelFileName); + + assertTrue(isModelDeployed(modelFileName)); // still deployed, although not active + assertEquals(defaultModelCnt, dictionaryService.getAllModels().size()); + assertNull(dictionaryService.getClass(typeName)); + + // try to deactivate an already deactivated model + try + { + repoAdminService.deactivateModel(modelFileName); + fail(); + } + catch (AlfrescoRuntimeException are) + { + // expected + assertTrue(are.getMessage().contains("Model deactivation failed")); + } + + // re-activate model + repoAdminService.activateModel(modelFileName); + + assertTrue(isModelDeployed(modelFileName)); + assertEquals(defaultModelCnt+1, dictionaryService.getAllModels().size()); + + myType = dictionaryService.getClass(typeName); + assertNotNull(myType); + assertEquals(modelName, myType.getModel().getName()); + + // try to activate an already activated model + try + { + repoAdminService.activateModel(modelFileName); + fail(); + } + catch (AlfrescoRuntimeException are) + { + // expected + assertTrue(are.getMessage().contains("Model activation failed")); + } + + // undeploy model + repoAdminService.undeployModel(modelFileName); + + assertFalse(isModelDeployed(modelFileName)); + assertEquals(defaultModelCnt, dictionaryService.getAllModels().size()); + assertNull(dictionaryService.getClass(typeName)); + + // try to undeploy an already undeployed (or non-existant) model + try + { + repoAdminService.undeployModel(modelFileName); + fail(); + } + catch (AlfrescoRuntimeException are) + { + // expected + assertTrue(are.getMessage().contains("Model undeployment failed")); + } + } + finally + { + if (isModelDeployed(modelFileName)) + { + // undeploy model + repoAdminService.undeployModel(modelFileName); + } + + assertNull(dictionaryService.getClass(typeName)); + } + } + + private boolean isModelDeployed(String modelFileName) + { + for (RepoModelDefinition modelDef : repoAdminService.getModels()) + { + if (modelDef.getRepoName().equals(modelFileName)) + { + return true; + } + } + + return false; + } + + private void undeployModels(int modelCnt) + { + for (int i = 1; i <= modelCnt; i++) + { + if (isModelDeployed(modelPrefix+i)) + { + repoAdminService.undeployModel(modelPrefix+i); + } + } + } + + private void deployModels(int modelCnt) throws UnsupportedEncodingException + { + for (int i = 1; i <= modelCnt; i++) + { + if (! isModelDeployed(modelPrefix+i)) + { + String model = MODEL_MKR_XML.replace(MKR, i+""); + InputStream modelStream = new ByteArrayInputStream(model.getBytes("UTF-8")); + repoAdminService.deployModel(modelStream, modelPrefix+i); + } + } + } + + public void testConcurrentDynamicModelCreate() throws Exception + { + final int n = 5; + + undeployModels(n); + + int deployedModelCount = repoAdminService.getModels().size(); + logger.info("Existing deployed custom model count: "+deployedModelCount); + + int dictModelCount = getModelCount(); + logger.info("Existing dictionary model count: "+dictModelCount); + + // concurrently deploy N models + runConcurrentOps(n, 1); + + assertEquals(deployedModelCount+n, repoAdminService.getModels().size()); + + for (int i = 1; i <= n; i++) + { + assertTrue(isModelDeployed(modelPrefix+i)); + } + + assertEquals(dictModelCount+n, getModelCount()); + + undeployModels(n); + } + + public void testConcurrentDynamicModelDelete() throws Exception + { + final int n = 5; + + undeployModels(n); + + int deployedModelCount = repoAdminService.getModels().size(); + logger.info("Existing deployed custom model count: "+deployedModelCount); + + int dictModelCount = getModelCount(); + logger.info("Existing dictionary model count: "+dictModelCount); + + deployModels(n); + + assertEquals(deployedModelCount+n, repoAdminService.getModels().size()); + + for (int i = 1; i <= n; i++) + { + assertTrue(isModelDeployed(modelPrefix+i)); + } + + assertEquals(dictModelCount+n, getModelCount()); + + // concurrently undeploy N models + runConcurrentOps(n, 2); + + assertEquals(deployedModelCount, repoAdminService.getModels().size()); + + for (int i = 1; i <= n; i++) + { + assertFalse(isModelDeployed(modelPrefix+i)); + } + + assertEquals(dictModelCount, getModelCount()); + } + + private int getModelCount() + { + return dictionaryService.getAllModels().size(); + } + + private void runConcurrentOps(int threadCnt, int opType) throws InterruptedException + { + Thread[] threads = new Thread[threadCnt]; + Tester[] testers = new Tester[threadCnt]; + + for (int i = 0; i < threadCnt; i++) + { + Tester tester = new Tester(i+1, opType); + testers[i] = tester; + + threads[i] = new Thread(tester); + threads[i].start(); + } + + for (int i = 0; i < threadCnt; i++) + { + threads[i].join(); + + if (testers[i].getErrorStackTrace() != null) + { + fail(testers[i].getErrorStackTrace()); + } + } + } + + private class Tester implements Runnable + { + private int i; + private int opType; + private String errorStackTrace = null; + + public Tester(int i, int opType) + { + this.i = i; + this.opType = opType; + } + + public String getErrorStackTrace() + { + return errorStackTrace; + } + + public void run() + { + try + { + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + if (opType == 1) + { + // Deploy model + String model = MODEL_MKR_XML.replace(MKR, i+""); + InputStream modelStream = new ByteArrayInputStream(model.getBytes("UTF-8")); + repoAdminService.deployModel(modelStream, modelPrefix+i); + + logger.info("["+i+"] Deploying test model: "+modelPrefix+i+" ["+AlfrescoTransactionSupport.getTransactionId()+"]"); + } + else if (opType == 2) + { + // Undeploy model + repoAdminService.undeployModel(modelPrefix+i); + + logger.info("["+i+"] Undeployed test model: "+modelPrefix+i+" ["+AlfrescoTransactionSupport.getTransactionId()+"]"); + } + + return null; + } + }); + } + catch (Throwable t) + { + StringWriter sw = new StringWriter(); + t.printStackTrace(new PrintWriter(sw)); + errorStackTrace = sw.toString(); + + logger.error("["+i+"] Failed to deploy test model: "+t); + } + } + } + + // + // TODO - Test custom message management + // +} diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryBootstrap.java b/source/java/org/alfresco/repo/dictionary/DictionaryBootstrap.java index 8a37404a7d..f9578a4208 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryBootstrap.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryBootstrap.java @@ -20,11 +20,13 @@ package org.alfresco.repo.dictionary; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.springframework.extensions.surf.util.I18NUtil; import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.dictionary.DictionaryException; +import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -32,7 +34,7 @@ import org.apache.commons.logging.LogFactory; /** * Bootstrap Dictionary DAO with pre-defined models & message resources (from classpath) * - * @author David Caruana + * @author David Caruana, janv * */ public class DictionaryBootstrap implements DictionaryListener @@ -50,7 +52,7 @@ public class DictionaryBootstrap implements DictionaryListener private TenantService tenantService; // Logger - private static Log logger = LogFactory.getLog(DictionaryDAO.class); + private static Log logger = LogFactory.getLog(DictionaryBootstrap.class); /** @@ -110,8 +112,7 @@ public class DictionaryBootstrap implements DictionaryListener */ public void register() { - dictionaryDAO.destroy(); // deployer - force reload on next get - dictionaryDAO.register(this); + dictionaryDAO.register(this); } /* @@ -120,6 +121,11 @@ public class DictionaryBootstrap implements DictionaryListener */ public void onDictionaryInit() { + long startTime = System.currentTimeMillis(); + + Collection modelsBefore = dictionaryDAO.getModels(); // note: on first bootstrap will init empty dictionary + int modelsBeforeCnt = (modelsBefore != null ? modelsBefore.size() : 0); + if ((tenantService == null) || (! tenantService.isTenantUser())) { // register models @@ -132,12 +138,13 @@ public class DictionaryBootstrap implements DictionaryListener } try { + M2Model model = M2Model.createModel(modelStream); + if (logger.isDebugEnabled()) { - logger.debug("Loading model from " + bootstrapModel); + logger.debug("Loading model: "+model.getName()+" (from "+bootstrapModel+")"); } - M2Model model = M2Model.createModel(modelStream); dictionaryDAO.putModel(model); } catch(DictionaryException e) @@ -145,6 +152,14 @@ public class DictionaryBootstrap implements DictionaryListener throw new DictionaryException("Could not import bootstrap model " + bootstrapModel, e); } } + + Collection modelsAfter = dictionaryDAO.getModels(); + int modelsAfterCnt = (modelsAfter != null ? modelsAfter.size() : 0); + + if (logger.isDebugEnabled()) + { + logger.debug("Model count: before="+modelsBeforeCnt+", load="+models.size()+", after="+modelsAfterCnt+" in "+(System.currentTimeMillis()-startTime)+" msecs"); + } } } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryComponent.java b/source/java/org/alfresco/repo/dictionary/DictionaryComponent.java index 9091351f0a..1186be2449 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryComponent.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryComponent.java @@ -45,8 +45,7 @@ import org.springframework.extensions.surf.util.ParameterCheck; public class DictionaryComponent implements DictionaryService, TenantDeployer { private DictionaryDAO dictionaryDAO; - - + // TODO: Check passed arguments are valid /** @@ -58,7 +57,6 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer { this.dictionaryDAO = dictionaryDAO; } - /* (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryService#getAllModels() @@ -67,7 +65,6 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer { return dictionaryDAO.getModels(); } - /* (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryService#getModel(org.alfresco.repo.ref.QName) @@ -76,7 +73,7 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer { return dictionaryDAO.getModel(model); } - + /* (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryService#getAllPropertyTypes() */ @@ -89,7 +86,6 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer } return propertyTypes; } - /* (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryService#getPropertyTypes(org.alfresco.repo.ref.QName) @@ -104,7 +100,6 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer } return qnames; } - /* (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryService#getAllTypes() @@ -128,7 +123,6 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer return dictionaryDAO.getSubTypes(superType, follow); } - /* (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryService#getTypes(org.alfresco.repo.ref.QName) */ @@ -142,7 +136,6 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer } return qnames; } - /* (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryService#getAllAspects() @@ -169,7 +162,6 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer } return associations; } - /* (non-Javadoc) * @see org.alfresco.service.cmr.dictionary.DictionaryService#getSubAspects(org.alfresco.service.namespace.QName, boolean) @@ -207,8 +199,7 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer } return qnames; } - - + /* (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryService#isSubClass(org.alfresco.repo.ref.QName, org.alfresco.repo.ref.QName) */ @@ -247,7 +238,6 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer } return subClassOf; } - /* (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryService#getPropertyType(org.alfresco.repo.ref.QName) @@ -256,7 +246,6 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer { return dictionaryDAO.getDataType(name); } - /* (non-Javadoc) * @see org.alfresco.service.cmr.dictionary.DictionaryService#getDataType(java.lang.Class) @@ -265,8 +254,7 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer { return dictionaryDAO.getDataType(javaClass); } - - + /* (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryService#getType(org.alfresco.repo.ref.QName) */ @@ -274,7 +262,6 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer { return dictionaryDAO.getType(name); } - /* (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryService#getAspect(org.alfresco.repo.ref.QName) @@ -283,7 +270,6 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer { return dictionaryDAO.getAspect(name); } - /* (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryService#getClass(org.alfresco.repo.ref.QName) @@ -330,7 +316,6 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer } return null; } - /* (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryService#getProperty(org.alfresco.repo.ref.QName) @@ -339,7 +324,7 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer { return dictionaryDAO.getProperty(propertyName); } - + /* (non-Javadoc) * @see org.alfresco.repo.dictionary.DictionaryService#getAssociation(org.alfresco.repo.ref.QName) */ @@ -347,8 +332,7 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer { return dictionaryDAO.getAssociation(associationName); } - - + /* * (non-Javadoc) * @see org.alfresco.service.cmr.dictionary.DictionaryService#getAllProperties(org.alfresco.service.namespace.QName) @@ -362,8 +346,7 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer } return aspects; } - - + /* * (non-Javadoc) * @see org.alfresco.service.cmr.dictionary.DictionaryService#getAllProperties(org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName) @@ -379,8 +362,10 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer return props; } - - + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.dictionary.DictionaryService#getProperties(org.alfresco.service.namespace.QName) + */ public Collection getProperties(QName model) { Collection propDefs = dictionaryDAO.getProperties(model); @@ -392,10 +377,29 @@ public class DictionaryComponent implements DictionaryService, TenantDeployer return props; } + /* (non-Javadoc) + * @see org.alfresco.service.cmr.dictionary.DictionaryService#getConstraint(org.alfresco.service.namespace.QName) + */ + public ConstraintDefinition getConstraint(QName constraintQName) + { + return dictionaryDAO.getConstraint(constraintQName); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.dictionary.DictionaryService#getConstraints(org.alfresco.service.namespace.QName) + */ public Collection getConstraints(QName model) { return dictionaryDAO.getConstraints(model); } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.dictionary.DictionaryService#getConstraints(org.alfresco.service.namespace.QName, boolean) + */ + public Collection getConstraints(QName model, boolean referenceableDefsOnly) + { + return dictionaryDAO.getConstraints(model, referenceableDefsOnly); + } public void init() { diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAO.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAO.java index f7104547ba..16226d0d70 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAO.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAO.java @@ -19,6 +19,7 @@ package org.alfresco.repo.dictionary; import java.util.Collection; +import java.util.List; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition; @@ -138,26 +139,26 @@ public interface DictionaryDAO extends ModelQuery public Collection getNamespaces(QName modelName); /** - * @param model the model to retrieve constraints for + * @param model the model to retrieve constraint defs (including property constaint refs) * @return the constraints of the model */ public Collection getConstraints(QName model); /** - * validate against dictionary - * - * if new model - * then nothing to validate - * - * else if an existing model - * then could be updated (or unchanged) so validate to currently only allow incremental updates - * - addition of new types, aspects (except default aspects), properties, associations - * - no deletion of types, aspects or properties or associations - * - no addition, update or deletion of default/mandatory aspects - * - * @param newOrUpdatedModel + * @param model the model to retrieve constraint defs (optionally only referenceable constraints) + * @return the constraints of the model */ - public void validateModel(M2Model newOrUpdatedModel); + public Collection getConstraints(QName model, boolean referenceableDefsOnly); + + /** + * Return diffs between input model and model in the Dictionary. + * + * If the input model does not exist in the Dictionary then no diffs will be returned. + * + * @param model + * @return model diffs (if any) + */ + public List diffModel(M2Model model); /** * diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java index c87e8e1711..7c8f297fd4 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java @@ -46,7 +46,6 @@ import org.alfresco.service.cmr.dictionary.NamespaceDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.namespace.QName; -import org.springframework.extensions.surf.util.ParameterCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -54,7 +53,7 @@ import org.apache.commons.logging.LogFactory; /** * Default implementation of the Dictionary. * - * @author David Caruana + * @author David Caruana, janv * */ public class DictionaryDAOImpl implements DictionaryDAO @@ -190,7 +189,7 @@ public class DictionaryDAOImpl implements DictionaryDAO DictionaryRegistry dictionaryRegistry = initDictionaryRegistry(tenantDomain); if (dictionaryRegistry == null) - { + { // unexpected throw new AlfrescoRuntimeException("Failed to init dictionaryRegistry " + tenantDomain); } @@ -298,13 +297,13 @@ public class DictionaryDAOImpl implements DictionaryDAO // Publish new Model Definition getCompiledModels(tenantDomain).put(modelName, compiledModel); - - if (logger.isDebugEnabled()) + + if (logger.isTraceEnabled()) { - logger.debug("Registered model " + modelName.toPrefixString(namespaceDAO)); + logger.trace("Registered model: " + modelName.toPrefixString(namespaceDAO)); for (M2Namespace namespace : model.getNamespaces()) { - logger.debug("Registered namespace '" + namespace.getUri() + "' (prefix '" + namespace.getPrefix() + "')"); + logger.trace("Registered namespace: '" + namespace.getUri() + "' (prefix '" + namespace.getPrefix() + "')"); } } @@ -942,10 +941,37 @@ public class DictionaryDAOImpl implements DictionaryDAO */ public Collection getConstraints(QName modelName) { + return getConstraints(modelName, false); + } + + public Collection getConstraints(QName modelName, boolean referenceableDefsOnly) + { CompiledModel model = getCompiledModel(modelName); - return model.getConstraints(); + if (referenceableDefsOnly) + { + return getReferenceableConstraintDefs(model); + } + else + { + return model.getConstraints(); + } } + private Collection getReferenceableConstraintDefs(CompiledModel model) + { + Collection conDefs = model.getConstraints(); + Collection propDefs = model.getProperties(); + for (PropertyDefinition propDef : propDefs) + { + for (ConstraintDefinition conDef : propDef.getConstraints()) + { + conDefs.remove(conDef); + } + } + + return conDefs; + } + // re-entrant (eg. via reset) private DictionaryRegistry getDictionaryRegistry(String tenantDomain) { @@ -974,9 +1000,9 @@ public class DictionaryDAOImpl implements DictionaryDAO readLock.unlock(); } - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("getDictionaryRegistry: not in cache (or threadlocal) - re-init ["+Thread.currentThread().getId()+", "+AlfrescoTransactionSupport.getTransactionId()+"]"+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")")); + logger.trace("getDictionaryRegistry: not in cache (or threadlocal) - re-init ["+Thread.currentThread().getId()+", "+AlfrescoTransactionSupport.getTransactionId()+"]"+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")")); } // reset caches - may have been invalidated (e.g. in a cluster) @@ -1088,13 +1114,12 @@ public class DictionaryDAOImpl implements DictionaryDAO /** * Return diffs between input model and model in the Dictionary. * - * If the input model does not exist in the Dictionary or is equivalent to the one in the Dictionary - * then no diffs will be returned. + * If the input model does not exist in the Dictionary then no diffs will be returned. * * @param model * @return model diffs (if any) */ - private List diffModel(M2Model model) + public List diffModel(M2Model model) { // Compile model definition CompiledModel compiledModel = model.compile(this, namespaceDAO); @@ -1140,7 +1165,8 @@ public class DictionaryDAOImpl implements DictionaryDAO { Collection previousTypes = previousVersion.getTypes(); Collection previousAspects = previousVersion.getAspects(); - + Collection previousConDefs = getReferenceableConstraintDefs(previousVersion); + if (model == null) { // delete model @@ -1151,13 +1177,18 @@ public class DictionaryDAOImpl implements DictionaryDAO for (AspectDefinition previousAspect : previousAspects) { M2ModelDiffs.add(new M2ModelDiff(previousAspect.getName(), M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_DELETED)); - } + } + for (ConstraintDefinition previousConDef : previousConDefs) + { + M2ModelDiffs.add(new M2ModelDiff(previousConDef.getName(), M2ModelDiff.TYPE_CONSTRAINT, M2ModelDiff.DIFF_DELETED)); + } } else { // update model Collection types = model.getTypes(); Collection aspects = model.getAspects(); + Collection conDefs = getReferenceableConstraintDefs(model); if (previousTypes.size() != 0) { @@ -1182,6 +1213,18 @@ public class DictionaryDAOImpl implements DictionaryDAO M2ModelDiffs.add(new M2ModelDiff(aspect.getName(), M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_CREATED)); } } + + if (previousConDefs.size() != 0) + { + M2ModelDiffs.addAll(M2ConstraintDefinition.diffConstraintLists(new ArrayList(previousConDefs), new ArrayList(conDefs))); + } + else + { + for (ConstraintDefinition conDef : conDefs) + { + M2ModelDiffs.add(new M2ModelDiff(conDef.getName(), M2ModelDiff.TYPE_CONSTRAINT, M2ModelDiff.DIFF_CREATED)); + } + } } } else @@ -1211,41 +1254,6 @@ public class DictionaryDAOImpl implements DictionaryDAO return M2ModelDiffs; } - /** - * validate against dictionary - * - * if new model - * then nothing to validate - * - * else if an existing model - * then could be updated (or unchanged) so validate to currently only allow incremental updates - * - addition of new types, aspects (except default aspects), properties, associations - * - no deletion of types, aspects or properties or associations - * - no addition, update or deletion of default/mandatory aspects - * - * @param newOrUpdatedModel - */ - public void validateModel(M2Model newOrUpdatedModel) - { - // Check that all the passed values are not null - ParameterCheck.mandatory("newOrUpdatedModel", newOrUpdatedModel); - - List modelDiffs = diffModel(newOrUpdatedModel); - - for (M2ModelDiff modelDiff : modelDiffs) - { - if (modelDiff.getDiffType().equals(M2ModelDiff.DIFF_DELETED)) - { - throw new AlfrescoRuntimeException("Failed to validate model update - found deleted " + modelDiff.getElementType() + " '" + modelDiff.getElementName() + "'"); - } - - if (modelDiff.getDiffType().equals(M2ModelDiff.DIFF_UPDATED)) - { - throw new AlfrescoRuntimeException("Failed to validate model update - found non-incrementally updated " + modelDiff.getElementType() + " '" + modelDiff.getElementName() + "'"); - } - } - } - /* package */ class DictionaryRegistry { private Map> uriToModels = new HashMap>(0); diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java b/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java index 40592bc46d..c425e8cae4 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java @@ -19,11 +19,12 @@ package org.alfresco.repo.dictionary; import java.io.Serializable; -import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; @@ -38,12 +39,16 @@ import org.alfresco.repo.tenant.TenantAdminService; import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionListenerAdapter; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.version.Version2Model; import org.alfresco.repo.workflow.BPMEngineRegistry; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.dictionary.ModelDefinition; import org.alfresco.service.cmr.dictionary.NamespaceDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentReader; @@ -58,6 +63,7 @@ import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.GUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -119,6 +125,8 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda /** The tenant deployer service */ private TenantAdminService tenantAdminService; + private TransactionService transactionService; + /** Transaction listener */ private DictionaryModelTypeTransactionListener transactionListener; @@ -205,6 +213,14 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda this.tenantAdminService = tenantAdminService; } + /** + * Set the transaction service + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + public void setStoreUrls(List storeUrls) { this.storeUrls = storeUrls; @@ -263,19 +279,25 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda */ public void onContentUpdate(NodeRef nodeRef, boolean newContent) { + if (logger.isTraceEnabled()) + { + logger.trace("onContentUpdate: nodeRef="+nodeRef+ " ["+AlfrescoTransactionSupport.getTransactionId()+"]"); + } + queueModel(nodeRef); } @SuppressWarnings("unchecked") private void queueModel(NodeRef nodeRef) { - Set pendingModelUpdates = (Set)AlfrescoTransactionSupport.getResource(KEY_PENDING_MODELS); - if (pendingModelUpdates == null) + Set pendingModels = (Set)AlfrescoTransactionSupport.getResource(KEY_PENDING_MODELS); + if (pendingModels == null) { - pendingModelUpdates = new HashSet(); - AlfrescoTransactionSupport.bindResource(KEY_PENDING_MODELS, pendingModelUpdates); + //pendingModels = Collections.newSetFromMap(new ConcurrentHashMap()); // Java 6 + pendingModels = new CopyOnWriteArraySet(); + AlfrescoTransactionSupport.bindResource(KEY_PENDING_MODELS, pendingModels); } - pendingModelUpdates.add(tenantService.getName(nodeRef)); + pendingModels.add(tenantService.getName(nodeRef)); AlfrescoTransactionSupport.bindListener(this.transactionListener); } @@ -292,6 +314,11 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda Map before, Map after) { + if (logger.isTraceEnabled()) + { + logger.trace("onUpdateProperties: nodeRef="+nodeRef+ " ["+AlfrescoTransactionSupport.getTransactionId()+"]"); + } + Boolean beforeValue = (Boolean)before.get(ContentModel.PROP_MODEL_ACTIVE); Boolean afterValue = (Boolean)after.get(ContentModel.PROP_MODEL_ACTIVE); @@ -312,12 +339,17 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda public void onRemoveAspect(NodeRef nodeRef, QName aspect) { - // undo/cancel checkout removes the "workingcopy" aspect prior to deleting the node - hence need to track here - if (aspect.equals(ContentModel.ASPECT_WORKING_COPY)) - { - AlfrescoTransactionSupport.bindResource(KEY_WORKING_COPY, nodeRef); - } - + if (logger.isTraceEnabled()) + { + logger.trace("onRemoveAspect: nodeRef="+nodeRef+ " ["+AlfrescoTransactionSupport.getTransactionId()+"]"); + } + + // undo/cancel checkout removes the "workingcopy" aspect prior to deleting the node - hence need to track here + if (aspect.equals(ContentModel.ASPECT_WORKING_COPY)) + { + AlfrescoTransactionSupport.bindResource(KEY_WORKING_COPY, nodeRef); + } + // restore removes the "archived" aspect prior to restoring (via delete/move) the node - hence need to track here if (aspect.equals(ContentModel.ASPECT_ARCHIVED)) { @@ -328,13 +360,13 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda @SuppressWarnings("unchecked") public void beforeDeleteNode(NodeRef nodeRef) { - boolean workingCopy = nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY); - NodeRef wcNodeRef = (NodeRef)AlfrescoTransactionSupport.getResource(KEY_WORKING_COPY); - if ((wcNodeRef != null) && (wcNodeRef.equals(nodeRef))) - { - workingCopy = true; - } - + boolean workingCopy = nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY); + NodeRef wcNodeRef = (NodeRef)AlfrescoTransactionSupport.getResource(KEY_WORKING_COPY); + if ((wcNodeRef != null) && (wcNodeRef.equals(nodeRef))) + { + workingCopy = true; + } + boolean archived = nodeService.hasAspect(nodeRef, ContentModel.ASPECT_ARCHIVED); NodeRef aNodeRef = (NodeRef)AlfrescoTransactionSupport.getResource(KEY_ARCHIVED); if ((aNodeRef != null) && (aNodeRef.equals(nodeRef))) @@ -342,24 +374,28 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda archived = true; } - // Ignore if the node is a working copy or archived - if (! (workingCopy || archived)) + boolean isVersionNode = nodeRef.getStoreRef().getIdentifier().equals(Version2Model.STORE_ID); + + // Ignore if the node is a working copy, archived or version node + if (! (workingCopy || archived || isVersionNode)) { QName modelName = (QName)this.nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_NAME); + + if (logger.isTraceEnabled()) + { + logger.trace("beforeDeleteNode: nodeRef="+nodeRef+" validate model delete (modelName="+modelName+")"); + } + if (modelName != null) { // Validate model delete against usages - content and/or workflows validateModelDelete(modelName); - if (logger.isDebugEnabled()) - { - logger.debug("beforeDeleteNode: modelName="+modelName+" ("+nodeRef+")"); - } - Set pendingModelDeletes = (Set)AlfrescoTransactionSupport.getResource(KEY_PENDING_DELETE_MODELS); if (pendingModelDeletes == null) { - pendingModelDeletes = new HashSet(); + //pendingModelDeletes = Collections.newSetFromMap(new ConcurrentHashMap()); // Java 6 + pendingModelDeletes = new CopyOnWriteArraySet(); AlfrescoTransactionSupport.bindResource(KEY_PENDING_DELETE_MODELS, pendingModelDeletes); } pendingModelDeletes.add(tenantService.getName(nodeRef)); @@ -367,13 +403,26 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda AlfrescoTransactionSupport.bindListener(this.transactionListener); } } + else + { + if (logger.isTraceEnabled()) + { + logger.trace("beforeDeleteNode: nodeRef="+nodeRef+ " ignored ("+(workingCopy ? " workingCopy " : "")+(archived ? " archived " : "")+(isVersionNode ? " isVersionNode " : "")+") ["+AlfrescoTransactionSupport.getTransactionId()+"]"); + } + } } - @SuppressWarnings("unchecked") + //@SuppressWarnings("unchecked") public void onDeleteNode(ChildAssociationRef childAssocRef, boolean isNodeArchived) { + /* NodeRef nodeRef = childAssocRef.getChildRef(); + if (logger.isTraceEnabled()) + { + logger.trace("onDeleteNode: nodeRef="+nodeRef+ " ["+AlfrescoTransactionSupport.getTransactionId()+"]"); + } + Set pendingDeleteModels = (Set)AlfrescoTransactionSupport.getResource(KEY_PENDING_DELETE_MODELS); if (pendingDeleteModels != null) @@ -393,20 +442,32 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda return null; } }, tenantSystemUserName); + + if (logger.isTraceEnabled()) + { + logger.trace("onDeleteNode: Dictionary destroyed ["+AlfrescoTransactionSupport.getTransactionId()+"]"); + } } } + */ } public void onCreateNode(ChildAssociationRef childAssocRef) { NodeRef nodeRef = childAssocRef.getChildRef(); + + if (logger.isTraceEnabled()) + { + logger.trace("onCreateNode: nodeRef="+nodeRef+ " ["+AlfrescoTransactionSupport.getTransactionId()+"]"); + } + if (nodeService.getType(nodeRef).equals(ContentModel.TYPE_DICTIONARY_MODEL)) { - Boolean value = (Boolean)nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_ACTIVE); - if ((value != null) && (value == true)) - { - queueModel(nodeRef); - } + Boolean value = (Boolean)nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_ACTIVE); + if ((value != null) && (value == true)) + { + queueModel(nodeRef); + } } } @@ -429,6 +490,81 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda this.contentService = contentService; } + @SuppressWarnings("unchecked") + @Override + public void afterCommit() + { + Set pendingModels = (Set)AlfrescoTransactionSupport.getResource(KEY_PENDING_MODELS); + Set pendingDeleteModels = (Set)AlfrescoTransactionSupport.getResource(KEY_PENDING_DELETE_MODELS); + + if (logger.isTraceEnabled()) + { + logger.trace("afterCommit: pendingModelsCnt="+(pendingModels != null ? pendingModels.size() : "0")+ + ", pendingDeleteModelsCnt="+(pendingDeleteModels != null ? pendingDeleteModels.size() : "0")); + } + + Set systemTenants = new HashSet(10); + + if (pendingModels != null) + { + // unbind the resource from the transaction + AlfrescoTransactionSupport.unbindResource(KEY_PENDING_MODELS); + + for (NodeRef nodeRef : pendingModels) + { + String tenantDomain = tenantService.getDomain(nodeRef.getStoreRef().getIdentifier()); + String tenantSystemUserName = tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain); + systemTenants.add(tenantSystemUserName); + } + } + + if (pendingDeleteModels != null) + { + // unbind the resource from the transaction + AlfrescoTransactionSupport.unbindResource(KEY_PENDING_DELETE_MODELS); + + for (NodeRef nodeRef : pendingDeleteModels) + { + String tenantDomain = tenantService.getDomain(nodeRef.getStoreRef().getIdentifier()); + String tenantSystemUserName = tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain); + systemTenants.add(tenantSystemUserName); + } + } + + if (systemTenants.size() > 0) + { + for (final String tenantSystemUserName : systemTenants) + { + RetryingTransactionCallback work = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + // invalidate - to force lazy re-init + // note: since afterCommit - need to either clear shared cache or destroy in separate txn + dictionaryDAO.destroy(); + + if (logger.isTraceEnabled()) + { + logger.trace("afterCommit: Dictionary destroyed ["+AlfrescoTransactionSupport.getTransactionId()+"]"); + } + + return null; + } + }, tenantSystemUserName); + + return null; + } + }; + + transactionService.getRetryingTransactionHelper().doInTransaction(work, true, true); + } + } + } + /** * @see org.alfresco.repo.transaction.TransactionListener#beforeCommit(boolean) */ @@ -440,14 +576,11 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda if (pendingModels != null) { - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("beforeCommit: pendingModelsCnt="+pendingModels.size()); + logger.trace("beforeCommit: pendingModelsCnt="+pendingModels.size()); } - // unbind the resource from the transaction - AlfrescoTransactionSupport.unbindResource(KEY_PENDING_MODELS); - for (NodeRef pendingNodeRef : pendingModels) { String tenantDomain = tenantService.getDomain(pendingNodeRef.getStoreRef().getIdentifier()); @@ -459,6 +592,12 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda { public Object doWork() { + // Ignore if the node no longer exists + if (! nodeService.exists(nodeRef)) + { + return null; + } + // Find out whether the model is active boolean isActive = false; Boolean value = (Boolean)nodeService.getProperty(nodeRef, ContentModel.PROP_MODEL_ACTIVE); @@ -472,7 +611,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda { if (isActive == true) { - // 1. Compile the model and update the details on the node + // 1. Compile the model and update the details on the node // 2. Re-put the model ContentReader contentReader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); @@ -482,7 +621,8 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda M2Model m2Model = M2Model.createModel(contentReader.getContentInputStream()); // Try and compile the model - ModelDefinition modelDefinition = m2Model.compile(dictionaryDAO, namespaceDAO).getModelDefinition(); + CompiledModel compiledModel= m2Model.compile(dictionaryDAO, namespaceDAO); + ModelDefinition modelDefinition = compiledModel.getModelDefinition(); // Update the meta data for the model Map props = nodeService.getProperties(nodeRef); @@ -493,32 +633,16 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda props.put(ContentModel.PROP_MODEL_VERSION, modelDefinition.getVersion()); nodeService.setProperties(nodeRef, props); - ArrayList modelNodeRefs = getModelNodes(nodeRef.getStoreRef(), modelDefinition.getName()); - for (NodeRef existingNodeRef : modelNodeRefs) - { - if (! existingNodeRef.equals(nodeRef)) - { - // check if existing model node is active - Boolean existingValue = (Boolean)nodeService.getProperty(existingNodeRef, ContentModel.PROP_MODEL_ACTIVE); - if ((existingValue != null) && (existingValue.booleanValue() == true)) - { - String name = (String)nodeService.getProperty(existingNodeRef, ContentModel.PROP_NAME); - - // for MT import, model may have been activated by DictionaryRepositoryBootstrap - if (logger.isDebugEnabled()) - { - logger.debug("Re-activating '"+modelDefinition.getName()+"' - existing active model: " + name); - } - //throw new AlfrescoRuntimeException("Cannot activate '"+modelDefinition.getName()+"' - existing active model: " + name); - } - } - } - - // Validate model against dictionary - could be new, unchanged or updated - dictionaryDAO.validateModel(m2Model); + // Validate model against dictionary - could be new, unchanged or updated + validateModel(modelDefinition.getName(), m2Model, compiledModel); // invalidate - to force lazy re-init - dictionaryDAO.destroy(); + //dictionaryDAO.destroy(); + + if (logger.isTraceEnabled()) + { + logger.trace("beforeCommit: activating nodeRef="+nodeRef+" ("+modelDefinition.getName()+")"); + } } } else @@ -530,7 +654,12 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda validateModelDelete(modelName); // invalidate - to force lazy re-init - dictionaryDAO.destroy(); + //dictionaryDAO.destroy(); + + if (logger.isTraceEnabled()) + { + logger.trace("beforeCommit: deactivating nodeRef="+nodeRef+" ("+modelName+")"); + } } } } @@ -572,7 +701,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda private void validateModelDelete(final QName modelName) { // TODO add model locking during delete (would need to be tenant-aware & cluster-aware) to avoid potential - // for concurrent addition of new content/workflow as model is being deleted + // for concurrent addition of new content/workflow as model is being deleted try { @@ -580,8 +709,11 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda } catch (DictionaryException e) { - logger.warn("Model ' + modelName + ' does not exist ... skip delete validation : " + e); - return; + if (logger.isDebugEnabled()) + { + logger.debug("Dictionary model '" + modelName + "' does not exist ... skip delete validation : " + e); + } + return; } // TODO - in case of MT we do not currently allow deletion of an overridden model (with usages) ... but could allow if (re-)inherited model is equivalent to an incremental update only ? @@ -687,29 +819,162 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda } } - private ArrayList getModelNodes(StoreRef storeRef, QName modelName) - { - ArrayList nodeRefs = new ArrayList(); - - ResultSet rs = searchService.query(storeRef, SearchService.LANGUAGE_LUCENE, "TYPE:\""+ContentModel.TYPE_DICTIONARY_MODEL+"\""); - try - { - if (rs.length() > 0) - { - for (NodeRef modelNodeRef : rs.getNodeRefs()) - { - QName name = (QName)nodeService.getProperty(modelNodeRef, ContentModel.PROP_MODEL_NAME); - if ((name != null) && (name.equals(modelName))) - { - nodeRefs.add(modelNodeRef); - } - } - } - } - finally - { - rs.close(); - } - return nodeRefs; - } + /** + * validate against dictionary + * + * if new model + * then nothing to validate + * + * else if an existing model + * then could be updated (or unchanged) so validate to currently only allow incremental updates + * - addition of new types, aspects (except default aspects), properties, associations + * - no deletion of types, aspects or properties or associations + * - no addition, update or deletion of default/mandatory aspects + * + * @paramn modelName + * @param newOrUpdatedModel + */ + private void validateModel(QName modelName, M2Model model, CompiledModel compiledModel) + { + List modelDiffs = dictionaryDAO.diffModel(model); + + for (M2ModelDiff modelDiff : modelDiffs) + { + if (modelDiff.getDiffType().equals(M2ModelDiff.DIFF_DELETED)) + { + // TODO - check tenants if model is shared / inherited + if (modelDiff.getElementType().equals(M2ModelDiff.TYPE_PROPERTY)) + { + validatePropertyDelete(modelName, modelDiff.getElementName(), false); + + continue; + } + else if (modelDiff.getElementType().equals(M2ModelDiff.TYPE_CONSTRAINT)) + { + validateConstraintDelete(compiledModel, modelDiff.getElementName(), false); + continue; + } + else + { + throw new AlfrescoRuntimeException("Failed to validate model update - found deleted " + modelDiff.getElementType() + " '" + modelDiff.getElementName() + "'"); + } + } + + if (modelDiff.getDiffType().equals(M2ModelDiff.DIFF_UPDATED)) + { + throw new AlfrescoRuntimeException("Failed to validate model update - found non-incrementally updated " + modelDiff.getElementType() + " '" + modelDiff.getElementName() + "'"); + } + } + + // TODO validate that any deleted constraints are not being referenced - else currently will become anon - or push down into model compilation (check backwards compatibility ...) + } + + private void validatePropertyDelete(QName modelName, QName propertyName, boolean sharedModel) + { + String tenantDomain = TenantService.DEFAULT_DOMAIN; + if (sharedModel) + { + tenantDomain = " for tenant [" + tenantService.getCurrentUserDomain() + "]"; + } + + boolean found = false; + + // check for property usages + for (PropertyDefinition prop : dictionaryDAO.getProperties(modelName, null)) + { + // TODO ... match property + if (prop.getName().equals(propertyName)) + { + // found + found = true; + validateIndexedProperty(tenantDomain, prop); + break; + } + } + + if (! found) + { + throw new AlfrescoRuntimeException("Failed to validate property delete" + tenantDomain + " - property definition '" + propertyName + "' not defined in model '" + modelName + "'"); + } + } + + private void validateIndexedProperty(String tenantDomain, PropertyDefinition propDef) + { + QName propName = propDef.getName(); + + if (! propDef.isIndexed()) + { + // TODO ... implement DB-level referential integrity + throw new AlfrescoRuntimeException("Failed to validate property delete" + tenantDomain + " - cannot delete unindexed property definition '" + propName); + } + + for (String storeUrl : this.storeUrls) + { + StoreRef store = new StoreRef(storeUrl); + + // search for indexed PROPERTY + String escapePropName = propName.toPrefixString().replace(":", "\\:"); + ResultSet rs = searchService.query(store, SearchService.LANGUAGE_LUCENE, "@"+escapePropName+":*"); + try + { + if (rs.length() > 0) + { + throw new AlfrescoRuntimeException("Failed to validate property delete" + tenantDomain + " - found " + rs.length() + " nodes in store " + store + " with PROPERTY '" + propName + "'" ); + } + } + finally + { + rs.close(); + } + } + } + + // validate delete of a referencable constraint def + private void validateConstraintDelete(CompiledModel compiledModel, QName constraintName, boolean sharedModel) + { + String tenantDomain = TenantService.DEFAULT_DOMAIN; + if (sharedModel) + { + tenantDomain = " for tenant [" + tenantService.getCurrentUserDomain() + "]"; + } + + Set referencedBy = new HashSet(0); + + // check for references to constraint definition + // note: could be anon prop constraint (if no referenceable constraint) + Collection allModels = dictionaryDAO.getModels(); + for (QName model : allModels) + { + Collection propDefs = null; + if (compiledModel.getModelDefinition().getName().equals(model)) + { + // TODO deal with multiple pending model updates + propDefs = compiledModel.getProperties(); + } + else + { + propDefs = dictionaryDAO.getProperties(model); + } + + for (PropertyDefinition propDef : propDefs) + { + for (ConstraintDefinition conDef : propDef.getConstraints()) + { + if (constraintName.equals(conDef.getRef())) + { + referencedBy.add(conDef.getName()); + } + } + } + } + + if (referencedBy.size() == 1) + { + throw new AlfrescoRuntimeException("Failed to validate constraint delete" + tenantDomain + " - constraint definition '" + constraintName + "' is being referenced by '" + referencedBy.toArray()[0] + "' property constraint"); + } + else if (referencedBy.size() > 1) + { + throw new AlfrescoRuntimeException("Failed to validate constraint delete" + tenantDomain + " - constraint definition '" + constraintName + "' is being referenced by " + referencedBy.size() + " property constraints"); + } + } } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryModelTypeTest.java b/source/java/org/alfresco/repo/dictionary/DictionaryModelTypeTest.java index ef621873e8..d85e68102a 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryModelTypeTest.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryModelTypeTest.java @@ -19,21 +19,23 @@ package org.alfresco.repo.dictionary; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.i18n.MessageService; import org.alfresco.repo.tenant.TenantAdminService; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.ModelDefinition; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.BaseAlfrescoSpringTest; @@ -42,14 +44,17 @@ import org.alfresco.util.PropertyMap; /** * Dictionary model type unit test * - * @author Roy Wetherall + * @author Roy Wetherall, janv */ public class DictionaryModelTypeTest extends BaseAlfrescoSpringTest { - /** QName of the test model */ - private static final QName TEST_MODEL_ONE = QName.createQName("{http://www.alfresco.org/test/testmodel1/1.0}testModelOne"); + /** QNames of the test models */ + + private static final QName TEST_MODEL_ONE = QName.createQName("{http://www.alfresco.org/test/testmodel1/1.0}testModelOne"); + private static final QName TEST_MODEL_TWO = QName.createQName("{http://www.alfresco.org/test/testmodel2/1.0}testModelTwo"); + + /** Test model XMLs */ - /** Test model XML */ public static final String MODEL_ONE_XML = "" + @@ -60,17 +65,19 @@ public class DictionaryModelTypeTest extends BaseAlfrescoSpringTest " " + " " + + " " + " " + - + " " + " " + " " + - + " " + - + " " + " Base" + " The Base Type" + + " cm:content" + " " + " " + " d:text" + @@ -92,22 +99,61 @@ public class DictionaryModelTypeTest extends BaseAlfrescoSpringTest " " + " " + + " " + " " + - + " " + " " + " " + - + " " + - + " " + " Base" + " The Base Type" + + " cm:content" + " " + " " + " d:text" + " " + " " + + " d:boolean" + + " " + + " " + + " " + + + " " + + + ""; + + public static final String MODEL_ONE_MODIFIED2_XML = + "" + + + " Test model one (updated 2)" + + " Alfresco" + + " 2005-05-30" + + " 1.2" + + + " " + + " " + + " " + + " " + + + " " + + " " + + " " + + + " " + + + " " + + " Base" + + " The Base Type" + + " cm:content" + + " " + + " " + + " d:text" + + " " + + " " + " d:text" + " " + " " + @@ -116,7 +162,126 @@ public class DictionaryModelTypeTest extends BaseAlfrescoSpringTest " " + ""; - + + public static final String MODEL_TWO_XML = + "" + + + " Test model two" + + " Alfresco" + + " 2010-01-13" + + " 1.0" + + + " " + + " " + + " " + + " " + + + " " + + " " + + " " + + + " " + + " " + + " " + + " " + + " alfresco" + + " file" + + " " + + " " + + " " + + " " + + + " " + + + " " + + " Base" + + " The Base Type" + + " cm:content" + + " " + + " " + + " d:text" + + " " + + " " + + " " + + " " + + " " + + " " + + + " " + + + ""; + + public static final String MODEL_TWO_INVALID_XML = + "" + + + " Test model two" + + " Alfresco" + + " 2010-01-14" + + " 1.1" + + + " " + + " " + + " " + + " " + + + " " + + " " + + " " + + + " " + + + " " + + " Base" + + " The Base Type" + + " cm:content" + + " " + + " " + + " d:text" + + " " + + " " + + " " + + " " + + " " + + " " + + + " " + + + ""; + + public static final String MODEL_TWO_MODIFIED_XML = + "" + + + " Test model two - modified" + + " Alfresco - modified" + + " 2010-01-14" + + " 1.1" + + + " " + + " " + + " " + + " " + + + " " + + " " + + " " + + + " " + + + " " + + " Base" + + " The Base Type" + + " cm:content" + + " " + + " " + + " d:text" + + " " + + " " + + " " + + + " " + + + ""; + /** Services used in tests */ private DictionaryService dictionaryService; private NamespaceService namespaceService; @@ -142,6 +307,11 @@ public class DictionaryModelTypeTest extends BaseAlfrescoSpringTest TenantAdminService tenantAdminService = (TenantAdminService)this.applicationContext.getBean("tenantAdminService"); MessageService messageService = (MessageService)this.applicationContext.getBean("messageService"); + List storeUrlsToValidate = new ArrayList(1); + storeUrlsToValidate.add(this.storeRef.toString()); + DictionaryModelType dictionaryModelType = (DictionaryModelType)this.applicationContext.getBean("dictionaryModelType"); + dictionaryModelType.setStoreUrls(storeUrlsToValidate); + DictionaryRepositoryBootstrap bootstrap = new DictionaryRepositoryBootstrap(); bootstrap.setContentService(this.contentService); bootstrap.setDictionaryDAO(this.dictionaryDAO); @@ -189,6 +359,7 @@ public class DictionaryModelTypeTest extends BaseAlfrescoSpringTest // Create a model node PropertyMap properties = new PropertyMap(1); properties.put(ContentModel.PROP_MODEL_ACTIVE, true); + final NodeRef modelNode = this.nodeService.createNode( this.rootNodeRef, ContentModel.ASSOC_CHILDREN, @@ -211,6 +382,10 @@ public class DictionaryModelTypeTest extends BaseAlfrescoSpringTest { public NodeRef execute() throws Exception { + // Check that the namespace is in the namespace service + String uri = namespaceService.getNamespaceURI("test1"); + assertNotNull(uri); + // Check that the meta data has been extracted from the model assertEquals(QName.createQName("{http://www.alfresco.org/test/testmodel1/1.0}testModelOne"), DictionaryModelTypeTest.this.nodeService.getProperty(modelNode, ContentModel.PROP_MODEL_NAME)); @@ -244,7 +419,7 @@ public class DictionaryModelTypeTest extends BaseAlfrescoSpringTest // Check that the policy has not been fired since we have updated a working copy assertEquals("1.0", DictionaryModelTypeTest.this.nodeService.getProperty(workingCopy, ContentModel.PROP_MODEL_VERSION)); - // Now check the model changed back in + // Check-in the model change DictionaryModelTypeTest.this.cociService.checkin(workingCopy, null); return null; } @@ -253,13 +428,319 @@ public class DictionaryModelTypeTest extends BaseAlfrescoSpringTest transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { public Object execute() throws Exception - { + { // Now check that the model has been updated assertEquals("1.1", DictionaryModelTypeTest.this.nodeService.getProperty(modelNode, ContentModel.PROP_MODEL_VERSION)); return null; } }); + // create node using new type + final NodeRef node1 = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Exception + { + NodeRef node = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("http://www.alfresco.org/model/system/1.0", "node1"), + QName.createQName("http://www.alfresco.org/test/testmodel1/1.0", "base"), + null).getChildRef(); + assertNotNull(node); + return node; + } + }); + + try + { + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + DictionaryModelTypeTest.this.nodeService.deleteNode(modelNode); + return null; + } + }); + + fail("Unexpected - should not be able to delete model"); + } + catch (AlfrescoRuntimeException are) + { + // expected + } + + // delete node + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + nodeService.deleteNode(node1); + return null; + } + }); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + DictionaryModelTypeTest.this.nodeService.deleteNode(modelNode); + return null; + } + }); + } + + public void testUpdateDictionaryModelPropertyDelete() + { + try + { + // Check that the model has not yet been loaded into the dictionary + this.dictionaryService.getModel(TEST_MODEL_ONE); + fail("This model has not yet been loaded into the dictionary service"); + } + catch (DictionaryException exception) + { + // We expect this exception + } + + // Check that the namespace is not yet in the namespace service + String uri = this.namespaceService.getNamespaceURI("test1"); + assertNull(uri); + + // Create a model node + PropertyMap properties = new PropertyMap(1); + properties.put(ContentModel.PROP_MODEL_ACTIVE, true); + final NodeRef modelNode = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.ALFRESCO_URI, "dictionaryModels"), + ContentModel.TYPE_DICTIONARY_MODEL, + properties).getChildRef(); + assertNotNull(modelNode); + + // Add the model content to the model node + ContentWriter contentWriter = this.contentService.getWriter(modelNode, ContentModel.PROP_CONTENT, true); + contentWriter.setEncoding("UTF-8"); + contentWriter.setMimetype(MimetypeMap.MIMETYPE_XML); + contentWriter.putContent(MODEL_ONE_MODIFIED_XML); + + // End the transaction to force update + setComplete(); + endTransaction(); + + // create node using new type + final NodeRef node1 = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Exception + { + // Check that the namespace is in the namespace service + String uri = namespaceService.getNamespaceURI("test1"); + assertNotNull(uri); + + // Create a model node + PropertyMap properties = new PropertyMap(1); + properties.put(QName.createQName("http://www.alfresco.org/test/testmodel1/1.0", "prop2"), "false"); // boolean + + NodeRef node = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.ALFRESCO_URI, "node1"), + QName.createQName("http://www.alfresco.org/test/testmodel1/1.0", "base"), + properties).getChildRef(); + assertNotNull(node); + return node; + } + }); + + final NodeRef workingCopy = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Exception + { + // Update model + NodeRef workingCopy = DictionaryModelTypeTest.this.cociService.checkout(modelNode); + ContentWriter contentWriter2 = DictionaryModelTypeTest.this.contentService.getWriter(workingCopy, ContentModel.PROP_CONTENT, true); + contentWriter2.putContent(MODEL_ONE_MODIFIED2_XML); + + return workingCopy; + } + }); + + try + { + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + // Check that the policy has not been fired since we have updated a working copy + assertEquals("1.1", DictionaryModelTypeTest.this.nodeService.getProperty(workingCopy, ContentModel.PROP_MODEL_VERSION)); + + // Check-in the model change + DictionaryModelTypeTest.this.cociService.checkin(workingCopy, null); + return null; + } + }); + + fail("Unexpected - should not be able to update model"); + } + catch (AlfrescoRuntimeException are) + { + // expected + } + + // delete node + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + nodeService.deleteNode(node1); + return null; + } + }); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + // Check that the policy has not been fired since we have updated a working copy + assertEquals("1.1", DictionaryModelTypeTest.this.nodeService.getProperty(workingCopy, ContentModel.PROP_MODEL_VERSION)); + + // Check-in the model change + DictionaryModelTypeTest.this.cociService.checkin(workingCopy, null); + return null; + } + }); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + // Now check that the model has been updated + assertEquals("1.2", DictionaryModelTypeTest.this.nodeService.getProperty(modelNode, ContentModel.PROP_MODEL_VERSION)); + return null; + } + }); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + DictionaryModelTypeTest.this.nodeService.deleteNode(modelNode); + return null; + } + }); + } + + public void testUpdateDictionaryModelConstraintDelete() + { + try + { + // Check that the model has not yet been loaded into the dictionary + this.dictionaryService.getModel(TEST_MODEL_TWO); + fail("This model has not yet been loaded into the dictionary service"); + } + catch (DictionaryException exception) + { + // We expect this exception + } + + // Check that the namespace is not yet in the namespace service + String uri = this.namespaceService.getNamespaceURI("test2"); + assertNull(uri); + + // Create a model node + PropertyMap properties = new PropertyMap(1); + properties.put(ContentModel.PROP_MODEL_ACTIVE, true); + final NodeRef modelNode = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.ALFRESCO_URI, "dictionaryModels"), + ContentModel.TYPE_DICTIONARY_MODEL, + properties).getChildRef(); + assertNotNull(modelNode); + + // Add the model content to the model node + ContentWriter contentWriter = this.contentService.getWriter(modelNode, ContentModel.PROP_CONTENT, true); + contentWriter.setEncoding("UTF-8"); + contentWriter.setMimetype(MimetypeMap.MIMETYPE_XML); + contentWriter.putContent(MODEL_TWO_XML); + + // End the transaction to force update + setComplete(); + endTransaction(); + + final NodeRef workingCopy = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Exception + { + // Check that the namespace is in the namespace service + String uri = namespaceService.getNamespaceURI("test2"); + assertNotNull(uri); + + Collection constraints = dictionaryService.getConstraints(TEST_MODEL_TWO, true); + assertEquals(1, constraints.size()); + assertEquals("test2:con1", constraints.iterator().next().getName().getPrefixString()); + + // Update model + NodeRef workingCopy = DictionaryModelTypeTest.this.cociService.checkout(modelNode); + ContentWriter contentWriter2 = DictionaryModelTypeTest.this.contentService.getWriter(workingCopy, ContentModel.PROP_CONTENT, true); + contentWriter2.putContent(MODEL_TWO_INVALID_XML); + + return workingCopy; + } + }); + + try + { + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + // Check that the policy has not been fired since we have updated a working copy + assertEquals("1.0", DictionaryModelTypeTest.this.nodeService.getProperty(workingCopy, ContentModel.PROP_MODEL_VERSION)); + + // Check-in the model change + DictionaryModelTypeTest.this.cociService.checkin(workingCopy, null); + return null; + } + }); + + fail("Unexpected - should not be able to update model"); + } + catch (AlfrescoRuntimeException are) + { + assertTrue(are.getMessage().contains("Failed to validate constraint delete")); + } + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + // Check that the policy has not been fired since the previous update was invalid + assertEquals("1.0", DictionaryModelTypeTest.this.nodeService.getProperty(modelNode, ContentModel.PROP_MODEL_VERSION)); + + // Update model + ContentWriter contentWriter2 = DictionaryModelTypeTest.this.contentService.getWriter(workingCopy, ContentModel.PROP_CONTENT, true); + contentWriter2.putContent(MODEL_TWO_MODIFIED_XML); + + // Check-in the model change + DictionaryModelTypeTest.this.cociService.checkin(workingCopy, null); + return null; + } + }); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + // Now check that the model has been updated + + Collection constraints = dictionaryService.getConstraints(TEST_MODEL_TWO, true); + assertEquals(0, constraints.size()); + + assertEquals("1.1", DictionaryModelTypeTest.this.nodeService.getProperty(modelNode, ContentModel.PROP_MODEL_VERSION)); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { public Object execute() throws Exception @@ -268,7 +749,6 @@ public class DictionaryModelTypeTest extends BaseAlfrescoSpringTest return null; } }); - } public void testIsActiveFlagAndDelete() @@ -371,8 +851,16 @@ public class DictionaryModelTypeTest extends BaseAlfrescoSpringTest // The model should now be loaded assertNotNull(DictionaryModelTypeTest.this.dictionaryService.getModel(TEST_MODEL_ONE)); + // Delete the model DictionaryModelTypeTest.this.nodeService.deleteNode(modelNode); - + return null; + } + }); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { // The model should not be loaded try { diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java b/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java index c6209bc768..2d2862907a 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java @@ -19,6 +19,7 @@ package org.alfresco.repo.dictionary; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -31,6 +32,8 @@ import org.alfresco.repo.i18n.MessageDeployer; import org.alfresco.repo.i18n.MessageService; import org.alfresco.repo.tenant.TenantAdminService; import org.alfresco.repo.tenant.TenantDeployer; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentReader; @@ -42,10 +45,11 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.service.transaction.TransactionService; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; +import org.springframework.extensions.surf.util.Pair; /** * Bootstrap the dictionary from specified locations within the repository @@ -55,8 +59,7 @@ import org.springframework.context.ApplicationEvent; public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean implements TenantDeployer, DictionaryListener, MessageDeployer { // Logging support - private static Log logger = LogFactory - .getLog("org.alfresco.repo.dictionary.DictionaryRepositoryBootstrap"); + private static Log logger = LogFactory.getLog(DictionaryRepositoryBootstrap.class); /** Locations in the repository from which models should be loaded */ private List repositoryModelsLocations = new ArrayList(); @@ -206,9 +209,16 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme */ public void onDictionaryInit() { + long startTime = System.currentTimeMillis(); + + Collection modelsBefore = dictionaryDAO.getModels(); + int modelsBeforeCnt = (modelsBefore != null ? modelsBefore.size() : 0); + + List loadedModels = new ArrayList(); + if (this.repositoryModelsLocations != null) { - Map modelMap = new HashMap(); + Map> modelMap = new HashMap>(); // Register the models found in the repository @@ -249,7 +259,7 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme for (M2Namespace namespace : model.getNamespaces()) { - modelMap.put(namespace.getUri(), model); + modelMap.put(namespace.getUri(), new Pair(repositoryLocation, model)); } } } @@ -264,10 +274,29 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme } // Load the models ensuring that they are loaded in the correct order - List loadedModels = new ArrayList(); - for (Map.Entry entry : modelMap.entrySet()) + for (Map.Entry> entry : modelMap.entrySet()) { - loadModel(modelMap, loadedModels, entry.getValue()); + RepositoryLocation importedLocation = entry.getValue().getFirst(); + M2Model importedModel = entry.getValue().getSecond(); + + loadModel(modelMap, loadedModels, importedModel, importedLocation); + } + } + + Collection modelsAfter = dictionaryDAO.getModels(); + int modelsAfterCnt = (modelsAfter != null ? modelsAfter.size() : 0); + + if (modelsAfterCnt != (modelsBeforeCnt + loadedModels.size())) + { + String tenantDomain = tenantAdminService.getCurrentUserDomain(); + logger.warn("Model count: before="+modelsBeforeCnt+", load="+loadedModels.size()+", after="+modelsAfterCnt+" ["+AlfrescoTransactionSupport.getTransactionId()+"] in "+(System.currentTimeMillis()-startTime)+" msecs "+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")")); + } + else + { + if (logger.isDebugEnabled()) + { + String tenantDomain = tenantAdminService.getCurrentUserDomain(); + logger.debug("Model count: before="+modelsBeforeCnt+", load="+loadedModels.size()+", after="+modelsAfterCnt+" ["+AlfrescoTransactionSupport.getTransactionId()+"] in "+(System.currentTimeMillis()-startTime)+" msecs "+(tenantDomain.equals(TenantService.DEFAULT_DOMAIN) ? "" : " (Tenant: "+tenantDomain+")")); } } } @@ -371,18 +400,21 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme * @param loadedModels the list of models already loaded * @param model the model to try and load */ - private void loadModel(Map modelMap, List loadedModels, M2Model model) + private void loadModel(Map> modelMap, List loadedModels, M2Model model, RepositoryLocation modelLocation) { String modelName = model.getName(); if (loadedModels.contains(modelName) == false) { for (M2Namespace importNamespace : model.getImports()) { - M2Model importedModel = modelMap.get(importNamespace.getUri()); - if (importedModel != null) + Pair entry = modelMap.get(importNamespace.getUri()); + if (entry != null) { + RepositoryLocation importedLocation = entry.getFirst(); + M2Model importedModel = entry.getSecond(); + // Ensure that the imported model is loaded first - loadModel(modelMap, loadedModels, importedModel); + loadModel(modelMap, loadedModels, importedModel, importedLocation); } // else we can assume that the imported model is already loaded, if this not the case then // an error will be raised during compilation @@ -390,6 +422,11 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme try { + if (logger.isDebugEnabled()) + { + logger.debug("Loading model: " + modelName + " (from ["+ modelLocation.getStoreRef() + "]"+ modelLocation.getPath() + ")"); + } + dictionaryDAO.putModel(model); loadedModels.add(modelName); } @@ -398,7 +435,7 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme // note: skip with warning - to allow server to start, and hence allow the possibility of fixing the broken model(s) logger.warn("Failed to load model '" + modelName + "' : " + e); } - } + } } /** @@ -446,11 +483,12 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme */ public void register() { - dictionaryDAO.destroy(); // deployer - force reload on next get + // deployer - force reload on next get (eg. bootstrap "rmc:rmcustom") + dictionaryDAO.destroy(); + + // register with Dictionary Service to allow (re-)init + dictionaryDAO.register(this); - // register with Dictionary Service to allow (re-)init - dictionaryDAO.register(this); - // register with Message Service to allow (re-)init messageService.register(this); diff --git a/source/java/org/alfresco/repo/dictionary/DiffModelTest.java b/source/java/org/alfresco/repo/dictionary/DiffModelTest.java index f3821b7561..1b95cf5b79 100644 --- a/source/java/org/alfresco/repo/dictionary/DiffModelTest.java +++ b/source/java/org/alfresco/repo/dictionary/DiffModelTest.java @@ -997,30 +997,35 @@ public class DiffModelTest extends TestCase System.out.println(M2ModelDiff.toString()); } - assertEquals(8, modelDiffs.size()); + assertEquals(16, modelDiffs.size()); assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_CREATED)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UNCHANGED)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UPDATED)); + assertEquals(2, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(0, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UPDATED)); assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_DELETED)); assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_CREATED)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UPDATED)); + assertEquals(2, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(0, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UPDATED)); assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_DELETED)); + + assertEquals(0, countDiffs(modelDiffs, M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_CREATED)); + assertEquals(6, countDiffs(modelDiffs, M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_UPDATED)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_DELETED)); } public void testIncUpdatePropertiesAdded() { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL2_XML.getBytes()); M2Model model = M2Model.createModel(byteArrayInputStream); - QName modelName = dictionaryDAO.putModel(model); + QName modelName = dictionaryDAO.putModel(model); CompiledModel previousVersion = dictionaryDAO.getCompiledModel(modelName); byteArrayInputStream = new ByteArrayInputStream(MODEL2_EXTRA_PROPERTIES_XML.getBytes()); model = M2Model.createModel(byteArrayInputStream); modelName = dictionaryDAO.putModel(model); - CompiledModel newVersion = dictionaryDAO.getCompiledModel(modelName); + CompiledModel newVersion = dictionaryDAO.getCompiledModel(modelName); List modelDiffs = dictionaryDAO.diffModel(previousVersion, newVersion); @@ -1029,10 +1034,12 @@ public class DiffModelTest extends TestCase System.out.println(modelDiff.toString()); } - assertEquals(2, modelDiffs.size()); + assertEquals(8, modelDiffs.size()); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UPDATED_INC)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UPDATED_INC)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(4, countDiffs(modelDiffs, M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(2, countDiffs(modelDiffs, M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_CREATED)); } public void testIncUpdateTypesAndAspectsAdded() @@ -1045,7 +1052,7 @@ public class DiffModelTest extends TestCase byteArrayInputStream = new ByteArrayInputStream(MODEL3_EXTRA_TYPES_AND_ASPECTS_XML.getBytes()); model = M2Model.createModel(byteArrayInputStream); modelName = dictionaryDAO.putModel(model); - CompiledModel newVersion = dictionaryDAO.getCompiledModel(modelName); + CompiledModel newVersion = dictionaryDAO.getCompiledModel(modelName); List modelDiffs = dictionaryDAO.diffModel(previousVersion, newVersion); @@ -1054,49 +1061,53 @@ public class DiffModelTest extends TestCase System.out.println(modelDiff.toString()); } - assertEquals(4, modelDiffs.size()); + assertEquals(8, modelDiffs.size()); assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UNCHANGED)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_CREATED)); assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_CREATED)); + + assertEquals(4, countDiffs(modelDiffs, M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_UNCHANGED)); } public void testIncUpdateAssociationsAdded() { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL5_XML.getBytes()); M2Model model = M2Model.createModel(byteArrayInputStream); - QName modelName = dictionaryDAO.putModel(model); + QName modelName = dictionaryDAO.putModel(model); CompiledModel previousVersion = dictionaryDAO.getCompiledModel(modelName); byteArrayInputStream = new ByteArrayInputStream(MODEL5_EXTRA_ASSOCIATIONS_XML.getBytes()); model = M2Model.createModel(byteArrayInputStream); modelName = dictionaryDAO.putModel(model); - CompiledModel newVersion = dictionaryDAO.getCompiledModel(modelName); + CompiledModel newVersion = dictionaryDAO.getCompiledModel(modelName); List modelDiffs = dictionaryDAO.diffModel(previousVersion, newVersion); for (M2ModelDiff modelDiff : modelDiffs) { System.out.println(modelDiff.toString()); - } + } - assertEquals(4, modelDiffs.size()); + assertEquals(12, modelDiffs.size()); assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UPDATED_INC)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UPDATED_INC)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UNCHANGED)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); - + + assertEquals(2, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); + + assertEquals(6, countDiffs(modelDiffs, M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_UNCHANGED)); + + assertEquals(2, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASSOCIATION, M2ModelDiff.DIFF_CREATED)); } public void testIncUpdateTitleDescription() { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL6_XML.getBytes()); M2Model model = M2Model.createModel(byteArrayInputStream); - QName modelName = dictionaryDAO.putModel(model); + QName modelName = dictionaryDAO.putModel(model); CompiledModel previousVersion = dictionaryDAO.getCompiledModel(modelName); byteArrayInputStream = new ByteArrayInputStream(MODEL6_UPDATE1_XML.getBytes()); @@ -1109,12 +1120,13 @@ public class DiffModelTest extends TestCase for (M2ModelDiff modelDiff : modelDiffs) { System.out.println(modelDiff.toString()); - } + } - assertEquals(2, modelDiffs.size()); + assertEquals(4, modelDiffs.size()); assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UPDATED_INC)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UPDATED_INC)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(2, countDiffs(modelDiffs, M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_UPDATED_INC)); } public void testNonIncUpdatePropertiesRemoved() @@ -1127,7 +1139,7 @@ public class DiffModelTest extends TestCase byteArrayInputStream = new ByteArrayInputStream(MODEL2_XML.getBytes()); model = M2Model.createModel(byteArrayInputStream); modelName = dictionaryDAO.putModel(model); - CompiledModel newVersion = dictionaryDAO.getCompiledModel(modelName); + CompiledModel newVersion = dictionaryDAO.getCompiledModel(modelName); List modelDiffs = dictionaryDAO.diffModel(previousVersion, newVersion); @@ -1136,91 +1148,99 @@ public class DiffModelTest extends TestCase System.out.println(modelDiff.toString()); } - assertEquals(2, modelDiffs.size()); + assertEquals(8, modelDiffs.size()); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UPDATED)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UPDATED)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(4, countDiffs(modelDiffs, M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(2, countDiffs(modelDiffs, M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_DELETED)); } public void testNonIncUpdateTypesAndAspectsRemoved() { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL3_EXTRA_TYPES_AND_ASPECTS_XML.getBytes()); M2Model model = M2Model.createModel(byteArrayInputStream); - QName modelName = dictionaryDAO.putModel(model); + QName modelName = dictionaryDAO.putModel(model); CompiledModel previousVersion = dictionaryDAO.getCompiledModel(modelName); byteArrayInputStream = new ByteArrayInputStream(MODEL3_XML.getBytes()); model = M2Model.createModel(byteArrayInputStream); modelName = dictionaryDAO.putModel(model); - CompiledModel newVersion = dictionaryDAO.getCompiledModel(modelName); + CompiledModel newVersion = dictionaryDAO.getCompiledModel(modelName); List modelDiffs = dictionaryDAO.diffModel(previousVersion, newVersion); for (M2ModelDiff modelDiff : modelDiffs) { System.out.println(modelDiff.toString()); - } + } - assertEquals(4, modelDiffs.size()); + assertEquals(8, modelDiffs.size()); assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UNCHANGED)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_DELETED)); assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_DELETED)); + + assertEquals(4, countDiffs(modelDiffs, M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_UNCHANGED)); } public void testNonIncUpdateDefaultAspectAdded() { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL4_XML.getBytes()); M2Model model = M2Model.createModel(byteArrayInputStream); - QName modelName = dictionaryDAO.putModel(model); + QName modelName = dictionaryDAO.putModel(model); CompiledModel previousVersion = dictionaryDAO.getCompiledModel(modelName); byteArrayInputStream = new ByteArrayInputStream(MODEL4_EXTRA_DEFAULT_ASPECT_XML.getBytes()); model = M2Model.createModel(byteArrayInputStream); modelName = dictionaryDAO.putModel(model); - CompiledModel newVersion = dictionaryDAO.getCompiledModel(modelName); + CompiledModel newVersion = dictionaryDAO.getCompiledModel(modelName); List modelDiffs = dictionaryDAO.diffModel(previousVersion, newVersion); for (M2ModelDiff modelDiff : modelDiffs) { System.out.println(modelDiff.toString()); - } + } - assertEquals(2, modelDiffs.size()); + assertEquals(4, modelDiffs.size()); assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UPDATED)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(2, countDiffs(modelDiffs, M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_UNCHANGED)); } public void testNonIncUpdateAssociationsRemoved() { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL5_EXTRA_ASSOCIATIONS_XML.getBytes()); M2Model model = M2Model.createModel(byteArrayInputStream); - QName modelName = dictionaryDAO.putModel(model); + QName modelName = dictionaryDAO.putModel(model); CompiledModel previousVersion = dictionaryDAO.getCompiledModel(modelName); byteArrayInputStream = new ByteArrayInputStream(MODEL5_XML.getBytes()); model = M2Model.createModel(byteArrayInputStream); modelName = dictionaryDAO.putModel(model); - CompiledModel newVersion = dictionaryDAO.getCompiledModel(modelName); + CompiledModel newVersion = dictionaryDAO.getCompiledModel(modelName); List modelDiffs = dictionaryDAO.diffModel(previousVersion, newVersion); for (M2ModelDiff modelDiff : modelDiffs) { System.out.println(modelDiff.toString()); - } + } - assertEquals(4, modelDiffs.size()); + assertEquals(12, modelDiffs.size()); assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UPDATED)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UPDATED)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UNCHANGED)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UNCHANGED)); - assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(2, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); + + assertEquals(6, countDiffs(modelDiffs, M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_UNCHANGED)); + + assertEquals(2, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASSOCIATION, M2ModelDiff.DIFF_DELETED)); } private int countDiffs(List M2ModelDiffs, String elementType, String diffType) diff --git a/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java b/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java index 48c5e5f994..f4915d6e1c 100644 --- a/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java @@ -570,38 +570,12 @@ import org.alfresco.util.EqualsHelper; // check all properties (including inherited properties) Collection propertyDiffs = M2PropertyDefinition.diffPropertyLists(getProperties().values(), classDef.getProperties().values()); - for (M2ModelDiff propertyDiff : propertyDiffs) - { - // note: incremental property updates not supported yet, added for completeness - if (propertyDiff.getDiffType().equals(M2ModelDiff.DIFF_CREATED) || propertyDiff.getDiffType().equals(M2ModelDiff.DIFF_UPDATED_INC)) - { - isUpdatedIncrementally = true; - } - - if (propertyDiff.getDiffType().equals(M2ModelDiff.DIFF_UPDATED) || propertyDiff.getDiffType().equals(M2ModelDiff.DIFF_DELETED)) - { - isUpdated = true; - break; - } - } + modelDiffs.addAll(propertyDiffs); // check all associations (including inherited associations, child associations and inherited child associations) Collection assocDiffs = M2AssociationDefinition.diffAssocLists(getAssociations().values(), classDef.getAssociations().values()); - - for (M2ModelDiff assocDiff : assocDiffs) - { - // note: incremental association updates not supported yet, added for completeness - if (assocDiff.getDiffType().equals(M2ModelDiff.DIFF_CREATED) || assocDiff.getDiffType().equals(M2ModelDiff.DIFF_UPDATED_INC)) - { - isUpdatedIncrementally = true; - } - - if (assocDiff.getDiffType().equals(M2ModelDiff.DIFF_UPDATED) || assocDiff.getDiffType().equals(M2ModelDiff.DIFF_DELETED)) - { - isUpdated = true; - break; - } - } + + modelDiffs.addAll(assocDiffs); // check default/mandatory aspects (including inherited default aspects) Collection defaultAspectsDiffs = M2ClassDefinition.diffClassLists(new ArrayList(getDefaultAspects()), new ArrayList(classDef.getDefaultAspects()), M2ModelDiff.TYPE_DEFAULT_ASPECT); @@ -676,16 +650,9 @@ import org.alfresco.util.EqualsHelper; } else { - for (M2ModelDiff modelDiff : modelDiffs) - { - if (! modelDiff.getDiffType().equals(M2ModelDiff.DIFF_UNCHANGED)) - { - throw new DictionaryException("Unexpected: diff found although '" + name + "' not marked as updated"); - } - } modelDiffs.add(new M2ModelDiff(name, modelDiffType, M2ModelDiff.DIFF_UNCHANGED)); } - + return modelDiffs; } diff --git a/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java b/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java index 11db36260e..41bcd8507e 100644 --- a/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java @@ -18,6 +18,8 @@ */ package org.alfresco.repo.dictionary; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint; @@ -31,6 +33,7 @@ import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.dictionary.ModelDefinition; import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.QName; +import org.alfresco.util.EqualsHelper; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.InvalidPropertyException; @@ -39,7 +42,7 @@ import org.springframework.beans.PropertyAccessException; /** * Compiled Property Constraint * - * @author Derek Hulley + * @author Derek Hulley. janv */ /* package */class M2ConstraintDefinition implements ConstraintDefinition { @@ -319,7 +322,18 @@ import org.springframework.beans.PropertyAccessException; { return constraint; } - + + public QName getRef() + { + QName refQName = null; + String ref = m2Constraint.getRef(); + if (ref != null) + { + refQName = QName.createQName(ref, prefixResolver); + } + return refQName; + } + /** * Well-known constraint types */ @@ -365,10 +379,106 @@ import org.springframework.beans.PropertyAccessException; return new ListOfValuesConstraint(); } }; - + /** * @return Returns the constraint implementation */ protected abstract Constraint newInstance(); } + + /* package */ M2ModelDiff diffConstraint(ConstraintDefinition conDef) + { + M2ModelDiff modelDiff = null; + boolean isUpdated = false; + boolean isUpdatedIncrementally = false; + + if (this == conDef) + { + modelDiff = new M2ModelDiff(name, M2ModelDiff.TYPE_CONSTRAINT, M2ModelDiff.DIFF_UNCHANGED); + return modelDiff; + } + + // check name - cannot be null + if (! name.equals(conDef.getName())) + { + isUpdated = true; + } + + // check title + if (! EqualsHelper.nullSafeEquals(getTitle(), conDef.getTitle(), false)) + { + isUpdatedIncrementally = true; + } + + // check description + if (! EqualsHelper.nullSafeEquals(getDescription(), conDef.getDescription(), false)) + { + isUpdatedIncrementally = true; + } + + // check type string + if (! EqualsHelper.nullSafeEquals(getConstraint().getType(), conDef.getConstraint().getType())) + { + isUpdated = true; + } + + if (isUpdated) + { + modelDiff = new M2ModelDiff(name, M2ModelDiff.TYPE_CONSTRAINT, M2ModelDiff.DIFF_UPDATED); + } + else if (isUpdatedIncrementally) + { + modelDiff = new M2ModelDiff(name, M2ModelDiff.TYPE_CONSTRAINT, M2ModelDiff.DIFF_UPDATED_INC); + } + else + { + modelDiff = new M2ModelDiff(name, M2ModelDiff.TYPE_CONSTRAINT, M2ModelDiff.DIFF_UNCHANGED); + } + + return modelDiff; + } + + /*package*/ static Collection diffConstraintLists(Collection previousConstraints, Collection newConstraints) + { + List modelDiffs = new ArrayList(); + + for (ConstraintDefinition previousConstraint : previousConstraints) + { + boolean found = false; + for (ConstraintDefinition newConstraint : newConstraints) + { + if (newConstraint.getName().equals(previousConstraint.getName())) + { + modelDiffs.add(((M2ConstraintDefinition)previousConstraint).diffConstraint(previousConstraint)); + found = true; + break; + } + } + + if (! found) + { + modelDiffs.add(new M2ModelDiff(previousConstraint.getName(), M2ModelDiff.TYPE_CONSTRAINT, M2ModelDiff.DIFF_DELETED)); + } + } + + for (ConstraintDefinition newConstraint : newConstraints) + { + boolean found = false; + for (ConstraintDefinition previousConstraint : previousConstraints) + { + if (newConstraint.getName().equals(previousConstraint.getName())) + { + found = true; + break; + } + } + + if (! found) + { + modelDiffs.add(new M2ModelDiff(newConstraint.getName(), M2ModelDiff.TYPE_CONSTRAINT, M2ModelDiff.DIFF_CREATED)); + } + } + + return modelDiffs; + } } diff --git a/source/java/org/alfresco/repo/dictionary/M2ModelDiff.java b/source/java/org/alfresco/repo/dictionary/M2ModelDiff.java index 856b7da907..1f3a1036e0 100644 --- a/source/java/org/alfresco/repo/dictionary/M2ModelDiff.java +++ b/source/java/org/alfresco/repo/dictionary/M2ModelDiff.java @@ -41,6 +41,7 @@ public class M2ModelDiff public static final String TYPE_DEFAULT_ASPECT = "DEFAULT_ASPECT"; public static final String TYPE_PROPERTY = "PROPERTY"; public static final String TYPE_ASSOCIATION = "ASSOCIATION"; + public static final String TYPE_CONSTRAINT = "TYPE_CONSTRAINT"; private QName elementName; private String elementType; @@ -53,11 +54,13 @@ public class M2ModelDiff ParameterCheck.mandatoryString("elementType", elementType); ParameterCheck.mandatoryString("diffType", diffType); - if ((!elementType.equals(TYPE_TYPE)) && - (!elementType.equals(TYPE_ASPECT)) && - (!elementType.equals(TYPE_DEFAULT_ASPECT)) && + if ((!elementType.equals(TYPE_TYPE)) && + (!elementType.equals(TYPE_ASPECT)) && + (!elementType.equals(TYPE_DEFAULT_ASPECT)) && (!elementType.equals(TYPE_PROPERTY)) && - (!elementType.equals(TYPE_ASSOCIATION))) + (!elementType.equals(TYPE_ASSOCIATION)) && + (!elementType.equals(TYPE_CONSTRAINT)) + ) { throw new AlfrescoRuntimeException("Unknown element type = " + elementType); } diff --git a/source/java/org/alfresco/repo/dictionary/NamespaceDAOImpl.java b/source/java/org/alfresco/repo/dictionary/NamespaceDAOImpl.java index 9e0e5e8bf0..c9a4356ba9 100644 --- a/source/java/org/alfresco/repo/dictionary/NamespaceDAOImpl.java +++ b/source/java/org/alfresco/repo/dictionary/NamespaceDAOImpl.java @@ -94,9 +94,9 @@ public class NamespaceDAOImpl implements NamespaceDAO removeNamespaceRegistry(tenantDomain); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("Namespaces destroyed"); + logger.trace("Namespaces destroyed"); } } @@ -111,18 +111,18 @@ public class NamespaceDAOImpl implements NamespaceDAO throw new AlfrescoRuntimeException("Dictionary should be registered in order to perform reset"); } - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("Resetting namespaces ..."); + logger.trace("Resetting namespaces ..."); } dictionaryDAO.init(); NamespaceRegistry namespaceRegistry = getNamespaceRegistry(tenantDomain); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("... resetting namespaces completed"); + logger.trace("... resetting namespaces completed"); } return namespaceRegistry; @@ -176,9 +176,9 @@ public class NamespaceDAOImpl implements NamespaceDAO getNamespaceRegistry(tenantDomain).setUrisCache(new ArrayList()); getNamespaceRegistry(tenantDomain).setPrefixesCache(new HashMap()); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("Empty namespaces initialised"); + logger.trace("Empty namespaces initialised"); } return getNamespaceRegistryLocal(tenantDomain); diff --git a/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java b/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java index e9b6c6079a..68186dae22 100644 --- a/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java +++ b/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java @@ -211,7 +211,7 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest bootstrap.setModels(bootstrapModels); bootstrap.setDictionaryDAO(dictionaryDAO); - bootstrap.register(); + bootstrap.bootstrap(); } /** diff --git a/source/java/org/alfresco/service/cmr/dictionary/ConstraintDefinition.java b/source/java/org/alfresco/service/cmr/dictionary/ConstraintDefinition.java index 7b4169a876..a00887c08c 100644 --- a/source/java/org/alfresco/service/cmr/dictionary/ConstraintDefinition.java +++ b/source/java/org/alfresco/service/cmr/dictionary/ConstraintDefinition.java @@ -51,4 +51,11 @@ public interface ConstraintDefinition * @return Returns the constraint implementation */ public Constraint getConstraint(); + + /** + * @return Returns the referenced constraint definition, if any (null for explicit or inline constraint def) + * + * @since 3.2R + */ + public QName getRef(); } diff --git a/source/java/org/alfresco/service/cmr/dictionary/DictionaryService.java b/source/java/org/alfresco/service/cmr/dictionary/DictionaryService.java index e1bf5ecd9d..69440a5e04 100644 --- a/source/java/org/alfresco/service/cmr/dictionary/DictionaryService.java +++ b/source/java/org/alfresco/service/cmr/dictionary/DictionaryService.java @@ -19,7 +19,6 @@ package org.alfresco.service.cmr.dictionary; import java.util.Collection; -import java.util.List; import java.util.Map; import org.alfresco.service.NotAuditable; @@ -258,6 +257,17 @@ public interface DictionaryService Collection getAllAssociations(); /** + * Gets the definition of the constraint + * + * @param constraintName the constraint name + * @return the constraint definition (or null, if it doesn't exist) + * + * @since 3.2.1 + */ + @NotAuditable + public ConstraintDefinition getConstraint(QName constraintQName); + + /** * Get constraints for the specified model * * @param model @@ -265,6 +275,19 @@ public interface DictionaryService */ public Collection getConstraints(QName model); + /** + * Get constraints for the specified model + + * Optionally return referenceable (ie. non-property specific) constraints only + * + * @param model + * @param referenceableDefsOnly + * @return + * + * @since 3.2R + */ + Collection getConstraints(QName model, boolean referenceableDefsOnly); + // TODO: Behaviour definitions }