[NO ISSUE][RT] Best-effort serialization of ErrorCode enum values

Change-Id: Ia8ffec3961f20db9355ec6b309a5b6fd99921da9
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/10683
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Michael Blow <mblow@apache.org>
Reviewed-by: Murtadha Hubail <mhubail@apache.org>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
index fcc6993..5153ebb 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
@@ -18,13 +18,7 @@
  */
 package org.apache.asterix.api.http.server;
 
-import static org.apache.asterix.common.exceptions.ErrorCode.INVALID_REQ_JSON_VAL;
-import static org.apache.asterix.common.exceptions.ErrorCode.INVALID_REQ_PARAM_VAL;
 import static org.apache.asterix.common.exceptions.ErrorCode.NO_STATEMENT_PROVIDED;
-import static org.apache.asterix.common.exceptions.ErrorCode.REJECT_BAD_CLUSTER_STATE;
-import static org.apache.asterix.common.exceptions.ErrorCode.REJECT_NODE_UNREGISTERED;
-import static org.apache.asterix.common.exceptions.ErrorCode.REQUEST_TIMEOUT;
-import static org.apache.hyracks.api.exceptions.ErrorCode.JOB_REQUIREMENTS_EXCEED_CAPACITY;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -35,6 +29,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentMap;
 import java.util.function.Function;
@@ -65,6 +60,7 @@
 import org.apache.asterix.common.context.IStorageComponentProvider;
 import org.apache.asterix.common.dataflow.ICcApplicationContext;
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.exceptions.RuntimeDataException;
 import org.apache.asterix.compiler.provider.ILangCompilationProvider;
 import org.apache.asterix.lang.common.base.IParser;
@@ -86,7 +82,8 @@
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.api.application.IServiceContext;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
-import org.apache.hyracks.api.exceptions.HyracksException;
+import org.apache.hyracks.api.exceptions.IError;
+import org.apache.hyracks.api.exceptions.IFormattedException;
 import org.apache.hyracks.api.exceptions.Warning;
 import org.apache.hyracks.api.result.IResultSet;
 import org.apache.hyracks.control.common.controllers.CCConfig;
@@ -419,6 +416,38 @@
         buildResponseResults(responsePrinter, sessionOutput, translator.getExecutionPlans(), warnings);
     }
 
+    protected boolean handleIFormattedException(IError error, IFormattedException ex,
+            RequestExecutionState executionState, QueryServiceRequestParameters param) {
+        if (error instanceof ErrorCode) {
+            switch ((ErrorCode) error) {
+                case INVALID_REQ_PARAM_VAL:
+                case INVALID_REQ_JSON_VAL:
+                case NO_STATEMENT_PROVIDED:
+                    executionState.setStatus(ResultStatus.FATAL, HttpResponseStatus.BAD_REQUEST);
+                    return true;
+                case REQUEST_TIMEOUT:
+                    LOGGER.info(() -> "handleException: request execution timed out: "
+                            + LogRedactionUtil.userData(param.toString()));
+                    executionState.setStatus(ResultStatus.TIMEOUT, HttpResponseStatus.OK);
+                    return true;
+                case REJECT_NODE_UNREGISTERED:
+                case REJECT_BAD_CLUSTER_STATE:
+                    LOGGER.warn(() -> "handleException: " + ex.getMessage() + ": "
+                            + LogRedactionUtil.userData(param.toString()));
+                    executionState.setStatus(ResultStatus.FATAL, HttpResponseStatus.SERVICE_UNAVAILABLE);
+                default:
+                    // fall-through
+            }
+        } else if (error instanceof org.apache.hyracks.api.exceptions.ErrorCode) {
+            switch ((org.apache.hyracks.api.exceptions.ErrorCode) error) {
+                case JOB_REQUIREMENTS_EXCEED_CAPACITY:
+                    executionState.setStatus(ResultStatus.FATAL, HttpResponseStatus.BAD_REQUEST);
+                    return true;
+            }
+        }
+        return false;
+    }
+
     protected void handleExecuteStatementException(Throwable t, RequestExecutionState executionState,
             QueryServiceRequestParameters param) {
         if (t instanceof org.apache.asterix.lang.sqlpp.parser.TokenMgrError || t instanceof AlgebricksException) {
@@ -430,30 +459,17 @@
                         + LogRedactionUtil.statement(param.toString()));
             }
             executionState.setStatus(ResultStatus.FATAL, HttpResponseStatus.BAD_REQUEST);
-        } else if (t instanceof HyracksException) {
-            HyracksException he = (HyracksException) t;
-            // TODO(mblow): reconsolidate
-            if (he.matchesAny(INVALID_REQ_PARAM_VAL, INVALID_REQ_JSON_VAL, NO_STATEMENT_PROVIDED,
-                    JOB_REQUIREMENTS_EXCEED_CAPACITY)) {
-                executionState.setStatus(ResultStatus.FATAL, HttpResponseStatus.BAD_REQUEST);
-            } else if (he.matches(REQUEST_TIMEOUT)) {
-                LOGGER.info(() -> "handleException: request execution timed out: "
-                        + LogRedactionUtil.userData(param.toString()));
-                executionState.setStatus(ResultStatus.TIMEOUT, HttpResponseStatus.OK);
-            } else if (he.matchesAny(REJECT_BAD_CLUSTER_STATE, REJECT_NODE_UNREGISTERED)) {
-                LOGGER.warn(() -> "handleException: " + he.getMessage() + ": "
-                        + LogRedactionUtil.userData(param.toString()));
-                executionState.setStatus(ResultStatus.FATAL, HttpResponseStatus.SERVICE_UNAVAILABLE);
-            } else {
-                LOGGER.warn(() -> "handleException: unexpected exception " + he.getMessage() + ": "
-                        + LogRedactionUtil.userData(param.toString()), he);
-                executionState.setStatus(ResultStatus.FATAL, HttpResponseStatus.INTERNAL_SERVER_ERROR);
+            return;
+        } else if (t instanceof IFormattedException) {
+            IFormattedException formattedEx = (IFormattedException) t;
+            Optional<IError> maybeError = formattedEx.getError();
+            if (maybeError.isPresent()
+                    && handleIFormattedException(maybeError.get(), (IFormattedException) t, executionState, param)) {
+                return;
             }
-        } else {
-            LOGGER.warn(() -> "handleException: unexpected exception: " + LogRedactionUtil.userData(param.toString()),
-                    t);
-            executionState.setStatus(ResultStatus.FATAL, HttpResponseStatus.INTERNAL_SERVER_ERROR);
         }
+        LOGGER.warn(() -> "handleException: unexpected exception: " + LogRedactionUtil.userData(param.toString()), t);
+        executionState.setStatus(ResultStatus.FATAL, HttpResponseStatus.INTERNAL_SERVER_ERROR);
     }
 
     private void setSessionConfig(SessionOutput sessionOutput, QueryServiceRequestParameters param,
diff --git a/hyracks-fullstack/algebricks/algebricks-common/src/main/java/org/apache/hyracks/algebricks/common/exceptions/AlgebricksException.java b/hyracks-fullstack/algebricks/algebricks-common/src/main/java/org/apache/hyracks/algebricks/common/exceptions/AlgebricksException.java
index f00161c..4b8179c 100644
--- a/hyracks-fullstack/algebricks/algebricks-common/src/main/java/org/apache/hyracks/algebricks/common/exceptions/AlgebricksException.java
+++ b/hyracks-fullstack/algebricks/algebricks-common/src/main/java/org/apache/hyracks/algebricks/common/exceptions/AlgebricksException.java
@@ -18,6 +18,9 @@
  */
 package org.apache.hyracks.algebricks.common.exceptions;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.io.Serializable;
 import java.util.Optional;
 
@@ -28,7 +31,7 @@
 import org.apache.hyracks.api.util.ErrorMessageUtil;
 
 public class AlgebricksException extends Exception implements IFormattedException {
-    private static final long serialVersionUID = 1L;
+    private static final long serialVersionUID = 2L;
 
     public static final int UNKNOWN = 0;
     private final String component;
@@ -36,7 +39,7 @@
     private final Serializable[] params;
     private final String nodeId;
     private final SourceLocation sourceLoc;
-    protected final transient IError error;
+    protected transient IError error;
 
     @SuppressWarnings("squid:S1165") // exception class not final
     private transient volatile String msgCache;
@@ -134,4 +137,12 @@
         }
         return msgCache;
     }
+
+    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
+        ErrorMessageUtil.writeObjectWithError(error, out);
+    }
+
+    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
+        error = ErrorMessageUtil.readObjectWithError(in).orElse(null);
+    }
 }
diff --git a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/exceptions/HyracksException.java b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/exceptions/HyracksException.java
index d6085f6..0769e18 100644
--- a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/exceptions/HyracksException.java
+++ b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/exceptions/HyracksException.java
@@ -25,15 +25,15 @@
 import org.apache.hyracks.api.util.ErrorMessageUtil;
 
 public class HyracksException extends IOException implements IFormattedException {
-    private static final long serialVersionUID = 1L;
+    private static final long serialVersionUID = 2L;
 
     public static final int UNKNOWN = 0;
     private final String component;
     private final int errorCode;
     private final Serializable[] params;
     private final String nodeId;
-    protected transient final IError error;
     private SourceLocation sourceLoc;
+    protected transient IError error;
     private transient volatile String msgCache;
 
     public static HyracksException create(Throwable cause) {
@@ -166,7 +166,11 @@
         return Optional.ofNullable(error);
     }
 
-    public boolean matches(ErrorCode errorCode) {
-        return component.equals(errorCode.component()) && getErrorCode() == errorCode.intValue();
+    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
+        ErrorMessageUtil.writeObjectWithError(error, out);
+    }
+
+    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
+        error = ErrorMessageUtil.readObjectWithError(in).orElse(null);
     }
 }
diff --git a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/exceptions/IFormattedException.java b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/exceptions/IFormattedException.java
index 1f814b4..33b3995 100644
--- a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/exceptions/IFormattedException.java
+++ b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/exceptions/IFormattedException.java
@@ -46,8 +46,13 @@
     String getMessage();
 
     /**
+     * See {@link Throwable#getSuppressed()}
+     */
+    Throwable[] getSuppressed();
+
+    /**
      * If available, returns the {@link IError} associated with this exception
-     * @return the error instance, othewise {@link Optional#empty()}
+     * @return the error instance, otherwise {@link Optional#empty()}
      * @since 0.3.5.1
      */
     Optional<IError> getError();
@@ -85,4 +90,16 @@
     static boolean matchesAny(Throwable th, IError candidate, IError... otherCandidates) {
         return th instanceof IFormattedException && ((IFormattedException) th).matchesAny(candidate, otherCandidates);
     }
+
+    /**
+     * If the supplied {@link Throwable} is an instance of {@link IFormattedException}, return the {@link IError}
+     * associated with this exception if available
+     *
+     * @return the error instance, otherwise {@link Optional#empty()}
+     * @since 0.3.5.1
+     */
+    static Optional<IError> getError(Throwable throwable) {
+        return throwable instanceof IFormattedException ? ((IFormattedException) throwable).getError()
+                : Optional.empty();
+    }
 }
diff --git a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/util/ErrorMessageUtil.java b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/util/ErrorMessageUtil.java
index a7372ae..6479c8d 100644
--- a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/util/ErrorMessageUtil.java
+++ b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/util/ErrorMessageUtil.java
@@ -21,11 +21,14 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.io.Serializable;
 import java.util.Arrays;
 import java.util.Formatter;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Properties;
 
 import org.apache.hyracks.api.exceptions.IError;
@@ -140,4 +143,22 @@
         }
         return enumMessages;
     }
+
+    public static void writeObjectWithError(IError error, ObjectOutputStream out) throws IOException {
+        out.defaultWriteObject();
+        out.writeObject(error);
+    }
+
+    public static Optional<IError> readObjectWithError(ObjectInputStream in)
+            throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        try {
+            return Optional.ofNullable((IError) in.readObject());
+        } catch (IllegalArgumentException e) {
+            // this is expected in case of error codes not available in this version; return null
+            LOGGER.debug("unable to deserialize error object due to {}, the error reference will be empty",
+                    String.valueOf(e));
+            return Optional.empty();
+        }
+    }
 }