# ErrorHandler.java

    /*
    ** Module   : ErrorHandler.java
    ** Abstract : Error handler for custom database functions implemented in Java
    **
    ** Copyright (c) 2004-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------Description----------------------------
    ** 001 ECF 20071212  @36497  Created initial version. An error handler for custom database
    **                           functions implemented in Java.
    ** 002 SVL 20080425  @38118  ErrorManager.setHeadless() is set only if the class is loaded
    **                           inside PL/Java JVM.
    ** 003 OM  20140728          Methods initError() and checkError() annotated as volatile
    **                           (non immutable) java functions for PL/SQL.
    ** 004 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    */
    /*
    ** 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.pl;

    import java.util.*;
    import java.util.logging.*;

    import com.goldencode.p2j.security.SecurityManager;
    import com.goldencode.p2j.util.logging.*;

    /**
     * An error handler for custom database functions implemented in Java.
     * <p>
     * Normally, exceptions thrown in Java code invoked by function and operator
     * implementations are allowed to propagate normally.  However, in certain
     * cases, exceptions must be caught and handled silently.  In these cases, the
     * fact that an error was handled must affect the outcome of a subexpression
     * within an SQL statement.  Currently, the only case in which errors must be
     * handled in this fashion is within a converted CAN-FIND statement, nested
     * within a where clause (including within the where clause of an enclosing,
     * converted, CAN-FIND statement).  In this case, a nested CAN-FIND is
     * converted into a subquery (a.k.a., subselect).  If that subquery contains
     * any built-in functions or operators which are implemented in Java, the
     * functions implemented within this class are used to safely handle any
     * potential exceptions.
     * <p>
     * Functions and operators implemented in Java, which are used in such a
     * circumstance, must be wrapped with a call to {@link
     * #checkError(Boolean, Boolean)}, such that the nearest enclosing
     * subexpression which can evaluate to a boolean either evaluates normally
     * (if no error occurs), or evaluates to <code>false</code> in the event an
     * error occurs.  For instance, the subexpression
     * <pre>
     *    toInt('foo') = 5
     * </pre>
     * must evaluate to <code>false</code> if it occurs within a subquery, because
     * the left side of the equals operation will fail with an error.  Such an
     * expression would be allowed to fail normally, however, if it occurred
     * outside a subquery.  To accomplish the former, the above subexpression,
     * when occurring within a subquery, would actually be expanded to
     * <pre>
     *    checkError(initError(false), toInt('foo') = 5)
     * </pre>
     * <p>
     * The call to <code>initError(false)</code> initializes the current scope of
     * the handler to a known (error-free) state.  In the event of an error within
     * the <code>toInt()</code> function, {@link #handleError(RuntimeException)}
     * is invoked, the current scope is marked as having an error, and
     * <code>null</code> is returned by <code>toInt()</code>.  When the enclosing
     * <code>checkError()</code> function is invoked, the current scope is checked
     * for errors.  If an error occurred, <code>checkError()</code> returns
     * <code>false</code>.  If no error occurred, the value of the second
     * parameter to {@link #checkError(Boolean, Boolean)} (i.e., the result of the
     * subexpression) is returned.
     * <p>
     * <b>Implementation Notes</b><br>
     * This implementation relies on several critical assumptions:
     * <ul>
     *   <li>The first parameter to <code>checkError()</code> will always be
     *       <code>initError(false)</code> (see next bullet as to why this is
     *       important).  This is the responsibility of the SQL author.
     *   <li>Parameters to a database function are always evaluated in order,
     *       from left to right.  It is imperative that the {@link
     *       #initError(Boolean)} method is invoked before the subexpression
     *       whose boolean result is passed as the second parameter to the {@link
     *       #checkError(Boolean, Boolean)} method.  The former initializes the
     *       state of the <code>ErrorHandler</code> for the current
     *       subexpression's scope.  If an error occurs within that subexpression,
     *       the state is assumed to be initialized properly.
     *   <li>An SQL expression which calls into the JVM for function services will
     *       always use the same JVM thread across multiple function calls.  The
     *       implementation of this class' methods relies upon a
     *       <code>ThreadLocal</code> variable to share state across these
     *       multiple calls.  If different threads were to be used, this scheme
     *       clearly would not work.
     *   <li>Every database function/operator implemented in Java will enclose its
     *       main body within a try-catch block which catches
     *       <code>RuntimeException</code>, will call {@link
     *       #handleError(RuntimeException)} from within that catch block, and
     *       will return <code>null</code> after that call.
     * </ul>
     */
    public final class ErrorHandler
    {
       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get("");

       /** Stack of error flags used to track errors by function scope */
       private static final ThreadLocal<Deque<Boolean>> errors =
       new ThreadLocal<Deque<Boolean>>()
       {
          protected Deque<Boolean> initialValue()
          {
             return (new LinkedList<>());
          }
       };

       static
       {
          // if class is loaded inside PL/Java JVM
          if (SecurityManager.getInstance() == null)
          {
             // Ensure ErrorManager does no logging and does not use UI.
             com.goldencode.p2j.util.ErrorManager.setHeadless();
          }
       }

       /**
        * Initialize the error flag stack for the current function scope by
        * pushing the given error state onto the stack.
        *
        * @param   state
        *          Error state to push onto the stack.
        *
        * @return  <code>Boolean.TRUE</code>.
        */
       @HQLFunction(immutable = false)
       public static Boolean initError(Boolean state)
       {
          Deque<Boolean> stack = errors.get();
          stack.push(state);

          return Boolean.TRUE;
       }

       /**
        * A special wrapper function for a function implemented in Java and called
        * within a SUBSELECT SQL phrase which represents a converted CAN-FIND
        * statement, where the wrapped function can potentially produce an error
        * condition.  For instance:
        * <pre>
        *   ...where (select id
        *             from some_table
        *             where checkError(initError(false), toInt_safe(...) = 5)
        *             or ...
        *             limit 1)
        *             is not null
        * </pre>
        * <p>
        * The intended convention is to call <code>initError(false)</code> as the
        * first parameter to the method, in order to initialize the error state
        * for the current scope, and to pass as the second parameter a boolean
        * sub-expression which contains a call to one of the *_safe() functions
        * implemented in Java.
        * <p>
        * This method checks the error flag for the current scope and does the
        * following:
        * <ul>
        *   <li>if the error flag for the current scope is <code>true</code>
        *       (indicating an error has occurred in a Java method invoked from
        *       the enclosed boolean sub-expression), this method returns
        *       <code>false</code>;
        *   <li>if the error flag for the current scope is <code>false</code>
        *       (indicating no error has occurred in Java within the boolean
        *       sub-expression), the result of executing the second parameter is
        *       returned.
        * </ul>
        *
        * @param   init
        *          The result of the <code>initError(false)</code> function
        *          called as the first parameter;  not used.
        * @param   result
        *          The result of executing the boolean expression containing the
        *          Java function or operator.  This result indicates the
        *          success/failure of the enclosed, boolean sub-expression.
        *
        * @return  <code>false</code> if an error occurred in this scope;
        *          otherwise, the result of the boolean sub-expression.
        */
       @HQLFunction(immutable = false)
       public static Boolean checkError(Boolean init, Boolean result)
       {
          Deque<Boolean> stack = errors.get();
          Boolean flag = stack.pop();

          return (flag ? Boolean.FALSE : result);
       }

       /**
        * Handle a P2J runtime error encountered in the current function scope.
        * If the error handler has been initialized with a previous invocation of
        * the {@link #initError(Boolean)} function, the error state for the
        * current function scope is set to <code>true</code>.  Otherwise, the
        * exception passed to this method is simply rethrown.
        *
        * @param   exc
        *          The exception to be handled.
        *
        * @see     #initError(Boolean)
        * @see     #checkError(Boolean, Boolean)
        */
       static void handleError(RuntimeException exc)
       {
          Deque<Boolean> stack = errors.get();

          // If there is nothing on the error stack, it was not initialized.  This
          // indicates we are not handling errors in the current scope, but rather
          // letting them propagate.  Rethrow the exception.
          if (stack.peek() == null)
          {
             throw exc;
          }

          // Replace the existing error flag for the current scope with true, to
          // indicate an error occurred.
          stack.pop();
          stack.push(true);

          // Log the error message text as a warning.
          if (LOG.isLoggable(Level.WARNING))
          {
             LOG.warning(exc.getMessage());
          }
       }
    }