This is an automated email from the ASF dual-hosted git repository. rmannibucau pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/johnzon.git
The following commit(s) were added to refs/heads/master by this push: new 7001b97 [JOHNZON-329] make jsonlogic completionstage friendly 7001b97 is described below commit 7001b971b72a7352f2b27f1b9a445e6fe533dd47 Author: Romain Manni-Bucau <rmannibu...@gmail.com> AuthorDate: Thu Dec 17 10:23:03 2020 +0100 [JOHNZON-329] make jsonlogic completionstage friendly --- .../org/apache/johnzon/jsonb/CdiAdapterTest.java | 24 ++++-- .../johnzon/jsonb/JohnzonConverterInJsonbTest.java | 4 +- .../apache/johnzon/jsonlogic/JohnzonJsonLogic.java | 41 +++++++++- .../spi/{Operator.java => AsyncOperator.java} | 23 +++++- .../org/apache/johnzon/jsonlogic/spi/Operator.java | 14 ++++ .../johnzon/jsonlogic/JohnzonJsonLogicTest.java | 90 ++++++++++++++++++++++ pom.xml | 15 ++-- 7 files changed, 191 insertions(+), 20 deletions(-) diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/CdiAdapterTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/CdiAdapterTest.java index 79685bb..d071541 100644 --- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/CdiAdapterTest.java +++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/CdiAdapterTest.java @@ -20,10 +20,14 @@ package org.apache.johnzon.jsonb; import org.apache.webbeans.config.WebBeansContext; import org.apache.webbeans.config.WebBeansFinder; -import org.apache.webbeans.lifecycle.test.OpenWebBeansTestLifeCycle; -import org.apache.webbeans.lifecycle.test.OpenWebBeansTestMetaDataDiscoveryService; +import org.apache.webbeans.corespi.se.DefaultScannerService; +import org.apache.webbeans.lifecycle.StandaloneLifeCycle; import org.apache.webbeans.proxy.OwbNormalScopeProxy; +import org.apache.webbeans.spi.ContainerLifecycle; +import org.apache.webbeans.spi.ScannerService; import org.apache.webbeans.util.WebBeansUtil; +import org.apache.xbean.finder.AnnotationFinder; +import org.apache.xbean.finder.archive.ClassesArchive; import org.junit.Test; import javax.enterprise.context.ApplicationScoped; @@ -32,8 +36,9 @@ import javax.json.bind.Jsonb; import javax.json.bind.JsonbBuilder; import javax.json.bind.adapter.JsonbAdapter; import javax.json.bind.annotation.JsonbTypeAdapter; +import java.util.Properties; -import static java.util.Arrays.asList; +import static java.util.Collections.singletonMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -42,10 +47,14 @@ public class CdiAdapterTest { @Test public void run() { WebBeansFinder.clearInstances(WebBeansUtil.getCurrentClassLoader()); - final OpenWebBeansTestLifeCycle testLifecycle = new OpenWebBeansTestLifeCycle(); - final WebBeansContext ctx = WebBeansContext.currentInstance(); - final OpenWebBeansTestMetaDataDiscoveryService discoveryService = OpenWebBeansTestMetaDataDiscoveryService.class.cast(ctx.getScannerService()); - discoveryService.deployClasses(asList(Service.class, ModelAdapter.class)); + final ContainerLifecycle testLifecycle = new StandaloneLifeCycle(); + new WebBeansContext(singletonMap( + ScannerService.class, new DefaultScannerService() { + @Override + protected AnnotationFinder initFinder() { + return new AnnotationFinder(new ClassesArchive(Service.class, ModelAdapter.class)); + } + }), new Properties()); testLifecycle.startApplication(null); try { Jsonb jsonb = JsonbBuilder.create(); @@ -57,6 +66,7 @@ public class CdiAdapterTest { } } finally { testLifecycle.stopApplication(null); + WebBeansFinder.clearInstances(WebBeansUtil.getCurrentClassLoader()); } } diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JohnzonConverterInJsonbTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JohnzonConverterInJsonbTest.java index c72cac5..4ce2e13 100644 --- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JohnzonConverterInJsonbTest.java +++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JohnzonConverterInJsonbTest.java @@ -53,7 +53,7 @@ public class JohnzonConverterInJsonbTest { assertNotNull(json); TestDTO deserialized = jsonb.fromJson(json, TestDTO.class); - assertEquals(dto.instant, deserialized.instant); + assertEquals(dto.instant.toEpochMilli(), deserialized.instant.toEpochMilli()); } @Test @@ -67,7 +67,7 @@ public class JohnzonConverterInJsonbTest { assertNotNull(json); TestDTOWithOC deserialized = jsonb.fromJson(json, TestDTOWithOC.class); - assertEquals(deserialized.dto.instant, dto.dto.instant); + assertEquals(deserialized.dto.instant.toEpochMilli(), dto.dto.instant.toEpochMilli()); } public static class TestDTOWithOC { diff --git a/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/JohnzonJsonLogic.java b/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/JohnzonJsonLogic.java index 691146c..9691c82 100644 --- a/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/JohnzonJsonLogic.java +++ b/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/JohnzonJsonLogic.java @@ -35,12 +35,15 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.function.BiPredicate; import java.util.stream.Collector; import java.util.stream.DoubleStream; import java.util.stream.Stream; import static java.util.Collections.emptyMap; +import static java.util.concurrent.CompletableFuture.completedFuture; import static java.util.stream.Collectors.joining; public class JohnzonJsonLogic { @@ -82,16 +85,42 @@ public class JohnzonJsonLogic { final Set<String> keys = object.keySet(); if (keys.size() != 1) { - throw new IllegalArgumentException("Invalid argument, multiple keys found: " + keys); + throw invalidArgument(keys); } final String operator = keys.iterator().next(); final Operator impl = operators.get(operator); if (impl == null) { - throw new IllegalArgumentException("Missing operator '" + operator + "'"); + throw missingOperator(operator); } return impl.apply(this, object.get(operator), args); } + public CompletionStage<JsonValue> applyStage(final JsonValue logic, final JsonValue args) { + if (logic.getValueType() != JsonValue.ValueType.OBJECT) { + return completedFuture(logic); + } + + final JsonObject object = logic.asJsonObject(); + if (object.size() > 1) { + return completedFuture(object); + } + + final Set<String> keys = object.keySet(); + if (keys.size() != 1) { + final CompletableFuture<JsonValue> promise = new CompletableFuture<>(); + promise.completeExceptionally(invalidArgument(keys)); + return promise; + } + final String operator = keys.iterator().next(); + final Operator impl = operators.get(operator); + if (impl == null) { + final CompletableFuture<JsonValue> promise = new CompletableFuture<>(); + promise.completeExceptionally(missingOperator(operator)); + return promise; + } + return impl.applyStage(this, object.get(operator), args); + } + public boolean isTruthy(final JsonValue value) { return !isFalsy(value); } @@ -206,6 +235,14 @@ public class JohnzonJsonLogic { return this; } + private IllegalArgumentException invalidArgument(final Set<String> keys) { + return new IllegalArgumentException("Invalid argument, multiple keys found: " + keys); + } + + private IllegalArgumentException missingOperator(final String operator) { + return new IllegalArgumentException("Missing operator '" + operator + "'"); + } + private JsonValue minImpl(final JohnzonJsonLogic logic, final JsonValue config, final JsonValue params) { if (config.getValueType() != JsonValue.ValueType.ARRAY) { throw new IllegalArgumentException("min only supports arrays: '" + config + "'"); diff --git a/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/spi/Operator.java b/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/spi/AsyncOperator.java similarity index 52% copy from johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/spi/Operator.java copy to johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/spi/AsyncOperator.java index 8531e0b..42e2d22 100644 --- a/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/spi/Operator.java +++ b/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/spi/AsyncOperator.java @@ -21,8 +21,27 @@ package org.apache.johnzon.jsonlogic.spi; import org.apache.johnzon.jsonlogic.JohnzonJsonLogic; import javax.json.JsonValue; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; @FunctionalInterface -public interface Operator { - JsonValue apply(JohnzonJsonLogic logic, JsonValue config, JsonValue params); +public interface AsyncOperator extends Operator { + @Override + CompletionStage<JsonValue> applyStage(JohnzonJsonLogic logic, JsonValue config, JsonValue params); + + @Override + default JsonValue apply(JohnzonJsonLogic logic, JsonValue config, JsonValue params) { + try { + return applyStage(logic, config, params).toCompletableFuture().get(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + return null; + } catch (ExecutionException e) { + final Throwable cause = e.getCause(); + if (RuntimeException.class.isInstance(cause)) { + throw RuntimeException.class.cast(cause); + } + throw new IllegalStateException(cause); + } + } } diff --git a/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/spi/Operator.java b/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/spi/Operator.java index 8531e0b..a66c16f 100644 --- a/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/spi/Operator.java +++ b/johnzon-jsonlogic/src/main/java/org/apache/johnzon/jsonlogic/spi/Operator.java @@ -21,8 +21,22 @@ package org.apache.johnzon.jsonlogic.spi; import org.apache.johnzon.jsonlogic.JohnzonJsonLogic; import javax.json.JsonValue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import static java.util.concurrent.CompletableFuture.completedFuture; @FunctionalInterface public interface Operator { + default CompletionStage<JsonValue> applyStage(JohnzonJsonLogic logic, JsonValue config, JsonValue params) { + try { + return completedFuture(apply(logic, config, params)); + } catch (final RuntimeException re) { + final CompletableFuture<JsonValue> promise = new CompletableFuture<>(); + promise.completeExceptionally(re); + return promise; + } + } + JsonValue apply(JohnzonJsonLogic logic, JsonValue config, JsonValue params); } diff --git a/johnzon-jsonlogic/src/test/java/org/apache/johnzon/jsonlogic/JohnzonJsonLogicTest.java b/johnzon-jsonlogic/src/test/java/org/apache/johnzon/jsonlogic/JohnzonJsonLogicTest.java index 49295b3..f126cfd 100644 --- a/johnzon-jsonlogic/src/test/java/org/apache/johnzon/jsonlogic/JohnzonJsonLogicTest.java +++ b/johnzon-jsonlogic/src/test/java/org/apache/johnzon/jsonlogic/JohnzonJsonLogicTest.java @@ -18,12 +18,18 @@ */ package org.apache.johnzon.jsonlogic; +import org.apache.johnzon.jsonlogic.spi.AsyncOperator; +import org.apache.johnzon.jsonlogic.spi.Operator; import org.junit.Test; import javax.json.Json; import javax.json.JsonBuilderFactory; import javax.json.JsonObject; import javax.json.JsonValue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; import static java.util.Collections.emptyMap; import static org.junit.Assert.assertEquals; @@ -33,6 +39,90 @@ public class JohnzonJsonLogicTest { private final JsonBuilderFactory builderFactory = Json.createBuilderFactory(emptyMap()); @Test + public void stage() throws InterruptedException { + // if the async exec is too immediate we will execute the thenAccept callback in main thread + // which is not the goal of this test so let's ensure we are in the expected case + final CountDownLatch waitChainReady = new CountDownLatch(1); + + final JohnzonJsonLogic jsonLogic = new JohnzonJsonLogic() + .registerOperator("async", new Operator() { + @Override + public CompletionStage<JsonValue> applyStage(final JohnzonJsonLogic logic, + final JsonValue config, + final JsonValue params) { + return logic.applyStage( + builderFactory.createObjectBuilder().add("async2", "ok").build(), + builderFactory.createObjectBuilder().add("p2", "1").build()) + .thenApplyAsync( + previous -> builderFactory.createObjectBuilder() + .add("thread", Thread.currentThread().getName()) + .add("config", config) + .add("params", params) + .add("exec2", previous) + .build(), + r -> new Thread(r, "async").start()); + } + + @Override + public JsonValue apply(final JohnzonJsonLogic logic, final JsonValue config, final JsonValue params) { + throw new UnsupportedOperationException(); + } + }) + .registerOperator("async2", new AsyncOperator() { + @Override + public CompletionStage<JsonValue> applyStage(final JohnzonJsonLogic logic, + final JsonValue config, + final JsonValue params) { + return CompletableFuture.supplyAsync(() -> { + try { + waitChainReady.await(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + return builderFactory.createObjectBuilder() + .add("thread2", Thread.currentThread().getName()) + .add("config2", config) + .add("params2", params) + .build(); + }, r -> new Thread(r, "async2").start()); + } + + @Override + public JsonValue apply(final JohnzonJsonLogic logic, final JsonValue config, final JsonValue params) { + throw new UnsupportedOperationException(); + } + }); + + final CountDownLatch latch = new CountDownLatch(1); // if we use Future.get we break stage threading + final AtomicReference<JsonValue> output = new AtomicReference<>(); + jsonLogic + .applyStage( + builderFactory.createObjectBuilder() + .add("async", "a") + .build(), + builderFactory.createObjectBuilder() + .add("p1", "0") + .build()) + .thenAccept(result -> { + output.set(builderFactory.createObjectBuilder() + .add("exec1", result) + .add("thenSyncThread", Thread.currentThread().getName()) + .build()); + latch.countDown(); + }); + waitChainReady.countDown(); + latch.await(); + assertEquals("" + + "{" + + "\"exec1\":{\"thread\":\"async\",\"config\":\"a\",\"params\":{\"p1\":\"0\"}," + + "\"exec2\":{\"thread2\":\"async2\",\"config2\":\"ok\",\"params2\":{\"p2\":\"1\"}}}," + + "\"thenSyncThread\":\"async\"" + + "}" + + "", + output.get().toString()); + } + + @Test public void varObjectString() { assertEquals(Json.createValue("b"), jsonLogic.apply( builderFactory.createObjectBuilder() diff --git a/pom.xml b/pom.xml index 38d33fa..6a44f98 100644 --- a/pom.xml +++ b/pom.xml @@ -45,12 +45,11 @@ <felix.plugin.version>4.0.0</felix.plugin.version> <bnd.version.policy>[$(version;==;$(@)),$(version;+;$(@)))</bnd.version.policy> <java-compile.version>1.8</java-compile.version> - <cxf.version>3.0.0</cxf.version> - <javadoc.params /> <!-- for java 8 set disable doclint (by profile) --> + <cxf.version>3.4.1</cxf.version> <checkstyle.version>2.15</checkstyle.version> <!-- checkstyle > 2.15 version do not support java 6 --> <!-- JVM values for surefire plugin --> <surefire.jvm.params>-Xms1024m -Xmx2048m -Dfile.encoding=UTF-8</surefire.jvm.params> - <owb.version>1.7.5</owb.version> + <owb.version>2.0.20</owb.version> </properties> <modules> @@ -345,7 +344,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>2.10.3</version> + <version>3.2.0</version> <executions> <execution> <id>attach-javadocs</id> @@ -354,7 +353,11 @@ </goals> <configuration> <show>private</show> - <additionalparam>${javadoc.params}</additionalparam> <!-- maven plugin generated a HelpMojo with malformed javadoc --> + <detectJavaApiLink>false</detectJavaApiLink> + <detectLinks>false</detectLinks> + <detectOfflineLinks>false</detectOfflineLinks> + <doclint>none</doclint> + <source>8</source> </configuration> </execution> </executions> @@ -508,7 +511,6 @@ <configuration> <notimestamp>true</notimestamp> <show>private</show> - <additionalparam>${javadoc.params}</additionalparam> <!-- maven plugin generated a HelpMojo with malformed javadoc --> </configuration> <reportSets> <reportSet> @@ -762,7 +764,6 @@ </activation> <properties> <checkstyle.version>2.17</checkstyle.version> - <javadoc.params>-Xdoclint:none</javadoc.params> </properties> </profile> </profiles>