RecordChangeEvent.java

/*
** Module   : RecordChangeEvent.java
** Abstract : Event sent with DMO state change notifications
**
** Copyright (c) 2004-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description----------------------------------
** 001 ECF 20060719   @28120 Created initial version. Defines the event
**                           sent with DMO state change notifications.
** 002 ECF 20060912   @29483 Fixed defect in constructor. Unsafe to make a
**                           deep copy of snapshot DMO when DMO has been
**                           deleted. If DMO has collections, Hibernate
**                           will not be able to initialize them after
**                           record has been deleted.
** 003 ECF 20061207   @31615 Support insert event type. Added isInsert()
**                           method.
** 004 ECF 20070707   @34416 Optimized constructor. It is not necessary to
**                           make a deep copy of the snapshot, since it is
**                           already a copy of the original DMO.
** 005 ECF 20070926   @35245 Added isUpdate() method. Reports whether
**                           event signifies a DMO update.
** 006 ECF 20071130   @36127 Integrated generics.
** 007 ECF 20080814   @39551 Added explicit type field and Type enum.
**                           Previously, we divined the type of event from
**                           its state, but this proved unreliable for
**                           subclasses.
** 008 ECF 20090107   @41013 Added support for bulk delete events. Full
**                           and partial deletes can be differentiated
**                           from one another.
** 009 ECF 20200906          New ORM implementation.
** 010 TJD 20240123          Java 17 compatibility updates
** 011 CA  20250305          Added state-changed 'refresh buffers' event, to force reload of all buffers in  
**                           current context, for the specified DMO.
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.persist.event;

import java.io.*;
import java.util.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.persist.Record;

/**
 * The event sent with DMO state change notifications.  Encapsulates
 * information about the event's source, the original DMO state, the current
 * DMO state, and which properties were modified.  Depending upon the type of
 * event (update, insert, or delete), some of these data may be missing.
 */
public class RecordChangeEvent
implements Serializable
{
   /** Event types */
   public static enum Type
   implements Serializable
   {
      /** Record update event */
      UPDATE,
      
      /** Record insert event */
      INSERT,
      
      /** Record delete event */
      DELETE,
      
      /** Record bulk delete event;  all records deleted */
      FULL_BULK_DELETE,
      
      /** Record bulk delete event;  selected records deleted */
      RESTRICTED_BULK_DELETE,
      
      /**
       * Event raised after a direct JDBC access has been made on a table, to force refresh all buffers in
       * current session.
       */
      BUFFER_REFRESH;
   };
   
   /** Event type */
   private final Type type;
   
   /** Record buffer which generated this event */
   private final transient RecordBuffer source;
   
   /** Changed record, reflects current state of DMO */
   private final Record dmo;
   
   /** Snapshot of the original DMO, before any change was made */
   private final Record snapshot;
   
   /** Map of property names to extents (for extent fields) */
   private final Map<String, List<Integer>> properties;
   
   /**
    * Constructor.
    *
    * @param   type
    *          Event type.
    * @param   source
    *          Record buffer which originated the event.
    * @param   dmo
    *          <b>Post-modification</b> state of the affected DMO (in the
    *          case where the modification is the deletion of the DMO from
    *          the database, this will be the object instance which was
    *          deleted).
    * @param   snapshot
    *          A deep copy of the DMO which reflects the state of the DMO as
    *          it was first set into the record buffer.  For a newly created
    *          record, this will contain the primary key assigned to the
    *          record.  All other properties will be set to their default
    *          values.
    * @param   properties
    *          A map whose keys are the names of those properties which have
    *          been modified and whose values are the indexes at which those
    *          values are stored (indexed properties only).  For a simple
    *          property, the matching value will be <code>null</code>.
    */
   public RecordChangeEvent(Type type,
                            RecordBuffer source,
                            Record dmo,
                            Record snapshot,
                            Map<String, List<Integer>> properties)
   {
      this.type = type;
      this.source = source;
      this.dmo = dmo;
      this.snapshot = snapshot;
      this.properties = (properties != null ? Collections.unmodifiableMap(properties) : null);
   }
   
   /**
    * Get the record buffer which is the source of the change event.
    * <p>
    * Note:  this will be <code>null</code> if this event has been serialized
    * and deserialized;  RecordBuffer objects cannot travel.
    *
    * @return  Source record buffer.
    */
   public RecordBuffer getSource()
   {
      return source;
   }
   
   /**
    * Get the DMO type associated with this event.  This corresponds with the
    * DMO implementation class' fully qualified name.
    * 
    * @return  DMO entity name.
    */
   public String getEntity()
   {
      return source.getEntityName();
   }
   
   /**
    * Get the record which represents the <b>post-modification</b> state of
    * the affected DMO (in the case where the modification is the deletion of
    * the DMO from the database, this will be the object instance which was
    * deleted).
    *
    * @return  Modified DMO.
    */
   public Record getDMO()
   {
      return dmo;
   }
   
   /**
    * Get a deep copy snapshot of the DMO which reflects the state of the DMO
    * as it was first set into the record buffer.  For a newly created
    * record, this will contain the primary key assigned to the record.  All
    * other properties will be set to their default values.
    *
    * @return  Snapshot of the unmodified DMO.
    */
   public Record getSnapshot()
   {
      return snapshot;
   }
   
   /**
    * Get a map whose keys are the names of those properties which have been modified and whose values are
    * lists of the indexes at which those values are stored (indexed properties only).  For a simple property,
    * the matching value will be {@code null}.  If the change represented by the event was a deletion of the
    * record, the map will be empty.
    *
    * @return  An unmodifiable map as described above.
    *
    * @see     #isDelete
    */
   public Map<String, List<Integer>> getProperties()
   {
      if (properties == null)
      {
         return Collections.emptyMap();
      }
      
      return properties;
   }
   
   /**
    * Does this event signify a record insert?
    *
    * @return  <code>true</code> if event represents record insert, else
    *          <code>false</code>.
    */
   public boolean isInsert()
   {
      return (type == Type.INSERT);
   }
   
   /**
    * Does this event signify a record delete?
    *
    * @return  <code>true</code> if event represents record delete, else
    *          <code>false</code>.
    */
   public boolean isDelete()
   {
      return (type == Type.DELETE);
   }
   
   /**
    * Does this event signify a bulk record delete?
    *
    * @return  <code>true</code> if event represents bulk record delete, else
    *          <code>false</code>.
    */
   public boolean isBulkDelete()
   {
      return (type == Type.FULL_BULK_DELETE || type == Type.RESTRICTED_BULK_DELETE);
   }
   
   /**
    * Does this event signify a <b>full</b> bulk record delete (all records in
    * a temp table with a given multiplex ID)?
    *
    * @return  <code>true</code> if event represents full bulk record delete,
    *          else <code>false</code>.
    */
   public boolean isFullBulkDelete()
   {
      return (type == Type.FULL_BULK_DELETE);
   }
   
   /**
    * Does this event signify a modification to one or more of a record's
    * properties?
    * 
    * @return  <code>true</code> if event represents record update, else
    *          <code>false</code>.
    */
   public boolean isUpdate()
   {
      return (type == Type.UPDATE);
   }
   
   /**
    * Does this event signify a force buffer refresh, as JDBC direct access was used on the table?
    * 
    * @return  <code>true</code> if event represents buffer refresh, else <code>false</code>.
    */
   public boolean isBufferRefresh()
   {
      return (type == Type.BUFFER_REFRESH);
   }
   
   /**
    * Report whether this change impacts only extent properties, or simple
    * properties as well.  This may be interesting to listeners who do not
    * care about updates to indexed properties.
    *
    * @return  <code>true</code> if change impacts only extent properties;
    *          <code>false</code> if change impacts any simple property in
    *          the affected DMO.
    */
   public boolean isExtentFieldChangeOnly()
   {
      Iterator<?> iter = getProperties().values().iterator();
      while (iter.hasNext())
      {
         if (iter.next() == null)
         {
            return false;
         }
      }
      
      return true;
   }
}