Checkin for RSOLR 031: "Remote API to get and compare models"

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28472 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Steven Glover
2011-06-20 09:28:02 +00:00
parent ac7658f463
commit 347d43b4a1
7 changed files with 461 additions and 2 deletions

View File

@@ -371,7 +371,6 @@ public class NodePropertyHelper
} }
} }
// TODO decrypt TEXT and MLTEXT properties where necessary
public Map<QName, Serializable> convertToPublicProperties(Map<NodePropertyKey, NodePropertyValue> propertyValues) public Map<QName, Serializable> convertToPublicProperties(Map<NodePropertyKey, NodePropertyValue> propertyValues)
{ {
Map<QName, Serializable> propertyMap = new HashMap<QName, Serializable>(propertyValues.size(), 1.0F); Map<QName, Serializable> propertyMap = new HashMap<QName, Serializable>(propertyValues.size(), 1.0F);

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -19,8 +19,11 @@
package org.alfresco.repo.solr; package org.alfresco.repo.solr;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.repo.domain.node.Node; import org.alfresco.repo.domain.node.Node;
import org.alfresco.service.namespace.QName;
/** /**
* Interface for component to provide tracking data for SOLR. * Interface for component to provide tracking data for SOLR.
@@ -84,7 +87,24 @@ public interface SOLRTrackingComponent
* @param callback a callback to receive the results * @param callback a callback to receive the results
*/ */
public void getNodesMetadata(NodeMetaDataParameters nodeMetaDataParameters, MetaDataResultsFilter resultFilter, NodeMetaDataQueryCallback callback); 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<AlfrescoModelDiff> getModelDiffs(Map<QName, Long> models);
/** /**
* The interface that will be used to give query results to the calling code. * The interface that will be used to give query results to the calling code.
*/ */

View File

@@ -28,6 +28,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.dictionary.DictionaryDAO;
import org.alfresco.repo.domain.node.Node; import org.alfresco.repo.domain.node.Node;
import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.domain.node.NodeDAO.ChildAssocRefQueryCallback; 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.AspectDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService; 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.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.InvalidNodeRefException;
@@ -59,6 +61,7 @@ public class SOLRTrackingComponentImpl implements SOLRTrackingComponent
private NodeDAO nodeDAO; private NodeDAO nodeDAO;
private QNameDAO qnameDAO; private QNameDAO qnameDAO;
private SOLRDAO solrDAO; private SOLRDAO solrDAO;
private DictionaryDAO dictionaryDAO;
private PermissionService permissionService; private PermissionService permissionService;
private OwnableService ownableService; private OwnableService ownableService;
private TenantService tenantService; private TenantService tenantService;
@@ -524,6 +527,62 @@ public class SOLRTrackingComponentImpl implements SOLRTrackingComponent
rowHandler.processResult(nodeMetaData); rowHandler.processResult(nodeMetaData);
} }
} }
/**
* {@inheritDoc}
*/
public AlfrescoModel getModel(QName modelName)
{
ModelDefinition modelDef = dictionaryService.getModel(modelName);
return (modelDef != null ? new AlfrescoModel(modelDef) : null);
}
/**
* {@inheritDoc}
*/
public List<AlfrescoModelDiff> getModelDiffs(Map<QName, Long> models)
{
List<AlfrescoModelDiff> diffs = new ArrayList<AlfrescoModelDiff>();
// get all models the repository knows about and add each to a list with its checksum
Collection<QName> 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 * Class that passes results from a result entity into the client callback

View File

@@ -18,8 +18,10 @@
*/ */
package org.alfresco.repo.solr; package org.alfresco.repo.solr;
import java.io.InputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -28,6 +30,10 @@ import java.util.Set;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.alfresco.model.ContentModel; 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.Node;
import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.security.authentication.AuthenticationComponent; 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;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry; 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.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.ApplicationContextHelper;
@@ -66,10 +74,13 @@ public class SOLRTrackingComponentTest extends TestCase
private AuthenticationComponent authenticationComponent; private AuthenticationComponent authenticationComponent;
private TransactionService transactionService; private TransactionService transactionService;
private DictionaryService dictionaryService;
private NamespaceService namespaceService;
private RetryingTransactionHelper txnHelper; private RetryingTransactionHelper txnHelper;
private NodeService nodeService; private NodeService nodeService;
private FileFolderService fileFolderService; private FileFolderService fileFolderService;
private NodeDAO nodeDAO; private NodeDAO nodeDAO;
private DictionaryDAO dictionaryDAO;
private SOLRTrackingComponent solrTrackingComponent; private SOLRTrackingComponent solrTrackingComponent;
private StoreRef storeRef; private StoreRef storeRef;
@@ -84,8 +95,11 @@ public class SOLRTrackingComponentTest extends TestCase
solrTrackingComponent = (SOLRTrackingComponent) ctx.getBean("solrTrackingComponent"); solrTrackingComponent = (SOLRTrackingComponent) ctx.getBean("solrTrackingComponent");
nodeDAO = (NodeDAO)ctx.getBean("nodeDAO"); nodeDAO = (NodeDAO)ctx.getBean("nodeDAO");
dictionaryDAO = (DictionaryDAO)ctx.getBean("dictionaryDAO");
nodeService = (NodeService)ctx.getBean("NodeService"); nodeService = (NodeService)ctx.getBean("NodeService");
fileFolderService = (FileFolderService)ctx.getBean("FileFolderService"); fileFolderService = (FileFolderService)ctx.getBean("FileFolderService");
dictionaryService = serviceRegistry.getDictionaryService();
namespaceService = serviceRegistry.getNamespaceService();
authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent"); authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent");
authenticationComponent.setSystemUserAsCurrentUser(); authenticationComponent.setSystemUserAsCurrentUser();
@@ -302,7 +316,138 @@ public class SOLRTrackingComponentTest extends TestCase
nodeMetaDataParams.setNodeIds(st.getNodeIds()); nodeMetaDataParams.setNodeIds(st.getNodeIds());
getNodeMetaData(nodeMetaDataParams, null, st); getNodeMetaData(nodeMetaDataParams, null, st);
} }
public void testModelDiffs()
{
Collection<QName> 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<AlfrescoModelDiff> 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<AlfrescoModelDiff> 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<AlfrescoModelDiff> newModels;
private List<AlfrescoModelDiff> changedModels;
private List<AlfrescoModelDiff> removedModels;
public ModelDiffResults(List<AlfrescoModelDiff> newModels, List<AlfrescoModelDiff> changedModels, List<AlfrescoModelDiff> removedModels)
{
super();
this.newModels = newModels;
this.changedModels = changedModels;
this.removedModels = removedModels;
}
public List<AlfrescoModelDiff> getNewModels()
{
return newModels;
}
public List<AlfrescoModelDiff> getChangedModels()
{
return changedModels;
}
public List<AlfrescoModelDiff> getRemovedModels()
{
return removedModels;
}
}
private class ModelDiffsTracker
{
private Map<QName, Long> trackedModels = new HashMap<QName, Long>();
public ModelDiffResults diff()
{
List<AlfrescoModelDiff> modelDiffs = solrTrackingComponent.getModelDiffs(trackedModels);
List<AlfrescoModelDiff> newModels = new ArrayList<AlfrescoModelDiff>();
List<AlfrescoModelDiff> changedModels = new ArrayList<AlfrescoModelDiff>();
List<AlfrescoModelDiff> removedModels = new ArrayList<AlfrescoModelDiff>();
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 static class NodeAssertions
{ {
private Set<QName> aspects; private Set<QName> aspects;

View File

@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- xsi:schemaLocation="http://www.alfresco.org/model/dictionary/1.0 modelSchema.xsd" -->
<model name="testcm:contentmodel"
xmlns="http://www.alfresco.org/model/dictionary/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<description>Alfresco Content Domain Model</description>
<author>Alfresco</author>
<published>2009-06-04</published>
<version>1.1</version>
<imports>
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
<import uri="http://www.alfresco.org/model/system/1.0" prefix="sys"/>
</imports>
<namespaces>
<namespace uri="http://www.alfresco.org/model/solrtest/1.0" prefix="testcm"/>
</namespaces>
<constraints>
<constraint name="testcm:filename" type="REGEX">
<parameter name="expression"><value><![CDATA[(.*[\"\*\\\>\<\?\/\:\|]+.*)|(.*[\.]?.*[\.]+$)|(.*[ ]+$)]]></value></parameter>
<parameter name="requiresMatch"><value>false</value></parameter>
</constraint>
<constraint name="testcm:userNameConstraint" type="org.alfresco.repo.dictionary.constraint.UserNameConstraint" />
<constraint name="testcm:authorityNameConstraint" type="org.alfresco.repo.dictionary.constraint.AuthorityNameConstraint" />
<constraint name="testcm:storeSelectorConstraint" type="REGISTERED">
<parameter name="registeredName"><value>defaultStoreSelector</value></parameter>
</constraint>
</constraints>
<types>
<type name="testcm:testobject">
<title>Object</title>
<parent>sys:base</parent>
<properties>
<property name="testcm:name">
<title>Name</title>
<type>d:text</type>
<mandatory enforced="true">true</mandatory>
<index enabled="true">
<atomic>true</atomic>
<stored>false</stored>
<tokenised>both</tokenised>
</index>
<constraints>
<constraint ref="testcm:filename" />
</constraints>
</property>
</properties>
<mandatory-aspects>
<aspect>testcm:testaspect</aspect>
</mandatory-aspects>
</type>
<type name="testcm:folder">
<title>Folder</title>
<parent>testcm:testobject</parent>
<archive>true</archive>
<associations>
<child-association name="testcm:contains">
<source>
<mandatory>false</mandatory>
<many>true</many>
</source>
<target>
<class>sys:base</class>
<mandatory>false</mandatory>
<many>true</many>
</target>
<duplicate>false</duplicate>
<propagateTimestamps>true</propagateTimestamps>
</child-association>
</associations>
</type>
<type name="testcm:content">
<title>Content</title>
<parent>testcm:testobject</parent>
<archive>true</archive>
<properties>
<property name="testcm:content">
<type>d:content</type>
<mandatory>false</mandatory>
<!-- Although content is marked as indexed atomically it may end up asynchronous -->
<!-- if the content conversion will take too long. Content that does not require conversion -->
<!-- to UTF8 test/plain will always be indexed atomically -->
<index enabled="true">
<atomic>true</atomic>
<stored>false</stored>
<tokenised>true</tokenised>
</index>
</property>
</properties>
</type>
</types>
<aspects>
<aspect name="testcm:testaspect">
<title>Titled</title>
<properties>
<property name="testcm:title">
<title>Title</title>
<type>d:mltext</type>
<index enabled="true">
<atomic>true</atomic>
<stored>false</stored>
<tokenised>both</tokenised>
</index>
</property>
<property name="testcm:description">
<title>Description</title>
<type>d:mltext</type>
<index enabled="true">
<atomic>true</atomic>
<stored>false</stored>
<tokenised>both</tokenised>
</index>
</property>
</properties>
</aspect>
</aspects>
</model>