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