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();
+}