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:
Derek Hulley
2005-12-08 07:13:07 +00:00
commit e1e6508fec
1095 changed files with 230566 additions and 0 deletions

View 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);
}
}
}

File diff suppressed because it is too large Load Diff

View 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>

View File

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

View 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);
}
}

View File

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

View 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
}
}

File diff suppressed because it is too large Load Diff

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

View 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);
}

View File

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

View File

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

View File

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

View 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();
}

View File

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

View 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);
}
}

View 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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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);
}
}

View File

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

View File

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