FindQuery.java

/*
** Module   : FindQuery.java
** Abstract : Backing query for converted FIND statements
**
** Copyright (c) 2004-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
** 001 ECF 20060330   @25253 Created initial version. Backing query for
**                           converted find statements.
** 002 ECF 20060424   @25697 Added new constructor variants. Each existing
**                           variant now has a counterpart which does not
**                           expect a WhereExpression parameter.
** 003 ECF 20060424   @25698 Roll back previous change.
** 004 ECF 20060529   @26646 Override parent's initializeBuffer method.
**                           This implementation does nothing, to prevent
**                           buffer from being reset during query
**                           construction.
** 005 ECF 20061027   @30751 Replaced Object with DataModelObject for DMO
**                           arguments to methods/constructors. Required
**                           for compile-time type safety.
** 006 ECF 20061115   @31208 Override ancestor's implementation of
**                           isResetSnapshot() method. A converted FIND
**                           must clear the placeholder snapshot when a
**                           record is not found.
** 007 ECF 20070216   @32166 Explicitly set errorIfNull to true. This is
**                           required to override the default setting of
**                           false made by the superclass.
** 008 ECF 20070712   @34859 Moved all CAN-FIND related processing from
**                           parent class. Added support for client-side
**                           CAN-FIND processing within a where clause
**                           (including SHARE-LOCK support).
** 009 ECF 20071130   @36121 Removed isRefreshSnapshot() method. The
**                           RecordBuffer placeholder snapshot is no
**                           longer cleared by any query type when the
**                           query does not find a record to place in the
**                           buffer.
** 010 ECF 20071206   @36269 Modified udpateBuffer() method. Signature
**                           changed in superclass.
** 011 CA  20080312   @37431 Overloaded the registerChangeListener method
**                           to not register a change listener.
** 012 CA  20090731   @43466 Fixed the off-end behavior for FIND NEXT/PREV
**                           statements; on off-end, it must throw a QOEE
**                           exception and perform the defined END-KEY action
**                           (which defaults to UNDO, LEAVE if no explicit
**                           action is defined).
** 013 CA  20090806   @43534 Fixed regression related to H012 (@43466) - a
**                           FIND NEXT/PREV with a NO-ERROR clause must not
**                           end the block (a QOEE must not be thrown, so
**                           lenientOffEnd must be true if we are in silent
**                           error mode).
** 014 OM  20120612          Added bracketing for headless mode for execution of pl/java code in
**                           CAN-FIND function.
** 015 ECF 20131028          Import change.
** 016 ECF 20150715          Added isIdOnly method.
** 017 OM  20160715          Added support for template records.
** 018 GES 20160804          Switched from WhereExpression to lambda usage.
**         20160919          Changes to match parent class constructor/initialization changes.
** 019 ECF 20171220          Changes to error handling of CAN-FINDs.
** 020 ECF 20180201          Removed initializeBuffer.
** 021 ECF 20200906          New ORM implementation.
** 022 GES 20200731          Eliminated context-local lookups by using ErrorHelper.
** 023 OM  20200915          Handled query navigation on fatal error cases.
** 024 ECF 20210806          Corrected CAN-FIND return value in event of fatal error. Fixed nested query
**                           error detection/reporting.
**     ECF 20210911          Javadoc fix.
**     OM  20221103          New class names for FQLPreprocessor, FQLExpression, FQLBundle, and FQLCache.
** 025 DDF 20230314          Add short-circuits for CAN-FIND queries.
** 026 DDF 20230316          Changed CAN-FIND short-circuits conditions.
** 027 DDF 20230904          Removed short-circuit because the table can have a record that was not flushed.
** 028 AL2 20230926          Short-circuit ANY find on temp database using fastHasRecords. 
** 029 CA  20240324          Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime.
** 030 DDF 20240826          Added addExternalBuffers() method to return the current FindQuery instance
**                           for silent mode methods (FIND ... WHERE CAN-FIND() NO-ERROR).
** 031 DDF 20240902          The query initialization should be subjected to the silent mode and the
**                           exceptions thrown should be handled if necessary so the initialization
**                           will not be handled in the constructor, but will be delegated to the
**                           find operation.
**     DDF 20240902          Override find methods, used to initialize the query.
**     DDF 20240903          Override all remaining find methods, but only initialize the query once.
**     DDF 20240903          Initialize the query for conversion emitted methods.
** 032 AL2 20241021          Allow hinting that this is a CAN-FIND query and the buffer is not going to be
**                           loaded by the found DMO.
** 033 OM  20241128          Multi-tenant runtime support: selected the proper persistence context, eventually
**                           based on [sharedDb] parameter.
**                           Implementation of [setMultiTenantAlternative] method.
*/

/*
** 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.function.*;
import com.goldencode.p2j.persist.lock.*;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.util.*;

/**
 * A convenience adapter for a <code>RandomAccessQuery</code>. Calls to 
 * <code>previous</code> and <code>next</code> will throw a 
 * <code>QueryOffEndException</code> when no data record is found, which is
 * consistent with the Progress FIND NEXT/PREV statement behavior.  
 * <p>
 * On query off-end, the FIND NEXT/PREV statement will execute the action
 * defined for the END-KEY condition (which defaults to UNDO, LEAVE, but can
 * be overridden by an user-defined action). This behavior is only valid to
 * following blocks:
 * <ul>
 *    <li>REPEAT</li>
 *    <li>PROCEDURE</li>
 *    <li>FOR</li>
 * </ul> 
 * <p>
 * This class also handles converted CAN-FIND statements.
 */
public final class FindQuery
extends RandomAccessQuery
{
   /** Types of can-find lookups */
   private static enum CanFind
   {
      /** Any record matching the query criteria */
      ANY,
      
      /** A unique record matching the query criteria */
      ONE,
   };
   
   /** Mode of this query if used for CAN-FIND. */
   private CanFind canFindMode = null;
   
   /** Flag indicating whether buffer is updated by query. */
   private boolean readOnly = false;
   
   /** Data Model Object of the buffer in which the records will be retrived. */
   private DataModelObject dmo;
   
   /** Where clause, using HQL syntax, can be <code>null</code>. */
   private String where;
   
   /** Client-side where clause expression, can be <code>null</code>. */
   private Supplier<logical> whereExpr;
   
   /** Order by clause, using HQL syntax, can be <code>null<code>. */
   private String sort;
   
   /** DMO to which this query should join via a foreign relation, can be <code>null</code>. */
   private DataModelObject inverse;
   
   /** Substitution parameters for HQL queries. */
   private Object[] args;
   
   /** Lock type to apply to records retrieved. */
   private LockType lockType;
   
   /** If the query was already initialized. */
   private boolean initialized = false;
   
   /**
    * Basic constructor which defaults record lock type to
    * <code>LockType.SHARE</code>.
    *
    * @param   dmo
    *          Data model object which determines the record buffer into which
    *          records are retrieved.
    * @param   where
    *          Where clause, using HQL syntax.  May be <code>null</code>.
    * @param   whereExpr
    *          Client-side where clause expression.  May be <code>null</code>.
    * @param   sort
    *          Order by clause, using HQL syntax.  May be <code>null</code>
    *          only if query is to be navigated only using one of the
    *          <code>unique</code> method variants.
    */
   public FindQuery(DataModelObject dmo, String where, Supplier<logical> whereExpr, String sort)
   {
      this(dmo, where, whereExpr, sort, null, null, LockType.SHARE);
   }
   
   /**
    * Constructor which is used when joining to another table using a foreign
    * key.  Defaults record lock type to <code>LockType.SHARE</code>.
    *
    * @param   dmo
    *          Data model object which determines the record buffer into which
    *          records are retrieved.
    * @param   where
    *          Where clause, using HQL syntax.  May be <code>null</code>.
    * @param   whereExpr
    *          Client-side where clause expression.  May be <code>null</code>.
    * @param   sort
    *          Order by clause, using HQL syntax.  May be <code>null</code>
    *          only if query is to be navigated only using one of the
    *          <code>unique</code> method variants.
    * @param   inverse
    *          DMO to which this query should join via a foreign relation.
    *          May be <code>null</code>.
    */
   public FindQuery(DataModelObject dmo,
                    String where,
                    Supplier<logical> whereExpr,
                    String sort,
                    DataModelObject inverse)
   {
      this(dmo, where, whereExpr, sort, inverse, null, LockType.SHARE);
   }
   
   /**
    * Constructor which defaults record lock type to
    * <code>LockType.SHARE</code> and accepts default substitution parameters.
    *
    * @param   dmo
    *          Data model object which determines the record buffer into which
    *          records are retrieved.
    * @param   where
    *          Where clause, using HQL syntax.  May be <code>null</code>.
    * @param   whereExpr
    *          Client-side where clause expression.  May be <code>null</code>.
    * @param   sort
    *          Order by clause, using HQL syntax.  May be <code>null</code>
    *          only if query is to be navigated only using one of the
    *          <code>unique</code> method variants.
    * @param   args
    *          Substitution parameters for HQL queries.  These will be used
    *          if not overridden by a record retrieval method.
    *
    * @throws  IllegalArgumentException
    *          if any substitution argument provided is not of a supported
    *          type.
    */
   public FindQuery(DataModelObject dmo,
                    String where,
                    Supplier<logical> whereExpr,
                    String sort,
                    Object[] args)
   {
      this(dmo, where, whereExpr, sort, null, args, LockType.SHARE);
   }
   
   /**
    * Constructor which is used when joining to another table using a foreign
    * key.  Defaults record lock type to <code>LockType.SHARE</code> and
    * accepts default substitution parameters.
    *
    * @param   dmo
    *          Data model object which determines the record buffer into which
    *          records are retrieved.
    * @param   where
    *          Where clause, using HQL syntax.  May be <code>null</code>.
    * @param   whereExpr
    *          Client-side where clause expression.  May be <code>null</code>.
    * @param   sort
    *          Order by clause, using HQL syntax.  May be <code>null</code>
    *          only if query is to be navigated only using one of the
    *          <code>unique</code> method variants.
    * @param   inverse
    *          DMO to which this query should join via a foreign relation.
    *          May be <code>null</code>.
    * @param   args
    *          Substitution parameters for HQL queries.  These will be used
    *          if not overridden by a record retrieval method.
    *
    * @throws  IllegalArgumentException
    *          if any substitution argument provided is not of a supported
    *          type.
    */
   public FindQuery(DataModelObject dmo,
                    String where,
                    Supplier<logical> whereExpr,
                    String sort,
                    DataModelObject inverse,
                    Object[] args)
   {
      this(dmo, where, whereExpr, sort, inverse, args, LockType.SHARE);
   }
   
   /**
    * Constructor which sets an explicit, default, record lock type.
    *
    * @param   dmo
    *          Data model object which determines the record buffer into which
    *          records are retrieved.
    * @param   where
    *          Where clause, using HQL syntax.  May be <code>null</code>.
    * @param   whereExpr
    *          Client-side where clause expression.  May be <code>null</code>.
    * @param   sort
    *          Order by clause, using HQL syntax.  May be <code>null</code>
    *          only if query is to be navigated only using one of the
    *          <code>unique</code> method variants.
    * @param   lockType
    *          Lock type to apply to records retrieved, if not overridden by
    *          a record retrieval method.
    */
   public FindQuery(DataModelObject dmo,
                    String where,
                    Supplier<logical> whereExpr,
                    String sort,
                    LockType lockType)
   {
      this(dmo, where, whereExpr, sort, null, null, lockType);
   }
   
   /**
    * Constructor which is used when joining to another table using a foreign
    * key.  Sets an explicit, default, record lock type.
    *
    * @param   dmo
    *          Data model object which determines the record buffer into which
    *          records are retrieved.
    * @param   where
    *          Where clause, using HQL syntax.  May be <code>null</code>.
    * @param   whereExpr
    *          Client-side where clause expression.  May be <code>null</code>.
    * @param   sort
    *          Order by clause, using HQL syntax.  May be <code>null</code>
    *          only if query is to be navigated only using one of the
    *          <code>unique</code> method variants.
    * @param   inverse
    *          DMO to which this query should join via a foreign relation.
    *          May be <code>null</code>.
    * @param   lockType
    *          Lock type to apply to records retrieved, if not overridden by
    *          a record retrieval method.
    */
   public FindQuery(DataModelObject dmo,
                    String where,
                    Supplier<logical> whereExpr,
                    String sort,
                    DataModelObject inverse,
                    LockType lockType)
   {
      this(dmo, where, whereExpr, sort, inverse, null, lockType);
   }
   
   /**
    * Constructor which sets an explicit, default, record lock type and
    * accepts default substitution parameters.
    *
    * @param   dmo
    *          Data model object which determines the record buffer into which
    *          records are retrieved.
    * @param   where
    *          Where clause, using HQL syntax.  May be <code>null</code>.
    * @param   whereExpr
    *          Client-side where clause expression.  May be <code>null</code>.
    * @param   sort
    *          Order by clause, using HQL syntax.  May be <code>null</code>
    *          only if query is to be navigated only using one of the
    *          <code>unique</code> method variants.
    * @param   args
    *          Substitution parameters for HQL queries.  These will be used
    *          if not overridden by a record retrieval method.
    * @param   lockType
    *          Lock type to apply to records retrieved, if not overridden by
    *          a record retrieval method.
    *
    * @throws  IllegalArgumentException
    *          if any substitution argument provided is not of a supported
    *          type.
    */
   public FindQuery(DataModelObject dmo,
                    String where,
                    Supplier<logical> whereExpr,
                    String sort,
                    Object[] args,
                    LockType lockType)
   {
      this(dmo, where, whereExpr, sort, null, args, lockType);
   }
   
   /**
    * Constructor which is used when joining to another table using a foreign
    * key.  Sets an explicit, default, record lock type and accepts default
    * substitution parameters.
    *
    * @param   dmo
    *          Data model object which determines the record buffer into which
    *          records are retrieved.
    * @param   where
    *          Where clause, using HQL syntax.  May be <code>null</code>.
    * @param   whereExpr
    *          Client-side where clause expression.  May be <code>null</code>.
    * @param   sort
    *          Order by clause, using HQL syntax.  May be <code>null</code>
    *          only if query is to be navigated only using one of the
    *          <code>unique</code> method variants.
    * @param   inverse
    *          DMO to which this query should join via a foreign relation.
    *          May be <code>null</code>.
    * @param   args
    *          Substitution parameters for HQL queries.  These will be used
    *          if not overridden by a record retrieval method.
    * @param   lockType
    *          Lock type to apply to records retrieved, if not overridden by
    *          a record retrieval method.
    *
    * @throws  IllegalArgumentException
    *          if any substitution argument provided is not of a supported
    *          type.
    */
   public FindQuery(DataModelObject dmo,
                    String where,
                    Supplier<logical> whereExpr,
                    String sort,
                    DataModelObject inverse,
                    Object[] args,
                    LockType lockType)
   {
      this.dmo = dmo;
      this.where = where;
      this.whereExpr = whereExpr;
      this.sort = sort;
      this.inverse = inverse;
      this.args = args;
      this.lockType = lockType;
   }
   
   /**
    * Set this query's can-find mode to 'any', meaning can it find any record
    * which meets the query's criteria, regardless of duplicates.
    * 
    * @return  This instance, so that this method can be chained to the
    *          constructor and return this instance to the caller.
    */
   public FindQuery setHasAny()
   {
      canFindMode = CanFind.ANY;
      
      return this;
   }
   
   /**
    * Set this query's can-find mode to 'one', meaning can it find one and
    * only one record which meets the query's criteria.
    * 
    * @return  This instance, so that this method can be chained to the
    *          constructor and return this instance to the caller.
    */
   public FindQuery setHasOne()
   {
      canFindMode = CanFind.ONE;
      
      return this;
   }
   
   /**
    * Indicate whether a request to find the first result of this query would succeed or fail.
    * <p>
    * Default substitution arguments and a lock type of {@code NONE} are used for this test.
    *
    * @return  {@code true} if any record matching the query's criteria exists, else {@code false}.
    */
   public logical hasAny()
   {
      initializeQuery();
      return hasAny(getCurrentArgs(), getLockType());
   }
   
   /**
    * Indicate whether a request to find the first result of this query would succeed or fail.
    * <p>
    * The specified substitution arguments and a lock type of {@code NONE} are used for this test.
    * 
    * @param   values
    *          Query substitution parameters.
    *
    * @return  {@code true} if any record matching the query's criteria exists, else {@code false}.
    */
   public logical hasAny(Object[] values)
   {
      initializeQuery();
      return hasAny(values, getLockType());
   }
   
   /**
    * Indicate whether a request to find the first result of this query would succeed or fail.
    * <p>
    * Default substitution arguments and the specified lock type are used for this test.
    *
    * @param   lockType
    *          Lock type for the query.  This lock is not actually acquired, only tested for availability.
    *
    * @return  {@code true} if any record matching the query's criteria exists, else {@code false}.
    */
   public logical hasAny(LockType lockType)
   {
      initializeQuery();
      return hasAny(getCurrentArgs(), lockType);
   }
   
   /**
    * Indicate whether a request to find the first result of this query would succeed or fail.
    * <p>
    * The specified substitution arguments and lock type are used for this test.
    *
    * @param   values
    *          Query substitution parameters.
    * @param   lockType
    *          Lock type for the query.  This lock is not actually acquired, only tested for availability.
    *
    * @return  {@code true} if any record matching the query's criteria exists, else {@code false}.
    */
   public logical hasAny(Object[] values, LockType lockType)
   {
      initializeQuery();
      
      if (!prepareFetch())
      {
         return (logical.of(false));
      }
      
      canFindMode = CanFind.ANY;
      
      return (logical.of(canFindImpl(values, lockType)));
   }
   
   /**
    * Indicate whether this query would produce a unique result.
    * <p>
    * Default substitution arguments and a lock type of {@code NONE} are used for this test.
    *
    * @return  {@code true} if one and only one record matching the query's criteria exists, else
    *          {@code false}.
    */
   public logical hasOne()
   {
      initializeQuery();
      return hasOne(getCurrentArgs(), getLockType());
   }
   
   /**
    * Indicate whether this query would produce a unique result.
    * <p>
    * The specified substitution arguments and a lock type of {@code NONE} are used for this test.
    *
    * @param   values
    *          Query substitution parameters.
    *
    * @return  {@code true} if one and only one record matching the query's criteria exists, else
    *          {@code false}.
    */
   public logical hasOne(Object[] values)
   {
      initializeQuery();
      return hasOne(values, getLockType());
   }
   
   /**
    * Indicate whether this query would produce a unique result.
    * <p>
    * Default substitution arguments and the specified lock type are used for this test.
    *
    * @param   lockType
    *          Lock type for the query.  This lock is not actually acquired, only tested for availability.
    *
    * @return  {@code true} if one and only one record matching the query's criteria exists, else
    *          {@code false}.
    */
   public logical hasOne(LockType lockType)
   {
      initializeQuery();
      return hasOne(getCurrentArgs(), lockType);
   }
   
   /**
    * Indicate whether this query would produce a unique result.
    * <p>
    * The specified substitution arguments and lock type are used for this test.
    *
    * @param   values
    *          Query substitution parameters.
    * @param   lockType
    *          Lock type for the query.  This lock is not actually acquired, only tested for availability.
    *
    * @return  {@code true} if one and only one record matching the query's criteria exists, else
    *          {@code false}.
    */
   public logical hasOne(Object[] values, LockType lockType)
   {
      initializeQuery();
      
      if (!prepareFetch())
      {
         return (logical.of(false));
      }
      
      canFindMode = CanFind.ONE;
      
      return (logical.of(canFindImpl(values, lockType)));
   }
   
   /**
    * Navigate to the first record which meets the query criteria and retrieve
    * it into its record buffer.  Use the default substitution parameters and
    * lock type.
    *
    * @return  <code>true</code> if query could retrieve the first record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    *
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if no record
    *          could be found when operating in standalone mode.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   @Override
   public boolean first()
   {
      initializeQuery();
      return super.first();
   }
   
   /**
    * Navigate to the first record which meets the query criteria and retrieve
    * it into its record buffer.  Use the default lock type, but override the
    * default substitution parameters with those provided.
    *
    * @param   values
    *          Substitution values to use when issuing FQL queries.
    *
    * @return  <code>true</code> if query could retrieve the last record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if no record
    *          could be found when operating in standalone mode.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   @Override
   public boolean first(Object[] values)
   {
      initializeQuery();
      return super.first(values);
   }
   
   /**
    * Navigate to the first record which meets the query criteria and retrieve
    * it into its record buffer.  Use the default substitution parameters, but
    * override the default lock type with that provided.
    *
    * @param   lockType
    *          Lock type to acquire for the retrieved record.
    *
    * @return  <code>true</code> if query could retrieve the last record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    *
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if no record
    *          could be found when operating in standalone mode.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   @Override
   public boolean first(LockType lockType)
   {
      initializeQuery();
      return super.first(lockType);
   }
   
   /**
    * Navigate to the first record which meets the query criteria and retrieve
    * it into its record buffer.  Override both the default substitution
    * parameters, and the default lock type with those provided.
    *
    * @param   values
    *          Substitution values to use when issuing FQL queries.
    * @param   lockType
    *          Lock type to acquire for the retrieved record.
    * 
    * @return  <code>true</code> if query could retrieve the last record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if no record
    *          could be found when operating in standalone mode.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   @Override
   public boolean first(Object[] values, LockType lockType)
   {
      initializeQuery();
      return super.first(values, lockType);
   }
   
   /**
    * Navigate to the last record which meets the query criteria and retrieve
    * it into its record buffer.  Use the default substitution parameters and
    * lock type.
    *
    * @return  <code>true</code> if query could retrieve the last record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    *
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if no record
    *          could be found when operating in standalone mode.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   @Override
   public boolean last()
   {
      initializeQuery();
      return super.last();
   }
   
   /**
    * Navigate to the last record which meets the query criteria and retrieve
    * it into its record buffer.  Use the default lock type, but override the
    * default substitution parameters with those provided.
    *
    * @param   values
    *          Substitution values to use when issuing FQL queries.
    * 
    * @return  <code>true</code> if query could retrieve the last record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if no record
    *          could be found when operating in standalone mode.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   @Override
   public boolean last(Object[] values)
   {
      initializeQuery();
      return super.last(values);
   }
   
   /**
    * Navigate to the last record which meets the query criteria and retrieve
    * it into its record buffer.  Use the default substitution parameters, but
    * override the default lock type with that provided.
    *
    * @param   lockType
    *          Lock type to acquire for the retrieved record.
    * 
    * @return  <code>true</code> if query could retrieve the last record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if no record
    *          could be found when operating in standalone mode.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   @Override
   public boolean last(LockType lockType)
   {
      initializeQuery();
      return super.last(lockType);
   }
   
   /**
    * Navigate to the last record which meets the query criteria and retrieve
    * it into its record buffer.  Override both the default substitution
    * parameters, and the default lock type with those provided.
    *
    * @param   values
    *          Substitution values to use when issuing FQL queries.
    * @param   lockType
    *          Lock type to acquire for the retrieved record.
    * 
    * @return  <code>true</code> if query could retrieve the last record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if no record
    *          could be found when operating in standalone mode.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   @Override
   public boolean last(Object[] values, LockType lockType)
   {
      initializeQuery();
      return super.last(values, lockType);
   }
   
   /**
    * Navigate to the next record which meets the query criteria and retrieve
    * it into its record buffer.  Use the default substitution parameters and
    * lock type.
    * <p>
    * Use the record most recently loaded into the backing buffer as the
    * reference point when determining the next record to visit.  If no
    * record has yet been loaded into the buffer, retrieve the first record
    * which meets the query criteria.
    * 
    * @return  <code>true</code> if query could retrieve the next record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval.
    * @throws  QueryOffEndException
    *          if no record could be found.
    */
   @Override
   public boolean next()
   {
      initializeQuery();
      return super.next();
   }
   
   /**
    * Navigate to the next record which meets the query criteria and retrieve
    * it into its record buffer.  Use the default lock type, but override the
    * default substitution parameters with those provided.
    * <p>
    * Use the record most recently loaded into the backing buffer as the
    * reference point when determining the next record to visit.  If no
    * record has yet been loaded into the buffer, retrieve the first record
    * which meets the query criteria.
    *
    * @param   values
    *          Substitution values to use when issuing FQL queries.
    * 
    * @return  <code>true</code> if query could retrieve the next record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval.
    * @throws  QueryOffEndException
    *          if no record could be found.
    */
   @Override
   public boolean next(Object[] values)
   {
      initializeQuery();
      return super.next(values);
   }
   
   /**
    * Navigate to the next record which meets the query criteria and retrieve
    * it into its record buffer.  Use the default substitution parameters, but
    * override the default lock type with that provided.
    * <p>
    * Use the record most recently loaded into the backing buffer as the
    * reference point when determining the next record to visit.  If no
    * record has yet been loaded into the buffer, retrieve the first record
    * which meets the query criteria.
    *
    * @param   lockType
    *          Lock type to acquire for the retrieved record.
    * 
    * @return  <code>true</code> if query could retrieve the next record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval.
    * @throws  QueryOffEndException
    *          if no record could be found.
    */
   @Override
   public boolean next(LockType lockType)
   {
      initializeQuery();
      return super.next(lockType);
   }
   
   /**
    * Navigate to the next record which meets the query criteria and retrieve
    * it into its record buffer.  Override both the default substitution
    * parameters, and the default lock type with those provided.
    * <p>
    * Use the record most recently loaded into the backing buffer as the
    * reference point when determining the next record to visit.  If no
    * record has yet been loaded into the buffer, retrieve the first record
    * which meets the query criteria.
    *
    * @param   values
    *          Substitution values to use when issuing FQL queries.
    * @param   lockType
    *          Lock type to acquire for the retrieved record.
    * 
    * @return  <code>true</code> if query could retrieve the next record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval.
    * @throws  QueryOffEndException
    *          if no record could be found and <code>lenientOffEnd</code> is
    *          <code>false</code>.
    */
   @Override
   public boolean next(Object[] values, LockType lockType)
   {
      initializeQuery();
      return super.next(values, lockType);
   }
   
   /**
    * Navigate to the previous record which meets the query criteria and
    * retrieve it into its record buffer.  Use the default substitution
    * parameters and lock type.
    * <p>
    * Use the record most recently loaded into the backing buffer as the
    * reference point when determining the previous record to visit.  If no
    * record has yet been loaded into the buffer, retrieve the last record
    * which meets the query criteria.
    * 
    * @return  <code>true</code> if query could retrieve the previous record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval.
    * @throws  QueryOffEndException
    *          if no record could be found.
    */
   @Override
   public boolean previous()
   {
      initializeQuery();
      return super.previous();
   }
   
   /**
    * Navigate to the previous record which meets the query criteria and
    * retrieve it into its record buffer.  Use the default lock type, but
    * override the default substitution parameters with those provided.
    * <p>
    * Use the record most recently loaded into the backing buffer as the
    * reference point when determining the previous record to visit.  If no
    * record has yet been loaded into the buffer, retrieve the last record
    * which meets the query criteria.
    *
    * @param   values
    *          Substitution values to use when issuing FQL queries.
    * 
    * @return  <code>true</code> if query could retrieve the previous record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval.
    * @throws  QueryOffEndException
    *          if no record could be found.
    */
   @Override
   public boolean previous(Object[] values)
   {
      initializeQuery();
      return super.previous(values);
   }
   
   /**
    * Navigate to the previous record which meets the query criteria and
    * retrieve it into its record buffer.  Use the default substitution
    * parameters, but override the default lock type with that provided.
    * <p>
    * Use the record most recently loaded into the backing buffer as the
    * reference point when determining the previous record to visit.  If no
    * record has yet been loaded into the buffer, retrieve the last record
    * which meets the query criteria.
    *
    * @param   lockType
    *          Lock type to acquire for the retrieved record.
    * 
    * @return  <code>true</code> if query could retrieve the previous record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval.
    * @throws  QueryOffEndException
    *          if no record could be found.
    */
   @Override
   public boolean previous(LockType lockType)
   {
      initializeQuery();
      return super.previous(lockType);
   }
   
   /**
    * Navigate to the previous record which meets the query criteria and
    * retrieve it into its record buffer.  Override both the default
    * substitution parameters, and the default lock type with those provided.
    * <p>
    * Use the record most recently loaded into the backing buffer as the
    * reference point when determining the previous record to visit.  If no
    * record has yet been loaded into the buffer, retrieve the last record
    * which meets the query criteria.
    *
    * @param   values
    *          Substitution values to use when issuing FQL queries.
    * @param   lockType
    *          Lock type to acquire for the retrieved record.
    * 
    * @return  <code>true</code> if query could retrieve the previous record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval.
    * @throws  QueryOffEndException
    *          if no record could be found and <code>lenientOffEnd</code> is
    *          <code>false</code>.
    */
   @Override
   public boolean previous(Object[] values, LockType lockType)
   {
      initializeQuery();
      return super.previous(values, lockType);
   }
   
   /**
    * Reload the record most recently loaded into the backing record buffer,
    * if any.  Use the default lock type.
    * 
    * @return  <code>true</code> if query could retrieve the current record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record reload.
    */
   @Override
   public boolean current()
   {
      initializeQuery();
      return super.current();
   }
   
   /**
    * Reload the record most recently loaded into the backing record buffer,
    * if any.  Override the default lock type.
    *
    * @param   lockType
    *          New lock type to apply to the record.
    * 
    * @return  <code>true</code> if query could retrieve the current record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record reload, or the lock could
    *          not be transitioned to the requested type.
    */
   @Override
   public boolean current(LockType lockType)
   {
      initializeQuery();
      return super.current(lockType);
   }
   
   /**
    * Navigate to a particular record which meets the query criteria and
    * retrieve it into its record buffer.  No more than one record may match
    * the criteria.  Use the default substitution parameters and lock type.
    * 
    * @return  <code>true</code> if query could retrieve the unique record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if more than
    *          one record is found which match the criteria.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   @Override
   public boolean unique()
   {
      initializeQuery();
      return super.unique();
   }
   
   /**
    * Navigate to a particular record which meets the query criteria and
    * retrieve it into its record buffer.  No more than one record may match
    * the criteria.  Use the default lock type, but override the default
    * substitution parameters with those provided.
    *
    * @param   values
    *          Substitution values to use when issuing FQL queries.
    * 
    * @return  <code>true</code> if query could retrieve the unique record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if more than
    *          one record is found which match the criteria.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   @Override
   public boolean unique(Object[] values)
   {
      initializeQuery();
      return super.unique(values);
   }
   
   /**
    * Navigate to a particular record which meets the query criteria and
    * retrieve it into its record buffer.  No more than one record may match
    * the criteria.  Use the default substitution parameters, but override
    * the default lock type with that provided.
    *
    * @param   lockType
    *          Lock type to acquire for the retrieved record.
    * 
    * @return  <code>true</code> if query could retrieve the unique record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if more than
    *          one record is found which match the criteria.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   @Override
   public boolean unique(LockType lockType)
   {
      initializeQuery();
      return super.unique(lockType);
   }
   
   /**
    * Navigate to a particular record which meets the query criteria and
    * retrieve it into its record buffer.  No more than one record may match
    * the criteria.  Override both the default substitution parameters, and
    * the default lock type with those provided.
    *
    * @param   values
    *          Substitution values to use when issuing FQL queries.
    * @param   lockType
    *          Lock type to acquire for the retrieved record.
    * 
    * @return  <code>true</code> if query could retrieve the unique record successfully;
    *          <code>false</code> if unsuccessful and <code>lenientOffEnd</code> is set on true.
    * 
    * @throws  ErrorConditionException
    *          if an error occurred during record retrieval or if more than
    *          one record is found which match the criteria.
    * @throws  QueryOffEndException
    *          if no record could be found when operating in compound query
    *          mode.
    */
   @Override
   public boolean unique(Object[] values, LockType lockType)
   {
      initializeQuery();
      return super.unique(values, lockType);
   }
   
   /**
    * Indicate whether this query is a simple RECID/ROWID lookup FIND query. This has implications as to
    * whether a 7254 error will be raised in the event this FIND is nested within a function called within
    * another query's WHERE clause.
    * 
    * @return  {@code true} if this is a RECID/ROWID lookup FIND query, else {@code false}.
    */
   public boolean isFindByRowid()
   {
      return getHelper().getFQLPreprocessor().isFindByRowid();
   }
   
   /**
    * Add one or more buffers to the list of those which this query references externally. These typically 
    * will be buffers referenced by converted CAN-FIND expressions embedded in the query's FQL expression, 
    * which are not the primary buffers associated with the query.
    * 
    * @param   dmos
    *          One or more external buffers.
    * 
    * @return  This query instance.
    */
   @Override
   public FindQuery addExternalBuffers(DataModelObject... dmos)
   {
      initializeQuery();
      super.addExternalBuffers(dmos);
      return this;
   }
   
   /**
    * Initializes the query at the latest moment possible (right before executing a find operation).
    * This method is compatible with silent mode operations, meaning that the exceptions 
    * {@link LegacyErrorException} and {@link ErrorConditionException} that are thrown when the
    * query is initialized are caught and handled.
    * <p>
    * Ensures the query is only initialized once.
    */
   public void initializeQuery()
   {
      if (initialized)
      {
         return;
      }
      
      super.initialize(dmo, where, whereExpr, sort, null, inverse, args, lockType);
      setErrorIfNull(true);
      
      initialized = true;
   }
   
   /**
    * Indicate whether a nested query error should be suppressed (i.e., reported only as a warning, but not
    * interrupt control flow), when detected while preprocessing this query's substitution parameters.
    * 
    * @return  {@code false}.
    */
   @Override
   protected boolean suppressNestedQueryError()
   {
      return false;
   }
   
   /**
    * This class doesn't need to register a change listener, so this method does nothing.
    */
   protected void registerChangeListener()
   {
      // do nothing
   }
   
   /**
    * Override the parent's method to return <code>true</code> if this is a CAN-FIND, else
    * delegate to the parent's implementation.
    * 
    * @return  <code>true</code> to produce a projection query, else <code>false</code>.
    */
   @Override
   protected boolean isIdOnly()
   {
      if (canFindMode != null)
      {
         return true;
      }
      
      return super.isIdOnly();
   }
   
   /**
    * Prepare buffer associated with this query for the query's execution.
    * If this query is in read-only mode, do nothing.  Otherwise, invoke the
    * superclass' implementation.
    * 
    * @throws  ValidationException
    *          if the parent's implementation is invoked and a buffer flush
    *          triggers a validation error for the record currently stored in
    *          the buffer.
    */
   protected void prepareBuffer()
   throws ValidationException
   {
      if (!readOnly)
      {
         super.prepareBuffer();
      }
   }
   
   /**
    * Update the record buffer with the given DMO, applying the specified
    * lock type and error-if-null setting.  This implementation causes the
    * record buffer's internal placeholder snapshot to be reset if
    * <code>dmo</code> is null.
    *
    * @param   dmo
    *          Record to be stored in buffer.
    * @param   lockType
    *          Lock type associated with the record, which will be remembered
    *          by the buffer.
    * @param   errorIfNull
    *          If <code>true</code>, setting a <code>null</code> value as the
    *          current record in an underlying buffer will raise an error
    *          condition (if not in silent error mode); if <code>false</code>,
    *          this action will raise an end condition instead.
    * @param   offEnd
    *          Enum indicating whether query is off-end, and if so, in which
    *          direction.
    */
   protected void updateBuffer(Record dmo, LockType lockType, boolean errorIfNull, OffEnd offEnd)
   {
      if (!readOnly)
      {
         super.updateBuffer(dmo, lockType, errorIfNull, offEnd);
      }
   }
   
   /**
    * Override the superclass' implementation to throw an exception
    * unconditionally when in read-only mode.  This ensures that any locking
    * error which occurs during a converted CAN-FIND is not reported to the
    * user, but does affect the outcome of the CAN-FIND test.
    * <p>
    * When the query is not in read-only mode, handling is delegated to the
    * superclass' implementation.
    * 
    * @param   exc
    *          Cause exception.
    */
   protected void handleExecuteException(LockUnavailableException exc)
   {
      if (readOnly)
      {
         ErrorManager.throwError(445,
                                 getBuffer().getLegacyName() + " record is locked",
                                 exc,
                                 true);
      }
      else
      {
         super.handleExecuteException(exc);
      }
   }
   
   /**
    * Override the superclass' implementation to throw an exception
    * unconditionally when in read-only mode.  This ensures that any
    * validation error which occurs during a converted CAN-FIND is not
    * reported to the user, but does affect the outcome of the CAN-FIND test.
    * <p>
    * When the query is not in read-only mode, handling is delegated to the
    * superclass' implementation.
    * 
    * @param   exc
    *          Cause exception.
    */
   protected void handleExecuteException(ValidationException exc)
   {
      if (readOnly)
      {
         ErrorManager.throwError(exc, true);
      }
      else
      {
         super.handleExecuteException(exc);
      }
   }
   
   /**
    * Override the superclass' implementation to throw an exception
    * unconditionally when in read-only mode.  This ensures that any
    * validation error which occurs during a converted CAN-FIND is not
    * reported to the user, but does affect the outcome of the CAN-FIND test.
    * <p>
    * When the query is not in read-only mode, handling is delegated to the
    * superclass' implementation.
    * 
    * @param   exc
    *          Cause exception.
    */
   protected void handleExecuteException(PersistenceException exc)
   {
      if (readOnly)
      {
         ErrorManager.throwError(exc, true);
      }
      else
      {
         super.handleExecuteException(exc);
      }
   }
   
   /**
    * Test whether the {@code first} / {@code last} navigation will throw {@code QueryOffEndException} if the
    * initialization failed. The super class must throw the exception if it fails the initialization in order
    * to prematurely exit the loop on basic navigation. Now we are processing a simple {@code FIND} statement
    * which does not require such thing because there is no loop to break.
    *
    * @return  always {@code false}.
    */
   protected boolean throwExceptionOnFatalError()
   {
      return false;
   }
   
   /**
    * Checks if the buffer associated with this query will be loaded as the result of this query execution.
    * This should always hold, except for CAN-FIND queries that do not modify the state of the buffer.
    * 
    * @return   {@code true} if the buffer is going to be loaded by the query.
    */
   @Override
   protected boolean isBufferLoadedByQuery()
   {
      return canFindMode == null;
   }
   
   /**
    * Indicate whether a record can be found with meets this query's criteria. The success of this
    * search is governed by the can-find mode of this query:
    * <ul>
    *   <li>ANY:  return {@code true} if <i>any</i> record can be found which meets the query's
    *             criteria, regardless of duplicates.  If no such record is found, return 
    *             {@code false}.</li>
    *   <li>ONE:  return {@code true} if <i>one and only one</i> record can be found which meets
    *             the query's criteria.  If no such record is found, or if more than one such
    *             record is found, return {@code false}.</li>
    * </ul>
    * 
    * @param   values
    *          Query substitution parameters
    * @param   lockType
    *          Lock type to use when performing test.  Lock is not actually acquired, only tested.
    *          
    * @return  See above.
    */
   private boolean canFindImpl(Object[] values, LockType lockType)
   {
      if (lockType == null)
      {
         lockType = LockType.NONE;
      }
      
      RecordBuffer buffer = this.getBuffer();

      FQLHelper helper = getHelper();
      if (helper.getFQLPreprocessor().isFindByRowid() && 
          helper.getFQLPreprocessor().getFindByRowid(values) < 0)
      {
         // permanent template records ids are validated against _file meta table. If requested 
         // rowid is not the proper one, then the CAN-FIND returns false
         // temporary tables don't have template records or at least they are not findable using 
         // _file meta table. Returning false for negative rowid lookup. 
         return !buffer.isTemporary() &&
               helper.isFindTemplate(values, buffer.getDMOImplementationClass());
      }

      // otherwise the query is not accessing records by their rowid or the value is positive so
      // it must be checked against the database
    
      // slight optimization to short-circuit cases where we can answer an ANY query with a
      // direct-access. this is only available for temporary database.
      if (canFindMode == CanFind.ANY && buffer.isTemporary() && !hasConstraints())
      {
         try
         {
            // make sure we make the records visible before attempting a direct-access call
            honorRecordNursery(null);
            Boolean hasRecords = buffer.fastHasRecords();
            // this should always suceed for temporary database.
            if (hasRecords != null)
            {
               return hasRecords;
            }
         }
         catch (PersistenceException e)
         {
            ErrorManager.recordOrThrowError(e);
            return false;
         }
      }
      
      if (canFindMode == CanFind.ONE && helper.getFQLPreprocessor().isUniqueFind(true))
      {
         canFindMode = CanFind.ANY;
      }
      
      boolean unique = false;
      
      switch (canFindMode)
      {
         case ANY:
            activateFirst();
            break;
         case ONE:
            activateUnique();
            unique = true;
            break;
      }
      
      Record dmo = null;
      readOnly = true;
      boolean result = false;
      
      try
      {
         // temporarily enter headless mode when executing CAN-FIND 
         buffer.txHelper.errHlp.setCanFindBracket(true); // enter CAN-FIND: push a CAN-FIND level
         
         dmo = execute(values, lockType, unique, false);
         result = (dmo != null && testLock(dmo, lockType));
      }
      catch (ErrorConditionException exc)
      {
         result = false;
      }
      finally
      {
         // restore previous headless mode
         buffer.txHelper.errHlp.setCanFindBracket(false); // leave CAN-FIND: pop out a level
         
         readOnly = false;
      }
      
      return result;
   }
   
   /**
    * Test whether the specified lock type could be acquired for the given
    * DMO, but do not actually acquire the lock.
    * <p>
    * Note:  because this method does not actually acquire the lock, it is
    * not a reliable test, since another context could easily acquire the
    * target lock after this method returns <code>true</code>, but before any
    * further action is taken on that information in the current context.
    * 
    * @param   dmo
    *          DMO for which the lock test should occur.
    * @param   lockType
    *          Lock type to test.
    * 
    * @return  <code>true</code> if the lock could be acquired at the moment
    *          of the test, else <code>false</code>.
    */
   private boolean testLock(Record dmo, LockType lockType)
   {
      if (lockType != LockType.NONE)
      {
         RecordBuffer buffer = getBuffer();
         Persistence persistence = buffer.getPersistence();
         String table = buffer.getTable();
         Long id = dmo.primaryKey();
         try
         {
            RecordIdentifier<String> ident = new RecordIdentifier<>(table, id);
            persistence.lock(lockType, ident, false, !buffer.getDmoInfo().multiTenant);
         }
         catch (LockUnavailableException exc)
         {
            return false;
         }
      }
      
      return true;
   }
}