mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
Moving to root below branch label
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2005 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
626
source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java
Normal file
626
source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java
Normal file
@@ -0,0 +1,626 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.domain.PropertyValue;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.BeforeAddAspectPolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.BeforeCreateChildAssociationPolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.BeforeCreateNodePolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.BeforeCreateStorePolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteChildAssociationPolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.BeforeRemoveAspectPolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.BeforeUpdateNodePolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.OnAddAspectPolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.OnCreateAssociationPolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.OnCreateChildAssociationPolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.OnCreateStorePolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.OnDeleteAssociationPolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.OnDeleteChildAssociationPolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.OnDeleteNodePolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.OnRemoveAspectPolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.OnUpdateNodePolicy;
|
||||
import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy;
|
||||
import org.alfresco.repo.policy.AssociationPolicyDelegate;
|
||||
import org.alfresco.repo.policy.ClassPolicyDelegate;
|
||||
import org.alfresco.repo.policy.PolicyComponent;
|
||||
import org.alfresco.repo.search.Indexer;
|
||||
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryException;
|
||||
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
|
||||
import org.alfresco.service.cmr.repository.AssociationRef;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
|
||||
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.repository.datatype.TypeConversionException;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.namespace.QNamePattern;
|
||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.alfresco.util.GUID;
|
||||
|
||||
/**
|
||||
* Provides common functionality for
|
||||
* {@link org.alfresco.service.cmr.repository.NodeService} implementations.
|
||||
* <p>
|
||||
* Some of the overloaded simpler versions of methods are implemented by passing
|
||||
* through the defaults as required.
|
||||
* <p>
|
||||
* The callback handling is also provided as a convenience for implementations.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public abstract class AbstractNodeServiceImpl implements NodeService
|
||||
{
|
||||
/** a uuid identifying this unique instance */
|
||||
private String uuid;
|
||||
/** controls policy delegates */
|
||||
private PolicyComponent policyComponent;
|
||||
|
||||
/*
|
||||
* Policy delegates
|
||||
*/
|
||||
private ClassPolicyDelegate<BeforeCreateStorePolicy> beforeCreateStoreDelegate;
|
||||
private ClassPolicyDelegate<OnCreateStorePolicy> onCreateStoreDelegate;
|
||||
private ClassPolicyDelegate<BeforeCreateNodePolicy> beforeCreateNodeDelegate;
|
||||
private ClassPolicyDelegate<OnCreateNodePolicy> onCreateNodeDelegate;
|
||||
private ClassPolicyDelegate<BeforeUpdateNodePolicy> beforeUpdateNodeDelegate;
|
||||
private ClassPolicyDelegate<OnUpdateNodePolicy> onUpdateNodeDelegate;
|
||||
private ClassPolicyDelegate<OnUpdatePropertiesPolicy> onUpdatePropertiesDelegate;
|
||||
private ClassPolicyDelegate<BeforeDeleteNodePolicy> beforeDeleteNodeDelegate;
|
||||
private ClassPolicyDelegate<OnDeleteNodePolicy> onDeleteNodeDelegate;
|
||||
private ClassPolicyDelegate<BeforeAddAspectPolicy> beforeAddAspectDelegate;
|
||||
private ClassPolicyDelegate<OnAddAspectPolicy> onAddAspectDelegate;
|
||||
private ClassPolicyDelegate<BeforeRemoveAspectPolicy> beforeRemoveAspectDelegate;
|
||||
private ClassPolicyDelegate<OnRemoveAspectPolicy> onRemoveAspectDelegate;
|
||||
private AssociationPolicyDelegate<BeforeCreateChildAssociationPolicy> beforeCreateChildAssociationDelegate;
|
||||
private AssociationPolicyDelegate<OnCreateChildAssociationPolicy> onCreateChildAssociationDelegate;
|
||||
private AssociationPolicyDelegate<BeforeDeleteChildAssociationPolicy> beforeDeleteChildAssociationDelegate;
|
||||
private AssociationPolicyDelegate<OnDeleteChildAssociationPolicy> onDeleteChildAssociationDelegate;
|
||||
private AssociationPolicyDelegate<OnCreateAssociationPolicy> onCreateAssociationDelegate;
|
||||
private AssociationPolicyDelegate<OnDeleteAssociationPolicy> onDeleteAssociationDelegate;
|
||||
|
||||
/**
|
||||
* @param policyComponent the component with which to register class policies and behaviour
|
||||
* @param dictionaryService
|
||||
* used to check that node operations conform to the model
|
||||
*/
|
||||
protected AbstractNodeServiceImpl(PolicyComponent policyComponent)
|
||||
{
|
||||
this.uuid = GUID.generate();
|
||||
this.policyComponent = policyComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks equality by type and uuid
|
||||
*/
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (!(obj instanceof AbstractNodeServiceImpl))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
AbstractNodeServiceImpl that = (AbstractNodeServiceImpl) obj;
|
||||
return this.uuid.equals(that.uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #uuid
|
||||
*/
|
||||
public int hashCode()
|
||||
{
|
||||
return uuid.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the node policies as well as node indexing behaviour if the
|
||||
* {@link #setIndexer(Indexer) indexer} is present.
|
||||
*/
|
||||
public void init()
|
||||
{
|
||||
// Register the various policies
|
||||
beforeCreateStoreDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeCreateStorePolicy.class);
|
||||
onCreateStoreDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnCreateStorePolicy.class);
|
||||
beforeCreateNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeCreateNodePolicy.class);
|
||||
onCreateNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnCreateNodePolicy.class);
|
||||
beforeUpdateNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeUpdateNodePolicy.class);
|
||||
onUpdateNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnUpdateNodePolicy.class);
|
||||
onUpdatePropertiesDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnUpdatePropertiesPolicy.class);
|
||||
beforeDeleteNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeDeleteNodePolicy.class);
|
||||
onDeleteNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnDeleteNodePolicy.class);
|
||||
|
||||
beforeAddAspectDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeAddAspectPolicy.class);
|
||||
onAddAspectDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnAddAspectPolicy.class);
|
||||
beforeRemoveAspectDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeRemoveAspectPolicy.class);
|
||||
onRemoveAspectDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnRemoveAspectPolicy.class);
|
||||
|
||||
beforeCreateChildAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.BeforeCreateChildAssociationPolicy.class);
|
||||
onCreateChildAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.OnCreateChildAssociationPolicy.class);
|
||||
beforeDeleteChildAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.BeforeDeleteChildAssociationPolicy.class);
|
||||
onDeleteChildAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.OnDeleteChildAssociationPolicy.class);
|
||||
|
||||
onCreateAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.OnCreateAssociationPolicy.class);
|
||||
onDeleteAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.OnDeleteAssociationPolicy.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.BeforeCreateStorePolicy#beforeCreateStore(QName,
|
||||
* StoreRef)
|
||||
*/
|
||||
protected void invokeBeforeCreateStore(QName nodeTypeQName, StoreRef storeRef)
|
||||
{
|
||||
NodeServicePolicies.BeforeCreateStorePolicy policy = this.beforeCreateStoreDelegate.get(nodeTypeQName);
|
||||
policy.beforeCreateStore(nodeTypeQName, storeRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.OnCreateStorePolicy#onCreateStore(NodeRef)
|
||||
*/
|
||||
protected void invokeOnCreateStore(NodeRef rootNodeRef)
|
||||
{
|
||||
// get qnames to invoke against
|
||||
Set<QName> qnames = getTypeAndAspectQNames(rootNodeRef);
|
||||
// execute policy for node type and aspects
|
||||
NodeServicePolicies.OnCreateStorePolicy policy = onCreateStoreDelegate.get(qnames);
|
||||
policy.onCreateStore(rootNodeRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.BeforeCreateNodePolicy#beforeCreateNode(NodeRef,
|
||||
* QName, QName, QName)
|
||||
*/
|
||||
protected void invokeBeforeCreateNode(NodeRef parentNodeRef, QName assocTypeQName, QName assocQName, QName childNodeTypeQName)
|
||||
{
|
||||
// execute policy for node type
|
||||
NodeServicePolicies.BeforeCreateNodePolicy policy = beforeCreateNodeDelegate.get(parentNodeRef, childNodeTypeQName);
|
||||
policy.beforeCreateNode(parentNodeRef, assocTypeQName, assocQName, childNodeTypeQName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.OnCreateNodePolicy#onCreateNode(ChildAssociationRef)
|
||||
*/
|
||||
protected void invokeOnCreateNode(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
NodeRef childNodeRef = childAssocRef.getChildRef();
|
||||
// get qnames to invoke against
|
||||
Set<QName> qnames = getTypeAndAspectQNames(childNodeRef);
|
||||
// execute policy for node type and aspects
|
||||
NodeServicePolicies.OnCreateNodePolicy policy = onCreateNodeDelegate.get(childNodeRef, qnames);
|
||||
policy.onCreateNode(childAssocRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.BeforeUpdateNodePolicy#beforeUpdateNode(NodeRef)
|
||||
*/
|
||||
protected void invokeBeforeUpdateNode(NodeRef nodeRef)
|
||||
{
|
||||
// get qnames to invoke against
|
||||
Set<QName> qnames = getTypeAndAspectQNames(nodeRef);
|
||||
// execute policy for node type and aspects
|
||||
NodeServicePolicies.BeforeUpdateNodePolicy policy = beforeUpdateNodeDelegate.get(nodeRef, qnames);
|
||||
policy.beforeUpdateNode(nodeRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.OnUpdateNodePolicy#onUpdateNode(NodeRef)
|
||||
*/
|
||||
protected void invokeOnUpdateNode(NodeRef nodeRef)
|
||||
{
|
||||
// get qnames to invoke against
|
||||
Set<QName> qnames = getTypeAndAspectQNames(nodeRef);
|
||||
// execute policy for node type and aspects
|
||||
NodeServicePolicies.OnUpdateNodePolicy policy = onUpdateNodeDelegate.get(nodeRef, qnames);
|
||||
policy.onUpdateNode(nodeRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.OnUpdateProperties#onUpdatePropertiesPolicy(NodeRef, Map, Map)
|
||||
*/
|
||||
protected void invokeOnUpdateProperties(
|
||||
NodeRef nodeRef,
|
||||
Map<QName, Serializable> before,
|
||||
Map<QName, Serializable> after)
|
||||
{
|
||||
// get qnames to invoke against
|
||||
Set<QName> qnames = getTypeAndAspectQNames(nodeRef);
|
||||
// execute policy for node type and aspects
|
||||
NodeServicePolicies.OnUpdatePropertiesPolicy policy = onUpdatePropertiesDelegate.get(nodeRef, qnames);
|
||||
policy.onUpdateProperties(nodeRef, before, after);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.BeforeDeleteNodePolicy#beforeDeleteNode(NodeRef)
|
||||
*/
|
||||
protected void invokeBeforeDeleteNode(NodeRef nodeRef)
|
||||
{
|
||||
// get qnames to invoke against
|
||||
Set<QName> qnames = getTypeAndAspectQNames(nodeRef);
|
||||
// execute policy for node type and aspects
|
||||
NodeServicePolicies.BeforeDeleteNodePolicy policy = beforeDeleteNodeDelegate.get(nodeRef, qnames);
|
||||
policy.beforeDeleteNode(nodeRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.OnDeleteNodePolicy#onDeleteNode(ChildAssociationRef)
|
||||
*/
|
||||
protected void invokeOnDeleteNode(ChildAssociationRef childAssocRef, QName childNodeTypeQName, Set<QName> childAspectQnames)
|
||||
{
|
||||
// get qnames to invoke against
|
||||
Set<QName> qnames = new HashSet<QName>(childAspectQnames.size() + 1);
|
||||
qnames.addAll(childAspectQnames);
|
||||
qnames.add(childNodeTypeQName);
|
||||
|
||||
// execute policy for node type and aspects
|
||||
NodeServicePolicies.OnDeleteNodePolicy policy = onDeleteNodeDelegate.get(childAssocRef.getChildRef(), qnames);
|
||||
policy.onDeleteNode(childAssocRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.BeforeAddAspectPolicy#beforeAddAspect(NodeRef,
|
||||
* QName)
|
||||
*/
|
||||
protected void invokeBeforeAddAspect(NodeRef nodeRef, QName aspectTypeQName)
|
||||
{
|
||||
NodeServicePolicies.BeforeAddAspectPolicy policy = beforeAddAspectDelegate.get(nodeRef, aspectTypeQName);
|
||||
policy.beforeAddAspect(nodeRef, aspectTypeQName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.OnAddAspectPolicy#onAddAspect(NodeRef, QName)
|
||||
*/
|
||||
protected void invokeOnAddAspect(NodeRef nodeRef, QName aspectTypeQName)
|
||||
{
|
||||
NodeServicePolicies.OnAddAspectPolicy policy = onAddAspectDelegate.get(nodeRef, aspectTypeQName);
|
||||
policy.onAddAspect(nodeRef, aspectTypeQName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.BeforeRemoveAspectPolicy#BeforeRemoveAspect(NodeRef,
|
||||
* QName)
|
||||
*/
|
||||
protected void invokeBeforeRemoveAspect(NodeRef nodeRef, QName aspectTypeQName)
|
||||
{
|
||||
NodeServicePolicies.BeforeRemoveAspectPolicy policy = beforeRemoveAspectDelegate.get(nodeRef, aspectTypeQName);
|
||||
policy.beforeRemoveAspect(nodeRef, aspectTypeQName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.OnRemoveAspectPolicy#onRemoveAspect(NodeRef,
|
||||
* QName)
|
||||
*/
|
||||
protected void invokeOnRemoveAspect(NodeRef nodeRef, QName aspectTypeQName)
|
||||
{
|
||||
NodeServicePolicies.OnRemoveAspectPolicy policy = onRemoveAspectDelegate.get(nodeRef, aspectTypeQName);
|
||||
policy.onRemoveAspect(nodeRef, aspectTypeQName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.BeforeCreateChildAssociationPolicy#beforeCreateChildAssociation(NodeRef,
|
||||
* NodeRef, QName, QName)
|
||||
*/
|
||||
protected void invokeBeforeCreateChildAssociation(NodeRef parentNodeRef, NodeRef childNodeRef, QName assocTypeQName, QName assocQName)
|
||||
{
|
||||
// get qnames to invoke against
|
||||
Set<QName> qnames = getTypeAndAspectQNames(parentNodeRef);
|
||||
// execute policy for node type
|
||||
NodeServicePolicies.BeforeCreateChildAssociationPolicy policy = beforeCreateChildAssociationDelegate.get(parentNodeRef, qnames, assocTypeQName);
|
||||
policy.beforeCreateChildAssociation(parentNodeRef, childNodeRef, assocTypeQName, assocQName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.OnCreateChildAssociationPolicy#onCreateChildAssociation(ChildAssociationRef)
|
||||
*/
|
||||
protected void invokeOnCreateChildAssociation(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
// Get the parent reference and the assoc type qName
|
||||
NodeRef parentNodeRef = childAssocRef.getParentRef();
|
||||
QName assocTypeQName = childAssocRef.getTypeQName();
|
||||
// get qnames to invoke against
|
||||
Set<QName> qnames = getTypeAndAspectQNames(parentNodeRef);
|
||||
// execute policy for node type and aspects
|
||||
NodeServicePolicies.OnCreateChildAssociationPolicy policy = onCreateChildAssociationDelegate.get(parentNodeRef, qnames, assocTypeQName);
|
||||
policy.onCreateChildAssociation(childAssocRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.BeforeDeleteChildAssociationPolicy#beforeDeleteChildAssociation(ChildAssociationRef)
|
||||
*/
|
||||
protected void invokeBeforeDeleteChildAssociation(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
NodeRef parentNodeRef = childAssocRef.getParentRef();
|
||||
QName assocTypeQName = childAssocRef.getTypeQName();
|
||||
// get qnames to invoke against
|
||||
Set<QName> qnames = getTypeAndAspectQNames(parentNodeRef);
|
||||
// execute policy for node type and aspects
|
||||
NodeServicePolicies.BeforeDeleteChildAssociationPolicy policy = beforeDeleteChildAssociationDelegate.get(parentNodeRef, qnames, assocTypeQName);
|
||||
policy.beforeDeleteChildAssociation(childAssocRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.OnDeleteChildAssociationPolicy#onDeleteChildAssociation(ChildAssociationRef)
|
||||
*/
|
||||
protected void invokeOnDeleteChildAssociation(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
NodeRef parentNodeRef = childAssocRef.getParentRef();
|
||||
QName assocTypeQName = childAssocRef.getTypeQName();
|
||||
// get qnames to invoke against
|
||||
Set<QName> qnames = getTypeAndAspectQNames(parentNodeRef);
|
||||
// execute policy for node type and aspects
|
||||
NodeServicePolicies.OnDeleteChildAssociationPolicy policy = onDeleteChildAssociationDelegate.get(parentNodeRef, qnames, assocTypeQName);
|
||||
policy.onDeleteChildAssociation(childAssocRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.OnCreateAssociationPolicy#onCreateAssociation(NodeRef, NodeRef, QName)
|
||||
*/
|
||||
protected void invokeOnCreateAssociation(AssociationRef nodeAssocRef)
|
||||
{
|
||||
NodeRef sourceNodeRef = nodeAssocRef.getSourceRef();
|
||||
QName assocTypeQName = nodeAssocRef.getTypeQName();
|
||||
// get qnames to invoke against
|
||||
Set<QName> qnames = getTypeAndAspectQNames(sourceNodeRef);
|
||||
// execute policy for node type and aspects
|
||||
NodeServicePolicies.OnCreateAssociationPolicy policy = onCreateAssociationDelegate.get(sourceNodeRef, qnames, assocTypeQName);
|
||||
policy.onCreateAssociation(nodeAssocRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeServicePolicies.OnDeleteAssociationPolicy#onDeleteAssociation(AssociationRef)
|
||||
*/
|
||||
protected void invokeOnDeleteAssociation(AssociationRef nodeAssocRef)
|
||||
{
|
||||
NodeRef sourceNodeRef = nodeAssocRef.getSourceRef();
|
||||
QName assocTypeQName = nodeAssocRef.getTypeQName();
|
||||
// get qnames to invoke against
|
||||
Set<QName> qnames = getTypeAndAspectQNames(sourceNodeRef);
|
||||
// execute policy for node type and aspects
|
||||
NodeServicePolicies.OnDeleteAssociationPolicy policy = onDeleteAssociationDelegate.get(sourceNodeRef, qnames, assocTypeQName);
|
||||
policy.onDeleteAssociation(nodeAssocRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all aspect and node type qualified names
|
||||
*
|
||||
* @param nodeRef
|
||||
* the node we are interested in
|
||||
* @return Returns a set of qualified names containing the node type and all
|
||||
* the node aspects, or null if the node no longer exists
|
||||
*/
|
||||
protected Set<QName> getTypeAndAspectQNames(NodeRef nodeRef)
|
||||
{
|
||||
Set<QName> qnames = null;
|
||||
try
|
||||
{
|
||||
Set<QName> aspectQNames = getAspects(nodeRef);
|
||||
QName typeQName = getType(nodeRef);
|
||||
qnames = new HashSet<QName>(aspectQNames.size() + 1);
|
||||
qnames.addAll(aspectQNames);
|
||||
qnames.add(typeQName);
|
||||
}
|
||||
catch (InvalidNodeRefException e)
|
||||
{
|
||||
qnames = Collections.emptySet();
|
||||
}
|
||||
// done
|
||||
return qnames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a GUID for the node using either the creation properties or just by
|
||||
* generating a value randomly.
|
||||
*
|
||||
* @param preCreationProperties the properties that will be applied to the node
|
||||
* @return Returns the ID to create the node with
|
||||
*/
|
||||
protected String generateGuid(Map<QName, Serializable> preCreationProperties)
|
||||
{
|
||||
String uuid = (String) preCreationProperties.get(ContentModel.PROP_NODE_UUID);
|
||||
if (uuid == null)
|
||||
{
|
||||
uuid = GUID.generate();
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove the property as we don't want to persist it
|
||||
preCreationProperties.remove(ContentModel.PROP_NODE_UUID);
|
||||
}
|
||||
// done
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all properties used by the
|
||||
* {@link ContentModel#ASPECT_REFERENCEABLE referencable aspect}.
|
||||
* <p>
|
||||
* This method can be used to ensure that the information already stored
|
||||
* by the node key is not duplicated by the properties.
|
||||
*
|
||||
* @param properties properties to change
|
||||
*/
|
||||
protected void removeReferencableProperties(Map<QName, Serializable> properties)
|
||||
{
|
||||
properties.remove(ContentModel.PROP_STORE_PROTOCOL);
|
||||
properties.remove(ContentModel.PROP_STORE_IDENTIFIER);
|
||||
properties.remove(ContentModel.PROP_NODE_UUID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all properties used by the
|
||||
* {@link ContentModel#ASPECT_REFERENCEABLE referencable aspect}.
|
||||
* <p>
|
||||
* This method can be used to ensure that the values used by the aspect
|
||||
* are present as node properties.
|
||||
* <p>
|
||||
* This method also ensures that the {@link ContentModel#PROP_NAME name property}
|
||||
* is always present as a property on a node.
|
||||
*
|
||||
* @param nodeRef the node reference containing the values required
|
||||
* @param properties the node properties
|
||||
*/
|
||||
protected void addReferencableProperties(NodeRef nodeRef, Map<QName, Serializable> properties)
|
||||
{
|
||||
properties.put(ContentModel.PROP_STORE_PROTOCOL, nodeRef.getStoreRef().getProtocol());
|
||||
properties.put(ContentModel.PROP_STORE_IDENTIFIER, nodeRef.getStoreRef().getIdentifier());
|
||||
properties.put(ContentModel.PROP_NODE_UUID, nodeRef.getId());
|
||||
// add the ID as the name, if required
|
||||
if (properties.get(ContentModel.PROP_NAME) == null)
|
||||
{
|
||||
properties.put(ContentModel.PROP_NAME, nodeRef.getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defers to the pattern matching overload
|
||||
*
|
||||
* @see RegexQNamePattern#MATCH_ALL
|
||||
* @see NodeService#getParentAssocs(NodeRef, QNamePattern, QNamePattern)
|
||||
*/
|
||||
public List<ChildAssociationRef> getParentAssocs(NodeRef nodeRef) throws InvalidNodeRefException
|
||||
{
|
||||
return getParentAssocs(nodeRef, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defers to the pattern matching overload
|
||||
*
|
||||
* @see RegexQNamePattern#MATCH_ALL
|
||||
* @see NodeService#getChildAssocs(NodeRef, QNamePattern, QNamePattern)
|
||||
*/
|
||||
public final List<ChildAssociationRef> getChildAssocs(NodeRef nodeRef) throws InvalidNodeRefException
|
||||
{
|
||||
return getChildAssocs(nodeRef, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to convert the <code>Serializable</code> value into a full,
|
||||
* persistable {@link PropertyValue}.
|
||||
* <p>
|
||||
* Where the property definition is null, the value will take on the
|
||||
* {@link DataTypeDefinition#ANY generic ANY} value.
|
||||
* <p>
|
||||
* Where the property definition specifies a multi-valued property but the
|
||||
* value provided is not a collection, the value will be wrapped in a collection.
|
||||
*
|
||||
* @param propertyDef the property dictionary definition, may be null
|
||||
* @param value the value, which will be converted according to the definition -
|
||||
* may be null
|
||||
* @return Returns the persistable property value
|
||||
*/
|
||||
protected PropertyValue makePropertyValue(PropertyDefinition propertyDef, Serializable value)
|
||||
{
|
||||
// get property attributes
|
||||
QName propertyTypeQName = null;
|
||||
if (propertyDef == null) // property not recognised
|
||||
{
|
||||
// allow it for now - persisting excess properties can be useful sometimes
|
||||
propertyTypeQName = DataTypeDefinition.ANY;
|
||||
}
|
||||
else
|
||||
{
|
||||
propertyTypeQName = propertyDef.getDataType().getName();
|
||||
// check that multi-valued properties are allowed
|
||||
boolean isMultiValued = propertyDef.isMultiValued();
|
||||
if (isMultiValued && !(value instanceof Collection))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
// put the value into a collection
|
||||
// the implementation gives back a Serializable list
|
||||
value = (Serializable) Collections.singletonList(value);
|
||||
}
|
||||
}
|
||||
else if (!isMultiValued && (value instanceof Collection))
|
||||
{
|
||||
throw new DictionaryException("A single-valued property may not be a collection: \n" +
|
||||
" Property: " + propertyDef + "\n" +
|
||||
" Value: " + value);
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
PropertyValue propertyValue = new PropertyValue(propertyTypeQName, value);
|
||||
// done
|
||||
return propertyValue;
|
||||
}
|
||||
catch (TypeConversionException e)
|
||||
{
|
||||
throw new TypeConversionException(
|
||||
"The property value is not compatible with the type defined for the property: \n" +
|
||||
" property: " + (propertyDef == null ? "unknown" : propertyDef) + "\n" +
|
||||
" value: " + value + "\n" +
|
||||
" value type: " + value.getClass(),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the externally-visible property from the {@link PropertyValue propertyValue}.
|
||||
*
|
||||
* @param propertyDef
|
||||
* @param propertyValue
|
||||
* @return Returns the value of the property in the format dictated by the property
|
||||
* definition, or null if the property value is null
|
||||
*/
|
||||
protected Serializable makeSerializableValue(PropertyDefinition propertyDef, PropertyValue propertyValue)
|
||||
{
|
||||
if (propertyValue == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// get property attributes
|
||||
QName propertyTypeQName = null;
|
||||
if (propertyDef == null)
|
||||
{
|
||||
// allow this for now
|
||||
propertyTypeQName = DataTypeDefinition.ANY;
|
||||
}
|
||||
else
|
||||
{
|
||||
propertyTypeQName = propertyDef.getDataType().getName();
|
||||
}
|
||||
try
|
||||
{
|
||||
Serializable value = propertyValue.getValue(propertyTypeQName);
|
||||
// done
|
||||
return value;
|
||||
}
|
||||
catch (TypeConversionException e)
|
||||
{
|
||||
throw new TypeConversionException(
|
||||
"The property value is not compatible with the type defined for the property: \n" +
|
||||
" property: " + (propertyDef == null ? "unknown" : propertyDef) + "\n" +
|
||||
" property value: " + propertyValue,
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
1445
source/java/org/alfresco/repo/node/BaseNodeServiceTest.java
Normal file
1445
source/java/org/alfresco/repo/node/BaseNodeServiceTest.java
Normal file
File diff suppressed because it is too large
Load Diff
294
source/java/org/alfresco/repo/node/BaseNodeServiceTest_model.xml
Normal file
294
source/java/org/alfresco/repo/node/BaseNodeServiceTest_model.xml
Normal file
@@ -0,0 +1,294 @@
|
||||
<model name="test:nodeservice" xmlns="http://www.alfresco.org/model/dictionary/1.0">
|
||||
|
||||
<description>Test Model for NodeService tests</description>
|
||||
<author>Alfresco</author>
|
||||
<published>2005-06-05</published>
|
||||
<version>0.1</version>
|
||||
|
||||
<imports>
|
||||
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
|
||||
<import uri="http://www.alfresco.org/model/system/1.0" prefix="sys"/>
|
||||
</imports>
|
||||
|
||||
<namespaces>
|
||||
<namespace uri="http://www.alfresco.org/test/BaseNodeServiceTest" prefix="test"/>
|
||||
</namespaces>
|
||||
|
||||
<types>
|
||||
<type name="test:content">
|
||||
<title>Content</title>
|
||||
<parent>sys:base</parent>
|
||||
<properties>
|
||||
<property name="test:content">
|
||||
<type>d:content</type>
|
||||
<mandatory>true</mandatory>
|
||||
<index enabled="true">
|
||||
<atomic>false</atomic>
|
||||
<stored>false</stored>
|
||||
<tokenised>true</tokenised>
|
||||
</index>
|
||||
</property>
|
||||
</properties>
|
||||
<associations>
|
||||
<child-association name="test:contains">
|
||||
<source>
|
||||
<mandatory>false</mandatory>
|
||||
<many>false</many>
|
||||
</source>
|
||||
<target>
|
||||
<class>sys:base</class>
|
||||
<mandatory>false</mandatory>
|
||||
<many>true</many>
|
||||
</target>
|
||||
<duplicate>false</duplicate>
|
||||
</child-association>
|
||||
<association name="test:next">
|
||||
<source>
|
||||
<mandatory>false</mandatory>
|
||||
<many>false</many>
|
||||
</source>
|
||||
<target>
|
||||
<class>test:content</class>
|
||||
<mandatory>false</mandatory>
|
||||
<many>true</many>
|
||||
</target>
|
||||
</association>
|
||||
</associations>
|
||||
<mandatory-aspects>
|
||||
<aspect>test:titled</aspect>
|
||||
</mandatory-aspects>
|
||||
</type>
|
||||
|
||||
<type name="test:extendedcontent">
|
||||
<title>Extended Content</title>
|
||||
<parent>test:content</parent>
|
||||
<properties>
|
||||
<property name="test:prop1">
|
||||
<type>d:text</type>
|
||||
<mandatory>true</mandatory>
|
||||
<default>defaultValue</default>
|
||||
</property>
|
||||
</properties>
|
||||
</type>
|
||||
|
||||
<type name="test:multiprop">
|
||||
<title>MultiProp</title>
|
||||
<parent>sys:base</parent>
|
||||
<properties>
|
||||
<property name="test:string0">
|
||||
<type>d:text</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:content0">
|
||||
<type>d:content</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:string1">
|
||||
<type>d:text</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:content1">
|
||||
<type>d:content</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:string2">
|
||||
<type>d:text</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:content2">
|
||||
<type>d:content</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:string3">
|
||||
<type>d:text</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:content3">
|
||||
<type>d:content</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:string4">
|
||||
<type>d:text</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:content4">
|
||||
<type>d:content</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:string5">
|
||||
<type>d:text</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:content5">
|
||||
<type>d:content</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:string6">
|
||||
<type>d:text</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:content6">
|
||||
<type>d:content</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:string7">
|
||||
<type>d:text</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:content7">
|
||||
<type>d:content</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:string8">
|
||||
<type>d:text</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:content8">
|
||||
<type>d:content</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:string9">
|
||||
<type>d:text</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
<property name="test:content9">
|
||||
<type>d:content</type>
|
||||
<mandatory>false</mandatory>
|
||||
</property>
|
||||
</properties>
|
||||
<associations>
|
||||
<child-association name="test:child">
|
||||
<source>
|
||||
<mandatory>false</mandatory>
|
||||
<many>false</many>
|
||||
</source>
|
||||
<target>
|
||||
<class>sys:base</class>
|
||||
<mandatory>false</mandatory>
|
||||
<many>true</many>
|
||||
</target>
|
||||
<duplicate>false</duplicate>
|
||||
</child-association>
|
||||
</associations>
|
||||
</type>
|
||||
|
||||
<type name="test:many-properties">
|
||||
<title>Busy</title>
|
||||
<parent>sys:base</parent>
|
||||
<properties>
|
||||
<property name="test:booleanValue">
|
||||
<type>d:boolean</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:integerValue">
|
||||
<type>d:int</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:longValue">
|
||||
<type>d:long</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:floatValue">
|
||||
<type>d:float</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:doubleValue">
|
||||
<type>d:double</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:stringValue">
|
||||
<type>d:text</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:dateValue">
|
||||
<type>d:date</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:serializableValue">
|
||||
<type>d:any</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:nodeRefValue">
|
||||
<type>d:noderef</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:qnameValue">
|
||||
<type>d:qname</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:contentValue">
|
||||
<type>d:content</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:pathValue">
|
||||
<type>d:path</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:categoryValue">
|
||||
<type>d:category</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:nullValue">
|
||||
<type>d:text</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:multiValue">
|
||||
<type>d:text</type>
|
||||
<mandatory>true</mandatory>
|
||||
<multiple>true</multiple>
|
||||
</property>
|
||||
</properties>
|
||||
</type>
|
||||
</types>
|
||||
|
||||
<aspects>
|
||||
|
||||
<aspect name="test:titled">
|
||||
<title>Titled</title>
|
||||
<properties>
|
||||
<property name="test:title">
|
||||
<type>d:text</type>
|
||||
<mandatory>true</mandatory>
|
||||
<index enabled="true">
|
||||
<atomic>false</atomic>
|
||||
<stored>false</stored>
|
||||
<tokenised>true</tokenised>
|
||||
</index>
|
||||
</property>
|
||||
<property name="test:description">
|
||||
<type>d:text</type>
|
||||
</property>
|
||||
</properties>
|
||||
<mandatory-aspects>
|
||||
<aspect>test:mandatoryaspect</aspect>
|
||||
</mandatory-aspects>
|
||||
</aspect>
|
||||
|
||||
<aspect name="test:marker">
|
||||
<title>Marker Aspect</title>
|
||||
<mandatory-aspects>
|
||||
<aspect>test:marker2</aspect>
|
||||
</mandatory-aspects>
|
||||
</aspect>
|
||||
|
||||
<aspect name="test:marker2">
|
||||
<title>Marker Aspect 2</title>
|
||||
</aspect>
|
||||
|
||||
<aspect name="test:mandatoryaspect">
|
||||
<title>Mandatory Aspect</title>
|
||||
</aspect>
|
||||
|
||||
<aspect name="test:withDefaultValue">
|
||||
<title>Marker Aspect</title>
|
||||
<properties>
|
||||
<property name="test:prop2">
|
||||
<type>d:text</type>
|
||||
<default>defaultValue</default>
|
||||
</property>
|
||||
</properties>
|
||||
</aspect>
|
||||
|
||||
</aspects>
|
||||
|
||||
</model>
|
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.repo.dictionary.DictionaryDAO;
|
||||
import org.alfresco.repo.dictionary.M2Model;
|
||||
import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
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.ResultSet;
|
||||
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.apache.lucene.index.IndexWriter;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
public class ConcurrentNodeServiceTest extends TestCase
|
||||
{
|
||||
public static final String NAMESPACE = "http://www.alfresco.org/test/BaseNodeServiceTest";
|
||||
public static final String TEST_PREFIX = "test";
|
||||
public static final QName TYPE_QNAME_TEST_CONTENT = QName.createQName(NAMESPACE, "content");
|
||||
public static final QName ASPECT_QNAME_TEST_TITLED = QName.createQName(NAMESPACE, "titled");
|
||||
public static final QName PROP_QNAME_TEST_TITLE = QName.createQName(NAMESPACE, "title");
|
||||
public static final QName PROP_QNAME_TEST_MIMETYPE = QName.createQName(NAMESPACE, "mimetype");
|
||||
|
||||
static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
||||
|
||||
private NodeService nodeService;
|
||||
private TransactionService transactionService;
|
||||
private NodeRef rootNodeRef;
|
||||
private FullTextSearchIndexer luceneFTS;
|
||||
|
||||
private AuthenticationComponent authenticationComponent;
|
||||
|
||||
public ConcurrentNodeServiceTest()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
protected void setUp() throws Exception
|
||||
{
|
||||
DictionaryDAO dictionaryDao = (DictionaryDAO) ctx.getBean("dictionaryDAO");
|
||||
// load the system model
|
||||
ClassLoader cl = BaseNodeServiceTest.class.getClassLoader();
|
||||
InputStream modelStream = cl.getResourceAsStream("alfresco/model/systemModel.xml");
|
||||
assertNotNull(modelStream);
|
||||
M2Model model = M2Model.createModel(modelStream);
|
||||
dictionaryDao.putModel(model);
|
||||
// load the test model
|
||||
modelStream = cl.getResourceAsStream("org/alfresco/repo/node/BaseNodeServiceTest_model.xml");
|
||||
assertNotNull(modelStream);
|
||||
model = M2Model.createModel(modelStream);
|
||||
dictionaryDao.putModel(model);
|
||||
|
||||
nodeService = (NodeService) ctx.getBean("dbNodeService");
|
||||
transactionService = (TransactionService) ctx.getBean("transactionComponent");
|
||||
luceneFTS = (FullTextSearchIndexer) ctx.getBean("LuceneFullTextSearchIndexer");
|
||||
this.authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent");
|
||||
|
||||
this.authenticationComponent.setSystemUserAsCurrentUser();
|
||||
|
||||
// create a first store directly
|
||||
UserTransaction tx = transactionService.getUserTransaction();
|
||||
tx.begin();
|
||||
StoreRef storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis());
|
||||
rootNodeRef = nodeService.getRootNode(storeRef);
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception
|
||||
{
|
||||
authenticationComponent.clearCurrentSecurityContext();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
protected Map<QName, ChildAssociationRef> buildNodeGraph() throws Exception
|
||||
{
|
||||
return BaseNodeServiceTest.buildNodeGraph(nodeService, rootNodeRef);
|
||||
}
|
||||
|
||||
protected Map<QName, ChildAssociationRef> commitNodeGraph() throws Exception
|
||||
{
|
||||
UserTransaction tx = transactionService.getUserTransaction();
|
||||
tx.begin();
|
||||
Map<QName, ChildAssociationRef> answer = buildNodeGraph();
|
||||
tx.commit();
|
||||
|
||||
return null;// answer;
|
||||
}
|
||||
|
||||
public void testConcurrent() throws Exception
|
||||
{
|
||||
luceneFTS.pause();
|
||||
IndexWriter.COMMIT_LOCK_TIMEOUT = 100000;
|
||||
int count = 10;
|
||||
int repeats = 10;
|
||||
|
||||
Map<QName, ChildAssociationRef> assocRefs = commitNodeGraph();
|
||||
Thread runner = null;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
runner = new Nester("Concurrent-" + i, runner, repeats);
|
||||
}
|
||||
if (runner != null)
|
||||
{
|
||||
runner.start();
|
||||
|
||||
try
|
||||
{
|
||||
runner.join();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
SearchService searcher = (SearchService) ctx.getBean(ServiceRegistry.SEARCH_SERVICE.getLocalName());
|
||||
assertEquals(2 * ((count * repeats) + 1), searcher.selectNodes(rootNodeRef, "/*", null,
|
||||
getNamespacePrefixReolsver(""), false).size());
|
||||
ResultSet results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"/*\"");
|
||||
// n6 has root aspect - there are three things at the root level in the
|
||||
// index
|
||||
assertEquals(3 * ((count * repeats) + 1), results.length());
|
||||
results.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Daemon thread
|
||||
*/
|
||||
class Nester extends Thread
|
||||
{
|
||||
Thread waiter;
|
||||
|
||||
int repeats;
|
||||
|
||||
Nester(String name, Thread waiter, int repeats)
|
||||
{
|
||||
super(name);
|
||||
this.setDaemon(true);
|
||||
this.waiter = waiter;
|
||||
this.repeats = repeats;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
if (waiter != null)
|
||||
{
|
||||
waiter.start();
|
||||
}
|
||||
try
|
||||
{
|
||||
System.out.println("Start " + this.getName());
|
||||
for (int i = 0; i < repeats; i++)
|
||||
{
|
||||
Map<QName, ChildAssociationRef> assocRefs = commitNodeGraph();
|
||||
System.out.println(" " + this.getName() + " " + i);
|
||||
}
|
||||
System.out.println("End " + this.getName());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
System.exit(12);
|
||||
}
|
||||
if (waiter != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
waiter.join();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private NamespacePrefixResolver getNamespacePrefixReolsver(String defaultURI)
|
||||
{
|
||||
DynamicNamespacePrefixResolver nspr = new DynamicNamespacePrefixResolver(null);
|
||||
nspr.registerNamespace(NamespaceService.SYSTEM_MODEL_PREFIX, NamespaceService.SYSTEM_MODEL_1_0_URI);
|
||||
nspr.registerNamespace(NamespaceService.CONTENT_MODEL_PREFIX, NamespaceService.CONTENT_MODEL_1_0_URI);
|
||||
nspr.registerNamespace(NamespaceService.APP_MODEL_PREFIX, NamespaceService.APP_MODEL_1_0_URI);
|
||||
nspr.registerNamespace("namespace", "namespace");
|
||||
nspr.registerNamespace(NamespaceService.DEFAULT_PREFIX, defaultURI);
|
||||
return nspr;
|
||||
}
|
||||
}
|
254
source/java/org/alfresco/repo/node/NodeServicePolicies.java
Normal file
254
source/java/org/alfresco/repo/node/NodeServicePolicies.java
Normal file
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.policy.AssociationPolicy;
|
||||
import org.alfresco.repo.policy.ClassPolicy;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.AssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
|
||||
/**
|
||||
* Node service policies
|
||||
*
|
||||
* @author Roy Wetherall
|
||||
*/
|
||||
public interface NodeServicePolicies
|
||||
{
|
||||
public interface BeforeCreateStorePolicy extends ClassPolicy
|
||||
{
|
||||
/**
|
||||
* Called before a new node store is created.
|
||||
*
|
||||
* @param nodeTypeQName the type of the node that will be used for the root
|
||||
* @param storeRef the reference to the store about to be created
|
||||
*/
|
||||
public void beforeCreateStore(QName nodeTypeQName, StoreRef storeRef);
|
||||
}
|
||||
|
||||
public interface OnCreateStorePolicy extends ClassPolicy
|
||||
{
|
||||
/**
|
||||
* Called when a new node store has been created.
|
||||
*
|
||||
* @param rootNodeRef the reference to the newly created root node
|
||||
*/
|
||||
public void onCreateStore(NodeRef rootNodeRef);
|
||||
}
|
||||
|
||||
public interface BeforeCreateNodePolicy extends ClassPolicy
|
||||
{
|
||||
/**
|
||||
* Called before a new node is created.
|
||||
*
|
||||
* @param parentRef the parent node reference
|
||||
* @param assocTypeQName the association type qualified name
|
||||
* @param assocQName the association qualified name
|
||||
* @param nodeTypeQName the node type qualified name
|
||||
*/
|
||||
public void beforeCreateNode(
|
||||
NodeRef parentRef,
|
||||
QName assocTypeQName,
|
||||
QName assocQName,
|
||||
QName nodeTypeQName);
|
||||
}
|
||||
|
||||
public interface OnCreateNodePolicy extends ClassPolicy
|
||||
{
|
||||
/**
|
||||
* Called when a new node has been created.
|
||||
*
|
||||
* @param childAssocRef the created child association reference
|
||||
*/
|
||||
public void onCreateNode(ChildAssociationRef childAssocRef);
|
||||
}
|
||||
|
||||
public interface BeforeUpdateNodePolicy extends ClassPolicy
|
||||
{
|
||||
/**
|
||||
* Called before a node is updated. This includes the modification of properties, child and target
|
||||
* associations and the addition of aspects.
|
||||
*
|
||||
* @param nodeRef reference to the node being updated
|
||||
*/
|
||||
public void beforeUpdateNode(NodeRef nodeRef);
|
||||
}
|
||||
|
||||
public interface OnUpdateNodePolicy extends ClassPolicy
|
||||
{
|
||||
/**
|
||||
* Called after a new node has been created. This includes the modification of properties, child and target
|
||||
* associations and the addition of aspects.
|
||||
*
|
||||
* @param nodeRef reference to the updated node
|
||||
*/
|
||||
public void onUpdateNode(NodeRef nodeRef);
|
||||
}
|
||||
|
||||
public interface OnUpdatePropertiesPolicy extends ClassPolicy
|
||||
{
|
||||
/**
|
||||
* Called after a node's properties have been changed.
|
||||
*
|
||||
* @param nodeRef reference to the updated node
|
||||
* @param before the node's properties before the change
|
||||
* @param after the node's properties after the change
|
||||
*/
|
||||
public void onUpdateProperties(
|
||||
NodeRef nodeRef,
|
||||
Map<QName, Serializable> before,
|
||||
Map<QName, Serializable> after);
|
||||
}
|
||||
|
||||
public interface BeforeDeleteNodePolicy extends ClassPolicy
|
||||
{
|
||||
/**
|
||||
* Called before a node is deleted.
|
||||
*
|
||||
* @param nodeRef the node reference
|
||||
*/
|
||||
public void beforeDeleteNode(NodeRef nodeRef);
|
||||
}
|
||||
|
||||
public interface OnDeleteNodePolicy extends ClassPolicy
|
||||
{
|
||||
/**
|
||||
* Called after a node is deleted. The reference given is for an association
|
||||
* which has been deleted and cannot be used to retrieve node or associaton
|
||||
* information from any of the services.
|
||||
*
|
||||
* @param childAssocRef the primary parent-child association of the deleted node
|
||||
*/
|
||||
public void onDeleteNode(ChildAssociationRef childAssocRef);
|
||||
}
|
||||
|
||||
public interface BeforeAddAspectPolicy extends ClassPolicy
|
||||
{
|
||||
/**
|
||||
* Called before an <b>aspect</b> is added to a node
|
||||
*
|
||||
* @param nodeRef the node to which the aspect will be added
|
||||
* @param aspectTypeQName the type of the aspect
|
||||
*/
|
||||
public void beforeAddAspect(NodeRef nodeRef, QName aspectTypeQName);
|
||||
}
|
||||
|
||||
public interface OnAddAspectPolicy extends ClassPolicy
|
||||
{
|
||||
/**
|
||||
* Called after an <b>aspect</b> has been added to a node
|
||||
*
|
||||
* @param nodeRef the node to which the aspect was added
|
||||
* @param aspectTypeQName the type of the aspect
|
||||
*/
|
||||
public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName);
|
||||
}
|
||||
|
||||
public interface BeforeRemoveAspectPolicy extends ClassPolicy
|
||||
{
|
||||
/**
|
||||
* Called before an <b>aspect</b> is removed from a node
|
||||
*
|
||||
* @param nodeRef the node from which the aspect will be removed
|
||||
* @param aspectTypeQName the type of the aspect
|
||||
*/
|
||||
public void beforeRemoveAspect(NodeRef nodeRef, QName aspectTypeQName);
|
||||
}
|
||||
|
||||
public interface OnRemoveAspectPolicy extends ClassPolicy
|
||||
{
|
||||
/**
|
||||
* Called after an <b>aspect</b> has been removed from a node
|
||||
*
|
||||
* @param nodeRef the node from which the aspect will be removed
|
||||
* @param aspectTypeQName the type of the aspect
|
||||
*/
|
||||
public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName);
|
||||
}
|
||||
|
||||
public interface BeforeCreateChildAssociationPolicy extends AssociationPolicy
|
||||
{
|
||||
/**
|
||||
* Called before a node child association is created.
|
||||
*
|
||||
* @param parentNodeRef
|
||||
* @param childNodeRef
|
||||
* @param assocTypeQName the type of the association
|
||||
* @param assocQName the name of the association
|
||||
*/
|
||||
public void beforeCreateChildAssociation(
|
||||
NodeRef parentNodeRef,
|
||||
NodeRef childNodeRef,
|
||||
QName assocTypeQName,
|
||||
QName assocQName);
|
||||
}
|
||||
|
||||
public interface OnCreateChildAssociationPolicy extends AssociationPolicy
|
||||
{
|
||||
/**
|
||||
* Called after a node child association has been created.
|
||||
*
|
||||
* @param childAssocRef the child association that has been created
|
||||
*/
|
||||
public void onCreateChildAssociation(ChildAssociationRef childAssocRef);
|
||||
}
|
||||
|
||||
public interface BeforeDeleteChildAssociationPolicy extends AssociationPolicy
|
||||
{
|
||||
/**
|
||||
* Called before a node child association is deleted.
|
||||
*
|
||||
* @param childAssocRef the child association to be deleted
|
||||
*/
|
||||
public void beforeDeleteChildAssociation(ChildAssociationRef childAssocRef);
|
||||
}
|
||||
|
||||
public interface OnDeleteChildAssociationPolicy extends AssociationPolicy
|
||||
{
|
||||
/**
|
||||
* Called after a node child association has been deleted.
|
||||
*
|
||||
* @param childAssocRef the child association that has been deleted
|
||||
*/
|
||||
public void onDeleteChildAssociation(ChildAssociationRef childAssocRef);
|
||||
}
|
||||
|
||||
public interface OnCreateAssociationPolicy extends AssociationPolicy
|
||||
{
|
||||
/**
|
||||
* Called after a regular node association is created.
|
||||
*
|
||||
* @param nodeAssocRef the regular node association that was created
|
||||
*/
|
||||
public void onCreateAssociation(AssociationRef nodeAssocRef);
|
||||
}
|
||||
|
||||
public interface OnDeleteAssociationPolicy extends AssociationPolicy
|
||||
{
|
||||
/**
|
||||
* Called after a regular node association is deleted.
|
||||
*
|
||||
* @param nodeAssocRef the regular node association that was removed
|
||||
*/
|
||||
public void onDeleteAssociation(AssociationRef nodeAssocRef);
|
||||
}
|
||||
}
|
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.dictionary.DictionaryComponent;
|
||||
import org.alfresco.repo.dictionary.DictionaryDAO;
|
||||
import org.alfresco.repo.dictionary.M2Model;
|
||||
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.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.ContentService;
|
||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.ApplicationContextHelper;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* PerformanceNodeServiceTest
|
||||
*/
|
||||
public class PerformanceNodeServiceTest extends TestCase
|
||||
{
|
||||
public static final String NAMESPACE = "http://www.alfresco.org/test/BaseNodeServiceTest";
|
||||
public static final String TEST_PREFIX = "test";
|
||||
public static final QName TYPE_QNAME_TEST = QName.createQName(NAMESPACE, "multiprop");
|
||||
public static final QName PROP_QNAME_NAME = QName.createQName(NAMESPACE, "name");
|
||||
public static final QName ASSOC_QNAME_CHILDREN = QName.createQName(NAMESPACE, "child");
|
||||
|
||||
private int flushCount = Integer.MAX_VALUE;
|
||||
|
||||
private int testDepth = 3;
|
||||
private int testChildCount = 5;
|
||||
private int testStringPropertyCount = 10;
|
||||
private int testContentPropertyCount = 10;
|
||||
|
||||
private static Log logger = LogFactory.getLog(PerformanceNodeServiceTest.class);
|
||||
private static ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext();
|
||||
|
||||
protected DictionaryService dictionaryService;
|
||||
protected NodeService nodeService;
|
||||
private ContentService contentService;
|
||||
private TransactionService txnService;
|
||||
|
||||
private int nodeCount = 0;
|
||||
|
||||
private long startTime;
|
||||
/** populated during setup */
|
||||
protected NodeRef rootNodeRef;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception
|
||||
{
|
||||
DictionaryDAO dictionaryDao = (DictionaryDAO) applicationContext.getBean("dictionaryDAO");
|
||||
|
||||
// load the system model
|
||||
ClassLoader cl = PerformanceNodeServiceTest.class.getClassLoader();
|
||||
InputStream modelStream = cl.getResourceAsStream("alfresco/model/contentModel.xml");
|
||||
assertNotNull(modelStream);
|
||||
M2Model model = M2Model.createModel(modelStream);
|
||||
dictionaryDao.putModel(model);
|
||||
|
||||
// load the test model
|
||||
modelStream = cl.getResourceAsStream("org/alfresco/repo/node/BaseNodeServiceTest_model.xml");
|
||||
assertNotNull(modelStream);
|
||||
model = M2Model.createModel(modelStream);
|
||||
dictionaryDao.putModel(model);
|
||||
|
||||
DictionaryComponent dictionary = new DictionaryComponent();
|
||||
dictionary.setDictionaryDAO(dictionaryDao);
|
||||
dictionaryService = loadModel(applicationContext);
|
||||
|
||||
nodeService = (NodeService) applicationContext.getBean("nodeService");
|
||||
txnService = (TransactionService) applicationContext.getBean("transactionComponent");
|
||||
contentService = (ContentService) applicationContext.getBean("contentService");
|
||||
|
||||
// create a first store directly
|
||||
TransactionWork<NodeRef> createStoreWork = new TransactionWork<NodeRef>()
|
||||
{
|
||||
public NodeRef doWork()
|
||||
{
|
||||
StoreRef storeRef = nodeService.createStore(
|
||||
StoreRef.PROTOCOL_WORKSPACE,
|
||||
"Test_" + System.nanoTime());
|
||||
return nodeService.getRootNode(storeRef);
|
||||
}
|
||||
};
|
||||
rootNodeRef = TransactionUtil.executeInUserTransaction(txnService, createStoreWork);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the test model required for building the node graphs
|
||||
*/
|
||||
public static DictionaryService loadModel(ApplicationContext applicationContext)
|
||||
{
|
||||
DictionaryDAO dictionaryDao = (DictionaryDAO) applicationContext.getBean("dictionaryDAO");
|
||||
|
||||
// load the system model
|
||||
ClassLoader cl = PerformanceNodeServiceTest.class.getClassLoader();
|
||||
InputStream modelStream = cl.getResourceAsStream("alfresco/model/contentModel.xml");
|
||||
assertNotNull(modelStream);
|
||||
M2Model model = M2Model.createModel(modelStream);
|
||||
dictionaryDao.putModel(model);
|
||||
|
||||
// load the test model
|
||||
modelStream = cl.getResourceAsStream("org/alfresco/repo/node/BaseNodeServiceTest_model.xml");
|
||||
assertNotNull(modelStream);
|
||||
model = M2Model.createModel(modelStream);
|
||||
dictionaryDao.putModel(model);
|
||||
|
||||
DictionaryComponent dictionary = new DictionaryComponent();
|
||||
dictionary.setDictionaryDAO(dictionaryDao);
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
public void testSetUp() throws Exception
|
||||
{
|
||||
assertNotNull("StoreService not set", nodeService);
|
||||
assertNotNull("NodeService not set", nodeService);
|
||||
assertNotNull("rootNodeRef not created", rootNodeRef);
|
||||
}
|
||||
|
||||
public void testPerformanceNodeService() throws Exception
|
||||
{
|
||||
startTime = System.currentTimeMillis();
|
||||
|
||||
// ensure that we execute the node tree building in a transaction
|
||||
TransactionWork<Object> buildChildrenWork = new TransactionWork<Object>()
|
||||
{
|
||||
public Object doWork()
|
||||
{
|
||||
buildNodeChildren(rootNodeRef, 1, testDepth, testChildCount);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
TransactionUtil.executeInUserTransaction(txnService, buildChildrenWork);
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
|
||||
System.out.println("Test completed: \n" +
|
||||
" Built " + nodeCount + " nodes in " + (endTime-startTime) + "ms \n" +
|
||||
" Depth: " + testDepth + "\n" +
|
||||
" Child count: " + testChildCount);
|
||||
}
|
||||
|
||||
public void buildNodeChildren(NodeRef parent, int level, int maxLevel, int childCount)
|
||||
{
|
||||
for (int i=0; i < childCount; i++)
|
||||
{
|
||||
ChildAssociationRef assocRef = this.nodeService.createNode(
|
||||
parent, ASSOC_QNAME_CHILDREN, QName.createQName(NAMESPACE, "child" + i), TYPE_QNAME_TEST);
|
||||
|
||||
nodeCount++;
|
||||
|
||||
NodeRef childRef = assocRef.getChildRef();
|
||||
|
||||
this.nodeService.setProperty(childRef,
|
||||
ContentModel.PROP_NAME, "node" + level + "_" + i);
|
||||
|
||||
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(17);
|
||||
for (int j = 0; j < testStringPropertyCount; j++)
|
||||
{
|
||||
properties.put(
|
||||
QName.createQName(NAMESPACE, "string" + j),
|
||||
level + "_" + i + "_" + j);
|
||||
}
|
||||
this.nodeService.setProperties(childRef, properties);
|
||||
|
||||
for (int j = 0; j < testContentPropertyCount; j++)
|
||||
{
|
||||
ContentWriter writer = this.contentService.getWriter(
|
||||
childRef, QName.createQName(NAMESPACE, "content" + j), true);
|
||||
|
||||
writer.setMimetype("text/plain");
|
||||
writer.putContent( level + "_" + i + "_" + j );
|
||||
}
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long diffTime = (currentTime - startTime);
|
||||
if (nodeCount % flushCount == 0)
|
||||
{
|
||||
System.out.println("Flushing transaction cache at nodecount: " + nodeCount);
|
||||
System.out.println("At time index " + diffTime + "ms");
|
||||
AlfrescoTransactionSupport.flush();
|
||||
}
|
||||
if (nodeCount % 100 == 0)
|
||||
{
|
||||
System.out.println("Interim summary: \n" +
|
||||
" nodes: " + nodeCount + "\n" +
|
||||
" time: " + (double)diffTime/1000.0/60.0 + " minutes \n" +
|
||||
" average: " + (double)nodeCount/(double)diffTime*1000.0 + " nodes/s");
|
||||
}
|
||||
|
||||
if (level < maxLevel)
|
||||
{
|
||||
buildNodeChildren(childRef, level + 1, maxLevel, childCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a test with more depth
|
||||
*/
|
||||
public static void main(String[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformanceNodeServiceTest test = new PerformanceNodeServiceTest();
|
||||
test.setUp();
|
||||
test.testChildCount = 5;
|
||||
test.testDepth = 6;
|
||||
test.flushCount = 1000;
|
||||
|
||||
test.testPerformanceNodeService();
|
||||
|
||||
test.tearDown();
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
80
source/java/org/alfresco/repo/node/ReferenceableAspect.java
Normal file
80
source/java/org/alfresco/repo/node/ReferenceableAspect.java
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.copy.CopyServicePolicies;
|
||||
import org.alfresco.repo.policy.JavaBehaviour;
|
||||
import org.alfresco.repo.policy.PolicyComponent;
|
||||
import org.alfresco.repo.policy.PolicyScope;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Registers and contains the behaviour specific to the
|
||||
* {@link org.alfresco.model.ContentModel#ASPECT_REFERENCEABLE referencable aspect}.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class ReferenceableAspect implements CopyServicePolicies.OnCopyNodePolicy
|
||||
{
|
||||
// Logger
|
||||
private static final Log logger = LogFactory.getLog(ReferenceableAspect.class);
|
||||
|
||||
// Dependencies
|
||||
private PolicyComponent policyComponent;
|
||||
|
||||
/**
|
||||
* @param policyComponent the policy component to register behaviour with
|
||||
*/
|
||||
public void setPolicyComponent(PolicyComponent policyComponent)
|
||||
{
|
||||
this.policyComponent = policyComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the Referencable Aspect
|
||||
* <p>
|
||||
* Ensures that the {@link ContentModel#ASPECT_REFERENCEABLE referencable aspect}
|
||||
* copy behaviour is disabled.
|
||||
*/
|
||||
public void init()
|
||||
{
|
||||
// disable copy for referencable aspect
|
||||
this.policyComponent.bindClassBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"),
|
||||
ContentModel.ASPECT_REFERENCEABLE,
|
||||
new JavaBehaviour(this, "onCopyNode"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing
|
||||
*/
|
||||
public void onCopyNode(
|
||||
QName classRef,
|
||||
NodeRef sourceNodeRef,
|
||||
StoreRef destinationStoreRef,
|
||||
boolean copyToNewNode,
|
||||
PolicyScope copyDetails)
|
||||
{
|
||||
// don't copy
|
||||
}
|
||||
}
|
1275
source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
Normal file
1275
source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
Normal file
File diff suppressed because it is too large
Load Diff
260
source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java
Normal file
260
source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* 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.db;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.domain.NodeStatus;
|
||||
import org.alfresco.repo.node.BaseNodeServiceTest;
|
||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
||||
import org.alfresco.repo.transaction.TransactionUtil;
|
||||
import org.alfresco.repo.transaction.TransactionUtil.TransactionWork;
|
||||
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.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
|
||||
/**
|
||||
* @see org.alfresco.repo.node.db.DbNodeServiceImpl
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class DbNodeServiceImplTest extends BaseNodeServiceTest
|
||||
{
|
||||
private TransactionService txnService;
|
||||
private NodeDaoService nodeDaoService;
|
||||
|
||||
protected NodeService getNodeService()
|
||||
{
|
||||
return (NodeService) applicationContext.getBean("NodeService");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetUpInTransaction() throws Exception
|
||||
{
|
||||
super.onSetUpInTransaction();
|
||||
txnService = (TransactionService) applicationContext.getBean("transactionComponent");
|
||||
nodeDaoService = (NodeDaoService) applicationContext.getBean("nodeDaoService");
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a child node and then iterates over the children of the parent node,
|
||||
* getting the QName. This caused some issues after we did some optimization
|
||||
* using lazy loading of the associations.
|
||||
*/
|
||||
public void testLazyLoadIssue() throws Exception
|
||||
{
|
||||
Map<QName, ChildAssociationRef> assocRefs = buildNodeGraph();
|
||||
// commit results
|
||||
setComplete();
|
||||
endTransaction();
|
||||
|
||||
UserTransaction userTransaction = txnService.getUserTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
userTransaction.begin();
|
||||
|
||||
ChildAssociationRef n6pn8Ref = assocRefs.get(QName.createQName(BaseNodeServiceTest.NAMESPACE, "n6_p_n8"));
|
||||
NodeRef n6Ref = n6pn8Ref.getParentRef();
|
||||
NodeRef n8Ref = n6pn8Ref.getChildRef();
|
||||
|
||||
// delete n8
|
||||
nodeService.deleteNode(n8Ref);
|
||||
|
||||
// get the parent children
|
||||
List<ChildAssociationRef> assocs = nodeService.getChildAssocs(n6Ref);
|
||||
for (ChildAssociationRef assoc : assocs)
|
||||
{
|
||||
// just checking
|
||||
}
|
||||
|
||||
userTransaction.commit();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
try { userTransaction.rollback(); } catch (IllegalStateException ee) {}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the node status changes correctly during:
|
||||
* <ul>
|
||||
* <li>creation</li>
|
||||
* <li>property changes</li>
|
||||
* <li>aspect changes</li>
|
||||
* <li>moving</li>
|
||||
* <li>deletion</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void testNodeStatus() throws Exception
|
||||
{
|
||||
Map<QName, ChildAssociationRef> assocRefs = buildNodeGraph();
|
||||
// get the node to play with
|
||||
ChildAssociationRef n6pn8Ref = assocRefs.get(QName.createQName(BaseNodeServiceTest.NAMESPACE, "n6_p_n8"));
|
||||
final NodeRef n6Ref = n6pn8Ref.getParentRef();
|
||||
final NodeRef n8Ref = n6pn8Ref.getChildRef();
|
||||
final Map<QName, Serializable> properties = nodeService.getProperties(n6Ref);
|
||||
|
||||
// commit results
|
||||
setComplete();
|
||||
endTransaction();
|
||||
|
||||
// change property - check status
|
||||
TransactionWork<Object> changePropertiesWork = new TransactionWork<Object>()
|
||||
{
|
||||
public Object doWork()
|
||||
{
|
||||
nodeService.setProperty(n6Ref, ContentModel.PROP_CREATED, new Date());
|
||||
return null;
|
||||
}
|
||||
};
|
||||
executeAndCheck(n6Ref, changePropertiesWork);
|
||||
|
||||
// add an aspect
|
||||
TransactionWork<Object> addAspectWork = new TransactionWork<Object>()
|
||||
{
|
||||
public Object doWork()
|
||||
{
|
||||
nodeService.addAspect(n6Ref, ASPECT_QNAME_TEST_MARKER, null);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
executeAndCheck(n6Ref, addAspectWork);
|
||||
|
||||
// remove an aspect
|
||||
TransactionWork<Object> removeAspectWork = new TransactionWork<Object>()
|
||||
{
|
||||
public Object doWork()
|
||||
{
|
||||
nodeService.removeAspect(n6Ref, ASPECT_QNAME_TEST_MARKER);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
executeAndCheck(n6Ref, removeAspectWork);
|
||||
|
||||
// move the node
|
||||
TransactionWork<Object> moveNodeWork = new TransactionWork<Object>()
|
||||
{
|
||||
public Object doWork()
|
||||
{
|
||||
nodeService.moveNode(
|
||||
n6Ref,
|
||||
rootNodeRef,
|
||||
ASSOC_TYPE_QNAME_TEST_CHILDREN,
|
||||
QName.createQName(NAMESPACE, "moved"));
|
||||
return null;
|
||||
}
|
||||
};
|
||||
executeAndCheck(n6Ref, moveNodeWork);
|
||||
|
||||
// delete the node
|
||||
TransactionWork<Object> deleteNodeWork = new TransactionWork<Object>()
|
||||
{
|
||||
public Object doWork()
|
||||
{
|
||||
nodeService.deleteNode(n6Ref);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
executeAndCheck(n6Ref, deleteNodeWork);
|
||||
|
||||
// check cascade-deleted nodes
|
||||
TransactionWork<Object> checkCascadeWork = new TransactionWork<Object>()
|
||||
{
|
||||
public Object doWork()
|
||||
{
|
||||
// check n6
|
||||
NodeStatus n6Status = nodeDaoService.getNodeStatus(
|
||||
n6Ref.getStoreRef().getProtocol(),
|
||||
n6Ref.getStoreRef().getIdentifier(),
|
||||
n6Ref.getId());
|
||||
if (!n6Status.isDeleted())
|
||||
{
|
||||
throw new RuntimeException("Deleted node does not have deleted status");
|
||||
}
|
||||
// n8 is a primary child - it should be deleted too
|
||||
NodeStatus n8Status = nodeDaoService.getNodeStatus(
|
||||
n8Ref.getStoreRef().getProtocol(),
|
||||
n8Ref.getStoreRef().getIdentifier(),
|
||||
n8Ref.getId());
|
||||
if (!n8Status.isDeleted())
|
||||
{
|
||||
throw new RuntimeException("Cascade-deleted node does not have deleted status");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
TransactionUtil.executeInUserTransaction(txnService, checkCascadeWork);
|
||||
|
||||
// check node recreation
|
||||
TransactionWork<Object> checkRecreateWork = new TransactionWork<Object>()
|
||||
{
|
||||
public Object doWork()
|
||||
{
|
||||
properties.put(ContentModel.PROP_STORE_PROTOCOL, n6Ref.getStoreRef().getProtocol());
|
||||
properties.put(ContentModel.PROP_STORE_IDENTIFIER, n6Ref.getStoreRef().getIdentifier());
|
||||
properties.put(ContentModel.PROP_NODE_UUID, n6Ref.getId());
|
||||
|
||||
// recreate n6
|
||||
nodeService.createNode(
|
||||
rootNodeRef,
|
||||
ASSOC_TYPE_QNAME_TEST_CHILDREN,
|
||||
QName.createQName(NAMESPACE, "recreated-n6"),
|
||||
ContentModel.TYPE_CONTAINER,
|
||||
properties);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
TransactionUtil.executeInUserTransaction(txnService, checkRecreateWork);
|
||||
}
|
||||
|
||||
private void executeAndCheck(NodeRef nodeRef, TransactionWork<Object> work) throws Exception
|
||||
{
|
||||
UserTransaction txn = txnService.getUserTransaction();
|
||||
txn.begin();
|
||||
|
||||
NodeRef.Status currentStatus = nodeService.getNodeStatus(nodeRef);
|
||||
assertNotNull(currentStatus);
|
||||
String currentTxnId = AlfrescoTransactionSupport.getTransactionId();
|
||||
assertNotNull(currentTxnId);
|
||||
assertNotSame(currentTxnId, currentStatus.getChangeTxnId());
|
||||
try
|
||||
{
|
||||
work.doWork();
|
||||
// get the status
|
||||
NodeRef.Status newStatus = nodeService.getNodeStatus(nodeRef);
|
||||
assertNotNull(newStatus);
|
||||
// check
|
||||
assertEquals("Change didn't update status", currentTxnId, newStatus.getChangeTxnId());
|
||||
txn.commit();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
try { txn.rollback(); } catch (Throwable ee) {}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
179
source/java/org/alfresco/repo/node/db/NodeDaoService.java
Normal file
179
source/java/org/alfresco/repo/node/db/NodeDaoService.java
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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.db;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.repo.domain.ChildAssoc;
|
||||
import org.alfresco.repo.domain.Node;
|
||||
import org.alfresco.repo.domain.NodeAssoc;
|
||||
import org.alfresco.repo.domain.NodeStatus;
|
||||
import org.alfresco.repo.domain.Store;
|
||||
import org.alfresco.service.cmr.dictionary.InvalidTypeException;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
|
||||
/**
|
||||
* Service layer accessing persistent <b>node</b> entities directly
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public interface NodeDaoService
|
||||
{
|
||||
/**
|
||||
* Are there any pending changes which must be synchronized with the store?
|
||||
*
|
||||
* @return true => changes are pending
|
||||
*/
|
||||
public boolean isDirty();
|
||||
|
||||
/**
|
||||
* Fetch a list of all stores in the repository
|
||||
*
|
||||
* @return Returns a list of stores
|
||||
*/
|
||||
public List<Store> getStores();
|
||||
|
||||
/**
|
||||
* Creates a unique store for the given protocol and identifier combination
|
||||
*
|
||||
* @param protocol a protocol, e.g. {@link org.alfresco.service.cmr.repository.StoreRef#PROTOCOL_WORKSPACE}
|
||||
* @param identifier a protocol-specific identifier
|
||||
* @return Returns the new persistent entity
|
||||
*/
|
||||
public Store createStore(String protocol, String identifier);
|
||||
|
||||
/**
|
||||
* @param protocol the protocol that the store serves
|
||||
* @param identifier the protocol-specific identifer
|
||||
* @return Returns a store with the given values or null if one doesn't exist
|
||||
*/
|
||||
public Store getStore(String protocol, String identifier);
|
||||
|
||||
/**
|
||||
* @param store the store to which the node must belong
|
||||
* @param id the node store-unique identifier
|
||||
* @param nodeTypeQName the type of the node
|
||||
* @return Returns a new node of the given type and attached to the store
|
||||
* @throws InvalidTypeException if the node type is invalid or if the node type
|
||||
* is not a valid real node
|
||||
*/
|
||||
public Node newNode(Store store, String id, QName nodeTypeQName) throws InvalidTypeException;
|
||||
|
||||
/**
|
||||
* @param protocol the store protocol
|
||||
* @param identifier the store identifier for the given protocol
|
||||
* @param id the store-specific node identifier
|
||||
* @return Returns the <b>node</b> entity
|
||||
*/
|
||||
public Node getNode(String protocol, String identifier, String id);
|
||||
|
||||
/**
|
||||
* Deletes the node instance, taking care of any cascades that are required over
|
||||
* and above those provided by the persistence mechanism.
|
||||
* <p>
|
||||
* A caller must able to delete the node using this method and not have to follow
|
||||
* up with any other ancillary deletes
|
||||
*
|
||||
* @param node the entity to delete
|
||||
* @param cascade true if the assoc deletions must cascade to primary child nodes
|
||||
*/
|
||||
public void deleteNode(Node node, boolean cascade);
|
||||
|
||||
/**
|
||||
* @return Returns the persisted and filled association
|
||||
*
|
||||
* @see ChildAssoc
|
||||
*/
|
||||
public ChildAssoc newChildAssoc(
|
||||
Node parentNode,
|
||||
Node childNode,
|
||||
boolean isPrimary,
|
||||
QName assocTypeQName,
|
||||
QName qname);
|
||||
|
||||
/**
|
||||
* @return Returns a matching association or null if one was not found
|
||||
*
|
||||
* @see ChildAssoc
|
||||
*/
|
||||
public ChildAssoc getChildAssoc(
|
||||
Node parentNode,
|
||||
Node childNode,
|
||||
QName assocTypeQName,
|
||||
QName qname);
|
||||
|
||||
|
||||
/**
|
||||
* @param assoc the child association to remove
|
||||
* @param cascade true if the assoc deletions must cascade to primary child nodes
|
||||
*/
|
||||
public void deleteChildAssoc(ChildAssoc assoc, boolean cascade);
|
||||
|
||||
/**
|
||||
* Finds the association between the node's primary parent and the node itself
|
||||
*
|
||||
* @param node the child node
|
||||
* @return Returns the primary <code>ChildAssoc</code> instance where the given node is the child.
|
||||
* The return value could be null for a root node - but ONLY a root node
|
||||
*/
|
||||
public ChildAssoc getPrimaryParentAssoc(Node node);
|
||||
|
||||
/**
|
||||
* @return Returns the persisted and filled association
|
||||
* @see NodeAssoc
|
||||
*/
|
||||
public NodeAssoc newNodeAssoc(
|
||||
Node sourceNode,
|
||||
Node targetNode,
|
||||
QName assocTypeQName);
|
||||
|
||||
/**
|
||||
* @return Returns the node association or null if not found
|
||||
*/
|
||||
public NodeAssoc getNodeAssoc(
|
||||
Node sourceNode,
|
||||
Node targetNode,
|
||||
QName assocTypeQName);
|
||||
|
||||
/**
|
||||
* @return Returns the target nodes for the association
|
||||
*/
|
||||
public Collection<Node> getNodeAssocTargets(Node sourceNode, QName assocTypeQName);
|
||||
|
||||
/**
|
||||
* @return Returns the source nodes for the association
|
||||
*/
|
||||
public Collection<Node> getNodeAssocSources(Node targetNode, QName assocTypeQName);
|
||||
|
||||
/**
|
||||
* @param assoc the node association to remove
|
||||
*/
|
||||
public void deleteNodeAssoc(NodeAssoc assoc);
|
||||
|
||||
/**
|
||||
* Gets the node's status. If the node <i>never</i> existed, then
|
||||
* <code>null</code> is returned.
|
||||
*
|
||||
* @param protocol the store protocol
|
||||
* @param identifier the store identifier for the given protocol
|
||||
* @param id the store-specific node status identifier
|
||||
* @return Returns the node status if the node exists or once existed, otherwise
|
||||
* returns <code>null</code>.
|
||||
*/
|
||||
public NodeStatus getNodeStatus(String protocol, String identifier, String id);
|
||||
}
|
@@ -0,0 +1,507 @@
|
||||
/*
|
||||
* 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.db.hibernate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.domain.ChildAssoc;
|
||||
import org.alfresco.repo.domain.Node;
|
||||
import org.alfresco.repo.domain.NodeAssoc;
|
||||
import org.alfresco.repo.domain.NodeKey;
|
||||
import org.alfresco.repo.domain.NodeStatus;
|
||||
import org.alfresco.repo.domain.Store;
|
||||
import org.alfresco.repo.domain.StoreKey;
|
||||
import org.alfresco.repo.domain.hibernate.ChildAssocImpl;
|
||||
import org.alfresco.repo.domain.hibernate.NodeAssocImpl;
|
||||
import org.alfresco.repo.domain.hibernate.NodeImpl;
|
||||
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.service.cmr.dictionary.InvalidTypeException;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.util.GUID;
|
||||
import org.hibernate.ObjectDeletedException;
|
||||
import org.hibernate.Query;
|
||||
import org.hibernate.Session;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.orm.hibernate3.HibernateCallback;
|
||||
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
|
||||
|
||||
/**
|
||||
* Hibernate-specific implementation of the persistence-independent <b>node</b> DAO interface
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements NodeDaoService
|
||||
{
|
||||
public static final String QUERY_GET_ALL_STORES = "store.GetAllStores";
|
||||
public static final String QUERY_GET_CHILD_ASSOC = "node.GetChildAssoc";
|
||||
public static final String QUERY_GET_NODE_ASSOC = "node.GetNodeAssoc";
|
||||
public static final String QUERY_GET_NODE_ASSOC_TARGETS = "node.GetNodeAssocTargets";
|
||||
public static final String QUERY_GET_NODE_ASSOC_SOURCES = "node.GetNodeAssocSources";
|
||||
|
||||
/** a uuid identifying this unique instance */
|
||||
private String uuid;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public HibernateNodeDaoServiceImpl()
|
||||
{
|
||||
this.uuid = GUID.generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks equality by type and uuid
|
||||
*/
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (!(obj instanceof HibernateNodeDaoServiceImpl))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
HibernateNodeDaoServiceImpl that = (HibernateNodeDaoServiceImpl) obj;
|
||||
return this.uuid.equals(that.uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #uuid
|
||||
*/
|
||||
public int hashCode()
|
||||
{
|
||||
return uuid.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this <tt>Session</tt> contain any changes which must be
|
||||
* synchronized with the store?
|
||||
*
|
||||
* @return true => changes are pending
|
||||
*/
|
||||
public boolean isDirty()
|
||||
{
|
||||
// create a callback for the task
|
||||
HibernateCallback callback = new HibernateCallback()
|
||||
{
|
||||
public Object doInHibernate(Session session)
|
||||
{
|
||||
return session.isDirty();
|
||||
}
|
||||
};
|
||||
// execute the callback
|
||||
return ((Boolean)getHibernateTemplate().execute(callback)).booleanValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #QUERY_GET_ALL_STORES
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Store> getStores()
|
||||
{
|
||||
HibernateCallback callback = new HibernateCallback()
|
||||
{
|
||||
public Object doInHibernate(Session session)
|
||||
{
|
||||
Query query = session.getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_ALL_STORES);
|
||||
return query.list();
|
||||
}
|
||||
};
|
||||
List<Store> queryResults = (List) getHibernateTemplate().execute(callback);
|
||||
// done
|
||||
return queryResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the store protocol/identifier combination is unique
|
||||
*/
|
||||
public Store createStore(String protocol, String identifier)
|
||||
{
|
||||
// ensure that the name isn't in use
|
||||
Store store = getStore(protocol, identifier);
|
||||
if (store != null)
|
||||
{
|
||||
throw new RuntimeException("A store already exists: \n" +
|
||||
" protocol: " + protocol + "\n" +
|
||||
" identifier: " + identifier + "\n" +
|
||||
" store: " + store);
|
||||
}
|
||||
|
||||
store = new StoreImpl();
|
||||
// set key
|
||||
store.setKey(new StoreKey(protocol, identifier));
|
||||
// persist so that it is present in the hibernate cache
|
||||
getHibernateTemplate().save(store);
|
||||
// create and assign a root node
|
||||
Node rootNode = newNode(
|
||||
store,
|
||||
GUID.generate(),
|
||||
ContentModel.TYPE_STOREROOT);
|
||||
store.setRootNode(rootNode);
|
||||
// done
|
||||
return store;
|
||||
}
|
||||
|
||||
public Store getStore(String protocol, String identifier)
|
||||
{
|
||||
StoreKey storeKey = new StoreKey(protocol, identifier);
|
||||
Store store = (Store) getHibernateTemplate().get(StoreImpl.class, storeKey);
|
||||
// done
|
||||
return store;
|
||||
}
|
||||
|
||||
public Node newNode(Store store, String id, QName nodeTypeQName) throws InvalidTypeException
|
||||
{
|
||||
NodeKey key = new NodeKey(store.getKey(), id);
|
||||
|
||||
// create (or reuse) the mandatory node status
|
||||
NodeStatus nodeStatus = (NodeStatus) getHibernateTemplate().get(NodeStatusImpl.class, key);
|
||||
if (nodeStatus == null)
|
||||
{
|
||||
nodeStatus = new NodeStatusImpl();
|
||||
}
|
||||
// set required status properties
|
||||
nodeStatus.setKey(key);
|
||||
nodeStatus.setDeleted(false);
|
||||
nodeStatus.setChangeTxnId(AlfrescoTransactionSupport.getTransactionId());
|
||||
// persist the nodestatus
|
||||
getHibernateTemplate().save(nodeStatus);
|
||||
|
||||
// build a concrete node based on a bootstrap type
|
||||
Node node = new NodeImpl();
|
||||
// set other required properties
|
||||
node.setKey(key);
|
||||
node.setTypeQName(nodeTypeQName);
|
||||
node.setStore(store);
|
||||
node.setStatus(nodeStatus);
|
||||
// persist the node
|
||||
getHibernateTemplate().save(node);
|
||||
// done
|
||||
return node;
|
||||
}
|
||||
|
||||
public Node getNode(String protocol, String identifier, String id)
|
||||
{
|
||||
try
|
||||
{
|
||||
NodeKey nodeKey = new NodeKey(protocol, identifier, id);
|
||||
Object obj = getHibernateTemplate().get(NodeImpl.class, nodeKey);
|
||||
// done
|
||||
return (Node) obj;
|
||||
}
|
||||
catch (ObjectDeletedException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
catch (DataAccessException e)
|
||||
{
|
||||
if (e.contains(ObjectDeletedException.class))
|
||||
{
|
||||
// the object no loner exists
|
||||
return null;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually ensures that all cascading of associations is taken care of
|
||||
*/
|
||||
public void deleteNode(Node node, boolean cascade)
|
||||
{
|
||||
// delete all parent assocs
|
||||
Collection<ChildAssoc> parentAssocs = node.getParentAssocs();
|
||||
parentAssocs = new ArrayList<ChildAssoc>(parentAssocs);
|
||||
for (ChildAssoc assoc : parentAssocs)
|
||||
{
|
||||
deleteChildAssoc(assoc, false); // we don't cascade upwards
|
||||
}
|
||||
// delete all child assocs
|
||||
Collection<ChildAssoc> childAssocs = node.getChildAssocs();
|
||||
childAssocs = new ArrayList<ChildAssoc>(childAssocs);
|
||||
for (ChildAssoc assoc : childAssocs)
|
||||
{
|
||||
deleteChildAssoc(assoc, cascade); // potentially cascade downwards
|
||||
}
|
||||
// delete all target assocs
|
||||
Collection<NodeAssoc> targetAssocs = node.getTargetNodeAssocs();
|
||||
targetAssocs = new ArrayList<NodeAssoc>(targetAssocs);
|
||||
for (NodeAssoc assoc : targetAssocs)
|
||||
{
|
||||
deleteNodeAssoc(assoc);
|
||||
}
|
||||
// delete all source assocs
|
||||
Collection<NodeAssoc> sourceAssocs = node.getSourceNodeAssocs();
|
||||
sourceAssocs = new ArrayList<NodeAssoc>(sourceAssocs);
|
||||
for (NodeAssoc assoc : sourceAssocs)
|
||||
{
|
||||
deleteNodeAssoc(assoc);
|
||||
}
|
||||
// update the node status
|
||||
NodeStatus nodeStatus = node.getStatus();
|
||||
nodeStatus.setDeleted(true);
|
||||
nodeStatus.setChangeTxnId(AlfrescoTransactionSupport.getTransactionId());
|
||||
// finally delete the node
|
||||
getHibernateTemplate().delete(node);
|
||||
// done
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the node status, if it exists
|
||||
*/
|
||||
public NodeStatus getNodeStatus(String protocol, String identifier, String id)
|
||||
{
|
||||
try
|
||||
{
|
||||
NodeKey nodeKey = new NodeKey(protocol, identifier, id);
|
||||
Object obj = getHibernateTemplate().get(NodeStatusImpl.class, nodeKey);
|
||||
// done
|
||||
return (NodeStatus) obj;
|
||||
}
|
||||
catch (DataAccessException e)
|
||||
{
|
||||
if (e.contains(ObjectDeletedException.class))
|
||||
{
|
||||
// the object no loner exists
|
||||
return null;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public ChildAssoc newChildAssoc(
|
||||
Node parentNode,
|
||||
Node childNode,
|
||||
boolean isPrimary,
|
||||
QName assocTypeQName,
|
||||
QName qname)
|
||||
{
|
||||
ChildAssoc assoc = new ChildAssocImpl();
|
||||
assoc.setTypeQName(assocTypeQName);
|
||||
assoc.setIsPrimary(isPrimary);
|
||||
assoc.setQname(qname);
|
||||
assoc.buildAssociation(parentNode, childNode);
|
||||
// persist
|
||||
getHibernateTemplate().save(assoc);
|
||||
// done
|
||||
return assoc;
|
||||
}
|
||||
|
||||
public ChildAssoc getChildAssoc(
|
||||
Node parentNode,
|
||||
Node childNode,
|
||||
QName assocTypeQName,
|
||||
QName qname)
|
||||
{
|
||||
ChildAssociationRef childAssocRef = new ChildAssociationRef(
|
||||
assocTypeQName,
|
||||
parentNode.getNodeRef(),
|
||||
qname,
|
||||
childNode.getNodeRef());
|
||||
// get all the parent's child associations
|
||||
Collection<ChildAssoc> assocs = parentNode.getChildAssocs();
|
||||
// hunt down the desired assoc
|
||||
for (ChildAssoc assoc : assocs)
|
||||
{
|
||||
// is it a match?
|
||||
if (!assoc.getChildAssocRef().equals(childAssocRef)) // not a match
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return assoc;
|
||||
}
|
||||
}
|
||||
// not found
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually enforces cascade deletions down primary associations
|
||||
*/
|
||||
public void deleteChildAssoc(ChildAssoc assoc, boolean cascade)
|
||||
{
|
||||
Node childNode = assoc.getChild();
|
||||
|
||||
// maintain inverse association sets
|
||||
assoc.removeAssociation();
|
||||
// remove instance
|
||||
getHibernateTemplate().delete(assoc);
|
||||
|
||||
if (cascade && assoc.getIsPrimary()) // the assoc is primary
|
||||
{
|
||||
// delete the child node
|
||||
deleteNode(childNode, cascade);
|
||||
/*
|
||||
* The child node deletion will cascade delete all assocs to
|
||||
* and from it, but we have safely removed this one, so no
|
||||
* duplicate call will be received to do this
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
public ChildAssoc getPrimaryParentAssoc(Node node)
|
||||
{
|
||||
// get the assocs pointing to the node
|
||||
Collection<ChildAssoc> parentAssocs = node.getParentAssocs();
|
||||
ChildAssoc primaryAssoc = null;
|
||||
for (ChildAssoc assoc : parentAssocs)
|
||||
{
|
||||
// ignore non-primary assocs
|
||||
if (!assoc.getIsPrimary())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (primaryAssoc != null)
|
||||
{
|
||||
// we have more than one somehow
|
||||
throw new DataIntegrityViolationException(
|
||||
"Multiple primary associations: \n" +
|
||||
" child: " + node + "\n" +
|
||||
" first primary assoc: " + primaryAssoc + "\n" +
|
||||
" second primary assoc: " + assoc);
|
||||
}
|
||||
primaryAssoc = assoc;
|
||||
// we keep looping to hunt out data integrity issues
|
||||
}
|
||||
// did we find a primary assoc?
|
||||
if (primaryAssoc == null)
|
||||
{
|
||||
// the only condition where this is allowed is if the given node is a root node
|
||||
Store store = node.getStore();
|
||||
Node rootNode = store.getRootNode();
|
||||
if (rootNode == null)
|
||||
{
|
||||
// a store without a root node - the entire store is hosed
|
||||
throw new DataIntegrityViolationException("Store has no root node: \n" +
|
||||
" store: " + store);
|
||||
}
|
||||
if (!rootNode.equals(node))
|
||||
{
|
||||
// it wasn't the root node
|
||||
throw new DataIntegrityViolationException("Non-root node has no primary parent: \n" +
|
||||
" child: " + node);
|
||||
}
|
||||
}
|
||||
// done
|
||||
return primaryAssoc;
|
||||
}
|
||||
|
||||
public NodeAssoc newNodeAssoc(Node sourceNode, Node targetNode, QName assocTypeQName)
|
||||
{
|
||||
NodeAssoc assoc = new NodeAssocImpl();
|
||||
assoc.setTypeQName(assocTypeQName);
|
||||
assoc.buildAssociation(sourceNode, targetNode);
|
||||
// persist
|
||||
getHibernateTemplate().save(assoc);
|
||||
// done
|
||||
return assoc;
|
||||
}
|
||||
|
||||
public NodeAssoc getNodeAssoc(
|
||||
final Node sourceNode,
|
||||
final Node targetNode,
|
||||
final QName assocTypeQName)
|
||||
{
|
||||
final NodeKey sourceKey = sourceNode.getKey();
|
||||
final NodeKey targetKey = targetNode.getKey();
|
||||
HibernateCallback callback = new HibernateCallback()
|
||||
{
|
||||
public Object doInHibernate(Session session)
|
||||
{
|
||||
Query query = session.getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODE_ASSOC);
|
||||
query.setString("sourceKeyProtocol", sourceKey.getProtocol())
|
||||
.setString("sourceKeyIdentifier", sourceKey.getIdentifier())
|
||||
.setString("sourceKeyGuid", sourceKey.getGuid())
|
||||
.setString("assocTypeQName", assocTypeQName.toString())
|
||||
.setString("targetKeyProtocol", targetKey.getProtocol())
|
||||
.setString("targetKeyIdentifier", targetKey.getIdentifier())
|
||||
.setString("targetKeyGuid", targetKey.getGuid());
|
||||
query.setMaxResults(1);
|
||||
return query.uniqueResult();
|
||||
}
|
||||
};
|
||||
Object queryResult = getHibernateTemplate().execute(callback);
|
||||
if (queryResult == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
NodeAssoc assoc = (NodeAssoc) queryResult;
|
||||
// done
|
||||
return assoc;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Collection<Node> getNodeAssocTargets(final Node sourceNode, final QName assocTypeQName)
|
||||
{
|
||||
final NodeKey sourceKey = sourceNode.getKey();
|
||||
HibernateCallback callback = new HibernateCallback()
|
||||
{
|
||||
public Object doInHibernate(Session session)
|
||||
{
|
||||
Query query = session.getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODE_ASSOC_TARGETS);
|
||||
query.setString("sourceKeyProtocol", sourceKey.getProtocol())
|
||||
.setString("sourceKeyIdentifier", sourceKey.getIdentifier())
|
||||
.setString("sourceKeyGuid", sourceKey.getGuid())
|
||||
.setString("assocTypeQName", assocTypeQName.toString());
|
||||
return query.list();
|
||||
}
|
||||
};
|
||||
List<Node> queryResults = (List) getHibernateTemplate().execute(callback);
|
||||
// done
|
||||
return queryResults;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Collection<Node> getNodeAssocSources(final Node targetNode, final QName assocTypeQName)
|
||||
{
|
||||
final NodeKey targetKey = targetNode.getKey();
|
||||
HibernateCallback callback = new HibernateCallback()
|
||||
{
|
||||
public Object doInHibernate(Session session)
|
||||
{
|
||||
Query query = session.getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODE_ASSOC_SOURCES);
|
||||
query.setString("targetKeyProtocol", targetKey.getProtocol())
|
||||
.setString("targetKeyIdentifier", targetKey.getIdentifier())
|
||||
.setString("targetKeyGuid", targetKey.getGuid())
|
||||
.setString("assocTypeQName", assocTypeQName.toString());
|
||||
return query.list();
|
||||
}
|
||||
};
|
||||
List<Node> queryResults = (List) getHibernateTemplate().execute(callback);
|
||||
// done
|
||||
return queryResults;
|
||||
}
|
||||
|
||||
public void deleteNodeAssoc(NodeAssoc assoc)
|
||||
{
|
||||
// maintain inverse association sets
|
||||
assoc.removeAssociation();
|
||||
// remove instance
|
||||
getHibernateTemplate().delete(assoc);
|
||||
}
|
||||
}
|
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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.index;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer;
|
||||
import org.alfresco.repo.transaction.TransactionUtil;
|
||||
import org.alfresco.repo.transaction.TransactionUtil.TransactionWork;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.search.SearchService;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Ensures that the FTS indexing picks up on any outstanding documents that
|
||||
* require indexing.
|
||||
* <p>
|
||||
* FTS indexing is a background process. It is therefore possible that
|
||||
* certain documents don't get indexed when the server shuts down.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class FtsIndexRecoveryComponent implements IndexRecovery
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(FtsIndexRecoveryComponent.class);
|
||||
|
||||
/** provides transactions to atomically index each missed transaction */
|
||||
private TransactionService transactionService;
|
||||
/** the FTS indexer that we will prompt to pick up on any un-indexed text */
|
||||
private FullTextSearchIndexer ftsIndexer;
|
||||
/** the component providing searches of the indexed nodes */
|
||||
private SearchService searcher;
|
||||
/** the component giving direct access to <b>node</b> instances */
|
||||
private NodeService nodeService;
|
||||
/** the workspaces to reindex */
|
||||
private List<StoreRef> storeRefs;
|
||||
|
||||
public FtsIndexRecoveryComponent()
|
||||
{
|
||||
this.storeRefs = new ArrayList<StoreRef>(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param transactionService provide transactions to index each missed transaction
|
||||
*/
|
||||
public void setTransactionService(TransactionService transactionService)
|
||||
{
|
||||
this.transactionService = transactionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ftsIndexer the FTS background indexer
|
||||
*/
|
||||
public void setFtsIndexer(FullTextSearchIndexer ftsIndexer)
|
||||
{
|
||||
this.ftsIndexer = ftsIndexer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nodeService provides information about nodes for indexing
|
||||
*/
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the workspaces that need reindexing
|
||||
*
|
||||
* @param storeRefStrings a list of strings representing store references
|
||||
*/
|
||||
public void setStores(List<String> storeRefStrings)
|
||||
{
|
||||
storeRefs.clear();
|
||||
for (String storeRefStr : storeRefStrings)
|
||||
{
|
||||
StoreRef storeRef = new StoreRef(storeRefStr);
|
||||
storeRefs.add(storeRef);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the FTS indexing is activated for any outstanding full text searches.
|
||||
*/
|
||||
public void reindex()
|
||||
{
|
||||
TransactionWork<Object> reindexWork = new TransactionWork<Object>()
|
||||
{
|
||||
public Object doWork()
|
||||
{
|
||||
// reindex each store
|
||||
for (StoreRef storeRef : storeRefs)
|
||||
{
|
||||
// check if the store exists
|
||||
if (!nodeService.exists(storeRef))
|
||||
{
|
||||
// store does not exist
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Skipping reindex of non-existent store: " + storeRef);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// prompt FTS to reindex the store
|
||||
ftsIndexer.requiresIndex(storeRef);
|
||||
}
|
||||
// done
|
||||
return null;
|
||||
}
|
||||
};
|
||||
TransactionUtil.executeInUserTransaction(transactionService, reindexWork);
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Prompted FTS index on stores: " + storeRefs);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.index;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.repo.search.Indexer;
|
||||
import org.alfresco.repo.transaction.TransactionUtil;
|
||||
import org.alfresco.repo.transaction.TransactionUtil.TransactionWork;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.ApplicationContextHelper;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* Checks that the FTS index recovery component is working
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class FtsIndexRecoveryComponentTest extends TestCase
|
||||
{
|
||||
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
||||
|
||||
private IndexRecovery indexRecoverer;
|
||||
private NodeService nodeService;
|
||||
private TransactionService txnService;
|
||||
private Indexer indexer;
|
||||
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
indexRecoverer = (IndexRecovery) ctx.getBean("indexRecoveryComponent");
|
||||
txnService = (TransactionService) ctx.getBean("transactionComponent");
|
||||
nodeService = (NodeService) ctx.getBean("nodeService");
|
||||
indexer = (Indexer) ctx.getBean("indexerComponent");
|
||||
}
|
||||
|
||||
public void testReindexing() throws Exception
|
||||
{
|
||||
// performs a reindex
|
||||
TransactionWork<Object> reindexWork = new TransactionWork<Object>()
|
||||
{
|
||||
public Object doWork()
|
||||
{
|
||||
indexRecoverer.reindex();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// reindex
|
||||
TransactionUtil.executeInNonPropagatingUserTransaction(txnService, reindexWork);
|
||||
}
|
||||
}
|
30
source/java/org/alfresco/repo/node/index/IndexRecovery.java
Normal file
30
source/java/org/alfresco/repo/node/index/IndexRecovery.java
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.index;
|
||||
|
||||
/**
|
||||
* Interface for components able to recover indexes.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public interface IndexRecovery
|
||||
{
|
||||
/**
|
||||
* Forces a reindex
|
||||
*/
|
||||
public void reindex();
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package org.alfresco.repo.node.index;
|
||||
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
|
||||
/**
|
||||
* Forces a index recovery using the {@link IndexRecovery recovery component} passed
|
||||
* in via the job detail.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class IndexRecoveryJob implements Job
|
||||
{
|
||||
/** KEY_INDEX_RECOVERY_COMPONENT = 'indexRecoveryComponent' */
|
||||
public static final String KEY_INDEX_RECOVERY_COMPONENT = "indexRecoveryComponent";
|
||||
|
||||
/**
|
||||
* Forces a full index recovery using the {@link IndexRecovery recovery component} passed
|
||||
* in via the job detail.
|
||||
*/
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException
|
||||
{
|
||||
IndexRecovery indexRecoveryComponent = (IndexRecovery) context.getJobDetail()
|
||||
.getJobDataMap().get(KEY_INDEX_RECOVERY_COMPONENT);
|
||||
if (indexRecoveryComponent == null)
|
||||
{
|
||||
throw new JobExecutionException("Missing job data: " + KEY_INDEX_RECOVERY_COMPONENT);
|
||||
}
|
||||
// reindex
|
||||
indexRecoveryComponent.reindex();
|
||||
}
|
||||
}
|
125
source/java/org/alfresco/repo/node/index/NodeIndexer.java
Normal file
125
source/java/org/alfresco/repo/node/index/NodeIndexer.java
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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.index;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.node.NodeServicePolicies;
|
||||
import org.alfresco.repo.policy.JavaBehaviour;
|
||||
import org.alfresco.repo.policy.PolicyComponent;
|
||||
import org.alfresco.repo.search.Indexer;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
|
||||
/**
|
||||
* Handles the node policy callbacks to ensure that the node hierarchy is properly
|
||||
* indexed.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class NodeIndexer
|
||||
implements NodeServicePolicies.BeforeCreateStorePolicy,
|
||||
NodeServicePolicies.OnCreateNodePolicy,
|
||||
NodeServicePolicies.OnUpdateNodePolicy,
|
||||
NodeServicePolicies.OnDeleteNodePolicy,
|
||||
NodeServicePolicies.OnCreateChildAssociationPolicy,
|
||||
NodeServicePolicies.OnDeleteChildAssociationPolicy
|
||||
{
|
||||
/** the component to register the behaviour with */
|
||||
private PolicyComponent policyComponent;
|
||||
/** the component to index the node hierarchy */
|
||||
private Indexer indexer;
|
||||
|
||||
/**
|
||||
* @param policyComponent used for registrations
|
||||
*/
|
||||
public void setPolicyComponent(PolicyComponent policyComponent)
|
||||
{
|
||||
this.policyComponent = policyComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param indexer the indexer that will be index
|
||||
*/
|
||||
public void setIndexer(Indexer indexer)
|
||||
{
|
||||
this.indexer = indexer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the policy behaviour methods
|
||||
*/
|
||||
private void init()
|
||||
{
|
||||
policyComponent.bindClassBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "beforeCreateStore"),
|
||||
ContentModel.TYPE_STOREROOT,
|
||||
new JavaBehaviour(this, "beforeCreateStore"));
|
||||
policyComponent.bindClassBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onCreateNode"));
|
||||
policyComponent.bindClassBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateNode"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onUpdateNode"));
|
||||
policyComponent.bindClassBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteNode"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onDeleteNode"));
|
||||
policyComponent.bindAssociationBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateChildAssociation"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onCreateChildAssociation"));
|
||||
policyComponent.bindAssociationBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteChildAssociation"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onDeleteChildAssociation"));
|
||||
}
|
||||
|
||||
public void beforeCreateStore(QName nodeTypeQName, StoreRef storeRef)
|
||||
{
|
||||
// indexer can perform some cleanup here, if required
|
||||
}
|
||||
|
||||
public void onCreateNode(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
indexer.createNode(childAssocRef);
|
||||
}
|
||||
|
||||
public void onUpdateNode(NodeRef nodeRef)
|
||||
{
|
||||
indexer.updateNode(nodeRef);
|
||||
}
|
||||
|
||||
public void onDeleteNode(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
indexer.deleteNode(childAssocRef);
|
||||
}
|
||||
|
||||
public void onCreateChildAssociation(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
indexer.createChildRelationship(childAssocRef);
|
||||
}
|
||||
|
||||
public void onDeleteChildAssociation(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
indexer.deleteChildRelationship(childAssocRef);
|
||||
}
|
||||
}
|
158
source/java/org/alfresco/repo/node/index/NodeIndexerTest.java
Normal file
158
source/java/org/alfresco/repo/node/index/NodeIndexerTest.java
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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.index;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.repo.node.BaseNodeServiceTest;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.search.ResultSet;
|
||||
import org.alfresco.service.cmr.search.SearchService;
|
||||
import org.alfresco.service.namespace.DynamicNamespacePrefixResolver;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.util.perf.PerformanceMonitor;
|
||||
|
||||
/**
|
||||
* Checks that the indexing of the node hierarchy is working
|
||||
*
|
||||
* @see org.alfresco.repo.node.index.NodeIndexer
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class NodeIndexerTest extends BaseNodeServiceTest
|
||||
{
|
||||
private SearchService searchService;
|
||||
private static StoreRef localStoreRef;
|
||||
private static NodeRef localRootNode;
|
||||
|
||||
@Override
|
||||
protected NodeService getNodeService()
|
||||
{
|
||||
return (NodeService) applicationContext.getBean("nodeService");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetUpInTransaction() throws Exception
|
||||
{
|
||||
super.onSetUpInTransaction();
|
||||
searchService = (SearchService) applicationContext.getBean("searchService");
|
||||
|
||||
if (localStoreRef == null)
|
||||
{
|
||||
localStoreRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_Persisted" + System.currentTimeMillis());
|
||||
localRootNode = nodeService.getRootNode(localStoreRef);
|
||||
}
|
||||
}
|
||||
|
||||
public void testCommitQueryData() throws Exception
|
||||
{
|
||||
rootNodeRef = localRootNode;
|
||||
buildNodeGraph();
|
||||
setComplete();
|
||||
}
|
||||
|
||||
public void testQuery() throws Exception
|
||||
{
|
||||
rootNodeRef = localRootNode;
|
||||
ResultSet results = searchService.query(rootNodeRef.getStoreRef(), "lucene", "PATH:\"" + BaseNodeServiceTest.TEST_PREFIX + ":root_p_n1\"", null, null);
|
||||
assertEquals(1, results.length());
|
||||
results.close();
|
||||
}
|
||||
|
||||
public void testLikeAndContains() throws Exception
|
||||
{
|
||||
rootNodeRef = localRootNode;
|
||||
|
||||
DynamicNamespacePrefixResolver namespacePrefixResolver = new DynamicNamespacePrefixResolver(null);
|
||||
namespacePrefixResolver.registerNamespace(NamespaceService.SYSTEM_MODEL_PREFIX, NamespaceService.SYSTEM_MODEL_1_0_URI);
|
||||
namespacePrefixResolver.registerNamespace(NamespaceService.CONTENT_MODEL_PREFIX, NamespaceService.CONTENT_MODEL_1_0_URI);
|
||||
namespacePrefixResolver.registerNamespace(BaseNodeServiceTest.TEST_PREFIX, BaseNodeServiceTest.NAMESPACE);
|
||||
|
||||
PerformanceMonitor selectNodesPerf = new PerformanceMonitor(getClass().getSimpleName(), "selectNodes");
|
||||
PerformanceMonitor selectPropertiesPerf = new PerformanceMonitor(getClass().getSimpleName(), "selectProperties");
|
||||
|
||||
List<NodeRef> answer;
|
||||
|
||||
selectNodesPerf.start();
|
||||
answer = searchService.selectNodes(rootNodeRef, "//*[like(@test:animal, 'm_nkey')]", null, namespacePrefixResolver, false);
|
||||
assertEquals(1, answer.size());
|
||||
selectNodesPerf.stop();
|
||||
|
||||
selectNodesPerf.start();
|
||||
answer = searchService.selectNodes(rootNodeRef, "//*[like(@test:animal, 'm%key')]", null, namespacePrefixResolver, false);
|
||||
assertEquals(1, answer.size());
|
||||
selectNodesPerf.stop();
|
||||
|
||||
selectNodesPerf.start();
|
||||
answer = searchService.selectNodes(rootNodeRef, "//*[like(@test:animal, 'monk__')]", null, namespacePrefixResolver, false);
|
||||
assertEquals(1, answer.size());
|
||||
selectNodesPerf.stop();
|
||||
|
||||
selectNodesPerf.start();
|
||||
answer = searchService.selectNodes(rootNodeRef, "//*[like(@test:animal, 'monk%')]", null, namespacePrefixResolver, false);
|
||||
assertEquals(1, answer.size());
|
||||
selectNodesPerf.stop();
|
||||
|
||||
selectNodesPerf.start();
|
||||
answer = searchService.selectNodes(rootNodeRef, "//*[like(@test:animal, 'monk\\%')]", null, namespacePrefixResolver, false);
|
||||
assertEquals(0, answer.size());
|
||||
selectNodesPerf.stop();
|
||||
|
||||
selectNodesPerf.start();
|
||||
answer = searchService.selectNodes(rootNodeRef, "//*[contains('monkey')]", null, namespacePrefixResolver, false);
|
||||
assertEquals(1, answer.size());
|
||||
selectNodesPerf.stop();
|
||||
|
||||
selectPropertiesPerf.start();
|
||||
List<Serializable> result = searchService.selectProperties(rootNodeRef, "//@*[contains('monkey')]", null, namespacePrefixResolver, false);
|
||||
assertEquals(2, result.size());
|
||||
selectPropertiesPerf.stop();
|
||||
|
||||
selectNodesPerf.start();
|
||||
answer = searchService.selectNodes(rootNodeRef, "//*[contains('mon?ey')]", null, namespacePrefixResolver, false);
|
||||
assertEquals(1, answer.size());
|
||||
selectNodesPerf.stop();
|
||||
|
||||
selectPropertiesPerf.start();
|
||||
result = searchService.selectProperties(rootNodeRef, "//@*[contains('mon?ey')]", null, namespacePrefixResolver, false);
|
||||
assertEquals(2, result.size());
|
||||
selectPropertiesPerf.stop();
|
||||
|
||||
selectNodesPerf.start();
|
||||
answer = searchService.selectNodes(rootNodeRef, "//*[contains('m*y')]", null, namespacePrefixResolver, false);
|
||||
assertEquals(1, answer.size());
|
||||
selectNodesPerf.stop();
|
||||
|
||||
selectPropertiesPerf.start();
|
||||
result = searchService.selectProperties(rootNodeRef, "//@*[contains('mon*')]", null, namespacePrefixResolver, false);
|
||||
assertEquals(2, result.size());
|
||||
selectPropertiesPerf.stop();
|
||||
|
||||
selectNodesPerf.start();
|
||||
answer = searchService.selectNodes(rootNodeRef, "//*[contains('*nkey')]", null, namespacePrefixResolver, false);
|
||||
assertEquals(1, answer.size());
|
||||
selectNodesPerf.stop();
|
||||
|
||||
selectPropertiesPerf.start();
|
||||
result = searchService.selectProperties(rootNodeRef, "//@*[contains('?onkey')]", null, namespacePrefixResolver, false);
|
||||
assertEquals(2, result.size());
|
||||
selectPropertiesPerf.stop();
|
||||
}
|
||||
}
|
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.util.EqualsHelper;
|
||||
|
||||
/**
|
||||
* Base class for integrity events. It provides basic support for checking
|
||||
* model integrity.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public abstract class AbstractIntegrityEvent implements IntegrityEvent
|
||||
{
|
||||
protected final NodeService nodeService;
|
||||
protected final DictionaryService dictionaryService;
|
||||
|
||||
/** the potential problem traces */
|
||||
private List<StackTraceElement[]> traces;
|
||||
/** support for derived classes */
|
||||
private final NodeRef nodeRef;
|
||||
/** support for derived classes */
|
||||
private final QName typeQName;
|
||||
/** support for derived classes */
|
||||
private final QName qname;
|
||||
|
||||
/** cached hashcode as the members are all final */
|
||||
private int hashCode = 0;
|
||||
|
||||
/**
|
||||
* Constructor with helper values for storage
|
||||
*/
|
||||
protected AbstractIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef nodeRef,
|
||||
QName typeQName,
|
||||
QName qname)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
this.dictionaryService = dictionaryService;
|
||||
this.traces = new ArrayList<StackTraceElement[]>(0);
|
||||
|
||||
this.nodeRef = nodeRef;
|
||||
this.typeQName = typeQName;
|
||||
this.qname = qname;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
if (hashCode == 0)
|
||||
{
|
||||
hashCode =
|
||||
0
|
||||
+ 1 * (nodeRef == null ? 0 : nodeRef.hashCode())
|
||||
- 17* (typeQName == null ? 0 : typeQName.hashCode())
|
||||
+ 17* (qname == null ? 0 : qname.hashCode());
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares based on the class of this instance and the incoming instance, before
|
||||
* comparing based on all the internal data. If derived classes store additional
|
||||
* data for their functionality, then they should override this.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
else if (this == obj)
|
||||
return true;
|
||||
else if (this.getClass() != obj.getClass())
|
||||
return false;
|
||||
// we can safely cast
|
||||
AbstractIntegrityEvent that = (AbstractIntegrityEvent) obj;
|
||||
return
|
||||
EqualsHelper.nullSafeEquals(this.nodeRef, that.nodeRef) &&
|
||||
EqualsHelper.nullSafeEquals(this.typeQName, that.typeQName) &&
|
||||
EqualsHelper.nullSafeEquals(this.qname, that.qname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(56);
|
||||
sb.append("IntegrityEvent")
|
||||
.append("[ name=").append(getClass().getName());
|
||||
if (nodeRef != null)
|
||||
sb.append(", nodeRef=").append(nodeRef);
|
||||
if (typeQName != null)
|
||||
sb.append(", typeQName=").append(typeQName);
|
||||
if (qname != null)
|
||||
sb.append(", qname=").append(qname);
|
||||
sb.append("]");
|
||||
// done
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node type if the node exists
|
||||
*
|
||||
* @param nodeRef
|
||||
* @return Returns the node's type or null if the node no longer exists
|
||||
*/
|
||||
protected QName getNodeType(NodeRef nodeRef)
|
||||
{
|
||||
try
|
||||
{
|
||||
return nodeService.getType(nodeRef);
|
||||
}
|
||||
catch (InvalidNodeRefException e)
|
||||
{
|
||||
// node has disappeared
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the traces (if present) that caused the creation of this event
|
||||
*/
|
||||
public List<StackTraceElement[]> getTraces()
|
||||
{
|
||||
return traces;
|
||||
}
|
||||
|
||||
public void addTrace(StackTraceElement[] trace)
|
||||
{
|
||||
traces.add(trace);
|
||||
}
|
||||
|
||||
protected NodeRef getNodeRef()
|
||||
{
|
||||
return nodeRef;
|
||||
}
|
||||
|
||||
protected QName getTypeQName()
|
||||
{
|
||||
return typeQName;
|
||||
}
|
||||
|
||||
protected QName getQName()
|
||||
{
|
||||
return qname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the association definition from the dictionary. If the source node type is
|
||||
* provided then the association particular to the subtype is attempted.
|
||||
*
|
||||
* @param eventResults results to add a violation message to
|
||||
* @param assocTypeQName the type of the association
|
||||
* @return Returns the association definition, or null if not found
|
||||
*/
|
||||
protected AssociationDefinition getAssocDef(List<IntegrityRecord> eventResults, QName assocTypeQName)
|
||||
{
|
||||
return dictionaryService.getAssociation(assocTypeQName);
|
||||
}
|
||||
|
||||
protected String getMultiplicityString(boolean mandatory, boolean allowMany)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(4);
|
||||
sb.append(mandatory ? "1" : "0");
|
||||
sb.append("..");
|
||||
sb.append(allowMany ? "*" : "1");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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 org.alfresco.service.cmr.dictionary.AssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.repository.AssociationRef;
|
||||
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.namespace.QName;
|
||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Event raised to check the source multiplicity for an association type
|
||||
* from the given node.
|
||||
* <p>
|
||||
* Checks are ignored is the target node doesn't exist.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class AssocSourceMultiplicityIntegrityEvent extends AbstractIntegrityEvent
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(AssocSourceMultiplicityIntegrityEvent.class);
|
||||
|
||||
/** true if the assoc type may not be valid, e.g. during association deletions */
|
||||
private boolean isDelete;
|
||||
|
||||
public AssocSourceMultiplicityIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef targetNodeRef,
|
||||
QName assocTypeQName,
|
||||
boolean isDelete)
|
||||
{
|
||||
super(nodeService, dictionaryService, targetNodeRef, assocTypeQName, null);
|
||||
this.isDelete = isDelete;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (!super.equals(obj))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// so far, so good
|
||||
AssocSourceMultiplicityIntegrityEvent that = (AssocSourceMultiplicityIntegrityEvent) obj;
|
||||
return this.isDelete == that.isDelete;
|
||||
}
|
||||
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults)
|
||||
{
|
||||
QName assocTypeQName = getTypeQName();
|
||||
NodeRef targetNodeRef = getNodeRef();
|
||||
// event is irrelevant if the node is gone
|
||||
QName targetNodeTypeQName = getNodeType(targetNodeRef);
|
||||
if (targetNodeTypeQName == null)
|
||||
{
|
||||
// target or source is missing
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Ignoring integrity check - node gone: \n" +
|
||||
" event: " + this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// get the association def
|
||||
AssociationDefinition assocDef = getAssocDef(eventResults, assocTypeQName);
|
||||
// the association definition must exist
|
||||
if (assocDef == null)
|
||||
{
|
||||
if (!isDelete) // strict about the type
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Association type does not exist: \n" +
|
||||
" Target Node Type: " + targetNodeTypeQName + "\n" +
|
||||
" Association Type: " + assocTypeQName);
|
||||
eventResults.add(result);
|
||||
return;
|
||||
}
|
||||
else // not strict about the type
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// perform required checks
|
||||
checkSourceMultiplicity(eventResults, assocDef, assocTypeQName, targetNodeRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the source multiplicity has not been violated for the
|
||||
* target of the association.
|
||||
*/
|
||||
protected void checkSourceMultiplicity(
|
||||
List<IntegrityRecord> eventResults,
|
||||
AssociationDefinition assocDef,
|
||||
QName assocTypeQName,
|
||||
NodeRef targetNodeRef)
|
||||
{
|
||||
// get the source multiplicity
|
||||
boolean mandatory = assocDef.isSourceMandatory();
|
||||
boolean allowMany = assocDef.isSourceMany();
|
||||
// do we need to check
|
||||
if (!mandatory && allowMany)
|
||||
{
|
||||
// it is not mandatory and it allows many on both sides of the assoc
|
||||
return;
|
||||
}
|
||||
int actualSize = 0;
|
||||
if (assocDef.isChild())
|
||||
{
|
||||
// check the parent assocs present
|
||||
List<ChildAssociationRef> parentAssocRefs = nodeService.getParentAssocs(
|
||||
targetNodeRef,
|
||||
assocTypeQName,
|
||||
RegexQNamePattern.MATCH_ALL);
|
||||
actualSize = parentAssocRefs.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
// check the source assocs present
|
||||
List<AssociationRef> sourceAssocRefs = nodeService.getSourceAssocs(targetNodeRef, assocTypeQName);
|
||||
actualSize = sourceAssocRefs.size();
|
||||
}
|
||||
if ((mandatory && actualSize == 0) || (!allowMany && actualSize > 1))
|
||||
{
|
||||
String parentOrSourceStr = (assocDef.isChild() ? "child" : "target");
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"The association " + parentOrSourceStr + " multiplicity has been violated: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Required " + parentOrSourceStr + " Multiplicity: " + getMultiplicityString(mandatory, allowMany) + "\n" +
|
||||
" Actual " + parentOrSourceStr + " Multiplicity: " + actualSize);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.AssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.ClassDefinition;
|
||||
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 to check the source type of an association
|
||||
* <p>
|
||||
* Checks are ignored if the source node has been deleted.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class AssocSourceTypeIntegrityEvent extends AbstractIntegrityEvent
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(AssocSourceTypeIntegrityEvent.class);
|
||||
|
||||
public AssocSourceTypeIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef sourceNodeRef,
|
||||
QName assocTypeQName)
|
||||
{
|
||||
super(nodeService, dictionaryService, sourceNodeRef, assocTypeQName, null);
|
||||
}
|
||||
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults)
|
||||
{
|
||||
QName assocTypeQName = getTypeQName();
|
||||
NodeRef sourceNodeRef = getNodeRef();
|
||||
// if the node is gone then the check is irrelevant
|
||||
QName sourceNodeTypeQName = getNodeType(sourceNodeRef);
|
||||
if (sourceNodeTypeQName == null)
|
||||
{
|
||||
// target or source is missing
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Ignoring integrity check - node gone: \n" +
|
||||
" event: " + this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// get the association def
|
||||
AssociationDefinition assocDef = getAssocDef(eventResults, assocTypeQName);
|
||||
// the association definition must exist
|
||||
if (assocDef == null)
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Association type does not exist: \n" +
|
||||
" Source Node Type: " + sourceNodeTypeQName + "\n" +
|
||||
" Association Type: " + assocTypeQName);
|
||||
eventResults.add(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// perform required checks
|
||||
checkSourceType(eventResults, assocDef, sourceNodeRef, sourceNodeTypeQName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the source node type is valid for the association.
|
||||
*/
|
||||
protected void checkSourceType(
|
||||
List<IntegrityRecord> eventResults,
|
||||
AssociationDefinition assocDef,
|
||||
NodeRef sourceNodeRef,
|
||||
QName sourceNodeTypeQName)
|
||||
{
|
||||
// check the association source type
|
||||
ClassDefinition sourceDef = assocDef.getSourceClass();
|
||||
if (sourceDef instanceof TypeDefinition)
|
||||
{
|
||||
// the node type must be a match
|
||||
if (!dictionaryService.isSubClass(sourceNodeTypeQName, sourceDef.getName()))
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"The association source type is incorrect: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Required Source Type: " + sourceDef.getName() + "\n" +
|
||||
" Actual Source Type: " + sourceNodeTypeQName);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
else if (sourceDef instanceof AspectDefinition)
|
||||
{
|
||||
// the source must have a relevant aspect
|
||||
Set<QName> sourceAspects = nodeService.getAspects(sourceNodeRef);
|
||||
boolean found = false;
|
||||
for (QName sourceAspectTypeQName : sourceAspects)
|
||||
{
|
||||
if (dictionaryService.isSubClass(sourceAspectTypeQName, sourceDef.getName()))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"The association source is missing the aspect required for this association: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Required Source Aspect: " + sourceDef.getName() + "\n" +
|
||||
" Actual Source Aspects: " + sourceAspects);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Unknown ClassDefinition subclass on the source definition: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Source Definition: " + sourceDef.getName());
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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 org.alfresco.service.cmr.dictionary.AssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.repository.AssociationRef;
|
||||
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.namespace.QName;
|
||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Event raised to check the target multiplicity for an association type
|
||||
* from the given node.
|
||||
* <p>
|
||||
* Checks are ignored is the target node doesn't exist.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class AssocTargetMultiplicityIntegrityEvent extends AbstractIntegrityEvent
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(AssocTargetMultiplicityIntegrityEvent.class);
|
||||
|
||||
/** true if the assoc type may not be valid, e.g. during association deletions */
|
||||
private boolean isDelete;
|
||||
|
||||
public AssocTargetMultiplicityIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef sourceNodeRef,
|
||||
QName assocTypeQName,
|
||||
boolean isDelete)
|
||||
{
|
||||
super(nodeService, dictionaryService, sourceNodeRef, assocTypeQName, null);
|
||||
this.isDelete = isDelete;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (!super.equals(obj))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// so far, so good
|
||||
AssocTargetMultiplicityIntegrityEvent that = (AssocTargetMultiplicityIntegrityEvent) obj;
|
||||
return this.isDelete == that.isDelete;
|
||||
}
|
||||
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults)
|
||||
{
|
||||
QName assocTypeQName = getTypeQName();
|
||||
NodeRef sourceNodeRef = getNodeRef();
|
||||
// event is irrelevant if the node is gone
|
||||
QName sourceNodeTypeQName = getNodeType(sourceNodeRef);
|
||||
if (sourceNodeTypeQName == null)
|
||||
{
|
||||
// target or target is missing
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Ignoring integrity check - node gone: \n" +
|
||||
" event: " + this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// get the association def
|
||||
AssociationDefinition assocDef = getAssocDef(eventResults, assocTypeQName);
|
||||
// the association definition must exist
|
||||
if (assocDef == null)
|
||||
{
|
||||
if (!isDelete) // strict about the type
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Association type does not exist: \n" +
|
||||
" Source Node Type: " + sourceNodeTypeQName + "\n" +
|
||||
" Association Type: " + assocTypeQName);
|
||||
eventResults.add(result);
|
||||
return;
|
||||
}
|
||||
else // not strict about the type
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// perform required checks
|
||||
checkTargetMultiplicity(eventResults, assocDef, assocTypeQName, sourceNodeRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the target multiplicity has not been violated for the
|
||||
* source of the association.
|
||||
*/
|
||||
protected void checkTargetMultiplicity(
|
||||
List<IntegrityRecord> eventResults,
|
||||
AssociationDefinition assocDef,
|
||||
QName assocTypeQName,
|
||||
NodeRef sourceNodeRef)
|
||||
{
|
||||
// get the source multiplicity
|
||||
boolean mandatory = assocDef.isTargetMandatory();
|
||||
boolean allowMany = assocDef.isTargetMany();
|
||||
// do we need to check
|
||||
if (!mandatory && allowMany)
|
||||
{
|
||||
// it is not mandatory and it allows many on both sides of the assoc
|
||||
return;
|
||||
}
|
||||
int actualSize = 0;
|
||||
if (assocDef.isChild())
|
||||
{
|
||||
// check the child assocs present
|
||||
List<ChildAssociationRef> childAssocRefs = nodeService.getChildAssocs(
|
||||
sourceNodeRef,
|
||||
assocTypeQName,
|
||||
RegexQNamePattern.MATCH_ALL);
|
||||
actualSize = childAssocRefs.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
// check the target assocs present
|
||||
List<AssociationRef> targetAssocRefs = nodeService.getTargetAssocs(sourceNodeRef, assocTypeQName);
|
||||
actualSize = targetAssocRefs.size();
|
||||
}
|
||||
if ((mandatory && actualSize == 0) || (!allowMany && actualSize > 1))
|
||||
{
|
||||
String childOrTargetStr = (assocDef.isChild() ? "child" : "target");
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"The association " + childOrTargetStr + " multiplicity has been violated: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Required " + childOrTargetStr + " Multiplicity: " + getMultiplicityString(mandatory, allowMany) + "\n" +
|
||||
" Actual " + childOrTargetStr + " Multiplicity: " + actualSize);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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 org.alfresco.service.cmr.dictionary.AssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Event to check the association target role name
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class AssocTargetRoleIntegrityEvent extends AbstractIntegrityEvent
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(AssocTargetRoleIntegrityEvent.class);
|
||||
|
||||
public AssocTargetRoleIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef sourceNodeRef,
|
||||
QName assocTypeQName,
|
||||
QName assocName)
|
||||
{
|
||||
super(nodeService, dictionaryService, sourceNodeRef, assocTypeQName, assocName);
|
||||
}
|
||||
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults)
|
||||
{
|
||||
NodeRef sourceNodeRef = getNodeRef();
|
||||
QName assocTypeQName = getTypeQName();
|
||||
QName assocQName = getQName();
|
||||
|
||||
// get the association def
|
||||
AssociationDefinition assocDef = getAssocDef(eventResults, assocTypeQName);
|
||||
// the association definition must exist
|
||||
if (assocDef == null)
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Association type does not exist: \n" +
|
||||
" Association Type: " + assocTypeQName);
|
||||
eventResults.add(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// check that we are dealing with child associations
|
||||
if (assocQName == null)
|
||||
{
|
||||
throw new IllegalArgumentException("The association qualified name must be supplied");
|
||||
}
|
||||
if (!assocDef.isChild())
|
||||
{
|
||||
throw new UnsupportedOperationException("This operation is only relevant to child associations");
|
||||
}
|
||||
ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef;
|
||||
|
||||
// perform required checks
|
||||
checkAssocQNameRegex(eventResults, childAssocDef, assocQName);
|
||||
checkAssocQNameDuplicate(eventResults, childAssocDef, sourceNodeRef, assocQName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the association name matches the constraints imposed by the model.
|
||||
*/
|
||||
protected void checkAssocQNameRegex(
|
||||
List<IntegrityRecord> eventResults,
|
||||
ChildAssociationDefinition assocDef,
|
||||
QName assocQName)
|
||||
{
|
||||
// check the association name
|
||||
QName assocRoleQName = assocDef.getTargetRoleName();
|
||||
if (assocRoleQName != null)
|
||||
{
|
||||
// the assoc defines a role name - check it
|
||||
RegexQNamePattern rolePattern = new RegexQNamePattern(assocRoleQName.toString());
|
||||
if (!rolePattern.isMatch(assocQName))
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"The association name does not match the allowed role names: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Allowed roles: " + rolePattern + "\n" +
|
||||
" Name assigned: " + assocRoleQName);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the association name matches the constraints imposed by the model.
|
||||
*/
|
||||
protected void checkAssocQNameDuplicate(
|
||||
List<IntegrityRecord> eventResults,
|
||||
ChildAssociationDefinition assocDef,
|
||||
NodeRef sourceNodeRef,
|
||||
QName assocQName)
|
||||
{
|
||||
if (assocDef.getDuplicateChildNamesAllowed())
|
||||
{
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
QName assocTypeQName = assocDef.getName();
|
||||
// see if there is another association with the same name
|
||||
try
|
||||
{
|
||||
List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(sourceNodeRef, assocTypeQName, assocQName);
|
||||
// duplicates not allowed
|
||||
if (childAssocs.size() > 1)
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Duplicate child associations are not allowed: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Name: " + assocQName);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
catch (InvalidNodeRefException e)
|
||||
{
|
||||
// node has gone
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.AssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.ClassDefinition;
|
||||
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 to check the target type of an association
|
||||
* <p>
|
||||
* Checks are ignored if the target node has been deleted.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class AssocTargetTypeIntegrityEvent extends AbstractIntegrityEvent
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(AssocTargetTypeIntegrityEvent.class);
|
||||
|
||||
public AssocTargetTypeIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef targetNodeRef,
|
||||
QName assocTypeQName)
|
||||
{
|
||||
super(nodeService, dictionaryService, targetNodeRef, assocTypeQName, null);
|
||||
}
|
||||
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults)
|
||||
{
|
||||
QName assocTypeQName = getTypeQName();
|
||||
NodeRef targetNodeRef = getNodeRef();
|
||||
// if the node is gone then the check is irrelevant
|
||||
QName targetNodeTypeQName = getNodeType(targetNodeRef);
|
||||
if (targetNodeTypeQName == null)
|
||||
{
|
||||
// target or source is missing
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Ignoring integrity check - node gone: \n" +
|
||||
" event: " + this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// get the association def
|
||||
AssociationDefinition assocDef = getAssocDef(eventResults, assocTypeQName);
|
||||
// the association definition must exist
|
||||
if (assocDef == null)
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Association type does not exist: \n" +
|
||||
" Target Node Type: " + targetNodeTypeQName + "\n" +
|
||||
" Association Type: " + assocTypeQName);
|
||||
eventResults.add(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// perform required checks
|
||||
checkTargetType(eventResults, assocDef, targetNodeRef, targetNodeTypeQName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the target node type is valid for the association.
|
||||
*/
|
||||
protected void checkTargetType(
|
||||
List<IntegrityRecord> eventResults,
|
||||
AssociationDefinition assocDef,
|
||||
NodeRef targetNodeRef,
|
||||
QName targetNodeTypeQName)
|
||||
{
|
||||
// check the association target type
|
||||
ClassDefinition targetDef = assocDef.getTargetClass();
|
||||
if (targetDef instanceof TypeDefinition)
|
||||
{
|
||||
// the node type must be a match
|
||||
if (!dictionaryService.isSubClass(targetNodeTypeQName, targetDef.getName()))
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"The association target type is incorrect: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Required Target Type: " + targetDef.getName() + "\n" +
|
||||
" Actual Target Type: " + targetNodeTypeQName);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
else if (targetDef instanceof AspectDefinition)
|
||||
{
|
||||
// the target must have a relevant aspect
|
||||
Set<QName> targetAspects = nodeService.getAspects(targetNodeRef);
|
||||
boolean found = false;
|
||||
for (QName targetAspectTypeQName : targetAspects)
|
||||
{
|
||||
if (dictionaryService.isSubClass(targetAspectTypeQName, targetDef.getName()))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"The association target is missing the aspect required for this association: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Required Target Aspect: " + targetDef.getName() + "\n" +
|
||||
" Actual Target Aspects: " + targetAspects);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Unknown ClassDefinition subclass on the target definition: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Source Definition: " + targetDef.getName());
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,643 @@
|
||||
/*
|
||||
* 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.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.node.NodeServicePolicies;
|
||||
import org.alfresco.repo.policy.JavaBehaviour;
|
||||
import org.alfresco.repo.policy.PolicyComponent;
|
||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
||||
import org.alfresco.service.cmr.dictionary.AspectDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.ClassDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryException;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.repository.AssociationRef;
|
||||
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.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link org.alfresco.repo.integrity.IntegrityService integrity service}
|
||||
* that uses the domain persistence mechanism to store and recall integrity events.
|
||||
* <p>
|
||||
* In order to fulfill the contract of the interface, this class registers to receive notifications
|
||||
* pertinent to changes in the node structure. These are then store away in the persistent
|
||||
* store until the request to
|
||||
* {@link org.alfresco.repo.integrity.IntegrityService#checkIntegrity(String) check integrity} is
|
||||
* made.
|
||||
* <p>
|
||||
* In order to ensure registration of these events, the {@link #init()} method must be called.
|
||||
* <p>
|
||||
* By default, this service is enabled, but can be disabled using {@link #setEnabled(boolean)}.<br>
|
||||
* Tracing of the event stacks is, for performance reasons, disabled by default but can be enabled
|
||||
* using {@link #setTraceOn(boolean)}.<br>
|
||||
* When enabled, the integrity check can either fail with a <tt>RuntimeException</tt> or not. In either
|
||||
* case, the integrity violations are logged as warnings or errors. This behaviour is controleed using
|
||||
* {@link #setFailOnViolation(boolean)} and is off by default. In other words, if not set, this service
|
||||
* will only log warnings about integrity violations.
|
||||
* <p>
|
||||
* Some integrity checks are not performed here as they are dealt with directly during the modification
|
||||
* operation in the {@link org.alfresco.service.cmr.repository.NodeService node service}.
|
||||
*
|
||||
* @see #setPolicyComponent(PolicyComponent)
|
||||
* @see #setDictionaryService(DictionaryService)
|
||||
* @see #setIntegrityDaoService(IntegrityDaoService)
|
||||
* @see #setMaxErrorsPerTransaction(int)
|
||||
* @see #setFlushSize(int)
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class IntegrityChecker
|
||||
implements NodeServicePolicies.OnCreateNodePolicy,
|
||||
NodeServicePolicies.OnUpdatePropertiesPolicy,
|
||||
NodeServicePolicies.OnDeleteNodePolicy,
|
||||
NodeServicePolicies.OnAddAspectPolicy,
|
||||
NodeServicePolicies.OnRemoveAspectPolicy,
|
||||
NodeServicePolicies.OnCreateChildAssociationPolicy,
|
||||
NodeServicePolicies.OnDeleteChildAssociationPolicy,
|
||||
NodeServicePolicies.OnCreateAssociationPolicy,
|
||||
NodeServicePolicies.OnDeleteAssociationPolicy
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(IntegrityChecker.class);
|
||||
|
||||
/** key against which the set of events is stored in the current transaction */
|
||||
private static final String KEY_EVENT_SET = "IntegrityChecker.EventSet";
|
||||
|
||||
private PolicyComponent policyComponent;
|
||||
private DictionaryService dictionaryService;
|
||||
private NodeService nodeService;
|
||||
private boolean enabled;
|
||||
private boolean failOnViolation;
|
||||
private int maxErrorsPerTransaction;
|
||||
private boolean traceOn;
|
||||
|
||||
/**
|
||||
*/
|
||||
public IntegrityChecker()
|
||||
{
|
||||
this.enabled = true;
|
||||
this.failOnViolation = false;
|
||||
this.maxErrorsPerTransaction = 10;
|
||||
this.traceOn = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param policyComponent the component to register behaviour with
|
||||
*/
|
||||
public void setPolicyComponent(PolicyComponent policyComponent)
|
||||
{
|
||||
this.policyComponent = policyComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param dictionaryService the dictionary against which to confirm model details
|
||||
*/
|
||||
public void setDictionaryService(DictionaryService dictionaryService)
|
||||
{
|
||||
this.dictionaryService = dictionaryService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nodeService the node service to use for browsing node structures
|
||||
*/
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param enabled set to false to disable integrity checking completely
|
||||
*/
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param traceOn set to <code>true</code> to enable stack traces recording
|
||||
* of events
|
||||
*/
|
||||
public void setTraceOn(boolean traceOn)
|
||||
{
|
||||
this.traceOn = traceOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param failOnViolation set to <code>true</code> to force failure by
|
||||
* <tt>RuntimeException</tt> when a violation occurs.
|
||||
*/
|
||||
public void setFailOnViolation(boolean failOnViolation)
|
||||
{
|
||||
this.failOnViolation = failOnViolation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param maxLogNumberPerTransaction upper limit on how many violations are
|
||||
* logged when multiple violations have been found.
|
||||
*/
|
||||
public void setMaxErrorsPerTransaction(int maxLogNumberPerTransaction)
|
||||
{
|
||||
this.maxErrorsPerTransaction = maxLogNumberPerTransaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the system-level policy behaviours
|
||||
*/
|
||||
public void init()
|
||||
{
|
||||
// check that required properties have been set
|
||||
if (dictionaryService == null)
|
||||
throw new AlfrescoRuntimeException("IntegrityChecker property not set: dictionaryService");
|
||||
if (nodeService == null)
|
||||
throw new AlfrescoRuntimeException("IntegrityChecker property not set: nodeService");
|
||||
if (policyComponent == null)
|
||||
throw new AlfrescoRuntimeException("IntegrityChecker property not set: policyComponent");
|
||||
|
||||
if (enabled) // only register behaviour if integrity checking is on
|
||||
{
|
||||
// register behaviour
|
||||
policyComponent.bindClassBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onCreateNode"));
|
||||
policyComponent.bindClassBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onUpdateProperties"));
|
||||
policyComponent.bindClassBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteNode"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onDeleteNode"));
|
||||
policyComponent.bindClassBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onAddAspect"));
|
||||
policyComponent.bindClassBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onRemoveAspect"));
|
||||
policyComponent.bindAssociationBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateChildAssociation"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onCreateChildAssociation"));
|
||||
policyComponent.bindAssociationBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteChildAssociation"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onDeleteChildAssociation"));
|
||||
policyComponent.bindAssociationBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateAssociation"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onCreateAssociation"));
|
||||
policyComponent.bindAssociationBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteAssociation"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onDeleteAssociation"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that this service is registered with the transaction and saves the event
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void save(IntegrityEvent event)
|
||||
{
|
||||
// optionally set trace
|
||||
if (traceOn)
|
||||
{
|
||||
// get a stack trace
|
||||
Throwable t = new Throwable();
|
||||
t.fillInStackTrace();
|
||||
StackTraceElement[] trace = t.getStackTrace();
|
||||
|
||||
event.addTrace(trace);
|
||||
// done
|
||||
}
|
||||
|
||||
// register this service
|
||||
AlfrescoTransactionSupport.bindIntegrityChecker(this);
|
||||
|
||||
// get the event list
|
||||
Map<IntegrityEvent, IntegrityEvent> events =
|
||||
(Map<IntegrityEvent, IntegrityEvent>) AlfrescoTransactionSupport.getResource(KEY_EVENT_SET);
|
||||
if (events == null)
|
||||
{
|
||||
events = new HashMap<IntegrityEvent, IntegrityEvent>(113, 0.75F);
|
||||
AlfrescoTransactionSupport.bindResource(KEY_EVENT_SET, events);
|
||||
}
|
||||
// check if the event is present
|
||||
IntegrityEvent existingEvent = events.get(event);
|
||||
if (existingEvent != null)
|
||||
{
|
||||
// the event (or its equivalent is already present - transfer the trace
|
||||
if (traceOn)
|
||||
{
|
||||
existingEvent.getTraces().addAll(event.getTraces());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the event doesn't already exist
|
||||
events.put(event, event);
|
||||
}
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("" + (existingEvent != null ? "Event already present in" : "Added event to") + " event set: \n" +
|
||||
" event: " + event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PropertiesIntegrityEvent
|
||||
*/
|
||||
public void onCreateNode(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
IntegrityEvent event = null;
|
||||
// check properties on child node
|
||||
event = new PropertiesIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getChildRef());
|
||||
save(event);
|
||||
|
||||
// check target role
|
||||
event = new AssocTargetRoleIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getParentRef(),
|
||||
childAssocRef.getTypeQName(),
|
||||
childAssocRef.getQName());
|
||||
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)
|
||||
{
|
||||
throw new DictionaryException("The node type is not recognized: " + childNodeTypeQName);
|
||||
}
|
||||
Map<QName, AssociationDefinition> childAssocDefs = nodeTypeDef.getAssociations();
|
||||
|
||||
// check the multiplicity of each association with the node acting as a source
|
||||
for (AssociationDefinition assocDef : childAssocDefs.values())
|
||||
{
|
||||
QName assocTypeQName = assocDef.getName();
|
||||
// check target multiplicity
|
||||
event = new AssocTargetMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childRef,
|
||||
assocTypeQName,
|
||||
false);
|
||||
save(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PropertiesIntegrityEvent
|
||||
*/
|
||||
public void onUpdateProperties(
|
||||
NodeRef nodeRef,
|
||||
Map<QName, Serializable> before,
|
||||
Map<QName, Serializable> after)
|
||||
{
|
||||
IntegrityEvent event = null;
|
||||
// check properties on node
|
||||
event = new PropertiesIntegrityEvent(nodeService, dictionaryService, nodeRef);
|
||||
save(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* No checking performed: The association changes will be handled
|
||||
*/
|
||||
public void onDeleteNode(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PropertiesIntegrityEvent
|
||||
*/
|
||||
public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName)
|
||||
{
|
||||
IntegrityEvent event = null;
|
||||
// check properties on node
|
||||
event = new PropertiesIntegrityEvent(nodeService, dictionaryService, nodeRef);
|
||||
save(event);
|
||||
|
||||
// check for associations defined on the aspect
|
||||
AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName);
|
||||
if (aspectDef == null)
|
||||
{
|
||||
throw new DictionaryException("The aspect type is not recognized: " + aspectTypeQName);
|
||||
}
|
||||
Map<QName, AssociationDefinition> assocDefs = aspectDef.getAssociations();
|
||||
|
||||
// check the multiplicity of each association with the node acting as a source
|
||||
for (AssociationDefinition assocDef : assocDefs.values())
|
||||
{
|
||||
QName assocTypeQName = assocDef.getName();
|
||||
// check target multiplicity
|
||||
event = new AssocTargetMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
nodeRef,
|
||||
assocTypeQName,
|
||||
false);
|
||||
save(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* No checking performed: The property changes will be handled
|
||||
*/
|
||||
public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName)
|
||||
{
|
||||
}
|
||||
|
||||
public void onCreateChildAssociation(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
IntegrityEvent event = null;
|
||||
// check source type
|
||||
event = new AssocSourceTypeIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getParentRef(),
|
||||
childAssocRef.getTypeQName());
|
||||
save(event);
|
||||
// check target type
|
||||
event = new AssocTargetTypeIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getChildRef(),
|
||||
childAssocRef.getTypeQName());
|
||||
save(event);
|
||||
// check source multiplicity
|
||||
event = new AssocSourceMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getChildRef(),
|
||||
childAssocRef.getTypeQName(),
|
||||
false);
|
||||
save(event);
|
||||
// check target multiplicity
|
||||
event = new AssocTargetMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getParentRef(),
|
||||
childAssocRef.getTypeQName(),
|
||||
false);
|
||||
save(event);
|
||||
// check target role
|
||||
event = new AssocTargetRoleIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getParentRef(),
|
||||
childAssocRef.getTypeQName(),
|
||||
childAssocRef.getQName());
|
||||
save(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CreateChildAssocIntegrityEvent
|
||||
*/
|
||||
public void onDeleteChildAssociation(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
IntegrityEvent event = null;
|
||||
// check source multiplicity
|
||||
event = new AssocSourceMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getChildRef(),
|
||||
childAssocRef.getTypeQName(),
|
||||
true);
|
||||
save(event);
|
||||
// check target multiplicity
|
||||
event = new AssocTargetMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getParentRef(),
|
||||
childAssocRef.getTypeQName(),
|
||||
true);
|
||||
save(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AbstractAssocIntegrityEvent
|
||||
*/
|
||||
public void onCreateAssociation(AssociationRef nodeAssocRef)
|
||||
{
|
||||
IntegrityEvent event = null;
|
||||
// check source type
|
||||
event = new AssocSourceTypeIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
nodeAssocRef.getSourceRef(),
|
||||
nodeAssocRef.getTypeQName());
|
||||
save(event);
|
||||
// check target type
|
||||
event = new AssocTargetTypeIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
nodeAssocRef.getTargetRef(),
|
||||
nodeAssocRef.getTypeQName());
|
||||
save(event);
|
||||
// check source multiplicity
|
||||
event = new AssocSourceMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
nodeAssocRef.getTargetRef(),
|
||||
nodeAssocRef.getTypeQName(),
|
||||
false);
|
||||
save(event);
|
||||
// check target multiplicity
|
||||
event = new AssocTargetMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
nodeAssocRef.getSourceRef(),
|
||||
nodeAssocRef.getTypeQName(),
|
||||
false);
|
||||
save(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AbstractAssocIntegrityEvent
|
||||
*/
|
||||
public void onDeleteAssociation(AssociationRef nodeAssocRef)
|
||||
{
|
||||
IntegrityEvent event = null;
|
||||
// check source multiplicity
|
||||
event = new AssocSourceMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
nodeAssocRef.getTargetRef(),
|
||||
nodeAssocRef.getTypeQName(),
|
||||
true);
|
||||
save(event);
|
||||
// check target multiplicity
|
||||
event = new AssocTargetMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
nodeAssocRef.getSourceRef(),
|
||||
nodeAssocRef.getTypeQName(),
|
||||
true);
|
||||
save(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs several types of checks, querying specifically for events that
|
||||
* will necessitate each type of test.
|
||||
* <p>
|
||||
* The interface contracts also requires that all events for the transaction
|
||||
* get cleaned up.
|
||||
*/
|
||||
public void checkIntegrity() throws IntegrityException
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// process events and check for failures
|
||||
List<IntegrityRecord> failures = processAllEvents();
|
||||
// clear out all events
|
||||
AlfrescoTransactionSupport.unbindResource(KEY_EVENT_SET);
|
||||
|
||||
// drop out quickly if there are no failures
|
||||
if (failures.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// handle errors according to instance flags
|
||||
// firstly, log all failures
|
||||
int failureCount = failures.size();
|
||||
StringBuilder sb = new StringBuilder(300 * failureCount);
|
||||
sb.append("Found ").append(failureCount).append(" integrity violations");
|
||||
if (maxErrorsPerTransaction < failureCount)
|
||||
{
|
||||
sb.append(" - first ").append(maxErrorsPerTransaction);
|
||||
}
|
||||
sb.append(":");
|
||||
int count = 0;
|
||||
for (IntegrityRecord failure : failures)
|
||||
{
|
||||
// break if we exceed the maximum number of log entries
|
||||
count++;
|
||||
if (count > maxErrorsPerTransaction)
|
||||
{
|
||||
break;
|
||||
}
|
||||
sb.append("\n").append(failure);
|
||||
}
|
||||
if (failOnViolation)
|
||||
{
|
||||
logger.error(sb.toString());
|
||||
throw new IntegrityException(failures);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.warn(sb.toString());
|
||||
// no exception
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through all the integrity events and checks integrity.
|
||||
* <p>
|
||||
* The events are stored in a set, so there are no duplicates. Since each
|
||||
* event performs a particular type of check, this ensures that we don't
|
||||
* duplicate checks.
|
||||
*
|
||||
* @return Returns a list of integrity violations, up to the
|
||||
* {@link #maxErrorsPerTransaction the maximum defined}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<IntegrityRecord> processAllEvents()
|
||||
{
|
||||
// the results
|
||||
ArrayList<IntegrityRecord> allIntegrityResults = new ArrayList<IntegrityRecord>(0); // generally unused
|
||||
|
||||
// get all the events for the transaction (or unit of work)
|
||||
// duplicates have been elimiated
|
||||
Map<IntegrityEvent, IntegrityEvent> events =
|
||||
(Map<IntegrityEvent, IntegrityEvent>) AlfrescoTransactionSupport.getResource(KEY_EVENT_SET);
|
||||
if (events == null)
|
||||
{
|
||||
// no events were registered - nothing of significance happened
|
||||
return allIntegrityResults;
|
||||
}
|
||||
|
||||
// failure results for the event
|
||||
List<IntegrityRecord> integrityRecords = new ArrayList<IntegrityRecord>(0);
|
||||
|
||||
// cycle through the events, performing checking integrity
|
||||
for (IntegrityEvent event : events.keySet())
|
||||
{
|
||||
try
|
||||
{
|
||||
event.checkIntegrity(integrityRecords);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
// log it as an error and move to next event
|
||||
IntegrityRecord exceptionRecord = new IntegrityRecord("" + e.getMessage());
|
||||
exceptionRecord.setTraces(Collections.singletonList(e.getStackTrace()));
|
||||
allIntegrityResults.add(exceptionRecord);
|
||||
// move on
|
||||
continue;
|
||||
}
|
||||
|
||||
// keep track of results needing trace added
|
||||
if (traceOn)
|
||||
{
|
||||
// record the current event trace if present
|
||||
for (IntegrityRecord integrityRecord : integrityRecords)
|
||||
{
|
||||
integrityRecord.setTraces(event.getTraces());
|
||||
}
|
||||
}
|
||||
|
||||
// copy all the event results to the final results
|
||||
allIntegrityResults.addAll(integrityRecords);
|
||||
// clear the event results
|
||||
integrityRecords.clear();
|
||||
|
||||
if (allIntegrityResults.size() >= maxErrorsPerTransaction)
|
||||
{
|
||||
// only so many errors wanted at a time
|
||||
break;
|
||||
}
|
||||
}
|
||||
// done
|
||||
return allIntegrityResults;
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Stores information for all events in the system
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public interface IntegrityEvent
|
||||
{
|
||||
/**
|
||||
* Checks integrity pertinent to the event
|
||||
*
|
||||
* @param eventResults the list of event results that can be added to
|
||||
*/
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults);
|
||||
|
||||
public List<StackTraceElement[]> getTraces();
|
||||
|
||||
public void addTrace(StackTraceElement[] trace);
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
|
||||
/**
|
||||
* @see org.alfresco.repo.node.integrity.IntegrityEvent
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class IntegrityEventTest extends TestCase
|
||||
{
|
||||
private static final String NAMESPACE = "http://test";
|
||||
|
||||
private NodeRef nodeRef;
|
||||
private QName typeQName;
|
||||
private QName qname;
|
||||
private IntegrityEvent event;
|
||||
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
nodeRef = new NodeRef("workspace://protocol/ID123");
|
||||
typeQName = QName.createQName(NAMESPACE, "SomeTypeQName");
|
||||
qname = QName.createQName(NAMESPACE, "qname");
|
||||
|
||||
event = new TestIntegrityEvent(null, null, nodeRef, typeQName, qname);
|
||||
}
|
||||
|
||||
public void testSetFunctionality() throws Exception
|
||||
{
|
||||
Set<IntegrityEvent> set = new HashSet<IntegrityEvent>(5);
|
||||
boolean added = set.add(event);
|
||||
assertTrue(added);
|
||||
added = set.add(new TestIntegrityEvent(null, null, nodeRef, typeQName, qname));
|
||||
assertFalse(added);
|
||||
}
|
||||
|
||||
private static class TestIntegrityEvent extends AbstractIntegrityEvent
|
||||
{
|
||||
public TestIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef nodeRef,
|
||||
QName typeQName,
|
||||
QName qname)
|
||||
{
|
||||
super(nodeService, dictionaryService, nodeRef, typeQName, qname);
|
||||
}
|
||||
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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.node.integrity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
|
||||
/**
|
||||
* Thrown when an integrity check fails
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class IntegrityException extends AlfrescoRuntimeException
|
||||
{
|
||||
private static final long serialVersionUID = -5036557255854195669L;
|
||||
|
||||
private List<IntegrityRecord> records;
|
||||
|
||||
public IntegrityException(List<IntegrityRecord> records)
|
||||
{
|
||||
super("Integrity failure");
|
||||
this.records = records;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns a list of all the integrity violations
|
||||
*/
|
||||
public List<IntegrityRecord> getRecords()
|
||||
{
|
||||
return records;
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Represents an integrity violation
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class IntegrityRecord
|
||||
{
|
||||
private String msg;
|
||||
private List<StackTraceElement[]> traces;
|
||||
|
||||
/**
|
||||
* @param msg the violation message
|
||||
*/
|
||||
public IntegrityRecord(String msg)
|
||||
{
|
||||
this.msg = msg;
|
||||
this.traces = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a stack trace to the list of traces associated with this failure
|
||||
*
|
||||
* @param trace a stack trace
|
||||
*/
|
||||
public void setTraces(List<StackTraceElement[]> traces)
|
||||
{
|
||||
this.traces = traces;
|
||||
}
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the integrity message and, if present, the stack trace
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(msg.length() * 2);
|
||||
if (traces == null)
|
||||
{
|
||||
sb.append(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(msg);
|
||||
for (StackTraceElement[] trace : traces)
|
||||
{
|
||||
sb.append("\n Trace of possible cause:");
|
||||
for (int i = 0; i < trace.length; i++)
|
||||
{
|
||||
sb.append("\n ").append(trace[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
385
source/java/org/alfresco/repo/node/integrity/IntegrityTest.java
Normal file
385
source/java/org/alfresco/repo/node/integrity/IntegrityTest.java
Normal file
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* 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.io.InputStream;
|
||||
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.dictionary.DictionaryDAO;
|
||||
import org.alfresco.repo.dictionary.M2Model;
|
||||
import org.alfresco.repo.node.BaseNodeServiceTest;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.ApplicationContextHelper;
|
||||
import org.alfresco.util.PropertyMap;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* Attempts to build faulty node structures in order to test integrity.
|
||||
* <p>
|
||||
* The entire application context is loaded as is, but the integrity fail-
|
||||
* mode is set to throw an exception.
|
||||
*
|
||||
* TODO: Role name restrictions must be checked
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class IntegrityTest extends TestCase
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(IntegrityTest.class);
|
||||
|
||||
public static final String NAMESPACE = "http://www.alfresco.org/test/IntegrityTest";
|
||||
public static final String TEST_PREFIX = "test";
|
||||
|
||||
public static final QName TEST_TYPE_WITHOUT_ANYTHING = QName.createQName(NAMESPACE, "typeWithoutAnything");
|
||||
public static final QName TEST_TYPE_WITH_ASPECT = QName.createQName(NAMESPACE, "typeWithAspect");
|
||||
public static final QName TEST_TYPE_WITH_PROPERTIES = QName.createQName(NAMESPACE, "typeWithProperties");
|
||||
public static final QName TEST_TYPE_WITH_ASSOCS = QName.createQName(NAMESPACE, "typeWithAssocs");
|
||||
public static final QName TEST_TYPE_WITH_CHILD_ASSOCS = QName.createQName(NAMESPACE, "typeWithChildAssocs");
|
||||
|
||||
public static final QName TEST_ASSOC_NODE_ZEROMANY_ZEROMANY = QName.createQName(NAMESPACE, "assoc-0to* - 0to*");
|
||||
public static final QName TEST_ASSOC_CHILD_ZEROMANY_ZEROMANY = QName.createQName(NAMESPACE, "child-0to* - 0to*");
|
||||
public static final QName TEST_ASSOC_NODE_ONE_ONE = QName.createQName(NAMESPACE, "assoc-1to1 - 1to1");
|
||||
public static final QName TEST_ASSOC_CHILD_ONE_ONE = QName.createQName(NAMESPACE, "child-1to1 - 1to1");
|
||||
public static final QName TEST_ASSOC_ASPECT_ONE_ONE = QName.createQName(NAMESPACE, "aspect-assoc-1to1 - 1to1");
|
||||
|
||||
public static final QName TEST_ASPECT_WITH_PROPERTIES = QName.createQName(NAMESPACE, "aspectWithProperties");
|
||||
public static final QName TEST_ASPECT_WITH_ASSOC = QName.createQName(NAMESPACE, "aspectWithAssoc");
|
||||
|
||||
public static final QName TEST_PROP_TEXT_A = QName.createQName(NAMESPACE, "prop-text-a");
|
||||
public static final QName TEST_PROP_TEXT_B = QName.createQName(NAMESPACE, "prop-text-b");
|
||||
public static final QName TEST_PROP_INT_A = QName.createQName(NAMESPACE, "prop-int-a");
|
||||
public static final QName TEST_PROP_INT_B = QName.createQName(NAMESPACE, "prop-int-b");
|
||||
|
||||
private static ApplicationContext ctx;
|
||||
static
|
||||
{
|
||||
ctx = ApplicationContextHelper.getApplicationContext();
|
||||
}
|
||||
|
||||
private IntegrityChecker integrityChecker;
|
||||
private ServiceRegistry serviceRegistry;
|
||||
private NodeService nodeService;
|
||||
private NodeRef rootNodeRef;
|
||||
private PropertyMap allProperties;
|
||||
private UserTransaction txn;
|
||||
private AuthenticationComponent authenticationComponent;
|
||||
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
DictionaryDAO dictionaryDao = (DictionaryDAO) ctx.getBean("dictionaryDAO");
|
||||
ClassLoader cl = BaseNodeServiceTest.class.getClassLoader();
|
||||
// load the test model
|
||||
InputStream modelStream = cl.getResourceAsStream("org/alfresco/repo/node/integrity/IntegrityTest_model.xml");
|
||||
assertNotNull(modelStream);
|
||||
M2Model model = M2Model.createModel(modelStream);
|
||||
dictionaryDao.putModel(model);
|
||||
|
||||
integrityChecker = (IntegrityChecker) ctx.getBean("integrityChecker");
|
||||
integrityChecker.setEnabled(true);
|
||||
integrityChecker.setFailOnViolation(true);
|
||||
integrityChecker.setTraceOn(true);
|
||||
integrityChecker.setMaxErrorsPerTransaction(100); // we want to count the correct number of errors
|
||||
|
||||
serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
|
||||
nodeService = serviceRegistry.getNodeService();
|
||||
this.authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent");
|
||||
|
||||
this.authenticationComponent.setSystemUserAsCurrentUser();
|
||||
|
||||
// begin a transaction
|
||||
TransactionService transactionService = serviceRegistry.getTransactionService();
|
||||
txn = transactionService.getUserTransaction();
|
||||
txn.begin();
|
||||
StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, getName());
|
||||
if (!nodeService.exists(storeRef))
|
||||
{
|
||||
nodeService.createStore(storeRef.getProtocol(), storeRef.getIdentifier());
|
||||
}
|
||||
rootNodeRef = nodeService.getRootNode(storeRef);
|
||||
|
||||
allProperties = new PropertyMap();
|
||||
allProperties.put(TEST_PROP_TEXT_A, "ABC");
|
||||
allProperties.put(TEST_PROP_TEXT_B, "DEF");
|
||||
allProperties.put(TEST_PROP_INT_A, "123");
|
||||
allProperties.put(TEST_PROP_INT_B, "456");
|
||||
}
|
||||
|
||||
public void tearDown() throws Exception
|
||||
{
|
||||
authenticationComponent.clearCurrentSecurityContext();
|
||||
txn.rollback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a node of the given type, and hanging off the root node
|
||||
*/
|
||||
private NodeRef createNode(String name, QName type, PropertyMap properties)
|
||||
{
|
||||
return nodeService.createNode(
|
||||
rootNodeRef,
|
||||
ContentModel.ASSOC_CHILDREN,
|
||||
QName.createQName(NAMESPACE, name),
|
||||
type,
|
||||
properties
|
||||
).getChildRef();
|
||||
}
|
||||
|
||||
private void checkIntegrityNoFailure() throws Exception
|
||||
{
|
||||
integrityChecker.checkIntegrity();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param failureMsg the fail message if an integrity exception doesn't occur
|
||||
* @param expectedCount the expected number of integrity failures, or -1 to ignore
|
||||
*/
|
||||
private void checkIntegrityExpectFailure(String failureMsg, int expectedCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
integrityChecker.checkIntegrity();
|
||||
fail(failureMsg);
|
||||
}
|
||||
catch (IntegrityException e)
|
||||
{
|
||||
if (expectedCount >= 0)
|
||||
{
|
||||
assertEquals("Incorrect number of integrity records generated", expectedCount, e.getRecords().size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testSetUp() throws Exception
|
||||
{
|
||||
assertNotNull("Static IntegrityChecker not created", integrityChecker);
|
||||
}
|
||||
|
||||
public void testCreateWithoutProperties() throws Exception
|
||||
{
|
||||
NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_PROPERTIES, null);
|
||||
checkIntegrityExpectFailure("Failed to detect missing properties", 1);
|
||||
}
|
||||
|
||||
public void testCreateWithProperties() throws Exception
|
||||
{
|
||||
NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_PROPERTIES, allProperties);
|
||||
checkIntegrityNoFailure();
|
||||
}
|
||||
|
||||
public void testMandatoryPropertiesRemoved() throws Exception
|
||||
{
|
||||
NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_PROPERTIES, allProperties);
|
||||
|
||||
// remove all the properties
|
||||
PropertyMap properties = new PropertyMap();
|
||||
nodeService.setProperties(nodeRef, properties);
|
||||
|
||||
checkIntegrityExpectFailure("Failed to detect missing removed properties", 1);
|
||||
}
|
||||
|
||||
public void testCreateWithoutPropertiesForAspect() throws Exception
|
||||
{
|
||||
NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_ASPECT, null);
|
||||
|
||||
checkIntegrityExpectFailure("Failed to detect missing properties for aspect", 1);
|
||||
}
|
||||
|
||||
public void testCreateWithPropertiesForAspect() throws Exception
|
||||
{
|
||||
NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_ASPECT, allProperties);
|
||||
checkIntegrityNoFailure();
|
||||
}
|
||||
|
||||
public void testCreateTargetOfAssocsWithMandatorySourcesPresent() throws Exception
|
||||
{
|
||||
// this is the target of 3 assoc types where the source cardinality is 1..1
|
||||
NodeRef targetAndChild = createNode("targetAndChild", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
|
||||
NodeRef source = createNode("source", TEST_TYPE_WITH_ASSOCS, null);
|
||||
nodeService.createAssociation(source, targetAndChild, TEST_ASSOC_NODE_ONE_ONE);
|
||||
|
||||
NodeRef parent = createNode("parent", TEST_TYPE_WITH_CHILD_ASSOCS, null);
|
||||
nodeService.addChild(parent, targetAndChild, TEST_ASSOC_CHILD_ONE_ONE, QName.createQName(NAMESPACE, "mandatoryChild"));
|
||||
|
||||
NodeRef aspected = createNode("aspectNode", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.addAspect(aspected, TEST_ASPECT_WITH_ASSOC, null);
|
||||
nodeService.createAssociation(aspected, targetAndChild, TEST_ASSOC_ASPECT_ONE_ONE);
|
||||
|
||||
checkIntegrityNoFailure();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: The dictionary support for the reverse lookup of mandatory associations will
|
||||
* allow this method to go in
|
||||
* <p>
|
||||
* <b>Does nothing</b>.
|
||||
*/
|
||||
public void testCreateTargetOfAssocsWithMandatorySourcesMissing() throws Exception
|
||||
{
|
||||
// // this is the target of 3 associations where the source cardinality is 1..1
|
||||
// NodeRef target = createNode("abc", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
//
|
||||
// checkIntegrityExpectFailure("Failed to detect missing mandatory assoc sources", 3);
|
||||
logger.error("Method commented out: testCreateTargetOfAssocsWithMandatorySourcesMissing");
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Reactivate once cascade delete notifications are back on
|
||||
* <p>
|
||||
* <b>Does nothing</b>.
|
||||
*/
|
||||
public void testRemoveSourcesOfMandatoryAssocs() throws Exception
|
||||
{
|
||||
// // this is the target of 3 assoc types where the source cardinality is 1..1
|
||||
// NodeRef targetAndChild = createNode("targetAndChild", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
//
|
||||
// NodeRef source = createNode("source", TEST_TYPE_WITH_ASSOCS, null);
|
||||
// nodeService.createAssociation(source, targetAndChild, TEST_ASSOC_NODE_ONE_ONE);
|
||||
//
|
||||
// NodeRef parent = createNode("parent", TEST_TYPE_WITH_CHILD_ASSOCS, null);
|
||||
// nodeService.addChild(parent, targetAndChild, TEST_ASSOC_CHILD_ONE_ONE, QName.createQName(NAMESPACE, "mandatoryChild"));
|
||||
//
|
||||
// NodeRef aspectSource = createNode("aspectSource", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
// nodeService.addAspect(aspectSource, TEST_ASPECT_WITH_ASSOC, null);
|
||||
// nodeService.createAssociation(aspectSource, targetAndChild, TEST_ASSOC_ASPECT_ONE_ONE);
|
||||
//
|
||||
// checkIntegrityNoFailure();
|
||||
//
|
||||
// // remove source nodes
|
||||
// nodeService.deleteNode(source);
|
||||
// nodeService.deleteNode(parent);
|
||||
// nodeService.deleteNode(aspectSource);
|
||||
//
|
||||
// checkIntegrityExpectFailure("Failed to detect removal of mandatory assoc sources", 3);
|
||||
logger.error("Method commented out: testRemoveSourcesOfMandatoryAssocs");
|
||||
}
|
||||
|
||||
public void testDuplicateTargetAssocs() throws Exception
|
||||
{
|
||||
NodeRef parent = createNode("source", TEST_TYPE_WITH_CHILD_ASSOCS, null);
|
||||
NodeRef child1 = createNode("child1", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
NodeRef child2 = createNode("child2", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
NodeRef child3 = createNode("child3", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
|
||||
// satisfy the one-to-one
|
||||
nodeService.addChild(parent, child3, TEST_ASSOC_CHILD_ONE_ONE, QName.createQName(NAMESPACE, "mandatoryChild"));
|
||||
|
||||
// create the non-duplicate assocs
|
||||
nodeService.addChild(parent, child1, TEST_ASSOC_CHILD_ZEROMANY_ZEROMANY, QName.createQName(NAMESPACE, "dupli_cate"));
|
||||
nodeService.addChild(parent, child2, TEST_ASSOC_CHILD_ZEROMANY_ZEROMANY, QName.createQName(NAMESPACE, "dupli_cate"));
|
||||
|
||||
checkIntegrityExpectFailure("Failed to detect duplicate association names", 1);
|
||||
}
|
||||
|
||||
public void testCreateSourceOfAssocsWithMandatoryTargetsPresent() throws Exception
|
||||
{
|
||||
NodeRef source = createNode("abc", TEST_TYPE_WITH_ASSOCS, null);
|
||||
NodeRef target = createNode("target", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.createAssociation(source, target, TEST_ASSOC_NODE_ONE_ONE);
|
||||
|
||||
NodeRef parent = createNode("parent", TEST_TYPE_WITH_CHILD_ASSOCS, null);
|
||||
NodeRef child = createNode("child", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.addChild(parent, child, TEST_ASSOC_CHILD_ONE_ONE, QName.createQName(NAMESPACE, "one-to-one"));
|
||||
|
||||
NodeRef aspectSource = createNode("aspectSource", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.addAspect(aspectSource, TEST_ASPECT_WITH_ASSOC, null);
|
||||
NodeRef aspectTarget = createNode("aspectTarget", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.createAssociation(aspectSource, aspectTarget, TEST_ASSOC_ASPECT_ONE_ONE);
|
||||
|
||||
checkIntegrityNoFailure();
|
||||
}
|
||||
|
||||
public void testCreateSourceOfAssocsWithMandatoryTargetsMissing() throws Exception
|
||||
{
|
||||
NodeRef source = createNode("abc", TEST_TYPE_WITH_ASSOCS, null);
|
||||
|
||||
NodeRef parent = createNode("parent", TEST_TYPE_WITH_CHILD_ASSOCS, null);
|
||||
|
||||
NodeRef aspectSource = createNode("aspectSource", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.addAspect(aspectSource, TEST_ASPECT_WITH_ASSOC, null);
|
||||
|
||||
checkIntegrityExpectFailure("Failed to detect missing assoc targets", 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Reactivate once cascade delete notifications are back on
|
||||
* <p>
|
||||
* <b>Does nothing</b>.
|
||||
*/
|
||||
public void testRemoveTargetsOfMandatoryAssocs() throws Exception
|
||||
{
|
||||
// NodeRef source = createNode("abc", TEST_TYPE_WITH_ASSOCS, null);
|
||||
// NodeRef target = createNode("target", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
// nodeService.createAssociation(source, target, TEST_ASSOC_NODE_ONE_ONE);
|
||||
//
|
||||
// NodeRef parent = createNode("parent", TEST_TYPE_WITH_CHILD_ASSOCS, null);
|
||||
// NodeRef child = createNode("child", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
// nodeService.addChild(parent, child, TEST_ASSOC_CHILD_ONE_ONE, QName.createQName(NAMESPACE, "one-to-one"));
|
||||
//
|
||||
// NodeRef aspectSource = createNode("aspectSource", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
// nodeService.addAspect(aspectSource, TEST_ASPECT_WITH_ASSOC, null);
|
||||
// NodeRef aspectTarget = createNode("aspectTarget", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
// nodeService.createAssociation(aspectSource, aspectTarget, TEST_ASSOC_ASPECT_ONE_ONE);
|
||||
//
|
||||
// checkIntegrityNoFailure();
|
||||
//
|
||||
// // remove target nodes
|
||||
// nodeService.deleteNode(target);
|
||||
// nodeService.deleteNode(child);
|
||||
// nodeService.deleteNode(aspectTarget);
|
||||
//
|
||||
// checkIntegrityExpectFailure("Failed to detect removal of mandatory assoc targets", 3);
|
||||
logger.error("Method commented out: testRemoveTargetsOfMandatoryAssocs");
|
||||
}
|
||||
|
||||
public void testExcessTargetsOfOneToOneAssocs() throws Exception
|
||||
{
|
||||
NodeRef source = createNode("abc", TEST_TYPE_WITH_ASSOCS, null);
|
||||
NodeRef target1 = createNode("target1", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
NodeRef target2 = createNode("target2", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.createAssociation(source, target1, TEST_ASSOC_NODE_ONE_ONE);
|
||||
nodeService.createAssociation(source, target2, TEST_ASSOC_NODE_ONE_ONE);
|
||||
|
||||
NodeRef parent = createNode("parent", TEST_TYPE_WITH_CHILD_ASSOCS, null);
|
||||
NodeRef child1 = createNode("child1", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
NodeRef child2 = createNode("child2", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.addChild(parent, child1, TEST_ASSOC_CHILD_ONE_ONE, QName.createQName(NAMESPACE, "one-to-one-first"));
|
||||
nodeService.addChild(parent, child2, TEST_ASSOC_CHILD_ONE_ONE, QName.createQName(NAMESPACE, "one-to-one-second"));
|
||||
|
||||
NodeRef aspectSource = createNode("aspectSource", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.addAspect(aspectSource, TEST_ASPECT_WITH_ASSOC, null);
|
||||
NodeRef aspectTarget1 = createNode("aspectTarget1", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
NodeRef aspectTarget2 = createNode("aspectTarget2", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.createAssociation(aspectSource, aspectTarget1, TEST_ASSOC_ASPECT_ONE_ONE);
|
||||
nodeService.createAssociation(aspectSource, aspectTarget2, TEST_ASSOC_ASPECT_ONE_ONE);
|
||||
|
||||
checkIntegrityExpectFailure("Failed to detect excess target cardinality for one-to-one assocs", 3);
|
||||
}
|
||||
}
|
@@ -0,0 +1,140 @@
|
||||
<model name="test:integrity" xmlns="http://www.alfresco.org/model/dictionary/1.0">
|
||||
|
||||
<description>Test Model for Integrity tests</description>
|
||||
<author>Alfresco</author>
|
||||
<published>2005-06-05</published>
|
||||
<version>0.1</version>
|
||||
|
||||
<imports>
|
||||
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
|
||||
<import uri="http://www.alfresco.org/model/system/1.0" prefix="sys"/>
|
||||
</imports>
|
||||
|
||||
<namespaces>
|
||||
<namespace uri="http://www.alfresco.org/test/IntegrityTest" prefix="test"/>
|
||||
</namespaces>
|
||||
|
||||
<types>
|
||||
<!-- Type without anyting -->
|
||||
<type name="test:typeWithoutAnything">
|
||||
<title>Type Without Anything</title>
|
||||
<parent>sys:base</parent>
|
||||
</type>
|
||||
<!-- Type with mandatory properties -->
|
||||
<type name="test:typeWithProperties">
|
||||
<title>Type With Properties</title>
|
||||
<parent>sys:base</parent>
|
||||
<properties>
|
||||
<property name="test:prop-text-a">
|
||||
<type>d:text</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:prop-text-b">
|
||||
<type>d:text</type>
|
||||
</property>
|
||||
</properties>
|
||||
</type>
|
||||
<!-- Type with mandatory aspect -->
|
||||
<type name="test:typeWithAspect">
|
||||
<title>Type With Aspect</title>
|
||||
<parent>sys:base</parent>
|
||||
<mandatory-aspects>
|
||||
<aspect>test:aspectWithProperties</aspect>
|
||||
</mandatory-aspects>
|
||||
</type>
|
||||
<!-- Type with assocs -->
|
||||
<type name="test:typeWithAssocs">
|
||||
<title>Type With Assocs</title>
|
||||
<parent>sys:base</parent>
|
||||
<associations>
|
||||
<association name="test:assoc-0to* - 0to*">
|
||||
<source>
|
||||
<mandatory>false</mandatory>
|
||||
<many>true</many>
|
||||
</source>
|
||||
<target>
|
||||
<class>test:typeWithoutAnything</class>
|
||||
<mandatory>false</mandatory>
|
||||
<many>true</many>
|
||||
</target>
|
||||
</association>
|
||||
<association name="test:assoc-1to1 - 1to1">
|
||||
<source>
|
||||
<mandatory>true</mandatory>
|
||||
<many>false</many>
|
||||
</source>
|
||||
<target>
|
||||
<class>test:typeWithoutAnything</class>
|
||||
<mandatory>true</mandatory>
|
||||
<many>false</many>
|
||||
</target>
|
||||
</association>
|
||||
</associations>
|
||||
</type>
|
||||
<!-- Type with child assocs -->
|
||||
<type name="test:typeWithChildAssocs">
|
||||
<title>Type With Child Assocs</title>
|
||||
<parent>sys:base</parent>
|
||||
<associations>
|
||||
<child-association name="test:child-0to* - 0to*">
|
||||
<source>
|
||||
<mandatory>false</mandatory>
|
||||
<many>true</many>
|
||||
</source>
|
||||
<target>
|
||||
<class>test:typeWithoutAnything</class>
|
||||
<mandatory>false</mandatory>
|
||||
<many>true</many>
|
||||
</target>
|
||||
<duplicate>false</duplicate>
|
||||
</child-association>
|
||||
<child-association name="test:child-1to1 - 1to1">
|
||||
<source>
|
||||
<mandatory>true</mandatory>
|
||||
<many>false</many>
|
||||
</source>
|
||||
<target>
|
||||
<class>test:typeWithoutAnything</class>
|
||||
<mandatory>true</mandatory>
|
||||
<many>false</many>
|
||||
</target>
|
||||
<duplicate>false</duplicate>
|
||||
</child-association>
|
||||
</associations>
|
||||
</type>
|
||||
</types>
|
||||
|
||||
<aspects>
|
||||
<!-- aspect with properties -->
|
||||
<aspect name="test:aspectWithProperties">
|
||||
<title>Aspect with Properties</title>
|
||||
<properties>
|
||||
<property name="test:prop-int-a">
|
||||
<type>d:int</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:prop-int-b">
|
||||
<type>d:int</type>
|
||||
</property>
|
||||
</properties>
|
||||
</aspect>
|
||||
<!-- aspect with associations -->
|
||||
<aspect name="test:aspectWithAssoc">
|
||||
<title>Aspect with associations</title>
|
||||
<associations>
|
||||
<association name="test:aspect-assoc-1to1 - 1to1">
|
||||
<source>
|
||||
<mandatory>true</mandatory>
|
||||
<many>false</many>
|
||||
</source>
|
||||
<target>
|
||||
<class>test:typeWithoutAnything</class>
|
||||
<mandatory>true</mandatory>
|
||||
<many>false</many>
|
||||
</target>
|
||||
</association>
|
||||
</associations>
|
||||
</aspect>
|
||||
</aspects>
|
||||
|
||||
</model>
|
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.service.cmr.dictionary.AspectDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.TypeDefinition;
|
||||
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
|
||||
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
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class PropertiesIntegrityEvent extends AbstractIntegrityEvent
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(PropertiesIntegrityEvent.class);
|
||||
|
||||
protected PropertiesIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef nodeRef)
|
||||
{
|
||||
super(nodeService, dictionaryService, nodeRef, null, null);
|
||||
}
|
||||
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults)
|
||||
{
|
||||
try
|
||||
{
|
||||
checkAllProperties(getNodeRef(), eventResults);
|
||||
}
|
||||
catch (InvalidNodeRefException e)
|
||||
{
|
||||
// node has gone
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Event ignored - node gone: " + this);
|
||||
}
|
||||
eventResults.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the properties for the type and aspects of the given node.
|
||||
*
|
||||
* @param nodeRef
|
||||
* @param eventResults
|
||||
*/
|
||||
private void checkAllProperties(NodeRef nodeRef, List<IntegrityRecord> eventResults)
|
||||
{
|
||||
// get all properties for the node
|
||||
Map<QName, Serializable> nodeProperties = nodeService.getProperties(nodeRef);
|
||||
|
||||
// get the node type
|
||||
QName nodeTypeQName = nodeService.getType(nodeRef);
|
||||
// get property definitions for the node type
|
||||
TypeDefinition typeDef = dictionaryService.getType(nodeTypeQName);
|
||||
Collection<PropertyDefinition> propertyDefs = typeDef.getProperties().values();
|
||||
// check them
|
||||
checkAllProperties(nodeRef, nodeTypeQName, propertyDefs, nodeProperties, eventResults);
|
||||
|
||||
// get the node aspects
|
||||
Set<QName> aspectTypeQNames = nodeService.getAspects(nodeRef);
|
||||
for (QName aspectTypeQName : aspectTypeQNames)
|
||||
{
|
||||
// get property definitions for the aspect
|
||||
AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName);
|
||||
propertyDefs = aspectDef.getProperties().values();
|
||||
// check them
|
||||
checkAllProperties(nodeRef, aspectTypeQName, propertyDefs, nodeProperties, eventResults);
|
||||
}
|
||||
// done
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the specific map of properties against the required property definitions
|
||||
*
|
||||
* @param nodeRef the node to which this applies
|
||||
* @param typeQName the qualified name of the aspect or type to which the properties belong
|
||||
* @param propertyDefs the definitions to check against - may be null or empty
|
||||
* @param nodeProperties the properties to check
|
||||
*/
|
||||
private void checkAllProperties(
|
||||
NodeRef nodeRef,
|
||||
QName typeQName,
|
||||
Collection<PropertyDefinition> propertyDefs,
|
||||
Map<QName, Serializable> nodeProperties,
|
||||
Collection<IntegrityRecord> eventResults)
|
||||
{
|
||||
// check for null or empty definitions
|
||||
if (propertyDefs == null || propertyDefs.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (PropertyDefinition propertyDef : propertyDefs)
|
||||
{
|
||||
QName propertyQName = propertyDef.getName();
|
||||
Serializable propertyValue = nodeProperties.get(propertyQName);
|
||||
// check that mandatory properties are set
|
||||
if (propertyDef.isMandatory() && !nodeProperties.containsKey(propertyQName))
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Mandatory property not set: \n" +
|
||||
" Node: " + nodeRef + "\n" +
|
||||
" Type: " + typeQName + "\n" +
|
||||
" Property: " + propertyQName);
|
||||
eventResults.add(result);
|
||||
// next one
|
||||
continue;
|
||||
}
|
||||
// TODO: Incorporate value constraint checks - JIRA AR166
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user