# RelatedResource.java

    /*
    ** Module   : RelatedResource.java
    ** Abstract : base class for concurrency primitives that are reference counted and thread safe
    **
    ** Copyright (c) 2020, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------Description---------------------------------
    ** 001 GES 20200116 First version.
    */

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

    import java.util.*;
    import java.util.function.*;

    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.security.SecurityManager; // must be explicit, conflicts with Java SM
    import com.goldencode.util.*;
    import com.goldencode.p2j.util.*;

    /**
     * Implements the base class for a named, reference counted concurrency primitive that can be
     * naturally shared across "related" threads.  The core logic for managing create/open and
     * close/destroy is implemented here.  This class is abstract since it does not represent any
     * real resource type, but rather just provides shared logic.
     * <p>
     * All access to instances of these resources MUST only be through static factory methods. This
     * means that each subclass should have a private constructor and should use the {@code create}
     * and {@code open} methods in this class to manage the access.   {@code create} is used to
     * instantiate a new resource of a given name.  Only one resource of that name is possible at
     * any given time.  When a {@code create} has occurred for a given name (once and only once),
     * any other thread needing access must use {@code open} to obtain the instance.  <b>It is very
     * important that these APIs are used for access. DO NOT pass references to these resources to
     * other threads. Doing so will damage the reference counted resource management.</b>  Calling
     * {@code create} for a named resource that already exists will cause an error.
     * <p>
     * The end of a thread's use of a resource is managed using the {@code close} or {@code destroy}
     * methods.  {@code close} is used to decrement the reference count and allow the resource to
     * do some cleanup.  When the last reference uses {@code close}, an implicit {@code destroy}
     * occurs.  At that point, no further usage of the resource is possible and any access will
     * generate an error.  One may also use {@code destroy} directly, but doing so while other
     * threads are accessing the resource will typically release all waiting threads and cause
     * errors for all subsequent access.  It is OK to call {@code create} on a name that has been
     * destroyed.
     * <p>
     * Each subclass must have a unique resource type name. This is used to namespace all resource
     * instances of a given type.  The names are in a format "/resource_type/instance_name" where
     * the instance name must be unique for all instances of the same resource type.  Generally, the
     * following rules are enforced:
     * <ul>
     *    <li> Resource type names must be unique for each subclass.  They must be non-null and
     *         non-empty.
     *    <li> Instance names must be non-null and non-empty.
     *    <li> Slash (/) characters and whitespace may not be included in either resource type names
     *         or in instance names.
     * </ul>
     * There are two modes of usage for these resources.  In local mode the resource may only be used
     * by "related" threads.  A related thread is one that shares the same security account, has its
     * own context (including transaction state) and was started using {@code RUN AS-THREAD}. This
     * mode allows all related threads full access to all operations, without any security manager
     * checks.  In contrast, non-local mode is a resource that can be used by any thread in the same
     * JVM.  These threads can be related threads but they do not need to be related.  In non-local
     * (i.e. cross-session) mode, all access is checked by the security manager to enforce security
     * permissions as defined in the directory.  The {@code ConcurrentResource} security plugin
     * implements these security checks and even related threads will have these access checks
     * enforced.  This non-local mode allows for an arbitrarily complex inter-thread communication
     * pattern to be implemented (across users, batch processes, appserver agents, REST agents...)
     * but at the cost of additional configuration and the runtime security checks.  If one can
     * implement in local mode it is much simpler so this is often preferred where possible.
     * <p>
     * For non-local resources, the rights are represented by a small set of flags:
     * <ul>
     *   <li> create - the subject may instantiate this resource (e.g. call {@code create})
     *   <li> delete - the subject may delete this resource (e.g. call {@code destroy})
     *   <li> read - the state or data associated with an existing runtime instance can be accessed
     *        by this subject
     *   <li> write - the state or data associated with an existing runtime instance can be modified
     *        by this subject
     *   <li> denied - no access is allowed (overrides any other access flags)
     * </ul>
     */
    public abstract class RelatedResource
    implements BitFlagsConstants
    {
       /** Cross-session (JVM-wide) registry of resources. */
       private static final HashMap shared = new HashMap<String, RelatedResource>();

       /** Hidden key used for storing/retrieving values between related threads. */
       private static final Object key = new Object();

       /** SecurityManager singleton instance. */
       private static final SecurityManager sm = SecurityManager.getInstance();

       /** Track how many threads are referencing this resource. */
       private long references = 1;

       /** Local or non-local resource. */
       private boolean local;

       /** Flag to identify a destroyed resource. */
       protected boolean active = true;

       /** The name of this resource. */
       protected final String name;

       /** Used to lock access to this instance. */
       protected final Object lock = new Object();

       /**
        * Constructor.
        *
        * @param    name
        *           Resource name, must not be {@code null}.
        */
       protected RelatedResource(String name)
       {
          this.name = name;
       }

       /**
        * Factory method which creates a new instance with the given name.
        * <p>
        * In non-local mode, this requires CREATE access.
        *
        * @param    prefix
        *           The non-null, non-empty resource type name which is a constant for all resources
        *           of the same type.  It MUST NOT contain any "/" character.
        * @param    iname
        *           The instance name, excluding the leading "/resource_type/" prefix.  It MUST NOT
        *           contain any "/" character or whitespace.
        * @param    creator
        *           Code for delegated creation of the resource.
        * @param    local
        *           {@code true} to create a local instance (one that is only accessible between
        *           threads in the same security context.  {@code false} creates an instance that
        *           is accessible from any session in the JVM.
        *
        * @return   The instance if it was created.
        *
        * @throws   ErrorConditionException
        *           If there was any problem in creation.
        */
       protected static RelatedResource create(String                                      prefix,
                                               String                                      iname,
                                               Function<String, ? extends RelatedResource> creator,
                                               boolean                                     local)
       throws ErrorConditionException
       {
          String name = RelatedResource.formatName(prefix, iname);

          // this will throw an error if bad
          checkName(prefix, name);

          // non-local needs C access right, local is always allowed as long as the name is valid
          if (!local && !checkAccess(name, BIT_CREATE_ACCESS))
          {
             throw new ErrorConditionException("CREATE access NOT allowed for non-local resource '" +
                                               name + "'.");
          }

          RelatedResource instance = null;

          synchronized (key)
          {
             HashMap<String, RelatedResource> registry = obtain(local);

             // confirm it does not already exist
             if (registry.containsKey(name))
             {
                throw new ErrorConditionException((local ? "Local" : "Non-local") + " resource '" +
                                                  name + "' already exists.");
             }

             // create new instance with the correct state; since it cannot be referenced (yet) by
             // any other thread, we can change the state without locking explicitly
             instance = creator.apply(name);
             instance.local = local;

             // store it
             registry.put(name, instance);
          }

          return instance;
       }

       /**
        * Opens an existing instance with the given name.
        * <p>
        * In non-local mode, this requires READ access.
        *
        * @param    prefix
        *           The non-null, non-empty resource type name which is a constant for all resources
        *           of the same type.  It MUST NOT contain any "/" character.
        * @param    iname
        *           The instance name, excluding the leading "/resource_type/" prefix.  It MUST NOT
        *           contain any "/" character or whitespace.
        * @param    local
        *           {@code true} to open a local instance (one that is only accessible between
        *           threads in the same security context.  {@code false} opens an instance that
        *           is accessible from any session in the JVM.
        *
        * @return   The instance if it exists and was opened.
        *
        * @throws   ErrorConditionException
        *           If there was any problem in opening.
        */
       protected static RelatedResource open(String prefix, String iname, boolean local)
       throws ErrorConditionException
       {
          String name = RelatedResource.formatName(prefix, iname);

          // this will throw an error if bad
          checkName(prefix, name);

          // non-local needs R access right, local is always allowed as long as the name is valid
          if (!local && !checkAccess(name, BIT_READ_ACCESS))
          {
             throw new ErrorConditionException("READ access NOT allowed for non-local resource '" +
                                               name + "'.");
          }

          RelatedResource instance = null;

          synchronized (key)
          {
             HashMap<String, RelatedResource> registry = obtain(local);

             instance = registry.get(name);

             // confirm it exists
             if (instance == null)
             {
                throw new ErrorConditionException((local ? "Local" : "Non-local") + " resource '" +
                                                  name + "' DOES NOT exist.");
             }

             // increment the reference count
             instance.increment();
          }

          return instance;
       }

       /**
        * Raise an error if the resource is non-local AND the current context does not have READ
        * access to this resource name.
        *
        * @throws   ErrorConditionException
        *           If non-local and access is denied.
        */
       public void checkRead()
       throws ErrorConditionException
       {
          checkWorker(BIT_READ_ACCESS, "READ");
       }

       /**
        * Raise an error if the resource is non-local AND the current context does not have WRITE
        * access to this resource name.
        *
        * @throws   ErrorConditionException
        *           If non-local and access is denied.
        */
       public void checkWrite()
       throws ErrorConditionException
       {
          checkWorker(BIT_WRITE_ACCESS, "WRITE");
       }

       /**
        * Raise an error if the resource is non-local AND the current context does not have DELETE
        * access to this resource name.
        *
        * @throws   ErrorConditionException
        *           If non-local and access is denied.
        */
       public void checkDelete()
       throws ErrorConditionException
       {
          checkWorker(BIT_DELETE_ACCESS, "DELETE");
       }

       /**
        * Release all waiting threads and delete the resource.  Any waiting threads will see the
        * {@code WaitStatus.DESTROYED}.
        * <p>
        * In non-local mode, this requires DELETE access.
        *
        * @throws   ErrorConditionException
        *           If there was any problem in destroying the resource.
        */
       public void destroy()
       throws ErrorConditionException
       {
          errorIfDead();
          checkDelete();

          // we know that we are going to modify the registry, so we need to exclude others
          synchronized (key)
          {
             synchronized (lock)
             {
                references = 0;
                cleanup();

                release();
             }
          }
       }

       /**
        * Close the current thread's access to this resource.  This will decrement the reference
        * count.  If this is the last thread referencing the resource, then the resource will be
        * destroyed and no further usage will be possible without re-creating the resource.
        * <p>
        * In non-local mode, this requires READ access.
        *
        * @throws   ErrorConditionException
        *           If there was any problem in closing the resource.
        */
       public void close()
       throws ErrorConditionException
       {
          errorIfDead();
          checkRead();

          // we MAY need to modify the registry, thus we must exclude access
          synchronized (key)
          {
             synchronized (lock)
             {
                if (!decrement())
                {
                   cleanup();
                }
             }
          }
       }

       /**
        * Reports on the status of the resource.
        * <p>
        * In non-local mode, this requires READ access.
        *
        * @return   {@code WaitStatus.OK} or {@code WaitStatus.DESTROYED}.
        */
       public WaitStatus status()
       {
          // don't error on dead, one should be able to safely check status anytime
          checkRead();

          WaitStatus status = WaitStatus.OK;

          synchronized (lock)
          {
             if (!active)
             {
                status = WaitStatus.DESTROYED;
             }
          }

          return status;
       }

       /**
        * Obtain the resource name (which makes up the prefix for valid names of this resource type).
        *
        * @return   The non-null, non-empty resource type without spaces or '/' characters. This
        *           must be the same for all resources of this type.
        */
       public abstract String resourceName();

       /**
        * Check with the security manager to see if the current context has the listed access to the
        * named resource.  If the ConcurrentResource security plugin is not registered, all access is
        * DISALLOWED.
        *
        * @param    name
        *           Resource name to check.
        * @param    perms
        *           The access type requested (see {@code BitFlagsConstants}.
        *
        * @return   {@code true} if the access is allowed.
        */
       protected static boolean checkAccess(String name, int perms)
       {
          ConcurrentResource ccr = (ConcurrentResource) sm.getPluginInstance("ConcurrentResource");

          return (ccr != null && ccr.accessWorker(name, perms));
       }

       /**
        * Confirm that the inputs are non-null and non-empty, then format as a properly namespaced
        * resource name (in /resource_name/instance_name format).
        *
        * @param    prefix
        *           The non-null, non-empty resource type name which is a constant for all resources
        *           of the same type.  It MUST NOT contain any "/" character.
        * @param    instance
        *           The instance name, excluding the leading "/resource_type/" prefix.  It MUST NOT
        *           contain any "/" character or whitespace.
        *
        * @return   The properly namespaced name.
        *
        * @throws   ErrorConditionException
        *           If the inputs are invalid.
        */
       protected static String formatName(String prefix, String instance)
       throws ErrorConditionException
       {
          if (prefix == null || prefix.length() == 0)
          {
             throw new ErrorConditionException("Resource name cannot be null or empty.");
          }

          if (instance == null || instance.length() == 0)
          {
             throw new ErrorConditionException("Instance name cannot be null or empty.");
          }

          return "/" + prefix + "/" + instance;
       }

       /**
        * Check the name raise an error if it is not valid.  The proper format is
        * "/resource_type_name/instance_name".  The instance name portion MUST NOT contain any "/"
        * character or whitespace.
        *
        * @param    prefix
        *           The non-null, non-empty resource type name which is a constant for all resources
        *           of the same type.  It MUST NOT contain any "/" character.
        * @param    name
        *           The fully formatted name to check.
        *
        * @throws   ErrorConditionException
        *           If the inputs are invalid.
        */
       protected static void checkName(String prefix, String name)
       throws ErrorConditionException
       {
          if (name == null || name.length() == 0)
          {
             throw new ErrorConditionException("Names cannot be null or empty.");
          }

          String[] parts = name.split("/");

          // if name is valid input with format "/resource_type_name/instance_name", the result
          // will be an array with 3 elements:
          // ""
          // "resource_type_name"
          // "instance_name"

          if (parts.length != 3)
          {
             throw new ErrorConditionException("Resource name must have 2 segments (" +
                                               "/resource_name/instance_name).");
          }

          if (parts[0].length() != 0)
          {
             throw new ErrorConditionException("Resource name must start with slash " +
                                               "instead of '" + parts[0] + "'.");
          }

          if (parts[1].length() == 0 || !parts[1].equals(prefix))
          {
             throw new ErrorConditionException("Resource type must be '" + prefix + "' " +
                                               "instead of '" + parts[1] + "'.");
          }

          if (parts[2].length() == 0 || StringHelper.containsWhitespace(parts[2]))
          {
             throw new ErrorConditionException("Resource type must be non-empty and without " +
                                               "whitespace (not '" + parts[2] + "').");
          }
       }

       /**
        * Raise an error if the resource has already been destroyed (and thus it cannot be used).
        *
        * @throws   ErrorConditionException
        *           If the active flag is {@code false}.
        */
       protected void errorIfDead()
       throws ErrorConditionException
       {
          synchronized (lock)
          {
             if (!active)
             {
                throw new ErrorConditionException("Resource " + name + " is dead.");
             }
          }
       }

       /**
        * Release all waiting threads.  By default this just calls {@code lock.notifyAll()}.
        * Override this if the blocking mechanism is NOT using the {@code lock} object
        * monitor.
        */
       protected void release()
       {
          synchronized (lock)
          {
             lock.notifyAll();
          }
       }

       /**
        * Access the internal storage used to map names to resources based on whether this is a local
        * or non-local request.  If this is a local resource request and there is no storage
        * allocated yet, then the storage will be allocated before this returns.
        *
        * @param    local
        *           {@code true} if this is a resource that is private to a set of related threads.
        *           {@code false} for resources that can be shared JVM-wide.
        *
        * @return   The storage.
        */
       private static HashMap<String, RelatedResource> obtain(boolean local)
       {
          HashMap<String, RelatedResource> storage = shared;

          if (local)
          {
             storage = (HashMap<String, RelatedResource>) RelatedThread.get(key);

             if (storage == null)
             {
                storage = new HashMap<String, RelatedResource>();

                RelatedThread.set(key, storage);
             }
          }

          return storage;
       }

       /**
        * Cleanup all resources associated with this instance and set the status to destroyed.
        *
        * @throws   ErrorConditionException
        *           If there was any problem in cleanup of the resources.
        */
       private void cleanup()
       throws ErrorConditionException
       {
          synchronized (lock)
          {
             if (references != 0)
             {
                throw new ErrorConditionException("Cannot cleanup resource that is still " +
                                                  "referenced (" + Long.toString(references) +
                                                  " references).");
             }

             synchronized (key)
             {
                // remove from the registry
                HashMap<String, RelatedResource> storage = obtain(local);
                storage.remove(name);
             }

             active = false;
          }
       }

       /**
        * Increment the reference count.
        */
       private void increment()
       {
          synchronized (lock)
          {
             references++;
          }
       }

       /**
        * Decrement the reference count.
        *
        * @return   {@code true} if there are still active references.
        */
       private boolean decrement()
       {
          synchronized (lock)
          {
             references--;

             return (references > 0);
          }
       }

       /**
        * Raise an error if the resource is non-local AND the current context does not have the
        * requested access to this resource name.
        *
        * @param    perms
        *           The permissions needed (see {@code BitFlagsConstants}).
        * @param    descr
        *           The permission name for the error message.
        *
        * @throws   ErrorConditionException
        *           If non-local and access is denied.
        */
       private void checkWorker(int perms, String descr)
       throws ErrorConditionException
       {
          if (!local && !checkAccess(name, perms))
          {
             throw new ErrorConditionException(descr + " access NOT allowed for non-local resource" +
                                               " '" + name + "'.");
          }
       }
    }