diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml index 95721aa27f..fc7132cfa0 100644 --- a/config/alfresco/dao/dao-context.xml +++ b/config/alfresco/dao/dao-context.xml @@ -253,4 +253,9 @@ + + + + + diff --git a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml index 219a61fddd..a6381530c3 100644 --- a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml +++ b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml @@ -41,7 +41,8 @@ - + + diff --git a/config/alfresco/ibatis/ibatis-context.xml b/config/alfresco/ibatis/ibatis-context.xml index 86d97dd573..964d4765eb 100644 --- a/config/alfresco/ibatis/ibatis-context.xml +++ b/config/alfresco/ibatis/ibatis-context.xml @@ -78,5 +78,7 @@ - + + + diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/solr-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/solr-common-SqlMap.xml new file mode 100644 index 0000000000..ceb5c0143b --- /dev/null +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/solr-common-SqlMap.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 57385f4f17..4af5a9f6a4 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -4,10 +4,10 @@ # Version label -version.major=3 -version.minor=5 +version.major=4 +version.minor=0 version.revision=0 -version.label=a +version.label= # Edition label @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=4306 +version.schema=5000 diff --git a/source/java/org/alfresco/repo/domain/solr/NodeParameters.java b/source/java/org/alfresco/repo/domain/solr/NodeParameters.java new file mode 100644 index 0000000000..bf720f680e --- /dev/null +++ b/source/java/org/alfresco/repo/domain/solr/NodeParameters.java @@ -0,0 +1,167 @@ +package org.alfresco.repo.domain.solr; + +import java.util.List; +import java.util.Set; + +import org.alfresco.service.namespace.QName; + +/** + * Stores node parameters for use in SOLR DAO queries + * + * @since 4.0 + */ +public class NodeParameters +{ + private List transactionIds; + private Long fromNodeId; + private Long toNodeId; + + private String storeProtocol; + private String storeIdentifier; + + private Set includeNodeTypes; + private Set excludeNodeTypes; + private List includeTypeIds; + private List excludeTypeIds; + + private Set includeAspects; + private Set excludeAspects; + private List includeAspectIds; + private List excludeAspectIds; + + public boolean getStoreFilter() + { + return (storeProtocol != null || storeIdentifier != null); + } + + public void setStoreProtocol(String storeProtocol) + { + this.storeProtocol = storeProtocol; + } + + public String getStoreProtocol() + { + return storeProtocol; + } + + public void setStoreIdentifier(String storeIdentifier) + { + this.storeIdentifier = storeIdentifier; + } + + public String getStoreIdentifier() + { + return storeIdentifier; + } + + public void setTransactionIds(List txnIds) + { + this.transactionIds = txnIds; + } + + public List getTransactionIds() + { + return transactionIds; + } + + public Long getFromNodeId() + { + return fromNodeId; + } + + public void setFromNodeId(Long fromNodeId) + { + this.fromNodeId = fromNodeId; + } + + public Long getToNodeId() + { + return toNodeId; + } + + public void setToNodeId(Long toNodeId) + { + this.toNodeId = toNodeId; + } + + public Set getIncludeNodeTypes() + { + return includeNodeTypes; + } + + public Set getExcludeNodeTypes() + { + return excludeNodeTypes; + } + + public Set getIncludeAspects() + { + return includeAspects; + } + + public Set getExcludeAspects() + { + return excludeAspects; + } + + public void setIncludeNodeTypes(Set includeNodeTypes) + { + this.includeNodeTypes = includeNodeTypes; + } + + public void setExcludeNodeTypes(Set excludeNodeTypes) + { + this.excludeNodeTypes = excludeNodeTypes; + } + + public void setIncludeAspects(Set includeAspects) + { + this.includeAspects = includeAspects; + } + + public void setExcludeAspects(Set excludeAspects) + { + this.excludeAspects = excludeAspects; + } + + public List getIncludeAspectIds() + { + return includeAspectIds; + } + + public void setIncludeAspectIds(List includeAspectIds) + { + this.includeAspectIds = includeAspectIds; + } + + public List getExcludeAspectIds() + { + return excludeAspectIds; + } + + public void setExcludeAspectIds(List excludeAspectIds) + { + this.excludeAspectIds = excludeAspectIds; + } + + public List getIncludeTypeIds() + { + return includeTypeIds; + } + + public void setIncludeTypeIds(List includeTypeIds) + { + this.includeTypeIds = includeTypeIds; + } + + public List getExcludeTypeIds() + { + return excludeTypeIds; + } + + public void setExcludeTypeIds(List excludeTypeIds) + { + this.excludeTypeIds = excludeTypeIds; + } + +} diff --git a/source/java/org/alfresco/repo/domain/solr/SOLRDAO.java b/source/java/org/alfresco/repo/domain/solr/SOLRDAO.java new file mode 100644 index 0000000000..bfb2363f77 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/solr/SOLRDAO.java @@ -0,0 +1,31 @@ +package org.alfresco.repo.domain.solr; + +import java.util.List; + +import org.alfresco.repo.domain.node.Node; + +/** + * DAO support for SOLR web scripts. + * + * @since 4.0 + */ +// TODO - permit shortened form of QNames for e.g. aspects i.e. cm:content vs {http://www.alfresco.org/model/content/1.0}content? +public interface SOLRDAO +{ + public List getTransactions(Long minTxnId, Long fromCommitTime, int maxResults); + public void getNodes(NodeParameters nodeParameters, int maxResults, NodeQueryCallback callback); + + /** + * The interface that will be used to give query results to the calling code. + */ + public static interface NodeQueryCallback + { + /** + * Handle a node. + * + * @param node the node + * @return Return true to continue processing rows or false to stop + */ + boolean handleNode(Node node); + } +} diff --git a/source/java/org/alfresco/repo/domain/solr/SOLRDAOTest.java b/source/java/org/alfresco/repo/domain/solr/SOLRDAOTest.java new file mode 100644 index 0000000000..0e621a4b4e --- /dev/null +++ b/source/java/org/alfresco/repo/domain/solr/SOLRDAOTest.java @@ -0,0 +1,250 @@ +package org.alfresco.repo.domain.solr; + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.node.Node; +import org.alfresco.repo.domain.solr.SOLRDAO.NodeQueryCallback; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +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.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.PropertyMap; +import org.springframework.context.ConfigurableApplicationContext; + +public class SOLRDAOTest extends TestCase +{ + private ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) ApplicationContextHelper.getApplicationContext(); + + private AuthenticationComponent authenticationComponent; + private TransactionService transactionService; + private RetryingTransactionHelper txnHelper; + private NodeService nodeService; + private FileFolderService fileFolderService; + private SOLRDAO solrDAO; + + private StoreRef storeRef; + private NodeRef rootNodeRef; + private NodeRef container1; + private NodeRef content1; + private NodeRef content2; + + @Override + public void setUp() throws Exception + { + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + transactionService = serviceRegistry.getTransactionService(); + txnHelper = transactionService.getRetryingTransactionHelper(); + + solrDAO = (SOLRDAO)ctx.getBean("solrDAO"); + nodeService = (NodeService)ctx.getBean("NodeService"); + fileFolderService = (FileFolderService)ctx.getBean("FileFolderService"); + authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent"); + + authenticationComponent.setSystemUserAsCurrentUser(); + + storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, getName() + System.currentTimeMillis()); + rootNodeRef = nodeService.getRootNode(storeRef); + } + + private void buildTransactions1() + { + txnHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + PropertyMap props = new PropertyMap(); + props.put(ContentModel.PROP_NAME, "Container1"); + container1 = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_FOLDER, + props).getChildRef(); + + System.out.println("container1 = " + container1); + + FileInfo content1Info = fileFolderService.create(container1, "Content1", ContentModel.TYPE_CONTENT); + content1 = content1Info.getNodeRef(); + + System.out.println("content1 = " + content1); + + return null; + } + }); + + txnHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + FileInfo content2Info = fileFolderService.create(container1, "Content2", ContentModel.TYPE_CONTENT); + content2 = content2Info.getNodeRef(); + + System.out.println("content2 = " + content2); + + fileFolderService.delete(content1); + + return null; + } + }); + } + + private void buildTransactions2() + { + txnHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + PropertyMap props = new PropertyMap(); + props.put(ContentModel.PROP_NAME, "Container1"); + container1 = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_FOLDER, + props).getChildRef(); + + System.out.println("container1 = " + container1); + + FileInfo content1Info = fileFolderService.create(container1, "Content1", ContentModel.TYPE_CONTENT); + content1 = content1Info.getNodeRef(); + + System.out.println("content1 = " + content1); + + return null; + } + }); + + txnHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + FileInfo content2Info = fileFolderService.create(container1, "Content2", ContentModel.TYPE_CONTENT); + content2 = content2Info.getNodeRef(); + + System.out.println("content2 = " + content2); + + fileFolderService.delete(content1); + + return null; + } + }); + } + + public void testQueryTransactions1() + { + long startTime = System.currentTimeMillis(); + + buildTransactions1(); + + List txns = solrDAO.getTransactions(null, startTime, 0); + assertEquals("Number of transactions is incorrect", 2, txns.size()); + + int[] updates = new int[] {1, 1}; + int[] deletes = new int[] {0, 1}; + List txnIds = new ArrayList(txns.size()); + int i = 0; + for(Transaction txn : txns) + { + assertEquals("Number of deletes is incorrect", deletes[i], txn.getDeletes()); + assertEquals("Number of updates is incorrect", updates[i], txn.getUpdates()); + i++; + + txnIds.add(txn.getId()); + } + + TestNodeQueryCallback nodeQueryCallback = new TestNodeQueryCallback(container1, content1, content2); + NodeParameters nodeParameters = new NodeParameters(); + nodeParameters.setTransactionIds(txnIds); + solrDAO.getNodes(nodeParameters, 0, nodeQueryCallback); + + assertEquals("Unxpected nodes", 3, nodeQueryCallback.getSuccessCount()); + } + + public void testQueryTransactions2() + { + long startTime = System.currentTimeMillis(); + + buildTransactions2(); + + List txns = solrDAO.getTransactions(null, startTime, 0); + assertEquals("Number of transactions is incorrect", 2, txns.size()); + + int[] updates = new int[] {1, 1}; + int[] deletes = new int[] {0, 1}; + List txnIds = new ArrayList(txns.size()); + int i = 0; + for(Transaction txn : txns) + { + assertEquals("Number of deletes is incorrect", deletes[i], txn.getDeletes()); + assertEquals("Number of updates is incorrect", updates[i], txn.getUpdates()); + i++; + + txnIds.add(txn.getId()); + } + + TestNodeQueryCallback nodeQueryCallback = new TestNodeQueryCallback(container1, content1, content2); + NodeParameters nodeParameters = new NodeParameters(); + nodeParameters.setTransactionIds(txnIds); + solrDAO.getNodes(nodeParameters, 0, nodeQueryCallback); + + assertEquals("Unxpected nodes", 3, nodeQueryCallback.getSuccessCount()); + } + + + private static class TestNodeQueryCallback implements NodeQueryCallback + { + private int successCount = 0; + private NodeRef container1; + private NodeRef content1; + private NodeRef content2; + + public TestNodeQueryCallback(NodeRef container1, + NodeRef content1, NodeRef content2) { + super(); + this.container1 = container1; + this.content1 = content1; + this.content2 = content2; + } + + @Override + public boolean handleNode(Node node) { + NodeRef nodeRef = node.getNodeRef(); + Boolean isDeleted = node.getDeleted(); + + System.out.println("Node: " + node.toString()); + + if(nodeRef.equals(container1) && !isDeleted) + { + successCount++; + } + + if(nodeRef.equals(content1) && isDeleted) + { + successCount++; + } + + if(nodeRef.equals(content2) && !isDeleted) + { + successCount++; + } + return true; + } + + public int getSuccessCount() + { + return successCount; + } + } + +} diff --git a/source/java/org/alfresco/repo/domain/solr/SOLRTransaction.java b/source/java/org/alfresco/repo/domain/solr/SOLRTransaction.java new file mode 100644 index 0000000000..ac6f5f76a5 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/solr/SOLRTransaction.java @@ -0,0 +1,14 @@ +package org.alfresco.repo.domain.solr; + +/** + * Interface for SOLR transaction objects. + * + * @since 4.0 + */ +public interface SOLRTransaction +{ + public Long getId(); + public Long getCommitTimeMs(); + public int getUpdates(); + public int getDeletes(); +} diff --git a/source/java/org/alfresco/repo/domain/solr/SOLRTransactionParameters.java b/source/java/org/alfresco/repo/domain/solr/SOLRTransactionParameters.java new file mode 100644 index 0000000000..4ef21fe183 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/solr/SOLRTransactionParameters.java @@ -0,0 +1,76 @@ +package org.alfresco.repo.domain.solr; + +import java.util.Date; +import java.util.List; + +/** + * Holds parameters for SOLR DAO calls + * + * @since 4.0 + */ +public class SOLRTransactionParameters { + private Long minTxnId; + private Long txnFromCommitTime; + private List transactionIds; + private Long fromNodeId; + private Long toNodeId; + + public SOLRTransactionParameters() + { + } + + public void setMinTxnId(Long minTxnId) + { + this.minTxnId = minTxnId; + } + + public Long getMinTxnId() + { + return minTxnId; + } + + public void setTxnFromCommitTime(Long txnFromCommitTime) { + this.txnFromCommitTime = txnFromCommitTime; + } + + public Long getTxnFromCommitTime() { + return txnFromCommitTime; + } + + public void setTransactionIds(List txnIds) { + this.transactionIds = txnIds; + } + + public List getTransactionIds() { + return transactionIds; + } + + public Long getFromNodeId() { + return fromNodeId; + } + + public void setFromNodeId(Long fromNodeId) { + this.fromNodeId = fromNodeId; + } + + public Long getToNodeId() { + return toNodeId; + } + + public void setToNodeId(Long toNodeId) { + this.toNodeId = toNodeId; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(512); + sb.append("SOLRTransactionParameters") + .append(", txnFromCommitTime").append(txnFromCommitTime == null ? null : new Date(txnFromCommitTime)) + .append(", fromNodeId").append(fromNodeId == null ? null : fromNodeId) + .append(", toNodeId").append(toNodeId == null ? null : toNodeId) + .append(", txnIds").append(transactionIds == null ? null : transactionIds.size()) + .append("]"); + return sb.toString(); + } +} diff --git a/source/java/org/alfresco/repo/domain/solr/Transaction.java b/source/java/org/alfresco/repo/domain/solr/Transaction.java new file mode 100644 index 0000000000..2b118fd1bc --- /dev/null +++ b/source/java/org/alfresco/repo/domain/solr/Transaction.java @@ -0,0 +1,14 @@ +package org.alfresco.repo.domain.solr; + +/** + * Interface for SOLR transaction objects. + * + * @since 4.0 + */ +public interface Transaction +{ + public Long getId(); + public Long getCommitTimeMs(); + public int getUpdates(); + public int getDeletes(); +} diff --git a/source/java/org/alfresco/repo/domain/solr/TransactionEntity.java b/source/java/org/alfresco/repo/domain/solr/TransactionEntity.java new file mode 100644 index 0000000000..92a4d80faa --- /dev/null +++ b/source/java/org/alfresco/repo/domain/solr/TransactionEntity.java @@ -0,0 +1,75 @@ +package org.alfresco.repo.domain.solr; + +/** + * Bean to represent SOLR transaction data. + * + * @since 4.0 + */ +public class TransactionEntity implements Transaction +{ + private Long id; + private Long commitTimeMs; + private int updates; + private int deletes; + + /** + * Required default constructor + */ + public TransactionEntity() + { + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(512); + sb.append("TransactionEntity") + .append("[ ID=").append(id) + .append(", updates=").append(updates) + .append(", deletes=").append(deletes) + .append(", commitTimeMs=").append(commitTimeMs) + .append("]"); + return sb.toString(); + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public int getUpdates() + { + return updates; + } + + public void setUpdates(int updates) + { + this.updates = updates; + } + + public int getDeletes() + { + return deletes; + } + + public void setDeletes(int deletes) + { + this.deletes = deletes; + } + + public Long getCommitTimeMs() + { + return commitTimeMs; + } + + public void setCommitTimeMs(Long commitTimeMs) + { + this.commitTimeMs = commitTimeMs; + } +} + diff --git a/source/java/org/alfresco/repo/domain/solr/ibatis/SOLRDAOImpl.java b/source/java/org/alfresco/repo/domain/solr/ibatis/SOLRDAOImpl.java new file mode 100644 index 0000000000..9b0de872a5 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/solr/ibatis/SOLRDAOImpl.java @@ -0,0 +1,147 @@ +package org.alfresco.repo.domain.solr.ibatis; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.alfresco.repo.domain.node.Node; +import org.alfresco.repo.domain.node.NodeEntity; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.domain.solr.NodeParameters; +import org.alfresco.repo.domain.solr.SOLRDAO; +import org.alfresco.repo.domain.solr.SOLRTransactionParameters; +import org.alfresco.repo.domain.solr.Transaction; +import org.springframework.orm.ibatis.SqlMapClientTemplate; + +/** + * DAO support for SOLR web scripts. + * + * @since 4.0 + */ +public class SOLRDAOImpl implements SOLRDAO +{ + private static final String SELECT_TRANSACTIONS = "alfresco.solr.select_Txns"; + private static final String SELECT_NODES = "alfresco.solr.select_Txn_Nodes"; + + private QNameDAO qnameDAO; + private SqlMapClientTemplate template; + + public void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate) + { + this.template = sqlMapClientTemplate; + } + + public SqlMapClientTemplate getSqlMapClientTemplate() + { + return this.template; + } + + public void setQNameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + + /* + * Initialize + */ + public void init() + { + } + + @SuppressWarnings("unchecked") + public List getTransactions(Long minTxnId, Long fromCommitTime, int maxResults) + { + if(minTxnId == null && fromCommitTime == null && (maxResults == 0 || maxResults == Integer.MAX_VALUE)) + { + throw new IllegalArgumentException("Must specify at least one parameter"); + } + + List txns = null; + SOLRTransactionParameters params = new SOLRTransactionParameters(); + params.setMinTxnId(minTxnId); + params.setTxnFromCommitTime(fromCommitTime); + + if(maxResults != 0 && maxResults != Integer.MAX_VALUE) + { + txns = (List)template.queryForList(SELECT_TRANSACTIONS, params, 0, maxResults); + } + else + { + txns = (List)template.queryForList(SELECT_TRANSACTIONS, params); + } + + return txns; + } + + @SuppressWarnings("unchecked") + // TODO should create qnames if don't exist? + public void getNodes(NodeParameters nodeParameters, int maxResults, NodeQueryCallback callback) + { + List nodes = null; + NodeQueryRowHandler rowHandler = new NodeQueryRowHandler(callback); + + if(nodeParameters.getIncludeTypeIds() == null && nodeParameters.getIncludeNodeTypes() != null) + { + Set qnamesIds = qnameDAO.convertQNamesToIds(nodeParameters.getIncludeNodeTypes(), false); + nodeParameters.setIncludeTypeIds(new ArrayList(qnamesIds)); + } + + if(nodeParameters.getExcludeTypeIds() == null && nodeParameters.getExcludeNodeTypes() != null) + { + Set qnamesIds = qnameDAO.convertQNamesToIds(nodeParameters.getExcludeNodeTypes(), false); + nodeParameters.setExcludeTypeIds(new ArrayList(qnamesIds)); + } + + if(nodeParameters.getExcludeAspectIds() == null && nodeParameters.getExcludeAspects() != null) + { + Set qnamesIds = qnameDAO.convertQNamesToIds(nodeParameters.getExcludeAspects(), false); + nodeParameters.setExcludeAspectIds(new ArrayList(qnamesIds)); + } + + if(nodeParameters.getIncludeAspectIds() == null && nodeParameters.getIncludeAspects() != null) + { + Set qnamesIds = qnameDAO.convertQNamesToIds(nodeParameters.getIncludeAspects(), false); + nodeParameters.setIncludeAspectIds(new ArrayList(qnamesIds)); + } + + if(maxResults != 0 && maxResults != Integer.MAX_VALUE) + { + nodes = (List)template.queryForList(SELECT_NODES, nodeParameters, 0, maxResults); + } + else + { + nodes = (List)template.queryForList(SELECT_NODES, nodeParameters); + } + + for(NodeEntity node : nodes) + { + rowHandler.processResult(node); + } + } + + /** + * Class that passes results from a result entity into the client callback + */ + protected class NodeQueryRowHandler + { + private final NodeQueryCallback callback; + private boolean more; + + private NodeQueryRowHandler(NodeQueryCallback callback) + { + this.callback = callback; + this.more = true; + } + + public void processResult(Node row) + { + if (!more) + { + // No more results required + return; + } + + more = callback.handleNode(row); + } + } +}