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