diff --git a/source/java/org/alfresco/repo/domain/node/NodePropertyHelper.java b/source/java/org/alfresco/repo/domain/node/NodePropertyHelper.java index 9fa4a7adac..9c3715d88e 100644 --- a/source/java/org/alfresco/repo/domain/node/NodePropertyHelper.java +++ b/source/java/org/alfresco/repo/domain/node/NodePropertyHelper.java @@ -371,7 +371,6 @@ public class NodePropertyHelper } } - // TODO decrypt TEXT and MLTEXT properties where necessary public Map convertToPublicProperties(Map propertyValues) { Map propertyMap = new HashMap(propertyValues.size(), 1.0F); diff --git a/source/java/org/alfresco/repo/solr/AlfrescoModel.java b/source/java/org/alfresco/repo/solr/AlfrescoModel.java new file mode 100644 index 0000000000..95682bfe36 --- /dev/null +++ b/source/java/org/alfresco/repo/solr/AlfrescoModel.java @@ -0,0 +1,55 @@ +package org.alfresco.repo.solr; + +import org.alfresco.service.cmr.dictionary.ModelDefinition; + +/** + * Represents an alfresco model and checksum. + * + * @since 4.0 + */ +public class AlfrescoModel +{ + private ModelDefinition modelDef; + private long checksum; + + protected AlfrescoModel(ModelDefinition modelDef) + { + this.modelDef = modelDef; + this.checksum = modelDef.getChecksum(ModelDefinition.XMLBindingType.SOLR); + } + + public ModelDefinition getModelDef() + { + return modelDef; + } + + public long getChecksum() + { + return checksum; + } + + public boolean equals(Object other) + { + if (this == other) + { + return true; + } + + if(!(other instanceof AlfrescoModel)) + { + return false; + } + + AlfrescoModel model = (AlfrescoModel)other; + return (modelDef.getName().equals(model.getModelDef().getName()) && + checksum == model.getChecksum()); + } + + public int hashcode() + { + int result = 17; + result = 31 * result + modelDef.hashCode(); + result = 31 * result + Long.valueOf(checksum).hashCode(); + return result; + } +} diff --git a/source/java/org/alfresco/repo/solr/AlfrescoModelDiff.java b/source/java/org/alfresco/repo/solr/AlfrescoModelDiff.java new file mode 100644 index 0000000000..15cac4bacc --- /dev/null +++ b/source/java/org/alfresco/repo/solr/AlfrescoModelDiff.java @@ -0,0 +1,52 @@ +package org.alfresco.repo.solr; + +import org.alfresco.service.namespace.QName; + +/** + * Represents a diff between the set of current repository Alfresco models and the set maintained in SOLR. + * The diff can represent a new, changed or removed Alfresco model. For a new model the newChecksum is + * populated; for a changed model both checksums are populated; for a removed model neither checksum is populated. + * + * @since 4.0 + */ +public class AlfrescoModelDiff +{ + public static enum TYPE + { + NEW, CHANGED, REMOVED; + }; + + private QName modelName; + private TYPE type; + private Long oldChecksum; + private Long newChecksum; + + public AlfrescoModelDiff(QName modelName, TYPE type, Long oldChecksum, Long newChecksum) + { + super(); + this.modelName = modelName; + this.type = type; + this.oldChecksum = oldChecksum; + this.newChecksum = newChecksum; + } + + public QName getModelName() + { + return modelName; + } + + public TYPE getType() + { + return type; + } + + public Long getOldChecksum() + { + return oldChecksum; + } + + public Long getNewChecksum() + { + return newChecksum; + } +} diff --git a/source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java b/source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java index 79573fd9dd..35eaa54519 100644 --- a/source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java +++ b/source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java @@ -19,8 +19,11 @@ package org.alfresco.repo.solr; import java.util.List; +import java.util.Map; +import java.util.Set; import org.alfresco.repo.domain.node.Node; +import org.alfresco.service.namespace.QName; /** * Interface for component to provide tracking data for SOLR. @@ -84,7 +87,24 @@ public interface SOLRTrackingComponent * @param callback a callback to receive the results */ public void getNodesMetadata(NodeMetaDataParameters nodeMetaDataParameters, MetaDataResultsFilter resultFilter, NodeMetaDataQueryCallback callback); - + + /** + * Returns the Alfresco model given by the name modelName + * + * @param modelName the name of the model + * @return the model plus a checksum + */ + public AlfrescoModel getModel(QName modelName); + + /** + * Returns a list of diffs representing differences between the current Repository models + * and those passed in the models parameter. + * + * @param models a set of mappings of model names to checksums + * @return a list of diffs between those in the repository and those passed in the models parameter + */ + public List getModelDiffs(Map models); + /** * The interface that will be used to give query results to the calling code. */ diff --git a/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java index 453aa88120..24f1cb6947 100644 --- a/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java +++ b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.Set; import org.alfresco.model.ContentModel; +import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.domain.node.Node; import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.domain.node.NodeDAO.ChildAssocRefQueryCallback; @@ -37,6 +38,7 @@ import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.ModelDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.InvalidNodeRefException; @@ -59,6 +61,7 @@ public class SOLRTrackingComponentImpl implements SOLRTrackingComponent private NodeDAO nodeDAO; private QNameDAO qnameDAO; private SOLRDAO solrDAO; + private DictionaryDAO dictionaryDAO; private PermissionService permissionService; private OwnableService ownableService; private TenantService tenantService; @@ -524,6 +527,62 @@ public class SOLRTrackingComponentImpl implements SOLRTrackingComponent rowHandler.processResult(nodeMetaData); } } + + /** + * {@inheritDoc} + */ + public AlfrescoModel getModel(QName modelName) + { + ModelDefinition modelDef = dictionaryService.getModel(modelName); + return (modelDef != null ? new AlfrescoModel(modelDef) : null); + } + + /** + * {@inheritDoc} + */ + public List getModelDiffs(Map models) + { + List diffs = new ArrayList(); + + // get all models the repository knows about and add each to a list with its checksum + Collection allModels = dictionaryService.getAllModels(); + + // look for changed and removed models + for(QName modelName : models.keySet()) + { + if(allModels.contains(modelName)) + { + Long checksum = models.get(modelName); + AlfrescoModel serverModel = getModel(modelName); + if(serverModel.getChecksum() != checksum.longValue()) + { + // model has changed, add the changed server model + diffs.add(new AlfrescoModelDiff(modelName, + AlfrescoModelDiff.TYPE.CHANGED, checksum, serverModel.getChecksum())); + } + } + else + { + // model no longer exists, just add it's name + diffs.add(new AlfrescoModelDiff(modelName, + AlfrescoModelDiff.TYPE.REMOVED, null, null)); + } + } + + // look for new models + for(QName modelName : allModels) + { + if(!models.containsKey(modelName)) + { + // new model, add the model xml and checksum + AlfrescoModel model = getModel(modelName); + diffs.add(new AlfrescoModelDiff(modelName, + AlfrescoModelDiff.TYPE.NEW, null, model.getChecksum())); + } + } + + return diffs; + } /** * Class that passes results from a result entity into the client callback diff --git a/source/java/org/alfresco/repo/solr/SOLRTrackingComponentTest.java b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentTest.java index 0744ee77b4..6f735b6e20 100644 --- a/source/java/org/alfresco/repo/solr/SOLRTrackingComponentTest.java +++ b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentTest.java @@ -18,8 +18,10 @@ */ package org.alfresco.repo.solr; +import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -28,6 +30,10 @@ import java.util.Set; import junit.framework.TestCase; import org.alfresco.model.ContentModel; +import org.alfresco.repo.dictionary.DictionaryDAO; +import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.repo.dictionary.M2Property; +import org.alfresco.repo.dictionary.M2Type; import org.alfresco.repo.domain.node.Node; import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.security.authentication.AuthenticationComponent; @@ -36,11 +42,13 @@ import org.alfresco.repo.solr.SOLRTrackingComponent.NodeQueryCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; 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.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; @@ -66,10 +74,13 @@ public class SOLRTrackingComponentTest extends TestCase private AuthenticationComponent authenticationComponent; private TransactionService transactionService; + private DictionaryService dictionaryService; + private NamespaceService namespaceService; private RetryingTransactionHelper txnHelper; private NodeService nodeService; private FileFolderService fileFolderService; private NodeDAO nodeDAO; + private DictionaryDAO dictionaryDAO; private SOLRTrackingComponent solrTrackingComponent; private StoreRef storeRef; @@ -84,8 +95,11 @@ public class SOLRTrackingComponentTest extends TestCase solrTrackingComponent = (SOLRTrackingComponent) ctx.getBean("solrTrackingComponent"); nodeDAO = (NodeDAO)ctx.getBean("nodeDAO"); + dictionaryDAO = (DictionaryDAO)ctx.getBean("dictionaryDAO"); nodeService = (NodeService)ctx.getBean("NodeService"); fileFolderService = (FileFolderService)ctx.getBean("FileFolderService"); + dictionaryService = serviceRegistry.getDictionaryService(); + namespaceService = serviceRegistry.getNamespaceService(); authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent"); authenticationComponent.setSystemUserAsCurrentUser(); @@ -302,7 +316,138 @@ public class SOLRTrackingComponentTest extends TestCase nodeMetaDataParams.setNodeIds(st.getNodeIds()); getNodeMetaData(nodeMetaDataParams, null, st); } + + public void testModelDiffs() + { + Collection allModels = dictionaryService.getAllModels(); + + ModelDiffsTracker tracker = new ModelDiffsTracker(); + ModelDiffResults diffResults = tracker.diff(); + // number of diffs should equal the number of models in the repository + assertEquals("Unexpected number of new models", allModels.size(), diffResults.getNewModels().size()); + assertEquals("Expected no removed models", 0, diffResults.getRemovedModels().size()); + assertEquals("Expected no changed models", 0, diffResults.getChangedModels().size()); + + // create a new model + InputStream modelStream = getClass().getClassLoader().getResourceAsStream("org/alfresco/repo/solr/testModel.xml"); + M2Model testModel = M2Model.createModel(modelStream); + dictionaryDAO.putModel(testModel); + + // call model diffs - should detect new model + ModelDiffResults diffResults1 = tracker.diff(); + assertEquals("Expected 1 new model", 1, diffResults1.getNewModels().size()); + assertEquals("Unexpected number of changed models", 0, diffResults1.getChangedModels().size()); + assertEquals("Unexpected number of removed models", 0, diffResults1.getRemovedModels().size()); + AlfrescoModelDiff diff = diffResults1.getNewModels().get(0); + assertEquals("Unexpected model name change", QName.createQName(testModel.getName(), namespaceService), diff.getModelName()); + + // get current checksum for the test model + Long testModelChecksum = tracker.getChecksum(QName.createQName(testModel.getName(), namespaceService)); + assertNotNull("", testModelChecksum); + + // create a new type and add it to the new test model + M2Type anotherType = testModel.createType("anothertype"); + M2Property prop1 = anotherType.createProperty("prop1"); + prop1.setType("d:text"); + + // call model diffs - should detect test model changes + ModelDiffResults diffResults2 = tracker.diff(); + List changedModels = diffResults2.getChangedModels(); + assertEquals("Expected no new models", 0, diffResults2.getNewModels().size()); + assertEquals("Expected no removed models", 0, diffResults2.getRemovedModels().size()); + assertEquals("Expected detection of changed testmodel", 1, changedModels.size()); + + AlfrescoModelDiff changedModel = changedModels.get(0); + assertEquals("Unexpected changed model name", QName.createQName(testModel.getName(), namespaceService), + changedModel.getModelName()); + assertNotNull("", changedModel.getOldChecksum().longValue()); + assertEquals("Old checksum value is incorrect", testModelChecksum.longValue(), changedModel.getOldChecksum().longValue()); + assertNotSame("Expected checksums to be different", changedModel.getOldChecksum(), changedModel.getNewChecksum()); + + // remove the model + dictionaryDAO.removeModel(QName.createQName(testModel.getName(), namespaceService)); + + // call model diffs - check that the model has been removed + ModelDiffResults diffResults3 = tracker.diff(); + List removedModels = diffResults3.getRemovedModels(); + assertEquals("Expected 1 removed model", 1, removedModels.size()); + QName removedModelName = removedModels.get(0).getModelName(); + String removedModelNamespace = removedModelName.getNamespaceURI(); + String removedModelLocalName = removedModelName.getLocalName(); + assertEquals("Removed model namespace is incorrect", "http://www.alfresco.org/model/solrtest/1.0", removedModelNamespace); + assertEquals("Removed model name is incorrect", "contentmodel", removedModelLocalName); + assertEquals("Expected no new models", 0, diffResults3.getNewModels().size()); + assertEquals("Expected no changed modeks", 0, diffResults3.getChangedModels().size()); + } + + private static class ModelDiffResults + { + private List newModels; + private List changedModels; + private List removedModels; + + public ModelDiffResults(List newModels, List changedModels, List removedModels) + { + super(); + this.newModels = newModels; + this.changedModels = changedModels; + this.removedModels = removedModels; + } + + public List getNewModels() + { + return newModels; + } + + public List getChangedModels() + { + return changedModels; + } + + public List getRemovedModels() + { + return removedModels; + } + } + + private class ModelDiffsTracker + { + private Map trackedModels = new HashMap(); + + public ModelDiffResults diff() + { + List modelDiffs = solrTrackingComponent.getModelDiffs(trackedModels); + List newModels = new ArrayList(); + List changedModels = new ArrayList(); + List removedModels = new ArrayList(); + + for(AlfrescoModelDiff diff : modelDiffs) + { + if(diff.getType().equals(AlfrescoModelDiff.TYPE.NEW)) + { + newModels.add(diff); + trackedModels.put(diff.getModelName(), diff.getNewChecksum()); + } + else if(diff.getType().equals(AlfrescoModelDiff.TYPE.CHANGED)) + { + changedModels.add(diff); + } + else if(diff.getType().equals(AlfrescoModelDiff.TYPE.REMOVED)) + { + removedModels.add(diff); + } + } + + return new ModelDiffResults(newModels, changedModels, removedModels); + } + + public Long getChecksum(QName modelName) + { + return trackedModels.get(modelName); + } + } + private static class NodeAssertions { private Set aspects; diff --git a/source/java/org/alfresco/repo/solr/testModel.xml b/source/java/org/alfresco/repo/solr/testModel.xml new file mode 100644 index 0000000000..9e5ed71c02 --- /dev/null +++ b/source/java/org/alfresco/repo/solr/testModel.xml @@ -0,0 +1,129 @@ + + + + + Alfresco Content Domain Model + Alfresco + 2009-06-04 + 1.1 + + + + + + + + + + + + + \<\?\/\:\|]+.*)|(.*[\.]?.*[\.]+$)|(.*[ ]+$)]]> + false + + + + + defaultStoreSelector + + + + + + + Object + sys:base + + + Name + d:text + true + + true + false + both + + + + + + + + testcm:testaspect + + + + + Folder + testcm:testobject + true + + + + false + true + + + sys:base + false + true + + false + true + + + + + + Content + testcm:testobject + true + + + d:content + false + + + + + true + false + true + + + + + + + + + + + Titled + + + Title + d:mltext + + true + false + both + + + + Description + d:mltext + + true + false + both + + + + + + + + \ No newline at end of file