/* * 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.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.alfresco.util.PropertyCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Component that generates and processes integrity events, enforcing the dictionary * model's node structure. *
* 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. *
* In order to ensure registration of these events, the {@link #init()} method must be called. *
* By default, this service is enabled, but can be disabled using {@link #setEnabled(boolean)}.
* Tracing of the event stacks is, for performance reasons, disabled by default but can be enabled
* using {@link #setTraceOn(boolean)}.
* When enabled, the integrity check can either fail with a RuntimeException 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.
*
* 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";
/** key to store the local flag to disable integrity errors, i.e. downgrade to warnings */
private static final String KEY_WARN_IN_TRANSACTION = "IntegrityChecker.WarnInTransaction";
private PolicyComponent policyComponent;
private DictionaryService dictionaryService;
private NodeService nodeService;
private boolean enabled;
private boolean failOnViolation;
private int maxErrorsPerTransaction;
private boolean traceOn;
/**
* Downgrade violations to warnings within the current transaction. This is temporary and
* is dependent on there being a current transaction active against the
* current thread. When set, this will override the global
* {@link #setFailOnViolation(boolean) failure behaviour}.
*/
public static void setWarnInTransaction()
{
AlfrescoTransactionSupport.bindResource(KEY_WARN_IN_TRANSACTION, Boolean.TRUE);
}
/**
* @return Returns true if the current transaction should only warn on violations.
* If
* 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
* 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 Listfalse
, the global setting will take effect.
*
* @see #setWarnInTransaction()
*/
public static boolean isWarnInTransaction()
{
Boolean warnInTransaction = (Boolean) AlfrescoTransactionSupport.getResource(KEY_WARN_IN_TRANSACTION);
if (warnInTransaction == null || warnInTransaction == Boolean.FALSE)
{
return false;
}
else
{
return true;
}
}
/**
*/
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 true
to enable stack traces recording
* of events
*/
public void setTraceOn(boolean traceOn)
{
this.traceOn = traceOn;
}
/**
* @param failOnViolation set to true
to force failure by
* RuntimeException 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
PropertyCheck.mandatory("IntegrityChecker", "dictionaryService", dictionaryService);
PropertyCheck.mandatory("IntegrityChecker", "nodeService", nodeService);
PropertyCheck.mandatory("IntegrityChecker", "policyComponent", 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