ErrorManager.java
/*
** Module : ErrorManager.java
** Abstract : Provides ERROR-STATUS, NO-ERROR and error message support.
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
** 001 GES 20050823 @21210 Created initial version which provides
** ERROR-STATUS and NO-ERROR support.
** 002 GES 20050829 @22362 Minor mods to more closely duplicate how
** Progress handles error text. There are
** cases where error text is added but no
** error condition is raised (even in a
** non-silent mode situation).
** 003 ECF 20051104 @23262 Added capability to extract error data from
** Java exceptions. This includes both formatted
** error texts as well as recording ErrorEntry
** objects. Overloaded recordOrThrowError for
** this purpose and added a set of overloaded
** errorTexts() methods.
** 004 ECF 20051205 @23698 Added recordOrShowError method variants.
** Displays an error message in an alert box and
** logs the error.
** 005 ECF 20051219 @23752 Added warning mode. When in warning mode,
** errors are reported to the UI using an alert
** box, rather than an exception being thrown.
** 006 ECF 20060117 @23959 Enabled show error code. Removed temporary
** code which used stderr and replaced with
** calls to LogicalTerminal.message().
** 007 GES 20060123 @24021 Moved to context-local storage.
** 008 GES 20060208 @24389 Errors are now displayed via message when
** not in silent error mode.
** 009 NVS 20060306 @24903 Errors occurring on the client are now passed
** to the server for centralized logging.
** 010 NVS 20060313 @24994 Error messages are displayed using Logical
** Terminal methods if on the server, and Client
** Exports directly otherwise.
** 011 GES 20060313 @24995 Use new logging infrastructure.
** 012 GES 20060626 @27569 Implemented scoped context local data which
** makes it safe to use silent error mode in
** a nested manner.
** 013 ECF 20060719 @28122 Changed logging level for silent mode errors
** from WARNING to FINE.
** 014 GES 20060807 @28454 Match new signature in ClientExports.
** 015 GES 20060927 @29966 Exposed displayError() as public.
** 016 GES 20061004 @30134 Added support for nested errors (errors
** generated during processing of another
** error).
** 017 NVS 20061017 @30470 Implemented ability to restart the session
** without exiting the process.
** 018 ECF 20071217 @36499 Added headless mode. Intended to support
** environments with no UI and limited logging.
** Implementation at this time is limited.
** 019 CA 20080530 @38463 Added methods related to displaying an error
** message before the application stops.
** 020 GES 20080619 @38886 Added a convenience method to format and
** display an error without throwing any
** exception, w/o being affected by silent
** error mode and without recording anything.
** 021 GES 20080624 @38912 Added an ignore flag to NOP all display,
** recording or exception generation. Cleaned
** up the API to meet coding standards.
** 022 CA 20080815 @39447 Added displayAbend - it displays an abend
** message, but no actual abend is triggered.
** 023 SVL 20081208 @40826 Match client exports API change.
** 024 ECF 20081215 @40913 Added throwError() variants. Unconditionally
** displays an error message and throws an error
** condition exception, regardless of silent
** error mode. Headless mode is still honored.
** 025 GES 20090422 @41908 Converted to standard string formatting.
** 026 SVL 20090612 @42669 showErrorAndAbend() can be executed on the client
** side.
** 027 ECF 20090622 @42987 Added new display option for errors. New variants
** of displayError() and buildErrorText() allow the
** standard message prefix to be omitted.
** 028 SIY 20090701 @43012 Made ErrorEntry public for use by other classes.
** Inferred generics. Minor cleanups.
** 029 SVL 20090803 @43476 showErrorAndAbend(int[],String[]) has been added.
** 030 GES 20090831 @43802 Removed stacking from the error-status data
** members. The 4GL doesn't stack the error list or
** the error flag. The previous stacking was an
** over-compensation for saving state across top-
** level block boundaries. The transaction mgr now
** handles saving/restoring the silent error mode
** flag in the necessary cases. This class now can
** be greatly simplified which also fixes some issues
** with the error-status system handle.
** 031 SVL 20090908 @43847 recordOrThrowError() saves error code in the
** ErrorConditionException.
** 032 GES 20090924 @44010 Completed headless support. Both the global flag
** and a context-specific flag are honored.
** 033 SIY 20110112 Minor refactorings and cleanups.
** 034 CA 20130112 Added asHandle, getType, isValid and isUnknown
** methods, accessed by the ERROR-STATUS handle.
** Added new recordOrThrowError APIs.
** 035 CA 20130216 Added support for ERROR-OBJECT-DETAIL attribute.
** 036 CA 20130221 getType getter for TYPE attribute was renamed to getResourceType.
** 037 CS 20130221 Added conversion support for _msg() function
** 038 CA 20130222 Added missing setter for ERROR attribute.
** 039 OM 20130304 Refactored isValid and isUnknown of WrappedResource to valid
** and unknown.
** 040 VMN 20130411 Added boolean parameter modal to recordOrShowError() method.
** 041 EVK 20130413 Implemented method getErrorNumberAtIndex
** 042 CA 20130612 ErrorConditionException's generated in recordOrThrowError will carry
** as cause a NumberedException identifying the error number and the
** associated message.
** 043 OM 20130606 Handle for soapFault set to null for the moment. This is a bug that
** manifests with pl/java and should be fixed (with lazy initialization ?)
** 044 EVL 20130718 Adding new method to display message and abend. Also it silently
** returns if NO-ERROR option is specified.
** 045 VMN 20130731 Adding asMsg parameter to recordOrThrowError() method.
** 046 CA 20130909 Added "prefix" parameter to recordOrShowError, to allow error
** messages without double-asterisk prefix.
** Added "isError" parameter to recordOrShowError, to allow messages to
** be recorded without setting the pending error flag.
** 047 CA 20130920 Fixed H046 - if ERROR attribute is set to false by recordOrShowError,
** than do not clean the error message list.
** 048 EVK 20130922 Added new addError version which will conditionally add the "**" to
** the error message, based on the new prefix parameter.
** 049 CA 20130927 Static proxies need the unknown() API. Resource type is determined
** from LegacyResource constants.
** 050 CA 20131219 Added setDontClean static implementation. Fixed implementation of
** isErrorFlag.
** 051 EVL 20131119 Separating error output from client implementation.
** 052 CA 20140107 Made setErrorObjectDetail package private. The WorkArea.soapFault
** must be initialized to null by default, to allow ErrorManager usage
** from pl/java (for this reason, setErrorObjectDetail will create a new
** handle and assign it to WorkArea.soapFault).
** 053 GES 20140121 Added noRecordOrThrowError().
** 054 CA 20140513 Added a weight for the context-local var, to ensure predetermined
** order during context reset.
** 055 VIG 20140701 Added separate error statuses for the current and pending statuses.
** Added class ErrorStatus, that corresponds to ERROR-STATUS handle.
** Removed clearList and ServerDataAccess.setError methods.
** 056 SVL 20140709 Prefix field was added to ErrorEntry.
** 057 VIG 20140709 Changed implementation of getElementAtIndex methods (_MSG),
** Added raisedConditions - list of error numbers and corresp. methods.
** 058 OM 20141015 Eliminate NPE in addRaisedCondition() when argument is null.
** Fixed postprocessOnAbend(String[] text) message.
** 059 SVL 20141209 Added ability to display error messages without finishing dot.
** 060 CA 20150308 Added support for client-side error data storage. This allows
** temporary disabling the error-management without a server-side trip.
** 061 CA 20150502 The resource ID needs to be kept at the resource, so it can be
** accessed after the resource is deleted.
** 062 OM 20151004 Added setter stub for ERROR-STATUS:NUM-MESSAGES attribute.
** 063 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 064 IAS 20160328 Client log support
** 065 HC 20160722 Added recordOrShowWarning() stub forwarding calls to
** recordOrShowError().
** 066 ECF 20160827 Minor refactoring to avoid expense of getting context-local work
** area multiple times in some API calls.
** 067 ECF 20160912 Minor logging change.
** 068 HC 20171003 Implemented SESSION:SUPPRESS-WARNINGS runtime support and the
** ability to handle (display or output depending on the runtime state)
** legacy warnings.
** 069 SVL 20171024 Added displayError(errmsg, noMessageBox).
** 070 CA 20171030 Made addError public.
** 071 GES 20171205 Reimplemented silent error mode using a worker that takes a lambda.
** This reads better in calling code. With this change, the silent mode
** now actually throws the error condition and it gets caught in the
** worker, which is more functionally correct because it aborts further
** processing on errors in the calling context rather than using
** isPendingError() to bypass other work. Anything with side effects
** causes issues in the old approach.
** ECF 20171212 Added method to clear pending error status. In silent error mode, do
** not generate NumberedException in recordOrThrowError, in order to
** avoid the overhead of stack trace generation.
** GES 20171220 Changed throwError() to set a force flag on the raised error
** condition such that the silent mode working will re-throw the error
** instead of supressing it. This is needed for some deferred error
** processing cases found in the persistence layer.
** 072 CA 20180503 Added SESSION:SYSTEM-ALERT-BOXES.
** 073 ECF 20190326 Minor performance improvement.
** 074 OM 20190611 Added id/parameters based APIs.
** 075 OM 20190720 Updated id/parameters error list.
** 076 OM 20191014 Added ERROR, schema validation and SAVE-ROW-CHANGES error messages.
** 077 IAS 20191022 Added DEBUG-ALERT support to the displayError method.
** 078 CA 20191119 Added nestedSilent(), a FWD-internal helper, which allows execution
** of code in silent-mode, even if the caller is already in NO-ERROR
** mode (and preserving any previous error state).
** 079 CA 20200324 More fixes related to OO-like error processing via AppError.
** CA 20200406 mustManageLegacyError must delegate the call to the server side.
** ERROR conditions generated by the FWD runtime must be deferred, so
** that they can be emitted by the client-side, too.
** SBI 20200430 Added UnableToSetComHandleProperty and WhileProcessingComponentProperty
** errors.
** CA 20200503 Added ERROR:ERROR-STRING stubs.
** 080 GES 20200803 Rework to expose ErrorHelper so that server-side callers can avoid context-local
** lookups. Fixed swapped parameters to ErrorConditionException constructor which
** was treating an error code as a key code.
** GES 20200820 Safety code in case ErrorHelper is requested on the client side.
** GES 20200821 Added ErrorHelper.isSuppressWarnings().
** ME 20201014 Do not append error number if zero, there are system errors thrown with number
** set to zero.
** OM 20201020 Renamed warning to errorsAsWarnings and added accessors in ErrorHelper.
** Added some missing error messages.
** OM 20201120 Changed method name to match no-bean prefixes buffer convention.
** 20201125 displayError is no-op in silent mode.
** OM 20201203 Fixed handling of READ/ONLY attributes.
** ME 20210128 Add helper method for 'invalid parameter' error and push all messages from an
** OO error up the stack for silent execution.
** CA 20210216 Allow the nestedSilent caller to process the errors logged during the invocation.
** VVT 20210322 Fixed after the review. See #4880-107.
** ME 20210322 Add an overload for recordOrThrowError to avoid adding the dot between message
** and number.
** OM 20210312 Added error messages related to LOBs and LONGCHAR variables.
** CA 20210629 Added error messages related to indexed dereferenced fields (lvalue and rvalue).
** OM 20210809 Added error messages for serialization of tables to JSON objects.
** CA 20210928 Added the text for error 12355.
** CA 20211001 Added setter support for ERROR-STATUS:ERROR.
** IAS 20211223 Special processing of errors raised by UDFs.
** OM 20220112 Added error messages related to SKIP-DELETED-RECORD attribute.
** CA 20220120 In NO-ERROR mode, delete any legacy exception once it was consumed.
** OM 20220217 Added error messages related to ATTACH-DATA-SOURCE method.
** CA 20220304 An ERROR condition being raised from within a block which can catch SysError must
** be morphed into a legacy SysError exception, and not raise an ERROR condition.
** OM 20220328 Added new JSON and XML deserialization errors (related to unique idx validation).
** ME 20220512 Do not show warning messages if suppressed for the session.
** CA 20220524 recordForLegacyThrow must rebuild the SysError reference if it got deleted.
** OM 20220525 Added text for error no. 15376.
** CA 20220526 Added displayAndRaiseError, which shows error texts and raises an ERROR condition
** regardless of NO-ERROR status (required by DELETE OBJECT extProg, when there are
** buffer validation errors).
** OM 20220527 Added text for error no. 12298. Flattened the stack-trace a bit.
** CA 20220531 displayError must throw a legacy exception, if the block is configured it.
** OM 20220531 Added some missing errors related to sequences.
** OM 20220604 Added some missing errors related to dereferenciation support.
** CA 20220614 recordOrShowError will raise legacy OO exceptions if the ERROR flag is set and
** the surrounding code allows for it.
** VVT 20220911 ErrorManager$ErrorEntry must be serializable. See #6694-16.
** VVT 20221003 CommonHandle.getResourceType() method renamed to resourceType() to prevent conflicts
** with namesakes in DMO. See #6694.
** CA 20221012 Fixed JSON import of tables with extent fields, when the source is a JsonObject.
** CA 20220524 recordForLegacyThrow must rebuild the SysError reference if it got deleted.
** OM 20220525 Added text for error no. 15376.
** CA 20220526 Added displayAndRaiseError, which shows error texts and raises an ERROR condition
** regardless of NO-ERROR status (required by DELETE OBJECT extProg, when there are
** buffer validation errors).
** OM 20220527 Added text for error no. 12298. Flattened the stack-trace a bit.
** CA 20220531 displayError must throw a legacy exception, if the block is configured it.
** OM 20220531 Added some missing errors related to sequences.
** OM 20220604 Added some missing errors related to dereferenciation support.
** CA 20220613 Allow OO exceptions to propagate to the remote side, when exiting the root block
** on a appserver call.
** OM 20220706 New error message related to PRIMARY attribute validation.
** VVT 20220913 CommonHandle.getResourceType() method renamed to resourceType() to prevent conflicts
** with namesakes in DMO. See #6694.
** IAS 20221003 Fixed error message for error 3131
** IAS 20221006 Reverted error message for error 3131 change.
** IAS 20221102 Added message for error 12323.
** IAS 20221100 Added message for warning 13095.
** IAS 20221112 Added recordOrShowError(int id, boolean isError, boolean isModal, String... params)
** CA 20230110 When NO-ERROR is used, it is assumed that an ERROR condition will be added to the
** 'errorList' in a significant less cases than the number of calls - thus let this
** list be created in a lazy manner, when an ERROR message is actually registered
** via NO-ERROR.
** CA 20230116 Improvement for context-local state resolution.
** 081 GBB 20230410 ErrorWriter.logStackTrace() replaced with rpc call to ErrorDataAccess#writeLog().
** Flag logCallStack added.
** 082 IAS 20230325 Added recordOrShowWarning(int id, String... params) method. Added
** error messages for 13184 and 15357.
** IAS 20230405 Added error message for error 14282.
** 083 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 084 GBB 20230608 LOG-MANAGER constant for a subsys renamed.
** 085 TT 20230614 Added the functionality for session:suppress-warnings-list.
** 086 CA 20230907 Added missing 10515 error text.
** 087 GBB 20231101 Hardcoded true replaced by boolean isFromLogManager in recordOrThrowError.
** 088 OM 20231110 Prevented downstream NPE caused by log accessing non-existing user context.
** 089 OM 20240110 Added message no. 12349.
** 090 DDF 20240423 Added an overload for showErrorAndAbend which forces an error to be displayed
** even if it's silent and does not mark it as an error (it is not recorded).
** 091 DDF 20240523 Added a displayError() method that receives an additional parameter.
** DDF 20240524 isError flag can be influenced by the existence of a CATCH block in the
** current block, removed unnecessary displayError() method.
** DDF 20240524 Modified condition for setting the isError flag in recordOrShowError().
** DDF 20240527 Added recordOrShowDatabaseError().
** 092 CA 20240609 An ERROR condition must be triggered in the caller (outside of the top-level
** block) is current file has ROUTINE-/BLOCK-LEVEL statetements and this condition
** can't be morphed in a structured exception in the current stacktrace.
** 093 GBB 20240610 noRecordOrThrowErrorWorker() should not log errors. Implementing
** RemoteErrorData.handleSoupFault() to be handled server-side.
** 094 GBB 20240621 New appserver connect error msgs added to numbered errors.
** Adding static throwErrorConditionException for network interface impl.
** 095 DDF 20240814 Added handleForwardPending(), getErrorStatus() and processLegacyErrorException()
** methods.
** DDF 20240822 Moved processLegacyErrorException() method from ErrorHelper to ErrorManager.
** 096 AS 20240911 Added 12311 and 12314 error support.
** 097 TJD 20230224 isSilent should be checked before calling <code>displayError</code>, not inside it.
** TJD 20240422 checking isSilent restored in <code>displayError</code>, temporary disable
** isSilent in <code>throwError</code> instead
** TJD 20240423 No need to restore silent mode if it was not set
** 098 OM 20241001 Added tenant-related error messages.
** 099 OM 20240925 The error message was not parametrised in recordOrShowError().
** 100 AL2 20241108 Reduce logging level if the error is going to be caught by a catch block.
** 101 CA 20240625 Certain database-level ERROR conditions (like 132/buffer already exists with)
** must be processed as if BLOCK-LEVEL ON ERROR UNDO, THROW is set (in other words,
** it can get morphed into a strcutured exceptions).
** 102 OM 20241128 A static method cannot be final. (It cannot be overridden). Formatting fixes.
** 103 AS 20241204 Do not log caught errors when using LOG-MANAGER.
** 104 ICP 20241202 Added buffer copy and temp table copy error messages.
** 105 FER 20241212 Added a displayError() method is facilitates an error box with custom msg.
** 106 ICP 20250129 Used logical constants to leverage cached instances.
** 107 GBB 20250403 Support for the new multi-session appserver.
** 108 ES 20250304 Generate a StopError legacy exception when error code is
** EXIT_CODE_PROCEDURE_NOT_FOUND
*/
/*
** 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.util;
import java.io.*;
import java.util.*;
import java.util.function.*;
import java.util.logging.*;
import com.goldencode.p2j.*;
import com.goldencode.p2j.net.*;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.util.appserver.*;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.proxy.*;
/**
* A context-local class providing static methods to support the
* <code>ERROR-STATUS</code> and <code>NO-ERROR</code> Progress features.
* <p>
* The following is the mapping of Progress features to Java:
* <p>
* <pre>
* Progress Feature Type Java Feature
* --------------------------- ------------ ----------------------------
* error-status:error attribute {@link #isError}
* error-status:num-messages attribute {@link #numErrors}
* error-status:get-message method {@link #getErrorText}
* error-status:get-number method {@link #getErrorNumber}
* </pre>
* <p>
* In order to properly use this class, all processing which must be handled
* on a <code>NO-ERROR</code> basis must be run as delegated code block processed by
* {@link #silent}. This code catches any error exception so that the business logic
* aborts at the point of the error but the exception does not reach the block level.
* All called code that honors <code>NO-ERROR</code> must be aware of silent error
* mode. This can be done via the following methods (in a library):
* <p>
* <ol>
* <li> If a simple return from a library is easily possible, delegate
* processing to this class using {@link #recordOrThrowError}. After
* calling this method, return silently from the library.
* <li> Do it yourself.
* <ul>
* <li> Use {@link #isSilentError} to determine if the silent mode is
* enabled.
* <li> Use {@link #addError} to add an error to the list if silent
* mode is enabled.
* <li> Throw {@link ErrorConditionException}.
* <li> This may be useful if a simple silent return is not easy to do
* as one can use the {@link ErrorUnwindException} to unwind the
* stack to the point in the library where a silent return is
* possible. This exception must be caught in the library code
* and will not be caught in the client application. Use of
* this exception should be completely hidden from the client
* application.
* </ul>
* </ol>
* <p>
* On the server, the silent error mode flag is saved at every top level
* scope (see {@link TransactionManager#pushScope}) and it is restored at
* the end of that scope (see {@link TransactionManager#popScope()}).
* This is done so that "nested" NO-ERROR conditions (e.g. during a RUN
* statement on an internal or external procedure or an assignment statement
* with a call to a user defined function) are maintained properly. In
* particular, the called procedure or function does NOT execute as if in
* silent mode unless the code there is so bracketed. However, the one
* exception to this is that the RETURN ERROR behavior implemented by the
* <code>TransactionManager</code> is aware of whether or not silent error
* mode was enabled in the caller.
* <p>
* It must be noted that Progress seems to provide a single global instance of
* ERROR-STATUS handle and all its data. Thus this data can be seen across
* call/return boundaries and is only modified at very specific points. for
* example, the error flag is only changed at the end of a NO-ERROR statement.
* Likewise, the previous list of errors can be read until the first error
* occurs after a NO-ERROR statement starts. This deferred clearing of the
* error list is implemented using the pending error flag. When that flag is
* set, the error list is cleared. If no errors occur in the NO-ERROR statement
* then the error list is cleared at the same time the error flag is cleared
* at the end of the NO-ERROR statement.
*/
public class ErrorManager
{
/** Capacity of error conditions stack for _MSG function */
private static final int ERR_CONDITIONS_CAPACITY = 25;
/** Logger (safe as a JVM-wide value rather than as context-local). */
private static final CentralLogger LOG = CentralLogger.get(ErrorManager.class);
/** Stores context-local state variables. */
private static final ContextContainer work = new ContextContainer();
/**
* Server-side it's a local instance.
* Client-side it's a remote interface accessing the error data on the server.
*/
private static RemoteErrorData server = new ServerErrorDataAccessor();
/** The multi-session appserver manager. */
private static final MultiSessionAppserverManager msaManager = MultiSessionAppserverManager.getInstance();
/** Handler for ignored errors. */
private static final RemoteErrorData suppressedHandler = new SuppressedErrorDataAccessor();
/** Headless mode */
private static boolean headless = false;
/** Error writer object instance for client. */
private static ErrorWriter errorWriter = null;
/** Does this instance belong to the server side part. */
private static boolean serverSide = true;
/**
* Get a the instance for the ERROR-STATUS system handle. Is obtained using
* a call to the {@link StaticProxy#obtain(Class, Class[])}, using the
* {@link CommonErrorStatus} interface and its methods implemented by these
* classes: {@link ErrorManager}.
*
* @return See above.
*/
public static handle asHandle()
{
WorkArea wa = work.get();
Class[] classes = { ErrorManager.class };
CommonErrorStatus proxy = StaticProxy.obtain(CommonErrorStatus.class, classes);
if (wa.id == null)
{
wa.id = handle.resourceId(proxy);
}
return new handle(proxy);
}
/**
* Get this resource's ID.
*
* @return The resource's ID.
*/
public static Long id()
{
WorkArea wa = work.get();
return wa.id;
}
/**
* Set this resource's ID.
* <p>
* This is a no-op for system handles.
*
* @param id
* The resource's ID.
*/
public static void id(long id)
{
// no-op
}
/**
* Implementation for the {@link WrappedResource#valid()} API.
*
* @return Always true.
*/
public static boolean valid()
{
return true;
}
/**
* Implementation for the {@link WrappedResource#unknown()} API.
*
* @return Always false.
*/
public static boolean unknown()
{
return false;
}
/**
* API needed to implement read-only attribute assignment (a 4GL "feature").
*
* @param attribute
* The attribute's name.
*
* @see handle#readOnlyError(handle, String)
*/
public static void readOnlyError(String attribute)
{
handle.readOnlyError(asHandle(), attribute);
}
/**
* API needed to implement read-only attribute assignment (a 4GL "feature").
*
* @param attribute
* The attribute's name.
* @param expr
* The value which is attempted to be assigned to the read-only attribute.
*
* @see handle#readOnlyError(handle, String, Object)
*/
public static void readOnlyError(String attribute, Object expr)
{
handle.readOnlyError(asHandle(), attribute);
}
/**
* Initialize <code>ErrorManager</code> for headless mode.
*/
public static void setHeadless()
{
headless = true;
}
/**
* Set the state of SESSION:SYSTEM-ALERT-BOXES attribute.
*
* @param on
* The new state for SESSION:SYSTEM-ALERT-BOXES attribute.
*/
public static void setSystemAlertBoxes(boolean on)
{
work.obtain().systemAlertBoxes = on;
}
/**
* Get the state of SESSION:SYSTEM-ALERT-BOXES attribute.
*
* @return the state of SESSION:SYSTEM-ALERT-BOXES attribute.
*/
public static boolean isSystemAlertBoxes()
{
return server.isSystemAlertBoxes();
}
/**
* Initializes the proper error data access on the client.
*
* @param errorServer
* The remote error interface
*/
public static void initClient(RemoteErrorData errorServer)
{
server = errorServer;
serverSide = false;
}
/**
* Initializes the proper error writer on the client.
*
* @param errorWriterInitial
* Error writer implementation for client side.
*/
public static void initErrorWriter(ErrorWriter errorWriterInitial)
{
errorWriter = errorWriterInitial;
}
/**
* Checks if the client is running in batch mode.
*
* @return <code>true</code> for batch mode, <code>false</code> otherwise.
*
*/
public static boolean isInBatchMode()
{
return errorWriter != null && errorWriter.isInBatchMode();
}
/**
* Stops the associated client session by generating UnstoppableExitException.
*/
public static void stopBatchSession()
{
if (errorWriter != null)
{
errorWriter.stopBatchSession();
}
}
/**
* Reverts the error data access on the client.
*/
public static void resetClient()
{
server = new ServerErrorDataAccessor();
}
/**
* Reports if silent error mode has been enabled.
*
* @return <code>true</code> if silent mode is enabled, otherwise
* <code>false</code>.
*/
public static boolean isSilentError()
{
return server.isSilent();
}
/**
* Queries the state of <code>ignore</code> mode.
*
* @return <code>true</code> if ignore mode is enabled, otherwise <code>false</code>.
*/
public static boolean isIgnore()
{
return isIgnoreWorker(work.obtain());
}
/**
* Execute the given block while supressing normal error processing by temporarily enabling the
* ignore flag.
*
* @param code
* The code to execute.
*/
public static void ignore(Runnable code)
{
ignoreWorker(code, work.obtain());
}
/**
* Execute the given block while suppressing ALL error processing.
*
* @param code
* The code to execute.
*/
public static void nullErrors(Runnable code)
{
RemoteErrorData old = server;
server = suppressedHandler;
try
{
ignore(code);
}
finally
{
server = old;
}
}
/**
* Reports if warning mode has been enabled. When in warning mode, error
* messages are reported to the UI using an alert box.
*
* @return <code>true</code> if warning mode is enabled, otherwise
* <code>false</code>.
*/
public static boolean isWarningMode()
{
return work.obtain().errorsAsWarnings;
}
/**
* Worker method that executes a given block of code with silent error mode turned on for
* the duration of the code block. Before the code block is executed, silent error mode
* is turned on and after the code block completes (normally or abnormally), silent error
* mode will be disabled.
* <p>
* This method can only be executed on the server side.
*
* @param code
* The block of code to execute in silent mode.
*
* @return The value of the ERROR-STATUS:ERROR flag when the execution is complete.
*/
public static boolean silent(Runnable code)
{
return silentWorker(code, (ServerErrorDataAccessor) server, work.obtain());
}
/**
* Worker method that executes a given block of code with silent error mode turned on for
* the duration of the code block. Before the code block is executed, silent error mode
* is turned on and after the code block completes (normally or abnormally), silent error
* mode will be disabled.
* <p>
* This method can only be executed on the server side. It will backup and restore the current
* {@link WorkArea#errorStatus}, to allow for nested silent-mode usages (internally by FWD).
*
* @param code
* The block of code to execute in silent mode.
*
* @return The value of the ERROR-STATUS:ERROR flag when the execution is complete.
*/
public static boolean nestedSilent(Runnable code)
{
return nestedSilent(code, null);
}
/**
* Worker method that executes a given block of code with silent error mode turned on for
* the duration of the code block. Before the code block is executed, silent error mode
* is turned on and after the code block completes (normally or abnormally), silent error
* mode will be disabled.
* <p>
* This method can only be executed on the server side. It will backup and restore the current
* {@link WorkArea#errorStatus}, to allow for nested silent-mode usages (internally by FWD).
*
* @param code
* The block of code to execute in silent mode.
* @param errorProcessor
* A consumer of the logged errors in silent mode.
*
* @return The value of the ERROR-STATUS:ERROR flag when the execution is complete.
*/
public static boolean nestedSilent(Runnable code, BiConsumer<String, Integer> errorProcessor)
{
return nestedSilent(null, code, errorProcessor);
}
/**
* Worker method that executes a given block of code with silent error mode turned on for
* the duration of the code block. Before the code block is executed, silent error mode
* is turned on and after the code block completes (normally or abnormally), silent error
* mode will be disabled.
* <p>
* This method can only be executed on the server side. It will backup and restore the current
* {@link WorkArea#errorStatus}, to allow for nested silent-mode usages (internally by FWD).
*
* @param wa
* The context-local state.
* @param code
* The block of code to execute in silent mode.
* @param errorProcessor
* A consumer of the logged errors in silent mode.
*
* @return The value of the ERROR-STATUS:ERROR flag when the execution is complete.
*/
public static boolean nestedSilent(WorkArea wa, Runnable code, BiConsumer<String, Integer> errorProcessor)
{
ServerErrorDataAccessor srv = (ServerErrorDataAccessor) server;
if (wa == null)
{
wa = work.obtain();
}
ErrorStatus saveError = wa.errorStatus;
ErrorStatus savePending = new ErrorStatus();
savePending.set(wa.pendingErrorStatus);
wa.errorStatus = new ErrorStatus();
wa.pendingErrorStatus.initialize();
forwardPending(wa);
boolean saveSilent = srv.isSilent();
// enable silent error processing
srv.setSilent(wa, true);
boolean hadError = false;
try
{
code.run();
}
catch (ErrorConditionException err)
{
// normally we just eat the exception because it is being used to abort processing
// exactly where the error occurred, which will restart processing after the given
// code block; however, there is an exception for some deferred error processing use
// cases where we must bypass silent error mode (throwError(NumberedException, boolean))
if (err.isForce())
{
// don't honor silent mode, no suppression of errors here
throw err;
}
}
finally
{
// disable further silent error processing
srv.setSilent(wa, false);
forwardPending(wa);
hadError = wa.errorStatus.error;
if (errorProcessor != null && wa.errorStatus.errorList != null)
{
for (Object o : wa.errorStatus.errorList)
{
ErrorEntry entry = (ErrorEntry) o;
String msg = (entry.prefix ? "** " : "") + entry.text;
errorProcessor.accept(msg, entry.num);
}
}
wa.errorStatus = saveError;
wa.pendingErrorStatus.set(savePending);
if (saveSilent)
{
// re-enable it if it was enabled
srv.setSilent(wa, true);
}
}
return hadError;
}
/**
* Enables warning mode, during which errors are non-fatal, but are
* reported to the UI of the current context via a call to
* <code>LogicalTerminal.message()</code>.
* <p>
* Each call to this method must be matched with a corresponding call
* to {@link #warningModeDisable}.
*/
public static void warningModeEnable()
{
// enable warning processing
work.obtain().errorsAsWarnings = true;
}
/**
* Disables warning mode.
* <p>
* Each call to this method must be matched with a corresponding, previous
* call to {@link #warningModeEnable}.
*/
public static void warningModeDisable()
{
// disable further warning processing
work.obtain().errorsAsWarnings = false;
}
/**
* Manufacture error text based on the given parameters, if silent error mode is enabled,
* then this error number and the generated error text will be stored in the error list,
* otherwise this generated error text will be used to display modal or modeless box in
* the UI.
* <p>
* The error message(s) will be prefixed with <code>**</code> and the <code>pending</code>
* error flag will be set to <code>true</code>.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param modal
* <code>true</code> if message will be shown in modal dialog.
*/
public static void recordOrShowError(int num, String text, boolean modal)
{
recordOrShowError(new int[] { num }, new String[] { text }, modal, true, true);
}
/**
* Manufacture error text based on the given parameters, if silent error mode is enabled,
* then this error number and the generated error text will be stored in the error list,
* otherwise this generated error text will be used to display modal or modeless box in
* the UI.
* <p>
* The error message(s) will be prefixed with <code>**</code> and the <code>pending</code>
* error flag will be set to <code>true</code>.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param modal
* <code>true</code> if message will be shown in modal dialog.
*/
public static void recordOrShowError(int[] num, String[] text, boolean modal)
{
recordOrShowError(num, text, modal, true, true);
}
/**
* Manufacture error text based on the given parameters, if silent error mode is enabled,
* then this error number and the generated error text will be stored in the error list,
* otherwise this generated error text will be used to display modal or modeless box in
* the UI.
* <p>
* The error message(s) will be prefixed with <code>**</code> depending on the
* <code>prefix</code> parameter and the <code>pending</code> error flag will be set to
* <code>true</code>.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param modal
* <code>true</code> if message will be shown in modal dialog.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the formatted error
* message, <code>false</code> to omit this text.
*/
public static void recordOrShowError(int num, String text, boolean modal, boolean prefix)
{
recordOrShowError(new int[] { num }, new String[] { text }, modal, prefix, true);
}
/**
* Manufacture error text based on the given parameters, if silent error mode is enabled,
* then this error number and the generated error text will be stored in the error list,
* otherwise this generated error text will be used to display modal or modeless box in
* the UI.
* <p>
* The error message(s) will be prefixed with <code>**</code> depending on the
* <code>prefix</code> parameter and the <code>pending</code> error flag will be set to
* <code>true</code>.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param modal
* <code>true</code> if message will be shown in modal dialog.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the formatted error
* message, <code>false</code> to omit this text.
*/
public static void recordOrShowError(int[] num, String[] text, boolean modal, boolean prefix)
{
recordOrShowError(num, text, modal, prefix, true);
}
/**
* Manufacture one or more error texts based on the given parameters, if silent error mode is
* enabled, then these error numbers and the respective generated error text messages will be
* stored in the error list, otherwise this generated error texts will be used to display
* modal or modeless box in the UI.
* <p>
* The error message(s) will be prefixed with <code>**</code> depending on the
* <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
* value specified by the <code>isError</code> flag.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param modal
* <code>true</code> if message will be shown in modal dialog.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the formatted error
* message, <code>false</code> to omit this text.
* @param isError
* The new value for the <code>pending</code> error flag.
*/
public static void recordOrShowError(int num,
String text,
boolean modal,
boolean prefix,
boolean isError)
{
recordOrShowError(num, text, modal, prefix, isError, false);
}
/**
* Manufacture one or more error texts based on the given parameters, if silent error mode is
* enabled, then these error numbers and the respective generated error text messages will be
* stored in the error list, otherwise this generated error texts will be used to display
* modal or modeless box in the UI.
* <p>
* The error message(s) will be prefixed with <code>**</code> depending on the
* <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
* value specified by the <code>isError</code> flag.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param modal
* <code>true</code> if message will be shown in modal dialog.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the formatted error
* message, <code>false</code> to omit this text.
* @param isError
* The new value for the <code>pending</code> error flag.
* @param asMsg
* <code>true</code> to use the given text as the message text else
* <code>false</code> to use buildErrorText.
*/
public static void recordOrShowError(int num,
String text,
boolean modal,
boolean prefix,
boolean isError,
boolean asMsg)
{
recordOrShowError(new int[] { num }, new String[] { text }, modal, prefix, isError, asMsg);
}
/**
* Manufacture one or more error texts based on the given parameters, if silent error mode is
* enabled, then these error numbers and the respective generated error text messages will be
* stored in the error list, otherwise this generated error texts will be used to display
* modal or modeless box in the UI.
* <p>
* The error message(s) will be prefixed with <code>**</code> depending on the
* <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
* value specified by the <code>isError</code> flag.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param modal
* <code>true</code> if message will be shown in modal dialog.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the formatted error
* message, <code>false</code> to omit this text.
* @param isError
* The new value for the <code>pending</code> error flag.
*/
public static void recordOrShowError(int[] num,
String[] text,
boolean modal,
boolean prefix,
boolean isError)
{
recordOrShowError(num, text, modal, prefix, isError, false);
}
/**
* Manufacture one or more error texts based on the given parameters, if silent error mode is
* enabled, then these error numbers and the respective generated error text messages will be
* stored in the error list, otherwise this generated error texts will be used to display
* modal or modeless box in the UI.
* <p>
* The error message(s) will be prefixed with <code>**</code> depending on the
* <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
* value specified by the <code>isError</code> flag.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param modal
* <code>true</code> if message will be shown in modal dialog.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the formatted error
* message, <code>false</code> to omit this text.
* @param isError
* The new value for the <code>pending</code> error flag.
* @param asMsg
* <code>true</code> to use the given text as the message text else
* <code>false</code> to use buildErrorText.
*/
public static void recordOrShowError(int[] num,
String[] text,
boolean modal,
boolean prefix,
boolean isError,
boolean asMsg)
{
recordOrShowError(num, text, modal, prefix, isError, asMsg, true);
}
/**
* Manufacture one or more error texts based on the given parameters, if silent error mode is
* enabled, then these error numbers and the respective generated error text messages will be
* stored in the error list, otherwise this generated error texts will be used to display
* modal or modeless box in the UI.
* <p>
* The error message(s) will be prefixed with <code>**</code> depending on the
* <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
* value specified by the <code>isError</code> flag.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param modal
* <code>true</code> if message will be shown in modal dialog.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the formatted error
* message, <code>false</code> to omit this text.
* @param isError
* The new value for the <code>pending</code> error flag.
* @param asMsg
* <code>true</code> to use the given text as the message text else
* <code>false</code> to use buildErrorText.
* @param addDot
* <code>true</code> if dot(s) are added to the end of the message(s).
*/
public static void recordOrShowError(int[] num,
String[] text,
boolean modal,
boolean prefix,
boolean isError,
boolean asMsg,
boolean addDot)
{
recordOrShowError(num, text, modal, prefix, isError, asMsg, addDot, false);
}
/**
* Manufacture one or more error texts based on the given parameters, if silent error mode is
* enabled, then these error numbers and the respective generated error text messages will be
* stored in the error list, otherwise this generated error texts will be used to display
* modal or modeless box in the UI.
* <p>
* The error message(s) will be prefixed with <code>**</code> depending on the
* <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
* value specified by the <code>isError</code> flag.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param modal
* <code>true</code> if message will be shown in modal dialog.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the formatted error
* message, <code>false</code> to omit this text.
* @param isError
* The new value for the <code>pending</code> error flag.
* @param asMsg
* <code>true</code> to use the given text as the message text else
* <code>false</code> to use buildErrorText.
* @param addDot
* <code>true</code> if dot(s) are added to the end of the message(s).
* @param force
* <code>true</code> if errors which cause a modal dialog should be logged even in
* silent (ignore NO-ERROR clause) and headless modes
*/
public static void recordOrShowError(int[] num,
String[] text,
boolean modal,
boolean prefix,
boolean isError,
boolean asMsg,
boolean addDot,
boolean force)
{
recordOrShowError(num, text, modal, prefix, isError, asMsg, addDot, force, false);
}
/**
* Manufacture one or more error texts based on the given parameters, if silent error mode is
* enabled, then these error numbers and the respective generated error text messages will be
* stored in the error list, otherwise this generated error texts will be used to display
* modal or modeless box in the UI.
* <p>
* The error message(s) will be prefixed with <code>**</code> depending on the
* <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
* value specified by the <code>isError</code> flag.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param modal
* <code>true</code> if message will be shown in modal dialog.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the formatted error
* message, <code>false</code> to omit this text.
* @param isError
* The new value for the <code>pending</code> error flag.
* @param asMsg
* <code>true</code> to use the given text as the message text else
* <code>false</code> to use buildErrorText.
* @param addDot
* <code>true</code> if dot(s) are added to the end of the message(s).
* @param force
* <code>true</code> if errors which cause a modal dialog should be logged even in
* silent (ignore NO-ERROR clause) and headless modes
* @param isFromLogManager
* <code>true</code> if used for LOG-MANAGER, <code>false</code>
*/
public static void recordOrShowError(int[] num,
String[] text,
boolean modal,
boolean prefix,
boolean isError,
boolean asMsg,
boolean addDot,
boolean force,
boolean isFromLogManager)
{
recordOrShowError(num, text, modal, prefix, isError, asMsg, addDot, force, isFromLogManager, false);
}
/**
* Manufacture one or more error texts based on the given parameters, if silent error mode is
* enabled, then these error numbers and the respective generated error text messages will be
* stored in the error list, otherwise this generated error texts will be used to display
* modal or modeless box in the UI.
* <p>
* The error message(s) will be prefixed with <code>**</code> depending on the
* <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
* value specified by the <code>isError</code> flag.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param modal
* <code>true</code> if message will be shown in modal dialog.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the formatted error
* message, <code>false</code> to omit this text.
* @param isError
* The new value for the <code>pending</code> error flag.
* @param asMsg
* <code>true</code> to use the given text as the message text else
* <code>false</code> to use buildErrorText.
* @param addDot
* <code>true</code> if dot(s) are added to the end of the message(s).
* @param force
* <code>true</code> if errors which cause a modal dialog should be logged even in
* silent (ignore NO-ERROR clause) and headless modes
* @param isFromLogManager
* <code>true</code> if used for LOG-MANAGER, <code>false</code>
* @param isDatabaseError
* <code>true</code> to allow errors related to database influence the value of the
* isError flag, <code>false</code> otherwise. THis flag must only be used for
* database related errors.
*/
private static void recordOrShowError(int[] num,
String[] text,
boolean modal,
boolean prefix,
boolean isError,
boolean asMsg,
boolean addDot,
boolean force,
boolean isFromLogManager,
boolean isDatabaseError)
{
if (isIgnore())
return;
if (num.length != text.length)
{
throw new IllegalArgumentException("Unbalanced error number and text message pairs");
}
StringBuilder buf = new StringBuilder();
String sep = System.getProperty("line.separator");
boolean silent = isSilentError();
// isError flag can be influenced if there is a CATCH block in the current block,
// making it possible to turn it into true even if it is specified as false.
if (isDatabaseError)
{
isError = isError || hasLegacyError();
}
boolean manageLegacyError = !silent && isError && server.mustThrowLegacyError();
for (int i = 0; i < num.length; i++)
{
String errmsg = asMsg ? text[i] : buildErrorText(num[i], text[i], prefix, addDot);
if (i > 0)
{
buf.append(sep);
}
buf.append(errmsg);
if (manageLegacyError)
{
server.recordForLegacyThrow(errmsg, num[i]);
}
else if (silent)
{
if (i == 0)
{
// this may reset the error list so it must be done BEFORE we add the new error(s) to the list;
// this only needs to be done the first time through this loop
server.setPending(isError);
}
server.addRecord(new ErrorEntry(num[i], errmsg));
}
}
String masterMsg = buf.toString();
if (manageLegacyError)
{
if (!isFromLogManager && !hasLegacyError())
{
writeLog(masterMsg);
}
throw new DeferredLegacyErrorException();
}
log(masterMsg, silent);
if ((!silent && !isHeadless()) || force)
{
if (errorWriter != null)
{
if (modal && server.isSystemAlertBoxes())
{
errorWriter.messageBox(masterMsg, "Error", force);
}
else
{
if (!isFromLogManager && !hasLegacyError())
{
writeLog(masterMsg);
}
errorWriter.message(masterMsg);
}
}
else
{
if (!isFromLogManager && !hasLegacyError())
{
writeLog(masterMsg);
}
}
addRaisedCondition(num);
}
}
/**
* Manufacture warning text based on the given parameters.
*
* @param id
* The id of the message. The associated text is filled with {@code params} tokens.
* @param params
* The parameters for the message.
*/
public static void recordOrShowWarning(int id, String... params)
{
ErrorManager.recordOrShowError(new int[] { id },
new String[] { replaceTokens(id, params) },
true, // modal
false, // no prefix
false, // error
false, // not as message
true); // add final dot
}
/**
* Manufacture one or more warning texts based on the given parameters.
* <p>
* The error message(s) will be prefixed with <code>**</code> depending on the
* <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
* value specified by the <code>isError</code> flag.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the formatted error
* message, <code>false</code> to omit this text.
* @param isError
* The new value for the <code>pending</code> error flag.
* @param asMsg
* <code>true</code> to use the given text as the message text else
* <code>false</code> to use buildErrorText.
* @param addDot
* <code>true</code> if dot(s) are added to the end of the message(s).
*/
public static void recordOrShowWarning(int num,
String text,
boolean prefix,
boolean isError,
boolean asMsg,
boolean addDot)
{
// TODO: Currently this method just forwards the call to recordOrShowError(),
// unless warnings are suppressed for the session.
// Depending on the expected semantics of the method, its name may change to
// showWarning.
boolean suppressWarningCode = work.get().suppressWarningsSet.contains(num);
if (!isSuppressWarnings() && !suppressWarningCode)
{
recordOrShowError(new int[] {num}, new String[] {text}, true, prefix, isError, asMsg, addDot);
}
}
/**
* Manufacture one or more warning texts based on the given parameters.
* <p>
* The error message(s) will be prefixed with <code>**</code> depending on the
* <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
* value specified by the <code>isError</code> flag.
* <p>
* Used by LOG-MANAGER exclusively.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the formatted error
* message, <code>false</code> to omit this text.
*/
static void recordOrShowLogManagerWarning(int num, String text, boolean prefix)
{
// TODO: Currently this method just forwards the call to recordOrShowError(),
// unless warnings are suppressed for the session.
// Depending on the expected semantics of the method, its name may change to
// showWarning.
if (!isSuppressWarnings())
{
recordOrShowError(new int[] {num}, new String[] {text}, true, prefix, false, false, false, false,
true);
}
}
/**
* Manufacture error text based on the given parameters, if silent error mode is enabled, then
* this error number and the generated error text will be stored in the error list, otherwise
* this generated error text will be used to display modal or modeless box in the UI.
*
* @param id
* The id of the message. The associated text is filled with {@code params} tokens.
* @param params
* The parameters for the message.
*/
public static void recordOrShowError(int id, String... params)
{
ErrorManager.recordOrShowError(new int[] { id },
new String[] { replaceTokens(id, params) },
true, // modal
false, // no prefix
true, // error
false, // not as message
true); // add final dot
}
/**
* Manufacture error text based on the given parameters, if silent error mode is enabled, then
* this error number and the generated error text will be stored in the error list, otherwise
* this generated error text will be used to display modal or modeless box in the UI.
*
* @param id
* The id of the message. The associated text is filled with {@code params} tokens.
* @param isError
* The new value for the <code>pending</code> error flag.
* @param params
* The parameters for the message.
*/
public static void recordOrShowError(int id, boolean isError, String... params)
{
ErrorManager.recordOrShowError(new int[] { id },
new String[] { replaceTokens(id, params) },
true, // modal
false, // no prefix
isError, // error
false, // not as message
true); // add final dot
}
/**
* Manufacture error text based on the given parameters, if silent error mode is enabled, then
* this error number and the generated error text will be stored in the error list, otherwise
* this generated error text will be thrown (and execution of the procedure halted).
*
* @param id
* The id of the message. The associated text is filled with {@code params} tokens.
* @param isModal
* <code>true</code> if message will be shown in modal dialog.
* @param isError
* The new value for the <code>pending</code> error flag.
* @param params
* The parameters for the message. If there are less than two token-parameters
* use empty Strings ({@code ""}) to make the compiler select the proper signature.
*/
public static void recordOrShowError(int id, boolean isError, boolean isModal, String... params)
{
ErrorManager.recordOrShowError(new int[] { id },
new String[] { replaceTokens(id, params) },
isModal, // not modal
false, // no prefix
isError, // not an error
false, // not as message
true); // add final dot
}
/**
* Manufacture one or more error texts based on the given parameters, if silent error mode is
* enabled, then these error numbers and the respective generated error text messages will be
* stored in the error list, otherwise this generated error texts will be used to display
* modal or modeless box in the UI.
* <p>
* The error message(s) will be prefixed with <code>**</code> depending on the
* <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
* value specified by the <code>isError</code> flag.
* <p>
* This is a specific method for errors related to the database. Such errors can influence
* the value of the isError flag, for example a CATCH block in the current block will
* force the isError flag to become <code>true</code>.
* <p>
* This method will call the recordOrShowError() method with the isError parameter set to
* <code>false</code> and isDatabaseError set to <code>true</code>.
*
* @param id
* The error number.
* @param message
* The error message.
* @param modal
* <code>true</code> if message will be shown in modal dialog.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the formatted error
* message, <code>false</code> to omit this text.
*/
public static void recordOrShowDatabaseError(int id, String message, boolean modal, boolean prefix)
{
recordOrShowDatabaseError(new int[] { id }, new String[] { message }, modal, prefix);
}
/**
* Manufacture one or more error texts based on the given parameters, if silent error mode is
* enabled, then these error numbers and the respective generated error text messages will be
* stored in the error list, otherwise this generated error texts will be used to display
* modal or modeless box in the UI.
* <p>
* The error message(s) will be prefixed with <code>**</code> depending on the
* <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
* value specified by the <code>isError</code> flag.
* <p>
* This is a specific method for errors related to the database. Such errors can influence
* the value of the isError flag, for example a CATCH block in the current block will
* force the isError flag to become <code>true</code>.
* <p>
* This method will call the recordOrShowError() method with the isError parameter set to
* <code>false</code> and isDatabaseError set to <code>true</code>.
*
* @param ids
* The error numbers.
* @param messages
* The error messages.
* @param modal
* <code>true</code> if message will be shown in modal dialog.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the formatted error
* message, <code>false</code> to omit this text.
*/
public static void recordOrShowDatabaseError(int[] ids, String[] messages, boolean modal, boolean prefix)
{
recordOrShowError(ids, messages, modal, prefix, false, false, true, false, false, true);
}
/**
* Display error text and throw an exception based upon the given exception.
* Manufactures error text based on the given parameters, if silent error mode is enabled, then
* this error number and the generated error text will be stored in the error list, otherwise
* this generated error text will be used to display modal or modeless box in the UI.
*
* @param id
* The id of the message. The associated text is filled with {@code params} tokens.
* @param params
* The parameters for the message.
*/
public static void throwError(int id, String... params)
{
ErrorManager.throwError(new NumberedException(replaceTokens(id, params), id, false), isSilent());
}
/**
* Manufacture error text based on the given parameters; after displaying
* the error text on the client, a pause is forced before stopping the
* application.
*
* @param num
* The error number.
* @param text
* The text of the error message.
*
* @throws StopConditionException
* always, unless the error manager is in ignore mode.
*/
public static void showErrorAndAbend(int num, String text)
throws StopConditionException
{
if (isIgnore())
return;
String errmsg = buildAbendText(num, text);
try
{
if (!isHeadless())
{
// display the error message (because of pause processing, an
// endkey, error... even stop condition can be generated here)
displayError(errmsg);
if (errorWriter != null)
{
errorWriter.pause();
}
}
}
finally
{
postprocessOnAbend(errmsg);
}
}
/**
* Manufacture error text based on the given parameters; After displaying
* the error text on the client, a pause is forced ignoring the NO-ERROR
* clause and headless modes. At the end, a StopErrorCondition is thrown.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
*
* @throws StopConditionException
* always, unless the error manager is in ignore mode.
*/
public static void showErrorAndAbend(int num, String text, boolean prefix)
throws StopConditionException
{
if (isIgnore())
{
return;
}
String errmsg = buildErrorText(num, text, prefix, true);
try
{
recordOrShowError(new int[] { num },
new String[] { text },
true,
prefix,
false,
false,
true,
true,
false);
}
finally
{
postprocessOnAbend(errmsg);
}
}
/**
* Manufacture error text based on the given parameters; after displaying the error text on
* the client, the application is stopping.
*
* @param num
* The error number.
* @param text
* The text of the error message.
*
* @throws StopConditionException
* always, unless the error manager is in ignore mode.
*/
public static void conditionalShowErrorAndAbend(int num, String text)
throws StopConditionException
{
// If the NO-ERROR option was specified this method just silently retuns doing nothing
if (isSilentError())
{
return;
}
if (isIgnore())
{
return;
}
String errmsg = buildAbendText(num, text);
try
{
if (!isHeadless())
{
// display the error message (because of pause processing, an
// endkey, error... even stop condition can be generated here)
displayError(errmsg);
}
}
finally
{
postprocessOnAbend(errmsg);
}
}
/**
* Perform postprocessing for {@link #showErrorAndAbend(int, String)}.
*
* @param errmsg
* Message text.
*/
private static void postprocessOnAbend(String errmsg)
{
throw new StopConditionException(errmsg);
}
/**
* Manufacture one or more error texts based on the given parameters, if
* silent error mode is enabled, then these error numbers and the
* respective generated error text messages will be stored in the error
* list, otherwise this generated error texts will be used to display a
* modal, alert (error) box in the UI.<p>
* After error messages has been processed, a STOP condition is raised.
*
* @param num
* The error numbers.
* @param text
* The text of the error messages.
*
* @throws StopConditionException
* always, unless the error manager is in ignore mode.
*/
public static void showErrorAndAbend(int[] num, String[] text)
throws StopConditionException
{
if (isIgnore())
{
return;
}
try
{
recordOrShowError(num, text, true);
}
finally
{
postprocessOnAbend(text);
}
}
/**
* Raise an error condition and optionally record the given error (in silent mode).
* <p>
* In silent mode, the {@link #silent} worker will catch the exception and it will not be
* seen by the converted code.
*
* @param num
* The error number.
* @param text
* The text of the error message.
*
* @throws ErrorConditionException
*/
public static void recordOrThrowError(int num, String text)
throws ErrorConditionException
{
recordOrThrowError(num, text, true);
}
/**
* Raise an error condition and optionally record the given error (in silent mode).
* <p>
* In silent mode, the {@link #silent} worker will catch the exception and it will not be
* seen by the converted code.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
*
* @throws ErrorConditionException
*/
public static void recordOrThrowError(int num, String text, boolean prefix)
{
recordOrThrowError(new int[] { num }, new String[] { text }, prefix, false);
}
/**
* Raise an error condition and optionally record the given error (in silent mode).
* <p>
* In silent mode, the {@link #silent} worker will catch the exception and it will not be
* seen by the converted code.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
*
* @throws ErrorConditionException
*/
static void recordOrThrowLogManagerError(int num, String text, boolean prefix)
{
recordOrThrowError(new int[] { num }, new String[] { text }, prefix, false, true, true);
}
/**
* Raise an error condition and optionally record the given error (in silent mode).
* <p>
* In silent mode, the {@link #silent} worker will catch the exception and it will not be
* seen by the converted code.
*
* @param nums
* The error numbers.
* @param texts
* The text of the error messages.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
*
* @throws ErrorConditionException
*/
public static void recordOrThrowError(int[] nums, String[] texts, boolean prefix)
throws ErrorConditionException
{
recordOrThrowError(nums, texts, prefix, false);
}
/**
* Raise an error condition and optionally record the given error (in silent mode).
* <p>
* In silent mode, the {@link #silent} worker will catch the exception and it will not be
* seen by the converted code.
*
* @param nums
* The error numbers.
* @param texts
* The text of the error messages.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
* @param asMsg
* <code>true</code> to use the given text as the message text else
* <code>false</code> to use buildErrorText.
*
* @throws ErrorConditionException
*/
public static void recordOrThrowError(int[] nums,
String[] texts,
boolean prefix,
boolean asMsg)
throws ErrorConditionException
{
recordOrThrowError(nums, texts, prefix, asMsg, true);
}
/**
* Raise an error condition and optionally record the given error (in silent mode).
* <p>
* In silent mode, the {@link #silent} worker will catch the exception and it will not be
* seen by the converted code.
*
* @param nums
* The error numbers.
* @param texts
* The text of the error messages.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
* @param asMsg
* <code>true</code> to use the given text as the message text else
* <code>false</code> to use buildErrorText.
* @param addDot
* <code>true</code> if a dot is added to the end of the message.
*
* @throws ErrorConditionException
*/
public static void recordOrThrowError(int[] nums,
String[] texts,
boolean prefix,
boolean asMsg,
boolean addDot)
throws ErrorConditionException
{
recordOrThrowError(nums, texts, prefix, asMsg, addDot, false);
}
/**
* Raise an error condition and optionally record the given error (in silent mode).
* <p>
* In silent mode, the {@link #silent} worker will catch the exception and it will not be
* seen by the converted code.
*
* @param nums
* The error numbers.
* @param texts
* The text of the error messages.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
* @param asMsg
* <code>true</code> to use the given text as the message text else
* <code>false</code> to use buildErrorText.
* @param addDot
* <code>true</code> if a dot is added to the end of the message.
* @param isFromLogManager
* Flag to indicate the error comes from LOG-MANAGER.
*
* @throws ErrorConditionException
*/
private static void recordOrThrowError(int[] nums,
String[] texts,
boolean prefix,
boolean asMsg,
boolean addDot,
boolean isFromLogManager)
throws ErrorConditionException
{
if (isIgnore())
return;
boolean silent = isSilentError();
boolean manageLegacyError = server.mustThrowLegacyError();
boolean mustManageError = !manageLegacyError && server.mustManageLegacyError();
boolean forceExplicitException = false;
if (!manageLegacyError && nums[0] == 132 && !ProcedureManager.getProcedureHelper().isBlockLevelUndoThrow())
{
forceExplicitException = true;
manageLegacyError = server.mustThrowLegacyError(true);
}
boolean throwError = false;
for (int i = 0; i < nums.length; i++)
{
int num = nums[i];
String text = texts[i];
boolean last = i == nums.length - 1;
String errmsg = asMsg ? text : buildErrorText(num, text, prefix, addDot);
if (!silent && !isFromLogManager && !hasLegacyError())
{
writeLog(errmsg);
}
log(errmsg, silent);
if (silent)
{
// this may reset the error list so it must be done BEFORE we add
// the new error to the list
server.setPending(true);
server.addRecord(new ErrorEntry(num, errmsg, false));
// this will be caught by the worker method silent()
if (last)
{
if (serverSide)
{
throw new ErrorConditionException(num, errmsg);
}
else
{
server.throwErrorConditionException(num, errmsg);
}
}
}
else if (isHeadless())
{
if (last)
{
if (mustManageError || (manageLegacyError && msaManager.isMsaContext()))
{
TransactionManager.triggerErrorInCaller(num, text, true, prefix);
}
throw new ErrorConditionException(num, errmsg, new NumberedException(text, num));
}
}
else
{
// TODO: if the current block can catch this condition OR if the current block has
// ON ERROR UNDO, THROW OR if BLOCK-LEVEL or ROUTINE-LEVEL is active for the current
// block (Depending on block type), then:
// - don't display
// - morph this into a SysError and throw it
try
{
if (manageLegacyError)
{
throwError = true;
server.recordForLegacyThrow(errmsg, num);
}
else
{
// display the error message (because of pause processing, an
// endkey, error... even stop condition can be generated here)
// it's already been logged earlier, so marking it for not logging
displayError(errmsg, false, null, true, isFromLogManager);
}
}
catch (ConditionException err)
{
if (serverSide && TransactionManager.isNestedMode())
{
// honor nested mode here
TransactionManager.saveNestedError(err);
}
else
{
throw err;
}
}
if (last && throwError)
{
work.get().forceExplicit = forceExplicitException;
throw new DeferredLegacyErrorException();
}
if (last && !isWarningMode())
{
if (mustManageError)
{
TransactionManager.triggerErrorInCaller(num, text, true, prefix);
}
// raise the error condition
throw new ErrorConditionException(num, errmsg, new NumberedException(text, nums));
}
}
}
}
/**
* Raise an error condition and optionally record the given error (in silent mode).
* <p>
* In silent mode, the {@link #silent} worker will catch the exception and it will not be
* seen by the converted code.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
* @param asMsg
* <code>true</code> to use the given text as the message text else
* <code>false</code> to use buildErrorText.
*
* @throws ErrorConditionException
*/
public static void recordOrThrowError(int num, String text, boolean prefix, boolean asMsg)
{
recordOrThrowError(new int[] { num }, new String[] { text }, prefix, asMsg);
}
/**
* Raise an error condition and optionally record a chain of errors (in silent mode).
* <p>
* In silent mode, the {@link #silent} worker will catch the exception and it will not be
* seen by the converted code.
* <p>
* In silent mode, the error messages and numbers are extracted from <code>error</code>
* and any other instances of <code>NumberedException</code> present in the chain of root
* cause exceptions.
* <p>
* The first error entry recorded is derived from the given error number and text, however.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param error
* Exception containing an error number, message, and optionally
* a root cause chain.
*/
public static void recordOrThrowError(int num,
String text,
NumberedException error)
{
recordOrThrowError(new NumberedException(text, num, error));
}
/**
* Manufacture error text based on the given parameters, if silent error mode is enabled, then
* this error number and the generated error text will be stored in the error list, otherwise
* this generated error text will be thrown (and execution of the procedure halted).
*
* @param id
* The id of the message. The associated text is filled with {@code params} tokens.
* @param params
* The parameters for the message. If there are less than two token-parameters
* use empty Strings ({@code ""}) to make the compiler select the proper signature.
*/
public static void recordOrThrowError(int id, String... params)
{
recordOrThrowError(new int[] { id }, new String[] { replaceTokens(id, params) }, false, false, true);
}
/**
* Raise an error condition and optionally record a chain of errors (in silent mode).
* <p>
* In silent mode, the {@link #silent} worker will catch the exception and it will not be
* seen by the converted code.
* <p>
* In silent mode, the error messages and numbers are extracted from <code>error</code>
* and any other instances of <code>NumberedException</code> present in the chain of root
* cause exceptions.
*
* @param error
* Exception containing an error number, message, and optionally
* a root cause chain.
*/
public static void recordOrThrowError(NumberedException error)
{
if (isIgnore())
{
return;
}
boolean silent = isSilentError();
log("stack trace follows", error, silent);
if (silent)
{
// this may reset the error list so it must be done BEFORE we add
// the new error(s) to the list
server.setPending(true);
for (ErrorEntry errorEntry : compileErrorEntries(error))
{
server.addRecord(errorEntry);
}
}
throwError(error, silent);
}
/**
* Throws an {@link ErrorConditionException} and if in silent mode, it sets the
* <code>pending</code> flag to <code>true</code>. NO error information (messages or
* error numbers) will be recorded in either case (i.e. silent mode or not). In silent
* mode the <code>ERROR-STATUS:ERROR</code> will be <code>true</code> but the other "records"
* of the error that normally exist, will not exist. No message will be displayed. Because
* no message is ever displayed or recorded, the normal error number/message processing
* is not executed.
* <p>
* This only works on the server side.
*
* @param msg
* The text to use in throwing the error exception.
* @param isFromLogManager
* Flag to indicate the error comes from LOG-MANAGER.
*/
public static void noRecordOrShowError(String msg, boolean isFromLogManager)
{
WorkArea wa = work.obtain();
if (isSilentWorker(wa))
{
// ERROR-STATUS:ERROR flag is set, but no error is recorded
setPendingWorker(wa, true);
return;
}
if (isIgnore())
return;
// show the ERROR message
if (!isHeadless() && errorWriter != null)
{
if (server.isSystemAlertBoxes())
{
errorWriter.messageBox(msg, "Error", false);
}
else
{
errorWriter.message(msg);
}
}
}
/**
* Throws an {@link ErrorConditionException} and if in silent mode, it sets the
* <code>pending</code> flag to <code>true</code>. NO error information (messages or
* error numbers) will be recorded in either case (i.e. silent mode or not). In silent
* mode the <code>ERROR-STATUS:ERROR</code> will be <code>true</code> but the other "records"
* of the error that normally exist, will not exist. No message will be displayed. Because
* no message is ever displayed or recorded, the normal error number/message processing
* is not executed.
* <p>
* This only works on the server side.
*
* @param msg
* The text to use in throwing the error exception.
*/
public static void noRecordOrThrowError(String msg)
{
noRecordOrThrowErrorWorker(work.obtain(), msg);
}
/**
* Display error text and throw an exception based upon the given
* exception. The error messages and numbers are extracted from
* <code>error</code> and any other instances of
* <code>NumberedException</code> present in the chain of root cause
* exceptions. The first error entry recorded is derived from the given
* error number and text, however.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param error
* Exception containing an error number, message, and optionally
* a root cause chain.
*/
public static void throwError(int num, String text, NumberedException error)
{
throwError(new NumberedException(text, num, error));
}
/**
* Display error text and throw an exception based upon the given
* exception. The error messages and numbers are extracted from
* <code>error</code> and any other instances of
* <code>NumberedException</code> present in the chain of root cause
* exceptions.
*
* @param error
* Exception containing an error number, message, and optionally
* a root cause chain.
*/
public static void throwError(NumberedException error)
{
throwError(error, isSilent());
}
/**
* Display error text and throw an exception based upon the given exception. The error
* messages and numbers are extracted from <code>error</code> and any other instances of
* <code>NumberedException</code> present in the chain of root cause exceptions. The first
* error entry recorded is derived from the given error number and text, however.
* <p>
* This version of the method allows explicit control over silent error mode processing. This
* means that error processing which is normally suppressed, can be explicitly re-enabled
* by passing {@code suppress} as {@code false} even if {@link #isSilentError} would return
* {@code true}. ONLY USE THIS IF YOU REALLY KNOW WHAT YOU ARE DOING. Bypassing silent error
* mode is usually a bad idea. There are some use cases in the 4GL where this is needed and
* so that is why this method exists.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param error
* Exception containing an error number, message, and optionally a root cause chain.
* @param suppress
* {@code true} to suppress the display of error texts, else {@code false}.
*/
public static void throwError(int num, String text, NumberedException error, boolean suppress)
{
throwError(new NumberedException(text, num, error), suppress);
}
/**
* Display error text and throw an exception based upon the given exception. The error
* messages and numbers are extracted from <code>error</code> and any other instances of
* <code>NumberedException</code> present in the chain of root cause exceptions.
* <p>
* This version of the method allows explicit control over silent error mode processing. This
* means that error processing which is normally suppressed, can be explicitly re-enabled
* by passing {@code suppress} as {@code false} even if {@link #isSilentError} would return
* {@code true}. ONLY USE THIS IF YOU REALLY KNOW WHAT YOU ARE DOING. Bypassing silent error
* mode is usually a bad idea. There are some use cases in the 4GL where this is needed and
* so that is why this method exists.
*
* @param error
* Exception containing an error number, message, and optionally a root cause chain.
* @param suppress
* {@code true} to suppress the display of error texts, else {@code false}.
*/
public static void throwError(NumberedException error, boolean suppress)
{
if (!isHeadless() && !suppress)
{
// display the messages
Iterator<String> iter = errorTexts(error);
// check if in silent mode
boolean wasSilent = ErrorManager.isSilent();
try
{
// temporarily disable silent mode
ErrorManager.setSilent(false);
while (iter.hasNext())
{
try
{
// because of possible pause processing, an endkey, error...
// even stop condition can be generated here
displayError(iter.next());
}
catch (ConditionException err)
{
if (serverSide && TransactionManager.isNestedMode())
{
// honor nested mode here
TransactionManager.saveNestedError(err);
}
else
{
throw err;
}
}
}
}
finally
{
// restore silent mode if needed
if (wasSilent)
{
ErrorManager.setSilent(true);
}
}
}
if (!isWarningMode())
{
// raise the error condition
throw new ErrorConditionException(error).setForce(!suppress);
}
else
{
addRaisedCondition(error.getNumbers());
}
}
/**
* Throws 4GL error #5890.
*
* @param method
* The method name
* @param reason
* The error reason
*/
public static void throwErrorWhileProcessingComponentProperty(String method, String reason)
{
recordOrShowError(5890,
"Error occuired while processing component property/method: " +
method + ".\n" + reason,
true);
}
/**
* Throws 4GL error #5677.
*/
public static void throwErrorUnableToSetComHandleProperty()
{
recordOrShowError(5677, "Unable to set com-handle property", true);
}
/**
* Manufactures Progress compatible formatted error text based on the
* given error data. The given data is substituted into the following
* string "** %s. (%i)".
*
* @param num
* The error number substituted for %i in the format string.
* @param text
* The text of the error message substituted for %s in the
* format string.
*
* @return The formatted text.
*/
public static String buildErrorText(int num, String text)
{
return buildErrorText(num, text, true);
}
/**
* Manufactures Progress compatible formatted error text based on the
* given error data. The given data is substituted into the following
* string "** %s. (%i)".
*
* @param num
* The error number substituted for %i in the format string.
* @param text
* The text of the error message substituted for %s in the
* format string.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
*
* @return The formatted text.
*/
public static String buildErrorText(int num, String text, boolean prefix)
{
return buildErrorText(num, text, prefix, true);
}
/**
* Manufactures Progress compatible formatted error text based on the
* given error data. The given data is substituted into the following
* string "** %s. (%i)".
*
* @param num
* The error number substituted for %i in the format string.
* @param text
* The text of the error message substituted for %s in the
* format string.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
* @param addDot
* <code>true</code> if a dot is added to the end of the message.
*
* @return The formatted text.
*/
public static String buildErrorText(int num, String text, boolean prefix, boolean addDot)
{
// build up the message with StringBuilder rather than String.format, as this code may
// be called quite often in normal control flow, and String.format is expensive
StringBuilder buf = (prefix ? new StringBuilder("** ") : new StringBuilder());
buf.append(text);
// SysError with error number 0 does not add this
if (num > 0)
{
buf.append(addDot ? ". (" : " (");
buf.append(num);
buf.append(')');
}
return buf.toString();
}
/**
* Manufactures Progress compatible formatted abend error text based on the
* given error data. The given data is substituted into the following
* string "%s. (%i)".
*
* @param num
* The error number substituted for %i in the format string.
* @param text
* The text of the error message substituted for %s in the
* format string.
*
* @return The formatted text.
*/
public static String buildAbendText(int num, String text)
{
return String.format("%s. (%d)", text, num);
}
/**
* Adds an error entry to the error list.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param errFlag
* If <code>false</code> this should add the number and text to
* the error list but not set the error flag. If
* <code>true</code> the error flag should also be set.
*/
public static void addError(int num, String text, boolean errFlag)
{
addError(num, text, errFlag, true);
}
/**
* Adds an error entry to the error list.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param errFlag
* If <code>false</code> this should add the number and text to
* the error list but not set the error flag. If
* <code>true</code> the error flag should also be set.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
*/
public static void addError(int num, String text, boolean errFlag, boolean prefix)
{
addErrorWorker(work.obtain(), num, text, errFlag, prefix);
}
/**
* Returns the state of the current error flag which denotes if any error has occurred
* while silent mode is active. If silent error mode is not active, then this method always
* will return <code>false</code>.
*
* @return State of the current error flag.
*/
public static boolean isPendingError()
{
return server.isSilent() ? server.isPending() : false;
}
/**
* Obtain the current ERROR:ERROR-STRING attribute.
*
* @return the current value.
*/
public static character errorString()
{
UnimplementedFeature.missing("ERROR-STATUS:ERROR-STRING");
return new character();
}
/**
* Sets the ERROR:ERROR-STRING attribute.
*
* @param errStr
* The new value.
*/
public static void changeErrorString(String errStr)
{
// TODO: 4GL reports this as invalid attribute, but it was found in customer code logic...
// maybe is related to appserver/rest/web calls?
UnimplementedFeature.missing("ERROR-STATUS:ERROR-STRING");
}
/**
* Sets the ERROR:ERROR-STRING attribute.
*
* @param errStr
* The new value.
*/
public static void changeErrorString(character errStr)
{
UnimplementedFeature.missing("ERROR-STATUS:ERROR-STRING");
}
/**
* Returns the state of the error flag which was set during the last silent mode processing,
* even if silent error mode is not currently active. If silent error mode was never used,
* then <code>false</code> will be returned.
*
* @return Current state of the error flag.
*/
@LegacyAttribute(name = "ERROR", ignore = true)
public static logical isError()
{
return logical.of(work.obtain().errorStatus.error);
}
/**
* Returns the state of the error flag which was set during the last silent mode processing,
* even if silent error mode is not currently active. If silent error mode was never used,
* then <code>false</code> will be returned.
*
* @return Current state of the error flag.
*/
public static logical error()
{
return isError();
}
/**
* Returns the number of the errors (recorded in the error list) which have occurred during
* the last usage of silent error mode.
*
* @return Current number of errors in the error list if silent mode has been enabled.
* This will be 0 if no errors have occurred since the last use of silent error
* or if silent error mode has never been used.
*/
public static integer numErrors()
{
ArrayList<Object> errors = work.obtain().errorStatus.errorList;
return new integer(errors == null ? 0 : errors.size());
}
/**
* Undocumented feature encountered in customer code. After all, NUM-MESSAGES (KW_NUM_MSG)
* attribute seems to be writable.
*
* TODO: investigate if any number can be set or if there are some constraints:
* Ex: only 0 can be assigned, meaning some kind of clear error status.
*
* @param errCount
* New value.
*/
@LegacyAttribute(name = "NUM-MESSAGES", setter = true)
public static void setNumErrors(int64 errCount)
{
UnimplementedFeature.missing("ERROR-STATUS:NUM-MESSAGES not implemented.");
}
/**
* Throw a StopConditionException condition exception, or differ it as a LegacyStop if
* -catchStop startup parameter is enabled and CATCH for Progress.Lang.Stop or of of it's
* subclasses was detected.
*
* @param msg
* The error message.
*/
public static void handleStopException(String msg)
{
handleStopException(new StopConditionException(msg));
}
/**
* Throw a StopConditionException condition exception, or differ it as a LegacyStop if
* -catchStop startup parameter is enabled and CATCH for Progress.Lang.Stop or of of it's
* subclasses was detected.
*
* @param msg
* The error message.
*/
public static void handleStopException(object<? extends Stop> stopException)
{
if (isIgnore())
{
return;
}
boolean manageLegacyStop = server.mustThrowLegacyStop();
if (!manageLegacyStop)
{
throw new StopConditionException("UNDO, THROW STOP FALLBACK");
}
LegacyStopException lex = new LegacyStopException(stopException);
throw lex;
}
/**
* Throw a StopConditionException condition exception, or differ it as a LegacyStop if
* -catchStop startup parameter is enabled and CATCH for Progress.Lang.Stop or of it's
* subclasses was detected.
*
* @param msg
* The error message.
*/
public static void handleStopException(StopConditionException ex)
{
if (isIgnore())
{
return;
}
boolean manageLegacyStop = server.mustThrowLegacyStop();
if (manageLegacyStop)
{
server.recordForLegacyStop(ex);
throw new DeferredLegacyStopException();
}
throw ex;
}
/**
* Returns the Progress compatible error number associated with the error
* entry at the given index in the error list. If the list is empty or
* if the index is invalid, 0 is returned.
*
* @param index
* Position in the error list from which to return the entry.
* This is 1-based list index, where 1 is the oldest error number
* and highest number is the most recent error number.
*
* @return The error number at that index or 0 if the list is empty or
* if the index is invalid.
*/
public static integer getErrorNumber(integer index)
{
if (index.isUnknown())
{
return new integer();
}
return getErrorNumber(index.intValue());
}
/**
* Returns the Progress compatible error number associated with the error
* entry at the given index in the error list. If the list is empty or
* if the index is invalid, 0 is returned.
*
* @param index
* Position in the error list from which to return the entry.
* This is 1-based list index, where 1 is the oldest error number
* and highest number is the most recent error number.
*
* @return The error number at that index or 0 if the list is empty or
* if the index is invalid.
*/
public static integer getErrorNumber(int index)
{
int result = 0;
if (index > 0 && index <= server.getSize())
{
ErrorEntry ee = (ErrorEntry) server.getRecord(--index);
result = ee.num;
}
return new integer(result);
}
/**
* Java implementation for the _msg() function.
* <p>
* This will return the error number found on the error stack at the given position,
* or 0 if there isn't any error in the stack at the given position.
* <p>
* This implementation always will return 0 for any non-positive number (0 or negative).
* <p>
* In the 4GL, some negative numbers will return what appears to be a random number.
* This is most likely a flaw in the 4GL implementation where there is improper boundary
* testing and an invalid index value is used to dereference memory that is not associated
* with the error stack.
* <p>
* As such the behavior is non-deterministic and also is a likely security flaw.
* <p>
* This implementation is deliberately not duplicating this bad behavior and it seems unlikely
* that an application would be coded to rely upon it.
*
* @param index
* The position of the stack from which we want to get the value error number.
* This should be greater than 0 for valid results.
* This is 1-based stack offset, where 1 is the most recent error number
* and highest number is oldest error number in the stack.
*
* @return The error number found at the given position in the error stack, or 0 if there
* isn't any error number at the specified position
*/
public static integer getErrorNumberAtIndex(long index)
{
return getErrorNumberAtIndex(new int64(index));
}
/**
* Java implementation for the _msg() function.
* <p>
* This will return the error number found on the error stack at the given position,
* or 0 if there isn't any error in the stack at the given position.
* <p>
* This implementation always will return 0 for any non-positive number (0 or negative).
* <p>
* In the 4GL, some negative numbers will return what appears to be a random number.
* This is most likely a flaw in the 4GL implementation where there is improper boundary
* testing and an invalid index value is used to dereference memory that is not associated
* with the error stack.
* <p>
* As such the behavior is non-deterministic and also is a likely security flaw.
* <p>
* This implementation is deliberately not duplicating this bad behavior and it seems unlikely
* that an application would be coded to rely upon it.
*
* @param index
* The position of the stack from which we want to get the value error number.
* This should be greater than 0 for valid results.
* This is 1-based stack offset, where 1 is the most recent error number
* and highest number is oldest error number in the stack.
*
* @return The error number found at the given position in the error stack, or 0 if there
* isn't any error number at the specified position
*/
public static integer getErrorNumberAtIndex(integer index)
{
return getErrorNumberAtIndex(new int64(index));
}
/**
* Java implementation for the _msg() function.
* <p>
* This will return the error number found on the error stack at the given position,
* or 0 if there isn't any error in the stack at the given position.
* <p>
* This implementation always will return 0 for any non-positive number (0 or negative).
* <p>
* In the 4GL, some negative numbers will return what appears to be a random number.
* This is most likely a flaw in the 4GL implementation where there is improper boundary
* testing and an invalid index value is used to dereference memory that is not associated
* with the error stack.
* <p>
* As such the behavior is non-deterministic and also is a likely security flaw.
* <p>
* This implementation is deliberately not duplicating this bad behavior and it seems unlikely
* that an application would be coded to rely upon it.
*
* @param index
* The position of the stack from which we want to get the value error number.
* This should be greater than 0 for valid results.
* This is 1-based stack offset, where 1 is the most recent error number
* and highest number is oldest error number in the stack.
*
* @return The error number found at the given position in the error stack, or 0 if there
* isn't any error number at the specified position
*/
public static integer getErrorNumberAtIndex(int64 index)
{
// default value for incorrect index or not existent value for it is 0
integer result = new integer(0);
int intIdx = index.intValue();
// check if index is valid
if (intIdx > 0 && intIdx <= ERR_CONDITIONS_CAPACITY)
{
LinkedList<Integer> conds = getRaisedConditionsWorker(work.obtain());
if (intIdx <= conds.size())
{
Integer errorNumber = conds.get(intIdx - 1);
// check if there are error number exists for specified index
if (errorNumber != null)
{
result = new integer(errorNumber);
}
}
}
return result;
}
/**
* Returns the Progress compatible error text associated with the error
* entry at the given index in the error list. If the list is empty or
* if the index is invalid, an empty string "" is returned.
*
* @param index
* Position in the error list from which to return the entry.
*
* @return The error text at that index or the empty string "" if the
* list is empty or if the index is invalid.
*/
public static character getErrorText(integer index)
{
if (index.isUnknown())
{
return new character();
}
return getErrorText(index.intValue());
}
/**
* Returns the Progress compatible error text associated with the error
* entry at the given index in the error list. If the list is empty or
* if the index is invalid, an empty string "" is returned.
*
* @param index
* Position in the error list from which to return the entry.
*
* @return The error text at that index or the empty string "" if the
* list is empty or if the index is invalid.
*/
public static character getErrorText(int index)
{
String result = "";
if (index > 0 && index <= server.getSize())
{
ErrorEntry ee = (ErrorEntry) server.getRecord(--index);
result = ee.text;
}
return new character(result);
}
/**
* Queries the state of <code>silent</code> variable.
*
* @return <code>true</code> if silent mode is enabled, otherwise
* <code>false</code>.
*/
public static boolean isSilent()
{
return server.isSilent();
}
/**
* Sets the <code>silent</code> variable.
*
* @param value
* The new state of the variable.
*/
public static void setSilent(boolean value)
{
server.setSilent(value);
}
/**
* Queries the state of <code>error</code> variable.
*
* @return <code>true</code> if error flag is turned on, otherwise
* <code>false</code>.
*/
public static boolean isErrorFlag()
{
return server.isErrorFlag();
}
/**
* Sets the error variable.
*
* @param value
* The new state of the variable.
*/
@LegacyAttribute(name = "ERROR", setter = true)
public static void setError(boolean value)
{
// this does not affect the recorded error messages or numbers.
work.obtain().errorStatus.error = value;
}
/**
* Sets the error variable.
*
* @param value
* The new state of the variable.
*/
public static void error(boolean value)
{
setError(value);
}
/**
* Sets the error variable.
*
* @param value
* The new state of the variable.
*/
@LegacyAttribute(name = "ERROR", setter = true)
public static void setError(logical value)
{
if (value.isUnknown())
{
// unknown value is ignored
return;
}
setError(value.booleanValue());
}
/**
* Sets the error variable. This is a no-op, as the ERROR-STATUS:ERROR attribute
* is read-only.
*
* @param value
* The new state of the variable.
*/
public static void error(logical value)
{
setError(value);
}
/**
* Queries the state of <code>pendingError</code> variable.
*
* @return <code>true</code> if pending error flag is turned on, otherwise
* <code>false</code>.
*/
public static boolean isPending()
{
return server.isPending();
}
/**
* Sets the <code>pendingError</code> variable.
*
* @param value
* The new state of the variable.
*/
public static void setPending(boolean value)
{
server.setPending(value);
}
/**
* Determines if headless mode should be supported. In headless mode, no client-side output will occur.
* If this is running on the client, then the return value is always {@code false} (because by definition
* there is a client side even in batch mode).
*
* @return <code>true</code> if the headless flag is turned on, otherwise <code>false</code>.
*/
public static boolean isHeadless()
{
return serverSide ? isHeadlessWorker(work.obtain()) : false;
}
/**
* Gets the size of the list of error records.
*
* @return The size of the list.
*/
public static int getSize()
{
return server.getSize();
}
/**
* Adds a record to the list of error records.
*
* @param record
* The object to add to the list of error records.
*/
public static void addRecord(Object record)
{
server.addRecord(record);
}
/**
* Gets a record from the list of error records.
*
* @param index
* The record index.
*
* @return The object associated with the given index in the list.
*/
public static Object getRecord(int index)
{
return server.getRecord(index);
}
// GES_TODO: remove when all references are switched to ErrorHelper
/**
* Clears pending error status.
*/
public static void clearPending()
{
work.obtain().pendingErrorStatus.initialize();
}
/**
* Check if the top-level block is ROUTINE-LEVEL UNDO, THROW or the current program is
* BLOCK-LEVEL UNDO, THROW.
*
* @return see above.
*/
public static boolean mustManageLegacyError()
{
return BlockManager.mustManageLegacyError();
}
/**
* Check if the current ERROR condition must be raised as a legacy {@link SysError}.
*
* @param forceBlockLevelUndoThrow
* Flag indicating that, when nothing else matches, a BLOCK-LEVEL UNDO, THROW exists by default.
*
* @return see above.
*/
public static boolean mustManageLegacyError(boolean forceBlockLevelUndoThrow)
{
return BlockManager.mustManageLegacyError(forceBlockLevelUndoThrow);
}
/**
* Check if the current ERROR condition must be raised as a legacy {@link SysError}.
*
* @return see above.
*/
public static boolean mustThrowLegacyError()
{
return mustThrowLegacyError(false);
}
/**
* Check if the current STOP condition must be raised as a legacy {@link LegacyStop}.
*
* @return see above.
*/
public static boolean mustThrowLegacyStop()
{
return BlockManager.hasLegacyStop(Stop.class);
}
/**
* Check if the current ERROR condition must be raised as a legacy {@link SysError}.
*
* @param forceBlockLevelUndoThrow
* Flag indicating that, when nothing else matches, a BLOCK-LEVEL UNDO, THROW exists by default.
*
* @return see above.
*/
public static boolean mustThrowLegacyError(boolean forceBlockLevelUndoThrow)
{
return BlockManager.hasLegacyError(SysError.class, 0) ||
(BlockManager.mustManageLegacyError(forceBlockLevelUndoThrow) &&
(BlockManager.hasLegacyError(SysError.class, -1) ||
(TransactionManager.isRootNestingLevel() && AppServerManager.isRemote())));
}
/**
* Check if any CATCH blocks defined at the specified enclosing block can manage this exception.
*
* @return <code>true</code> if this exception can be processed by the current block.
*/
public static boolean hasLegacyError()
{
return BlockManager.hasLegacyError(SysError.class, 0);
}
/**
* Record the specified error message and number, to be later thrown as a
* {@link LegacyErrorException}.
*
* @param errmsg
* The error message.
* @param num
* The error number.
*/
public static void recordForLegacyThrow(String errmsg, int num)
{
WorkArea wa = work.get();
if (num == 293 && (wa.legacyError == null || !wa.legacyError._isValid()))
{
// StopError is raised when a procedure in a run statement is not found.
// Catch blocks that handles StopError errors, will not be able to handle
// the error if raised as a SysError.
wa.legacyError = StopError.newInstance(errmsg, num);
}
else if (wa.legacyError == null || !wa.legacyError._isValid())
{
wa.legacyError = SysError.newInstance(errmsg, num);
}
else
{
wa.legacyError.ref().addMessage(new character(errmsg), new integer(num));
}
}
/**
* STOP condition registered, to be later thrown as a
* {@link LegacyStopException}.
*
* @param ex
* StopConditionException to be converted in LegacyStop.
*/
public static void recordForLegacyStop(StopConditionException ex)
{
WorkArea wa = work.get();
StopAfterTimer stopAfterTimer = wa.stopAfterTimer;
if (wa.legacyStop == null || !wa.legacyStop._isValid())
{
if (stopAfterTimer != null && stopAfterTimer.isStopAfterConditionRaised())
{
wa.legacyStop = StopAfter.newInstance();
return;
}
else if (ex instanceof UserInterruptConditionException)
{
wa.legacyStop = UserInterrupt.newInstance();
return;
}
else if (ex instanceof LockConflictConditionException)
{
wa.legacyStop = LockConflict.newInstance();
return;
}
wa.legacyStop = Stop.newInstance();
}
}
/**
* Throw the specified error message and number.
*
* @param num
* The error number.
* @param errmsg
* The error message.
*/
public static void throwErrorConditionException(int num, String errmsg)
{
throw new ErrorConditionException(num, errmsg);
}
/**
* Get the deferred legacy error from {@link WorkArea#legacyError}, and wrap it as a
* {@link LegacyErrorException}.
* <p>
* This approach allows the FWD client-side to defer throwing a {@link LegacyErrorException},
* as the FWD {@link object} instances (and any {@link _BaseObject_} instance) are not
* serializable, and can't be sent over the network.
*
* @return See above.
*/
public static LegacyErrorException getDeferredLegacyError()
{
WorkArea wa = work.get();
object<? extends SysError> legacyError = wa.legacyError;
wa.legacyError = null;
LegacyErrorException lex = new LegacyErrorException(legacyError);
lex.setExplicit(wa.forceExplicit);
wa.forceExplicit = false;
return lex;
}
/**
* Get the deferred legacy stop error from {@link WorkArea#legacyStop}, and wrap it as a
* {@link LegacyStopException}.
* <p>
* This approach allows the FWD client-side to defer throwing a {@link LegacyStopException},
* as the FWD {@link object} instances (and any {@link _BaseObject_} instance) are not
* serializable, and can't be sent over the network.
*
* @return See above.
*/
public static LegacyStopException getDeferredLegacyStop()
{
WorkArea wa = work.get();
object<? extends Stop> legacyStop = wa.legacyStop;
wa.legacyStop = null;
LegacyStopException lex = null;
lex = new LegacyStopException(legacyStop);
return lex;
}
/**
* Display and raise an ERROR condition indiscriminately of the NO-ERROR state.
*
* @param errors
* The error texts to display.
*/
public static void displayAndRaiseError(List<String> errors)
{
boolean silent = ErrorManager.isSilentError();
try
{
ErrorManager.setSilent(false);
for (String err : errors)
{
displayError(err);
}
ErrorConditionException exc = new ErrorConditionException("raising multiple errors");
exc.setForce(true);
throw exc;
}
finally
{
ErrorManager.setSilent(silent);
}
}
/**
* Format the text of the error message and then display that text on
* on the terminal. Adds the error number to _MSG stack.
*
* @param num
* Error number.
* @param msg
* The text to display in the message line.
*/
public static void displayError(int num, String msg)
{
displayError(num, msg, true);
}
/**
* Format the text of the error message and then display that text on
* on the terminal. Adds the error number to _MSG stack.
*
* @param num
* Error number.
* @param msg
* The text to display in the message line.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
*/
public static void displayError(int num, String msg, boolean prefix)
{
boolean manageLegacyError = server.mustThrowLegacyError();
msg = buildErrorText(num, msg, prefix);
if (manageLegacyError)
{
server.recordForLegacyThrow(msg, num);
throw new DeferredLegacyErrorException();
}
displayError(msg);
addRaisedCondition(new int[] {num});
}
/**
* Format the text of the warning message and then display that text on
* on the terminal.
*
* @param num
* Error number.
* @param msg
* The text to display.
*/
public static void displayWarning(int num, String msg)
{
displayWarning(num, msg, true);
}
/**
* Format the text of the warning message and then display that text on
* on the terminal.
*
* @param num
* Error number.
* @param msg
* The text to display.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
*/
public static void displayWarning(int num, String msg, boolean prefix)
{
String errText = buildErrorText(num, msg, prefix);
// manually using the work area is OK in this case since this method
// is only used on the server
WorkArea wa = work.get();
boolean silentError = isSilentError();
if (!silentError)
{
addRaisedConditionWorker(wa, new int[] {num});
}
if (wa.suppressWarnings)
{
// when SESSION:SUPPRESS-WARNING set to TRUE the warning is not displayed, output or set in
// ERROR-STATUS, only added to _MSG
return;
}
if (silentError)
{
// no-error will modify ERROR-STATUS, but only when SUPPRESS-WARNINGS not set to TRUE
wa.pendingErrorStatus.addError(new ErrorEntry(num, errText));
}
else
{
displayWarning(errText);
}
}
/**
* Format the text of the error message and then display that text on the terminal. This is
* the redirected version of the displayError. If the output is redirected to file the message
* is writing to the file. Adds the error number to _MSG stack.
*
* @param num
* Error number.
* @param msg
* The text to display in the message line.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
*/
public static void displayErrorRedirected(int num, String msg, boolean prefix)
{
displayErrorRedirected(buildErrorText(num, msg, prefix));
addRaisedCondition(new int[] {num});
}
/**
* Format the text of the error message and then display that text on the terminal. This is
* the redirected version of the displayError. If the output is redirected to file the message
* is writing to the file.
*
* @param msg
* The text to display in the message line.
*/
public static void displayErrorRedirected(String msg)
{
displayErrorRedirected(msg, true);
}
/**
* Format the text of the error message and then display that text on the terminal. This is
* the redirected version of the displayError. If the output is redirected to file the message
* is writing to the file.
*
* @param msg
* The text to display in the message line.
* @param logCallStack
* Add call stack to log. <code>false</code> when ALERT_BOX message.
*/
public static void displayErrorRedirected(String msg, boolean logCallStack)
{
if (errorWriter != null)
{
errorWriter.displayErrorRedirected(msg, logCallStack);
}
else
{
writeLog(msg, logCallStack);
}
}
/**
* Format the text of the abend message and then display that text on
* on the terminal.
*
* @param num
* Error number.
* @param msg
* The text to display in the message line.
*/
public static void displayAbend(int num, String msg)
{
displayError(buildAbendText(num, msg));
addRaisedCondition(new int[]{num});
}
/**
* Display the given error message on the terminal.
* This method does not add a record to _MSG stack.
*
* @param errmsg
* The text to display in the message line.
*/
public static void displayError(String errmsg)
{
displayError(errmsg, false);
}
/**
* Display the given error message on the terminal. This method does not add a record to
* _MSG stack.
*
* @param errmsg
* The text to display in the message line.
* @param noMessageBox
* <code>true</code> if error is always displayed in message area or logged rather
* than displayed as a message box.
*/
public static void displayError(String errmsg, boolean noMessageBox)
{
displayError(errmsg, noMessageBox, null, true);
}
/**
* Display the given error message.
* This method does not add a record to _MSG stack.
* @param num
* Error number.
* @param msg
* The text to display in the message line.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
* @param noMessageBox
* <code>true</code> if error is always displayed in message area or logged rather
* than displayed as a message box.
*/
public static void displayError(int num, String msg, boolean prefix, boolean noMessageBox)
{
msg = buildErrorText(num, msg, prefix);
displayError(msg, noMessageBox);
}
/**
* Display the given error message on the terminal. This method does not add a record to
* _MSG stack.
*
* @param errmsg
* The text to display in the message line.
* @param noMessageBox
* <code>true</code> if error is always displayed in message area or logged rather
* than displayed as a message box.
* @param stack
* Progress stack trace
* @param logCallStack
* Add call stack to log. <code>false</code> when ALERT_BOX message.
*/
public static void displayError(String errmsg, boolean noMessageBox, String[] stack, boolean logCallStack)
{
displayError(errmsg, noMessageBox, stack, logCallStack, false);
}
/**
* Display the given error message on the terminal. This method does not add a record to
* _MSG stack.
*
* @param errmsg
* The text to display in the message line.
* @param noMessageBox
* <code>true</code> if error is always displayed in message area or logged rather
* than displayed as a message box.
* @param stack
* Progress stack trace
* @param logCallStack
* Add call stack to log. <code>false</code> when ALERT_BOX message.
* @param isFromLogManager
* Flag to indicate the error comes from LOG-MANAGER.
*/
public static void displayError(String errmsg, boolean noMessageBox, String[] stack,
boolean logCallStack, boolean isFromLogManager)
{
if (isIgnore() || isHeadless() || isSilent())
{
return;
}
if (errorWriter != null)
{
errorWriter.displayError(errmsg, noMessageBox, stack, logCallStack, isFromLogManager);
}
}
/**
* Display the given warning message on the terminal.
* This method does not add a record to _MSG stack.
*
* @param wrnmsg
* The text to display in the message line.
*/
public static void displayWarning(String wrnmsg)
{
if (isIgnore() || isHeadless())
{
return;
}
if (errorWriter != null)
{
errorWriter.displayWarning(wrnmsg);
}
}
/**
* Process a LegacyErrorException, setting the pendingError flag and recording the errors.
*
* @param lex
* The LegacyErrorException that is processed.
* @param eh
* the context-local error helper for adding the error entries.
*/
public static void processLegacyErrorException(LegacyErrorException lex, ErrorHelper eh)
{
eh.srv.setPending(true);
LegacyError err = lex.getErrorRef();
if (!(err instanceof AppError) || !((AppError) err).isFromReturn())
{
ProError proErr = (ProError) err;
int numErr = err.getNumMessages().intValue();
int[] errnums = new int[numErr];
String[] errmsgs = new String[numErr];
for (int i = 0; i < numErr; i++)
{
errnums[i] = proErr.getMessageNum(new integer(i + 1)).intValue();
errmsgs[i] = proErr.getMessage(new integer(i + 1)).toStringMessage();
eh.wa.pendingErrorStatus.addError(new ErrorEntry(errnums[i], errmsgs[i]));
}
addRaisedConditionWorker(eh.wa, errnums);
}
if (lex.getError()._isValid())
{
ObjectOps.delete(err);
}
}
/**
* Handles a SOAP fault.
*
* @param operationName
* The operation that failed.
* @param faultMsg
* The fault error msg.
* @param faultActor
* The fault actor.
* @param faultCode
* The fault code.
* @param faultString
* The fault string.
* @param faultDetailXML
* The fault details as xml.
*/
public static void handleSoapFault(String operationName,
String faultMsg,
String faultActor,
String faultCode,
String faultString,
String faultDetailXML)
{
server.handleSoapFault(operationName, faultMsg, faultActor, faultCode, faultString, faultDetailXML);
}
/**
* Writes log record.
*
* @param msg
* The message to write.
*/
public static void writeLog(String msg)
{
if (!msaManager.isMsaContext() &&
(SessionManager.get() == null || SessionManager.get().getSession() == null))
{
return; // no context: import or server starting up
}
server.writeLog(msg, true, Thread.currentThread().getId());
}
/**
* Writes log record.
*
* @param msg
* The log message.
* @param logCallStack
* Add call stack to log. <code>false</code> when ALERT_BOX message.
*/
public static void writeLog(String msg, boolean logCallStack)
{
server.writeLog(msg, logCallStack, Thread.currentThread().getId());
}
/**
* Writes log record.
*
* @param msg
* The log message.
* @param logCallStack
* Add call stack to log. <code>false</code> when ALERT_BOX message.
* @param threadId
* The id of the thread recording the error message.
*/
public static void writeLog(String msg, boolean logCallStack, long threadId)
{
server.writeLog(msg, logCallStack, threadId);
}
/**
* Get the type of its associated handle.
*
* @return Always return the PSEUDO-WIDGET value.
*/
public static character resourceType()
{
return new character(LegacyResource.PSEUDO_WIDGET);
}
/**
* Returns a handle for the ERROR-OBJECT-DETAIL attribute, set in cases of SOAP-related faults.
*
* @return See above.
*/
public static handle getErrorObjectDetail()
{
WorkArea wa = work.get();
return (wa.errorStatus.soapFault == null)
? new handle()
: new handle(wa.errorStatus.soapFault);
}
/**
* Reflects the value of SESSION:SUPPRESS-WARNINGS attribute.
*
* @return see above.
*/
public static boolean isSuppressWarnings()
{
return server.isSuppressWarnings();
}
/**
* Reflects the value of SESSION:SUPPRESS-WARNINGS-LIST attribute.
*
* @return see above.
*/
public static String getSuppressWarningsList()
{
return server.getSuppressWarningsList();
}
/**
* This method can be only called on the server.
*
* @param value
* The new value of {@code SESSION:SUPPRESS-WARNINGS} attribute.
*/
public static void setSuppressWarnings(boolean value)
{
work.get().suppressWarnings = value;
}
/**
* This method can be only called on the server.
*
* @param value
* The new value of {@code SESSION:SUPPRESS-WARNINGS-LIST} attribute.
*/
public static void setSuppressWarningsList(String value)
{
WorkArea workArea = work.get();
for (String s : value.split(","))
{
s = s.trim();
try
{
workArea.suppressWarningsSet.add(Integer.parseInt(s));
}
catch (Exception e)
{
int[] nums = {16478, 3131};
String[] texts = {"The entry " + s + " in the warning list is invalid",
"Unable to set attribute SUPPRESS-WARNINGS-LIST in widget of type PSEUDO-WIDGET"};
recordOrThrowError(nums, texts, false);
workArea.suppressWarningsSet.clear();
return;
}
}
workArea.suppressWarningsList = value;
}
/**
* Get a helper to access certain context-local state required for error support.
*
* @return The context-local helper.
*/
public static ErrorHelper getErrorHelper()
{
// the usage of Stream.putWorker() is used on the client side, which calls TM.setNestedMode() which
// initializes the TransactionManager on the client; we need safety code here
ServerErrorDataAccessor errorDataAccessor = server instanceof ServerErrorDataAccessor ?
(ServerErrorDataAccessor) server :
null;
return new ErrorHelper(work.obtain(), errorDataAccessor);
}
/**
* Generate an error if a character is invalid in a format string.
*
* @param idx
* The 0-based index of the incorrect character in the format
* string (this will be converted to a 1-based index).
* @param fmt
* The incorrect format string.
*
* @return -1.
*
* @throws ErrorConditionException
*/
public static int genInvalidCharError(int idx, String fmt)
throws ErrorConditionException
{
String spec = "Character number %d of format %s is invalid";
recordOrThrowError(22, String.format(spec, idx + 1, fmt));
// this is the safest thing to return (it should be undone anyway)
return -1;
}
/**
* Generate an "Invalid ... parameter..." error string.
*
* @param type
* the type name, e.g "index", "array-value"
* @param instance
* the instance
* @param method
* the method name
* @param errMsg
* the error message format string
*
* @return the generated string
*/
public static String getInvalidParameterError(String type,
_BaseObject_ instance, String method, String errMsg)
{
return String.format("Invalid %s parameter to %s:%s( ). %s",
type,
instance.getLegacyClass().ref().getTypeName().getValue(),
method,
errMsg);
}
/**
* Manufacture error text based on the given parameters. We start with the id of a template
* and replace the positional placeholders to be replaced with the provided string parameters.
* <p>
* Because the errors are inconsistent relative to the double asterisk prefix, the returned
* value will contain it, if the case; following that when the message is displayed, the
* prefix to be used to be always {@code false};
*
* @param id
* The id of the message. The associated text is filled with {@code params} tokens.
* @param params
* The parameters for the message.
*/
public static String replaceTokens(int id, String... params)
{
// NOTE: cases without indentation were found while doing investigations but are not YET implemented in code.
String msg = "";
switch (id)
{
case 0: msg = "%1"; break;
case 26: msg = "** Array subscript %1 is out of range"; break;
case 43: msg = "** Cannot find or open file %1, errno = %2"; break;
case 142: msg = "** Unable to update %1 Field"; break;
case 143: msg = "** %1: Unable to evaluate field for assignment"; break;
case 223: msg = "** Incompatible data types in expression or assignment"; break;
case 257: msg = "** The first character of %1 must be alphabetic"; break;
case 274: msg = "** The character %1 is not permitted in name %2"; break;
case 293: msg = "** \"%1\" was not found"; break;
case 341: msg = "** There is no current %1 record. Delete failed"; break;
case 912: msg = "Code page attribute table for %1 was not found in %2"; break;
case 1043: msg = "Collation table for code page %1 and collation name %2 was not found in %3"; break;
case 1587: msg = "ASC could not find the appropriate conversion table in %1"; break;
case 2913: msg = "Unknown sequence expression %1"; break;
case 3131: msg = "Unable to set attribute %1 in widget %2 of type %3"; break;
case 3133: msg = "SYSTEM ERROR: Sequence %1 not found"; break;
case 3140: msg = "Cannot access the %1 attribute because the widget does not exist"; break; // when invoking APPLY-CALLBACK on an uninitialized handle.
case 3147: msg = "Cannot browse query %1 because query is already being browsed"; break;
case 3588: msg = "Sequence %1 not found"; break;
case 4052: msg = "**%1 is not a %2 attribute for %3"; break;
case 4056: msg = "**Attribute %1 for the %2 has an invalid value of UNKNOWN"; break;
case 4058: msg = "**Attribute %1 for the %2 has an invalid value of %3"; break;
case 4061: msg = "**Attribute %1 for the %2 has an invalid value"; break;
case 4065: msg = "**The %1 attribute on the %2 has invalid arguments"; break;
case 4078: msg = "**Unable to set attribute %1 for %2"; break;
case 4083: msg = "**Unable to assign UNKNOWN value to attribute %1 on %2"; break;
case 5368: msg = "Couldn't update field %1 of target in a BUFFER-COPY statement"; break;
case 5442: msg = "Invalid datatype for argument to method '%1'. Expecting '%2'"; break;
case 5468: msg = "Application server connect failure"; break;
case 5482: msg = "Unknown hostname %1"; break;
case 5484: msg = "Unknown service %1 transport TCP"; break;
case 5729: msg = "Incompatible datatypes found during runtime conversion"; break;
case 6063: msg = "Code page conversion table for %1 to %2 was not found in %3."; break;
case 7319: msg = "ADD/SET-BUFFERS argument %1 was invalid or not found"; break;
case 7331: msg = "Cannot reposition query %1 to recid/rowid(s) given"; break;
case 7351: msg = "BUFFER-FIELD %1 was not found in buffer %2"; break;
case 7352: msg = "You may only invoke EMPTY-TEMP-TABLE on valid temp-tables"; break;
case 7363: msg = "SKIP-DELETED-RECORD cannot be set on during BROWSE"; break;
case 7365: msg = "%1 BUFFER-VALUE argument must be valid array index, less than or equal to field extent %2"; break;
case 7372: msg = "Object of type DATASET widget used where BUFFER-FIELD object required"; break;
case 9034: msg = "Pair with %1 failed to have a member in each table"; break;
case 9035: msg = "Pairs list had too many pairs or odd number of entries"; break;
case 9065: msg = "Cannot set PRIMARY on a TEMP-TABLE that has been prepared with TEMP-TABLE-PREPARE"; break;
case 9069: msg = "Unable to get TEMP-TABLE handle for parameter"; break;
case 9092: msg = "Invalid COMPARE operator: %1"; break;
case 9093: msg = "Invalid COMPARE strength: %2"; break;
case 9407: msg = "Connection failure for host %1 port %2 transport TCP"; break;
case 10068: msg = "Lead attributes in a chained-attribute expression (a:b:c) must be type HANDLE or a user-defined type and valid (not UNKNOWN)"; break;
case 10515: msg = "Handle type not valid as XML input source"; break;
case 11265: msg = "Attempt to access blob beyond its end"; break;
case 11279: msg = "File offset %1 is greater than size of file %2 for file '%3'"; break; // no DOT at the end
case 11295: msg = "Attempt to access past end of MEMPTR during COPY-LOB"; break; // no DOT at the end
case 11305: msg = "Cannot copy large object starting at %1 for %2 bytes when object size is %3"; break;
case 11316: msg = "Could not obtain LONGCHAR"; break;
case 11329: msg = "File offset %1 is greater than size of file %2 for file '%3'"; break;
case 11330: msg = "File offset %1 plus copy length %2 is greater than size of file %3 for '%4'"; break;
case 11331: msg = "File offset %1 is greater than size of file %2 for file '%3'"; break;
case 11332: msg = "Invalid offset specified in COPY-LOB statement"; break;
case 11334: msg = "Offset supplied for source large object is greater than object size"; break;
case 11335: msg = "Offset supplied for target large object is greater than object size"; break;
case 11339: msg = "The source codepage can not be specified for a CLOB/LONGCHAR field"; break;
case 11343: msg = "Unknown or invalid file offset specified in COPY-LOB statement"; break;
case 11344: msg = "Unknown or invalid length specified in COPY-LOB statement"; break;
case 11345: msg = "Unknown or invalid source file specified in COPY-LOB statement"; break;
case 11341: msg = "The target codepage conflicts with the longchar's fixed codepage"; break;
case 11346: msg = "Unknown or invalid target file specified in COPY-LOB statement"; break;
case 11350: msg = "Write to file '%1' failed during COPY-LOB"; break;
case 11382: msg = "INPUT/OUTPUT operations are not allowed with RAW, ROWID, MEMPTR, BLOB, CLOB or LONGCHAR type variables"; break;
case 11393: msg = "MEMPTR target of COPY-LOB must be initialized"; break;
case 11394: msg = "MEMPTR target of COPY-LOB is not big enough"; break;
case 11395: msg = "Large object assign or copy failed"; break;
case 11679: msg = "Mismatch between source-codepage argument and LONGCHAR codepage"; break;
case 11683: msg = "FIX-CODEPAGE can only be run when the LONGCHAR variable is empty"; break;
case 11851: msg = "COPY-LOB operations between BLOB and CLOB fields are not supported"; break;
case 11858: msg = "Cannot add/set buffers on a static dataset"; break;
case 11860: msg = "Buffer %1 may not appear more than once in any dataset"; break;
case 11863: msg = "FILL is not valid for table %1 when TRACKING-CHANGES is on"; break;
case 11867: msg = "Query buffers and data-source buffers must match"; break;
case 11872: msg = "Invalid timezone expression"; break; // no DOT at the end
case 11874: msg = "FILL requires an attached data-source for buffer %1, or an active before-fill callback procedure"; break;
case 11875: msg = "FILL of buffer %1 requires a DATA-SOURCE QUERY to be prepared and assigned for this type of buffer"; break;
case 11876: msg = "FILL of buffer %1 with user-supplied query requires that the query be QUERY-PREPARED"; break;
case 11878: msg = "FILL got error %1 creating record %2"; break;
case 11883: msg = "Unable to auto-generate a FILL query predicate for %1 if field %2 in %3 is not mapped"; break;
case 11884: msg = "Dataset buffer %1 must be for a temp-table"; break;
case 11885: msg = "A unique primary index is required in the target table, each field of which is mapped to some source field, in order to do a replace-mode or parent-mode COPY/GET/MERGE/FILL type of operation on a dataset table"; break;
case 11887: msg = "ACCEPT-CHANGES run on table %1 that has no BEFORE-TABLE"; break;
case 11891: msg = "Found error during GET-CHANGES method"; break;
case 11892: msg = "Attempt to reference dataset buffer of a data-source before ATTACH-DATA-SOURCE for data-source buffer %1"; break;
case 11893: msg = "SAVE-WHERE-STRING cannot be generated when there is no BEFORE-TABLE--define one or use TRACKING-CHANGES"; break;
case 11894: msg = "KEYS phrase for data-source buffer %1 contains ROWID, but no field in %2 was mapped to the rowid during ATTACH-DATA-SOURCE"; break;
case 11895: msg = "Unable to get SAVE-WHERE-STRING because no KEYS phrase given in DATA-SOURCE definition and no unique index found in %1"; break;
case 11896: msg = "Unable to generate a save predicate for %1 if field %2 is not mapped in %3"; break;
case 11897: msg = "Record %1 is not available to set ERROR-STRING"; break;
case 11906: msg = "Unable to identify data-source buffer for SAVE-ROW-CHANGES"; break;
case 11909: msg = "Unable to find sequence field %1 in table %2 during SAVE-ROW-CHANGES"; break;
case 11910: msg = "SAVE-ROW-CHANGES was unable to update the database table %1"; break;
case 11912: msg = "Unable to find record for db buffer %1 during SAVE-ROW-CHANGES"; break;
case 11913: msg = "SAVE-ROW-CHANGES found %1 record with conflicting change by another user"; break;
case 11916: msg = "%1 record is not available to set ERROR in"; break;
case 11917: msg = "Cannot set ERROR if there is no BEFORE-TABLE for %1"; break;
case 11918: msg = "Cannot set ERROR if there is no BEFORE-TABLE row for %1"; break;
case 11919: msg = "REJECT-CHANGES run on table %1 that has no BEFORE-TABLE"; break;
case 11922: msg = "MERGE-CHANGES may not be run on table %1 with TRACKING-CHANGES on"; break;
case 11923: msg = "MERGE-CHANGES run on original table %1 that does not match the ORIGIN-HANDLE in the GET-CHANGES table %2"; break;
case 11924: msg = "MERGE-CHANGES run on original table %1 and change table %2 whose columns do not match exactly"; break;
case 11925: msg = "GET/MERGE-CHANGES require a BEFORE-TABLE for table %1"; break;
case 11926: msg = "Found unexpected error while merging changes"; break;
case 11928: msg = "INVALID origin-rowid of the change table %1 of %2"; break;
case 11933: msg = "ACCEPT-ROW-CHANGES must be run on the BEFORE table of buffer %1"; break;
case 11934: msg = "ACCEPT-ROW-CHANGES buffer %1 not available"; break;
case 11935: msg = "SAVE-ROW-CHANGES buffer %1 not available"; break;
case 11936: msg = "REJECT-ROW-CHANGES buffer %1 not available"; break;
case 11937: msg = "MERGE-ROW-CHANGES buffer %1 not available"; break;
case 11938: msg = "SAVE-ROW-CHANGES must be run on the BEFORE table of buffer %1"; break;
case 11939: msg = "REJECT-ROW-CHANGES must be run on the BEFORE table of buffer %1"; break;
case 11942: msg = "SAVE-ROW-CHANGES for BEFORE buffer %1, requires that the corresponding AFTER dataset member buffer %2 be attached to a DATA-SOURCE"; break;
case 11943: msg = "First argument to MERGE-ROW-CHANGES must be a valid BUFFER handle"; break;
case 11944: msg = "MERGE-ROW-CHANGES finds ORIGIN-HANDLE in table %1 is no longer valid"; break;
case 11945: msg = "BUFFER:FILL is only valid for buffers that are members of a dataset"; break;
case 11946: msg = "You cannot EMPTY a BEFORE-TABLE, Use ACCEPT-CHANGES or EMPTY the AFTER-TABLE"; break;
case 11947: msg = "bufferhdl:GET-CHANGES must have a valid buffer handle to the original table, from which changes are coming, as the first argument"; break;
case 11948: msg = "First argument to MERGE-CHANGES must be a valid BUFFER handle"; break;
case 11949: msg = "APPLY-CALLBACK is only for buffers that are members of datasets"; break;
case 11950: msg = "Argument %1 to CALLBACK method must be a valid buffer or dataset event name"; break;
case 11951: msg = "SET-CALLBACK-PROCEDURE is only valid for buffers that are members of a dataset"; break;
case 11952: msg = "ATTACH-DATA-SOURCE is only valid for buffers that are members of a dataset"; break;
case 11953: msg = "Dataset GET-CHANGES and MERGE-CHANGES must have a valid dataset handle as the first argument"; break;
case 11954: msg = "Dataset GET-CHANGES and MERGE-CHANGES must have a change dataset and an original dataset with the same number of members and the member buffers must have matching schema"; break;
case 11955: msg = "The argument to Dataset-handle:ADD-RELATION method was invalid"; break;
case 11956: msg = "ADD-RELATION method requires buffer objects and not table objects"; break;
case 11957: msg = "ATTACH-DATA-SOURCE must have a valid data-source handle as the first argument"; break;
case 11958: msg = "ATTACH-DATA-SOURCE may not be called until source buffers have been assigned to the DATA-SOURCE"; break;
case 11959: msg = "FILL-MODE is only valid for a dataset buffer"; break;
case 11960: msg = "FILL-MODE must be one of NO-FILL, MERGE, EMPTY, etc."; break;
case 11961: msg = "Buffers in ADD-RELATION must be members of the dataset"; break;
case 11962: msg = "Odd number of join pairs in ADD-RELATON"; /* TYPO in ABL */ break;
case 11963: msg = "ADD-RELATION join pairs list entry %1 not valid--list must have unqualified field names with no embedded blanks, in parent-fld,child-fld order, e.g. 'cust-num,cust-num'"; break;
case 11964: msg = "ADD-RELATION join pairs list was not valid"; break;
case 11965: msg = "Attempt to relate dataset buffer %1 to parent with itself in ancestry"; break;
case 11967: msg = "CLEAR may not be used on a static dataset"; break;
case 11968: msg = "First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name\""; break; // yes, the double quote is part of error message
case 11969: msg = "Unable to CLEAR target CREATE-LIKE dataset"; break;
case 11971: msg = "Cannot delete a relation of a dataset"; break;
case 11973: msg = "You may not change the buffers for a static data-source"; break;
case 11974: msg = "The first argument to ADD-SOURCE-BUFFER must be a valid buffer handle"; break;
case 11975: msg = "ADD-SOURCE-BUFFER key name %1 must be a field in buffer %2"; break;
case 11976: msg = "Data-source QUERY attribute requires valid query handle or unknown"; break;
case 11979: msg = "Buffer %1 can have only 1 active parent relation"; break;
case 11980: msg = "Must assign a user query to a data-source that is a join before filling"; break;
case 11981: msg = "Failed to auto-prepare an automatic fill query"; break;
case 12008: msg = "Invalid character code found in data for codepage %1"; break; // no DOT at the end!
case 12012: msg = "Invalid character data found in MEMPTR for codepage %1"; break; // no DOT at the end!
case 12117: msg = "Unacceptable datatype for %1 argument"; break; // no DOT at the end!
case 12125: msg = "The codepage \"%1\" cannot be used for CLOB or LONGCHAR variables"; break;
case 12298: msg = "Buffer %1 may be in only one data-source at a time. Use different buffers for the same table"; break;
case 12301: msg = "Source and target are the same table in temp-table copy operation for %1"; break; // dataset-copy (itself)
case 12303: msg = "COPY-TEMP-TABLE requires %1 and %2 columns to match exactly unless the fourth loose-mode parameter is passed as TRUE"; break; // dataset-copy (table structure not matching)
case 12304: msg = "Error found during COPY-TEMP-TABLE operation on %1 and %2"; break;
case 12310: msg = "Caller and called dataset parameters have different number of members"; break;
case 12311: msg = "Parameters (table %1 and table %2) do not match"; break;
case 12314: msg = "Invalid DATASET-Handle parameter given"; break;
case 12323: msg = "A NO-SCHEMA-MARSHAL table cannot be used as a parameter where the receiving side does not have a pre-prepared schema"; break;
case 12344: msg = "Cannot turn on TRACKING-CHANGES for a static temp-table unless DEFINED with a BEFORE-TABLE phrase"; break;
case 12349: msg = "Cannot delete a dataset relation auto-generated query"; break;
case 12352: msg = "Argument %1 to query callback method must be a valid query event name such as OFF-END"; break;
case 12355: msg = "TRACKING-CHANGES may not be turned on for a non-dataset temp-table"; break;
case 12360: msg = "LAST-BATCH may only be set for dataset temp-tables"; break;
case 12361: msg = "BATCH-SIZE may only be set for dataset temp-tables"; break;
case 12362: msg = "COPY-DATASET must have a valid dataset handle as first argument"; break;
case 12373: msg = "You may not create BEFORE-TABLE %1 record"; break;
case 12374: msg = "You may not delete BEFORE-TABLE %1 record. Use ACCEPT-ROW-CHANGES instead"; break;
case 12375: msg = "You may not update BEFORE-TABLE %1 record"; break;
case 12377: msg = "DYNAMIC-NEXT-VALUE and DYNAMIC-CURRENT-VALUE require the second DBNAME argument"; break;
case 12384: msg = "DATA-RELATION parent and child buffers must also be in the dataset"; break;
case 12385: msg = "Pairs in DATA-RELATION RELATION-FIELDS phrase must be in the form: parent-field, child-field, parent-field, child-field etc"; break;
case 12411: msg = "Cannot set attribute for a static object"; break;
case 12744: msg = "Cannot update fields in a BEFORE-TABLE"; break; // This is a compile/conversion time error.
case 12747: msg = "You must provide a valid connected database name to dynamic sequence functions"; break;
case 12748: msg = "Unable to evaluate sequence name for dynamic sequence function"; break;
case 12753: msg = "In COPY-TEMP-TABLE operation the static target table %1 must have an empty BEFORE-TABLE if the source table has any BEFORE-TABLE records"; break;
case 12758: msg = "You may not EMPTY temp-table %1 when TRACKING-CHANGES is true"; break;
case 12764: msg = "REFERENCE-ONLY temp-table is unexpectedly UNKNOWN for %1"; break;
case 12766: msg = "%1 Cannot bind BY-REFERENCE parameter tables or datasets unless column datatypes, positions, extents and indexes match for tables %2 and %3"; break;
case 12778: msg = "GET-PARENT mode not supported for buffer GET-CHANGES"; break;
case 12779: msg = "Cannot have both exclude list and include list"; break;
case 12780: msg = "DATA-SOURCE-COMPLETE-MAP only legal for dataset tables"; break;
case 12781: msg = "DATA-SOURCE-COMPLETE-MAP requires prior ATTACH-DATA-SOURCE method to be run"; break;
case 12783: msg = "Odd number of tables in pairs list parameter in COPY-DATASET"; break;
case 12784: msg = "COPY-DATASET pair %1 and %2 must have a table in each dataset"; break;
case 12785: msg = "Table %1 not found in dataset %2"; break;
case 12786: msg = "FILL-WHERE-STRING cannot be assigned unless the DATA-SOURCE is attached to some dataset buffer by a prior ATTACH-DATA-SOURCE"; break;
case 12787: msg = "Attempt to reference uninitialized dataset"; break;
case 12788: msg = "OFF-END CALLBACK not supported for query %1 whose buffer %2 is not in a dataset"; break;
case 12790: msg = "Invalid value given for SCHEMA-MARSHAL %1"; break;
case 12860: msg = "Datatypes in pairlist argument for ATTACH-DATA-SOURCE, BUFFER-COPY or BUFFER-COMPARE methods must match for fields %1 and %2"; break;
case 13009: msg = "%1 BIND modifier not allowed for cases where neither the caller nor the called TABLE or DATASET parameter %2 has been defined REFERENCE-ONLY and neither caller nor called parameter is a TABLE-HANDLE or DATASET-HANDLE"; break;
case 13011: msg = "%1 If BIND used when caller TABLE or DATASET parameter is bound and called parameter %2 is also bound, the caller and called parameters must be the same instance"; break;
case 13012: msg = "%1 BIND or BY-REFERENCE modifier required on RUN, FUNCTION or METHOD invocation parameter when called TABLE or DATASET parameter %2 is REFERENCE-ONLY and is still not bound"; break;
case 13031: msg = "Cannot have both schema-location and write-schema arguments set"; break;
case 13032: msg = "Unable to create Temp-Table or dataset schema from XML Schema"; break;
case 13033: msg = "Verify Temp-Table or dataset schema against XML Schema failed"; break;
case 13035: msg = "Error reading XML file '%1'"; break;
case 13055: msg = "Unable to update indexes for table '%1'"; break;
case 13036: msg = "Error reading XML from a MEMPTR or LONGCHAR"; break;
case 13048: msg = "The XML for READ-XMLSCHEMA must start with a 'schema' element"; break;
case 13050: msg = "Unable to create record for '%1' during READ-XML/READ-JSON"; break;
case 13053: msg = "Unable to update field '%1' in table '%2'"; break;
case 13054: msg = "Invalid number of array elements for field '%1' in table '%2'"; break;
case 13058: msg = "No data instance in diffgram to parse"; break;
case 13063: msg = "REPLACE mode requires a unique primary index in the target table %1"; break;
case 13064: msg = "READ-XML encountered an error while parsing the XML Document: %1"; break;
case 13066: msg = "Unable to write XML Schema for temp-table %1"; break;
case 13078: msg = "Unsupported data type for XML Serialization: %1"; break;
case 13079: msg = "XML Schema definition for temp-table '%1' not found"; break;
case 13082: msg = "XML Schema definition for data-relation between '%1' and '%2' not found"; break;
case 13081: msg = "XML Schema definition for '%1' does not match dataset definition."; break;
case 13083: msg = "XML Schema contains extra temp-table definition '%1'"; break;
case 13086: msg = "XML Schema contains extra data-relation definition '%1'"; break;
case 13089: msg = "XML Schema definition for field '%1' in table '%2' does not match Temp-Table field definition"; break;
case 13090: msg = "XML Schema definition for field '%1' in table '%2' not found"; break;
case 13091: msg = "XML Schema definition for table '%1' contains extra field '%2'"; break;
case 13093: msg = "Write temp-table data failed for WRITE-XML"; break;
case 13095: msg = "WRITE-XML target-type must be 'FILE' when buffers of a dataset have a different NAMESPACE-URI"; break;
case 13097: msg = "Write schema failed for WRITE-XMLSCHEMA"; break;
case 13138: msg = "Temp-table '%1' not found in XML Schema"; break;
case 13139: msg = "Field '%1' already added from XML Schema"; break;
case 13140: msg = "Invalid 'maxOccurs' attribute value '%1' for field '%2'"; break;
case 13141: msg = "Invalid 'maxOccurs' attribute value set for BLOB or CLOB field '%1'"; break;
case 13142: msg = "Invalid format '%1' in XML Schema for field '%2'"; break;
case 13143: msg = "Unable to set initial value '%1' from XML Schema for field '%2'"; break;
case 13158: msg = "XML Methods only allowed on TEMP-TABLE buffers"; break;
case 13159: msg = "Dynamic temp-table must be in CLEAR or PREPARED state for READ-XML Methods"; break;
case 13160: msg = "Dynamic temp-table must be in PREPARED state for WRITE-XML Methods"; break;
case 13161: msg = "%1 If a caller or called REFERENCE-ONLY parameter %2 will become BOUND to its opposite as a result of the call, then the BIND keyword must be supplied on both the caller invocation and on the called parameter definition"; break;
case 13184: msg = "Invalid source-type for READ-XML: %1"; break;
case 13185: msg = "Invalid target-type for WRITE-XML: %1"; break;
case 13186: msg = "Handle type not valid as WRITE-XML target"; break;
case 13200: msg = "Odd number of values in field-type-mapping argument"; break;
case 13201: msg = "Invalid data type override '%1' for field '%2'"; break;
case 13233: msg = "Cannot set XML-NODE-TYPE of field '%1' to 'TEXT'"; break;
case 13273: msg = "Third argument to SET-CALLBACK must be a valid procedure handle or object reference"; break;
case 13272: msg = "Use SET-CALLBACK instead of SET-CALLBACK-PROCEDURE for methods in a class"; break;
case 13452: msg = "Method '%1' for callback must be valid public method"; break;
case 13511: msg = "The Session-free connection failed because the Appserver is in a session-managed operating mode"; break;
case 13514: msg = "%1 name '%2' in namespace '%3' not found in XML Document"; break;
case 13515: msg = "Invalid encoding for WRITE-XML"; break;
case 14274: msg = "MARK-ROW-STATE and MARK-NEW may only be used on a dataset member buffer with an existing BEFORE-TABLE"; break;
case 14275: msg = "Dataset record for %1 required for DATA-SOURCE-ROWID"; break;
case 14276: msg = "Must run ATTACH-DATA-SOURCE on buffer %1 in order to get DATA-SOURCE-ROWID"; break;
case 14277: msg = "Unable to identify DATA-SOURCE buffer %1 in DATA-SOURCE-ROWID"; break;
case 14278: msg = "Unable to generate FIND for %1 in DATA-SOURCE-ROWID"; break;
case 14282: msg = "Unable to recurse during FILL"; break;
case 14288: msg = "Invalid ROW-STATE given to MARK-ROW-STATE"; break;
case 14289: msg = "Invalid handle or handle type for MARK-ROW-STATE"; break;
case 14293: msg = "TOP-NAV-QUERY requires a valid query on top dataset buffer %1"; break;
case 14435: msg = "Large object fields can only be defined in NO-UNDO Temp-Tables"; break;
case 14500: msg = "Source file does not contain sufficient data starting at %1 to copy %2 bytes"; break; // no final DOT ?
case 14902: msg = "Indeterminate extent error"; break;
case 14904: msg = "Unable to get right-hand-side array for assignment"; break;
case 14905: msg = "Whole-array assignment target and source must have the same extent unless the target is indeterminate"; break;
case 14906: msg = "Uninitialized array used as source of assignment"; break;
case 14907: msg = "Could not convert data for array assignment"; break;
case 14908: msg = "Could not update indeterminate array in assignment"; break;
case 14909: msg = "Unable to extract array %1"; break;
case 14910: msg = "Dynamic BUFFER-VALUE array assignment for field %1 requires that extents match"; break;
case 15008: msg = "You may not add before-table buffer %1 to a DATASET"; break;
case 15342: msg = "Invalid encoding '%1' argument for %2"; break;
case 15344: msg = "Invalid mode '%1' for %2"; break;
case 15357: msg = "Invalid source-type for READ-JSON: %1"; break;
case 15358: msg = "Error parsing JSON: expected %1, but found %2"; break;
case 15360: msg = "Error parsing JSON: unexpected token: %1"; break;
case 15364: msg = "Array token encountered, but %1 is not an array field"; break;
case 15366: msg = "Unable to update indexes for table '%1'"; break;
case 15374: msg = "Unable to infer Temp-Table or dataset schema from JSON Data"; break;
case 15375: msg = "Dataset name '%1' in JSON does not match '%2'"; break;
case 15376: msg = "Temp-table name '%1' in JSON does not match '%2'"; break;
case 15378: msg = "JSON methods only allowed on TEMP-TABLE buffers"; break;
case 15391: msg = "Unsupported data type for JSON serialization: %1"; break;
case 15956: msg = "Only super-tenants may use a tenant-id other than their own in a sequence function"; break;
case 15974: msg = "Second argument to dynamic sequence function must be a database name character expression"; break;
case 15976: msg = "Database '%1' must be connected for tenant function %2"; break;
case 15977: msg = "Must supply a connected database name for tenant function %1"; break;
case 15991: msg = "Attempt to create in multi-tenant table '%1' where the partition (usually default) has not been allocated"; break;
case 16017: msg = "No record in buffer for SERIALIZE-ROW"; break;
case 16019: msg = "Invalid target-format '%1' for SERIALIZE-ROW"; break;
case 16020: msg = "omit-outer-object argument not allowed in SERIALIZE-ROW for XML"; break; // this message DOES NOT have a "." at the end!
case 16025: msg = "The argument to Dataset-handle:ADD-PARENT-ID-RELATION method was invalid."; break;
case 16027: msg = "Buffers in ADD-PARENT-ID-RELATION must be members of the dataset"; break;
case 16028: msg = "ADD-PARENT-ID-RELATION parent-id-field %1 is not valid"; break;
case 16029: msg = "ADD-PARENT-ID-RELATION parent-fields-before entry %1 is not valid"; break;
case 16030: msg = "ADD-PARENT-ID-RELATION parent-fields-after entry %1 is not valid"; break;
case 16031: msg = "Invalid XML-NODE-TYPE %1 for Dataset %2"; break;
case 16484: msg = "Duplicate field %1 found in PARENT-FIELDS-BEFORE / PARENT-FIELDS-AFTER"; break;
case 16617: msg = "%1 is not a %2 attribute for %3"; break; // unlike 4052, this does not have ** prefix
case 17092: msg = "Cannot set the DECIMALS attribute for a database field"; break;
case 17093: msg = "DECIMALS attribute only allowed on DECIMAL datatypes"; break;
case 17366: msg = "write-before-image not allowed for TEMP-TABLE or BUFFER object"; break;
case 17586: msg = "Cannot serialize object of unsupported type '%1'"; break;
case 19043: msg = "JsonArray is not a valid target-type for %1 method"; break;
case 19079: msg = "%1 argument for %2 is not valid"; break;
case 19080: msg = "JsonArray is not a valid source-type for READ-JSON on a ProDataset"; break;
default:
LOG.warning("Unknown error id: " + id);
}
if (params != null && params.length != 0)
{
StringBuilder sb = new StringBuilder();
boolean[] used = new boolean[params.length];
int lastWord = 0;
int scan = msg.indexOf('%');
while (scan >= 0)
{
sb.append(msg, lastWord, scan);
boolean found = false;
if (scan + 1 < msg.length())
{
char ch = msg.charAt(scan + 1);
if (ch >= '1' && ch <= '9')
{
int idx = ch - '0' - 1;
if (idx < params.length)
{
sb.append(params[idx]);
used[idx] = true;
}
else
{
LOG.warning("Not enough positional parameters (" + idx + "+) in ErrorMessage (" + id + ")");
}
found = true;
}
}
if (!found)
{
sb.append('%');
lastWord = scan + 1;
}
else
{
lastWord = scan + 2;
}
scan = msg.indexOf('%', lastWord);
}
// append the rest of the message, which does not contain any positional markers:
sb.append(msg.substring(lastWord));
boolean remains = false;
for (int i = 0; i < used.length; i++)
{
if (!used[i] && params[i] != null && !params[i].isEmpty())
{
remains = true;
break;
}
}
if (remains)
{
LOG.warning("Unused positional parameter in ErrorMessage (" + id + ")");
}
msg = sb.toString();
}
return msg;
}
/**
* Set the ERROR-OBJECT-DETAIL attribute, a handle of type SOAP-FAULT.
* <p>
* If the handle is valid and the referred resource is not a {@link SOAPFault} instance, then
* this will throw a {@link RuntimeException}.
* <p>
* If the handle is {@code null} or invalid, the {@link WorkArea#pendingErrorStatus} 's
* <code>soapFault</code> field will be set to {@code null}.
*
* @param fault
* The resource representing the soap-fault.
*/
static void setErrorObjectDetail(SOAPFaultImpl fault)
{
handle hFault = new handle();
// we need to force assignment, as passing the resource at the c'tor will not save it,
// because at this step we are in no-error mode.
hFault.set(fault);
work.get().pendingErrorStatus.soapFault = hFault;
}
/**
* Get an iterator on Progress-style error text strings, derived from the
* error information stored in <code>error</code> (including its possible
* chain of root causes).
*
* @param error
* A throwable which is an instance of
* <code>NumberedException</code> or which contains instances
* thereof.
*
* @return Iterator on error text strings.
*/
static Iterator<String> errorTexts(Throwable error)
{
return errorTexts(compileErrorEntries(error));
}
/**
* Get an iterator on Progress-style error text strings, derived from the
* error information stored in <code>error</code> (including its possible
* chain of root causes), and from the given error number and text.
*
* @param num
* Error number of the first message to occur in the resulting
* iteration.
* @param text
* Error text of the first message to occur in the resulting
* iteration.
* @param error
* A throwable which is an instance of
* <code>NumberedException</code> or which contains instances
* thereof.
*
* @return Iterator on error text strings.
*/
static Iterator<String> errorTexts(int num, String text, Throwable error)
{
List<ErrorEntry> list = compileErrorEntries(error);
ErrorEntry entry = new ErrorEntry(num, text);
list.add(entry);
return errorTexts(list);
}
/**
* Get an iterator on Progress-style error text strings, based upon the
* <code>ErrorEntry</code> objects stored in a list.
*
* @param entries
* List of error entries.
*
* @return Iterator on error text strings.
*/
private static Iterator<String> errorTexts(final List<ErrorEntry> entries)
{
return new Iterator<String>()
{
private final Iterator<ErrorEntry> iter = entries.iterator();
@Override
public boolean hasNext()
{
return iter.hasNext();
}
@Override
public String next()
{
ErrorEntry entry = iter.next();
return buildErrorText(entry.num, entry.text, entry.prefix);
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
/**
* Extract from <code>error</code> and any chained root cause exceptions
* of type {@link com.goldencode.p2j.NumberedException NumberedException}
* error messages and numbers with which to compile a list of error
* entries. This list is compiled in reverse order of the root cause
* chain traversal, such that the most specific errors appear earliest in
* the list. If no instances of <code>NumberedException</code> are
* detected during the traversal, the list will be empty.
*
* @param error
* Error from which to extract error messages and numbers.
*
* @return List of <code>ErrorEntry</code> objects.
*/
private static List<ErrorEntry> compileErrorEntries(Throwable error)
{
List<ErrorEntry> list = new LinkedList<>();
Throwable next = error;
do
{
if (next instanceof NumberedException)
{
NumberedException exc = (NumberedException) next;
int num = exc.getNumber();
String msg = exc.getMessage();
boolean prefix = exc.isPrefix();
ErrorEntry entry = new ErrorEntry(num, msg, prefix);
list.add(0, entry);
}
next = next.getCause();
}
while (next != null);
return list;
}
/**
* Write a formatted log entry with the error text. In silent error mode
* generate a warning and in normal mode generate a severe entry.
*
* @param message
* The text to insert into the log.
* @param silent
* <code>true</code> if silent error mode is active.
*/
private static void log(String message, boolean silent)
{
log(message, null, silent);
}
/**
* Write a formatted log entry with the error text. In silent error mode
* generate a warning and in normal mode generate a severe entry.
*
* @param message
* The text to insert into the log.
* @param trw
* The cause of the failure.
* @param silent
* <code>true</code> if silent error mode is active.
*/
private static void log(String message, Throwable trw, boolean silent)
{
if (isHeadless())
{
return;
}
Level lvl = silent || hasLegacyError() ? Level.FINE : Level.SEVERE;
if (LOG.isLoggable(lvl))
{
String msg = silent ? "(silent mode) " + message : message;
if (trw == null)
{
LOG.log(lvl, msg);
}
else
{
LOG.log(lvl, trw, msg);
}
}
}
/**
* Perform postprocessing for {@link #showErrorAndAbend(int[], String[])}.
*
* @param text
* Message text.
*/
private static void postprocessOnAbend(String[] text)
{
StringBuilder sb = new StringBuilder();
String sep = System.getProperty("line.separator");
for (int i = 0; i < text.length; i++)
{
sb.append(text[i]);
if (i != text.length - 1)
{
sb.append(sep);
}
}
throw new StopConditionException(sb.toString());
}
/**
* Reports on the context-local {@code silent} flag.
*
* @param wa
* Context-local error state.
*
* @return {@code true} if silent error mode is active.
*/
private static boolean isSilentWorker(WorkArea wa)
{
return wa.silent;
}
/**
* Throws an {@link ErrorConditionException} and if in silent mode, it sets the
* <code>pending</code> flag to <code>true</code>. NO error information (messages or
* error numbers) will be recorded in either case (i.e. silent mode or not). In silent
* mode the <code>ERROR-STATUS:ERROR</code> will be <code>true</code> but the other "records"
* of the error that normally exist, will not exist. No message will be displayed. Because
* no message is ever displayed or recorded, the normal error number/message processing
* is not executed.
*
* @param wa
* Context-local error state.
* @param msg
* The text to use in throwing the error exception.
*/
private static void noRecordOrThrowErrorWorker(WorkArea wa, String msg)
{
if (isSilentWorker(wa))
{
// ERROR-STATUS:ERROR flag is set, but no error is recorded
setPendingWorker(wa, true);
}
else
{
writeLog(msg);
}
// raise the ERROR condition
throw new ErrorConditionException(msg);
}
/**
* Worker method that executes a given block of code with silent error mode turned on for
* the duration of the code block. Before the code block is executed, silent error mode
* is turned on and after the code block completes (normally or abnormally), silent error
* mode will be disabled.
* <p>
* This method can only be executed on the server side.
*
* @param code
* The block of code to execute in silent mode.
* @param srv
* Server-side error workers.
* @param wa
* Context-local error state.
*
* @return The value of the ERROR-STATUS:ERROR flag when the execution is complete.
*/
private static boolean silentWorker(Runnable code, ServerErrorDataAccessor srv, WorkArea wa)
{
// enable silent error processing
boolean wasSilent = srv.isSilent();
if (!wasSilent)
{
srv.setSilent(wa, true);
}
try
{
code.run();
}
catch (LegacyErrorException lex)
{
srv.setPending(true);
LegacyError err = lex.getErrorRef();
if (!(err instanceof AppError) || !((AppError) err).isFromReturn())
{
ProError proErr = (ProError) err;
int numErr = err.getNumMessages().intValue();
int[] errnums = new int[numErr];
String[] errmsgs = new String[numErr];
for (int i = 0; i < numErr; i++)
{
errnums[i] = proErr.getMessageNum(new integer(i + 1)).intValue();
errmsgs[i] = proErr.getMessage(new integer(i + 1)).toStringMessage();
wa.pendingErrorStatus.addError(new ErrorEntry(errnums[i], errmsgs[i]));
}
addRaisedConditionWorker(wa, errnums);
}
if (lex.getError()._isValid())
{
ObjectOps.delete(err);
}
}
catch (ErrorConditionException err)
{
// normally we just eat the exception because it is being used to abort processing
// exactly where the error occurred, which will restart processing after the given
// code block; however, there is an exception for some deferred error processing use
// cases where we must bypass silent error mode (throwError(NumberedException, boolean))
if (err.isForce())
{
// don't honor silent mode, no suppression of errors here
throw err;
}
}
finally
{
// disable further silent error processing
if (!wasSilent)
{
srv.setSilent(wa, false);
}
forwardPending(wa);
}
return wa.errorStatus.error;
}
/**
* Execute the given block while supressing normal error processing by temporarily enabling the
* ignore flag.
*
* @param code
* The block of code to execute in ignore mode.
* @param wa
* Context-local error state.
*/
private static void ignoreWorker(Runnable code, WorkArea wa)
{
wa.ignore++;
try
{
code.run();
}
finally
{
wa.ignore--;
}
}
/**
* Queries the state of <code>ignore</code> mode.
*
* @param wa
* Context-local error state.
*
* @return <code>true</code> if ignore mode is enabled, otherwise <code>false</code>.
*/
private static boolean isIgnoreWorker(WorkArea wa)
{
return wa.ignore > 0;
}
/**
* Determines if headless mode should be supported. In headless mode, no
* client-side output will occur.
*
* @param wa
* Context-local error state.
*
* @return <code>true</code> if the headless flag is turned on, otherwise <code>false</code>.
*/
private static boolean isHeadlessWorker(WorkArea wa)
{
return headless || wa.headless || wa.canFindLevel > 0;
}
/**
* Adds an error entry to the error list.
*
* @param wa
* Context-local error state.
* @param num
* The error number.
* @param text
* The text of the error message.
* @param errFlag
* If <code>false</code> this should add the number and text to
* the error list but not set the error flag. If
* <code>true</code> the error flag should also be set.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
*/
private static void addErrorWorker(WorkArea wa, int num, String text, boolean errFlag, boolean prefix)
{
if (errFlag)
{
// this may reset the error list so it must be done BEFORE we add the new error to the list
setPendingWorker(wa, true);
}
String errmsg = buildErrorText(num, text, prefix, true);
// manually using the work area is OK in this case since this method is only used on the server
wa.pendingErrorStatus.addError(new ErrorEntry(num, errmsg));
// add error number to _MSG stack.
addRaisedConditionWorker(wa, new int[] { num });
}
/**
* Sets the <code>pendingError</code> variable.
*
* @param wa
* Context-local error state.
* @param value
* The new state of the variable.
*/
private static void setPendingWorker(WorkArea wa, boolean value)
{
wa.pendingErrorStatus.error = value;
}
/**
* Makes pending error status current.
*
* @param wa
* Context-local error state.
*/
private static void forwardPending(WorkArea wa)
{
wa.errorStatus.set(wa.pendingErrorStatus);
wa.pendingErrorStatus.initialize();
}
/**
* Gets err nums for raised errors.
*
* @param wa
* Context-local error state.
*
* @return stack of errors numbers
*/
private static LinkedList<Integer> getRaisedConditionsWorker(WorkArea wa)
{
return wa.raisedConditions;
}
/**
* Adds error numbers to raised error numbers stack (for _MSG function).
*
* @param errNums
* errors numbers.
*/
private static void addRaisedCondition(int[] errNums)
{
addRaisedConditionWorker(work.obtain(), errNums);
}
/**
* Adds error numbers to raised error numbers stack (for _MSG function).
*
* @param wa
* Context-local error state.
* @param errNums
* errors numbers.
*/
private static void addRaisedConditionWorker(WorkArea wa, int[] errNums)
{
if (isSilent() || errNums == null)
{
return;
}
LinkedList<Integer> raisedConditions = getRaisedConditionsWorker(wa);
for (int errNum : errNums)
{
raisedConditions.addFirst(errNum);
if (raisedConditions.size() > ERR_CONDITIONS_CAPACITY)
{
raisedConditions.removeLast();
}
}
}
/**
* Helper to expose error state in a way that avoids context local lookups.
*/
public static class ErrorHelper
{
/** Context-local error state. */
private WorkArea wa;
/** Server-side error methods. */
private ServerErrorDataAccessor srv;
/**
* Create a new instance and associate the given WorkArea instance.
*
* @param wa
* Context-local error state.
* @param srv
* Server-side error methods.
*/
public ErrorHelper(WorkArea wa, ServerErrorDataAccessor srv)
{
this.wa = wa;
this.srv = srv;
}
/**
* Worker method that executes a given block of code with silent error mode turned on for
* the duration of the code block. Before the code block is executed, silent error mode
* is turned on and after the code block completes (normally or abnormally), silent error
* mode will be disabled.
* <p>
* This method can only be executed on the server side. It will backup and restore the current
* {@link WorkArea#errorStatus}, to allow for nested silent-mode usages (internally by FWD).
*
* @param code
* The block of code to execute in silent mode.
* @param errorProcessor
* A consumer of the logged errors in silent mode.
*
* @return The value of the ERROR-STATUS:ERROR flag when the execution is complete.
*/
public boolean nestedSilent(Runnable code, BiConsumer<String, Integer> errorProcessor)
{
return ErrorManager.nestedSilent(wa, code, errorProcessor);
}
/**
* Reflects the value of SESSION:SUPPRESS-WARNINGS attribute.
*
* @return The suppress warnings flag.
*/
public boolean isSuppressWarnings()
{
return wa.suppressWarnings;
}
/**
* Reflects the value of SESSION:SUPPRESS-WARNINGS-LIST attribute.
*
* @return The suppress warnings list flag.
*/
public String getSuppressWarningsList()
{
return wa.suppressWarningsList;
}
/**
* Reports on the context-local {@code silent} flag.
*
* @return {@code true} if silent error mode is active.
*/
public boolean isSilent()
{
return isSilentWorker(wa);
}
/**
* Sets the <code>silent</code> variable.
*
* @param value
* The new state of the variable.
*/
public void setSilent(boolean value)
{
srv.setSilent(wa, value);
}
/**
* Worker method that executes a given block of code with silent error mode turned on for
* the duration of the code block. Before the code block is executed, silent error mode
* is turned on and after the code block completes (normally or abnormally), silent error
* mode will be disabled.
* <p>
* This method can only be executed on the server side.
*
* @param code
* The block of code to execute in silent mode.
*
* @return The value of the ERROR-STATUS:ERROR flag when the execution is complete.
*/
public boolean silent(Runnable code)
{
return silentWorker(code, srv, wa);
}
/**
* Queries the state of <code>pendingError</code> variable.
*
* @return <code>true</code> if pending error flag is turned on, otherwise
* <code>false</code>.
*/
public boolean isPending()
{
return wa.pendingErrorStatus.error;
}
/**
* Sets the <code>pendingError</code> variable.
*
* @param value
* The new state of the variable.
*/
public void setPending(boolean value)
{
setPendingWorker(wa, value);
}
/**
* Clears pending error status.
*/
public void clearPending()
{
wa.pendingErrorStatus.initialize();
}
/**
* Handles the change of the pending error status, making it
* into the current error status.
*/
public void handleForwardPending()
{
forwardPending(wa);
}
/**
* Retrieves the ERROR-STATUS:ERROR of the current WorkArea.
*
* @return value of ERROR-STATUS:ERROR of the current WorkArea.
*/
public boolean getErrorStatus()
{
return wa.errorStatus.error;
}
/**
* Determines if headless mode should be supported. In headless mode, no
* client-side output will occur.
*
* @return <code>true</code> if the headless flag is turned on, otherwise <code>false</code>.
*/
public boolean isHeadless()
{
return isHeadlessWorker(wa);
}
/**
* Set the headless flag for this context.
*
* @param headless
* {@code true} if this context has no UI.
*/
public void setHeadless(boolean headless)
{
wa.headless = headless;
}
/**
* Set the headless flag for this context.
*
* @param headless
* {@code true} if this context has no UI.
*/
public void setStopAfterTimer(StopAfterTimer stopAfterTimer)
{
wa.stopAfterTimer = stopAfterTimer;
}
/**
* Detects if the temporary CAN-FIND headless mode is active. This really should be something
* generic instead of being hard coded to the persistence package.
*
* @return <code>true</code> if this is a CAN-FIND bracket.
*/
public boolean isInCanFindBracket()
{
return wa.canFindLevel > 0;
}
/**
* Sets silent mode during CAN-FIND calls. The semaphore keep tracks of the nested CAN-FIND
* calls.
* If the counter is positive <code>ErrorManager</code> will run similar to headless mode
* and will silently discard error messages.
* <p>
* <strong>Note</strong>Always call this method in pairs (one with enter = true before
* executing the query and, one with enter = false, immediately after) to assure the
* correct management of the headless context flag.
*
* @param enter
* If <code>true</code> will increment the nested CAN-FIND count.
*/
public void setCanFindBracket(boolean enter)
{
if (enter)
{
// push a CAN-FIND level
wa.canFindLevel++;
}
else
{
// pop put CAN-FIND level:
wa.canFindLevel--;
}
}
/**
* Execute the given block while supressing normal error processing by temporarily enabling the
* ignore flag.
*
* @param code
* The code to execute.
*/
public void ignore(Runnable code)
{
ignoreWorker(code, wa);
}
/**
* Adds error numbers to raised error numbers stack
*
* @param e
* The condition exception meant to be iterated for causes
*/
public void addNumbersToConditionException(ConditionException e)
{
boolean found = false;
for (Throwable cause = e; cause != null; cause = cause.getCause())
{
if (cause instanceof NumberedException)
{
addRaisedConditionWorker(wa, ((NumberedException)cause).getNumbers());
found = true;
break;
}
}
if (!found)
{
addRaisedConditionWorker(wa, new int[]{0});
}
}
/**
* Adds an error entry to the error list.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param errFlag
* If <code>false</code> this should add the number and text to
* the error list but not set the error flag. If
* <code>true</code> the error flag should also be set.
* @param prefix
* <code>true</code> to prepend a double asterisk prefix to the
* formatted error message, <code>false</code> to omit this text.
*/
public void addError(int num, String text, boolean errFlag, boolean prefix)
{
addErrorWorker(wa, num, text, errFlag, prefix);
}
/**
* Throws an {@link ErrorConditionException} and if in silent mode, it sets the
* <code>pending</code> flag to <code>true</code>. NO error information (messages or
* error numbers) will be recorded in either case (i.e. silent mode or not). In silent
* mode the <code>ERROR-STATUS:ERROR</code> will be <code>true</code> but the other "records"
* of the error that normally exist, will not exist. No message will be displayed. Because
* no message is ever displayed or recorded, the normal error number/message processing
* is not executed.
* <p>
* This only works on the server side.
*
* @param msg
* The text to use in throwing the error exception.
*/
public void noRecordOrThrowError(String msg)
{
noRecordOrThrowErrorWorker(wa, msg);
}
/**
* Reports if warning mode has been enabled. When in warning mode, error messages are reported to the UI
* using an alert box.
*
* @return {@code true} if warning mode is enabled, otherwise {@code false}.
*/
public boolean isWarningMode()
{
return wa.errorsAsWarnings;
}
/**
* Configures warning mode, during which errors are non-fatal (when {@code on} is {@code true}), but are
* reported to the UI of the current context via a call to {@code LogicalTerminal.message()}.
* <p>
* Each call to this method must be matched with a corresponding call to {@link #warningModeDisable}.
*
* @param on
* Pass {@code true} to enable reported but non-fatal errors, and {@code false} to handle
* errors normally.
*/
public void setWarningMode(boolean on)
{
wa.errorsAsWarnings = on;
}
}
/**
* Stores the data related to a single error.
*/
public static class ErrorEntry
implements Serializable
{
/** Error number. */
public final int num;
/** Error text. */
public final String text;
/** Determines if the prefix should be displayed for the error message. */
public final boolean prefix;
/**
* Construct an instance.
*
* @param num
* The error number.
* @param text
* The text of the error message.
*/
public ErrorEntry(int num, String text)
{
this.num = num;
this.text = text;
this.prefix = true;
}
/**
* Construct an instance.
*
* @param num
* The error number.
* @param text
* The text of the error message.
* @param prefix
* Determines if the prefix should be displayed for the error message.
*/
public ErrorEntry(int num, String text, boolean prefix)
{
this.num = num;
this.text = text;
this.prefix = prefix;
}
}
/**
* Process errors with ignore.
*/
private static class SuppressedErrorDataAccessor
extends ServerErrorDataAccessor
{
/**
* Sets the {@code silent} variable.
*
* @param value
* The new state of the variable.
*/
@Override
public void setSilent(boolean value)
{
setSilent(work.obtain(), value);
}
/**
* Sets the {@code silent} variable.
*
* @param wa
* Context local data storage.
* @param value
* The new state of the variable.
*/
@Override
protected void setSilent(WorkArea wa, boolean value)
{
wa.silent = value;
}
/**
* Check if the top-level block is ROUTINE-LEVEL UNDO, THROW or the current program is
* BLOCK-LEVEL UNDO, THROW.
*
* @return see above.
*/
@Override
public boolean mustManageLegacyError()
{
return false;
}
/**
* Check if the current ERROR condition must be raised as a legacy {@link SysError}.
*
* @param forceBlockLevelUndoThrow
* Flag indicating that, when nothing else matches, a BLOCK-LEVEL UNDO, THROW exists by default.
*
* @return see above.
*/
@Override
public boolean mustManageLegacyError(boolean forceBlockLevelUndoThrow)
{
return false;
}
/**
* Check if the current ERROR condition must be raised as a legacy {@link SysError}.
*
* @return see above.
*/
@Override
public boolean mustThrowLegacyError()
{
return false;
}
/**
* Check if the current ERROR condition must be raised as a legacy {@link SysError}.
*
* @param forceBlockLevelUndoThrow
* Flag indicating that, when nothing else matches, a BLOCK-LEVEL UNDO, THROW exists by default.
*
* @return see above.
*/
@Override
public boolean mustThrowLegacyError(boolean forceBlockLevelUndoThrow)
{
return false;
}
/**
* Record the specified error message and number, to be later thrown as a
* {@link LegacyErrorException}.
*
* @param errmsg
* The error message.
* @param num
* The error number.
*/
@Override
public void recordForLegacyThrow(String errmsg, int num)
{
// no-op
}
/**
* Writes a log record.
*
* @param msg
* The log message.
* @param logCallStack
* Flag to indicate if call stack should be included in log.
* @param threadId
* The id of the thread recording the error message.
*/
@Override
public void writeLog(String msg, boolean logCallStack, long threadId)
{
// no-op
}
}
/**
* Provides access to the error data.
*/
private static class ServerErrorDataAccessor
implements RemoteErrorData
{
/**
* Queries the state of <code>silent</code> variable.
*
* @return <code>true</code> if silent mode is enabled, otherwise
* <code>false</code>.
*/
@Override
public boolean isSilent()
{
return work.obtain().silent;
}
/**
* Queries the state of <code>error</code> variable.
*
* @return <code>true</code> if error flag is turned on, otherwise
* <code>false</code>.
*/
@Override
public boolean isErrorFlag()
{
return work.obtain().errorStatus.error;
}
/**
* Sets the <code>pendingError</code> variable.
*
* @param value
* The new state of the variable.
*/
@Override
public void setPending(boolean value)
{
setPendingWorker(work.obtain(), value);
}
/**
* Gets the size of the list of error records.
*
* @return The size of the list.
*/
@Override
public int getSize()
{
List<Object> errors = work.obtain().errorStatus.errorList;
return errors == null ? 0 : errors.size();
}
/**
* Adds a record to the list of error records.
*
* @param record
* The object to add to the list of error records.
*/
@Override
public void addRecord(Object record)
{
work.obtain().pendingErrorStatus.addError(record);
}
/**
* Gets a record from the list of error records.
*
* @param index
* The record index.
*
* @return The object associated with the given index in the list.
*/
@Override
public Object getRecord(int index)
{
return work.obtain().errorStatus.getError(index);
}
/**
* Queries the state of <code>pendingError</code> variable.
*
* @return <code>true</code> if pending error flag is turned on, otherwise
* <code>false</code>.
*/
@Override
public boolean isPending()
{
return work.obtain().pendingErrorStatus.error;
}
/**
* Reflects the value of SESSION:SUPPRESS-WARNINGS attribute.
*
* @return see above.
*/
@Override
public boolean isSuppressWarnings()
{
return work.obtain().suppressWarnings;
}
/**
* Reflects the value of SESSION:SUPPRESS-WARNINGS-LIST attribute.
*
* @return see above.
*/
@Override
public String getSuppressWarningsList()
{
return work.obtain().suppressWarningsList;
}
/**
* Get the state of SESSION:SYSTEM-ALERT-BOXES attribute.
*
* @return the state of SESSION:SYSTEM-ALERT-BOXES attribute.
*/
@Override
public boolean isSystemAlertBoxes()
{
return work.obtain().systemAlertBoxes;
}
/**
* Sets the <code>silent</code> variable.
*
* @param value
* The new state of the variable.
*/
@Override
public void setSilent(boolean value)
{
setSilent(work.obtain(), value);
}
/**
* Sets the {@code silent} variable.
*
* @param wa
* Context local data storage.
* @param value
* The new state of the variable.
*/
protected void setSilent(WorkArea wa, boolean value)
{
wa.silent = value;
if (value)
{
// we can't delete the soapFault handle here, as this might be a call for accessing
// some soap-fault attribute/method with the NO-ERROR clause set!
}
else
{
if (wa.errorStatus.soapFault != null && wa.errorStatus.soapFault._isValid())
{
// delete it only if we haven't deleted it already
HandleOps.delete(wa.errorStatus.soapFault);
}
wa.errorStatus.soapFault = wa.pendingErrorStatus.soapFault;
wa.pendingErrorStatus.soapFault = null;
}
}
/**
* Check if the top-level block is ROUTINE-LEVEL UNDO, THROW or the current program is
* BLOCK-LEVEL UNDO, THROW.
*
* @return see above.
*/
@Override
public boolean mustManageLegacyError()
{
return ErrorManager.mustManageLegacyError();
}
/**
* Check if the current ERROR condition must be raised as a legacy {@link SysError}.
*
* @param forceBlockLevelUndoThrow
* Flag indicating that, when nothing else matches, a BLOCK-LEVEL UNDO, THROW exists by default.
*
* @return see above.
*/
@Override
public boolean mustManageLegacyError(boolean forceBlockLevelUndoThrow)
{
return ErrorManager.mustManageLegacyError(forceBlockLevelUndoThrow);
}
/**
* Check if the current ERROR condition must be raised as a legacy {@link SysError}.
*
* @return see above.
*/
@Override
public boolean mustThrowLegacyError()
{
return ErrorManager.mustThrowLegacyError();
}
/**
* Check if the current STOP condition must be raised as a legacy {@link Stop}.
*
* @return see above.
*/
public boolean mustThrowLegacyStop()
{
return ErrorManager.mustThrowLegacyStop();
}
/**
* Check if the current ERROR condition must be raised as a legacy {@link SysError}.
*
* @param forceBlockLevelUndoThrow
* Flag indicating that, when nothing else matches, a BLOCK-LEVEL UNDO, THROW exists by default.
*
* @return see above.
*/
@Override
public boolean mustThrowLegacyError(boolean forceBlockLevelUndoThrow)
{
return ErrorManager.mustThrowLegacyError(forceBlockLevelUndoThrow);
}
/**
* Record the specified error message and number, to be later thrown as a
* {@link LegacyErrorException}.
*
* @param errmsg
* The error message.
* @param num
* The error number.
*/
@Override
public void recordForLegacyThrow(String errmsg, int num)
{
ErrorManager.recordForLegacyThrow(errmsg, num);
}
/**
* Record the STOP condition, to be later thrown as a
* {@link LegacyStopException}.
*
* @param errmsg
* The error message.
* @param num
* The error number.
*/
public void recordForLegacyStop(StopConditionException ex)
{
ErrorManager.recordForLegacyStop(ex);
}
/**
* Throw the specified error message and number.
*
* @param num
* The error number.
* @param errmsg
* The error message.
*/
@Override
public void throwErrorConditionException(int num, String errmsg)
{
// throw on the Conversation thread
throw new ErrorConditionException(num, errmsg);
}
/**
* Writes a log record.
*
* @param msg
* The log message.
* @param logCallStack
* Flag to indicate if call stack should be included in log.
* @param threadId
* The id of the thread recording the error message.
*/
@Override
public void writeLog(String msg, boolean logCallStack, long threadId)
{
String[] callStack = !logCallStack ? null : ProcedureManager.getStackTrace().toArray(new String[0]);
LegacyLogOps.logMgr().writeMessage(msg,
null,
null,
LoggingSubsys.EMPTY,
LoggingLevel.ERROR,
callStack,
threadId);
}
/**
* Handles a SOAP fault.
*
* @param operationName
* The operation that failed.
* @param faultMsg
* The fault error msg.
* @param faultActor
* The fault actor.
* @param faultCode
* The fault code.
* @param faultString
* The fault string.
* @param faultDetailXML
* The fault details as xml.
*/
@Override
public void handleSoapFault(String operationName,
String faultMsg,
String faultActor,
String faultCode,
String faultString,
String faultDetailXML)
{
final String msg = "Web service operation %s generated a SOAP Fault. SOAP faultstring is: %s";
recordOrThrowError(new int[] { 11506 },
new String[] { String.format(msg, operationName, faultMsg) },
false,
true);
// if this is reached, we are in silent-error mode, thus generate the SOAP-fault object
SOAPFaultDetailImpl soapFaultDetail = SOAPFactory.createSOAPFaultDetail(faultDetailXML);
SOAPFaultImpl soapFault = SOAPFactory.createSOAPFault(
faultActor, faultCode, faultString, soapFaultDetail);
setErrorObjectDetail(soapFault);
}
}
/**
* Stores global data relating to the state of the current context's
* transactions.
*/
private static class WorkArea
{
/** The resource's ID. */
private Long id = null;
/** Current error status value. */
private ErrorStatus errorStatus = new ErrorStatus();
/** Pending error status value. */
private final ErrorStatus pendingErrorStatus = new ErrorStatus();
/** No error flag (if <code>true</code> exceptions are silently recorded rather than thrown). */
private boolean silent = false;
/**
* Flags when a query is executed in a CAN-FIND function.
* In a CAN-FIND, errors are "swallowed" similar to headless mode.
*/
private int canFindLevel = 0;
/** <code>true</code> to NO-OP all error recording, display and exception generation. */
private int ignore = 0;
/** Whether exceptions are processed as non-fatal, warning messages. */
private boolean errorsAsWarnings = false;
/** {@code true} to mark this context as having NO UI. */
private boolean headless = false;
/**
* Raised errors numbers stack. Used for undocumented _MSG function implementation.
* Can contain ≤25 elements, indexed starting from 1.
*/
private final LinkedList<Integer> raisedConditions = new LinkedList<>();
/** Reflects the value of SESSION:SUPPRESS-WARNINGS attribute. */
private boolean suppressWarnings = false;
/** Reflects the value of SESSION:SUPPRESS-WARNINGS-LIST attribute. */
private String suppressWarningsList = "";
/** Reflects the value of SESSION:SUPPRESS-WARNINGS-LIST attribute. */
private Set<Integer> suppressWarningsSet = new HashSet<>();
/** Holds the state of the SESSION:SYSTEM-ALERT-BOXES attribute. */
private boolean systemAlertBoxes = false;
/** The legacy-style OE error which is being built. */
private object<? extends SysError> legacyError = null;
/** The legacy-style OE STOP error which is being built. */
private object<? extends Stop> legacyStop = null;
/** Flag indicating that the deferred {@link #legacyError} must be marked as explicit. */
private boolean forceExplicit = false;
/** Object responsible for managing stop-after on Progress blocks. */
private StopAfterTimer stopAfterTimer = null;
}
/**
* Simple container that stores and returns a context-local instance of
* the global work area.
*/
private static class ContextContainer
extends ContextLocal<WorkArea>
{
/**
* Get the weight of this context-local var.
*
* @return Always {@link WeightFactor#LAST}.
*/
@Override
public WeightFactor getWeight()
{
return WeightFactor.LAST;
}
/**
* Obtains the context-local instance of the contained global work
* area.
*
* @return The work area associated with this context.
*/
public WorkArea obtain()
{
return this.get();
}
/**
* Initializes the work area, the first time it is requested within a
* new context.
*
* @return The newly instantiated work area.
*/
@Override
protected synchronized WorkArea initialValue()
{
return new WorkArea();
}
}
/**
* This class corresponds ERROR-STATUS p2j handle, that can contain supressed errors
* information in case of NO-ERROR expression used in P4GL statement.
*/
private static class ErrorStatus
{
/** List of errors. */
private ArrayList<Object> errorList = null;
/** Stores whether an error condition has occurred previously. */
private boolean error = false;
/**
* Handle containing the value of the ERROR-OBJECT-DETAIL attribute set by the last statement
* executed with a NO-ERROR clause.
*/
private handle soapFault = null;
/**
* Add an error entry to the {@link #errorList}.
*
* @param error
* The error to add.
*/
private void addError(Object error)
{
if (errorList == null)
{
errorList = new ArrayList<>(1);
}
errorList.add(error);
}
/**
* Get an error from the {@link #errorList}.
*
* @param index
* The index.
*
* @return See above.
*/
public Object getError(int index)
{
if (errorList == null)
{
throw new ArrayIndexOutOfBoundsException();
}
return errorList.get(index);
}
/**
* Sets all fields to initial state.
*/
private void initialize()
{
if (this.errorList != null)
{
this.errorList.clear();
}
this.error = false;
this.soapFault = null;
}
/**
* Sets all fields with values from specified errStatus.
*
* @param errStatus
* source error status.
*/
private void set(ErrorStatus errStatus)
{
if (errStatus.errorList != null && !errStatus.errorList.isEmpty())
{
if (this.errorList == null)
{
this.errorList = new ArrayList<>();
}
else
{
this.errorList.clear();
}
this.errorList.addAll(errStatus.errorList);
}
else
{
this.errorList = null;
}
this.error = errStatus.error;
this.soapFault = errStatus.soapFault;
}
}
}