KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > alfresco > repo > node > integrity > IntegrityChecker


1 /*
2  * Copyright (C) 2005 Alfresco, Inc.
3  *
4  * Licensed under the Mozilla Public License version 1.1
5  * with a permitted attribution clause. You may obtain a
6  * copy of the License at
7  *
8  * http://www.alfresco.org/legal/license.txt
9  *
10  * Unless required by applicable law or agreed to in writing,
11  * software distributed under the License is distributed on an
12  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13  * either express or implied. See the License for the specific
14  * language governing permissions and limitations under the
15  * License.
16  */

17 package org.alfresco.repo.node.integrity;
18
19 import java.io.Serializable JavaDoc;
20 import java.util.ArrayList JavaDoc;
21 import java.util.Collections JavaDoc;
22 import java.util.HashMap JavaDoc;
23 import java.util.List JavaDoc;
24 import java.util.Map JavaDoc;
25
26 import org.alfresco.error.AlfrescoRuntimeException;
27 import org.alfresco.repo.node.NodeServicePolicies;
28 import org.alfresco.repo.policy.JavaBehaviour;
29 import org.alfresco.repo.policy.PolicyComponent;
30 import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
31 import org.alfresco.service.cmr.dictionary.AspectDefinition;
32 import org.alfresco.service.cmr.dictionary.AssociationDefinition;
33 import org.alfresco.service.cmr.dictionary.ClassDefinition;
34 import org.alfresco.service.cmr.dictionary.DictionaryException;
35 import org.alfresco.service.cmr.dictionary.DictionaryService;
36 import org.alfresco.service.cmr.repository.AssociationRef;
37 import org.alfresco.service.cmr.repository.ChildAssociationRef;
38 import org.alfresco.service.cmr.repository.NodeRef;
39 import org.alfresco.service.cmr.repository.NodeService;
40 import org.alfresco.service.namespace.NamespaceService;
41 import org.alfresco.service.namespace.QName;
42 import org.apache.commons.logging.Log;
43 import org.apache.commons.logging.LogFactory;
44
45 /**
46  * Implementation of the {@link org.alfresco.repo.integrity.IntegrityService integrity service}
47  * that uses the domain persistence mechanism to store and recall integrity events.
48  * <p>
49  * In order to fulfill the contract of the interface, this class registers to receive notifications
50  * pertinent to changes in the node structure. These are then store away in the persistent
51  * store until the request to
52  * {@link org.alfresco.repo.integrity.IntegrityService#checkIntegrity(String) check integrity} is
53  * made.
54  * <p>
55  * In order to ensure registration of these events, the {@link #init()} method must be called.
56  * <p>
57  * By default, this service is enabled, but can be disabled using {@link #setEnabled(boolean)}.<br>
58  * Tracing of the event stacks is, for performance reasons, disabled by default but can be enabled
59  * using {@link #setTraceOn(boolean)}.<br>
60  * When enabled, the integrity check can either fail with a <tt>RuntimeException</tt> or not. In either
61  * case, the integrity violations are logged as warnings or errors. This behaviour is controleed using
62  * {@link #setFailOnViolation(boolean)} and is off by default. In other words, if not set, this service
63  * will only log warnings about integrity violations.
64  * <p>
65  * Some integrity checks are not performed here as they are dealt with directly during the modification
66  * operation in the {@link org.alfresco.service.cmr.repository.NodeService node service}.
67  *
68  * @see #setPolicyComponent(PolicyComponent)
69  * @see #setDictionaryService(DictionaryService)
70  * @see #setIntegrityDaoService(IntegrityDaoService)
71  * @see #setMaxErrorsPerTransaction(int)
72  * @see #setFlushSize(int)
73  *
74  * @author Derek Hulley
75  */

76 public class IntegrityChecker
77         implements NodeServicePolicies.OnCreateNodePolicy,
78                     NodeServicePolicies.OnUpdatePropertiesPolicy,
79                     NodeServicePolicies.OnDeleteNodePolicy,
80                     NodeServicePolicies.OnAddAspectPolicy,
81                     NodeServicePolicies.OnRemoveAspectPolicy,
82                     NodeServicePolicies.OnCreateChildAssociationPolicy,
83                     NodeServicePolicies.OnDeleteChildAssociationPolicy,
84                     NodeServicePolicies.OnCreateAssociationPolicy,
85                     NodeServicePolicies.OnDeleteAssociationPolicy
86 {
87     private static Log logger = LogFactory.getLog(IntegrityChecker.class);
88     
89     /** key against which the set of events is stored in the current transaction */
90     private static final String JavaDoc KEY_EVENT_SET = "IntegrityChecker.EventSet";
91     
92     private PolicyComponent policyComponent;
93     private DictionaryService dictionaryService;
94     private NodeService nodeService;
95     private boolean enabled;
96     private boolean failOnViolation;
97     private int maxErrorsPerTransaction;
98     private boolean traceOn;
99     
100     /**
101      */

102     public IntegrityChecker()
103     {
104         this.enabled = true;
105         this.failOnViolation = false;
106         this.maxErrorsPerTransaction = 10;
107         this.traceOn = false;
108     }
109
110     /**
111      * @param policyComponent the component to register behaviour with
112      */

113     public void setPolicyComponent(PolicyComponent policyComponent)
114     {
115         this.policyComponent = policyComponent;
116     }
117
118     /**
119      * @param dictionaryService the dictionary against which to confirm model details
120      */

121     public void setDictionaryService(DictionaryService dictionaryService)
122     {
123         this.dictionaryService = dictionaryService;
124     }
125
126     /**
127      * @param nodeService the node service to use for browsing node structures
128      */

129     public void setNodeService(NodeService nodeService)
130     {
131         this.nodeService = nodeService;
132     }
133
134     /**
135      * @param enabled set to false to disable integrity checking completely
136      */

137     public void setEnabled(boolean enabled)
138     {
139         this.enabled = enabled;
140     }
141
142     /**
143      * @param traceOn set to <code>true</code> to enable stack traces recording
144      * of events
145      */

146     public void setTraceOn(boolean traceOn)
147     {
148         this.traceOn = traceOn;
149     }
150
151     /**
152      * @param failOnViolation set to <code>true</code> to force failure by
153      * <tt>RuntimeException</tt> when a violation occurs.
154      */

155     public void setFailOnViolation(boolean failOnViolation)
156     {
157         this.failOnViolation = failOnViolation;
158     }
159
160     /**
161      * @param maxLogNumberPerTransaction upper limit on how many violations are
162      * logged when multiple violations have been found.
163      */

164     public void setMaxErrorsPerTransaction(int maxLogNumberPerTransaction)
165     {
166         this.maxErrorsPerTransaction = maxLogNumberPerTransaction;
167     }
168
169     /**
170      * Registers the system-level policy behaviours
171      */

172     public void init()
173     {
174         // check that required properties have been set
175
if (dictionaryService == null)
176             throw new AlfrescoRuntimeException("IntegrityChecker property not set: dictionaryService");
177         if (nodeService == null)
178             throw new AlfrescoRuntimeException("IntegrityChecker property not set: nodeService");
179         if (policyComponent == null)
180             throw new AlfrescoRuntimeException("IntegrityChecker property not set: policyComponent");
181
182         if (enabled) // only register behaviour if integrity checking is on
183
{
184             // register behaviour
185
policyComponent.bindClassBehaviour(
186                     QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"),
187                     this,
188                     new JavaBehaviour(this, "onCreateNode"));
189             policyComponent.bindClassBehaviour(
190                     QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"),
191                     this,
192                     new JavaBehaviour(this, "onUpdateProperties"));
193             policyComponent.bindClassBehaviour(
194                     QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteNode"),
195                     this,
196                     new JavaBehaviour(this, "onDeleteNode"));
197             policyComponent.bindClassBehaviour(
198                     QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"),
199                     this,
200                     new JavaBehaviour(this, "onAddAspect"));
201             policyComponent.bindClassBehaviour(
202                     QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"),
203                     this,
204                     new JavaBehaviour(this, "onRemoveAspect"));
205             policyComponent.bindAssociationBehaviour(
206                     QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateChildAssociation"),
207                     this,
208                     new JavaBehaviour(this, "onCreateChildAssociation"));
209             policyComponent.bindAssociationBehaviour(
210                     QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteChildAssociation"),
211                     this,
212                     new JavaBehaviour(this, "onDeleteChildAssociation"));
213             policyComponent.bindAssociationBehaviour(
214                     QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateAssociation"),
215                     this,
216                     new JavaBehaviour(this, "onCreateAssociation"));
217             policyComponent.bindAssociationBehaviour(
218                     QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteAssociation"),
219                     this,
220                     new JavaBehaviour(this, "onDeleteAssociation"));
221         }
222     }
223     
224     /**
225      * Ensures that this service is registered with the transaction and saves the event
226      *
227      * @param event
228      */

229     @SuppressWarnings JavaDoc("unchecked")
230     private void save(IntegrityEvent event)
231     {
232         // optionally set trace
233
if (traceOn)
234         {
235             // get a stack trace
236
Throwable JavaDoc t = new Throwable JavaDoc();
237             t.fillInStackTrace();
238             StackTraceElement JavaDoc[] trace = t.getStackTrace();
239             
240             event.addTrace(trace);
241             // done
242
}
243         
244         // register this service
245
AlfrescoTransactionSupport.bindIntegrityChecker(this);
246         
247         // get the event list
248
Map JavaDoc<IntegrityEvent, IntegrityEvent> events =
249             (Map JavaDoc<IntegrityEvent, IntegrityEvent>) AlfrescoTransactionSupport.getResource(KEY_EVENT_SET);
250         if (events == null)
251         {
252             events = new HashMap JavaDoc<IntegrityEvent, IntegrityEvent>(113, 0.75F);
253             AlfrescoTransactionSupport.bindResource(KEY_EVENT_SET, events);
254         }
255         // check if the event is present
256
IntegrityEvent existingEvent = events.get(event);
257         if (existingEvent != null)
258         {
259             // the event (or its equivalent is already present - transfer the trace
260
if (traceOn)
261             {
262                 existingEvent.getTraces().addAll(event.getTraces());
263             }
264         }
265         else
266         {
267             // the event doesn't already exist
268
events.put(event, event);
269         }
270         if (logger.isDebugEnabled())
271         {
272             logger.debug("" + (existingEvent != null ? "Event already present in" : "Added event to") + " event set: \n" +
273                     " event: " + event);
274         }
275     }
276
277     /**
278      * @see PropertiesIntegrityEvent
279      */

280     public void onCreateNode(ChildAssociationRef childAssocRef)
281     {
282         IntegrityEvent event = null;
283         // check properties on child node
284
event = new PropertiesIntegrityEvent(
285                 nodeService,
286                 dictionaryService,
287                 childAssocRef.getChildRef());
288         save(event);
289         
290         // check target role
291
event = new AssocTargetRoleIntegrityEvent(
292                 nodeService,
293                 dictionaryService,
294                 childAssocRef.getParentRef(),
295                 childAssocRef.getTypeQName(),
296                 childAssocRef.getQName());
297         save(event);
298         
299         // check for associations defined on the new node (child)
300
NodeRef childRef = childAssocRef.getChildRef();
301         QName childNodeTypeQName = nodeService.getType(childRef);
302         ClassDefinition nodeTypeDef = dictionaryService.getClass(childNodeTypeQName);
303         if (nodeTypeDef == null)
304         {
305             throw new DictionaryException("The node type is not recognized: " + childNodeTypeQName);
306         }
307         Map JavaDoc<QName, AssociationDefinition> childAssocDefs = nodeTypeDef.getAssociations();
308         
309         // check the multiplicity of each association with the node acting as a source
310
for (AssociationDefinition assocDef : childAssocDefs.values())
311         {
312             QName assocTypeQName = assocDef.getName();
313             // check target multiplicity
314
event = new AssocTargetMultiplicityIntegrityEvent(
315                     nodeService,
316                     dictionaryService,
317                     childRef,
318                     assocTypeQName,
319                     false);
320             save(event);
321         }
322     }
323
324     /**
325      * @see PropertiesIntegrityEvent
326      */

327     public void onUpdateProperties(
328             NodeRef nodeRef,
329             Map JavaDoc<QName, Serializable JavaDoc> before,
330             Map JavaDoc<QName, Serializable JavaDoc> after)
331     {
332         IntegrityEvent event = null;
333         // check properties on node
334
event = new PropertiesIntegrityEvent(nodeService, dictionaryService, nodeRef);
335         save(event);
336     }
337
338     /**
339      * No checking performed: The association changes will be handled
340      */

341     public void onDeleteNode(ChildAssociationRef childAssocRef)
342     {
343     }
344
345     /**
346      * @see PropertiesIntegrityEvent
347      */

348     public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName)
349     {
350         IntegrityEvent event = null;
351         // check properties on node
352
event = new PropertiesIntegrityEvent(nodeService, dictionaryService, nodeRef);
353         save(event);
354         
355         // check for associations defined on the aspect
356
AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName);
357         if (aspectDef == null)
358         {
359             throw new DictionaryException("The aspect type is not recognized: " + aspectTypeQName);
360         }
361         Map JavaDoc<QName, AssociationDefinition> assocDefs = aspectDef.getAssociations();
362         
363         // check the multiplicity of each association with the node acting as a source
364
for (AssociationDefinition assocDef : assocDefs.values())
365         {
366             QName assocTypeQName = assocDef.getName();
367             // check target multiplicity
368
event = new AssocTargetMultiplicityIntegrityEvent(
369                     nodeService,
370                     dictionaryService,
371                     nodeRef,
372                     assocTypeQName,
373                     false);
374             save(event);
375         }
376     }
377
378     /**
379      * No checking performed: The property changes will be handled
380      */

381     public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName)
382     {
383     }
384
385     public void onCreateChildAssociation(ChildAssociationRef childAssocRef)
386     {
387         IntegrityEvent event = null;
388         // check source type
389
event = new AssocSourceTypeIntegrityEvent(
390                 nodeService,
391                 dictionaryService,
392                 childAssocRef.getParentRef(),
393                 childAssocRef.getTypeQName());
394         save(event);
395         // check target type
396
event = new AssocTargetTypeIntegrityEvent(
397                 nodeService,
398                 dictionaryService,
399                 childAssocRef.getChildRef(),
400                 childAssocRef.getTypeQName());
401         save(event);
402         // check source multiplicity
403
event = new AssocSourceMultiplicityIntegrityEvent(
404                 nodeService,
405                 dictionaryService,
406                 childAssocRef.getChildRef(),
407                 childAssocRef.getTypeQName(),
408                 false);
409         save(event);
410         // check target multiplicity
411
event = new AssocTargetMultiplicityIntegrityEvent(
412                 nodeService,
413                 dictionaryService,
414                 childAssocRef.getParentRef(),
415                 childAssocRef.getTypeQName(),
416                 false);
417         save(event);
418         // check target role
419
event = new AssocTargetRoleIntegrityEvent(
420                 nodeService,
421                 dictionaryService,
422                 childAssocRef.getParentRef(),
423                 childAssocRef.getTypeQName(),
424                 childAssocRef.getQName());
425         save(event);
426     }
427
428     /**
429      * @see CreateChildAssocIntegrityEvent
430      */

431     public void onDeleteChildAssociation(ChildAssociationRef childAssocRef)
432     {
433         IntegrityEvent event = null;
434         // check source multiplicity
435
event = new AssocSourceMultiplicityIntegrityEvent(
436                 nodeService,
437                 dictionaryService,
438                 childAssocRef.getChildRef(),
439                 childAssocRef.getTypeQName(),
440                 true);
441         save(event);
442         // check target multiplicity
443
event = new AssocTargetMultiplicityIntegrityEvent(
444                 nodeService,
445                 dictionaryService,
446                 childAssocRef.getParentRef(),
447                 childAssocRef.getTypeQName(),
448                 true);
449         save(event);
450     }
451
452     /**
453      * @see AbstractAssocIntegrityEvent
454      */

455     public void onCreateAssociation(AssociationRef nodeAssocRef)
456     {
457         IntegrityEvent event = null;
458         // check source type
459
event = new AssocSourceTypeIntegrityEvent(
460                 nodeService,
461                 dictionaryService,
462                 nodeAssocRef.getSourceRef(),
463                 nodeAssocRef.getTypeQName());
464         save(event);
465         // check target type
466
event = new AssocTargetTypeIntegrityEvent(
467                 nodeService,
468                 dictionaryService,
469                 nodeAssocRef.getTargetRef(),
470                 nodeAssocRef.getTypeQName());
471         save(event);
472         // check source multiplicity
473
event = new AssocSourceMultiplicityIntegrityEvent(
474                 nodeService,
475                 dictionaryService,
476                 nodeAssocRef.getTargetRef(),
477                 nodeAssocRef.getTypeQName(),
478                 false);
479         save(event);
480         // check target multiplicity
481
event = new AssocTargetMultiplicityIntegrityEvent(
482                 nodeService,
483                 dictionaryService,
484                 nodeAssocRef.getSourceRef(),
485                 nodeAssocRef.getTypeQName(),
486                 false);
487         save(event);
488     }
489
490     /**
491      * @see AbstractAssocIntegrityEvent
492      */

493     public void onDeleteAssociation(AssociationRef nodeAssocRef)
494     {
495         IntegrityEvent event = null;
496         // check source multiplicity
497
event = new AssocSourceMultiplicityIntegrityEvent(
498                 nodeService,
499                 dictionaryService,
500                 nodeAssocRef.getTargetRef(),
501                 nodeAssocRef.getTypeQName(),
502                 true);
503         save(event);
504         // check target multiplicity
505
event = new AssocTargetMultiplicityIntegrityEvent(
506                 nodeService,
507                 dictionaryService,
508                 nodeAssocRef.getSourceRef(),
509                 nodeAssocRef.getTypeQName(),
510                 true);
511         save(event);
512     }
513     
514     /**
515      * Runs several types of checks, querying specifically for events that
516      * will necessitate each type of test.
517      * <p>
518      * The interface contracts also requires that all events for the transaction
519      * get cleaned up.
520      */

521     public void checkIntegrity() throws IntegrityException
522     {
523         if (!enabled)
524         {
525             return;
526         }
527         
528         // process events and check for failures
529
List JavaDoc<IntegrityRecord> failures = processAllEvents();
530         // clear out all events
531
AlfrescoTransactionSupport.unbindResource(KEY_EVENT_SET);
532         
533         // drop out quickly if there are no failures
534
if (failures.isEmpty())
535         {
536             return;
537         }
538         
539         // handle errors according to instance flags
540
// firstly, log all failures
541
int failureCount = failures.size();
542         StringBuilder JavaDoc sb = new StringBuilder JavaDoc(300 * failureCount);
543         sb.append("Found ").append(failureCount).append(" integrity violations");
544         if (maxErrorsPerTransaction < failureCount)
545         {
546             sb.append(" - first ").append(maxErrorsPerTransaction);
547         }
548         sb.append(":");
549         int count = 0;
550         for (IntegrityRecord failure : failures)
551         {
552             // break if we exceed the maximum number of log entries
553
count++;
554             if (count > maxErrorsPerTransaction)
555             {
556                 break;
557             }
558             sb.append("\n").append(failure);
559         }
560         if (failOnViolation)
561         {
562             logger.error(sb.toString());
563             throw new IntegrityException(failures);
564         }
565         else
566         {
567             logger.warn(sb.toString());
568             // no exception
569
}
570     }
571     
572     /**
573      * Loops through all the integrity events and checks integrity.
574      * <p>
575      * The events are stored in a set, so there are no duplicates. Since each
576      * event performs a particular type of check, this ensures that we don't
577      * duplicate checks.
578      *
579      * @return Returns a list of integrity violations, up to the
580      * {@link #maxErrorsPerTransaction the maximum defined}
581      */

582     @SuppressWarnings JavaDoc("unchecked")
583     private List JavaDoc<IntegrityRecord> processAllEvents()
584     {
585         // the results
586
ArrayList JavaDoc<IntegrityRecord> allIntegrityResults = new ArrayList JavaDoc<IntegrityRecord>(0); // generally unused
587

588         // get all the events for the transaction (or unit of work)
589
// duplicates have been elimiated
590
Map JavaDoc<IntegrityEvent, IntegrityEvent> events =
591                 (Map JavaDoc<IntegrityEvent, IntegrityEvent>) AlfrescoTransactionSupport.getResource(KEY_EVENT_SET);
592         if (events == null)
593         {
594             // no events were registered - nothing of significance happened
595
return allIntegrityResults;
596         }
597
598         // failure results for the event
599
List JavaDoc<IntegrityRecord> integrityRecords = new ArrayList JavaDoc<IntegrityRecord>(0);
600
601         // cycle through the events, performing checking integrity
602
for (IntegrityEvent event : events.keySet())
603         {
604             try
605             {
606                 event.checkIntegrity(integrityRecords);
607             }
608             catch (Throwable JavaDoc e)
609             {
610                 e.printStackTrace();
611                 // log it as an error and move to next event
612
IntegrityRecord exceptionRecord = new IntegrityRecord("" + e.getMessage());
613                 exceptionRecord.setTraces(Collections.singletonList(e.getStackTrace()));
614                 allIntegrityResults.add(exceptionRecord);
615                 // move on
616
continue;
617             }
618
619             // keep track of results needing trace added
620
if (traceOn)
621             {
622                 // record the current event trace if present
623
for (IntegrityRecord integrityRecord : integrityRecords)
624                 {
625                     integrityRecord.setTraces(event.getTraces());
626                 }
627             }
628             
629             // copy all the event results to the final results
630
allIntegrityResults.addAll(integrityRecords);
631             // clear the event results
632
integrityRecords.clear();
633             
634             if (allIntegrityResults.size() >= maxErrorsPerTransaction)
635             {
636                 // only so many errors wanted at a time
637
break;
638             }
639         }
640         // done
641
return allIntegrityResults;
642     }
643 }
644
Popular Tags