mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
Moving to root below branch label
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2005 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.node.integrity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.util.EqualsHelper;
|
||||
|
||||
/**
|
||||
* Base class for integrity events. It provides basic support for checking
|
||||
* model integrity.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public abstract class AbstractIntegrityEvent implements IntegrityEvent
|
||||
{
|
||||
protected final NodeService nodeService;
|
||||
protected final DictionaryService dictionaryService;
|
||||
|
||||
/** the potential problem traces */
|
||||
private List<StackTraceElement[]> traces;
|
||||
/** support for derived classes */
|
||||
private final NodeRef nodeRef;
|
||||
/** support for derived classes */
|
||||
private final QName typeQName;
|
||||
/** support for derived classes */
|
||||
private final QName qname;
|
||||
|
||||
/** cached hashcode as the members are all final */
|
||||
private int hashCode = 0;
|
||||
|
||||
/**
|
||||
* Constructor with helper values for storage
|
||||
*/
|
||||
protected AbstractIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef nodeRef,
|
||||
QName typeQName,
|
||||
QName qname)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
this.dictionaryService = dictionaryService;
|
||||
this.traces = new ArrayList<StackTraceElement[]>(0);
|
||||
|
||||
this.nodeRef = nodeRef;
|
||||
this.typeQName = typeQName;
|
||||
this.qname = qname;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
if (hashCode == 0)
|
||||
{
|
||||
hashCode =
|
||||
0
|
||||
+ 1 * (nodeRef == null ? 0 : nodeRef.hashCode())
|
||||
- 17* (typeQName == null ? 0 : typeQName.hashCode())
|
||||
+ 17* (qname == null ? 0 : qname.hashCode());
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares based on the class of this instance and the incoming instance, before
|
||||
* comparing based on all the internal data. If derived classes store additional
|
||||
* data for their functionality, then they should override this.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
else if (this == obj)
|
||||
return true;
|
||||
else if (this.getClass() != obj.getClass())
|
||||
return false;
|
||||
// we can safely cast
|
||||
AbstractIntegrityEvent that = (AbstractIntegrityEvent) obj;
|
||||
return
|
||||
EqualsHelper.nullSafeEquals(this.nodeRef, that.nodeRef) &&
|
||||
EqualsHelper.nullSafeEquals(this.typeQName, that.typeQName) &&
|
||||
EqualsHelper.nullSafeEquals(this.qname, that.qname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(56);
|
||||
sb.append("IntegrityEvent")
|
||||
.append("[ name=").append(getClass().getName());
|
||||
if (nodeRef != null)
|
||||
sb.append(", nodeRef=").append(nodeRef);
|
||||
if (typeQName != null)
|
||||
sb.append(", typeQName=").append(typeQName);
|
||||
if (qname != null)
|
||||
sb.append(", qname=").append(qname);
|
||||
sb.append("]");
|
||||
// done
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node type if the node exists
|
||||
*
|
||||
* @param nodeRef
|
||||
* @return Returns the node's type or null if the node no longer exists
|
||||
*/
|
||||
protected QName getNodeType(NodeRef nodeRef)
|
||||
{
|
||||
try
|
||||
{
|
||||
return nodeService.getType(nodeRef);
|
||||
}
|
||||
catch (InvalidNodeRefException e)
|
||||
{
|
||||
// node has disappeared
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the traces (if present) that caused the creation of this event
|
||||
*/
|
||||
public List<StackTraceElement[]> getTraces()
|
||||
{
|
||||
return traces;
|
||||
}
|
||||
|
||||
public void addTrace(StackTraceElement[] trace)
|
||||
{
|
||||
traces.add(trace);
|
||||
}
|
||||
|
||||
protected NodeRef getNodeRef()
|
||||
{
|
||||
return nodeRef;
|
||||
}
|
||||
|
||||
protected QName getTypeQName()
|
||||
{
|
||||
return typeQName;
|
||||
}
|
||||
|
||||
protected QName getQName()
|
||||
{
|
||||
return qname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the association definition from the dictionary. If the source node type is
|
||||
* provided then the association particular to the subtype is attempted.
|
||||
*
|
||||
* @param eventResults results to add a violation message to
|
||||
* @param assocTypeQName the type of the association
|
||||
* @return Returns the association definition, or null if not found
|
||||
*/
|
||||
protected AssociationDefinition getAssocDef(List<IntegrityRecord> eventResults, QName assocTypeQName)
|
||||
{
|
||||
return dictionaryService.getAssociation(assocTypeQName);
|
||||
}
|
||||
|
||||
protected String getMultiplicityString(boolean mandatory, boolean allowMany)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(4);
|
||||
sb.append(mandatory ? "1" : "0");
|
||||
sb.append("..");
|
||||
sb.append(allowMany ? "*" : "1");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.node.integrity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.repository.AssociationRef;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Event raised to check the source multiplicity for an association type
|
||||
* from the given node.
|
||||
* <p>
|
||||
* Checks are ignored is the target node doesn't exist.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class AssocSourceMultiplicityIntegrityEvent extends AbstractIntegrityEvent
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(AssocSourceMultiplicityIntegrityEvent.class);
|
||||
|
||||
/** true if the assoc type may not be valid, e.g. during association deletions */
|
||||
private boolean isDelete;
|
||||
|
||||
public AssocSourceMultiplicityIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef targetNodeRef,
|
||||
QName assocTypeQName,
|
||||
boolean isDelete)
|
||||
{
|
||||
super(nodeService, dictionaryService, targetNodeRef, assocTypeQName, null);
|
||||
this.isDelete = isDelete;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (!super.equals(obj))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// so far, so good
|
||||
AssocSourceMultiplicityIntegrityEvent that = (AssocSourceMultiplicityIntegrityEvent) obj;
|
||||
return this.isDelete == that.isDelete;
|
||||
}
|
||||
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults)
|
||||
{
|
||||
QName assocTypeQName = getTypeQName();
|
||||
NodeRef targetNodeRef = getNodeRef();
|
||||
// event is irrelevant if the node is gone
|
||||
QName targetNodeTypeQName = getNodeType(targetNodeRef);
|
||||
if (targetNodeTypeQName == null)
|
||||
{
|
||||
// target or source is missing
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Ignoring integrity check - node gone: \n" +
|
||||
" event: " + this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// get the association def
|
||||
AssociationDefinition assocDef = getAssocDef(eventResults, assocTypeQName);
|
||||
// the association definition must exist
|
||||
if (assocDef == null)
|
||||
{
|
||||
if (!isDelete) // strict about the type
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Association type does not exist: \n" +
|
||||
" Target Node Type: " + targetNodeTypeQName + "\n" +
|
||||
" Association Type: " + assocTypeQName);
|
||||
eventResults.add(result);
|
||||
return;
|
||||
}
|
||||
else // not strict about the type
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// perform required checks
|
||||
checkSourceMultiplicity(eventResults, assocDef, assocTypeQName, targetNodeRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the source multiplicity has not been violated for the
|
||||
* target of the association.
|
||||
*/
|
||||
protected void checkSourceMultiplicity(
|
||||
List<IntegrityRecord> eventResults,
|
||||
AssociationDefinition assocDef,
|
||||
QName assocTypeQName,
|
||||
NodeRef targetNodeRef)
|
||||
{
|
||||
// get the source multiplicity
|
||||
boolean mandatory = assocDef.isSourceMandatory();
|
||||
boolean allowMany = assocDef.isSourceMany();
|
||||
// do we need to check
|
||||
if (!mandatory && allowMany)
|
||||
{
|
||||
// it is not mandatory and it allows many on both sides of the assoc
|
||||
return;
|
||||
}
|
||||
int actualSize = 0;
|
||||
if (assocDef.isChild())
|
||||
{
|
||||
// check the parent assocs present
|
||||
List<ChildAssociationRef> parentAssocRefs = nodeService.getParentAssocs(
|
||||
targetNodeRef,
|
||||
assocTypeQName,
|
||||
RegexQNamePattern.MATCH_ALL);
|
||||
actualSize = parentAssocRefs.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
// check the source assocs present
|
||||
List<AssociationRef> sourceAssocRefs = nodeService.getSourceAssocs(targetNodeRef, assocTypeQName);
|
||||
actualSize = sourceAssocRefs.size();
|
||||
}
|
||||
if ((mandatory && actualSize == 0) || (!allowMany && actualSize > 1))
|
||||
{
|
||||
String parentOrSourceStr = (assocDef.isChild() ? "child" : "target");
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"The association " + parentOrSourceStr + " multiplicity has been violated: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Required " + parentOrSourceStr + " Multiplicity: " + getMultiplicityString(mandatory, allowMany) + "\n" +
|
||||
" Actual " + parentOrSourceStr + " Multiplicity: " + actualSize);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.node.integrity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.service.cmr.dictionary.AspectDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.ClassDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.dictionary.TypeDefinition;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Event to check the source type of an association
|
||||
* <p>
|
||||
* Checks are ignored if the source node has been deleted.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class AssocSourceTypeIntegrityEvent extends AbstractIntegrityEvent
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(AssocSourceTypeIntegrityEvent.class);
|
||||
|
||||
public AssocSourceTypeIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef sourceNodeRef,
|
||||
QName assocTypeQName)
|
||||
{
|
||||
super(nodeService, dictionaryService, sourceNodeRef, assocTypeQName, null);
|
||||
}
|
||||
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults)
|
||||
{
|
||||
QName assocTypeQName = getTypeQName();
|
||||
NodeRef sourceNodeRef = getNodeRef();
|
||||
// if the node is gone then the check is irrelevant
|
||||
QName sourceNodeTypeQName = getNodeType(sourceNodeRef);
|
||||
if (sourceNodeTypeQName == null)
|
||||
{
|
||||
// target or source is missing
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Ignoring integrity check - node gone: \n" +
|
||||
" event: " + this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// get the association def
|
||||
AssociationDefinition assocDef = getAssocDef(eventResults, assocTypeQName);
|
||||
// the association definition must exist
|
||||
if (assocDef == null)
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Association type does not exist: \n" +
|
||||
" Source Node Type: " + sourceNodeTypeQName + "\n" +
|
||||
" Association Type: " + assocTypeQName);
|
||||
eventResults.add(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// perform required checks
|
||||
checkSourceType(eventResults, assocDef, sourceNodeRef, sourceNodeTypeQName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the source node type is valid for the association.
|
||||
*/
|
||||
protected void checkSourceType(
|
||||
List<IntegrityRecord> eventResults,
|
||||
AssociationDefinition assocDef,
|
||||
NodeRef sourceNodeRef,
|
||||
QName sourceNodeTypeQName)
|
||||
{
|
||||
// check the association source type
|
||||
ClassDefinition sourceDef = assocDef.getSourceClass();
|
||||
if (sourceDef instanceof TypeDefinition)
|
||||
{
|
||||
// the node type must be a match
|
||||
if (!dictionaryService.isSubClass(sourceNodeTypeQName, sourceDef.getName()))
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"The association source type is incorrect: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Required Source Type: " + sourceDef.getName() + "\n" +
|
||||
" Actual Source Type: " + sourceNodeTypeQName);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
else if (sourceDef instanceof AspectDefinition)
|
||||
{
|
||||
// the source must have a relevant aspect
|
||||
Set<QName> sourceAspects = nodeService.getAspects(sourceNodeRef);
|
||||
boolean found = false;
|
||||
for (QName sourceAspectTypeQName : sourceAspects)
|
||||
{
|
||||
if (dictionaryService.isSubClass(sourceAspectTypeQName, sourceDef.getName()))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"The association source is missing the aspect required for this association: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Required Source Aspect: " + sourceDef.getName() + "\n" +
|
||||
" Actual Source Aspects: " + sourceAspects);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Unknown ClassDefinition subclass on the source definition: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Source Definition: " + sourceDef.getName());
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.node.integrity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.repository.AssociationRef;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Event raised to check the target multiplicity for an association type
|
||||
* from the given node.
|
||||
* <p>
|
||||
* Checks are ignored is the target node doesn't exist.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class AssocTargetMultiplicityIntegrityEvent extends AbstractIntegrityEvent
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(AssocTargetMultiplicityIntegrityEvent.class);
|
||||
|
||||
/** true if the assoc type may not be valid, e.g. during association deletions */
|
||||
private boolean isDelete;
|
||||
|
||||
public AssocTargetMultiplicityIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef sourceNodeRef,
|
||||
QName assocTypeQName,
|
||||
boolean isDelete)
|
||||
{
|
||||
super(nodeService, dictionaryService, sourceNodeRef, assocTypeQName, null);
|
||||
this.isDelete = isDelete;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (!super.equals(obj))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// so far, so good
|
||||
AssocTargetMultiplicityIntegrityEvent that = (AssocTargetMultiplicityIntegrityEvent) obj;
|
||||
return this.isDelete == that.isDelete;
|
||||
}
|
||||
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults)
|
||||
{
|
||||
QName assocTypeQName = getTypeQName();
|
||||
NodeRef sourceNodeRef = getNodeRef();
|
||||
// event is irrelevant if the node is gone
|
||||
QName sourceNodeTypeQName = getNodeType(sourceNodeRef);
|
||||
if (sourceNodeTypeQName == null)
|
||||
{
|
||||
// target or target is missing
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Ignoring integrity check - node gone: \n" +
|
||||
" event: " + this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// get the association def
|
||||
AssociationDefinition assocDef = getAssocDef(eventResults, assocTypeQName);
|
||||
// the association definition must exist
|
||||
if (assocDef == null)
|
||||
{
|
||||
if (!isDelete) // strict about the type
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Association type does not exist: \n" +
|
||||
" Source Node Type: " + sourceNodeTypeQName + "\n" +
|
||||
" Association Type: " + assocTypeQName);
|
||||
eventResults.add(result);
|
||||
return;
|
||||
}
|
||||
else // not strict about the type
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// perform required checks
|
||||
checkTargetMultiplicity(eventResults, assocDef, assocTypeQName, sourceNodeRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the target multiplicity has not been violated for the
|
||||
* source of the association.
|
||||
*/
|
||||
protected void checkTargetMultiplicity(
|
||||
List<IntegrityRecord> eventResults,
|
||||
AssociationDefinition assocDef,
|
||||
QName assocTypeQName,
|
||||
NodeRef sourceNodeRef)
|
||||
{
|
||||
// get the source multiplicity
|
||||
boolean mandatory = assocDef.isTargetMandatory();
|
||||
boolean allowMany = assocDef.isTargetMany();
|
||||
// do we need to check
|
||||
if (!mandatory && allowMany)
|
||||
{
|
||||
// it is not mandatory and it allows many on both sides of the assoc
|
||||
return;
|
||||
}
|
||||
int actualSize = 0;
|
||||
if (assocDef.isChild())
|
||||
{
|
||||
// check the child assocs present
|
||||
List<ChildAssociationRef> childAssocRefs = nodeService.getChildAssocs(
|
||||
sourceNodeRef,
|
||||
assocTypeQName,
|
||||
RegexQNamePattern.MATCH_ALL);
|
||||
actualSize = childAssocRefs.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
// check the target assocs present
|
||||
List<AssociationRef> targetAssocRefs = nodeService.getTargetAssocs(sourceNodeRef, assocTypeQName);
|
||||
actualSize = targetAssocRefs.size();
|
||||
}
|
||||
if ((mandatory && actualSize == 0) || (!allowMany && actualSize > 1))
|
||||
{
|
||||
String childOrTargetStr = (assocDef.isChild() ? "child" : "target");
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"The association " + childOrTargetStr + " multiplicity has been violated: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Required " + childOrTargetStr + " Multiplicity: " + getMultiplicityString(mandatory, allowMany) + "\n" +
|
||||
" Actual " + childOrTargetStr + " Multiplicity: " + actualSize);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.node.integrity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Event to check the association target role name
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class AssocTargetRoleIntegrityEvent extends AbstractIntegrityEvent
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(AssocTargetRoleIntegrityEvent.class);
|
||||
|
||||
public AssocTargetRoleIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef sourceNodeRef,
|
||||
QName assocTypeQName,
|
||||
QName assocName)
|
||||
{
|
||||
super(nodeService, dictionaryService, sourceNodeRef, assocTypeQName, assocName);
|
||||
}
|
||||
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults)
|
||||
{
|
||||
NodeRef sourceNodeRef = getNodeRef();
|
||||
QName assocTypeQName = getTypeQName();
|
||||
QName assocQName = getQName();
|
||||
|
||||
// get the association def
|
||||
AssociationDefinition assocDef = getAssocDef(eventResults, assocTypeQName);
|
||||
// the association definition must exist
|
||||
if (assocDef == null)
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Association type does not exist: \n" +
|
||||
" Association Type: " + assocTypeQName);
|
||||
eventResults.add(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// check that we are dealing with child associations
|
||||
if (assocQName == null)
|
||||
{
|
||||
throw new IllegalArgumentException("The association qualified name must be supplied");
|
||||
}
|
||||
if (!assocDef.isChild())
|
||||
{
|
||||
throw new UnsupportedOperationException("This operation is only relevant to child associations");
|
||||
}
|
||||
ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef;
|
||||
|
||||
// perform required checks
|
||||
checkAssocQNameRegex(eventResults, childAssocDef, assocQName);
|
||||
checkAssocQNameDuplicate(eventResults, childAssocDef, sourceNodeRef, assocQName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the association name matches the constraints imposed by the model.
|
||||
*/
|
||||
protected void checkAssocQNameRegex(
|
||||
List<IntegrityRecord> eventResults,
|
||||
ChildAssociationDefinition assocDef,
|
||||
QName assocQName)
|
||||
{
|
||||
// check the association name
|
||||
QName assocRoleQName = assocDef.getTargetRoleName();
|
||||
if (assocRoleQName != null)
|
||||
{
|
||||
// the assoc defines a role name - check it
|
||||
RegexQNamePattern rolePattern = new RegexQNamePattern(assocRoleQName.toString());
|
||||
if (!rolePattern.isMatch(assocQName))
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"The association name does not match the allowed role names: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Allowed roles: " + rolePattern + "\n" +
|
||||
" Name assigned: " + assocRoleQName);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the association name matches the constraints imposed by the model.
|
||||
*/
|
||||
protected void checkAssocQNameDuplicate(
|
||||
List<IntegrityRecord> eventResults,
|
||||
ChildAssociationDefinition assocDef,
|
||||
NodeRef sourceNodeRef,
|
||||
QName assocQName)
|
||||
{
|
||||
if (assocDef.getDuplicateChildNamesAllowed())
|
||||
{
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
QName assocTypeQName = assocDef.getName();
|
||||
// see if there is another association with the same name
|
||||
try
|
||||
{
|
||||
List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(sourceNodeRef, assocTypeQName, assocQName);
|
||||
// duplicates not allowed
|
||||
if (childAssocs.size() > 1)
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Duplicate child associations are not allowed: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Name: " + assocQName);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
catch (InvalidNodeRefException e)
|
||||
{
|
||||
// node has gone
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.node.integrity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.service.cmr.dictionary.AspectDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.ClassDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.dictionary.TypeDefinition;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Event to check the target type of an association
|
||||
* <p>
|
||||
* Checks are ignored if the target node has been deleted.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class AssocTargetTypeIntegrityEvent extends AbstractIntegrityEvent
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(AssocTargetTypeIntegrityEvent.class);
|
||||
|
||||
public AssocTargetTypeIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef targetNodeRef,
|
||||
QName assocTypeQName)
|
||||
{
|
||||
super(nodeService, dictionaryService, targetNodeRef, assocTypeQName, null);
|
||||
}
|
||||
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults)
|
||||
{
|
||||
QName assocTypeQName = getTypeQName();
|
||||
NodeRef targetNodeRef = getNodeRef();
|
||||
// if the node is gone then the check is irrelevant
|
||||
QName targetNodeTypeQName = getNodeType(targetNodeRef);
|
||||
if (targetNodeTypeQName == null)
|
||||
{
|
||||
// target or source is missing
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Ignoring integrity check - node gone: \n" +
|
||||
" event: " + this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// get the association def
|
||||
AssociationDefinition assocDef = getAssocDef(eventResults, assocTypeQName);
|
||||
// the association definition must exist
|
||||
if (assocDef == null)
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Association type does not exist: \n" +
|
||||
" Target Node Type: " + targetNodeTypeQName + "\n" +
|
||||
" Association Type: " + assocTypeQName);
|
||||
eventResults.add(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// perform required checks
|
||||
checkTargetType(eventResults, assocDef, targetNodeRef, targetNodeTypeQName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the target node type is valid for the association.
|
||||
*/
|
||||
protected void checkTargetType(
|
||||
List<IntegrityRecord> eventResults,
|
||||
AssociationDefinition assocDef,
|
||||
NodeRef targetNodeRef,
|
||||
QName targetNodeTypeQName)
|
||||
{
|
||||
// check the association target type
|
||||
ClassDefinition targetDef = assocDef.getTargetClass();
|
||||
if (targetDef instanceof TypeDefinition)
|
||||
{
|
||||
// the node type must be a match
|
||||
if (!dictionaryService.isSubClass(targetNodeTypeQName, targetDef.getName()))
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"The association target type is incorrect: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Required Target Type: " + targetDef.getName() + "\n" +
|
||||
" Actual Target Type: " + targetNodeTypeQName);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
else if (targetDef instanceof AspectDefinition)
|
||||
{
|
||||
// the target must have a relevant aspect
|
||||
Set<QName> targetAspects = nodeService.getAspects(targetNodeRef);
|
||||
boolean found = false;
|
||||
for (QName targetAspectTypeQName : targetAspects)
|
||||
{
|
||||
if (dictionaryService.isSubClass(targetAspectTypeQName, targetDef.getName()))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"The association target is missing the aspect required for this association: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Required Target Aspect: " + targetDef.getName() + "\n" +
|
||||
" Actual Target Aspects: " + targetAspects);
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Unknown ClassDefinition subclass on the target definition: \n" +
|
||||
" Association: " + assocDef + "\n" +
|
||||
" Source Definition: " + targetDef.getName());
|
||||
eventResults.add(result);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,643 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.node.integrity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.node.NodeServicePolicies;
|
||||
import org.alfresco.repo.policy.JavaBehaviour;
|
||||
import org.alfresco.repo.policy.PolicyComponent;
|
||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
||||
import org.alfresco.service.cmr.dictionary.AspectDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.ClassDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryException;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.repository.AssociationRef;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link org.alfresco.repo.integrity.IntegrityService integrity service}
|
||||
* that uses the domain persistence mechanism to store and recall integrity events.
|
||||
* <p>
|
||||
* In order to fulfill the contract of the interface, this class registers to receive notifications
|
||||
* pertinent to changes in the node structure. These are then store away in the persistent
|
||||
* store until the request to
|
||||
* {@link org.alfresco.repo.integrity.IntegrityService#checkIntegrity(String) check integrity} is
|
||||
* made.
|
||||
* <p>
|
||||
* In order to ensure registration of these events, the {@link #init()} method must be called.
|
||||
* <p>
|
||||
* By default, this service is enabled, but can be disabled using {@link #setEnabled(boolean)}.<br>
|
||||
* Tracing of the event stacks is, for performance reasons, disabled by default but can be enabled
|
||||
* using {@link #setTraceOn(boolean)}.<br>
|
||||
* When enabled, the integrity check can either fail with a <tt>RuntimeException</tt> or not. In either
|
||||
* case, the integrity violations are logged as warnings or errors. This behaviour is controleed using
|
||||
* {@link #setFailOnViolation(boolean)} and is off by default. In other words, if not set, this service
|
||||
* will only log warnings about integrity violations.
|
||||
* <p>
|
||||
* Some integrity checks are not performed here as they are dealt with directly during the modification
|
||||
* operation in the {@link org.alfresco.service.cmr.repository.NodeService node service}.
|
||||
*
|
||||
* @see #setPolicyComponent(PolicyComponent)
|
||||
* @see #setDictionaryService(DictionaryService)
|
||||
* @see #setIntegrityDaoService(IntegrityDaoService)
|
||||
* @see #setMaxErrorsPerTransaction(int)
|
||||
* @see #setFlushSize(int)
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class IntegrityChecker
|
||||
implements NodeServicePolicies.OnCreateNodePolicy,
|
||||
NodeServicePolicies.OnUpdatePropertiesPolicy,
|
||||
NodeServicePolicies.OnDeleteNodePolicy,
|
||||
NodeServicePolicies.OnAddAspectPolicy,
|
||||
NodeServicePolicies.OnRemoveAspectPolicy,
|
||||
NodeServicePolicies.OnCreateChildAssociationPolicy,
|
||||
NodeServicePolicies.OnDeleteChildAssociationPolicy,
|
||||
NodeServicePolicies.OnCreateAssociationPolicy,
|
||||
NodeServicePolicies.OnDeleteAssociationPolicy
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(IntegrityChecker.class);
|
||||
|
||||
/** key against which the set of events is stored in the current transaction */
|
||||
private static final String KEY_EVENT_SET = "IntegrityChecker.EventSet";
|
||||
|
||||
private PolicyComponent policyComponent;
|
||||
private DictionaryService dictionaryService;
|
||||
private NodeService nodeService;
|
||||
private boolean enabled;
|
||||
private boolean failOnViolation;
|
||||
private int maxErrorsPerTransaction;
|
||||
private boolean traceOn;
|
||||
|
||||
/**
|
||||
*/
|
||||
public IntegrityChecker()
|
||||
{
|
||||
this.enabled = true;
|
||||
this.failOnViolation = false;
|
||||
this.maxErrorsPerTransaction = 10;
|
||||
this.traceOn = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param policyComponent the component to register behaviour with
|
||||
*/
|
||||
public void setPolicyComponent(PolicyComponent policyComponent)
|
||||
{
|
||||
this.policyComponent = policyComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param dictionaryService the dictionary against which to confirm model details
|
||||
*/
|
||||
public void setDictionaryService(DictionaryService dictionaryService)
|
||||
{
|
||||
this.dictionaryService = dictionaryService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nodeService the node service to use for browsing node structures
|
||||
*/
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param enabled set to false to disable integrity checking completely
|
||||
*/
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param traceOn set to <code>true</code> to enable stack traces recording
|
||||
* of events
|
||||
*/
|
||||
public void setTraceOn(boolean traceOn)
|
||||
{
|
||||
this.traceOn = traceOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param failOnViolation set to <code>true</code> to force failure by
|
||||
* <tt>RuntimeException</tt> when a violation occurs.
|
||||
*/
|
||||
public void setFailOnViolation(boolean failOnViolation)
|
||||
{
|
||||
this.failOnViolation = failOnViolation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param maxLogNumberPerTransaction upper limit on how many violations are
|
||||
* logged when multiple violations have been found.
|
||||
*/
|
||||
public void setMaxErrorsPerTransaction(int maxLogNumberPerTransaction)
|
||||
{
|
||||
this.maxErrorsPerTransaction = maxLogNumberPerTransaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the system-level policy behaviours
|
||||
*/
|
||||
public void init()
|
||||
{
|
||||
// check that required properties have been set
|
||||
if (dictionaryService == null)
|
||||
throw new AlfrescoRuntimeException("IntegrityChecker property not set: dictionaryService");
|
||||
if (nodeService == null)
|
||||
throw new AlfrescoRuntimeException("IntegrityChecker property not set: nodeService");
|
||||
if (policyComponent == null)
|
||||
throw new AlfrescoRuntimeException("IntegrityChecker property not set: policyComponent");
|
||||
|
||||
if (enabled) // only register behaviour if integrity checking is on
|
||||
{
|
||||
// register behaviour
|
||||
policyComponent.bindClassBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onCreateNode"));
|
||||
policyComponent.bindClassBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onUpdateProperties"));
|
||||
policyComponent.bindClassBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteNode"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onDeleteNode"));
|
||||
policyComponent.bindClassBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onAddAspect"));
|
||||
policyComponent.bindClassBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onRemoveAspect"));
|
||||
policyComponent.bindAssociationBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateChildAssociation"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onCreateChildAssociation"));
|
||||
policyComponent.bindAssociationBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteChildAssociation"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onDeleteChildAssociation"));
|
||||
policyComponent.bindAssociationBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateAssociation"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onCreateAssociation"));
|
||||
policyComponent.bindAssociationBehaviour(
|
||||
QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteAssociation"),
|
||||
this,
|
||||
new JavaBehaviour(this, "onDeleteAssociation"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that this service is registered with the transaction and saves the event
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void save(IntegrityEvent event)
|
||||
{
|
||||
// optionally set trace
|
||||
if (traceOn)
|
||||
{
|
||||
// get a stack trace
|
||||
Throwable t = new Throwable();
|
||||
t.fillInStackTrace();
|
||||
StackTraceElement[] trace = t.getStackTrace();
|
||||
|
||||
event.addTrace(trace);
|
||||
// done
|
||||
}
|
||||
|
||||
// register this service
|
||||
AlfrescoTransactionSupport.bindIntegrityChecker(this);
|
||||
|
||||
// get the event list
|
||||
Map<IntegrityEvent, IntegrityEvent> events =
|
||||
(Map<IntegrityEvent, IntegrityEvent>) AlfrescoTransactionSupport.getResource(KEY_EVENT_SET);
|
||||
if (events == null)
|
||||
{
|
||||
events = new HashMap<IntegrityEvent, IntegrityEvent>(113, 0.75F);
|
||||
AlfrescoTransactionSupport.bindResource(KEY_EVENT_SET, events);
|
||||
}
|
||||
// check if the event is present
|
||||
IntegrityEvent existingEvent = events.get(event);
|
||||
if (existingEvent != null)
|
||||
{
|
||||
// the event (or its equivalent is already present - transfer the trace
|
||||
if (traceOn)
|
||||
{
|
||||
existingEvent.getTraces().addAll(event.getTraces());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the event doesn't already exist
|
||||
events.put(event, event);
|
||||
}
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("" + (existingEvent != null ? "Event already present in" : "Added event to") + " event set: \n" +
|
||||
" event: " + event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PropertiesIntegrityEvent
|
||||
*/
|
||||
public void onCreateNode(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
IntegrityEvent event = null;
|
||||
// check properties on child node
|
||||
event = new PropertiesIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getChildRef());
|
||||
save(event);
|
||||
|
||||
// check target role
|
||||
event = new AssocTargetRoleIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getParentRef(),
|
||||
childAssocRef.getTypeQName(),
|
||||
childAssocRef.getQName());
|
||||
save(event);
|
||||
|
||||
// check for associations defined on the new node (child)
|
||||
NodeRef childRef = childAssocRef.getChildRef();
|
||||
QName childNodeTypeQName = nodeService.getType(childRef);
|
||||
ClassDefinition nodeTypeDef = dictionaryService.getClass(childNodeTypeQName);
|
||||
if (nodeTypeDef == null)
|
||||
{
|
||||
throw new DictionaryException("The node type is not recognized: " + childNodeTypeQName);
|
||||
}
|
||||
Map<QName, AssociationDefinition> childAssocDefs = nodeTypeDef.getAssociations();
|
||||
|
||||
// check the multiplicity of each association with the node acting as a source
|
||||
for (AssociationDefinition assocDef : childAssocDefs.values())
|
||||
{
|
||||
QName assocTypeQName = assocDef.getName();
|
||||
// check target multiplicity
|
||||
event = new AssocTargetMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childRef,
|
||||
assocTypeQName,
|
||||
false);
|
||||
save(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PropertiesIntegrityEvent
|
||||
*/
|
||||
public void onUpdateProperties(
|
||||
NodeRef nodeRef,
|
||||
Map<QName, Serializable> before,
|
||||
Map<QName, Serializable> after)
|
||||
{
|
||||
IntegrityEvent event = null;
|
||||
// check properties on node
|
||||
event = new PropertiesIntegrityEvent(nodeService, dictionaryService, nodeRef);
|
||||
save(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* No checking performed: The association changes will be handled
|
||||
*/
|
||||
public void onDeleteNode(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PropertiesIntegrityEvent
|
||||
*/
|
||||
public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName)
|
||||
{
|
||||
IntegrityEvent event = null;
|
||||
// check properties on node
|
||||
event = new PropertiesIntegrityEvent(nodeService, dictionaryService, nodeRef);
|
||||
save(event);
|
||||
|
||||
// check for associations defined on the aspect
|
||||
AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName);
|
||||
if (aspectDef == null)
|
||||
{
|
||||
throw new DictionaryException("The aspect type is not recognized: " + aspectTypeQName);
|
||||
}
|
||||
Map<QName, AssociationDefinition> assocDefs = aspectDef.getAssociations();
|
||||
|
||||
// check the multiplicity of each association with the node acting as a source
|
||||
for (AssociationDefinition assocDef : assocDefs.values())
|
||||
{
|
||||
QName assocTypeQName = assocDef.getName();
|
||||
// check target multiplicity
|
||||
event = new AssocTargetMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
nodeRef,
|
||||
assocTypeQName,
|
||||
false);
|
||||
save(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* No checking performed: The property changes will be handled
|
||||
*/
|
||||
public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName)
|
||||
{
|
||||
}
|
||||
|
||||
public void onCreateChildAssociation(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
IntegrityEvent event = null;
|
||||
// check source type
|
||||
event = new AssocSourceTypeIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getParentRef(),
|
||||
childAssocRef.getTypeQName());
|
||||
save(event);
|
||||
// check target type
|
||||
event = new AssocTargetTypeIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getChildRef(),
|
||||
childAssocRef.getTypeQName());
|
||||
save(event);
|
||||
// check source multiplicity
|
||||
event = new AssocSourceMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getChildRef(),
|
||||
childAssocRef.getTypeQName(),
|
||||
false);
|
||||
save(event);
|
||||
// check target multiplicity
|
||||
event = new AssocTargetMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getParentRef(),
|
||||
childAssocRef.getTypeQName(),
|
||||
false);
|
||||
save(event);
|
||||
// check target role
|
||||
event = new AssocTargetRoleIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getParentRef(),
|
||||
childAssocRef.getTypeQName(),
|
||||
childAssocRef.getQName());
|
||||
save(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CreateChildAssocIntegrityEvent
|
||||
*/
|
||||
public void onDeleteChildAssociation(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
IntegrityEvent event = null;
|
||||
// check source multiplicity
|
||||
event = new AssocSourceMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getChildRef(),
|
||||
childAssocRef.getTypeQName(),
|
||||
true);
|
||||
save(event);
|
||||
// check target multiplicity
|
||||
event = new AssocTargetMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
childAssocRef.getParentRef(),
|
||||
childAssocRef.getTypeQName(),
|
||||
true);
|
||||
save(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AbstractAssocIntegrityEvent
|
||||
*/
|
||||
public void onCreateAssociation(AssociationRef nodeAssocRef)
|
||||
{
|
||||
IntegrityEvent event = null;
|
||||
// check source type
|
||||
event = new AssocSourceTypeIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
nodeAssocRef.getSourceRef(),
|
||||
nodeAssocRef.getTypeQName());
|
||||
save(event);
|
||||
// check target type
|
||||
event = new AssocTargetTypeIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
nodeAssocRef.getTargetRef(),
|
||||
nodeAssocRef.getTypeQName());
|
||||
save(event);
|
||||
// check source multiplicity
|
||||
event = new AssocSourceMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
nodeAssocRef.getTargetRef(),
|
||||
nodeAssocRef.getTypeQName(),
|
||||
false);
|
||||
save(event);
|
||||
// check target multiplicity
|
||||
event = new AssocTargetMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
nodeAssocRef.getSourceRef(),
|
||||
nodeAssocRef.getTypeQName(),
|
||||
false);
|
||||
save(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AbstractAssocIntegrityEvent
|
||||
*/
|
||||
public void onDeleteAssociation(AssociationRef nodeAssocRef)
|
||||
{
|
||||
IntegrityEvent event = null;
|
||||
// check source multiplicity
|
||||
event = new AssocSourceMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
nodeAssocRef.getTargetRef(),
|
||||
nodeAssocRef.getTypeQName(),
|
||||
true);
|
||||
save(event);
|
||||
// check target multiplicity
|
||||
event = new AssocTargetMultiplicityIntegrityEvent(
|
||||
nodeService,
|
||||
dictionaryService,
|
||||
nodeAssocRef.getSourceRef(),
|
||||
nodeAssocRef.getTypeQName(),
|
||||
true);
|
||||
save(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs several types of checks, querying specifically for events that
|
||||
* will necessitate each type of test.
|
||||
* <p>
|
||||
* The interface contracts also requires that all events for the transaction
|
||||
* get cleaned up.
|
||||
*/
|
||||
public void checkIntegrity() throws IntegrityException
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// process events and check for failures
|
||||
List<IntegrityRecord> failures = processAllEvents();
|
||||
// clear out all events
|
||||
AlfrescoTransactionSupport.unbindResource(KEY_EVENT_SET);
|
||||
|
||||
// drop out quickly if there are no failures
|
||||
if (failures.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// handle errors according to instance flags
|
||||
// firstly, log all failures
|
||||
int failureCount = failures.size();
|
||||
StringBuilder sb = new StringBuilder(300 * failureCount);
|
||||
sb.append("Found ").append(failureCount).append(" integrity violations");
|
||||
if (maxErrorsPerTransaction < failureCount)
|
||||
{
|
||||
sb.append(" - first ").append(maxErrorsPerTransaction);
|
||||
}
|
||||
sb.append(":");
|
||||
int count = 0;
|
||||
for (IntegrityRecord failure : failures)
|
||||
{
|
||||
// break if we exceed the maximum number of log entries
|
||||
count++;
|
||||
if (count > maxErrorsPerTransaction)
|
||||
{
|
||||
break;
|
||||
}
|
||||
sb.append("\n").append(failure);
|
||||
}
|
||||
if (failOnViolation)
|
||||
{
|
||||
logger.error(sb.toString());
|
||||
throw new IntegrityException(failures);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.warn(sb.toString());
|
||||
// no exception
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through all the integrity events and checks integrity.
|
||||
* <p>
|
||||
* The events are stored in a set, so there are no duplicates. Since each
|
||||
* event performs a particular type of check, this ensures that we don't
|
||||
* duplicate checks.
|
||||
*
|
||||
* @return Returns a list of integrity violations, up to the
|
||||
* {@link #maxErrorsPerTransaction the maximum defined}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<IntegrityRecord> processAllEvents()
|
||||
{
|
||||
// the results
|
||||
ArrayList<IntegrityRecord> allIntegrityResults = new ArrayList<IntegrityRecord>(0); // generally unused
|
||||
|
||||
// get all the events for the transaction (or unit of work)
|
||||
// duplicates have been elimiated
|
||||
Map<IntegrityEvent, IntegrityEvent> events =
|
||||
(Map<IntegrityEvent, IntegrityEvent>) AlfrescoTransactionSupport.getResource(KEY_EVENT_SET);
|
||||
if (events == null)
|
||||
{
|
||||
// no events were registered - nothing of significance happened
|
||||
return allIntegrityResults;
|
||||
}
|
||||
|
||||
// failure results for the event
|
||||
List<IntegrityRecord> integrityRecords = new ArrayList<IntegrityRecord>(0);
|
||||
|
||||
// cycle through the events, performing checking integrity
|
||||
for (IntegrityEvent event : events.keySet())
|
||||
{
|
||||
try
|
||||
{
|
||||
event.checkIntegrity(integrityRecords);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
// log it as an error and move to next event
|
||||
IntegrityRecord exceptionRecord = new IntegrityRecord("" + e.getMessage());
|
||||
exceptionRecord.setTraces(Collections.singletonList(e.getStackTrace()));
|
||||
allIntegrityResults.add(exceptionRecord);
|
||||
// move on
|
||||
continue;
|
||||
}
|
||||
|
||||
// keep track of results needing trace added
|
||||
if (traceOn)
|
||||
{
|
||||
// record the current event trace if present
|
||||
for (IntegrityRecord integrityRecord : integrityRecords)
|
||||
{
|
||||
integrityRecord.setTraces(event.getTraces());
|
||||
}
|
||||
}
|
||||
|
||||
// copy all the event results to the final results
|
||||
allIntegrityResults.addAll(integrityRecords);
|
||||
// clear the event results
|
||||
integrityRecords.clear();
|
||||
|
||||
if (allIntegrityResults.size() >= maxErrorsPerTransaction)
|
||||
{
|
||||
// only so many errors wanted at a time
|
||||
break;
|
||||
}
|
||||
}
|
||||
// done
|
||||
return allIntegrityResults;
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.node.integrity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Stores information for all events in the system
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public interface IntegrityEvent
|
||||
{
|
||||
/**
|
||||
* Checks integrity pertinent to the event
|
||||
*
|
||||
* @param eventResults the list of event results that can be added to
|
||||
*/
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults);
|
||||
|
||||
public List<StackTraceElement[]> getTraces();
|
||||
|
||||
public void addTrace(StackTraceElement[] trace);
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.node.integrity;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
|
||||
/**
|
||||
* @see org.alfresco.repo.node.integrity.IntegrityEvent
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class IntegrityEventTest extends TestCase
|
||||
{
|
||||
private static final String NAMESPACE = "http://test";
|
||||
|
||||
private NodeRef nodeRef;
|
||||
private QName typeQName;
|
||||
private QName qname;
|
||||
private IntegrityEvent event;
|
||||
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
nodeRef = new NodeRef("workspace://protocol/ID123");
|
||||
typeQName = QName.createQName(NAMESPACE, "SomeTypeQName");
|
||||
qname = QName.createQName(NAMESPACE, "qname");
|
||||
|
||||
event = new TestIntegrityEvent(null, null, nodeRef, typeQName, qname);
|
||||
}
|
||||
|
||||
public void testSetFunctionality() throws Exception
|
||||
{
|
||||
Set<IntegrityEvent> set = new HashSet<IntegrityEvent>(5);
|
||||
boolean added = set.add(event);
|
||||
assertTrue(added);
|
||||
added = set.add(new TestIntegrityEvent(null, null, nodeRef, typeQName, qname));
|
||||
assertFalse(added);
|
||||
}
|
||||
|
||||
private static class TestIntegrityEvent extends AbstractIntegrityEvent
|
||||
{
|
||||
public TestIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef nodeRef,
|
||||
QName typeQName,
|
||||
QName qname)
|
||||
{
|
||||
super(nodeService, dictionaryService, nodeRef, typeQName, qname);
|
||||
}
|
||||
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.node.integrity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
|
||||
/**
|
||||
* Thrown when an integrity check fails
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class IntegrityException extends AlfrescoRuntimeException
|
||||
{
|
||||
private static final long serialVersionUID = -5036557255854195669L;
|
||||
|
||||
private List<IntegrityRecord> records;
|
||||
|
||||
public IntegrityException(List<IntegrityRecord> records)
|
||||
{
|
||||
super("Integrity failure");
|
||||
this.records = records;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns a list of all the integrity violations
|
||||
*/
|
||||
public List<IntegrityRecord> getRecords()
|
||||
{
|
||||
return records;
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.node.integrity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents an integrity violation
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class IntegrityRecord
|
||||
{
|
||||
private String msg;
|
||||
private List<StackTraceElement[]> traces;
|
||||
|
||||
/**
|
||||
* @param msg the violation message
|
||||
*/
|
||||
public IntegrityRecord(String msg)
|
||||
{
|
||||
this.msg = msg;
|
||||
this.traces = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a stack trace to the list of traces associated with this failure
|
||||
*
|
||||
* @param trace a stack trace
|
||||
*/
|
||||
public void setTraces(List<StackTraceElement[]> traces)
|
||||
{
|
||||
this.traces = traces;
|
||||
}
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the integrity message and, if present, the stack trace
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(msg.length() * 2);
|
||||
if (traces == null)
|
||||
{
|
||||
sb.append(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(msg);
|
||||
for (StackTraceElement[] trace : traces)
|
||||
{
|
||||
sb.append("\n Trace of possible cause:");
|
||||
for (int i = 0; i < trace.length; i++)
|
||||
{
|
||||
sb.append("\n ").append(trace[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
385
source/java/org/alfresco/repo/node/integrity/IntegrityTest.java
Normal file
385
source/java/org/alfresco/repo/node/integrity/IntegrityTest.java
Normal file
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.node.integrity;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.dictionary.DictionaryDAO;
|
||||
import org.alfresco.repo.dictionary.M2Model;
|
||||
import org.alfresco.repo.node.BaseNodeServiceTest;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.ApplicationContextHelper;
|
||||
import org.alfresco.util.PropertyMap;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* Attempts to build faulty node structures in order to test integrity.
|
||||
* <p>
|
||||
* The entire application context is loaded as is, but the integrity fail-
|
||||
* mode is set to throw an exception.
|
||||
*
|
||||
* TODO: Role name restrictions must be checked
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class IntegrityTest extends TestCase
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(IntegrityTest.class);
|
||||
|
||||
public static final String NAMESPACE = "http://www.alfresco.org/test/IntegrityTest";
|
||||
public static final String TEST_PREFIX = "test";
|
||||
|
||||
public static final QName TEST_TYPE_WITHOUT_ANYTHING = QName.createQName(NAMESPACE, "typeWithoutAnything");
|
||||
public static final QName TEST_TYPE_WITH_ASPECT = QName.createQName(NAMESPACE, "typeWithAspect");
|
||||
public static final QName TEST_TYPE_WITH_PROPERTIES = QName.createQName(NAMESPACE, "typeWithProperties");
|
||||
public static final QName TEST_TYPE_WITH_ASSOCS = QName.createQName(NAMESPACE, "typeWithAssocs");
|
||||
public static final QName TEST_TYPE_WITH_CHILD_ASSOCS = QName.createQName(NAMESPACE, "typeWithChildAssocs");
|
||||
|
||||
public static final QName TEST_ASSOC_NODE_ZEROMANY_ZEROMANY = QName.createQName(NAMESPACE, "assoc-0to* - 0to*");
|
||||
public static final QName TEST_ASSOC_CHILD_ZEROMANY_ZEROMANY = QName.createQName(NAMESPACE, "child-0to* - 0to*");
|
||||
public static final QName TEST_ASSOC_NODE_ONE_ONE = QName.createQName(NAMESPACE, "assoc-1to1 - 1to1");
|
||||
public static final QName TEST_ASSOC_CHILD_ONE_ONE = QName.createQName(NAMESPACE, "child-1to1 - 1to1");
|
||||
public static final QName TEST_ASSOC_ASPECT_ONE_ONE = QName.createQName(NAMESPACE, "aspect-assoc-1to1 - 1to1");
|
||||
|
||||
public static final QName TEST_ASPECT_WITH_PROPERTIES = QName.createQName(NAMESPACE, "aspectWithProperties");
|
||||
public static final QName TEST_ASPECT_WITH_ASSOC = QName.createQName(NAMESPACE, "aspectWithAssoc");
|
||||
|
||||
public static final QName TEST_PROP_TEXT_A = QName.createQName(NAMESPACE, "prop-text-a");
|
||||
public static final QName TEST_PROP_TEXT_B = QName.createQName(NAMESPACE, "prop-text-b");
|
||||
public static final QName TEST_PROP_INT_A = QName.createQName(NAMESPACE, "prop-int-a");
|
||||
public static final QName TEST_PROP_INT_B = QName.createQName(NAMESPACE, "prop-int-b");
|
||||
|
||||
private static ApplicationContext ctx;
|
||||
static
|
||||
{
|
||||
ctx = ApplicationContextHelper.getApplicationContext();
|
||||
}
|
||||
|
||||
private IntegrityChecker integrityChecker;
|
||||
private ServiceRegistry serviceRegistry;
|
||||
private NodeService nodeService;
|
||||
private NodeRef rootNodeRef;
|
||||
private PropertyMap allProperties;
|
||||
private UserTransaction txn;
|
||||
private AuthenticationComponent authenticationComponent;
|
||||
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
DictionaryDAO dictionaryDao = (DictionaryDAO) ctx.getBean("dictionaryDAO");
|
||||
ClassLoader cl = BaseNodeServiceTest.class.getClassLoader();
|
||||
// load the test model
|
||||
InputStream modelStream = cl.getResourceAsStream("org/alfresco/repo/node/integrity/IntegrityTest_model.xml");
|
||||
assertNotNull(modelStream);
|
||||
M2Model model = M2Model.createModel(modelStream);
|
||||
dictionaryDao.putModel(model);
|
||||
|
||||
integrityChecker = (IntegrityChecker) ctx.getBean("integrityChecker");
|
||||
integrityChecker.setEnabled(true);
|
||||
integrityChecker.setFailOnViolation(true);
|
||||
integrityChecker.setTraceOn(true);
|
||||
integrityChecker.setMaxErrorsPerTransaction(100); // we want to count the correct number of errors
|
||||
|
||||
serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
|
||||
nodeService = serviceRegistry.getNodeService();
|
||||
this.authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent");
|
||||
|
||||
this.authenticationComponent.setSystemUserAsCurrentUser();
|
||||
|
||||
// begin a transaction
|
||||
TransactionService transactionService = serviceRegistry.getTransactionService();
|
||||
txn = transactionService.getUserTransaction();
|
||||
txn.begin();
|
||||
StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, getName());
|
||||
if (!nodeService.exists(storeRef))
|
||||
{
|
||||
nodeService.createStore(storeRef.getProtocol(), storeRef.getIdentifier());
|
||||
}
|
||||
rootNodeRef = nodeService.getRootNode(storeRef);
|
||||
|
||||
allProperties = new PropertyMap();
|
||||
allProperties.put(TEST_PROP_TEXT_A, "ABC");
|
||||
allProperties.put(TEST_PROP_TEXT_B, "DEF");
|
||||
allProperties.put(TEST_PROP_INT_A, "123");
|
||||
allProperties.put(TEST_PROP_INT_B, "456");
|
||||
}
|
||||
|
||||
public void tearDown() throws Exception
|
||||
{
|
||||
authenticationComponent.clearCurrentSecurityContext();
|
||||
txn.rollback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a node of the given type, and hanging off the root node
|
||||
*/
|
||||
private NodeRef createNode(String name, QName type, PropertyMap properties)
|
||||
{
|
||||
return nodeService.createNode(
|
||||
rootNodeRef,
|
||||
ContentModel.ASSOC_CHILDREN,
|
||||
QName.createQName(NAMESPACE, name),
|
||||
type,
|
||||
properties
|
||||
).getChildRef();
|
||||
}
|
||||
|
||||
private void checkIntegrityNoFailure() throws Exception
|
||||
{
|
||||
integrityChecker.checkIntegrity();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param failureMsg the fail message if an integrity exception doesn't occur
|
||||
* @param expectedCount the expected number of integrity failures, or -1 to ignore
|
||||
*/
|
||||
private void checkIntegrityExpectFailure(String failureMsg, int expectedCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
integrityChecker.checkIntegrity();
|
||||
fail(failureMsg);
|
||||
}
|
||||
catch (IntegrityException e)
|
||||
{
|
||||
if (expectedCount >= 0)
|
||||
{
|
||||
assertEquals("Incorrect number of integrity records generated", expectedCount, e.getRecords().size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testSetUp() throws Exception
|
||||
{
|
||||
assertNotNull("Static IntegrityChecker not created", integrityChecker);
|
||||
}
|
||||
|
||||
public void testCreateWithoutProperties() throws Exception
|
||||
{
|
||||
NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_PROPERTIES, null);
|
||||
checkIntegrityExpectFailure("Failed to detect missing properties", 1);
|
||||
}
|
||||
|
||||
public void testCreateWithProperties() throws Exception
|
||||
{
|
||||
NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_PROPERTIES, allProperties);
|
||||
checkIntegrityNoFailure();
|
||||
}
|
||||
|
||||
public void testMandatoryPropertiesRemoved() throws Exception
|
||||
{
|
||||
NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_PROPERTIES, allProperties);
|
||||
|
||||
// remove all the properties
|
||||
PropertyMap properties = new PropertyMap();
|
||||
nodeService.setProperties(nodeRef, properties);
|
||||
|
||||
checkIntegrityExpectFailure("Failed to detect missing removed properties", 1);
|
||||
}
|
||||
|
||||
public void testCreateWithoutPropertiesForAspect() throws Exception
|
||||
{
|
||||
NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_ASPECT, null);
|
||||
|
||||
checkIntegrityExpectFailure("Failed to detect missing properties for aspect", 1);
|
||||
}
|
||||
|
||||
public void testCreateWithPropertiesForAspect() throws Exception
|
||||
{
|
||||
NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_ASPECT, allProperties);
|
||||
checkIntegrityNoFailure();
|
||||
}
|
||||
|
||||
public void testCreateTargetOfAssocsWithMandatorySourcesPresent() throws Exception
|
||||
{
|
||||
// this is the target of 3 assoc types where the source cardinality is 1..1
|
||||
NodeRef targetAndChild = createNode("targetAndChild", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
|
||||
NodeRef source = createNode("source", TEST_TYPE_WITH_ASSOCS, null);
|
||||
nodeService.createAssociation(source, targetAndChild, TEST_ASSOC_NODE_ONE_ONE);
|
||||
|
||||
NodeRef parent = createNode("parent", TEST_TYPE_WITH_CHILD_ASSOCS, null);
|
||||
nodeService.addChild(parent, targetAndChild, TEST_ASSOC_CHILD_ONE_ONE, QName.createQName(NAMESPACE, "mandatoryChild"));
|
||||
|
||||
NodeRef aspected = createNode("aspectNode", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.addAspect(aspected, TEST_ASPECT_WITH_ASSOC, null);
|
||||
nodeService.createAssociation(aspected, targetAndChild, TEST_ASSOC_ASPECT_ONE_ONE);
|
||||
|
||||
checkIntegrityNoFailure();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: The dictionary support for the reverse lookup of mandatory associations will
|
||||
* allow this method to go in
|
||||
* <p>
|
||||
* <b>Does nothing</b>.
|
||||
*/
|
||||
public void testCreateTargetOfAssocsWithMandatorySourcesMissing() throws Exception
|
||||
{
|
||||
// // this is the target of 3 associations where the source cardinality is 1..1
|
||||
// NodeRef target = createNode("abc", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
//
|
||||
// checkIntegrityExpectFailure("Failed to detect missing mandatory assoc sources", 3);
|
||||
logger.error("Method commented out: testCreateTargetOfAssocsWithMandatorySourcesMissing");
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Reactivate once cascade delete notifications are back on
|
||||
* <p>
|
||||
* <b>Does nothing</b>.
|
||||
*/
|
||||
public void testRemoveSourcesOfMandatoryAssocs() throws Exception
|
||||
{
|
||||
// // this is the target of 3 assoc types where the source cardinality is 1..1
|
||||
// NodeRef targetAndChild = createNode("targetAndChild", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
//
|
||||
// NodeRef source = createNode("source", TEST_TYPE_WITH_ASSOCS, null);
|
||||
// nodeService.createAssociation(source, targetAndChild, TEST_ASSOC_NODE_ONE_ONE);
|
||||
//
|
||||
// NodeRef parent = createNode("parent", TEST_TYPE_WITH_CHILD_ASSOCS, null);
|
||||
// nodeService.addChild(parent, targetAndChild, TEST_ASSOC_CHILD_ONE_ONE, QName.createQName(NAMESPACE, "mandatoryChild"));
|
||||
//
|
||||
// NodeRef aspectSource = createNode("aspectSource", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
// nodeService.addAspect(aspectSource, TEST_ASPECT_WITH_ASSOC, null);
|
||||
// nodeService.createAssociation(aspectSource, targetAndChild, TEST_ASSOC_ASPECT_ONE_ONE);
|
||||
//
|
||||
// checkIntegrityNoFailure();
|
||||
//
|
||||
// // remove source nodes
|
||||
// nodeService.deleteNode(source);
|
||||
// nodeService.deleteNode(parent);
|
||||
// nodeService.deleteNode(aspectSource);
|
||||
//
|
||||
// checkIntegrityExpectFailure("Failed to detect removal of mandatory assoc sources", 3);
|
||||
logger.error("Method commented out: testRemoveSourcesOfMandatoryAssocs");
|
||||
}
|
||||
|
||||
public void testDuplicateTargetAssocs() throws Exception
|
||||
{
|
||||
NodeRef parent = createNode("source", TEST_TYPE_WITH_CHILD_ASSOCS, null);
|
||||
NodeRef child1 = createNode("child1", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
NodeRef child2 = createNode("child2", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
NodeRef child3 = createNode("child3", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
|
||||
// satisfy the one-to-one
|
||||
nodeService.addChild(parent, child3, TEST_ASSOC_CHILD_ONE_ONE, QName.createQName(NAMESPACE, "mandatoryChild"));
|
||||
|
||||
// create the non-duplicate assocs
|
||||
nodeService.addChild(parent, child1, TEST_ASSOC_CHILD_ZEROMANY_ZEROMANY, QName.createQName(NAMESPACE, "dupli_cate"));
|
||||
nodeService.addChild(parent, child2, TEST_ASSOC_CHILD_ZEROMANY_ZEROMANY, QName.createQName(NAMESPACE, "dupli_cate"));
|
||||
|
||||
checkIntegrityExpectFailure("Failed to detect duplicate association names", 1);
|
||||
}
|
||||
|
||||
public void testCreateSourceOfAssocsWithMandatoryTargetsPresent() throws Exception
|
||||
{
|
||||
NodeRef source = createNode("abc", TEST_TYPE_WITH_ASSOCS, null);
|
||||
NodeRef target = createNode("target", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.createAssociation(source, target, TEST_ASSOC_NODE_ONE_ONE);
|
||||
|
||||
NodeRef parent = createNode("parent", TEST_TYPE_WITH_CHILD_ASSOCS, null);
|
||||
NodeRef child = createNode("child", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.addChild(parent, child, TEST_ASSOC_CHILD_ONE_ONE, QName.createQName(NAMESPACE, "one-to-one"));
|
||||
|
||||
NodeRef aspectSource = createNode("aspectSource", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.addAspect(aspectSource, TEST_ASPECT_WITH_ASSOC, null);
|
||||
NodeRef aspectTarget = createNode("aspectTarget", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.createAssociation(aspectSource, aspectTarget, TEST_ASSOC_ASPECT_ONE_ONE);
|
||||
|
||||
checkIntegrityNoFailure();
|
||||
}
|
||||
|
||||
public void testCreateSourceOfAssocsWithMandatoryTargetsMissing() throws Exception
|
||||
{
|
||||
NodeRef source = createNode("abc", TEST_TYPE_WITH_ASSOCS, null);
|
||||
|
||||
NodeRef parent = createNode("parent", TEST_TYPE_WITH_CHILD_ASSOCS, null);
|
||||
|
||||
NodeRef aspectSource = createNode("aspectSource", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.addAspect(aspectSource, TEST_ASPECT_WITH_ASSOC, null);
|
||||
|
||||
checkIntegrityExpectFailure("Failed to detect missing assoc targets", 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Reactivate once cascade delete notifications are back on
|
||||
* <p>
|
||||
* <b>Does nothing</b>.
|
||||
*/
|
||||
public void testRemoveTargetsOfMandatoryAssocs() throws Exception
|
||||
{
|
||||
// NodeRef source = createNode("abc", TEST_TYPE_WITH_ASSOCS, null);
|
||||
// NodeRef target = createNode("target", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
// nodeService.createAssociation(source, target, TEST_ASSOC_NODE_ONE_ONE);
|
||||
//
|
||||
// NodeRef parent = createNode("parent", TEST_TYPE_WITH_CHILD_ASSOCS, null);
|
||||
// NodeRef child = createNode("child", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
// nodeService.addChild(parent, child, TEST_ASSOC_CHILD_ONE_ONE, QName.createQName(NAMESPACE, "one-to-one"));
|
||||
//
|
||||
// NodeRef aspectSource = createNode("aspectSource", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
// nodeService.addAspect(aspectSource, TEST_ASPECT_WITH_ASSOC, null);
|
||||
// NodeRef aspectTarget = createNode("aspectTarget", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
// nodeService.createAssociation(aspectSource, aspectTarget, TEST_ASSOC_ASPECT_ONE_ONE);
|
||||
//
|
||||
// checkIntegrityNoFailure();
|
||||
//
|
||||
// // remove target nodes
|
||||
// nodeService.deleteNode(target);
|
||||
// nodeService.deleteNode(child);
|
||||
// nodeService.deleteNode(aspectTarget);
|
||||
//
|
||||
// checkIntegrityExpectFailure("Failed to detect removal of mandatory assoc targets", 3);
|
||||
logger.error("Method commented out: testRemoveTargetsOfMandatoryAssocs");
|
||||
}
|
||||
|
||||
public void testExcessTargetsOfOneToOneAssocs() throws Exception
|
||||
{
|
||||
NodeRef source = createNode("abc", TEST_TYPE_WITH_ASSOCS, null);
|
||||
NodeRef target1 = createNode("target1", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
NodeRef target2 = createNode("target2", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.createAssociation(source, target1, TEST_ASSOC_NODE_ONE_ONE);
|
||||
nodeService.createAssociation(source, target2, TEST_ASSOC_NODE_ONE_ONE);
|
||||
|
||||
NodeRef parent = createNode("parent", TEST_TYPE_WITH_CHILD_ASSOCS, null);
|
||||
NodeRef child1 = createNode("child1", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
NodeRef child2 = createNode("child2", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.addChild(parent, child1, TEST_ASSOC_CHILD_ONE_ONE, QName.createQName(NAMESPACE, "one-to-one-first"));
|
||||
nodeService.addChild(parent, child2, TEST_ASSOC_CHILD_ONE_ONE, QName.createQName(NAMESPACE, "one-to-one-second"));
|
||||
|
||||
NodeRef aspectSource = createNode("aspectSource", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.addAspect(aspectSource, TEST_ASPECT_WITH_ASSOC, null);
|
||||
NodeRef aspectTarget1 = createNode("aspectTarget1", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
NodeRef aspectTarget2 = createNode("aspectTarget2", TEST_TYPE_WITHOUT_ANYTHING, null);
|
||||
nodeService.createAssociation(aspectSource, aspectTarget1, TEST_ASSOC_ASPECT_ONE_ONE);
|
||||
nodeService.createAssociation(aspectSource, aspectTarget2, TEST_ASSOC_ASPECT_ONE_ONE);
|
||||
|
||||
checkIntegrityExpectFailure("Failed to detect excess target cardinality for one-to-one assocs", 3);
|
||||
}
|
||||
}
|
@@ -0,0 +1,140 @@
|
||||
<model name="test:integrity" xmlns="http://www.alfresco.org/model/dictionary/1.0">
|
||||
|
||||
<description>Test Model for Integrity tests</description>
|
||||
<author>Alfresco</author>
|
||||
<published>2005-06-05</published>
|
||||
<version>0.1</version>
|
||||
|
||||
<imports>
|
||||
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
|
||||
<import uri="http://www.alfresco.org/model/system/1.0" prefix="sys"/>
|
||||
</imports>
|
||||
|
||||
<namespaces>
|
||||
<namespace uri="http://www.alfresco.org/test/IntegrityTest" prefix="test"/>
|
||||
</namespaces>
|
||||
|
||||
<types>
|
||||
<!-- Type without anyting -->
|
||||
<type name="test:typeWithoutAnything">
|
||||
<title>Type Without Anything</title>
|
||||
<parent>sys:base</parent>
|
||||
</type>
|
||||
<!-- Type with mandatory properties -->
|
||||
<type name="test:typeWithProperties">
|
||||
<title>Type With Properties</title>
|
||||
<parent>sys:base</parent>
|
||||
<properties>
|
||||
<property name="test:prop-text-a">
|
||||
<type>d:text</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:prop-text-b">
|
||||
<type>d:text</type>
|
||||
</property>
|
||||
</properties>
|
||||
</type>
|
||||
<!-- Type with mandatory aspect -->
|
||||
<type name="test:typeWithAspect">
|
||||
<title>Type With Aspect</title>
|
||||
<parent>sys:base</parent>
|
||||
<mandatory-aspects>
|
||||
<aspect>test:aspectWithProperties</aspect>
|
||||
</mandatory-aspects>
|
||||
</type>
|
||||
<!-- Type with assocs -->
|
||||
<type name="test:typeWithAssocs">
|
||||
<title>Type With Assocs</title>
|
||||
<parent>sys:base</parent>
|
||||
<associations>
|
||||
<association name="test:assoc-0to* - 0to*">
|
||||
<source>
|
||||
<mandatory>false</mandatory>
|
||||
<many>true</many>
|
||||
</source>
|
||||
<target>
|
||||
<class>test:typeWithoutAnything</class>
|
||||
<mandatory>false</mandatory>
|
||||
<many>true</many>
|
||||
</target>
|
||||
</association>
|
||||
<association name="test:assoc-1to1 - 1to1">
|
||||
<source>
|
||||
<mandatory>true</mandatory>
|
||||
<many>false</many>
|
||||
</source>
|
||||
<target>
|
||||
<class>test:typeWithoutAnything</class>
|
||||
<mandatory>true</mandatory>
|
||||
<many>false</many>
|
||||
</target>
|
||||
</association>
|
||||
</associations>
|
||||
</type>
|
||||
<!-- Type with child assocs -->
|
||||
<type name="test:typeWithChildAssocs">
|
||||
<title>Type With Child Assocs</title>
|
||||
<parent>sys:base</parent>
|
||||
<associations>
|
||||
<child-association name="test:child-0to* - 0to*">
|
||||
<source>
|
||||
<mandatory>false</mandatory>
|
||||
<many>true</many>
|
||||
</source>
|
||||
<target>
|
||||
<class>test:typeWithoutAnything</class>
|
||||
<mandatory>false</mandatory>
|
||||
<many>true</many>
|
||||
</target>
|
||||
<duplicate>false</duplicate>
|
||||
</child-association>
|
||||
<child-association name="test:child-1to1 - 1to1">
|
||||
<source>
|
||||
<mandatory>true</mandatory>
|
||||
<many>false</many>
|
||||
</source>
|
||||
<target>
|
||||
<class>test:typeWithoutAnything</class>
|
||||
<mandatory>true</mandatory>
|
||||
<many>false</many>
|
||||
</target>
|
||||
<duplicate>false</duplicate>
|
||||
</child-association>
|
||||
</associations>
|
||||
</type>
|
||||
</types>
|
||||
|
||||
<aspects>
|
||||
<!-- aspect with properties -->
|
||||
<aspect name="test:aspectWithProperties">
|
||||
<title>Aspect with Properties</title>
|
||||
<properties>
|
||||
<property name="test:prop-int-a">
|
||||
<type>d:int</type>
|
||||
<mandatory>true</mandatory>
|
||||
</property>
|
||||
<property name="test:prop-int-b">
|
||||
<type>d:int</type>
|
||||
</property>
|
||||
</properties>
|
||||
</aspect>
|
||||
<!-- aspect with associations -->
|
||||
<aspect name="test:aspectWithAssoc">
|
||||
<title>Aspect with associations</title>
|
||||
<associations>
|
||||
<association name="test:aspect-assoc-1to1 - 1to1">
|
||||
<source>
|
||||
<mandatory>true</mandatory>
|
||||
<many>false</many>
|
||||
</source>
|
||||
<target>
|
||||
<class>test:typeWithoutAnything</class>
|
||||
<mandatory>true</mandatory>
|
||||
<many>false</many>
|
||||
</target>
|
||||
</association>
|
||||
</associations>
|
||||
</aspect>
|
||||
</aspects>
|
||||
|
||||
</model>
|
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.node.integrity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.service.cmr.dictionary.AspectDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.TypeDefinition;
|
||||
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Event raised to check nodes
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class PropertiesIntegrityEvent extends AbstractIntegrityEvent
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(PropertiesIntegrityEvent.class);
|
||||
|
||||
protected PropertiesIntegrityEvent(
|
||||
NodeService nodeService,
|
||||
DictionaryService dictionaryService,
|
||||
NodeRef nodeRef)
|
||||
{
|
||||
super(nodeService, dictionaryService, nodeRef, null, null);
|
||||
}
|
||||
|
||||
public void checkIntegrity(List<IntegrityRecord> eventResults)
|
||||
{
|
||||
try
|
||||
{
|
||||
checkAllProperties(getNodeRef(), eventResults);
|
||||
}
|
||||
catch (InvalidNodeRefException e)
|
||||
{
|
||||
// node has gone
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Event ignored - node gone: " + this);
|
||||
}
|
||||
eventResults.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the properties for the type and aspects of the given node.
|
||||
*
|
||||
* @param nodeRef
|
||||
* @param eventResults
|
||||
*/
|
||||
private void checkAllProperties(NodeRef nodeRef, List<IntegrityRecord> eventResults)
|
||||
{
|
||||
// get all properties for the node
|
||||
Map<QName, Serializable> nodeProperties = nodeService.getProperties(nodeRef);
|
||||
|
||||
// get the node type
|
||||
QName nodeTypeQName = nodeService.getType(nodeRef);
|
||||
// get property definitions for the node type
|
||||
TypeDefinition typeDef = dictionaryService.getType(nodeTypeQName);
|
||||
Collection<PropertyDefinition> propertyDefs = typeDef.getProperties().values();
|
||||
// check them
|
||||
checkAllProperties(nodeRef, nodeTypeQName, propertyDefs, nodeProperties, eventResults);
|
||||
|
||||
// get the node aspects
|
||||
Set<QName> aspectTypeQNames = nodeService.getAspects(nodeRef);
|
||||
for (QName aspectTypeQName : aspectTypeQNames)
|
||||
{
|
||||
// get property definitions for the aspect
|
||||
AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName);
|
||||
propertyDefs = aspectDef.getProperties().values();
|
||||
// check them
|
||||
checkAllProperties(nodeRef, aspectTypeQName, propertyDefs, nodeProperties, eventResults);
|
||||
}
|
||||
// done
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the specific map of properties against the required property definitions
|
||||
*
|
||||
* @param nodeRef the node to which this applies
|
||||
* @param typeQName the qualified name of the aspect or type to which the properties belong
|
||||
* @param propertyDefs the definitions to check against - may be null or empty
|
||||
* @param nodeProperties the properties to check
|
||||
*/
|
||||
private void checkAllProperties(
|
||||
NodeRef nodeRef,
|
||||
QName typeQName,
|
||||
Collection<PropertyDefinition> propertyDefs,
|
||||
Map<QName, Serializable> nodeProperties,
|
||||
Collection<IntegrityRecord> eventResults)
|
||||
{
|
||||
// check for null or empty definitions
|
||||
if (propertyDefs == null || propertyDefs.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (PropertyDefinition propertyDef : propertyDefs)
|
||||
{
|
||||
QName propertyQName = propertyDef.getName();
|
||||
Serializable propertyValue = nodeProperties.get(propertyQName);
|
||||
// check that mandatory properties are set
|
||||
if (propertyDef.isMandatory() && !nodeProperties.containsKey(propertyQName))
|
||||
{
|
||||
IntegrityRecord result = new IntegrityRecord(
|
||||
"Mandatory property not set: \n" +
|
||||
" Node: " + nodeRef + "\n" +
|
||||
" Type: " + typeQName + "\n" +
|
||||
" Property: " + propertyQName);
|
||||
eventResults.add(result);
|
||||
// next one
|
||||
continue;
|
||||
}
|
||||
// TODO: Incorporate value constraint checks - JIRA AR166
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user