# DirtyShareFactory.java

    /*
    ** Module   : DirtyShareFactory.java
    ** Abstract : Factory for DirtyShareContext and DirtyShareManager instances
    **
    ** Copyright (c) 2004-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- -JPRM- ------------------------------------Description------------------------------------
    ** 001 ECF 20080403 @38630 Created initial version. Factory for DirtyShareContext and
    **                         DirtyShareManager instances.
    ** 002 SVL 20080806 @39309 Added sessions mapping and functions for deregistration.
    ** 003 CA  20140513        Added a weight for the context-local var, to ensure predetermined order during
    **                         context reset.
    ** 004 OM  20200602        New ORM implementation.
    ** 005 SVL 20130116        Dirty sharing can be disabled in the directory.
    ** 006 GBB 20230512        Logging methods replaced by CentralLogger/ConversionStatus.
    ** 007 SVL 20230516        Support for more granular configuration of dirty sharing functionality.
    ** 008 DDF 20230627        Replaced static variables of DirtyShareSupport with static method calls.
    ** 009 OM  20240909        Improved Database API.
    */

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

    import java.util.*;
    import com.goldencode.p2j.net.*;
    import com.goldencode.p2j.persist.*;
    import com.goldencode.p2j.persist.remote.*;
    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.util.logging.*;

    /**
     * Factory for {@link DirtyShareContext} and {@link DirtyShareManager} instances. The manager keeps a cache of
     * one of these instances for each database and provides the values at request until they are expressly
     * unregistered.
     */
    public final class DirtyShareFactory
    {
       /** Cache of {@link DefaultDirtyShareManager} instances by database */
       private static final Map<Database, DirtyShareManager> cache = new HashMap<>();

       /** Map of DirtyShareManager to Session instances. */
       private static final Map<DirtyShareManager, Session> sessions = new HashMap<>();

       /** Context local map of Databases to DirtyShareContext instances */
       private static final ContextLocal<Map<Database, DirtyShareContext>> local =
          new ContextLocal<Map<Database, DirtyShareContext>>()
       {
          public WeightFactor getWeight()
          {
             return WeightFactor.LEVEL_7;
          }

          protected Map<Database, DirtyShareContext> initialValue()
          {
             return new HashMap<>();
          }

          protected void cleanup(Map<Database, DirtyShareContext> map)
          {
             for (DirtyShareContext ctx : map.values())
             {
                ctx.cleanup();
             }
          }
       };

       /**
        * Get the dirty share context for the given, primary database.  This method will first check a local cache
        * for the context.  If not present in the cache, the context is created and cached for future retrievals.
        * For the temporary table database and when PRIMARY_NON_DIRTY is specified, {@code null} is returned.
        *
        * @param   database
        *          Primary database which is the key for the dirty share context in the local cache. Use
        *          {@code PRIMARY_NON_DIRTY} database type to request a NOP context for primary permanent tables
        *          that are marked as non DIRTY-READ.
        *
        * @return  Dirty share context associated with the given database.
        */
       public static DirtyShareContext getContextInstance(Database database)
       {
          if (!(DirtyShareSupport.isEnabledCrossSession() || DirtyShareSupport.isEnabledIntraSession()) ||
              database.getType() == Database.Type.PRIMARY_NON_DIRTY ||
              database.isTemporary())
          {
             return null;
          }

          Map<Database, DirtyShareContext> map = local.get();
          DirtyShareContext ctx = map.get(database);
          if (ctx == null)
          {
             ctx = new DirtyShareContext(getManagerInstance(database));

             map.put(database, ctx);
          }

          return ctx;
       }

       /**
        * Removes the dirty share share context for the given database from the local cache, if any.
        *
        * @param   database
        *          Primary database which is the key for the dirty share context in the local cache.
        */
       public static void unregisterContextInstance(Database database)
       {
          Map<Database, DirtyShareContext> map = local.get();

          if (map.remove(database) == null)
          {
             CentralLogger.get(DirtyShareFactory.class)
                .info(database + " could not be unregistered from " + "DirtyShareFactory");
          }
       }

       /**
        * Get the dirty share manager for the given, primary database. This method will first check a shared cache
        * for the manager.  If not present in the cache, the manager is created and cached for future retrievals.
        * For local databases, an instance of {@link DefaultDirtyShareManager} is returned.  For remote databases,
        * an instance of {@link com.goldencode.p2j.persist.remote.RemoteDirtyShareManager} is returned.
        *
        * @param   database
        *          Primary database which is the key for the dirty share manager in the shared cache.  Use
        *          {@code PRIMARY_NON_DIRTY} database type to request a NOP context for primary permanent tables
        *          that are marked as non DIRTY-READ.
        *
        * @return  Dirty share manager associated with the given database.
        */
       public static DirtyShareManager getManagerInstance(Database database)
       {
          if (database.isTemporary() || database.getType() == Database.Type.PRIMARY_NON_DIRTY)
          {
             throw new IllegalArgumentException(
                   "Cannot create DirtyShareManager for temporary or non-dirty table database)");
          }

          synchronized (cache)
          {
             DirtyShareManager dsm = cache.get(database);

             if (dsm == null)
             {
                if (database.isLocal())
                {
                   dsm = new DefaultDirtyShareManager(database);
                }
                else
                {
                   // retrieve network session
                   Session session = ConnectionManager.getSession(database);

                   // get dirty share manager multiplexer proxy
                   DirtyShareMultiplexer multiplexer = (DirtyShareMultiplexer)
                         RemoteObject.obtainNetworkInstance(DirtyShareMultiplexer.class, session);

                   // Instantiate the remote dirty share manager.
                   RemoteDirtyShareManager rdsm = new RemoteDirtyShareManager(multiplexer);
                   rdsm.setDatabase(database);
                   dsm = rdsm;

                   sessions.put(dsm, session);
                }
                cache.put(database, dsm);
             }

             return dsm;
          }
       }

       /**
        * Determines whether the given session for the given database is used by the dirty share manager for this
        * database.
        *
        * @param   database
        *          Primary database which is the key for the dirty share manager in the shared cache.
        * @param   session
        *          Candidate for checking whether it is used by the shared manager.
        *
        * @return  {@code true} of the session is used by the dirty share manager for this database, {@code false}
        *          otherwise.
        */
       public static boolean isUsedByManager(Database database, Session session)
       {
          synchronized (cache)
          {
             DirtyShareManager dsm = cache.get(database);
             return dsm != null && session == sessions.get(dsm);
          }
       }

       /**
        * Removes the dirty share manager for the given database from the shared cache, if any. Also terminates
        * and removes from the cache session used by this manager.
        *
        * @param   database
        *          Primary database which is the key for the dirty share manager in the shared cache.
        */
       public static void unregisterManagerForDatabase(Database database)
       {
          Session session = null;
          synchronized (cache)
          {
             DirtyShareManager dsm = cache.get(database);
             if (dsm != null)
             {
                cache.remove(database);

                session = sessions.remove(dsm);
             }
          }

          if (session != null)
          {
             session.terminate();
          }
       }
    }