From a03df08f5e74ff7bae1e64b2907cbcbf5a51e603 Mon Sep 17 00:00:00 2001 From: Jan Vonka Date: Fri, 7 Sep 2007 11:32:51 +0000 Subject: [PATCH] First-cut validation of dynamic model changes - in case of re-deploy (dynamic update) check for incremental updates only - in case of undeploy (dynamic delete) check for type/aspect usages (in content and workflows) git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@6698 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/repo-admin-context.xml | 8 + .../repo/admin/RepoAdminServiceImpl.java | 130 +- .../repo/dictionary/DictionaryDAO.java | 31 +- .../repo/dictionary/DictionaryDAOImpl.java | 168 +++ .../repo/dictionary/DiffModelTest.java | 1111 +++++++++++++++++ .../dictionary/M2AssociationDefinition.java | 57 + .../repo/dictionary/M2ClassDefinition.java | 203 +++ .../alfresco/repo/dictionary/M2ModelDiff.java | 104 ++ .../dictionary/M2NamespaceDefinition.java | 64 + .../repo/dictionary/M2PropertyDefinition.java | 76 +- .../cmr/dictionary/NamespaceDefinition.java | 48 + 11 files changed, 1980 insertions(+), 20 deletions(-) create mode 100755 source/java/org/alfresco/repo/dictionary/DiffModelTest.java create mode 100755 source/java/org/alfresco/repo/dictionary/M2ModelDiff.java create mode 100755 source/java/org/alfresco/repo/dictionary/M2NamespaceDefinition.java create mode 100755 source/java/org/alfresco/service/cmr/dictionary/NamespaceDefinition.java diff --git a/config/alfresco/repo-admin-context.xml b/config/alfresco/repo-admin-context.xml index 1233dc614d..73b29d4755 100755 --- a/config/alfresco/repo-admin-context.xml +++ b/config/alfresco/repo-admin-context.xml @@ -11,10 +11,18 @@ + + + + ${spaces.store} + ${spaces.archive.store} + + + diff --git a/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java b/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java index a4e169d378..1f41027b8a 100755 --- a/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java @@ -43,7 +43,12 @@ import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.M2Model; import org.alfresco.repo.dictionary.RepositoryLocation; import org.alfresco.repo.i18n.MessageService; +import org.alfresco.repo.workflow.BPMEngineRegistry; import org.alfresco.service.cmr.admin.RepoAdminService; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.NamespaceDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; @@ -51,7 +56,11 @@ 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.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +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.util.ParameterCheck; @@ -79,10 +88,13 @@ public class RepoAdminServiceImpl implements RepoAdminService private ContentService contentService; private NamespaceService namespaceService; private MessageService messageService; + private WorkflowService workflowService; private RepositoryLocation repoModelsLocation; private RepositoryLocation repoMessagesLocation; + private List storeUrls; // stores against which model changes (updates/deletes) should be validated + public final static String CRITERIA_ALL = "/*"; // immediate children only public final static String defaultSubtypeOfDictionaryModel = "subtypeOf('cm:dictionaryModel')"; @@ -114,10 +126,15 @@ public class RepoAdminServiceImpl implements RepoAdminService this.namespaceService = namespaceService; } - public void setmessageService(MessageService messageService) + public void setMessageService(MessageService messageService) { this.messageService = messageService; } + + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } public void setRepositoryModelsLocation(RepositoryLocation repoModelsLocation) @@ -130,6 +147,11 @@ public class RepoAdminServiceImpl implements RepoAdminService this.repoMessagesLocation = repoMessagesLocation; } + public void setStoreUrls(List storeUrls) + { + this.storeUrls = storeUrls; + } + /* * (non-Javadoc) @@ -202,7 +224,7 @@ public class RepoAdminServiceImpl implements RepoAdminService ParameterCheck.mandatory("ModelStream", modelStream); ParameterCheck.mandatoryString("ModelFileName", modelFileName); - QName modelName = null; + QName modelQName = null; try { @@ -262,10 +284,13 @@ public class RepoAdminServiceImpl implements RepoAdminService writer.putContent(out.toString("UTF-8")); */ - // parse and update model in the dictionary - modelName = dictionaryDAO.putModel(model); + // validate model against dictionary - could be new, unchanged or updated + dictionaryDAO.validateModel(model); - logger.info("Model re-deployed: " + modelName); + // parse and update model in the dictionary + modelQName = dictionaryDAO.putModel(model); + + logger.info("Model re-deployed: " + modelQName); } else { @@ -305,11 +330,14 @@ public class RepoAdminServiceImpl implements RepoAdminService model.toXML(out); // fails with NPE in JIBX - see also: http://issues.alfresco.com/browse/AR-1304 writer.putContent(out.toString("UTF-8")); */ + + // validate model against dictionary - could be new, unchanged or updated + dictionaryDAO.validateModel(model); // parse and add model to dictionary - modelName = dictionaryDAO.putModel(model); + modelQName = dictionaryDAO.putModel(model); - logger.info("Model deployed: " + modelName); + logger.info("Model deployed: " + modelQName); } } catch (Throwable e) @@ -317,7 +345,7 @@ public class RepoAdminServiceImpl implements RepoAdminService throw new AlfrescoRuntimeException("Model deployment failed", e); } - return modelName; + return modelQName; } /* @@ -359,12 +387,13 @@ public class RepoAdminServiceImpl implements RepoAdminService if (model != null) { - String modelName = model.getName(); - + // validate model against dictionary - could be new, unchanged or updated + dictionaryDAO.validateModel(model); + // parse and update model in the dictionary - modelQName = dictionaryDAO.putModel(model); - - logger.info("Model loaded: " + modelName); + modelQName = dictionaryDAO.putModel(model); + + logger.info("Model loaded: " + modelQName); } } catch (Throwable e) @@ -430,9 +459,12 @@ public class RepoAdminServiceImpl implements RepoAdminService modelQName = QName.createQName(modelName, namespaceService); + // simple check for usages + validateModelDelete(modelQName); + dictionaryDAO.removeModel(modelQName); - logger.info("Model undeployed: " + modelFileName); + logger.info("Model undeployed: " + modelQName); } catch (Throwable e) { @@ -738,4 +770,74 @@ public class RepoAdminServiceImpl implements RepoAdminService throw new AlfrescoRuntimeException("Message resource re-load failed", e); } } + + /** + * validate against repository contents / workflows (e.g. when deleting an existing model) + * + * @param modelName + */ + private void validateModelDelete(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 (WorkflowDefinition workflowDef : workflowService.getDefinitions()) + { + String workflowDefName = workflowDef.getName(); + String workflowNamespaceURI = QName.createQName(BPMEngineRegistry.getLocalId(workflowDefName), namespaceService).getNamespaceURI(); + for (NamespaceDefinition namespace : dictionaryDAO.getNamespaces(modelName)) + { + if (workflowNamespaceURI.equals(namespace.getUri())) + { + throw new AlfrescoRuntimeException("Failed to validate model delete - found workflow process definition " + workflowDefName + " using model namespace '" + namespace.getUri() + "'"); + } + } + } + + for (TypeDefinition type : dictionaryDAO.getTypes(modelName)) + { + validateClass(type); + } + + for (AspectDefinition aspect : dictionaryDAO.getAspects(modelName)) + { + validateClass(aspect); + } + } + + private void validateClass(ClassDefinition classDef) + { + QName className = classDef.getName(); + + String classType = "TYPE"; + if (classDef instanceof AspectDefinition) + { + classType = "ASPECT"; + } + + for (String storeUrl : this.storeUrls) + { + StoreRef store = new StoreRef(storeUrl); + + // search for TYPE or ASPECT - TODO - alternative would be to extract QName and search by namespace ... + ResultSet rs = searchService.query(store, SearchService.LANGUAGE_LUCENE, classType+":\""+className+"\""); + if (rs.length() > 0) + { + throw new AlfrescoRuntimeException("Failed to validate model delete - found " + rs.length() + " nodes in store " + store + " with " + classType + " '" + className + "'"); + } + } + + // check against workflow usage + for (WorkflowDefinition workflowDef : workflowService.getDefinitions()) + { + for (WorkflowTaskDefinition workflowTaskDef : workflowService.getTaskDefinitions(workflowDef.getId())) + { + TypeDefinition workflowTypeDef = workflowTaskDef.metadata; + if (workflowTypeDef.getName().toString().equals(className)) + { + throw new AlfrescoRuntimeException("Failed to validate model delete - found task definition in workflow " + workflowDef.getName() + " with " + classType + " '" + className + "'"); + } + } + } + } } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAO.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAO.java index 0166c0c580..3e2edbfebd 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAO.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAO.java @@ -29,6 +29,7 @@ import java.util.Collection; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.ModelDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +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; @@ -70,12 +71,10 @@ public interface DictionaryDAO extends ModelQuery * @return the aspects of the model */ public Collection getAspects(QName model); - - + /** - * - * @param model the model for which to get properties - * @return + * @param model the model for which to get properties for + * @return the properties of the model */ public Collection getProperties(QName model); @@ -114,6 +113,28 @@ public interface DictionaryDAO extends ModelQuery * @return */ public Collection getProperties(QName modelName, QName dataType); + + /** + * @param model the model to retrieve namespaces for + * @return the namespaces of the model + */ + public Collection getNamespaces(QName modelName); + + /** + * 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); /** * diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java index 4ad0dbe721..4d7e50396e 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java @@ -47,9 +47,11 @@ import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; 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.namespace.QName; +import org.alfresco.util.ParameterCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -449,6 +451,7 @@ public class DictionaryDAOImpl implements DictionaryDAO /* (non-Javadoc) * @see org.alfresco.repo.dictionary.ModelQuery#getDataType(java.lang.Class) */ + @SuppressWarnings("unchecked") public DataTypeDefinition getDataType(Class javaClass) { if (tenantService.isTenantUser() == true) @@ -758,6 +761,24 @@ public class DictionaryDAOImpl implements DictionaryDAO return properties; } + /* + * (non-Javadoc) + * @see org.alfresco.repo.dictionary.DictionaryDAO#getNamespaces(org.alfresco.service.namespace.QName) + */ + public Collection getNamespaces(QName modelName) + { + CompiledModel model = getCompiledModel(modelName); + ModelDefinition modelDef = model.getModelDefinition(); + + List namespaces = new ArrayList(); + for (M2Namespace namespace : model.getM2Model().getNamespaces()) + { + namespaces.add(new M2NamespaceDefinition(modelDef, namespace.getUri(), namespace.getPrefix())); + } + + return namespaces; + } + /** * Get compiledModels from the cache (in the context of the current user's tenant domain) * @@ -950,4 +971,151 @@ public class DictionaryDAOImpl implements DictionaryDAO { return tenantService.getCurrentUserDomain(); } + + /** + * 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. + * + * @param model + * @return model diffs (if any) + */ + private List diffModel(M2Model model) + { + // Compile model definition + CompiledModel compiledModel = model.compile(this, namespaceDAO); + QName modelName = compiledModel.getModelDefinition().getName(); + + CompiledModel previousVersion = getCompiledModels().get(modelName); + if (previousVersion == null) + { + return new ArrayList(0); + } + else + { + return diffModel(previousVersion, compiledModel); + } + } + + /** + * Return diffs between two compiled models. + * + * + * @param model + * @return model diffs (if any) + */ + /* package */ List diffModel(CompiledModel previousVersion, CompiledModel model) + { + List M2ModelDiffs = new ArrayList(); + + if (previousVersion != null) + { + Collection previousTypes = previousVersion.getTypes(); + Collection previousAspects = previousVersion.getAspects(); + + if (model == null) + { + // delete model + for (TypeDefinition previousType : previousTypes) + { + M2ModelDiffs.add(new M2ModelDiff(previousType.getName(), M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_DELETED)); + } + for (AspectDefinition previousAspect : previousAspects) + { + M2ModelDiffs.add(new M2ModelDiff(previousAspect.getName(), M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_DELETED)); + } + } + else + { + // update model + Collection types = model.getTypes(); + Collection aspects = model.getAspects(); + + if (previousTypes.size() != 0) + { + M2ModelDiffs.addAll(M2ClassDefinition.diffClassLists(new ArrayList(previousTypes), new ArrayList(types), M2ModelDiff.TYPE_TYPE)); + } + else + { + for (TypeDefinition type : types) + { + M2ModelDiffs.add(new M2ModelDiff(type.getName(), M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_CREATED)); + } + } + + if (previousAspects.size() != 0) + { + M2ModelDiffs.addAll(M2ClassDefinition.diffClassLists(new ArrayList(previousAspects), new ArrayList(aspects), M2ModelDiff.TYPE_ASPECT)); + } + else + { + for (AspectDefinition aspect : aspects) + { + M2ModelDiffs.add(new M2ModelDiff(aspect.getName(), M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_CREATED)); + } + } + } + } + else + { + if (model != null) + { + // new model + Collection types = model.getTypes(); + Collection aspects = model.getAspects(); + + for (TypeDefinition type : types) + { + M2ModelDiffs.add(new M2ModelDiff(type.getName(), M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_CREATED)); + } + + for (AspectDefinition aspect : aspects) + { + M2ModelDiffs.add(new M2ModelDiff(aspect.getName(), M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_CREATED)); + } + } + else + { + // nothing to diff + } + } + + 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() + "'"); + } + } + } } diff --git a/source/java/org/alfresco/repo/dictionary/DiffModelTest.java b/source/java/org/alfresco/repo/dictionary/DiffModelTest.java new file mode 100755 index 0000000000..538b47b5d8 --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/DiffModelTest.java @@ -0,0 +1,1111 @@ +package org.alfresco.repo.dictionary; + +import java.io.ByteArrayInputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; +import net.sf.ehcache.Cache; +import net.sf.ehcache.CacheManager; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.cache.EhCacheAdapter; +import org.alfresco.repo.dictionary.M2ModelDiff; +import org.alfresco.repo.tenant.SingleTServiceImpl; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.namespace.QName; + +public class DiffModelTest extends TestCase +{ + public static final String MODEL1_XML = + "" + + + " Another description" + + " Alfresco" + + " 2007-08-01" + + " 1.0" + + + " " + + " " + + " " + + + " " + + " " + + " " + + + " " + + + " " + + " Base" + + " The Base Type 1" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + " Base" + + " The Base Type 2" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + " Base" + + " The Base Type 3" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + + " " + + + " " + + " Base" + + " The Base Aspect 1" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + " Base" + + " The Base Aspect 2" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + " Base" + + " The Base Aspect 3" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + + ""; + + public static final String MODEL1_UPDATE1_XML = + "" + + + " Another description" + + " Alfresco" + + " 2007-08-01" + + " 1.0" + + + " " + + " " + + " " + + + " " + + " " + + " " + + + " " + + + " " + + " Base" + + " The Base Type 1" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + " Base" + + " The Base Type 3" + + " " + + " " + + " d:text" + + " " + + " " + + " " + + + " " + + " Base" + + " The Base Type 4" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + + " " + + + " " + + " Base" + + " The Base Aspect 1" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + " Base" + + " The Base Aspect 3" + + " " + + " " + + " d:int" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + " Base" + + " The Base Aspect 4" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + + ""; + + public static final String MODEL2_XML = + "" + + + " Another description" + + " Alfresco" + + " 2007-08-01" + + " 1.0" + + + " " + + " " + + " " + + + " " + + " " + + " " + + + " " + + + " " + + " Base" + + " The Base Type 1" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + + " " + + + " " + + " Base" + + " The Base Aspect 1" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + + ""; + + public static final String MODEL2_EXTRA_PROPERTIES_XML = + "" + + + " Another description" + + " Alfresco" + + " 2007-08-01" + + " 1.0" + + + " " + + " " + + " " + + + " " + + " " + + " " + + + " " + + + " " + + " Base" + + " The Base Type 1" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " d:date" + + " " + + " " + + " " + + + " " + + + " " + + + " " + + " Base" + + " The Base Aspect 1" + + " " + + " " + + " d:boolean" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + + ""; + + public static final String MODEL3_XML = + "" + + + " Another description" + + " Alfresco" + + " 2007-08-01" + + " 1.0" + + + " " + + " " + + " " + + + " " + + " " + + " " + + + " " + + + " " + + " Base" + + " The Base Type 1" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + + " " + + + " " + + " Base" + + " The Base Aspect 1" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + + ""; + + public static final String MODEL3_EXTRA_TYPES_AND_ASPECTS_XML = + "" + + + " Another description" + + " Alfresco" + + " 2007-08-01" + + " 1.0" + + + " " + + " " + + " " + + + " " + + " " + + " " + + + " " + + + " " + + " Base" + + " The Base Type 1" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + " Base" + + " The Base Type 2" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + + " " + + + " " + + " Base" + + " The Base Aspect 1" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + " Base" + + " The Base Aspect 2" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + + ""; + + public static final String MODEL4_XML = + "" + + + " Another description" + + " Alfresco" + + " 2007-08-01" + + " 1.0" + + + " " + + " " + + " " + + + " " + + " " + + " " + + + " " + + + " " + + " Base" + + " The Base Type 1" + + " " + + " " + + " d:text" + + " " + + " " + + " " + + + " " + + + " " + + + " " + + " Base" + + " The Base Aspect 1" + + " " + + " " + + " d:text" + + " " + + " " + + " " + + + " " + + + ""; + + public static final String MODEL4_EXTRA_DEFAULT_ASPECT_XML = + "" + + + " Another description" + + " Alfresco" + + " 2007-08-01" + + " 1.0" + + + " " + + " " + + " " + + + " " + + " " + + " " + + + " " + + + " " + + " Base" + + " The Base Type 1" + + " " + + " " + + " d:text" + + " " + + " " + + " " + + " test1:aspect1" + + " " + + " " + + + " " + + + " " + + + " " + + " Base" + + " The Base Aspect 1" + + " " + + " " + + " d:text" + + " " + + " " + + " " + + + " " + + + ""; + + public static final String MODEL5_XML = + "" + + + " Another description" + + " Alfresco" + + " 2007-08-01" + + " 1.0" + + + " " + + " " + + " " + + + " " + + " " + + " " + + + " " + + + " " + + " Base" + + " The Base Type 1" + + " " + + " " + + " d:text" + + " " + + " " + + " " + + + " " + + " Base" + + " The Base Type 2" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + + " " + + + " " + + " Base" + + " The Base Aspect 1" + + " " + + " " + + " d:text" + + " " + + " " + + " " + + + " " + + " Base" + + " The Base Aspect 2" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + + ""; + + public static final String MODEL5_EXTRA_ASSOCIATIONS_XML = + "" + + + " Another description" + + " Alfresco" + + " 2007-08-01" + + " 1.0" + + + " " + + " " + + " " + + + " " + + " " + + " " + + + " " + + + " " + + " Base" + + " The Base Type 1" + + " " + + " " + + " d:text" + + " " + + " " + + " " + + " " + + " " + + " false" + + " false" + + " " + + " " + + " test1:type2" + + " false" + + " false" + + " " + + " " + + " " + + " " + + + " " + + " Base" + + " The Base Type 2" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + + " " + + + " " + + " Base" + + " The Base Aspect 1" + + " " + + " " + + " d:text" + + " " + + " " + + " " + + " " + + " " + + " test1:role1" + + " false" + + " true" + + " " + + " " + + " test1:aspect2" + + " test1:role2" + + " false" + + " true" + + " " + + " " + + " " + + " " + + + " " + + " Base" + + " The Base Aspect 2" + + " " + + " " + + " d:text" + + " " + + " " + + " d:int" + + " " + + " " + + " " + + + " " + + + ""; + + private DictionaryDAOImpl dictionaryDAO; + + /** + * Setup + */ + protected void setUp() throws Exception + { + // Initialise the Dictionary + TenantService tenantService = new SingleTServiceImpl(); + + NamespaceDAOImpl namespaceDAO = new NamespaceDAOImpl(); + namespaceDAO.setTenantService(tenantService); + + initNamespaceCaches(namespaceDAO); + + dictionaryDAO = new DictionaryDAOImpl(namespaceDAO); + dictionaryDAO.setTenantService(tenantService); + + initDictionaryCaches(dictionaryDAO); + + + // include Alfresco dictionary model + List bootstrapModels = new ArrayList(); + bootstrapModels.add("alfresco/model/dictionaryModel.xml"); + + DictionaryBootstrap bootstrap = new DictionaryBootstrap(); + bootstrap.setModels(bootstrapModels); + bootstrap.setDictionaryDAO(dictionaryDAO); + bootstrap.bootstrap(); + } + + private void initDictionaryCaches(DictionaryDAOImpl dictionaryDAO) + { + CacheManager cacheManager = new CacheManager(); + + Cache uriToModelsEhCache = new Cache("uriToModelsCache", 50, false, true, 0L, 0L); + cacheManager.addCache(uriToModelsEhCache); + EhCacheAdapter>> uriToModelsCache = new EhCacheAdapter>>(); + uriToModelsCache.setCache(uriToModelsEhCache); + + dictionaryDAO.setUriToModelsCache(uriToModelsCache); + + Cache compileModelsEhCache = new Cache("compiledModelsCache", 50, false, true, 0L, 0L); + cacheManager.addCache(compileModelsEhCache); + EhCacheAdapter> compileModelCache = new EhCacheAdapter>(); + compileModelCache.setCache(compileModelsEhCache); + + dictionaryDAO.setCompiledModelsCache(compileModelCache); + } + + private void initNamespaceCaches(NamespaceDAOImpl namespaceDAO) + { + CacheManager cacheManager = new CacheManager(); + + Cache urisEhCache = new Cache("urisCache", 50, false, true, 0L, 0L); + cacheManager.addCache(urisEhCache); + EhCacheAdapter> urisCache = new EhCacheAdapter>(); + urisCache.setCache(urisEhCache); + + namespaceDAO.setUrisCache(urisCache); + + Cache prefixesEhCache = new Cache("prefixesCache", 50, false, true, 0L, 0L); + cacheManager.addCache(prefixesEhCache); + EhCacheAdapter> prefixesCache = new EhCacheAdapter>(); + prefixesCache.setCache(prefixesEhCache); + + namespaceDAO.setPrefixesCache(prefixesCache); + } + + public void testDeleteModel() + { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL1_XML.getBytes()); + + M2Model model = M2Model.createModel(byteArrayInputStream); + QName modelName = dictionaryDAO.putModel(model); + + CompiledModel previousVersion = dictionaryDAO.getCompiledModels().get(modelName); + + List modelDiffs = dictionaryDAO.diffModel(previousVersion, null); + + for (M2ModelDiff modelDiff : modelDiffs) + { + System.out.println(modelDiff.toString()); + } + + assertEquals(6, modelDiffs.size()); + + assertEquals(3, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_DELETED)); + assertEquals(3, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_DELETED)); + } + + @SuppressWarnings("unused") + public void testNoExistingModelToDelete() + { + try + { + List modelDiffs = dictionaryDAO.diffModel(null, null); + assertTrue("Should throw exeception that there is no previous version of the model to delete", true); + } + catch (AlfrescoRuntimeException e) + { + assertTrue("Wrong error message", e.getMessage().equals("Invalid arguments - no previous version of model to delete")); + } + + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL1_XML.getBytes()); + + M2Model model = M2Model.createModel(byteArrayInputStream); + QName modelName = dictionaryDAO.putModel(model); + + CompiledModel compiledModel = dictionaryDAO.getCompiledModels().get(modelName); + + try + { + List modelDiffs = dictionaryDAO.diffModel(null, compiledModel); + assertTrue("Should throw exeception that there is no previous version of the model to delete", true); + } + catch (AlfrescoRuntimeException e) + { + assertTrue("Wrong error message", e.getMessage().equals("Invalid arguments - no previous version of model to delete")); + } + } + + public void testNewModel() + { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL1_XML.getBytes()); + + M2Model model = M2Model.createModel(byteArrayInputStream); + QName modelName = dictionaryDAO.putModel(model); + + CompiledModel newVersion = dictionaryDAO.getCompiledModels().get(modelName); + + List modelDiffs = dictionaryDAO.diffModel(null, newVersion); + + for (M2ModelDiff modelDiff : modelDiffs) + { + System.out.println(modelDiff.toString()); + } + + assertEquals(6, modelDiffs.size()); + + assertEquals(3, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_CREATED)); + assertEquals(3, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_CREATED)); + } + + public void testNonIncUpdateModel() + { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL1_XML.getBytes()); + M2Model model = M2Model.createModel(byteArrayInputStream); + QName modelName = dictionaryDAO.putModel(model); + CompiledModel previousVersion = dictionaryDAO.getCompiledModels().get(modelName); + + byteArrayInputStream = new ByteArrayInputStream(MODEL1_UPDATE1_XML.getBytes()); + model = M2Model.createModel(byteArrayInputStream); + modelName = dictionaryDAO.putModel(model); + CompiledModel newVersion = dictionaryDAO.getCompiledModels().get(modelName); + + List modelDiffs = dictionaryDAO.diffModel(previousVersion, newVersion); + + for (M2ModelDiff M2ModelDiff : modelDiffs) + { + System.out.println(M2ModelDiff.toString()); + } + + assertEquals(8, 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(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(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_DELETED)); + } + + public void testIncUpdatePropertiesAdded() + { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL2_XML.getBytes()); + M2Model model = M2Model.createModel(byteArrayInputStream); + QName modelName = dictionaryDAO.putModel(model); + CompiledModel previousVersion = dictionaryDAO.getCompiledModels().get(modelName); + + byteArrayInputStream = new ByteArrayInputStream(MODEL2_EXTRA_PROPERTIES_XML.getBytes()); + model = M2Model.createModel(byteArrayInputStream); + modelName = dictionaryDAO.putModel(model); + CompiledModel newVersion = dictionaryDAO.getCompiledModels().get(modelName); + + List modelDiffs = dictionaryDAO.diffModel(previousVersion, newVersion); + + for (M2ModelDiff modelDiff : modelDiffs) + { + System.out.println(modelDiff.toString()); + } + + assertEquals(2, modelDiffs.size()); + + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UPDATED_INC)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UPDATED_INC)); + } + + public void testIncUpdateTypesAndAspectsAdded() + { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL3_XML.getBytes()); + M2Model model = M2Model.createModel(byteArrayInputStream); + QName modelName = dictionaryDAO.putModel(model); + CompiledModel previousVersion = dictionaryDAO.getCompiledModels().get(modelName); + + byteArrayInputStream = new ByteArrayInputStream(MODEL3_EXTRA_TYPES_AND_ASPECTS_XML.getBytes()); + model = M2Model.createModel(byteArrayInputStream); + modelName = dictionaryDAO.putModel(model); + CompiledModel newVersion = dictionaryDAO.getCompiledModels().get(modelName); + + List modelDiffs = dictionaryDAO.diffModel(previousVersion, newVersion); + + for (M2ModelDiff modelDiff : modelDiffs) + { + System.out.println(modelDiff.toString()); + } + + assertEquals(4, 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_TYPE, M2ModelDiff.DIFF_CREATED)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_CREATED)); + } + + public void testIncUpdateAssociationsAdded() + { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL5_XML.getBytes()); + M2Model model = M2Model.createModel(byteArrayInputStream); + QName modelName = dictionaryDAO.putModel(model); + CompiledModel previousVersion = dictionaryDAO.getCompiledModels().get(modelName); + + byteArrayInputStream = new ByteArrayInputStream(MODEL5_EXTRA_ASSOCIATIONS_XML.getBytes()); + model = M2Model.createModel(byteArrayInputStream); + modelName = dictionaryDAO.putModel(model); + CompiledModel newVersion = dictionaryDAO.getCompiledModels().get(modelName); + + List modelDiffs = dictionaryDAO.diffModel(previousVersion, newVersion); + + for (M2ModelDiff modelDiff : modelDiffs) + { + System.out.println(modelDiff.toString()); + } + + 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_TYPE, M2ModelDiff.DIFF_UNCHANGED)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UNCHANGED)); + + } + + public void testNonIncUpdatePropertiesRemoved() + { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL2_EXTRA_PROPERTIES_XML.getBytes()); + M2Model model = M2Model.createModel(byteArrayInputStream); + QName modelName = dictionaryDAO.putModel(model); + CompiledModel previousVersion = dictionaryDAO.getCompiledModels().get(modelName); + + byteArrayInputStream = new ByteArrayInputStream(MODEL2_XML.getBytes()); + model = M2Model.createModel(byteArrayInputStream); + modelName = dictionaryDAO.putModel(model); + CompiledModel newVersion = dictionaryDAO.getCompiledModels().get(modelName); + + List modelDiffs = dictionaryDAO.diffModel(previousVersion, newVersion); + + for (M2ModelDiff modelDiff : modelDiffs) + { + System.out.println(modelDiff.toString()); + } + + assertEquals(2, modelDiffs.size()); + + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UPDATED)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_UPDATED)); + } + + public void testNonIncUpdateTypesAndAspectsRemoved() + { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL3_EXTRA_TYPES_AND_ASPECTS_XML.getBytes()); + M2Model model = M2Model.createModel(byteArrayInputStream); + QName modelName = dictionaryDAO.putModel(model); + CompiledModel previousVersion = dictionaryDAO.getCompiledModels().get(modelName); + + byteArrayInputStream = new ByteArrayInputStream(MODEL3_XML.getBytes()); + model = M2Model.createModel(byteArrayInputStream); + modelName = dictionaryDAO.putModel(model); + CompiledModel newVersion = dictionaryDAO.getCompiledModels().get(modelName); + + List modelDiffs = dictionaryDAO.diffModel(previousVersion, newVersion); + + for (M2ModelDiff modelDiff : modelDiffs) + { + System.out.println(modelDiff.toString()); + } + + assertEquals(4, 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_TYPE, M2ModelDiff.DIFF_DELETED)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, M2ModelDiff.DIFF_DELETED)); + } + + public void testNonIncUpdateDefaultAspectAdded() + { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(MODEL4_XML.getBytes()); + M2Model model = M2Model.createModel(byteArrayInputStream); + QName modelName = dictionaryDAO.putModel(model); + CompiledModel previousVersion = dictionaryDAO.getCompiledModels().get(modelName); + + byteArrayInputStream = new ByteArrayInputStream(MODEL4_EXTRA_DEFAULT_ASPECT_XML.getBytes()); + model = M2Model.createModel(byteArrayInputStream); + modelName = dictionaryDAO.putModel(model); + CompiledModel newVersion = dictionaryDAO.getCompiledModels().get(modelName); + + List modelDiffs = dictionaryDAO.diffModel(previousVersion, newVersion); + + for (M2ModelDiff modelDiff : modelDiffs) + { + System.out.println(modelDiff.toString()); + } + + assertEquals(2, modelDiffs.size()); + + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_TYPE, M2ModelDiff.DIFF_UPDATED)); + assertEquals(1, countDiffs(modelDiffs, M2ModelDiff.TYPE_ASPECT, 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); + CompiledModel previousVersion = dictionaryDAO.getCompiledModels().get(modelName); + + byteArrayInputStream = new ByteArrayInputStream(MODEL5_XML.getBytes()); + model = M2Model.createModel(byteArrayInputStream); + modelName = dictionaryDAO.putModel(model); + CompiledModel newVersion = dictionaryDAO.getCompiledModels().get(modelName); + + List modelDiffs = dictionaryDAO.diffModel(previousVersion, newVersion); + + for (M2ModelDiff modelDiff : modelDiffs) + { + System.out.println(modelDiff.toString()); + } + + assertEquals(4, 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)); + } + + private int countDiffs(List M2ModelDiffs, String elementType, String diffType) + { + int count = 0; + for (M2ModelDiff modelDiff : M2ModelDiffs) + { + if (modelDiff.getDiffType().equals(diffType) && modelDiff.getElementType().equals(elementType)) + { + count++; + } + } + return count; + } + +} + diff --git a/source/java/org/alfresco/repo/dictionary/M2AssociationDefinition.java b/source/java/org/alfresco/repo/dictionary/M2AssociationDefinition.java index 4df8d382f3..5d142db7c6 100644 --- a/source/java/org/alfresco/repo/dictionary/M2AssociationDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2AssociationDefinition.java @@ -24,6 +24,10 @@ */ package org.alfresco.repo.dictionary; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DictionaryException; @@ -70,6 +74,7 @@ import org.alfresco.service.namespace.QName; @Override public String toString() { + // note: currently used for model 'diffs' StringBuilder sb = new StringBuilder(56); sb.append("Association") .append("[ class=").append(classDef) @@ -245,5 +250,57 @@ import org.alfresco.service.namespace.QName; { return assoc.isTargetMany(); } + + /*package*/ static Collection diffAssocLists(Collection previousAssocs, Collection newAssocs) + { + List M2ModelDiffs = new ArrayList(); + + for (AssociationDefinition previousAssoc : previousAssocs) + { + boolean found = false; + for (AssociationDefinition newAssoc : newAssocs) + { + if (newAssoc.getName().equals(previousAssoc.getName())) + { + // TODO currently uses toString() to check whether changed - could override equals() + if ((((M2AssociationDefinition)previousAssoc).toString()).equals(((M2AssociationDefinition)newAssoc).toString())) + { + M2ModelDiffs.add(new M2ModelDiff(newAssoc.getName(), M2ModelDiff.TYPE_ASSOCIATION, M2ModelDiff.DIFF_UNCHANGED)); + } + else + { + M2ModelDiffs.add(new M2ModelDiff(newAssoc.getName(), M2ModelDiff.TYPE_ASSOCIATION, M2ModelDiff.DIFF_UPDATED)); + } + found = true; + break; + } + } + + if (! found) + { + M2ModelDiffs.add(new M2ModelDiff(previousAssoc.getName(), M2ModelDiff.TYPE_ASSOCIATION, M2ModelDiff.DIFF_DELETED)); + } + } + + for (AssociationDefinition newAssoc : newAssocs) + { + boolean found = false; + for (AssociationDefinition previousAssoc : previousAssocs) + { + if (newAssoc.getName().equals(previousAssoc.getName())) + { + found = true; + break; + } + } + + if (! found) + { + M2ModelDiffs.add(new M2ModelDiff(newAssoc.getName(), M2ModelDiff.TYPE_ASSOCIATION, M2ModelDiff.DIFF_CREATED)); + } + } + + return M2ModelDiffs; + } } diff --git a/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java b/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java index 01ecd5b076..f6f62d6416 100644 --- a/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java @@ -26,6 +26,7 @@ package org.alfresco.repo.dictionary; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -460,4 +461,206 @@ import org.alfresco.service.namespace.QName; return name.equals(((M2ClassDefinition)obj).name); } + /** + * return differences in class definition + * + * note: + * - ignores changes in model title, description, author, published date, version + * - ignores changes in default values + * - checks properties for incremental updates, but does not include the diffs + * - checks assocs &Ęchild assocs for incremental updates, but does not include the diffs + * - does not check default values + */ + /* package */ List diffClass(ClassDefinition classDef) + { + List modelDiffs = new ArrayList(); + boolean isUpdated = false; + boolean isUpdatedIncrementally = false; + + if (this == classDef) + { + return modelDiffs; + } + + // check name - cannot be null + if (! name.equals(classDef.getName())) + { + isUpdatedIncrementally = true; + } + + // check parent name + if (parentName != null) + { + if (! parentName.equals(classDef.getParentName())) + { + isUpdated = true; + } + } + else if (classDef.getParentName() != null) + { + isUpdated = true; + } + + // check if aspect (or type) + if (isAspect() != classDef.isAspect()) + { + isUpdated = true; + } + + // check if container + if (isContainer() != classDef.isContainer()) + { + if (isContainer()) + { + // updated (non-incrementally) if class was a container and now is not a container - ie. all child associations removed + isUpdated = true; + } + + if (classDef.isContainer()) + { + // updated incrementally if class was not a container and now is a container - ie. some child associations added + isUpdatedIncrementally = true; + } + } + + // 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; + } + } + + // 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; + } + } + + // check default/mandatory aspects (including inherited default aspects) + Collection defaultAspectsDiffs = M2ClassDefinition.diffClassLists(new ArrayList(getDefaultAspects()), new ArrayList(classDef.getDefaultAspects()), M2ModelDiff.TYPE_DEFAULT_ASPECT); + + for (M2ModelDiff defaultAspectDiff : defaultAspectsDiffs) + { + // note: incremental default/mandatory aspect updates not supported yet, added for completeness + if (defaultAspectDiff.getDiffType().equals(M2ModelDiff.DIFF_UPDATED_INC)) + { + isUpdatedIncrementally = true; + } + + if (defaultAspectDiff.getDiffType().equals(M2ModelDiff.DIFF_CREATED) || defaultAspectDiff.getDiffType().equals(M2ModelDiff.DIFF_UPDATED) || defaultAspectDiff.getDiffType().equals(M2ModelDiff.DIFF_DELETED)) + { + isUpdated = true; + break; + } + } + + // check archive/inheritedArchive + if (isArchive() != classDef.isArchive()) + { + isUpdatedIncrementally = true; + } + + String modelDiffType; + if (isAspect()) + { + modelDiffType = M2ModelDiff.TYPE_ASPECT; + } + else + { + modelDiffType = M2ModelDiff.TYPE_TYPE; + } + + if (isUpdated) + { + modelDiffs.add(new M2ModelDiff(name, modelDiffType, M2ModelDiff.DIFF_UPDATED)); + } + else if (isUpdatedIncrementally) + { + modelDiffs.add(new M2ModelDiff(name, modelDiffType, M2ModelDiff.DIFF_UPDATED_INC)); + } + 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; + } + + /** + * return differences in class definition lists + * + */ + /*package*/ static List diffClassLists(Collection previousClasses, Collection newClasses, String M2ModelDiffType) + { + List modelDiffs = new ArrayList(); + + for (ClassDefinition previousClass : previousClasses) + { + boolean found = false; + for (ClassDefinition newClass : newClasses) + { + if (newClass.getName().equals(previousClass.getName())) + { + modelDiffs.addAll(((M2ClassDefinition)previousClass).diffClass(newClass)); + found = true; + break; + } + } + + if (! found) + { + modelDiffs.add(new M2ModelDiff(previousClass.getName(), M2ModelDiffType, M2ModelDiff.DIFF_DELETED)); + } + } + + for (ClassDefinition newClass : newClasses) + { + boolean found = false; + for (ClassDefinition previousClass : previousClasses) + { + if (newClass.getName().equals(previousClass.getName())) + { + found = true; + break; + } + } + + if (! found) + { + modelDiffs.add(new M2ModelDiff(newClass.getName(), M2ModelDiffType, 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 new file mode 100755 index 0000000000..ce976b7b86 --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/M2ModelDiff.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2005-2007 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.dictionary; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ParameterCheck; + +/** + * Compiled Model Difference + * + * @author JanV + * + */ +public class M2ModelDiff +{ + public static final String DIFF_CREATED = "created"; + public static final String DIFF_UPDATED = "updated"; + public static final String DIFF_UPDATED_INC = "updated_inc"; // incremental update + public static final String DIFF_DELETED = "deleted"; + public static final String DIFF_UNCHANGED = "unchanged"; + + public static final String TYPE_TYPE = "TYPE"; + public static final String TYPE_ASPECT = "ASPECT"; + public static final String TYPE_DEFAULT_ASPECT = "DEFAULT_ASPECT"; + public static final String TYPE_PROPERTY = "PROPERTY"; + public static final String TYPE_ASSOCIATION = "ASSOCIATION"; + + private QName elementName; + private String elementType; + private String diffType; + + public M2ModelDiff(QName elementName, String elementType, String diffType) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("elementName", elementName); + ParameterCheck.mandatoryString("elementType", elementType); + ParameterCheck.mandatoryString("diffType", diffType); + + if ((!elementType.equals(TYPE_TYPE)) && + (!elementType.equals(TYPE_ASPECT)) && + (!elementType.equals(TYPE_DEFAULT_ASPECT)) && + (!elementType.equals(TYPE_PROPERTY)) && + (!elementType.equals(TYPE_ASSOCIATION))) + { + throw new AlfrescoRuntimeException("Unknown element type = " + elementType); + } + + if ((! diffType.equals(DIFF_CREATED)) && + (! diffType.equals(DIFF_UPDATED)) && + (! diffType.equals(DIFF_UPDATED_INC)) && + (! diffType.equals(DIFF_DELETED)) && + (! diffType.equals(DIFF_UNCHANGED))) + { + throw new AlfrescoRuntimeException("Unknown diff type = " + diffType); + } + + this.elementName = elementName; + this.elementType = elementType; + this.diffType = diffType; + } + + public QName getElementName() + { + return elementName; + } + + public String getElementType() + { + return elementType; + } + + public String getDiffType() + { + return diffType; + } + + public String toString() + { + return new String(elementType + " " + elementName + " " + diffType); + } + } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/dictionary/M2NamespaceDefinition.java b/source/java/org/alfresco/repo/dictionary/M2NamespaceDefinition.java new file mode 100755 index 0000000000..962c6b4eaf --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/M2NamespaceDefinition.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2005-2007 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.dictionary; + +import org.alfresco.service.cmr.dictionary.ModelDefinition; +import org.alfresco.service.cmr.dictionary.NamespaceDefinition; + + +/** + * Namespace Definition. + * + * + */ +public class M2NamespaceDefinition implements NamespaceDefinition +{ + ModelDefinition model = null; + private String uri = null; + private String prefix = null; + + + /*package*/ M2NamespaceDefinition(ModelDefinition model, String uri, String prefix) + { + this.model = model; + this.uri = uri; + this.prefix = prefix; + } + + public ModelDefinition getModel() + { + return model; + } + + public String getUri() + { + return uri; + } + + public String getPrefix() + { + return prefix; + } +} diff --git a/source/java/org/alfresco/repo/dictionary/M2PropertyDefinition.java b/source/java/org/alfresco/repo/dictionary/M2PropertyDefinition.java index 08347bad03..26c5f718f9 100644 --- a/source/java/org/alfresco/repo/dictionary/M2PropertyDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2PropertyDefinition.java @@ -25,6 +25,7 @@ package org.alfresco.repo.dictionary; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -224,7 +225,24 @@ import org.alfresco.service.namespace.QName; @Override public String toString() { - return getName().toString(); + // note: currently used for model 'diffs' + StringBuffer sb = new StringBuffer(); + sb.append("Name: " + getName() + "\n"); + sb.append("Title: " + getTitle() + "\n"); + sb.append("Description: " + getDescription() + "\n"); + sb.append("Default Value: " + getDefaultValue() + "\n"); + sb.append("DataType Name: " + getDataType().getName() + "\n"); + sb.append("ContainerClass Name: " + getContainerClass().getName() + "\n"); + sb.append("isMultiValued: " + isMultiValued() + "\n"); + sb.append("isMandatory: " + isMandatory() + "\n"); + sb.append("isMandatoryEnforced: " + isMandatoryEnforced() + "\n"); + sb.append("isProtected: " + isProtected() + "\n"); + sb.append("isIndexed: " + isIndexed() + "\n"); + sb.append("isStoredInIndex: " + isStoredInIndex() + "\n"); + sb.append("isIndexedAtomically: " + isIndexedAtomically() + "\n"); + sb.append("isTokenisedInIndex: " + isTokenisedInIndex() + "\n"); + + return sb.toString(); } /* (non-Javadoc) @@ -365,8 +383,64 @@ import org.alfresco.service.namespace.QName; return m2Property.isTokenisedInIndex(); } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.dictionary.PropertyDefinition#getConstraints() + */ public List getConstraints() { return constraintDefs; } + + /*package*/ static Collection diffPropertyLists(Collection previousProperties, Collection newProperties) + { + List M2ModelDiffs = new ArrayList(); + + for (PropertyDefinition previousProperty : previousProperties) + { + boolean found = false; + for (PropertyDefinition newProperty : newProperties) + { + if (newProperty.getName().equals(previousProperty.getName())) + { + // TODO currently uses toString() to check whether changed - could override equals() + if ((((M2PropertyDefinition)previousProperty).toString()).equals(((M2PropertyDefinition)newProperty).toString())) + { + M2ModelDiffs.add(new M2ModelDiff(newProperty.getName(), M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_UNCHANGED)); + } + else + { + M2ModelDiffs.add(new M2ModelDiff(newProperty.getName(), M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_UPDATED)); + } + found = true; + break; + } + } + + if (! found) + { + M2ModelDiffs.add(new M2ModelDiff(previousProperty.getName(), M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_DELETED)); + } + } + + for (PropertyDefinition newProperty : newProperties) + { + boolean found = false; + for (PropertyDefinition previousProperty : previousProperties) + { + if (newProperty.getName().equals(previousProperty.getName())) + { + found = true; + break; + } + } + + if (! found) + { + M2ModelDiffs.add(new M2ModelDiff(newProperty.getName(), M2ModelDiff.TYPE_PROPERTY, M2ModelDiff.DIFF_CREATED)); + } + } + + return M2ModelDiffs; + } } diff --git a/source/java/org/alfresco/service/cmr/dictionary/NamespaceDefinition.java b/source/java/org/alfresco/service/cmr/dictionary/NamespaceDefinition.java new file mode 100755 index 0000000000..91b2c38574 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/dictionary/NamespaceDefinition.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005-2007 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.service.cmr.dictionary; + + +/** + * Read-only definition of a Namespace. + * + */ +public interface NamespaceDefinition +{ + /** + * @return defining model + */ + public ModelDefinition getModel(); + + /** + * @return the namespace URI + */ + public String getUri(); + + /** + * @return the namespace Prefix + */ + public String getPrefix(); +}