# PersistenceFactory.java

    /*
    ** Module   : PersistenceFactory.java
    ** Abstract : Factory for Persistence objects
    **
    ** Copyright (c) 2004-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
    ** 001 ECF 20071018   @35570 Created initial version. Factory for
    **                           Persistence objects. Can create local and
    **                           remote versions.
    ** 002 ECF 20080611   @38698 Disallow creation of a Persistence instance
    **                           for a dirty database. In order to prevent
    **                           programming errors, IllegalArgumentException
    **                           is thrown if this is attempted.
    ** 003 ECF 20131010          Minor documentation update.
    ** 004 AIL 20200812          Initialize the temp-table multiple times (for each per-session database).
    ** 005 ECF 20200906          New ORM implementation.
    **     ECF 20210926          Report UDF version only after UDFs have been installed in an embedded database.
    *      IAS 20220321          Make reportUDFVersion an instance method instead of static one
    ** 006 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    ** 007 DDF 20240322          Close open temporary databases when removing them.
    **     DDF 20240325          Refactored remove() to avoid returning a Persistence instance without
    **                           closing the database.
    ** 008 DDF 20240404          Reverted changes made to remove().
    ** 009 RAA 20240423          Added multi-tenancy support.
    ** 010 SB  20240828          Added getInstance overload to retrieve an unsafe persistence. Refs #8968.
    ** 011 OM  20240901          Improved Database API. Concurrent multitenancy functionality implementation.
    ** 012 DDF 20241002          Close open temporary databases when removing them (removed previously).
    */

    /*
    ** 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.util.*;
    import java.util.logging.*;
    import java.lang.reflect.Proxy;
    import com.goldencode.p2j.persist.dialect.*;
    import com.goldencode.p2j.persist.remote.*;
    import com.goldencode.p2j.util.logging.*;

    /**
     * Factory for {@link Persistence} objects.  Manages a cache of
     * <code>Persistence</code> objects, keyed by {@link Database}.  When an
     * instance is requested via one of the <code>getInstance()</code> method
     * variants, the cache is checked for an existing instance.  If none exists,
     * the factory determines what type of <code>Persistence</code> implementation
     * is necessary for a particular database, instantiates it, initializes it,
     * caches it, and returns it.
     * <p>
     * Currently, this factory knows how to generate instances of:
     * <ul>
     *   <li>{@link Persistence} - used for locally managed databases.
     *   <li>{@link com.goldencode.p2j.persist.remote.RemotePersistence
     *       RemotePersistence} - used for remotely managed databases.
     * </ul>
     * <p>
     * Persistence objects can be removed from the cache by other persistence
     * framework classes via the {@link #remove(Database)} method.
     *
     * @author ECF
     */
    public final class PersistenceFactory
    {
       /** Logger (shared with {@link Persistence} class) */
       private static final CentralLogger log = CentralLogger.get(Persistence.class.getName());

       /** Cache of <code>Persistence</code> instances by database */
       private static final Map<Database, Persistence> cache = new HashMap<>();

       /**
        * Get the {@link Persistence} instance associated with the given, physical database name,
        * creating it first if necessary. Assumes the name specifies a primary database. Newly
        * created instances are stored in a static cache.
        *
        * @param   name
        *          Name of physical database with which the instance is permanently associated.
        *
        * @return  Persistence object associated with the given database.
        */
       public static Persistence getInstance(String name)
       {
          return getInstance(new Database(name), null);
       }

       /**
        * Get the instance of this class associated with the given, physical database, creating it
        * first if necessary. Newly created instances are stored in a static cache.
        *
        * @param   database
        *          Database with which the instance is permanently associated.
        *
        * @return  Persistence object associated with the given database.
        *
        * @throws  IllegalArgumentException
        *          if <code>database</code> represents a dirty database.
        */
       public static Persistence getInstance(Database database)
       {
          return getInstance(database, null);
       }

       /**
        * Get the instance of this class associated with the given, physical database, creating it
        * first if necessary. Newly created instances are stored in a static cache.
        *
        * @param   database
        *          Database with which the instance is permanently associated.
        * @param   cfg
        *          The database configuration in use.
        *
        * @return  Persistence object associated with the given database.
        *
        * @throws  IllegalArgumentException
        *          if <code>database</code> represents a dirty database.
        */
       public static Persistence getInstance(Database database, DatabaseConfig cfg)
       {
          if (database.isDirty())
          {
             throw new IllegalArgumentException(
                "Cannot create a Persistence instance for dirty database '" + database + "'");
          }

          database = database.getDefault();
          Persistence instance;
          synchronized (cache)
          {
             instance = cache.get(database);
             if (instance == null)
             {
                Dialect dialect = DatabaseManager.getDialect(database);
                if (dialect == null)
                {
                   throw new IllegalArgumentException(
                      "Unable to determine database dialect for database " + database);
                }

                instance = database.isLocal()
                         ? new Persistence(database, dialect)
                         : new RemotePersistence(database, dialect);

                try
                {
                   instance.reportUDFVersion(database, cfg);
                }
                catch (PersistenceException exc)
                {
                   if (log.isLoggable(Level.WARNING))
                   {
                      log.log(Level.WARNING, "Database access error", exc);
                   }
                }

                cache.put(database, instance);
             }
          }

          if (database.isTemporary())
          {
             TemporaryDatabaseManager.initializeMyTempDatabase();
          }

          return instance;
       }

       /**
        * Get the {@link Persistence} instance associated with the given, physical database name,
        * creating it first if necessary. Assumes the name specifies a primary database. Newly
        * created instances are stored in a static cache.
        *
        * @param   name
        *          Name of physical database with which the instance is permanently associated.
        *
        * @param   unsafe
        *          If {@code true} a proxy for an unsafe persistence will be returned, else
        *          a normal persistence instance will be returned.
        *
        * @return  Persistence object associated with the given database.
        */
       public static UnsafePersistence getInstance(String name, boolean unsafe)
       {
          UnsafePersistence persistence = getInstance(name);
          if (unsafe)
          {
             persistence = (UnsafePersistence) Proxy.newProxyInstance(
                           persistence.getClass().getClassLoader(),
                           new Class<?>[]{UnsafePersistence.class},
                           new PersistenceInvocationHandler((Persistence) persistence));
          }

          return persistence;
       }

       /**
        * Remove the <code>Persistence</code> instance associated with the given
        * database from the global cache.
        * <p>
        * This method should be called with caution.  It is provided primarily to
        * support transient databases, which are connected explicitly, used for a
        * time by one or more users, then disconnected.
        * <p>
        * For a temporary database that runs in shared mode, it will not be closed
        * because it is reused.
        *
        * @param   database
        *          Database with which the instance is associated.
        *
        * @return  Instance of <code>Persistence</code> which was removed
        */
       static Persistence remove(Database database)
       {
          boolean temporary = database.isTemporary();
          boolean removeDb = temporary && TemporaryDatabaseManager.removeMyTempDatabase();
          if (temporary && TemporaryDatabaseManager.isTempDatabaseCloseable())
          {
             try
             {
                H2Helper.closeDatabase(database);
             }
             catch (PersistenceException e)
             {
                log.log(Level.SEVERE, "Error closing embedded database", e);
             }
          }

          if (!temporary || removeDb)
          {
             synchronized (cache)
             {
                return cache.remove(database);
             }
          }

          return null;
       }
    }