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();
      }
   }
}