# InverseSyncher.java

    /*
    ** Module   : InverseSyncher.java
    ** Abstract : Synchronizes a foreign association based on changes made to the
    **            referent DMO
    **
    ** Copyright (c) 2006-2020, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ---------------------------------Description----------------------------------
    ** 001 ECF 20061113   @31138 Created initial version. Automatically
    **                           synchronizes the keys of a foreign
    **                           association based on changes made to legacy
    **                           key properties in the referent DMO.
    ** 002 ECF 20071219   @36578 Changed use of superclass' acquireLock()
    **                           method to reflect new signature.
    ** 003 GES 20090421   @41824 Matched utility class package change.
    ** 004 ECF 20131028          Import change.
    ** 005 OM  20191101          Used new persistence API.
    ** 006 ECF 20200919          API change.
    ** 007 ES  20250407          Use timeout parameter in the call to Persistence.load.
    */

    /*
    ** 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;

    import java.lang.reflect.*;
    import java.util.*;
    import com.goldencode.p2j.persist.lock.*;
    import com.goldencode.util.*;
    import com.goldencode.p2j.persist.orm.*;

    /**
     * Automatically synchronizes both ends of a foreign key association, based
     * upon changes made to the values of DMO properties which participate in a
     * legacy foreign key.  This class drives synchronization when it is
     * triggered by changes to the DMO at the referent, or "inverse", or foreign
     * end of the association.  In P2J, the association is actually
     * bidirectional;  however, in the database schema, the end of the
     * association which stores the foreign key determines which end is the
     * referencing end and which is the referent end.
     *
     * @see  AssociationSyncher
     * @see  ForeignResolver
     * @see  RelationInfo
     * @see  RecordBuffer
     */
    final class InverseSyncher
    extends AssociationSyncher
    {
       /** Object which resolves a list of DMO IDs from legacy keys */
       private final ForeignResolver resolver;

       /**
        * Constructor.
        *
        * @param   buffer
        *          Record buffer containing the DMO whose property change(s)
        *          will trigger synchronization attempts.
        * @param   info
        *          Foreign relation descriptor object.
        */
       InverseSyncher(RecordBuffer buffer, RelationInfo info)
       {
          super(buffer, info, info.getLocalClass());

          resolver = new ForeignResolver(info, buffer, true);
       }

       /**
        * Retrieve the set of property names which represent the columns which
        * comprise the legacy key joining two records.  Changes to these
        * properties trigger synchronization of the two ends of a foreign key
        * association.  A set is returned because legacy keys are often
        * composites of multiple columns.
        *
        * @return  Set of property names comprising the legacy key.  Although
        *          they should generally be the same as those of the referencing
        *          end of the association, this implementation returns the
        *          property names associated with the referent end.
        */
       protected Set<String> getLegacyKeys()
       {
          return resolver.getInfo().getForeignProperties();
       }

       /**
        * The foreign end of an association has changed.  Find the old local
        * DMOs it referenced.  If different than the new local DMOs provided,
        * null out the foreign DMO reference from each of the old local DMOs.
        *
        * @param   newLocalDMOs
        *          Set of zero or more local DMOs which are about to be updated
        *          with the new association.
        * @param   newForeignDMO
        *          Foreign DMO which is about to be updated with the new
        *          association.
        *
        * @throws  IllegalAccessException
        *          if an access error occurs while invoking a method using
        *          reflection.
        * @throws  InvocationTargetException
        *          if a method invoked using reflection throws an exception.
        */
       protected void preSynch(Set<Record> newLocalDMOs, Record newForeignDMO)
       throws IllegalAccessException,
              InvocationTargetException
       {
          Record foreignDMO = buffer.getCurrentRecord();
          Set<Record> oldLocalDMOs = helper.getLocalDMOs(foreignDMO);
          Set<Record> copy = new HashSet<>(oldLocalDMOs);
          copy.removeAll(newLocalDMOs);

          // copy now contains only those DMOs which no longer should reference
          // the foreign DMO.  Null out its reference in each of them.
          Iterator<Record> iter = copy.iterator();
          while (iter.hasNext())
          {
             Record next = iter.next();
             foreignSetter.invoke(next, new Object[] { null });
          }
       }

       /**
        * Retrieve the set of DMOs which comprise the local end of the foreign
        * association.  The records are resolved using the new property values of
        * the underlying buffer's current record.  This method uses {@link
        * #acquireLock} to lock the records before they are retrieved.
        * <p>
        * If the association is one-to-one, the set will contain zero or one
        * records.
        * <p>
        * If the association is one-to-many, the set may contain zero or more
        * records.
        *
        * @return  Set of newly resolved local DMOs.  The set may be empty, but
        *          it will not be <code>null</code>.
        *
        * @throws  PersistenceException
        *          if there are any errors retrieving DMOs from the database.
        */
       protected Set<Record> retrieveLocalDMOs()
       throws PersistenceException
       {
          List<Long> recordIDs = resolver.resolveLocal(buffer);
          if (recordIDs == null || recordIDs.isEmpty())
          {
             return new EmptySet<>();
          }

          Persistence persistence = buffer.getPersistence();
          Set<Record> localDMOs = new HashSet<>();

          Iterator<Long> iter = recordIDs.iterator();
          while (iter.hasNext())
          {
             Long id = iter.next();
             acquireLock(id, LockType.EXCLUSIVE);
          }

          Class<? extends Record> localClass = resolver.getInfo().getLocalClass();
          iter = recordIDs.iterator();
          while (iter.hasNext())
          {
             Long id = iter.next();
             long timeout = persistence.getTimeOut();
             Record dmo = persistence.load(localClass, id, LockType.NONE, timeout, false);
             localDMOs.add(dmo);
          }

          return localDMOs;
       }

       /**
        * Retrieve the DMO at the foreign end of the association, which coincides
        * with the DMO whose property changes triggered synchronization.  Because
        * these changes would not have been possible without an exclusive record
        * lock, this method assumes one has been acquired and does not attempt to
        * acquire a lock.
        *
        * @return  Foreign DMO.
        *
        * @throws  PersistenceException
        *          if there are any errors retrieving the DMO from the database.
        */
       protected Record retrieveForeignDMO()
       throws PersistenceException
       {
          return buffer.getCurrentRecord();
       }
    }