METRON-1853: Add shutdown hook to Stellar BaseFunctionResolver (mmiklavc via 
mmiklavc) closes apache/metron#1251


Project: http://git-wip-us.apache.org/repos/asf/metron/repo
Commit: http://git-wip-us.apache.org/repos/asf/metron/commit/85cd21aa
Tree: http://git-wip-us.apache.org/repos/asf/metron/tree/85cd21aa
Diff: http://git-wip-us.apache.org/repos/asf/metron/diff/85cd21aa

Branch: refs/heads/feature/METRON-1090-stellar-assignment
Commit: 85cd21aa0f5045184c168248dc2b81c1cfd41ddd
Parents: b9461e7
Author: mmiklavc <michael.miklav...@gmail.com>
Authored: Tue Nov 6 18:09:56 2018 -0700
Committer: Michael Miklavcic <michael.miklav...@gmail.com>
Committed: Tue Nov 6 18:09:56 2018 -0700

----------------------------------------------------------------------
 .../ElasticsearchSearchIntegrationTest.java     |   1 -
 .../metron/stellar/dsl/StellarFunction.java     |   9 +-
 .../metron/stellar/dsl/StellarFunctions.java    |   5 +
 .../resolver/BaseFunctionResolver.java          |  44 +++++
 .../functions/resolver/FunctionResolver.java    |  14 +-
 .../stellar/dsl/functions/BasicStellarTest.java |  20 ++-
 .../resolver/BaseFunctionResolverTest.java      | 169 +++++++++++++++++++
 7 files changed, 251 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metron/blob/85cd21aa/metron-platform/metron-elasticsearch/src/test/java/org/apache/metron/elasticsearch/integration/ElasticsearchSearchIntegrationTest.java
----------------------------------------------------------------------
diff --git 
a/metron-platform/metron-elasticsearch/src/test/java/org/apache/metron/elasticsearch/integration/ElasticsearchSearchIntegrationTest.java
 
b/metron-platform/metron-elasticsearch/src/test/java/org/apache/metron/elasticsearch/integration/ElasticsearchSearchIntegrationTest.java
index 1d2d48e..8187468 100644
--- 
a/metron-platform/metron-elasticsearch/src/test/java/org/apache/metron/elasticsearch/integration/ElasticsearchSearchIntegrationTest.java
+++ 
b/metron-platform/metron-elasticsearch/src/test/java/org/apache/metron/elasticsearch/integration/ElasticsearchSearchIntegrationTest.java
@@ -25,7 +25,6 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import org.adrianwalker.multilinestring.Multiline;
 import org.apache.metron.common.Constants;
 import org.apache.metron.common.utils.JSONUtils;
 import org.apache.metron.elasticsearch.dao.ElasticsearchDao;

http://git-wip-us.apache.org/repos/asf/metron/blob/85cd21aa/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/StellarFunction.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/StellarFunction.java
 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/StellarFunction.java
index efdd185..4fabfaf 100644
--- 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/StellarFunction.java
+++ 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/StellarFunction.java
@@ -17,10 +17,17 @@
  */
 package org.apache.metron.stellar.dsl;
 
+import java.io.Closeable;
+import java.io.IOException;
 import java.util.List;
 
-public interface StellarFunction {
+public interface StellarFunction extends Closeable {
   Object apply(List<Object> args, Context context) throws ParseException;
   void initialize(Context context);
   boolean isInitialized();
+
+  @Override
+  default void close() throws IOException {
+
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/85cd21aa/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/StellarFunctions.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/StellarFunctions.java
 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/StellarFunctions.java
index dfec90e..73df82f 100644
--- 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/StellarFunctions.java
+++ 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/StellarFunctions.java
@@ -18,6 +18,7 @@
 
 package org.apache.metron.stellar.dsl;
 
+import java.io.IOException;
 import org.apache.metron.stellar.dsl.functions.resolver.FunctionResolver;
 import 
org.apache.metron.stellar.dsl.functions.resolver.SingletonFunctionResolver;
 
@@ -30,4 +31,8 @@ public class StellarFunctions {
   public static void initialize(Context context) {
     SingletonFunctionResolver.getInstance().initialize(context);
   }
+
+  public static void close() throws IOException {
+    SingletonFunctionResolver.getInstance().close();
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/85cd21aa/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/resolver/BaseFunctionResolver.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/resolver/BaseFunctionResolver.java
 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/resolver/BaseFunctionResolver.java
index aeed9d9..38a32d1 100644
--- 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/resolver/BaseFunctionResolver.java
+++ 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/resolver/BaseFunctionResolver.java
@@ -23,6 +23,7 @@ import static java.lang.String.format;
 import com.google.common.base.Joiner;
 import com.google.common.base.Supplier;
 import com.google.common.base.Suppliers;
+import java.io.IOException;
 import java.io.Serializable;
 import java.lang.invoke.MethodHandles;
 import java.util.Arrays;
@@ -58,9 +59,15 @@ public abstract class BaseFunctionResolver implements 
FunctionResolver, Serializ
    */
   protected Context context;
 
+  /**
+   * Indicates if closed has been called on this resolver.
+   */
+  private boolean closed;
+
   public BaseFunctionResolver() {
     // memoize provides lazy initialization and thread-safety (the ugly cast 
is necessary for serialization)
     functions = Suppliers.memoize((Supplier<Map<String, StellarFunctionInfo>> 
& Serializable) this::resolveFunctions);
+    closed = false;
   }
 
   /**
@@ -95,6 +102,43 @@ public abstract class BaseFunctionResolver implements 
FunctionResolver, Serializ
   }
 
   /**
+   * Makes an attempt to close all Stellar functions. Calling close multiple 
times has no effect.
+   * @throws IOException Catches all exceptions and summarizes them.
+   */
+  @Override
+  public void close() throws IOException {
+    if (!closed) {
+      LOG.info("Calling close() on Stellar functions.");
+      Map<String, Throwable> errors = new HashMap<>();
+      for (StellarFunctionInfo info : getFunctionInfo()) {
+        try {
+          info.getFunction().close();
+        } catch (Throwable t) {
+          errors.put(info.getName(), t);
+        }
+      }
+      if (!errors.isEmpty()) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Unable to close Stellar functions:");
+        for (Map.Entry<String, Throwable> e : errors.entrySet()) {
+          Throwable throwable = e.getValue();
+          String eText = String
+              .format("Exception - Function: %s; Message: %s; Cause: %s", 
e.getKey(),
+                  throwable.getMessage(),
+                  throwable.getCause());
+          sb.append(System.lineSeparator());
+          sb.append(eText);
+        }
+        closed = true;
+        throw new IOException(sb.toString());
+      }
+      closed = true;
+    } else {
+      LOG.info("close() already called on Stellar functions - skipping.");
+    }
+  }
+
+  /**
    * Resolves a function by name.
    * @param functionName The name of the function to resolve.
    * @return The executable StellarFunction.

http://git-wip-us.apache.org/repos/asf/metron/blob/85cd21aa/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/resolver/FunctionResolver.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/resolver/FunctionResolver.java
 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/resolver/FunctionResolver.java
index 5acb42c..4047586 100644
--- 
a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/resolver/FunctionResolver.java
+++ 
b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/resolver/FunctionResolver.java
@@ -17,16 +17,17 @@
  */
 package org.apache.metron.stellar.dsl.functions.resolver;
 
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.function.Function;
 import org.apache.metron.stellar.dsl.Context;
 import org.apache.metron.stellar.dsl.StellarFunction;
 import org.apache.metron.stellar.dsl.StellarFunctionInfo;
 
-import java.util.function.Function;
-
 /**
  * Responsible for function resolution in Stellar.
  */
-public interface FunctionResolver extends Function<String, StellarFunction> {
+public interface FunctionResolver extends Function<String, StellarFunction>, 
Closeable {
 
   /**
    * Provides metadata about each Stellar function that is resolvable.
@@ -43,4 +44,11 @@ public interface FunctionResolver extends Function<String, 
StellarFunction> {
    * @param context Context used to initialize.
    */
   void initialize(Context context);
+
+  /**
+   * Perform any cleanup necessary for the loaded Stellar functions.
+   */
+  @Override
+  default void close() throws IOException {}
+
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/85cd21aa/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/BasicStellarTest.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/BasicStellarTest.java
 
b/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/BasicStellarTest.java
index dec05a8..79f97bc 100644
--- 
a/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/BasicStellarTest.java
+++ 
b/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/BasicStellarTest.java
@@ -18,9 +18,20 @@
 
 package org.apache.metron.stellar.dsl.functions;
 
+import static org.apache.metron.stellar.common.utils.StellarProcessorUtils.run;
+import static 
org.apache.metron.stellar.common.utils.StellarProcessorUtils.runPredicate;
+import static 
org.apache.metron.stellar.common.utils.StellarProcessorUtils.validate;
+
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.metron.stellar.common.StellarProcessor;
 import org.apache.metron.stellar.dsl.Context;
@@ -37,12 +48,6 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
-import java.util.*;
-
-import static org.apache.metron.stellar.common.utils.StellarProcessorUtils.run;
-import static 
org.apache.metron.stellar.common.utils.StellarProcessorUtils.validate;
-import static 
org.apache.metron.stellar.common.utils.StellarProcessorUtils.runPredicate;
-
 @SuppressWarnings("ALL")
 public class BasicStellarTest {
 
@@ -70,6 +75,7 @@ public class BasicStellarTest {
     public boolean isInitialized() {
       return true;
     }
+
   }
 
   @Stellar(
@@ -96,6 +102,7 @@ public class BasicStellarTest {
     public boolean isInitialized() {
       return true;
     }
+
   }
 
   @Test
@@ -1000,4 +1007,5 @@ public class BasicStellarTest {
     checkFalsey("{}");
     checkFalsey("LIST_ADD([])");
   }
+
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/85cd21aa/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/resolver/BaseFunctionResolverTest.java
----------------------------------------------------------------------
diff --git 
a/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/resolver/BaseFunctionResolverTest.java
 
b/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/resolver/BaseFunctionResolverTest.java
new file mode 100644
index 0000000..47cbda3
--- /dev/null
+++ 
b/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/resolver/BaseFunctionResolverTest.java
@@ -0,0 +1,169 @@
+/**
+ * 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 org.apache.metron.stellar.dsl.functions.resolver;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.metron.stellar.dsl.BaseStellarFunction;
+import org.apache.metron.stellar.dsl.Stellar;
+import org.apache.metron.stellar.dsl.StellarFunction;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class BaseFunctionResolverTest {
+
+  public static class TestResolver extends BaseFunctionResolver {
+
+    Set<Class<? extends StellarFunction>> classesToResolve = new HashSet<>();
+
+    @Override
+    public Set<Class<? extends StellarFunction>> resolvables() {
+      return classesToResolve;
+    }
+
+    /**
+     * Will attempt to resolve any Stellar functions defined within the 
specified class.
+     * @param clazz The class which may contain a Stellar function.
+     */
+    public TestResolver withClass(Class<? extends StellarFunction> clazz) {
+      this.classesToResolve.add(clazz);
+      return this;
+    }
+  }
+
+  /**
+   * Often imitated, never duplicated.
+   */
+  @Stellar(namespace = "namespace", name = "afunction", description = 
"description", returns = "returns", params = {
+      "param1"})
+  private static class IAmAFunction extends BaseStellarFunction {
+
+    public static int closeCallCount;
+    public static boolean throwException = false; // init here bc of 
reflection in resolver.
+
+    public IAmAFunction() {
+      closeCallCount = 0;
+    }
+
+    @Override
+    public Object apply(List<Object> args) {
+      return null;
+    }
+
+    @Override
+    public void close() throws IOException {
+      closeCallCount++;
+      if (throwException) {
+        Throwable cause = new Throwable("Some nasty nasty cause.");
+        throw new IOException("Bad things happened", cause);
+      }
+    }
+  }
+
+  /**
+   * Scratch that. I was wrong.
+   */
+  @Stellar(namespace = "namespace", name = "anotherfunction", description = 
"description", returns = "returns", params = {
+      "param1"})
+  private static class IAmAnotherFunction extends BaseStellarFunction {
+
+    public static int closeCallCount;
+    public static boolean throwException = false; // init here bc of 
reflection in resolver.
+
+    public IAmAnotherFunction() {
+      closeCallCount = 0;
+    }
+
+    @Override
+    public Object apply(List<Object> args) {
+      return null;
+    }
+
+    @Override
+    public void close() throws IOException {
+      closeCallCount++;
+      if (throwException) {
+        throw new NullPointerException("A most annoying exception.");
+      }
+    }
+  }
+
+  private TestResolver resolver;
+
+  @Before
+  public void setup() {
+    resolver = new TestResolver();
+    IAmAFunction.throwException = false;
+    IAmAnotherFunction.throwException = false;
+  }
+
+  @Test
+  public void close_calls_all_loaded_function_close_methods() throws 
IOException {
+    resolver.withClass(IAmAFunction.class);
+    resolver.withClass(IAmAnotherFunction.class);
+    resolver.close();
+    assertThat(IAmAFunction.closeCallCount, equalTo(1));
+    assertThat(IAmAnotherFunction.closeCallCount, equalTo(1));
+  }
+
+  @Rule
+  public final ExpectedException exception = ExpectedException.none();
+
+  @Test
+  public void 
close_collects_all_exceptions_thrown_on_loaded_function_close_methods()
+      throws IOException {
+    IAmAFunction.throwException = true;
+    IAmAnotherFunction.throwException = true;
+    resolver.withClass(IAmAFunction.class);
+    resolver.withClass(IAmAnotherFunction.class);
+    exception.expect(IOException.class);
+    resolver.close();
+  }
+
+  @Test
+  public void close_only_throws_exceptions_on_first_invocation()
+      throws IOException {
+    IAmAFunction.throwException = true;
+    IAmAnotherFunction.throwException = true;
+    resolver.withClass(IAmAFunction.class);
+    resolver.withClass(IAmAnotherFunction.class);
+    try {
+      resolver.close();
+      Assert.fail("Should have thrown an exception.");
+    } catch (IOException e) {
+      // intentionally empty
+    }
+    assertThat(IAmAFunction.closeCallCount, equalTo(1));
+    assertThat(IAmAnotherFunction.closeCallCount, equalTo(1));
+    // should not throw exceptions or call any function's close again.
+    resolver.close();
+    resolver.close();
+    resolver.close();
+    assertThat(IAmAFunction.closeCallCount, equalTo(1));
+    assertThat(IAmAnotherFunction.closeCallCount, equalTo(1));
+  }
+}

Reply via email to