Repository: open-feature/java-sdk (java)

Issue:
RejectedExecutionException thrown when calling setProvider after shutdown
While writing a unit test I found a bug when calling `OpenFeatureAPI.getInstance().shutdown();` and calling back `setProvider()` we have an exception thrown (`java.util.concurrent.RejectedExecutionException`).

I don't know if this is expected, but this exception is not super explicit anyway.

## How to reproduce

```java
public class ProviderTests2 {
    @Test
    void testXXX() {
        NoOpProvider p = new NoOpProvider();
        OpenFeatureAPI.getInstance().setProvider(p);
        var client = OpenFeatureAPI.getInstance().getClient();
        OpenFeatureAPI.getInstance().shutdown();

        NoOpProvider p2 = new NoOpProvider();
        OpenFeatureAPI.getInstance().setProvider(p2);
    }
}
```

The second `setProvider` is throwing this exception:

```
/Users/thomas.poignant/Library/Java/JavaVirtualMachines/corretto-11.0.18/Contents/Home/bin/java -ea -Didea.test.cyclic.buffer.size=1048576 -javaagent:/Users/thomas.poignant/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/231.9011.34/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=57732:/Users/thomas.poignant/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/231.9011.34/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/thomas.poignant/.m2/repository/org/junit/platform/junit-platform-launcher/1.10.0/junit-platform-launcher-1.10.0.jar:/Users/thomas.poignant/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/231.9011.34/IntelliJ IDEA.app/Contents/lib/idea_rt.jar:/Users/thomas.poignant/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/231.9011.34/IntelliJ IDEA.app/Contents/plugins/junit/lib/junit5-rt.jar:/Users/thomas.poignant/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/231.9011.34/IntelliJ IDEA.app/Contents/plugins/junit/lib/junit-rt.jar:/Users/thomas.poignant/dev/thomaspoignant/go-feature-flag/openfeature/provider_tests/java-integration-tests/target/test-classes:/Users/thomas.poignant/dev/thomaspoignant/go-feature-flag/openfeature/provider_tests/java-integration-tests/target/classes:/Users/thomas.poignant/.m2/repository/org/junit/jupiter/junit-jupiter-engine/5.10.0/junit-jupiter-engine-5.10.0.jar:/Users/thomas.poignant/.m2/repository/org/junit/platform/junit-platform-engine/1.10.0/junit-platform-engine-1.10.0.jar:/Users/thomas.poignant/.m2/repository/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar:/Users/thomas.poignant/.m2/repository/org/junit/platform/junit-platform-commons/1.10.0/junit-platform-commons-1.10.0.jar:/Users/thomas.poignant/.m2/repository/org/junit/jupiter/junit-jupiter-api/5.10.0/junit-jupiter-api-5.10.0.jar:/Users/thomas.poignant/.m2/repository/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar:/Users/thomas.poignant/.m2/repository/dev/openfeature/sdk/1.4.1/sdk-1.4.1.jar:/Users/thomas.poignant/.m2/repository/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7.jar:/Users/thomas.poignant/.m2/repository/dev/openfeature/contrib/providers/go-feature-flag/0.2.11/go-feature-flag-0.2.11.jar:/Users/thomas.poignant/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.15.2/jackson-datatype-jsr310-2.15.2.jar:/Users/thomas.poignant/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.15.2/jackson-annotations-2.15.2.jar:/Users/thomas.poignant/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.15.2/jackson-core-2.15.2.jar:/Users/thomas.poignant/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.15.2/jackson-databind-2.15.2.jar:/Users/thomas.poignant/.m2/repository/com/squareup/okhttp3/okhttp/4.11.0/okhttp-4.11.0.jar:/Users/thomas.poignant/.m2/repository/com/squareup/okio/okio/3.2.0/okio-3.2.0.jar:/Users/thomas.poignant/.m2/repository/com/squareup/okio/okio-jvm/3.2.0/okio-jvm-3.2.0.jar:/Users/thomas.poignant/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib/1.6.20/kotlin-stdlib-1.6.20.jar:/Users/thomas.poignant/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.20/kotlin-stdlib-common-1.6.20.jar:/Users/thomas.poignant/.m2/repository/org/jetbrains/annotations/13.0/annotations-13.0.jar:/Users/thomas.poignant/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.20/kotlin-stdlib-jdk8-1.6.20.jar:/Users/thomas.poignant/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.20/kotlin-stdlib-jdk7-1.6.20.jar:/Users/thomas.poignant/.m2/repository/com/google/guava/guava/32.1.1-jre/guava-32.1.1-jre.jar:/Users/thomas.poignant/.m2/repository/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar:/Users/thomas.poignant/.m2/repository/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:/Users/thomas.poignant/.m2/repository/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar:/Users/thomas.poignant/.m2/repository/org/checkerframework/checker-qual/3.33.0/checker-qual-3.33.0.jar:/Users/thomas.poignant/.m2/repository/com/google/errorprone/error_prone_annotations/2.18.0/error_prone_annotations-2.18.0.jar:/Users/thomas.poignant/.m2/repository/com/google/j2objc/j2objc-annotations/2.8/j2objc-annotations-2.8.jar com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit5 org.gofeatureflag.integrationtests.ProviderTests2
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.

java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@8317c52[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@5f683daf[Wrapped task = dev.openfeature.sdk.ProviderRepository$$Lambda$360/0x000000080016cc40@78ffe6dc]] rejected from java.util.concurrent.ThreadPoolExecutor@325f903d[Shutting down, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 2]

	at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2055)
	at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:825)
	at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1355)
	at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:118)
	at dev.openfeature.sdk.ProviderRepository.initializeProvider(ProviderRepository.java:104)
	at dev.openfeature.sdk.ProviderRepository.setProvider(ProviderRepository.java:69)
	at dev.openfeature.sdk.OpenFeatureAPI.setProvider(OpenFeatureAPI.java:100)
	at org.gofeatureflag.integrationtests.ProviderTests2.testXXX(ProviderTests2.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)


Process finished with exit code 255
```

Pull request:
fix: ability to set provider after shutdown
## This PR

Fix #552 

Since API is a singleton, we must re-initialize all cleaned-up resources. 

Patch:
diff --git a/src/main/java/dev/openfeature/sdk/EventSupport.java b/src/main/java/dev/openfeature/sdk/EventSupport.java
index 3a55f29..7620c3f 100644
--- a/src/main/java/dev/openfeature/sdk/EventSupport.java
+++ b/src/main/java/dev/openfeature/sdk/EventSupport.java
@@ -145,7 +145,11 @@ class EventSupport {
      * Stop the event handler task executor.
      */
     public void shutdown() {
-        taskExecutor.shutdown();
+        try {
+            taskExecutor.shutdown();
+        } catch (Exception e) {
+            log.warn("Exception while attempting to shutdown task executor", e);
+        }
     }
 
     // Handler store maintains a set of handlers for each event type.
diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java
index 42ff470..94047b8 100644
--- a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java
+++ b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java
@@ -21,13 +21,15 @@ import lombok.extern.slf4j.Slf4j;
 public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
     // package-private multi-read/single-write lock
     static AutoCloseableReentrantReadWriteLock lock = new AutoCloseableReentrantReadWriteLock();
-    private EvaluationContext evaluationContext;
     private final List<Hook> apiHooks;
-    private ProviderRepository providerRepository = new ProviderRepository();
-    private EventSupport eventSupport = new EventSupport();
+    private ProviderRepository providerRepository;
+    private EventSupport eventSupport;
+    private EvaluationContext evaluationContext;
 
     protected OpenFeatureAPI() {
         apiHooks = new ArrayList<>();
+        providerRepository = new ProviderRepository();
+        eventSupport = new EventSupport();
     }
 
     private static class SingletonHolder {
@@ -190,9 +192,19 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
         }
     }
 
+    /**
+     * Shut down and reset the current status of OpenFeature API.
+     * This call cleans up all active providers and attempts to shut down internal event handling mechanisms.
+     * Once shut down is complete, API is reset and ready to use again.
+     * */
     public void shutdown() {
-        providerRepository.shutdown();
-        eventSupport.shutdown();
+        try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
+            providerRepository.shutdown();
+            eventSupport.shutdown();
+
+            providerRepository = new ProviderRepository();
+            eventSupport = new EventSupport();
+        }
     }
 
     /**
@@ -264,15 +276,6 @@ public class OpenFeatureAPI implements EventBus<OpenFeatureAPI> {
         }
     }
 
-    /**
-     * This method is only here for testing as otherwise all tests after the API
-     * shutdown test would fail.
-     */
-    final void reset() {
-        providerRepository = new ProviderRepository();
-        eventSupport = new EventSupport();
-    }
-
     /**
      * Runs the handlers associated with a particular provider.
      * 


Test patch:
diff --git a/src/test/java/dev/openfeature/sdk/ShutdownBehaviorSpecTest.java b/src/test/java/dev/openfeature/sdk/ShutdownBehaviorSpecTest.java
index e470819..8659ce7 100644
--- a/src/test/java/dev/openfeature/sdk/ShutdownBehaviorSpecTest.java
+++ b/src/test/java/dev/openfeature/sdk/ShutdownBehaviorSpecTest.java
@@ -109,9 +109,22 @@ class ShutdownBehaviorSpecTest {
                             verify(defaultProvider).shutdown();
                             verify(namedProvider).shutdown();
                         });
-
-                api.reset();
             }
         }
+
+
+        @Test
+        @DisplayName("once shutdown is complete, api must be ready to use again")
+        void apiIsReadyToUseAfterShutdown() {
+            final OpenFeatureAPI openFeatureAPI = OpenFeatureAPI.getInstance();
+
+            NoOpProvider p1 = new NoOpProvider();
+            openFeatureAPI.setProvider(p1);
+
+            openFeatureAPI.shutdown();
+
+            NoOpProvider p2 = new NoOpProvider();
+            openFeatureAPI.setProvider(p2);
+        }
     }
 }