GEODE-10402: Fix FunctionException handling (#981)

* GEODE-10402: Fix FunctionException handling

 - Fixed handling for FunctionException.
 - Added InternalFunctionInvocationTargetException and replaced
   GF_FUNCTION_EXCEPTION by
   GF_INTERNAL_FUNCTION_INVOCATION_TARGET_EXCEPTION, so function
   exceptions are properly handled.
 - Code modified to adapt to the above changes.

* GEODE-10402: Revision 1

 - Actually added handling for FunctionException exception in
   ThinClientRegion.
 - Removed InternalFunctionInvocationTargetException as part of the
   public API since this is part of the internal Java API.
 - Added a new IT to verify that FunctionExceptions thrown by the user
   are actually thrown in the native library as FunctionException's.

* GEODE-10402: Format code

* GEODE-10402: Revision 2

 - Fixed clang-tidy warnings.
 - Fixed format on one of the Java classes for testing.
diff --git a/cppcache/include/geode/ExceptionTypes.hpp b/cppcache/include/geode/ExceptionTypes.hpp
index e18ea63..783d6e0 100644
--- a/cppcache/include/geode/ExceptionTypes.hpp
+++ b/cppcache/include/geode/ExceptionTypes.hpp
@@ -681,14 +681,15 @@
 /**
  *@brief Thrown if function execution failed
  **/
-class APACHE_GEODE_EXPORT FunctionExecutionException : public Exception {
+class APACHE_GEODE_EXPORT FunctionException : public Exception {
  public:
   using Exception::Exception;
-  ~FunctionExecutionException() noexcept override {}
+  ~FunctionException() noexcept override {}
   std::string getName() const override {
-    return "apache::geode::client::FunctionExecutionException";
+    return "apache::geode::client::FunctionException";
   }
 };
+
 /**
  *@brief Thrown if the No locators are active to reply for new connection.
  **/
@@ -850,6 +851,8 @@
   }
 };
 
+using FunctionExecutionException = FunctionException;
+
 }  // namespace client
 }  // namespace geode
 }  // namespace apache
diff --git a/cppcache/integration/test/FunctionExecutionTest.cpp b/cppcache/integration/test/FunctionExecutionTest.cpp
index bc509b8..fa4d3ce 100644
--- a/cppcache/integration/test/FunctionExecutionTest.cpp
+++ b/cppcache/integration/test/FunctionExecutionTest.cpp
@@ -44,12 +44,16 @@
 using apache::geode::client::CacheImpl;
 using apache::geode::client::CacheRegionHelper;
 using apache::geode::client::Exception;
-using apache::geode::client::FunctionExecutionException;
+using apache::geode::client::FunctionException;
 using apache::geode::client::FunctionService;
 using apache::geode::client::NotConnectedException;
 using apache::geode::client::Region;
 using apache::geode::client::RegionShortcut;
 using apache::geode::client::ResultCollector;
+
+using ::testing::Eq;
+using ::testing::Le;
+
 const int ON_SERVERS_TEST_REGION_ENTRIES_SIZE = 34;
 const int PARTITION_REGION_ENTRIES_SIZE = 113;
 
@@ -103,7 +107,7 @@
 
   ASSERT_THROW(FunctionService::onServer(region->getRegionService())
                    .execute("I_Don_t_Exist"),
-               FunctionExecutionException);
+               FunctionException);
 }
 
 TEST(FunctionExecutionTest, UnknownFunctionOnRegion) {
@@ -122,7 +126,7 @@
   auto region = setupRegion(cache);
 
   ASSERT_THROW(FunctionService::onRegion(region).execute("I_Don_t_Exist"),
-               FunctionExecutionException);
+               FunctionException);
 }
 
 TEST(FunctionExecutionTest, UnknownFunctionAsyncOnServer) {
@@ -143,7 +147,7 @@
   ASSERT_THROW(FunctionService::onServer(region->getRegionService())
                    .withCollector(std::make_shared<TestResultCollector>())
                    .execute("I_Don_t_Exist"),
-               FunctionExecutionException);
+               FunctionException);
 }
 
 TEST(FunctionExecutionTest, UnknownFunctionAsyncOnRegion) {
@@ -164,7 +168,7 @@
   ASSERT_THROW(FunctionService::onRegion(region)
                    .withCollector(std::make_shared<TestResultCollector>())
                    .execute("I_Don_t_Exist"),
-               FunctionExecutionException);
+               FunctionException);
 }
 
 TEST(FunctionExecutionTest,
@@ -356,8 +360,7 @@
   auto functionService = FunctionService::onRegion(region);
   auto execute =
       functionService.withCollector(std::make_shared<TestResultCollector>());
-  ASSERT_THROW(execute.execute("MultiGetAllFunctionNonHA"),
-               FunctionExecutionException);
+  ASSERT_THROW(execute.execute("MultiGetAllFunctionNonHA"), FunctionException);
 }
 
 TEST(FunctionExecutionTest,
@@ -415,8 +418,7 @@
   auto execute =
       functionService.withCollector(std::make_shared<TestResultCollector>())
           .withFilter(filter);
-  ASSERT_THROW(execute.execute("MultiGetAllFunctionNonHA"),
-               FunctionExecutionException);
+  ASSERT_THROW(execute.execute("MultiGetAllFunctionNonHA"), FunctionException);
 }
 
 TEST(FunctionExecutionTest, OnServersWithReplicatedRegionsInPool) {
@@ -558,3 +560,35 @@
 
   threadAux->join();
 }
+
+TEST(FunctionExecutionTest, testUserFunctionExceptionThrowsRightException) {
+  Cluster cluster{
+      InitialLocators{{{"localhost", Framework::getAvailablePort()}}},
+      InitialServers{{{"localhost", Framework::getAvailablePort()},
+                      {"localhost", Framework::getAvailablePort()},
+                      {"localhost", Framework::getAvailablePort()}}}};
+
+  cluster.start([&]() {
+    cluster.getGfsh()
+        .deploy()
+        .jar(getFrameworkString(FrameworkVariable::JavaObjectJarPath))
+        .execute();
+  });
+
+  cluster.getGfsh()
+      .create()
+      .region()
+      .withName("region")
+      .withType("REPLICATE")
+      .execute();
+
+  auto cache = cluster.createCache();
+  auto region = cache.createRegionFactory(RegionShortcut::PROXY)
+                    .setPoolName("default")
+                    .create("region");
+
+  auto functionService = FunctionService::onRegion(region);
+  auto execute =
+      functionService.withCollector(std::make_shared<TestResultCollector>());
+  EXPECT_THROW(execute.execute("UserExceptionFunction"), FunctionException);
+}
diff --git a/cppcache/integration/test/PdxJsonTypeTest.cpp b/cppcache/integration/test/PdxJsonTypeTest.cpp
index 9c79178..0d54094 100644
--- a/cppcache/integration/test/PdxJsonTypeTest.cpp
+++ b/cppcache/integration/test/PdxJsonTypeTest.cpp
@@ -45,9 +45,7 @@
 using apache::geode::client::CacheableKey;
 using apache::geode::client::CacheableString;
 using apache::geode::client::CacheRegionHelper;
-using apache::geode::client::CacheServerException;
 using apache::geode::client::FunctionService;
-using apache::geode::client::IllegalStateException;
 using apache::geode::client::LocalRegion;
 using apache::geode::client::PdxFieldTypes;
 using apache::geode::client::PdxInstance;
diff --git a/cppcache/src/DefaultResultCollector.cpp b/cppcache/src/DefaultResultCollector.cpp
index c81c0c0..5da7dac 100644
--- a/cppcache/src/DefaultResultCollector.cpp
+++ b/cppcache/src/DefaultResultCollector.cpp
@@ -32,7 +32,7 @@
     return resultList;
   }
 
-  throw FunctionExecutionException(
+  throw FunctionException(
       "Result is not ready, endResults callback is called before invoking "
       "getResult() method");
 }
diff --git a/cppcache/src/ErrType.hpp b/cppcache/src/ErrType.hpp
index e15c38a..2dbaab9 100644
--- a/cppcache/src/ErrType.hpp
+++ b/cppcache/src/ErrType.hpp
@@ -103,7 +103,7 @@
   GF_CACHE_ENTRY_UPDATED = 133,
   GF_CACHE_LOCATOR_EXCEPTION = 134, /** Exception in Locator */
   GF_INVALID_DELTA = 135,
-  GF_FUNCTION_EXCEPTION = 136,
+  GF_INTERNAL_FUNCTION_INVOCATION_TARGET_EXCEPTION = 136,
   GF_ROLLBACK_EXCEPTION = 137,
   GF_COMMIT_CONFLICT_EXCEPTION = 138,
   GF_TRANSACTION_DATA_NODE_HAS_DEPARTED_EXCEPTION = 139,
@@ -111,6 +111,7 @@
   GF_PUTALL_PARTIAL_RESULT_EXCEPTION = 141,
   GF_LOW_MEMORY_EXCEPTION = 142,
   GF_QUERY_EXECUTION_LOW_MEMORY_EXCEPTION = 143,
+  GF_FUNCTION_EXCEPTION = 144,
   GF_EUNDEF = 999 /**< unknown exception */
 } GfErrType;
 
diff --git a/cppcache/src/ExceptionTypes.cpp b/cppcache/src/ExceptionTypes.cpp
index 252a0ee..9f8033b 100644
--- a/cppcache/src/ExceptionTypes.cpp
+++ b/cppcache/src/ExceptionTypes.cpp
@@ -42,7 +42,7 @@
 using apache::geode::client::EntryDestroyedException;
 using apache::geode::client::EntryExistsException;
 using apache::geode::client::EntryNotFoundException;
-using apache::geode::client::FunctionExecutionException;
+using apache::geode::client::FunctionException;
 using apache::geode::client::GeodeIOException;
 using apache::geode::client::IllegalArgumentException;
 using apache::geode::client::IllegalStateException;
@@ -316,11 +316,11 @@
   throw AllConnectionsInUseException{message};
 }
 
-[[noreturn]] void functionExecutionException(std::string message,
-                                             std::string& exMsg, GfErrType,
-                                             std::string) {
+[[noreturn]] void functionException(std::string message,
+                                    const std::string& exMsg, GfErrType,
+                                    std::string) {
   message.append(!exMsg.empty() ? exMsg : ": Function execution failed");
-  throw FunctionExecutionException{message};
+  throw FunctionException{message};
 }
 
 [[noreturn]] void diskFailureException(std::string message, std::string& exMsg,
@@ -440,7 +440,8 @@
       {GF_REMOTE_QUERY_EXCEPTION, queryException},
       {GF_CACHE_LOCATOR_EXCEPTION, noAvailableLocatorsException},
       {GF_ALL_CONNECTIONS_IN_USE_EXCEPTION, allConnectionsInUseException},
-      {GF_FUNCTION_EXCEPTION, functionExecutionException},
+      {GF_INTERNAL_FUNCTION_INVOCATION_TARGET_EXCEPTION, functionException},
+      {GF_FUNCTION_EXCEPTION, functionException},
       {GF_DISKFULL, diskFailureException},
       {GF_ROLLBACK_EXCEPTION, rollbackException},
       {GF_COMMIT_CONFLICT_EXCEPTION, commitConflictException},
diff --git a/cppcache/src/ExecutionImpl.cpp b/cppcache/src/ExecutionImpl.cpp
index c35fb19..d6d38bc 100644
--- a/cppcache/src/ExecutionImpl.cpp
+++ b/cppcache/src/ExecutionImpl.cpp
@@ -381,7 +381,7 @@
     }
     case TcrMessage::REQUEST_DATA_ERROR: {
       LOGERROR("Error message from server: " + reply.getValue()->toString());
-      throw FunctionExecutionException(reply.getValue()->toString());
+      throw FunctionException(reply.getValue()->toString());
     }
     default: {
       LOGERROR("Unknown message type %d while getting function attributes.",
@@ -427,7 +427,7 @@
       } else {
         message = "Execute: failed to execute function with server.";
       }
-      throw FunctionExecutionException(message);
+      throw FunctionException(message);
     } else {
       throwExceptionIfError("Execute", err);
     }
diff --git a/cppcache/src/NoResult.hpp b/cppcache/src/NoResult.hpp
index 119cc66..161ef0f 100644
--- a/cppcache/src/NoResult.hpp
+++ b/cppcache/src/NoResult.hpp
@@ -52,7 +52,7 @@
 
   inline std::shared_ptr<CacheableVector> getResult(
       std::chrono::milliseconds) final {
-    throw FunctionExecutionException(
+    throw FunctionException(
         "Cannot return any result, as Function.hasResult() is false");
   }
 
diff --git a/cppcache/src/ThinClientRegion.cpp b/cppcache/src/ThinClientRegion.cpp
index 682e01a..3957514 100644
--- a/cppcache/src/ThinClientRegion.cpp
+++ b/cppcache/src/ThinClientRegion.cpp
@@ -2648,6 +2648,13 @@
   } else if (exceptionMsg.find("org.apache.geode.internal.cache.execute."
                                "InternalFunctionInvocationTargetException") !=
              std::string::npos) {
+    error = GF_INTERNAL_FUNCTION_INVOCATION_TARGET_EXCEPTION;
+  } else if (exceptionMsg.find("org.apache.geode.internal.cache.execute."
+                               "InternalFunctionInvocationTargetException") !=
+             std::string::npos) {
+    error = GF_INTERNAL_FUNCTION_INVOCATION_TARGET_EXCEPTION;
+  } else if (exceptionMsg.find("org.apache.geode.cache.execute."
+                               "FunctionException") != std::string::npos) {
     error = GF_FUNCTION_EXCEPTION;
   } else if (exceptionMsg.find(
                  "org.apache.geode.cache.CommitConflictException") !=
@@ -2881,15 +2888,16 @@
     if (ThinClientBaseDM::isFatalClientError(err)) {
       throwExceptionIfError("ExecuteOnRegion:", err);
     } else if (err != GF_NOERR) {
-      if (err == GF_FUNCTION_EXCEPTION) {
+      if (err == GF_INTERNAL_FUNCTION_INVOCATION_TARGET_EXCEPTION) {
         reExecute = true;
         rc->clearResults();
         std::shared_ptr<CacheableHashSet> failedNodesIds(reply.getFailedNode());
         failedNodes->clear();
         if (failedNodesIds) {
           LOGDEBUG(
-              "ThinClientRegion::executeFunction with GF_FUNCTION_EXCEPTION "
-              "failedNodesIds size = %zu ",
+              "ThinClientRegion::executeFunction with "
+              "GF_INTERNAL_FUNCTION_INVOCATION_TARGET_EXCEPTION failedNodesIds "
+              "size = %zu ",
               failedNodesIds->size());
           failedNodes->insert(failedNodesIds->begin(), failedNodesIds->end());
         }
@@ -2975,7 +2983,7 @@
     if (ThinClientBaseDM::isFatalClientError(err)) {
       throwExceptionIfError("ExecuteOnRegion:", err);
     } else if (err != GF_NOERR) {
-      if (err == GF_FUNCTION_EXCEPTION) {
+      if (err == GF_INTERNAL_FUNCTION_INVOCATION_TARGET_EXCEPTION) {
         reExecute = true;
         rc->clearResults();
         std::shared_ptr<CacheableHashSet> failedNodesIds(reply.getFailedNode());
@@ -2983,8 +2991,8 @@
         if (failedNodesIds) {
           LOGDEBUG(
               "ThinClientRegion::reExecuteFunction with "
-              "GF_FUNCTION_EXCEPTION "
-              "failedNodesIds size = %zu ",
+              "GF_INTERNAL_FUNCTION_INVOCATION_TARGET_EXCEPTION failedNodesIds "
+              "size = %zu ",
               failedNodesIds->size());
           failedNodes->insert(failedNodesIds->begin(), failedNodesIds->end());
         }
@@ -3053,7 +3061,7 @@
     }
 
     if (err != GF_NOERR) {
-      if (err == GF_FUNCTION_EXCEPTION) {
+      if (err == GF_INTERNAL_FUNCTION_INVOCATION_TARGET_EXCEPTION) {
         if (auto poolDM =
                 std::dynamic_pointer_cast<ThinClientPoolDM>(m_tcrdm)) {
           if (poolDM->getClientMetaDataService()) {
@@ -3077,7 +3085,7 @@
           if (failedNodeIds) {
             LOGDEBUG(
                 "ThinClientRegion::executeFunctionSH with "
-                "GF_FUNCTION_EXCEPTION "
+                "GF_INTERNAL_FUNCTION_INVOCATION_TARGET_EXCEPTION "
                 "failedNodeIds size = %zu ",
                 failedNodeIds->size());
             failedNodes->insert(failedNodeIds->begin(), failedNodeIds->end());
@@ -3153,7 +3161,7 @@
     }
     case TcrMessage::REQUEST_DATA_ERROR: {
       LOGERROR("Error message from server: " + reply.getValue()->toString());
-      throw FunctionExecutionException(reply.getValue()->toString());
+      throw FunctionException(reply.getValue()->toString());
     }
     default: {
       LOGERROR("Unknown message type %d while getting function attributes.",
diff --git a/tests/javaobject/UserExceptionFunction.java b/tests/javaobject/UserExceptionFunction.java
new file mode 100644
index 0000000..59af51e
--- /dev/null
+++ b/tests/javaobject/UserExceptionFunction.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javaobject;
+
+import java.io.Serializable;
+
+import org.apache.geode.cache.Declarable;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.execute.FunctionAdapter;
+import org.apache.geode.cache.execute.FunctionContext;
+import org.apache.geode.cache.execute.FunctionException;
+import org.apache.geode.cache.execute.RegionFunctionContext;
+
+import java.util.Properties;
+
+/**
+ * Function that returns the value of the key passed in through the filter,
+ * using the default result collector.
+ */
+public class UserExceptionFunction extends FunctionAdapter implements Declarable {
+
+  private static final String ID = "GetKeyFunction";
+
+  public void execute(FunctionContext context) {
+    throw new FunctionException("This is an user expected exception");
+  }
+
+  public String getId() {
+    return ID;
+  }
+
+  public boolean hasResult() {
+    return true;
+  }
+
+  public void init(Properties p) {
+  }
+  public boolean isHA() {
+    return false;
+  }
+
+}
+