restoredByB = nodeArchiveService.restoreAllArchivedNodes(workStoreRef);
+ assertEquals("User B should be able to see only B's delete", 1, restoredByB.size());
+ }
+
+ /**
+ * Deny the current user the rights to write to the destination location
+ * and ensure that the use-case is handled properly.
+ */
+ public void testPermissionsLackingOnDestination() throws Exception
+ {
+ // remove 'b', deny permissions to workspace root and attempt a restore
+ nodeService.deleteNode(b);
+ permissionService.setPermission(workStoreRootNodeRef, USER_B, PermissionService.ADD_CHILDREN, false);
+ commitAndBeginNewTransaction();
+
+ // the restore of b should fail for user B
+ authenticationService.authenticate(USER_B, USER_B.toCharArray());
+ RestoreNodeReport report = nodeArchiveService.restoreArchivedNode(b_);
+ assertEquals("Expected permission denied status", RestoreStatus.FAILURE_PERMISSION, report.getStatus());
+ }
}
diff --git a/source/java/org/alfresco/repo/node/archive/NodeArchiveService.java b/source/java/org/alfresco/repo/node/archive/NodeArchiveService.java
index 72dd318b94..8ba5d9c02e 100644
--- a/source/java/org/alfresco/repo/node/archive/NodeArchiveService.java
+++ b/source/java/org/alfresco/repo/node/archive/NodeArchiveService.java
@@ -40,6 +40,15 @@ public interface NodeArchiveService
*/
public NodeRef getStoreArchiveNode(StoreRef originalStoreRef);
+ /**
+ * Get the likely node reference for the original node. There is no
+ * guarantee that the node exists in the archive store.
+ *
+ * @param originalNodeRef the original node reference
+ * @return Returns the node ref of the node if it was archived.
+ */
+ public NodeRef getArchivedNode(NodeRef originalNodeRef);
+
/**
* Attempt to restore the given archived node into its original location.
*
diff --git a/source/java/org/alfresco/repo/node/archive/NodeArchiveServiceImpl.java b/source/java/org/alfresco/repo/node/archive/NodeArchiveServiceImpl.java
index 0fc0f48441..82840572f5 100644
--- a/source/java/org/alfresco/repo/node/archive/NodeArchiveServiceImpl.java
+++ b/source/java/org/alfresco/repo/node/archive/NodeArchiveServiceImpl.java
@@ -72,6 +72,17 @@ public class NodeArchiveServiceImpl implements NodeArchiveService
return nodeService.getStoreArchiveNode(originalStoreRef);
}
+ public NodeRef getArchivedNode(NodeRef originalNodeRef)
+ {
+ StoreRef orginalStoreRef = originalNodeRef.getStoreRef();
+ NodeRef archiveRootNodeRef = nodeService.getStoreArchiveNode(orginalStoreRef);
+ // create the likely location of the archived node
+ NodeRef archivedNodeRef = new NodeRef(
+ archiveRootNodeRef.getStoreRef(),
+ originalNodeRef.getId());
+ return archivedNodeRef;
+ }
+
/**
* Get all the nodes that were archived from the given store.
*/
diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
index add21c98f5..23224667dc 100644
--- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
+++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
@@ -595,20 +595,6 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
// get the node
Node node = getNodeNotNull(nodeRef);
- // check that the aspect may be removed
- TypeDefinition nodeTypeDef = dictionaryService.getType(node.getTypeQName());
- if (nodeTypeDef == null)
- {
- throw new InvalidNodeRefException("The node type is no longer valid: " + nodeRef, nodeRef);
- }
- List defaultAspects = nodeTypeDef.getDefaultAspects();
- if (defaultAspects.contains(aspectDef))
- {
- throw new InvalidAspectException(
- "The aspect is a default for the node's type and cannot be removed: " + aspectTypeQName,
- aspectTypeQName);
- }
-
// remove the aspect, if present
boolean removed = node.getAspects().remove(aspectTypeQName);
// if the aspect was present, remove the associated properties
@@ -784,7 +770,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
ret.put(propertyQName, value);
}
// spoof referencable properties
- addReferencableProperties(nodeRef, ret);
+ addReferencableProperties(nodeRef, node.getId(), ret);
// done
return ret;
}
@@ -807,6 +793,12 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
// get the property from the node
Node node = getNodeNotNull(nodeRef);
+
+ if (qname.equals(ContentModel.PROP_NODE_DBID))
+ {
+ return node.getId();
+ }
+
Map properties = node.getProperties();
PropertyValue propertyValue = properties.get(qname);
@@ -1310,7 +1302,8 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
ChildAssoc primaryParentAssoc = nodeDaoService.getPrimaryParentAssoc(node);
// add the aspect
- node.getAspects().add(ContentModel.ASPECT_ARCHIVED);
+ Set aspects = node.getAspects();
+ aspects.add(ContentModel.ASPECT_ARCHIVED);
Map properties = node.getProperties();
PropertyValue archivedByProperty = makePropertyValue(
dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_BY),
@@ -1324,6 +1317,21 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC),
primaryParentAssoc.getChildAssocRef());
properties.put(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC, archivedPrimaryParentNodeRefProperty);
+ PropertyValue originalOwnerProperty = properties.get(ContentModel.PROP_OWNER);
+ PropertyValue originalCreatorProperty = properties.get(ContentModel.PROP_CREATOR);
+ if (originalOwnerProperty != null || originalCreatorProperty != null)
+ {
+ properties.put(
+ ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER,
+ originalOwnerProperty != null ? originalOwnerProperty : originalCreatorProperty);
+ }
+
+ // change the node ownership
+ aspects.add(ContentModel.ASPECT_OWNABLE);
+ PropertyValue newOwnerProperty = makePropertyValue(
+ dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER),
+ AuthenticationUtil.getCurrentUserName());
+ properties.put(ContentModel.PROP_OWNER, newOwnerProperty);
// move the node
NodeRef archiveStoreRootNodeRef = getRootNode(archiveStoreRef);
@@ -1557,11 +1565,20 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
ChildAssociationRef originalPrimaryParentAssocRef = (ChildAssociationRef) makeSerializableValue(
dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC),
properties.get(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC));
+ PropertyValue originalOwnerProperty = properties.get(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER);
// remove the aspect archived aspect
aspects.remove(ContentModel.ASPECT_ARCHIVED);
properties.remove(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC);
properties.remove(ContentModel.PROP_ARCHIVED_BY);
properties.remove(ContentModel.PROP_ARCHIVED_DATE);
+ properties.remove(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER);
+
+ // restore the original ownership
+ if (originalOwnerProperty != null)
+ {
+ aspects.add(ContentModel.ASPECT_OWNABLE);
+ properties.put(ContentModel.PROP_OWNER, originalOwnerProperty);
+ }
if (destinationParentNodeRef == null)
{
diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java
index a9f298b566..3c582044ac 100644
--- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java
+++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java
@@ -35,6 +35,7 @@ import org.alfresco.repo.domain.hibernate.NodeStatusImpl;
import org.alfresco.repo.domain.hibernate.StoreImpl;
import org.alfresco.repo.node.db.NodeDaoService;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
+import org.alfresco.repo.transaction.TransactionalDao;
import org.alfresco.service.cmr.dictionary.InvalidTypeException;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
@@ -54,7 +55,7 @@ import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
*
* @author Derek Hulley
*/
-public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements NodeDaoService
+public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements NodeDaoService, TransactionalDao
{
private static final String QUERY_GET_ALL_STORES = "store.GetAllStores";
private static final String QUERY_GET_CONTENT_DATA_STRINGS = "node.GetContentDataStrings";
diff --git a/source/java/org/alfresco/repo/node/integrity/AspectsIntegrityEvent.java b/source/java/org/alfresco/repo/node/integrity/AspectsIntegrityEvent.java
new file mode 100644
index 0000000000..0632dadeca
--- /dev/null
+++ b/source/java/org/alfresco/repo/node/integrity/AspectsIntegrityEvent.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2005 Alfresco, Inc.
+ *
+ * Licensed under the Mozilla Public License version 1.1
+ * with a permitted attribution clause. You may obtain a
+ * copy of the License at
+ *
+ * http://www.alfresco.org/legal/license.txt
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the
+ * License.
+ */
+package org.alfresco.repo.node.integrity;
+
+import java.util.List;
+import java.util.Set;
+
+import org.alfresco.service.cmr.dictionary.AspectDefinition;
+import org.alfresco.service.cmr.dictionary.DictionaryService;
+import org.alfresco.service.cmr.dictionary.TypeDefinition;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.namespace.QName;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Event raised to check nodes' aspects
+ *
+ * @author Derek Hulley
+ */
+public class AspectsIntegrityEvent extends AbstractIntegrityEvent
+{
+ private static Log logger = LogFactory.getLog(AspectsIntegrityEvent.class);
+
+ protected AspectsIntegrityEvent(
+ NodeService nodeService,
+ DictionaryService dictionaryService,
+ NodeRef nodeRef)
+ {
+ super(nodeService, dictionaryService, nodeRef, null, null);
+ }
+
+ public void checkIntegrity(List eventResults)
+ {
+ NodeRef nodeRef = getNodeRef();
+ if (!nodeService.exists(nodeRef))
+ {
+ // node has gone
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Event ignored - node gone: " + this);
+ }
+ eventResults.clear();
+ return;
+ }
+ else
+ {
+ checkMandatoryAspects(getNodeRef(), eventResults);
+ }
+ }
+
+ /**
+ * Checks that the node has the required mandatory aspects applied
+ */
+ private void checkMandatoryAspects(NodeRef nodeRef, List eventResults)
+ {
+ Set aspects = nodeService.getAspects(nodeRef);
+
+ // get the node type
+ QName nodeTypeQName = nodeService.getType(nodeRef);
+ // get the aspects that should exist
+ TypeDefinition typeDef = dictionaryService.getType(nodeTypeQName);
+ List mandatoryAspectDefs = typeDef.getDefaultAspects();
+
+ // check
+ for (AspectDefinition aspect : mandatoryAspectDefs)
+ {
+ if (aspects.contains(aspect.getName()))
+ {
+ // it's fine
+ continue;
+ }
+ IntegrityRecord result = new IntegrityRecord(
+ "Mandatory aspect not set: \n" +
+ " Node: " + nodeRef + "\n" +
+ " Type: " + nodeTypeQName + "\n" +
+ " Aspect: " + aspect.getName());
+ eventResults.add(result);
+ // next one
+ continue;
+ }
+ // done
+ }
+}
diff --git a/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java b/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java
index 95a9d20322..1af48b8f78 100644
--- a/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java
+++ b/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java
@@ -278,25 +278,23 @@ public class IntegrityChecker
*/
public void onCreateNode(ChildAssociationRef childAssocRef)
{
+ NodeRef childRef = childAssocRef.getChildRef();
IntegrityEvent event = null;
// check properties on child node
event = new PropertiesIntegrityEvent(
nodeService,
dictionaryService,
- childAssocRef.getChildRef());
+ childRef);
save(event);
- // check target role
- event = new AssocTargetRoleIntegrityEvent(
- nodeService,
- dictionaryService,
- childAssocRef.getParentRef(),
- childAssocRef.getTypeQName(),
- childAssocRef.getQName());
+ // check that the multiplicity and other properties of the new association are allowed
+ onCreateChildAssociation(childAssocRef);
+
+ // check mandatory aspects
+ event = new AspectsIntegrityEvent(nodeService, dictionaryService, childRef);
save(event);
// check for associations defined on the new node (child)
- NodeRef childRef = childAssocRef.getChildRef();
QName childNodeTypeQName = nodeService.getType(childRef);
ClassDefinition nodeTypeDef = dictionaryService.getClass(childNodeTypeQName);
if (nodeTypeDef == null)
@@ -376,10 +374,15 @@ public class IntegrityChecker
}
/**
- * No checking performed: The property changes will be handled
+ * @see AspectsIntegrityEvent
*/
public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName)
{
+ IntegrityEvent event = null;
+ // check mandatory aspects
+ event = new AspectsIntegrityEvent(nodeService, dictionaryService, nodeRef);
+ save(event);
+
}
/**
diff --git a/source/java/org/alfresco/repo/node/integrity/IntegrityTest.java b/source/java/org/alfresco/repo/node/integrity/IntegrityTest.java
index c036134d4d..036ddbf033 100644
--- a/source/java/org/alfresco/repo/node/integrity/IntegrityTest.java
+++ b/source/java/org/alfresco/repo/node/integrity/IntegrityTest.java
@@ -135,8 +135,22 @@ public class IntegrityTest extends TestCase
public void tearDown() throws Exception
{
- authenticationComponent.clearCurrentSecurityContext();
- txn.rollback();
+ try
+ {
+ authenticationComponent.clearCurrentSecurityContext();
+ }
+ catch (Throwable e)
+ {
+ e.printStackTrace();
+ }
+ try
+ {
+ txn.rollback();
+ }
+ catch (Throwable e)
+ {
+ e.printStackTrace();
+ }
}
/**
@@ -220,6 +234,15 @@ public class IntegrityTest extends TestCase
checkIntegrityNoFailure();
}
+ public void testRemoveMandatoryAspect() throws Exception
+ {
+ NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_ASPECT, allProperties);
+ // just remove the aspect
+ nodeService.removeAspect(nodeRef, TEST_ASPECT_WITH_PROPERTIES);
+
+ checkIntegrityExpectFailure("Failed to removal of mandatory aspect", 1);
+ }
+
public void testCreateTargetOfAssocsWithMandatorySourcesPresent() throws Exception
{
// this is the target of 3 assoc types where the source cardinality is 1..1
diff --git a/source/java/org/alfresco/repo/rule/RuleTypeImpl.java b/source/java/org/alfresco/repo/rule/RuleTypeImpl.java
index 8045eea043..9980872562 100644
--- a/source/java/org/alfresco/repo/rule/RuleTypeImpl.java
+++ b/source/java/org/alfresco/repo/rule/RuleTypeImpl.java
@@ -130,16 +130,8 @@ public class RuleTypeImpl extends CommonResourceAbstractBase implements RuleType
logger.debug("Triggering rule " + rule.getId());
}
- if (rule.getExecuteAsychronously() == true)
- {
- // Execute the rule now since it will be be queued for async execution later
- this.actionService.executeAction(rule, actionedUponNodeRef);
- }
- else
- {
- // Queue the rule to be executed at the end of the transaction (but still in the transaction)
- ((RuntimeRuleService)this.ruleService).addRulePendingExecution(nodeRef, actionedUponNodeRef, rule);
- }
+ // Queue the rule to be executed at the end of the transaction (but still in the transaction)
+ ((RuntimeRuleService)this.ruleService).addRulePendingExecution(nodeRef, actionedUponNodeRef, rule);
}
}
else
diff --git a/source/java/org/alfresco/repo/search/Indexer.java b/source/java/org/alfresco/repo/search/Indexer.java
index 71a38fa741..1de19baf08 100644
--- a/source/java/org/alfresco/repo/search/Indexer.java
+++ b/source/java/org/alfresco/repo/search/Indexer.java
@@ -16,6 +16,7 @@
*/
package org.alfresco.repo.search;
+import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexerImpl;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
@@ -99,7 +100,8 @@ public interface Indexer
* @param relationshipRef
*/
public void deleteChildRelationship(ChildAssociationRef relationshipRef);
-
+
+
}
diff --git a/source/java/org/alfresco/repo/search/IndexerAndSearcher.java b/source/java/org/alfresco/repo/search/IndexerAndSearcher.java
index 16f0094dd2..b92657aad5 100644
--- a/source/java/org/alfresco/repo/search/IndexerAndSearcher.java
+++ b/source/java/org/alfresco/repo/search/IndexerAndSearcher.java
@@ -34,7 +34,7 @@ public interface IndexerAndSearcher
* @return
* @throws IndexerException
*/
- public abstract Indexer getIndexer(StoreRef storeRef) throws IndexerException;
+ public abstract IndexerSPI getIndexer(StoreRef storeRef) throws IndexerException;
/**
* Get a searcher for a store
diff --git a/source/java/org/alfresco/repo/search/IndexerSPI.java b/source/java/org/alfresco/repo/search/IndexerSPI.java
new file mode 100644
index 0000000000..82a6e311ee
--- /dev/null
+++ b/source/java/org/alfresco/repo/search/IndexerSPI.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005 Alfresco, Inc.
+ *
+ * Licensed under the Mozilla Public License version 1.1
+ * with a permitted attribution clause. You may obtain a
+ * copy of the License at
+ *
+ * http://www.alfresco.org/legal/license.txt
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the
+ * License.
+ */
+package org.alfresco.repo.search;
+
+import org.alfresco.repo.search.impl.lucene.fts.FTSIndexerAware;
+
+public interface IndexerSPI extends Indexer
+{
+ public void registerCallBack(FTSIndexerAware callBack);
+
+ public void updateFullTextSearch(int i);
+
+}
diff --git a/source/java/org/alfresco/repo/search/impl/NodeSearcher.java b/source/java/org/alfresco/repo/search/impl/NodeSearcher.java
index 4c7bfda98f..c244a08ff0 100644
--- a/source/java/org/alfresco/repo/search/impl/NodeSearcher.java
+++ b/source/java/org/alfresco/repo/search/impl/NodeSearcher.java
@@ -66,7 +66,7 @@ public class NodeSearcher
/**
* @see NodeServiceXPath
*/
- public synchronized List selectNodes(NodeRef contextNodeRef, String xpathIn,
+ public List selectNodes(NodeRef contextNodeRef, String xpathIn,
QueryParameterDefinition[] paramDefs, NamespacePrefixResolver namespacePrefixResolver,
boolean followAllParentLinks, String language)
{
diff --git a/source/java/org/alfresco/repo/search/impl/lucene/FilterIndexReaderByNodeRefs2.java b/source/java/org/alfresco/repo/search/impl/lucene/FilterIndexReaderByNodeRefs2.java
new file mode 100644
index 0000000000..d19e58a239
--- /dev/null
+++ b/source/java/org/alfresco/repo/search/impl/lucene/FilterIndexReaderByNodeRefs2.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2005 Alfresco, Inc.
+ *
+ * Licensed under the Mozilla Public License version 1.1
+ * with a permitted attribution clause. You may obtain a
+ * copy of the License at
+ *
+ * http://www.alfresco.org/legal/license.txt
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the
+ * License.
+ */
+package org.alfresco.repo.search.impl.lucene;
+
+import java.io.IOException;
+import java.util.BitSet;
+import java.util.Set;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.apache.lucene.index.FilterIndexReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.TermEnum;
+import org.apache.lucene.index.TermPositions;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Hits;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.MultiSearcher;
+import org.apache.lucene.search.Searcher;
+import org.apache.lucene.search.TermQuery;
+
+public class FilterIndexReaderByNodeRefs2 extends FilterIndexReader
+{
+ BitSet deletedDocuments;
+
+ public FilterIndexReaderByNodeRefs2(IndexReader reader, Set deletions, boolean deleteNodesOnly)
+ {
+ super(reader);
+ deletedDocuments = new BitSet(reader.maxDoc());
+
+ try
+ {
+ if (!deleteNodesOnly)
+ {
+ for (NodeRef nodeRef : deletions)
+ {
+ TermDocs td = reader.termDocs(new Term("ID", nodeRef.toString()));
+ while (td.next())
+ {
+ deletedDocuments.set(td.doc(), true);
+ }
+ }
+ }
+ else
+ {
+
+ Searcher searcher = new IndexSearcher(reader);
+ for (NodeRef nodeRef : deletions)
+ {
+ BooleanQuery query = new BooleanQuery();
+ query.add(new TermQuery(new Term("ID", nodeRef.toString())), true, false);
+ query.add(new TermQuery(new Term("ISNODE", "T")), false, false);
+ Hits hits = searcher.search(query);
+ if (hits.length() > 0)
+ {
+ for (int i = 0; i < hits.length(); i++)
+ {
+ deletedDocuments.set(hits.id(i), true);
+ }
+ }
+ }
+
+ }
+ }
+ catch (IOException e)
+ {
+ throw new AlfrescoRuntimeException("Failed to construct filtering index reader", e);
+ }
+ }
+
+ public static class FilterTermDocs implements TermDocs
+ {
+ BitSet deletedDocuments;
+
+ protected TermDocs in;
+
+ public FilterTermDocs(TermDocs in, BitSet deletedDocuments)
+ {
+ this.in = in;
+ this.deletedDocuments = deletedDocuments;
+ }
+
+ public void seek(Term term) throws IOException
+ {
+ // Seek is left to the base implementation
+ in.seek(term);
+ }
+
+ public void seek(TermEnum termEnum) throws IOException
+ {
+ // Seek is left to the base implementation
+ in.seek(termEnum);
+ }
+
+ public int doc()
+ {
+ // The current document info is valid in the base implementation
+ return in.doc();
+ }
+
+ public int freq()
+ {
+ // The frequency is valid in the base implementation
+ return in.freq();
+ }
+
+ public boolean next() throws IOException
+ {
+ while (in.next())
+ {
+ if (!deletedDocuments.get(in.doc()))
+ {
+ // Not masked
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int read(int[] docs, int[] freqs) throws IOException
+ {
+ int[] innerDocs = new int[docs.length];
+ int[] innerFreq = new int[docs.length];
+ int count = in.read(innerDocs, innerFreq);
+
+ // Is the stream exhausted
+ if (count == 0)
+ {
+ return 0;
+ }
+
+ if (allDeleted(innerDocs, count))
+ {
+ // Did not find anything - try again
+ return read(docs, freqs);
+ }
+
+ // Add non deleted
+
+ int insertPosition = 0;
+ for (int i = 0; i < count; i++)
+ {
+ if (!deletedDocuments.get(innerDocs[i]))
+ {
+ docs[insertPosition] = innerDocs[i];
+ freqs[insertPosition] = innerFreq[i];
+ insertPosition++;
+ }
+ }
+
+ return insertPosition;
+ }
+
+ private boolean allDeleted(int[] docs, int fillSize)
+ {
+ for (int i = 0; i < fillSize; i++)
+ {
+ if (!deletedDocuments.get(docs[i]))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean skipTo(int i) throws IOException
+ {
+ boolean result = in.skipTo(i);
+ if (result == false)
+ {
+ return false;
+ }
+
+ if (deletedDocuments.get(in.doc()))
+ {
+ return skipTo(i);
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ public void close() throws IOException
+ {
+ // Leave to internal implementation
+ in.close();
+ }
+ }
+
+ /** Base class for filtering {@link TermPositions} implementations. */
+ public static class FilterTermPositions extends FilterTermDocs implements TermPositions
+ {
+
+ public FilterTermPositions(TermPositions in, BitSet deletedDocuements)
+ {
+ super(in, deletedDocuements);
+ }
+
+ public int nextPosition() throws IOException
+ {
+ return ((TermPositions) this.in).nextPosition();
+ }
+ }
+
+ @Override
+ public int numDocs()
+ {
+ return super.numDocs() - deletedDocuments.cardinality();
+ }
+
+ @Override
+ public TermDocs termDocs() throws IOException
+ {
+ return new FilterTermDocs(super.termDocs(), deletedDocuments);
+ }
+
+ @Override
+ public TermPositions termPositions() throws IOException
+ {
+ return new FilterTermPositions(super.termPositions(), deletedDocuments);
+ }
+}
diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneBase2.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneBase2.java
new file mode 100644
index 0000000000..4db47d16c4
--- /dev/null
+++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneBase2.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2005 Alfresco, Inc.
+ *
+ * Licensed under the Mozilla Public License version 1.1
+ * with a permitted attribution clause. You may obtain a
+ * copy of the License at
+ *
+ * http://www.alfresco.org/legal/license.txt
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the
+ * License.
+ */
+package org.alfresco.repo.search.impl.lucene;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Set;
+
+import org.alfresco.repo.search.IndexerException;
+import org.alfresco.repo.search.impl.lucene.index.IndexInfo;
+import org.alfresco.repo.search.impl.lucene.index.TransactionStatus;
+import org.alfresco.service.cmr.dictionary.DictionaryService;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.apache.log4j.Logger;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Searcher;
+
+/**
+ * Common support for abstracting the lucene indexer from its configuration and management requirements.
+ *
+ *
+ * This class defines where the indexes are stored. This should be via a configurable Bean property in Spring.
+ *
+ *
+ * The default file structure is
+ *
+ * - "base"/"protocol"/"name"/ for the main index
+ *
- "base"/"protocol"/"name"/deltas/"id" for transactional updates
+ *
- "base"/"protocol"/"name"/undo/"id" undo information
+ *
+ *
+ *
+ * The IndexWriter and IndexReader for a given index are toggled (one should be used for delete and the other for write). These are reused/closed/initialised as required.
+ *
+ *
+ * The index deltas are buffered to memory and persisted in the file system as required.
+ *
+ * @author Andy Hind
+ *
+ */
+
+public abstract class LuceneBase2
+{
+ private static Logger s_logger = Logger.getLogger(LuceneBase2.class);
+
+ private IndexInfo indexInfo;
+
+ /**
+ * The identifier for the store
+ */
+
+ protected StoreRef store;
+
+ /**
+ * The identifier for the delta
+ */
+
+ protected String deltaId;
+
+ private LuceneConfig config;
+
+ // "lucene-indexes";
+
+ /**
+ * Initialise the configuration elements of the lucene store indexers and searchers.
+ *
+ * @param store
+ * @param deltaId
+ * @throws IOException
+ */
+ protected void initialise(StoreRef store, String deltaId, boolean createMain, boolean createDelta)
+ throws LuceneIndexException
+ {
+ this.store = store;
+ this.deltaId = deltaId;
+
+ String basePath = getBasePath();
+ File baseDir = new File(basePath);
+ indexInfo = IndexInfo.getIndexInfo(baseDir);
+ try
+ {
+ if (deltaId != null)
+ {
+ indexInfo.setStatus(deltaId, TransactionStatus.ACTIVE, null, null);
+ }
+ }
+ catch (IOException e)
+ {
+ throw new IndexerException("Filed to set delta as active");
+ }
+
+ }
+
+ /**
+ * Utility method to find the path to the base index
+ *
+ * @return
+ */
+ private String getBasePath()
+ {
+ if (config.getIndexRootLocation() == null)
+ {
+ throw new IndexerException("No configuration for index location");
+ }
+ String basePath = config.getIndexRootLocation()
+ + File.separator + store.getProtocol() + File.separator + store.getIdentifier() + File.separator;
+ return basePath;
+ }
+
+ /**
+ * Get a searcher for the main index TODO: Split out support for the main index. We really only need this if we want to search over the changing index before it is committed
+ *
+ * @return
+ * @throws IOException
+ */
+
+ protected IndexSearcher getSearcher() throws LuceneIndexException
+ {
+ try
+ {
+ return new ClosingIndexSearcher(indexInfo.getMainIndexReferenceCountingReadOnlyIndexReader());
+ }
+ catch (IOException e)
+ {
+ s_logger.error("Error", e);
+ throw new LuceneIndexException("Failed to open IndexSarcher for " + getBasePath(), e);
+ }
+ }
+
+ protected Searcher getSearcher(LuceneIndexer2 luceneIndexer) throws LuceneIndexException
+ {
+ // If we know the delta id we should do better
+
+ try
+ {
+ if (luceneIndexer == null)
+ {
+ return new ClosingIndexSearcher(indexInfo.getMainIndexReferenceCountingReadOnlyIndexReader());
+ }
+ else
+ {
+ // TODO: Create appropriate reader that lies about deletions
+ // from the first
+ //
+ luceneIndexer.flushPending();
+ return new ClosingIndexSearcher(indexInfo.getMainIndexReferenceCountingReadOnlyIndexReader(deltaId,
+ luceneIndexer.getDeletions(), luceneIndexer.getDeleteOnlyNodes()));
+ }
+
+ }
+ catch (IOException e)
+ {
+ s_logger.error("Error", e);
+ throw new LuceneIndexException("Failed to open IndexSarcher for " + getBasePath(), e);
+ }
+ }
+
+ /**
+ * Get a reader for the on file portion of the delta
+ *
+ * @return
+ * @throws IOException
+ * @throws IOException
+ */
+
+ protected IndexReader getDeltaReader() throws LuceneIndexException, IOException
+ {
+ return indexInfo.getDeltaIndexReader(deltaId);
+ }
+
+ /**
+ * Close the on file reader for the delta if it is open
+ *
+ * @throws IOException
+ *
+ * @throws IOException
+ */
+
+ protected void closeDeltaReader() throws LuceneIndexException, IOException
+ {
+ indexInfo.closeDeltaIndexReader(deltaId);
+ }
+
+ /**
+ * Get the on file writer for the delta
+ *
+ * @return
+ * @throws IOException
+ * @throws IOException
+ */
+ protected IndexWriter getDeltaWriter() throws LuceneIndexException, IOException
+ {
+ return indexInfo.getDeltaIndexWriter(deltaId, new LuceneAnalyser(dictionaryService));
+ }
+
+ /**
+ * Close the on disk delta writer
+ *
+ * @throws IOException
+ *
+ * @throws IOException
+ */
+
+ protected void closeDeltaWriter() throws LuceneIndexException, IOException
+ {
+ indexInfo.closeDeltaIndexWriter(deltaId);
+ }
+
+ /**
+ * Save the in memory delta to the disk, make sure there is nothing held in memory
+ *
+ * @throws IOException
+ *
+ * @throws IOException
+ */
+ protected void saveDelta() throws LuceneIndexException, IOException
+ {
+ // Only one should exist so we do not need error trapping to execute the
+ // other
+ closeDeltaReader();
+ closeDeltaWriter();
+ }
+
+ protected void setInfo(long docs, Set deletions, boolean deleteNodesOnly) throws IOException
+ {
+ indexInfo.setPreparedState(deltaId, deletions, docs, deleteNodesOnly);
+ }
+
+ protected void setStatus(TransactionStatus status) throws IOException
+ {
+ indexInfo.setStatus(deltaId, status, null, null);
+ }
+
+ private DictionaryService dictionaryService;
+
+ protected IndexReader getReader() throws LuceneIndexException, IOException
+ {
+ return indexInfo.getMainIndexReferenceCountingReadOnlyIndexReader();
+ }
+
+ public void setDictionaryService(DictionaryService dictionaryService)
+ {
+ this.dictionaryService = dictionaryService;
+ }
+
+ public DictionaryService getDictionaryService()
+ {
+ return dictionaryService;
+ }
+
+ public void setLuceneConfig(LuceneConfig config)
+ {
+ this.config = config;
+ }
+
+ public LuceneConfig getLuceneConfig()
+ {
+ return config;
+ }
+
+ public String getDeltaId()
+ {
+ return deltaId;
+ }
+
+}
diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneCategoryTest2.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneCategoryTest2.java
new file mode 100644
index 0000000000..2341ec5820
--- /dev/null
+++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneCategoryTest2.java
@@ -0,0 +1,672 @@
+/*
+ * Copyright (C) 2005 Alfresco, Inc.
+ *
+ * Licensed under the Mozilla Public License version 1.1
+ * with a permitted attribution clause. You may obtain a
+ * copy of the License at
+ *
+ * http://www.alfresco.org/legal/license.txt
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the
+ * License.
+ */
+package org.alfresco.repo.search.impl.lucene;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Random;
+
+import javax.transaction.UserTransaction;
+
+import junit.framework.TestCase;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.dictionary.DictionaryDAO;
+import org.alfresco.repo.dictionary.M2Aspect;
+import org.alfresco.repo.dictionary.M2Model;
+import org.alfresco.repo.dictionary.M2Property;
+import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer;
+import org.alfresco.repo.search.transaction.LuceneIndexLock;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.dictionary.DictionaryService;
+import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
+import org.alfresco.service.cmr.repository.ChildAssociationRef;
+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.CategoryService;
+import org.alfresco.service.cmr.search.ResultSet;
+import org.alfresco.service.cmr.search.ResultSetRow;
+import org.alfresco.service.cmr.search.SearchService;
+import org.alfresco.service.namespace.DynamicNamespacePrefixResolver;
+import org.alfresco.service.namespace.NamespacePrefixResolver;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.ApplicationContextHelper;
+import org.springframework.context.ApplicationContext;
+
+public class LuceneCategoryTest2 extends TestCase
+{
+ private ServiceRegistry serviceRegistry;
+
+ static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
+ NodeService nodeService;
+ DictionaryService dictionaryService;
+ LuceneIndexLock luceneIndexLock;
+ private NodeRef rootNodeRef;
+ private NodeRef n1;
+ private NodeRef n2;
+ private NodeRef n3;
+ private NodeRef n4;
+ private NodeRef n6;
+ private NodeRef n5;
+ private NodeRef n7;
+ private NodeRef n8;
+ private NodeRef n9;
+ private NodeRef n10;
+ private NodeRef n11;
+ private NodeRef n12;
+ private NodeRef n13;
+ private NodeRef n14;
+
+ private NodeRef catContainer;
+ private NodeRef catRoot;
+ private NodeRef catACBase;
+ private NodeRef catACOne;
+ private NodeRef catACTwo;
+ private NodeRef catACThree;
+ private FullTextSearchIndexer luceneFTS;
+ private DictionaryDAO dictionaryDAO;
+ private String TEST_NAMESPACE = "http://www.alfresco.org/test/lucenecategorytest";
+ private QName regionCategorisationQName;
+ private QName assetClassCategorisationQName;
+ private QName investmentRegionCategorisationQName;
+ private QName marketingRegionCategorisationQName;
+ private NodeRef catRBase;
+ private NodeRef catROne;
+ private NodeRef catRTwo;
+ private NodeRef catRThree;
+ private SearchService searcher;
+ private LuceneIndexerAndSearcher indexerAndSearcher;
+
+ private CategoryService categoryService;
+
+ public LuceneCategoryTest2()
+ {
+ super();
+ }
+
+ public LuceneCategoryTest2(String arg0)
+ {
+ super(arg0);
+ }
+
+ public void setUp() throws Exception
+ {
+ nodeService = (NodeService)ctx.getBean("dbNodeService");
+ luceneIndexLock = (LuceneIndexLock)ctx.getBean("luceneIndexLock");
+ dictionaryService = (DictionaryService)ctx.getBean("dictionaryService");
+ luceneFTS = (FullTextSearchIndexer) ctx.getBean("LuceneFullTextSearchIndexer");
+ dictionaryDAO = (DictionaryDAO) ctx.getBean("dictionaryDAO");
+ searcher = (SearchService) ctx.getBean("searchService");
+ indexerAndSearcher = (LuceneIndexerAndSearcher) ctx.getBean("luceneIndexerAndSearcherFactory");
+ categoryService = (CategoryService) ctx.getBean("categoryService");
+ serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
+
+ createTestTypes();
+
+ TransactionService transactionService = serviceRegistry.getTransactionService();
+ UserTransaction tx = transactionService.getUserTransaction();
+ tx.begin();
+
+ StoreRef storeRef = nodeService.createStore(
+ StoreRef.PROTOCOL_WORKSPACE,
+ "Test_" + System.currentTimeMillis());
+ rootNodeRef = nodeService.getRootNode(storeRef);
+
+ n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_CONTAINER).getChildRef();
+ nodeService.setProperty(n1, QName.createQName("{namespace}property-1"), "value-1");
+ n2 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}two"), ContentModel.TYPE_CONTAINER).getChildRef();
+ nodeService.setProperty(n2, QName.createQName("{namespace}property-1"), "value-1");
+ nodeService.setProperty(n2, QName.createQName("{namespace}property-2"), "value-2");
+ n3 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}three"), ContentModel.TYPE_CONTAINER).getChildRef();
+ n4 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}four"), ContentModel.TYPE_CONTAINER).getChildRef();
+ n5 = nodeService.createNode(n1, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}five"), ContentModel.TYPE_CONTAINER).getChildRef();
+ n6 = nodeService.createNode(n1, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}six"), ContentModel.TYPE_CONTAINER).getChildRef();
+ n7 = nodeService.createNode(n2, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}seven"), ContentModel.TYPE_CONTAINER).getChildRef();
+ n8 = nodeService.createNode(n2, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}eight-2"), ContentModel.TYPE_CONTAINER).getChildRef();
+ n9 = nodeService.createNode(n5, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}nine"), ContentModel.TYPE_CONTAINER).getChildRef();
+ n10 = nodeService.createNode(n5, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}ten"), ContentModel.TYPE_CONTAINER).getChildRef();
+ n11 = nodeService.createNode(n5, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}eleven"), ContentModel.TYPE_CONTAINER).getChildRef();
+ n12 = nodeService.createNode(n5, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}twelve"), ContentModel.TYPE_CONTAINER).getChildRef();
+ n13 = nodeService.createNode(n12, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}thirteen"), ContentModel.TYPE_CONTAINER).getChildRef();
+ n14 = nodeService.createNode(n13, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}fourteen"), ContentModel.TYPE_CONTAINER).getChildRef();
+
+ nodeService.addChild(rootNodeRef, n8, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}eight-0"));
+ nodeService.addChild(n1, n8, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}eight-1"));
+ nodeService.addChild(n2, n13, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}link"));
+
+ nodeService.addChild(n1, n14, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}common"));
+ nodeService.addChild(n2, n14, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}common"));
+ nodeService.addChild(n5, n14, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}common"));
+ nodeService.addChild(n6, n14, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}common"));
+ nodeService.addChild(n12, n14, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}common"));
+ nodeService.addChild(n13, n14, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}common"));
+
+ // Categories
+
+ catContainer = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "categoryContainer"), ContentModel.TYPE_CONTAINER).getChildRef();
+ catRoot = nodeService.createNode(catContainer, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "categoryRoot"), ContentModel.TYPE_CATEGORYROOT).getChildRef();
+
+
+
+ catRBase = nodeService.createNode(catRoot, ContentModel.ASSOC_CATEGORIES, QName.createQName(TEST_NAMESPACE, "Region"), ContentModel.TYPE_CATEGORY).getChildRef();
+ catROne = nodeService.createNode(catRBase, ContentModel.ASSOC_SUBCATEGORIES, QName.createQName(TEST_NAMESPACE, "Europe"), ContentModel.TYPE_CATEGORY).getChildRef();
+ catRTwo = nodeService.createNode(catRBase, ContentModel.ASSOC_SUBCATEGORIES, QName.createQName(TEST_NAMESPACE, "RestOfWorld"), ContentModel.TYPE_CATEGORY).getChildRef();
+ catRThree = nodeService.createNode(catRTwo, ContentModel.ASSOC_SUBCATEGORIES, QName.createQName(TEST_NAMESPACE, "US"), ContentModel.TYPE_CATEGORY).getChildRef();
+
+ nodeService.addChild(catRoot, catRBase, ContentModel.ASSOC_CATEGORIES, QName.createQName(TEST_NAMESPACE, "InvestmentRegion"));
+ nodeService.addChild(catRoot, catRBase, ContentModel.ASSOC_CATEGORIES, QName.createQName(TEST_NAMESPACE, "MarketingRegion"));
+
+
+ catACBase = nodeService.createNode(catRoot, ContentModel.ASSOC_CATEGORIES, QName.createQName(TEST_NAMESPACE, "AssetClass"), ContentModel.TYPE_CATEGORY).getChildRef();
+ catACOne = nodeService.createNode(catACBase, ContentModel.ASSOC_SUBCATEGORIES, QName.createQName(TEST_NAMESPACE, "Fixed"), ContentModel.TYPE_CATEGORY).getChildRef();
+ catACTwo = nodeService.createNode(catACBase, ContentModel.ASSOC_SUBCATEGORIES, QName.createQName(TEST_NAMESPACE, "Equity"), ContentModel.TYPE_CATEGORY).getChildRef();
+ catACThree = nodeService.createNode(catACTwo, ContentModel.ASSOC_SUBCATEGORIES, QName.createQName(TEST_NAMESPACE, "SpecialEquity"), ContentModel.TYPE_CATEGORY).getChildRef();
+
+
+
+ nodeService.addAspect(n1, assetClassCategorisationQName, createMap("assetClass", catACBase));
+ nodeService.addAspect(n1, regionCategorisationQName, createMap("region", catRBase));
+
+ nodeService.addAspect(n2, assetClassCategorisationQName, createMap("assetClass", catACOne));
+ nodeService.addAspect(n3, assetClassCategorisationQName, createMap("assetClass", catACOne));
+ nodeService.addAspect(n4, assetClassCategorisationQName, createMap("assetClass", catACOne));
+ nodeService.addAspect(n5, assetClassCategorisationQName, createMap("assetClass", catACOne));
+ nodeService.addAspect(n6, assetClassCategorisationQName, createMap("assetClass", catACOne));
+
+ nodeService.addAspect(n7, assetClassCategorisationQName, createMap("assetClass", catACTwo));
+ nodeService.addAspect(n8, assetClassCategorisationQName, createMap("assetClass", catACTwo));
+ nodeService.addAspect(n9, assetClassCategorisationQName, createMap("assetClass", catACTwo));
+ nodeService.addAspect(n10, assetClassCategorisationQName, createMap("assetClass", catACTwo));
+ nodeService.addAspect(n11, assetClassCategorisationQName, createMap("assetClass", catACTwo));
+
+ nodeService.addAspect(n12, assetClassCategorisationQName, createMap("assetClass", catACOne, catACTwo));
+ nodeService.addAspect(n13, assetClassCategorisationQName, createMap("assetClass", catACOne, catACTwo, catACThree));
+ nodeService.addAspect(n14, assetClassCategorisationQName, createMap("assetClass", catACOne, catACTwo));
+
+ nodeService.addAspect(n2, regionCategorisationQName, createMap("region", catROne));
+ nodeService.addAspect(n3, regionCategorisationQName, createMap("region", catROne));
+ nodeService.addAspect(n4, regionCategorisationQName, createMap("region", catRTwo));
+ nodeService.addAspect(n5, regionCategorisationQName, createMap("region", catRTwo));
+
+ nodeService.addAspect(n5, investmentRegionCategorisationQName, createMap("investmentRegion", catRBase));
+ nodeService.addAspect(n5, marketingRegionCategorisationQName, createMap("marketingRegion", catRBase));
+ nodeService.addAspect(n6, investmentRegionCategorisationQName, createMap("investmentRegion", catRBase));
+ nodeService.addAspect(n7, investmentRegionCategorisationQName, createMap("investmentRegion", catRBase));
+ nodeService.addAspect(n8, investmentRegionCategorisationQName, createMap("investmentRegion", catRBase));
+ nodeService.addAspect(n9, investmentRegionCategorisationQName, createMap("investmentRegion", catRBase));
+ nodeService.addAspect(n10, marketingRegionCategorisationQName, createMap("marketingRegion", catRBase));
+ nodeService.addAspect(n11, marketingRegionCategorisationQName, createMap("marketingRegion", catRBase));
+ nodeService.addAspect(n12, marketingRegionCategorisationQName, createMap("marketingRegion", catRBase));
+ nodeService.addAspect(n13, marketingRegionCategorisationQName, createMap("marketingRegion", catRBase));
+ nodeService.addAspect(n14, marketingRegionCategorisationQName, createMap("marketingRegion", catRBase));
+
+ tx.commit();
+ }
+
+ private HashMap createMap(String name, NodeRef[] nodeRefs)
+ {
+ HashMap map = new HashMap();
+ Serializable value = (Serializable) Arrays.asList(nodeRefs);
+ map.put(QName.createQName(TEST_NAMESPACE, name), value);
+ return map;
+ }
+
+ private HashMap createMap(String name, NodeRef nodeRef)
+ {
+ return createMap(name, new NodeRef[]{nodeRef});
+ }
+
+ private HashMap createMap(String name, NodeRef nodeRef1, NodeRef nodeRef2)
+ {
+ return createMap(name, new NodeRef[]{nodeRef1, nodeRef2});
+ }
+
+ private HashMap createMap(String name, NodeRef nodeRef1, NodeRef nodeRef2, NodeRef nodeRef3)
+ {
+ return createMap(name, new NodeRef[]{nodeRef1, nodeRef2, nodeRef3});
+ }
+
+ private void createTestTypes()
+ {
+ M2Model model = M2Model.createModel("test:lucenecategory");
+ model.createNamespace(TEST_NAMESPACE, "test");
+ model.createImport(NamespaceService.DICTIONARY_MODEL_1_0_URI, NamespaceService.DICTIONARY_MODEL_PREFIX);
+ model.createImport(NamespaceService.CONTENT_MODEL_1_0_URI, NamespaceService.CONTENT_MODEL_PREFIX);
+
+ regionCategorisationQName = QName.createQName(TEST_NAMESPACE, "Region");
+ M2Aspect generalCategorisation = model.createAspect("test:" + regionCategorisationQName.getLocalName());
+ generalCategorisation.setParentName("cm:" + ContentModel.ASPECT_CLASSIFIABLE.getLocalName());
+ M2Property genCatProp = generalCategorisation.createProperty("test:region");
+ genCatProp.setIndexed(true);
+ genCatProp.setIndexedAtomically(true);
+ genCatProp.setMandatory(true);
+ genCatProp.setMultiValued(true);
+ genCatProp.setStoredInIndex(true);
+ genCatProp.setTokenisedInIndex(true);
+ genCatProp.setType("d:" + DataTypeDefinition.CATEGORY.getLocalName());
+
+ assetClassCategorisationQName = QName.createQName(TEST_NAMESPACE, "AssetClass");
+ M2Aspect assetClassCategorisation = model.createAspect("test:" + assetClassCategorisationQName.getLocalName());
+ assetClassCategorisation.setParentName("cm:" + ContentModel.ASPECT_CLASSIFIABLE.getLocalName());
+ M2Property acProp = assetClassCategorisation.createProperty("test:assetClass");
+ acProp.setIndexed(true);
+ acProp.setIndexedAtomically(true);
+ acProp.setMandatory(true);
+ acProp.setMultiValued(true);
+ acProp.setStoredInIndex(true);
+ acProp.setTokenisedInIndex(true);
+ acProp.setType("d:" + DataTypeDefinition.CATEGORY.getLocalName());
+
+ investmentRegionCategorisationQName = QName.createQName(TEST_NAMESPACE, "InvestmentRegion");
+ M2Aspect investmentRegionCategorisation = model.createAspect("test:" + investmentRegionCategorisationQName.getLocalName());
+ investmentRegionCategorisation.setParentName("cm:" + ContentModel.ASPECT_CLASSIFIABLE.getLocalName());
+ M2Property irProp = investmentRegionCategorisation.createProperty("test:investmentRegion");
+ irProp.setIndexed(true);
+ irProp.setIndexedAtomically(true);
+ irProp.setMandatory(true);
+ irProp.setMultiValued(true);
+ irProp.setStoredInIndex(true);
+ irProp.setTokenisedInIndex(true);
+ irProp.setType("d:" + DataTypeDefinition.CATEGORY.getLocalName());
+
+ marketingRegionCategorisationQName = QName.createQName(TEST_NAMESPACE, "MarketingRegion");
+ M2Aspect marketingRegionCategorisation = model.createAspect("test:" + marketingRegionCategorisationQName.getLocalName());
+ marketingRegionCategorisation.setParentName("cm:" + ContentModel.ASPECT_CLASSIFIABLE.getLocalName());
+ M2Property mrProp = marketingRegionCategorisation.createProperty("test:marketingRegion");
+ mrProp.setIndexed(true);
+ mrProp.setIndexedAtomically(true);
+ mrProp.setMandatory(true);
+ mrProp.setMultiValued(true);
+ mrProp.setStoredInIndex(true);
+ mrProp.setTokenisedInIndex(true);
+ mrProp.setType("d:" + DataTypeDefinition.CATEGORY.getLocalName());
+
+ dictionaryDAO.putModel(model);
+ }
+
+ private void buildBaseIndex()
+ {
+ LuceneIndexerImpl2 indexer = LuceneIndexerImpl2.getUpdateIndexer(rootNodeRef.getStoreRef(), "delta" + System.currentTimeMillis() + "_" + (new Random().nextInt()), indexerAndSearcher);
+ indexer.setNodeService(nodeService);
+ //indexer.setLuceneIndexLock(luceneIndexLock);
+ indexer.setDictionaryService(dictionaryService);
+ indexer.setLuceneFullTextSearchIndexer(luceneFTS);
+ //indexer.clearIndex();
+ indexer.createNode(new ChildAssociationRef(null, null, null, rootNodeRef));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, rootNodeRef, QName.createQName("{namespace}one"), n1));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, rootNodeRef, QName.createQName("{namespace}two"), n2));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, rootNodeRef, QName.createQName("{namespace}three"), n3));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, rootNodeRef, QName.createQName("{namespace}four"), n4));
+
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, rootNodeRef, QName.createQName("{namespace}categoryContainer"), catContainer));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, catContainer, QName.createQName("{cat}categoryRoot"), catRoot));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CATEGORIES, catRoot, QName.createQName(TEST_NAMESPACE, "AssetClass"), catACBase));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_SUBCATEGORIES, catACBase, QName.createQName(TEST_NAMESPACE, "Fixed"), catACOne));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_SUBCATEGORIES, catACBase, QName.createQName(TEST_NAMESPACE, "Equity"), catACTwo));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_SUBCATEGORIES, catACTwo, QName.createQName(TEST_NAMESPACE, "SpecialEquity"), catACThree));
+
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CATEGORIES, catRoot, QName.createQName(TEST_NAMESPACE, "Region"), catRBase));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_SUBCATEGORIES, catRBase, QName.createQName(TEST_NAMESPACE, "Europe"), catROne));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_SUBCATEGORIES, catRBase, QName.createQName(TEST_NAMESPACE, "RestOfWorld"), catRTwo));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_SUBCATEGORIES, catRTwo, QName.createQName(TEST_NAMESPACE, "US"), catRThree));
+
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CATEGORIES, n1, QName.createQName("{namespace}five"), n5));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CATEGORIES, n1, QName.createQName("{namespace}six"), n6));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CATEGORIES, n2, QName.createQName("{namespace}seven"), n7));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CATEGORIES, n2, QName.createQName("{namespace}eight"), n8));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CATEGORIES, n5, QName.createQName("{namespace}nine"), n9));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CATEGORIES, n5, QName.createQName("{namespace}ten"), n10));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CATEGORIES, n5, QName.createQName("{namespace}eleven"), n11));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CATEGORIES, n5, QName.createQName("{namespace}twelve"), n12));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CATEGORIES, n12, QName.createQName("{namespace}thirteen"), n13));
+ indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CATEGORIES, n13, QName.createQName("{namespace}fourteen"), n14));
+ indexer.prepare();
+ indexer.commit();
+ }
+
+
+ public void testMulti() throws Exception
+ {
+ TransactionService transactionService = serviceRegistry.getTransactionService();
+ UserTransaction tx = transactionService.getUserTransaction();
+ tx.begin();
+ buildBaseIndex();
+
+ LuceneSearcherImpl2 searcher = LuceneSearcherImpl2.getSearcher(rootNodeRef.getStoreRef(), indexerAndSearcher);
+
+ searcher.setNodeService(nodeService);
+ searcher.setDictionaryService(dictionaryService);
+ searcher.setNamespacePrefixResolver(getNamespacePrefixReolsver(""));
+ ResultSet results;
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"//*\" AND (PATH:\"/test:AssetClass/test:Equity/member\" PATH:\"/test:MarketingRegion/member\")", null, null);
+ //printPaths(results);
+ assertEquals(9, results.length());
+ results.close();
+ tx.rollback();
+ }
+
+ public void testBasic() throws Exception
+ {
+ TransactionService transactionService = serviceRegistry.getTransactionService();
+ UserTransaction tx = transactionService.getUserTransaction();
+ tx.begin();
+ buildBaseIndex();
+
+ LuceneSearcherImpl2 searcher = LuceneSearcherImpl2.getSearcher(rootNodeRef.getStoreRef(), indexerAndSearcher);
+
+ searcher.setNodeService(nodeService);
+ searcher.setDictionaryService(dictionaryService);
+ searcher.setNamespacePrefixResolver(getNamespacePrefixReolsver(""));
+ ResultSet results;
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:MarketingRegion\"", null, null);
+ //printPaths(results);
+ assertEquals(1, results.length());
+ results.close();
+
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:MarketingRegion//member\"", null, null);
+ //printPaths(results);
+ assertEquals(6, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/cm:categoryContainer\"", null, null);
+ assertEquals(1, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/cm:categoryContainer/cm:categoryRoot\"", null, null);
+ assertEquals(1, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/cm:categoryContainer/cm:categoryRoot\"", null, null);
+ assertEquals(1, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/cm:categoryContainer/cm:categoryRoot/test:AssetClass\"", null, null);
+ assertEquals(1, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/cm:categoryContainer/cm:categoryRoot/test:AssetClass/member\" ", null, null);
+ assertEquals(1, results.length());
+ results.close();
+
+
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/cm:categoryContainer/cm:categoryRoot/test:AssetClass/test:Fixed\"", null, null);
+ assertEquals(1, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/cm:categoryContainer/cm:categoryRoot/test:AssetClass/test:Equity\"", null, null);
+ assertEquals(1, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:AssetClass\"", null, null);
+ assertEquals(1, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:AssetClass/test:Fixed\"", null, null);
+ assertEquals(1, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:AssetClass/test:Equity\"", null, null);
+ assertEquals(1, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:AssetClass/test:*\"", null, null);
+ assertEquals(2, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:AssetClass//test:*\"", null, null);
+ assertEquals(3, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:AssetClass/test:Fixed/member\"", null, null);
+ //printPaths(results);
+ assertEquals(8, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:AssetClass/test:Equity/member\"", null, null);
+ //printPaths(results);
+ assertEquals(8, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:AssetClass/test:Equity/test:SpecialEquity/member//.\"", null, null);
+ assertEquals(1, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:AssetClass/test:Equity/test:SpecialEquity/member//*\"", null, null);
+ assertEquals(0, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:AssetClass/test:Equity/test:SpecialEquity/member\"", null, null);
+ assertEquals(1, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "+PATH:\"/test:AssetClass/test:Equity/member\" AND +PATH:\"/test:AssetClass/test:Fixed/member\"", null, null);
+ //printPaths(results);
+ assertEquals(3, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:AssetClass/test:Equity/member\" PATH:\"/test:AssetClass/test:Fixed/member\"", null, null);
+ //printPaths(results);
+ assertEquals(13, results.length());
+ results.close();
+
+ // Region
+
+ assertEquals(4, nodeService.getChildAssocs(catRoot).size());
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:Region\"", null, null);
+ //printPaths(results);
+ assertEquals(1, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:Region/member\"", null, null);
+ //printPaths(results);
+ assertEquals(1, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:Region/test:Europe/member\"", null, null);
+ //printPaths(results);
+ assertEquals(2, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:Region/test:RestOfWorld/member\"", null, null);
+ //printPaths(results);
+ assertEquals(2, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:Region//member\"", null, null);
+ //printPaths(results);
+ assertEquals(5, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:InvestmentRegion//member\"", null, null);
+ //printPaths(results);
+ assertEquals(5, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:MarketingRegion//member\"", null, null);
+ //printPaths(results);
+ assertEquals(6, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "+PATH:\"/test:AssetClass/test:Fixed/member\" AND +PATH:\"/test:Region/test:Europe/member\"", null, null);
+ //printPaths(results);
+ assertEquals(2, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "+PATH:\"/cm:categoryContainer/cm:categoryRoot/test:AssetClass/test:Fixed/member\" AND +PATH:\"/cm:categoryContainer/cm:categoryRoot/test:Region/test:Europe/member\"", null, null);
+ //printPaths(results);
+ assertEquals(2, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/test:AssetClass/test:Equity/member\" PATH:\"/test:MarketingRegion/member\"", null, null);
+ //printPaths(results);
+ assertEquals(9, results.length());
+ results.close();
+ tx.rollback();
+ }
+
+ public void testCategoryServiceImpl() throws Exception
+ {
+ TransactionService transactionService = serviceRegistry.getTransactionService();
+ UserTransaction tx = transactionService.getUserTransaction();
+ tx.begin();
+ buildBaseIndex();
+
+ LuceneSearcherImpl2 searcher = LuceneSearcherImpl2.getSearcher(rootNodeRef.getStoreRef(), indexerAndSearcher);
+
+ searcher.setNodeService(nodeService);
+ searcher.setDictionaryService(dictionaryService);
+ searcher.setNamespacePrefixResolver(getNamespacePrefixReolsver(""));
+
+ ResultSet
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/cm:categoryContainer/cm:categoryRoot/test:AssetClass/*\" ", null, null);
+ assertEquals(3, results.length());
+ results.close();
+
+ results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/cm:categoryContainer/cm:categoryRoot/test:AssetClass/member\" ", null, null);
+ assertEquals(1, results.length());
+ results.close();
+
+ LuceneCategoryServiceImpl impl = new LuceneCategoryServiceImpl();
+ impl.setNodeService(nodeService);
+ impl.setNamespacePrefixResolver(getNamespacePrefixReolsver(""));
+ impl.setIndexerAndSearcher(indexerAndSearcher);
+ impl.setDictionaryService(dictionaryService);
+
+ Collection
+ result = impl.getChildren(catACBase , CategoryService.Mode.MEMBERS, CategoryService.Depth.IMMEDIATE);
+ assertEquals(1, result.size());
+
+
+ result = impl.getChildren(catACBase , CategoryService.Mode.ALL, CategoryService.Depth.IMMEDIATE);
+ assertEquals(3, result.size());
+
+
+ result = impl.getChildren(catACBase , CategoryService.Mode.SUB_CATEGORIES, CategoryService.Depth.IMMEDIATE);
+ assertEquals(2, result.size());
+
+
+ result = impl.getChildren(catACBase , CategoryService.Mode.MEMBERS, CategoryService.Depth.ANY);
+ assertEquals(18, result.size());
+
+
+ result = impl.getChildren(catACBase , CategoryService.Mode.ALL, CategoryService.Depth.ANY);
+ assertEquals(21, result.size());
+
+
+ result = impl.getChildren(catACBase , CategoryService.Mode.SUB_CATEGORIES, CategoryService.Depth.ANY);
+ assertEquals(3, result.size());
+
+
+ result = impl.getClassifications(rootNodeRef.getStoreRef());
+ assertEquals(4, result.size());
+
+
+ result = impl.getCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass"), CategoryService.Depth.IMMEDIATE);
+ assertEquals(2, result.size());
+
+
+ Collection aspects = impl.getClassificationAspects();
+ assertEquals(6, aspects.size());
+
+ tx.rollback();
+ }
+
+ private NamespacePrefixResolver getNamespacePrefixReolsver(String defaultURI)
+ {
+ DynamicNamespacePrefixResolver nspr = new DynamicNamespacePrefixResolver(null);
+ nspr.registerNamespace(NamespaceService.CONTENT_MODEL_PREFIX, NamespaceService.CONTENT_MODEL_1_0_URI);
+ nspr.registerNamespace("namespace", "namespace");
+ nspr.registerNamespace("test", TEST_NAMESPACE);
+ nspr.registerNamespace(NamespaceService.DEFAULT_PREFIX, defaultURI);
+ return nspr;
+ }
+
+ public void testCategoryService() throws Exception
+ {
+ TransactionService transactionService = serviceRegistry.getTransactionService();
+ UserTransaction tx = transactionService.getUserTransaction();
+ tx.begin();
+ buildBaseIndex();
+ assertEquals(1, categoryService.getChildren(catACBase , CategoryService.Mode.MEMBERS, CategoryService.Depth.IMMEDIATE).size());
+ assertEquals(2, categoryService.getChildren(catACBase , CategoryService.Mode.SUB_CATEGORIES, CategoryService.Depth.IMMEDIATE).size());
+ assertEquals(3, categoryService.getChildren(catACBase , CategoryService.Mode.ALL, CategoryService.Depth.IMMEDIATE).size());
+ assertEquals(18, categoryService.getChildren(catACBase , CategoryService.Mode.MEMBERS, CategoryService.Depth.ANY).size());
+ assertEquals(3, categoryService.getChildren(catACBase , CategoryService.Mode.SUB_CATEGORIES, CategoryService.Depth.ANY).size());
+ assertEquals(21, categoryService.getChildren(catACBase , CategoryService.Mode.ALL, CategoryService.Depth.ANY).size());
+ assertEquals(4, categoryService.getClassifications(rootNodeRef.getStoreRef()).size());
+ assertEquals(2, categoryService.getCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass"), CategoryService.Depth.IMMEDIATE).size());
+ assertEquals(3, categoryService.getCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass"), CategoryService.Depth.ANY).size());
+ assertEquals(6, categoryService.getClassificationAspects().size());
+ assertEquals(2, categoryService.getRootCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass")).size());
+
+ NodeRef newRoot = categoryService.createRootCategory(rootNodeRef.getStoreRef(),QName.createQName(TEST_NAMESPACE, "AssetClass"), "Fruit");
+ tx.commit();
+ tx = transactionService.getUserTransaction();
+ tx.begin();
+ assertEquals(3, categoryService.getRootCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass")).size());
+ assertEquals(3, categoryService.getCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass"), CategoryService.Depth.IMMEDIATE).size());
+ assertEquals(4, categoryService.getCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass"), CategoryService.Depth.ANY).size());
+
+ NodeRef newCat = categoryService.createCategory(newRoot, "Banana");
+ tx.commit();
+ tx = transactionService.getUserTransaction();
+ tx.begin();
+ assertEquals(3, categoryService.getRootCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass")).size());
+ assertEquals(3, categoryService.getCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass"), CategoryService.Depth.IMMEDIATE).size());
+ assertEquals(5, categoryService.getCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass"), CategoryService.Depth.ANY).size());
+
+ categoryService.deleteCategory(newCat);
+ tx.commit();
+ tx = transactionService.getUserTransaction();
+ tx.begin();
+ assertEquals(3, categoryService.getRootCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass")).size());
+ assertEquals(3, categoryService.getCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass"), CategoryService.Depth.IMMEDIATE).size());
+ assertEquals(4, categoryService.getCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass"), CategoryService.Depth.ANY).size());
+
+ categoryService.deleteCategory(newRoot);
+ tx.commit();
+ tx = transactionService.getUserTransaction();
+ tx.begin();
+ assertEquals(2, categoryService.getRootCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass")).size());
+ assertEquals(2, categoryService.getCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass"), CategoryService.Depth.IMMEDIATE).size());
+ assertEquals(3, categoryService.getCategories(rootNodeRef.getStoreRef(), QName.createQName(TEST_NAMESPACE, "AssetClass"), CategoryService.Depth.ANY).size());
+
+
+ tx.rollback();
+ }
+
+ private int getTotalScore(ResultSet results)
+ {
+ int totalScore = 0;
+ for(ResultSetRow row: results)
+ {
+ totalScore += row.getScore();
+ }
+ return totalScore;
+ }
+}
diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneIndexer.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneIndexer.java
index 763ea20996..68c8763abe 100644
--- a/source/java/org/alfresco/repo/search/impl/lucene/LuceneIndexer.java
+++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneIndexer.java
@@ -19,6 +19,7 @@ package org.alfresco.repo.search.impl.lucene;
import java.util.Set;
import org.alfresco.repo.search.Indexer;
+import org.alfresco.repo.search.IndexerSPI;
import org.alfresco.repo.search.impl.lucene.fts.FTSIndexerAware;
import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer;
import org.alfresco.service.cmr.dictionary.DictionaryService;
@@ -28,7 +29,7 @@ import org.alfresco.service.cmr.repository.NodeService;
/**
* @author Andy Hind
*/
-public interface LuceneIndexer extends Indexer, Lockable
+public interface LuceneIndexer extends IndexerSPI, Lockable
{
public void commit();
@@ -39,9 +40,6 @@ public interface LuceneIndexer extends Indexer, Lockable
public void setDictionaryService(DictionaryService dictionaryService);
public void setLuceneFullTextSearchIndexer(FullTextSearchIndexer luceneFullTextSearchIndexer);
- public void updateFullTextSearch(int size);
- public void registerCallBack(FTSIndexerAware indexer);
-
public String getDeltaId();
public void flushPending() throws LuceneIndexException;
public Set getDeletions();
diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneIndexer2.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneIndexer2.java
new file mode 100644
index 0000000000..a7924ed311
--- /dev/null
+++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneIndexer2.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2005 Alfresco, Inc.
+ *
+ * Licensed under the Mozilla Public License version 1.1
+ * with a permitted attribution clause. You may obtain a
+ * copy of the License at
+ *
+ * http://www.alfresco.org/legal/license.txt
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the
+ * License.
+ */
+package org.alfresco.repo.search.impl.lucene;
+
+import java.util.Set;
+
+import org.alfresco.repo.search.Indexer;
+import org.alfresco.repo.search.IndexerSPI;
+import org.alfresco.repo.search.impl.lucene.fts.FTSIndexerAware;
+import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer;
+import org.alfresco.service.cmr.dictionary.DictionaryService;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+
+/**
+ * @author Andy Hind
+ */
+public interface LuceneIndexer2 extends IndexerSPI
+{
+
+ public void commit();
+ public void rollback();
+ public int prepare();
+ public boolean isModified();
+ public void setNodeService(NodeService nodeService);
+ public void setDictionaryService(DictionaryService dictionaryService);
+ public void setLuceneFullTextSearchIndexer(FullTextSearchIndexer luceneFullTextSearchIndexer);
+
+ public String getDeltaId();
+ public void flushPending() throws LuceneIndexException;
+ public Set getDeletions();
+ public boolean getDeleteOnlyNodes();
+}
diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneIndexerAndSearcherFactory.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneIndexerAndSearcherFactory.java
index 00b3026795..a341edf844 100644
--- a/source/java/org/alfresco/repo/search/impl/lucene/LuceneIndexerAndSearcherFactory.java
+++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneIndexerAndSearcherFactory.java
@@ -135,6 +135,9 @@ public class LuceneIndexerAndSearcherFactory implements LuceneIndexerAndSearcher
private QueryRegisterComponent queryRegister;
+ /** the maximum transformation time to allow atomically, defaulting to 20ms */
+ private long maxAtomicTransformationTime = 20;
+
private int indexerMaxFieldLength;
/**
@@ -187,6 +190,18 @@ public class LuceneIndexerAndSearcherFactory implements LuceneIndexerAndSearcher
this.queryRegister = queryRegister;
}
+ /**
+ * Set the maximum average transformation time allowed to a transformer in order to have
+ * the transformation performed in the current transaction. The default is 20ms.
+ *
+ * @param maxAtomicTransformationTime the maximum average time that a text transformation may
+ * take in order to be performed atomically.
+ */
+ public void setMaxAtomicTransformationTime(long maxAtomicTransformationTime)
+ {
+ this.maxAtomicTransformationTime = maxAtomicTransformationTime;
+ }
+
/**
* Check if we are in a global transactoin according to the transaction
* manager
@@ -345,6 +360,7 @@ public class LuceneIndexerAndSearcherFactory implements LuceneIndexerAndSearcher
indexer.setLuceneIndexLock(luceneIndexLock);
indexer.setLuceneFullTextSearchIndexer(luceneFullTextSearchIndexer);
indexer.setContentService(contentService);
+ indexer.setMaxAtomicTransformationTime(maxAtomicTransformationTime);
return indexer;
}
diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneIndexerAndSearcherFactory2.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneIndexerAndSearcherFactory2.java
new file mode 100644
index 0000000000..29cf9cd323
--- /dev/null
+++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneIndexerAndSearcherFactory2.java
@@ -0,0 +1,1104 @@
+/*
+ * Copyright (C) 2005 Alfresco, Inc.
+ *
+ * Licensed under the Mozilla Public License version 1.1
+ * with a permitted attribution clause. You may obtain a
+ * copy of the License at
+ *
+ * http://www.alfresco.org/legal/license.txt
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the
+ * License.
+ */
+package org.alfresco.repo.search.impl.lucene;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.transaction.RollbackException;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.repo.search.IndexerException;
+import org.alfresco.repo.search.QueryRegisterComponent;
+import org.alfresco.repo.search.SearcherException;
+import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer;
+import org.alfresco.repo.search.transaction.LuceneIndexLock;
+import org.alfresco.repo.search.transaction.SimpleTransaction;
+import org.alfresco.repo.search.transaction.SimpleTransactionManager;
+import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
+import org.alfresco.repo.transaction.TransactionUtil;
+import org.alfresco.repo.transaction.TransactionUtil.TransactionWork;
+import org.alfresco.service.cmr.dictionary.DictionaryService;
+import org.alfresco.service.cmr.repository.ContentService;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.GUID;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.store.Lock;
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+
+/**
+ * This class is resource manager LuceneIndexers and LuceneSearchers.
+ *
+ * It supports two phase commit inside XA transactions and outside transactions
+ * it provides thread local transaction support.
+ *
+ * TODO: Provide pluggable support for a transaction manager TODO: Integrate
+ * with Spring transactions
+ *
+ * @author andyh
+ *
+ */
+
+public class LuceneIndexerAndSearcherFactory2 implements LuceneIndexerAndSearcher, XAResource
+{
+ private DictionaryService dictionaryService;
+
+ private NamespaceService nameSpaceService;
+
+ private int queryMaxClauses;
+
+ private int indexerBatchSize;
+
+ private int indexerMinMergeDocs;
+
+ private int indexerMergeFactor;
+
+ private int indexerMaxMergeDocs;
+
+ private String lockDirectory;
+
+ /**
+ * A map of active global transactions . It contains all the indexers a
+ * transaction has used, with at most one indexer for each store within a
+ * transaction
+ */
+
+ private static Map> activeIndexersInGlobalTx = new HashMap>();
+
+ /**
+ * Suspended global transactions.
+ */
+ private static Map> suspendedIndexersInGlobalTx = new HashMap>();
+
+ /**
+ * Thread local indexers - used outside a global transaction
+ */
+
+ private static ThreadLocal