FQLHelperCache.java

/*
** Module   : FQLHelperCache.java
** Abstract : Shared cache for FQLHelper object instances
**
** Copyright (c) 2004-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20051007   @23249 Created initial version. Caches FQLHelper objects available in the
**                           current context for faster access to queries.
** 002 ECF 20060120   @24043 Integrated with P2J server. Replaced ThreadLocal with ContextLocal.
** 003 ECF 20060306   @25022 Added support for foreign relation joins. The foreign DMO interface
**                           was added as an additional, optional, secondary key component.
** 004 ECF 20070228   @32245 Added idOnly designation to key. Designates a projection query
**                           returning the primary key ID only.
** 005 ECF 20070827   @35001 Minor optimization. Replace StringBuffer with StringBuilder.
** 006 ECF 20080310   @37476 Added Hibernate types to key.
** 007 ECF 20090204   @41256 Improved performance. Replaced string-based cache key with a faster
**                           implementation.
** 008 ECF 20090205   @41260 Fixed NPE in Key's hashCode() and equals() methods.
** 009 ECF 20150301          Replaced context-local caches with a shared, fixed size cache for a
**                           massive memory savings.
** 010 ECF 20160225          Switched from DMO implementation class to DMO interface as a key
**                           element.
** 011 ECF 20190327          Replaced LFUAgingCache with LRUCache.
** 012 ECF 20200906          New ORM implementation.
** 013 CA  20200930          Cache the hash code value.
**     OM  20221103          Renamed class to FQLHelperCache.
** 014 DDF 20230608          Made the size of the LRUCache configurable.
**     DDF 20230613          Removed the static block that reads the configuration size of the cache.
**                           CacheManager will handle the finding of the configuration size and the
**                           cache creation.
**     DDF 20230627          Made the cache final.
**     DDF 20230706          Replaced createLRUCache call with another one that will use a null 
**                           discriminator by default.
** 015 LS  20250512          Added the included/excluded fields to the Key.
*/

/*
** 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 com.goldencode.cache.*;
import com.goldencode.p2j.persist.orm.Property;

import java.util.Arrays;

/**
 * A cache of {@link FQLHelper} objects mapped to a complex key which encompasses the DMO
 * implementation class, base where clause, Hibernate substitution types, restriction sorting,
 * foreign key information, and whether the query is a project query.
 * <p>
 * An <code>FQLHelper</code> instance is retrieved using the {@link #get} method;  it is stored
 * in the cache using the {@link #put} method.
 */
final class FQLHelperCache
{
   /** Expiry cache of FQL helper objects */
   private final Cache<Key, FQLHelper> cache;
   
   /**
    * Default constructor.
    */
   FQLHelperCache()
   {
      cache = CacheManager.createLRUCache(FQLHelperCache.class, 8192);
   }
   
   /**
    * Attempt to retrieve an {@link FQLHelper} from the cache.
    * 
    * @param   dmoIface
    *          DMO interface associated with the helper. May not be <code>null</code>.
    * @param   types
    *          Hibernate types of query substitution parameters. May be an empty array, but may
    *          not be <code>null</code>.
    * @param   idOnly
    *          Indicates whether query is a projection query returning the primary key ID only.
    * @param   where
    *          Where clause of a query. May be <code>null</code>.
    * @param   sort
    *          Order by clause of a query. May be <code>null</code>.
    * @param   joinIface
    *          Interface DMO associated within query by foreign key.  May be <code>null</code>.
    * @param   included
    *          The included properties.
    * @param   excluded
    *          The excluded properties.
    * 
    * @return  A cached <code>FQLHelper</code> instance or <code>null</code> if an existing
    *          instance was not found in the cache.
    */
   FQLHelper get(Class<? extends DataModelObject> dmoIface,
                 FqlType[] types,
                 boolean idOnly,
                 String where,
                 String sort,
                 Class<? extends DataModelObject> joinIface,
                 Property[] included,
                 Property[] excluded)
   {
      FQLHelper helper = null;
      Key key = new Key(dmoIface, where, types, idOnly, sort, joinIface, included, excluded);
      
      synchronized (cache)
      {
         helper = cache.get(key);
      }
      
      return helper;
   }
   
   /**
    * Store an {@link FQLHelper} in the cache.
    *
    * @param   dmoIface
    *          DMO interface associated with the helper. May not be <code>null</code>.
    * @param   types
    *          Hibernate types of query substitution parameters. May be an empty array, but may
    *          not be <code>null</code>.
    * @param   idOnly
    *          Indicates whether query is a projection query returning the primary key ID only.
    * @param   where
    *          Where clause of a query. May be <code>null</code>.
    * @param   sort
    *          Order by clause of a query. May be <code>null</code>.
    * @param   joinIface
    *          Interface DMO associated within query by foreign key.  May be <code>null</code>.
    * @param   helper
    *          <code>FQLHelper</code> instance to be cached.
    * @param   included
    *          The included properties.
    * @param   excluded
    *          The excluded properties.
    */
   void put(Class<? extends DataModelObject> dmoIface,
            FqlType[] types,
            boolean idOnly,
            String where,
            String sort,
            Class<? extends DataModelObject> joinIface,
            FQLHelper helper,
            Property[] included,
            Property[] excluded)
   {
      Key key = new Key(dmoIface, where, types, idOnly, sort, joinIface, included, excluded);
      
      synchronized (cache)
      {
         cache.put(key, helper);
      }
   }
   
   /**
    * <code>FQLHelper</code> hashable lookup key.
    */
   private static class Key
   {
      /** DMO interface */
      private final Class<? extends DataModelObject> dmoIface;
      
      /** FQL where clause, if any */
      private final String where;
      
      /** Hibernate types of query substitution parameters, if any */
      private final FqlType[] types;
      
      /** Is query a projection query returning primary key ID only? */
      private final boolean idOnly;
      
      /** Order by clause, if any */
      private final String sort;
      
      /** Interface of DMO with which query's records are joined, if any */
      private final Class<?> joinIface;
      
      /** Pre-computed hash code. */
      private final int hashCode;

      /** The included properties (FIELDS option). */
      private final Property[] included;

      /** The excluded properties (EXCEPT option). */
      private final Property[] excluded;
      
      /**
       * Constructor for a lookup key for the given data.
       *
       * @param   dmoIface
       *          DMO interface associated with the helper. May not be <code>null</code>.
       * @param   types
       *          Hibernate types of query substitution parameters. May be an empty array, but may
       *          not be <code>null</code>.
       * @param   idOnly
       *          Indicates whether query is a projection query returning the primary key ID only.
       * @param   where
       *          Where clause of a query. May be <code>null</code>.
       * @param   sort
       *          Order by clause of a query. May be <code>null</code>.
       * @param   joinIface
       *          Interface DMO associated within query by foreign key.  May be <code>null</code>.
       * @param   included
       *          The included properties.
       * @param   excluded
       *          The excluded properties.
       */
      Key(Class<? extends DataModelObject> dmoIface,
          String where,
          FqlType[] types,
          boolean idOnly,
          String sort,
          Class<?> joinIface,
          Property[] included,
          Property[] excluded)
      {
         this.dmoIface = dmoIface;
         this.where = where;
         this.types = types;
         this.idOnly = idOnly;
         this.sort = sort;
         this.joinIface = joinIface;
         this.included = included != null ? included.clone(): null;
         this.excluded = excluded != null ? excluded.clone(): null;

         int result = 17;
         
         result = 37 * result + dmoIface.hashCode();
         result = 37 * result + (where == null ? 0 : where.hashCode());
         result = 37 * result + (idOnly ? 1 : 0);
         result = 37 * result + (sort == null ? 0 : sort.hashCode());
         result = 37 * result + (joinIface == null ? 0 : joinIface.hashCode());
         for (FqlType type : types)
         {
            result = 37 * result + type.hashCode();
         }
         result = 37 * result + Arrays.hashCode(included);
         result = 37 * result + Arrays.hashCode(excluded);

         this.hashCode = result;
      }
      
      /**
       * A hash code implementation consistent with our overridden {@link #equals(Object)} method.
       * 
       * @return  Hash code.
       */
      public int hashCode()
      {
         return hashCode;
      }
      
      /**
       * Test for equivalence with another object.
       * 
       * @param   o
       *          Another <code>ColumnKey</code> instance.
       * 
       * @return  <code>true</code> if <code>o</code> has same internal
       *          state as this object;  else <code>false</code>.
       */
      public boolean equals(Object o)
      {
         if (!(o instanceof Key))
         {
            return false;
         }
         
         Key that = (Key) o;
         
         if ((this.joinIface == null || that.joinIface == null) &&
             this.joinIface != that.joinIface)
         {
            return false;
         }
         
         if ((this.where == null || that.where == null) &&
             this.where != that.where)
         {
            return false;
         }
         
         if ((this.sort == null || that.sort == null) &&
             this.sort != that.sort)
         {
            return false;
         }
         
         boolean same = (this.dmoIface.equals(that.dmoIface)                               &&
                         this.idOnly == that.idOnly                                        &&
                         (this.joinIface == null || this.joinIface.equals(that.joinIface)) &&
                         (this.sort == null || this.sort.equals(that.sort))                &&
                         (this.where == null || this.where.equals(that.where)));
         
         int len = this.types.length;
         if (!same || len != that.types.length)
         {
            return false;
         }
         return Arrays.equals(included, that.included) && Arrays.equals(excluded, that.excluded);
         
//         for (int i = 0; i < len; i++)
//         {
//            if (!this.types[i].equals(that.types[i]))
//            {
//               return false;
//            }
//         }
      }
      
      /**
       * Debug method to provide string representation of this object's state.
       * 
       * @return  String representation of this object's state.
       */
      public String toString()
      {
         StringBuilder buf = new StringBuilder();
         buf.append(dmoIface.getName());
         buf.append("@");
         buf.append(idOnly ? DatabaseManager.PRIMARY_KEY : "dmo");
         buf.append("@");
         buf.append(where);
         buf.append("@");
         buf.append(sort);
         buf.append("@");
         buf.append(joinIface != null ? joinIface.getName() : "null");
         for (FqlType type : types)
         {
            buf.append("#");
            buf.append(type);
         }
         if (included != null)
         {
            for (Property p : included)
            {
               if (p != null)
               {
                  buf.append("#");
                  buf.append(p);
               }
            }
         }
         if (excluded != null)
         {
            for (Property p : excluded)
            {
               if (p != null)
               {
                  buf.append("#");
                  buf.append(p);
               }
            }
         }
         return buf.toString();
      }
   }
}