001 package com.sptci.prevayler;
002
003 import com.sptci.ReflectionUtility;
004 import com.sptci.prevayler.annotations.ForeignKey;
005 import com.sptci.prevayler.annotations.ForeignKeys;
006 import com.sptci.prevayler.annotations.NotNull;
007
008 import java.lang.reflect.Field;
009 import java.util.Collection;
010 import java.util.Date;
011 import java.util.Map;
012
013 /**
014 * System that abstracts all constraint enforcing rules for the prevalent
015 * system.
016 *
017 * <p>© Copyright 2008 <a href='http://sptci.com/' target='_top'>Sans Pareil Technologies, Inc.</a></p>
018 * @author Rakesh Vidyadharan 2008-05-23
019 * @version $Id: ConstraintSystem.java 22 2008-11-24 19:04:25Z sptrakesh $
020 */
021 abstract class ConstraintSystem extends IndexSystem
022 {
023 private static final long serialVersionUID = 1L;
024
025 /**
026 * Check the prevalent object specified to ensure that it may be safely
027 * added to the prevalent system.
028 *
029 * @see #checkUnique
030 * @param object The prevalent object to check.
031 * @throws PrevalentException If the checking fails.
032 */
033 protected void preAdd( final PrevalentObject object )
034 throws PrevalentException
035 {
036 checkUnique( object );
037 checkNull( object );
038 }
039
040 /**
041 * Process any delete constraints configured for the specified prevalent
042 * object.
043 *
044 * @see #preDelete( String, PrevalentObject, Date )
045 * @param object The prevalent object that is to be deleted from the system.
046 * @param executionTime The datetime at which the transaction was executed.
047 * @throws PrevalentException If the object cannot be deleted due to
048 * constraints or other considerations.
049 */
050 protected void preDelete( final PrevalentObject object,
051 final Date executionTime ) throws PrevalentException
052 {
053 final RelationStorage relationStorage = getRelationStorage( object.getClass() );
054
055 for ( String className : relationStorage.getRelations() )
056 {
057 preDelete( className, object, executionTime );
058 }
059 }
060
061 /**
062 * Check the foreign key relationship from instances of the specified
063 * class to the prevalent object specified and process as necessary. The
064 * appropriate actions are defined by the {@link
065 * com.sptci.prevayler.annotations.ForeignKey#deleteAction()} value.
066 *
067 * @see #cascadeDelete
068 * @see #nullReference
069 * @param className The prevalent class that holds references to the
070 * prevalent object to be deleted.
071 * @param object The prevalent object to be deleted.
072 * @param executionTime The datetime at which the transaction was executed.
073 * @throws DeleteException If the object cannot be deleted due to references
074 * to it from other prevalent objects.
075 * @throws PrevalentException If the prevalent object cannot be deleted.
076 */
077 protected void preDelete( final String className,
078 final PrevalentObject object, final Date executionTime )
079 throws PrevalentException
080 {
081 final RelationStorage relationStorage = getRelationStorage( object.getClass() );
082
083 for ( Map.Entry<String, ForeignKey.DeleteAction> entry :
084 relationStorage.getDeleteRules( className ).entrySet() )
085 {
086 switch ( entry.getValue() )
087 {
088 case CASCADE:
089 cascadeDelete( className, entry.getKey(), object, executionTime );
090 break;
091 case NULL:
092 nullReference( className, entry.getKey(), object );
093 break;
094 case EXCEPTION:
095 throw new DeleteException( object );
096 }
097 }
098 }
099
100 /**
101 * Cascade delete all objects that hold a reference to the specified
102 * prevalent object.
103 *
104 * @see IndexStorage#get(String, Object)
105 * @see #fetch
106 * @see #delete
107 * @param className The fully qualified class name of the prevalent object
108 * that holds a reference to the specified prevalent object that is
109 * being deleted.
110 * @param field The name of the field in <code>className</code> that is
111 * a reference to the prevalent object being deleted.
112 * @param object The prevalent object that is being deleted from the system.
113 * @param executionTime The datetime at which the transaction was executed.
114 * @throws PrevalentException If errors are encountered while deleting the
115 * referencing object.
116 */
117 private void cascadeDelete( final String className, final String field,
118 final PrevalentObject object, final Date executionTime )
119 throws PrevalentException
120 {
121 final IndexStorage indexStorage = getIndexStorage( className );
122
123 for ( IndexedObject obj : indexStorage.get( field, object ) )
124 {
125 final PrevalentObject po = fetch( obj.type, obj.objectId );
126 delete( po, executionTime );
127 }
128 }
129
130 /**
131 * Set the <code>field</code> in <code>className</code> that refers to
132 * <code>object</code> to <code>null</code>. If the <code>field</code>
133 * is a {@link java.util.Collection}, then the <code>object</code> is
134 * removed from it.
135 *
136 * @param className The fully qualified class name of the prevalent object
137 * that holds a reference to the specified prevalent object being deleted.
138 * @param field The field in <code>className</code> that holds a reference
139 * to the prevalent object being deleted.
140 * @param object The prevalent object being deleted from the system.
141 * @throws PrevalentException If errors are encountered while setting
142 * the field to null.
143 */
144 private void nullReference( final String className, final String field,
145 final PrevalentObject object ) throws PrevalentException
146 {
147 final IndexStorage indexStorage = getIndexStorage( className );
148
149 try
150 {
151 for ( IndexedObject obj : indexStorage.get( field, object ) )
152 {
153 final PrevalentObject po = fetch( obj.type, obj.objectId );
154
155 final ReferenceStorage referenceStorage =
156 getReferenceStorage( obj.type );
157 referenceStorage.remove( po, field, object );
158 indexStorage.remove( field, object, po );
159 }
160 }
161 catch ( Throwable t )
162 {
163 throw new PrevalentException( t );
164 }
165 }
166
167 /**
168 * Over-ridden to process any {@link com.sptci.prevayler.annotations.ForeignKeys}
169 * and {@link com.sptci.prevayler.annotations.ForeignKey} annotations.
170 *
171 * @see IndexSystem#checkUnique
172 * @see #checkForeignKeys
173 * @see #checkForeignKey
174 * @param object The prevalent object to check.
175 * @throws ConstraintException If unique constraints are violated.
176 * @throws PrevalentException If errors are encountered while processing
177 * the fields of the prevalent object.
178 */
179 @Override
180 protected void checkUnique( final PrevalentObject object )
181 throws PrevalentException
182 {
183 checkForeignKeys( object );
184
185 final ForeignKey key = object.getClass().getAnnotation( ForeignKey.class );
186 if ( key != null ) checkForeignKey( key, object );
187
188 super.checkUnique( object );
189 }
190
191 /**
192 * Check the {@link com.sptci.prevayler.annotations.ForeignKeys} annotation
193 * for the prevalent class and perform actions as necessary.
194 *
195 * @param object The prevalent object whose annotation is to be used to
196 * check constraints.
197 * @throws ConstraintException If a unique constraint is violated.
198 * @throws PrevalentException If exceptions are encountered while processing
199 * the fields of the prevalent object.
200 */
201 private void checkForeignKeys( final PrevalentObject object )
202 throws PrevalentException
203 {
204 final ForeignKeys keys = object.getClass().getAnnotation( ForeignKeys.class );
205 if ( keys == null ) return;
206
207 for ( ForeignKey key : keys.value() )
208 {
209 checkForeignKey( key, object );
210 }
211 }
212
213 /**
214 * Check the {@link com.sptci.prevayler.annotations.ForeignKey} annotation
215 * and check for unique constraint violations.
216 *
217 * @see #addReference
218 * @param key The annotation to check for unique constraint.
219 * @param object The prevalent object being checked.
220 * @throws ConstraintException If a unique constraint is violated.
221 * @throws PrevalentException If exceptions are encountered while processing
222 * the fields of the prevalent object.
223 */
224 private void checkForeignKey( final ForeignKey key,
225 final PrevalentObject object ) throws PrevalentException
226 {
227 addReference( object, key, key.member() );
228 final IndexStorage indexStorage = getIndexStorage( object.getClass() );
229
230 if ( key.unique() )
231 {
232 Object value;
233
234 try
235 {
236 value = ReflectionUtility.fetchObject( key.member(), object );
237 }
238 catch ( Throwable t )
239 {
240 throw new PrevalentException( t );
241 }
242
243 if ( indexStorage.isIndexed( key.member(), value ) )
244 {
245 throw new ConstraintException( object, key.member() );
246 }
247 }
248 }
249
250 /**
251 * Over-ridden to check for {@link com.sptci.prevayler.annotations.ForeignKey}
252 * annotation and check for unique constraint violations. Super-class
253 * implementation is also applied.
254 *
255 * @see #addReference
256 * @see IndexSystem#checkFields
257 * @param object The prevalent object to check.
258 * @throws ConstraintException If a unique constraint is violated.
259 * @throws PrevalentException If exceptions are encountered while processing
260 * the fields of the prevalent object.
261 */
262 @Override
263 protected void checkFields( final PrevalentObject object )
264 throws PrevalentException
265 {
266 final IndexStorage indexStorage = getIndexStorage( object.getClass() );
267
268 for ( Field field : ReflectionUtility.fetchFields( object ).values() )
269 {
270 final ForeignKey key = field.getAnnotation( ForeignKey.class );
271 if ( key != null )
272 {
273 addReference( object, key, field.getName() );
274
275 if ( key.unique() )
276 {
277 Object value;
278
279 try
280 {
281 value =
282 ReflectionUtility.fetchObject( field.getName(), object );
283 }
284 catch ( Throwable t )
285 {
286 throw new PrevalentException( t );
287 }
288
289 if ( ( value != null ) && value instanceof PrevalentObject )
290 {
291 checkUnique( field, object, (PrevalentObject) value );
292 }
293
294 if ( indexStorage.isIndexed( field.getName(), value ) )
295 {
296 throw new ConstraintException( object, field.getName() );
297 }
298 }
299 }
300 }
301
302 super.checkFields( object );
303 }
304
305 /**
306 * Maintain the reference relationships between the specified prevalent
307 * object and any other prevalent object the object references. This method
308 * adds support for managed relations.
309 *
310 * @param object The prevalent object whose references are to be processed.
311 * @param key The foreign key annotation for the field.
312 * @param name The name of the field that references a prevalent object.
313 * This is necessary since the <code>key</code> may not contain the
314 * name of the field it references if annotated at the field level.
315 * @throws PrevalentException If errors are encountered while accessing the
316 * field in <code>object</code>.
317 */
318 private void addReference( final PrevalentObject object,
319 final ForeignKey key, final String name ) throws PrevalentException
320 {
321 try
322 {
323 final Field field = ReflectionUtility.fetchField( name, object );
324 Class cls = field.getType();
325 if ( Collection.class.isAssignableFrom( cls ) )
326 {
327 cls = Class.forName( key.collectionEntry() );
328 }
329
330 final RelationStorage relationStorage = getRelationStorage( cls );
331 relationStorage.add( object, name, key.deleteAction() );
332 }
333 catch ( Throwable t )
334 {
335 throw new PrevalentException( t );
336 }
337 }
338
339 /**
340 * Check the {@link com.sptci.prevayler.annotations.NotNull} annotation on
341 * the prevalent object and its fields and throw exceptions if necessary.
342 *
343 * @see #checkNull( String, PrevalentObject )
344 * @see #checkNullFields
345 * @param object The prevalent object that is to be verified.
346 * @throws NullException If the field value is <code>null</code>.
347 * @throws PrevalentException If errors are encountered while fetching the
348 * field value.
349 */
350 private void checkNull( final PrevalentObject object )
351 throws PrevalentException
352 {
353 final NotNull annotation = object.getClass().getAnnotation( NotNull.class );
354 if ( annotation != null )
355 {
356 for ( String member : annotation.members() )
357 {
358 checkNull( member, object );
359 }
360 }
361
362 checkNullFields( object );
363 }
364
365 /**
366 * Check the value of the member identified by the name specified in the
367 * prevalent object for null value.
368 *
369 * @param member The name of the field that is to be checked.
370 * @param object The prevalent object to check.
371 * @throws NullException If the field value is <code>null</code>.
372 * @throws PrevalentException If errors are encountered while fetching the
373 * field value.
374 */
375 private void checkNull( final String member, final PrevalentObject object )
376 throws PrevalentException
377 {
378 Object value;
379
380 try
381 {
382 value = ReflectionUtility.fetchObject( member, object );
383 }
384 catch ( Throwable t )
385 {
386 throw new PrevalentException( t );
387 }
388
389 if ( value == null )
390 {
391 throw new NullException( object, member );
392 }
393 }
394
395 /**
396 * Check all the fields in the prevalent object to see if they have been
397 * annotated with the {@link com.sptci.prevayler.annotations.NotNull}
398 * annotation. Annotated fields are checked to ensure that their value is
399 * non-null.
400 *
401 * @param object The prevalent object whose fields are to be checked.
402 * @throws NullException If the field value is <code>null</code>.
403 * @throws PrevalentException If errors are encountered while fetching the
404 * field value.
405 */
406 private void checkNullFields( final PrevalentObject object )
407 throws PrevalentException
408 {
409 try
410 {
411 for ( Field field : ReflectionUtility.fetchFields( object ).values() )
412 {
413 final NotNull ann = field.getAnnotation( NotNull.class );
414 if ( ann != null )
415 {
416 Object value = field.get( object );
417 if ( value == null ) throw new NullException( object, field.getName() );
418 }
419 }
420 }
421 catch ( NullException nex )
422 {
423 throw nex;
424 }
425 catch ( Throwable t )
426 {
427 throw new PrevalentException( t );
428 }
429 }
430
431 /**
432 * Check the specified child prevalent object to see if a unique constraint
433 * is defined on the <code>parent</code> object.
434 *
435 * @param field The field in <code>parent</code> that the child represents.
436 * @param parent The parent prevalent object.
437 * @param child The referenced prevalent object in <code>parent</code>
438 * @throws ConstraintException If a unique constratint violation occurs.
439 */
440 protected void checkUnique( final Field field, final PrevalentObject parent,
441 final PrevalentObject child ) throws ConstraintException
442 {
443 if ( child == null ) return;
444 final IndexStorage indexStorage = getIndexStorage( parent.getClass() );
445 boolean unique = false;
446
447 ForeignKey key = field.getAnnotation( ForeignKey.class );
448 if ( key == null )
449 {
450 key = parent.getClass().getAnnotation( ForeignKey.class );
451 if ( field.getName().equals( key.member() ) )
452 {
453 unique = key.unique();
454 }
455 else
456 {
457 ForeignKeys keys = parent.getClass().getAnnotation( ForeignKeys.class );
458 for ( ForeignKey k : keys.value() )
459 {
460 if ( field.getName().equals( k.member() ) )
461 {
462 unique = k.unique();
463 }
464 }
465 }
466 }
467
468 if ( unique && indexStorage.isIndexed( field.getName(), child ) )
469 {
470 throw new ConstraintException( parent, field.getName() );
471 }
472 }
473
474 /**
475 * Over-ridden to process {@link com.sptci.prevayler.annotations.ForeignKey}
476 * annotations on the fields of the specified prevalent object and manage
477 * the {@link #indexMap} as appropriate. Also processes references from
478 * {@link ReferenceStorage} to ensure that foreign keys are indexed.
479 * Super-class impelementation is also invoked.
480 *
481 * @see IndexSystem#indexFields
482 * @param object The prevalent object to index.
483 * @throws PrevalentException If errors are encountered while processing
484 * the object fields.
485 */
486 @Override
487 protected void indexFields( final PrevalentObject object )
488 throws PrevalentException
489 {
490 final IndexStorage indexStorage = getIndexStorage( object.getClass() );
491 final ReferenceStorage referenceStorage =
492 getReferenceStorage( object.getClass() );
493
494 try
495 {
496 for ( Field field : ReflectionUtility.fetchFields( object ).values() )
497 {
498 final ForeignKey key = field.getAnnotation( ForeignKey.class );
499 if ( key == null ) continue;
500
501 final Object value = referenceStorage.getValue( object, field.getName() );
502 if ( value != null )
503 {
504 if ( value instanceof Collection )
505 {
506 Collection collection = (Collection) value;
507 final PrimaryStorage primaryStorage =
508 getPrimaryStorage( key.collectionEntry() );
509
510 for ( Object oid : collection )
511 {
512 final PrevalentObject po = primaryStorage.get( oid );
513 indexStorage.add( field.getName(), po, object );
514 }
515 }
516 else
517 {
518 indexStorage.add( field.getName(), value, object );
519 }
520 }
521 }
522 }
523 catch ( Throwable t )
524 {
525 throw new PrevalentException( t );
526 }
527
528 super.indexFields( object );
529 }
530
531 /**
532 * Over-ridden to process the foreign key annotations on the prevalent
533 * object and manage the @link StorageSystsem#indexMap} as appropriate. The
534 * {@link ReferenceStorage} is used to fetch the appropriate referenced
535 * prevalent object. The super-class implementation is also invoked.
536 *
537 * @see #processForeignKeys
538 * @see #processForeignKey
539 * @see IndexSystem#indexClass
540 * @param object The prevalent object to process.
541 * @throws com.sptci.prevayler.PrevalentException If errors are encountered
542 * while processing the field values of the prevalent object.
543 */
544 @Override
545 protected void indexClass( final PrevalentObject object )
546 throws PrevalentException
547 {
548 processForeignKeys( object );
549
550 final ForeignKey key = object.getClass().getAnnotation( ForeignKey.class );
551 if ( key != null ) processForeignKey( key, object );
552
553 super.indexClass( object );
554 }
555
556 /**
557 * Process the foreign keys annotation for the prevalent class.
558 *
559 * @see #processForeignKey(com.sptci.prevayler.annotations.ForeignKey, PrevalentObject)
560 * @param object The prevalent object that is to be indexed if necessary.
561 * @throws PrevalentException If errors are encountered while fetching the
562 * field value.
563 */
564 private void processForeignKeys( final PrevalentObject object )
565 throws PrevalentException
566 {
567 final ForeignKeys keys = object.getClass().getAnnotation( ForeignKeys.class );
568 if ( keys == null ) return;
569
570 for ( ForeignKey key : keys.value() )
571 {
572 processForeignKey( key, object );
573 }
574 }
575
576 /**
577 * Process the foreign key annotation for a field declared at the prevalent
578 * class level and manage the {@link StorageSystem#indexMap} as appropriate.
579 *
580 * @param key The annotation being processed.
581 * @param object The prevalent object that is to be indexed if necessary.
582 * @throws PrevalentException If errors are encountered while fetching the
583 * field value.
584 */
585 private void processForeignKey( final ForeignKey key,
586 final PrevalentObject object ) throws PrevalentException
587 {
588 final IndexStorage indexStorage = getIndexStorage( object.getClass() );
589 final ReferenceStorage referenceStorage = getReferenceStorage( object.getClass() );
590
591 try
592 {
593 final Object value = referenceStorage.getValue( object, key.member() );
594 if ( value == null ) return;
595
596 if ( value instanceof Collection )
597 {
598 Collection collection = (Collection) value;
599 final PrimaryStorage primaryStorage =
600 getPrimaryStorage( key.collectionEntry() );
601
602 for ( Object oid : collection )
603 {
604 final PrevalentObject po = primaryStorage.get( oid );
605 indexStorage.add( key.member(), po, object );
606 }
607 }
608 else
609 {
610 indexStorage.add( key.member(), value, object );
611 }
612 }
613 catch ( Throwable t )
614 {
615 throw new PrevalentException( t );
616 }
617 }
618
619 /**
620 * Over-ridden to remove the references to the prevalent object being
621 * removed from the system from the reference storage container.
622 *
623 * {@inheritDoc}
624 */
625 @Override
626 protected void remove( final PrevalentObject object )
627 throws PrevalentException
628 {
629 super.remove( object );
630 final ReferenceStorage referenceStorage =
631 getReferenceStorage( object.getClass() );
632 referenceStorage.remove( object );
633 }
634 }