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>&copy; 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    }