diff --git a/bom/pom.xml b/bom/pom.xml index 7940cf898ed..5ef324ae42d 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -441,6 +441,11 @@ helidon-microprofile-access-log ${helidon.version} + + io.helidon.microprofile + helidon-microprofile-reactive-streams + ${helidon.version} + io.helidon.metrics diff --git a/common/reactive/etc/spotbugs/exclude.xml b/common/reactive/etc/spotbugs/exclude.xml deleted file mode 100644 index 201268dc797..00000000000 --- a/common/reactive/etc/spotbugs/exclude.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/common/reactive/pom.xml b/common/reactive/pom.xml index 8b3d103b2e9..cb8a670692c 100644 --- a/common/reactive/pom.xml +++ b/common/reactive/pom.xml @@ -1,7 +1,7 @@ + * | | passed in publisher + * | +--------------------------+ + * | | | + * +-------+ + * | Outlet/downstream subscriber + * v + * + * + * @param Inlet and passed in subscriber item type + * @param Outlet and passed in publisher item type + */ +public class MultiCoupledProcessor implements Flow.Processor, Multi { + + private SubscriberReference passedInSubscriber; + private SubscriberReference outletSubscriber; + private Flow.Publisher passedInPublisher; + private Flow.Subscriber inletSubscriber; + private Flow.Subscription inletSubscription; + private Flow.Subscription passedInPublisherSubscription; + private AtomicBoolean cancelled = new AtomicBoolean(false); + + private MultiCoupledProcessor(Flow.Subscriber passedInSubscriber, Flow.Publisher passedInPublisher) { + this.passedInSubscriber = SubscriberReference.create(passedInSubscriber); + this.passedInPublisher = passedInPublisher; + this.inletSubscriber = this; + } + + /** + * Create new {@link MultiCoupledProcessor}. + * + * @param passedInSubscriber to send items from inlet to + * @param passedInPublisher to get items for outlet from + * @param Inlet and passed in subscriber item type + * @param Outlet and passed in publisher item type + * @return {@link MultiCoupledProcessor} + */ + public static MultiCoupledProcessor create(Flow.Subscriber passedInSubscriber, + Flow.Publisher passedInPublisher) { + return new MultiCoupledProcessor<>(passedInSubscriber, passedInPublisher); + } + + @Override + public void subscribe(Flow.Subscriber outletSubscriber) { + this.outletSubscriber = SubscriberReference.create(outletSubscriber); + passedInPublisher.subscribe(new Flow.Subscriber() { + + @Override + public void onSubscribe(Flow.Subscription passedInPublisherSubscription) { + //Passed in publisher called onSubscribed + Objects.requireNonNull(passedInPublisherSubscription); + // https://github.com/reactive-streams/reactive-streams-jvm#2.5 + if (Objects.nonNull(MultiCoupledProcessor.this.passedInPublisherSubscription) || cancelled.get()) { + passedInPublisherSubscription.cancel(); + return; + } + MultiCoupledProcessor.this.passedInPublisherSubscription = passedInPublisherSubscription; + } + + @Override + @SuppressWarnings("unchecked") + public void onNext(R t) { + //Passed in publisher sent onNext + Objects.requireNonNull(t); + outletSubscriber.onNext(t); + } + + @Override + public void onError(Throwable t) { + //Passed in publisher sent onError + cancelled.set(true); + Objects.requireNonNull(t); + outletSubscriber.onError(t); + passedInSubscriber.onError(t); + inletSubscriber.onError(t); + //203 https://github.com/eclipse/microprofile-reactive-streams-operators/issues/131 + Optional.ofNullable(inletSubscription).ifPresent(Flow.Subscription::cancel); + } + + @Override + public void onComplete() { + //Passed in publisher completed + cancelled.set(true); + outletSubscriber.onComplete(); + passedInSubscriber.onComplete(); + //203 https://github.com/eclipse/microprofile-reactive-streams-operators/issues/131 + Optional.ofNullable(inletSubscription).ifPresent(Flow.Subscription::cancel); + } + }); + + outletSubscriber.onSubscribe(new Flow.Subscription() { + + @Override + public void request(long n) { + // Request from outlet subscriber + StreamValidationUtils.checkRecursionDepth(2, (actDepth, t) -> outletSubscriber.onError(t)); + passedInPublisherSubscription.request(n); + } + + @Override + public void cancel() { + // Cancel from outlet subscriber + passedInSubscriber.onComplete(); + Optional.ofNullable(inletSubscription).ifPresent(Flow.Subscription::cancel); + passedInPublisherSubscription.cancel(); + MultiCoupledProcessor.this.passedInSubscriber.releaseReference(); + MultiCoupledProcessor.this.outletSubscriber.releaseReference(); + } + }); + } + + @Override + public void onSubscribe(Flow.Subscription inletSubscription) { + Objects.requireNonNull(inletSubscription); + // https://github.com/reactive-streams/reactive-streams-jvm#2.5 + if (Objects.nonNull(this.inletSubscription) || cancelled.get()) { + inletSubscription.cancel(); + return; + } + this.inletSubscription = inletSubscription; + passedInSubscriber.onSubscribe(new Flow.Subscription() { + @Override + public void request(long n) { + StreamValidationUtils.checkRecursionDepth(5, (actDepth, t) -> passedInSubscriber.onError(t)); + inletSubscription.request(n); + } + + @Override + public void cancel() { + // Cancel from passed in subscriber + if (cancelled.getAndSet(true)) { + return; + } + inletSubscription.cancel(); + outletSubscriber.onComplete(); + passedInPublisherSubscription.cancel(); + passedInSubscriber.releaseReference(); + outletSubscriber.releaseReference(); + } + }); + } + + @Override + public void onNext(T t) { + // Inlet/upstream publisher sent onNext + passedInSubscriber.onNext(Objects.requireNonNull(t)); + } + + @Override + public void onError(Throwable t) { + // Inlet/upstream publisher sent error + cancelled.set(true); + passedInSubscriber.onError(Objects.requireNonNull(t)); + outletSubscriber.onError(t); + passedInPublisherSubscription.cancel(); + } + + @Override + public void onComplete() { + // Inlet/upstream publisher completed + cancelled.set(true); + passedInSubscriber.onComplete(); + outletSubscriber.onComplete(); + passedInPublisherSubscription.cancel(); + } + +} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/MultiDistinctProcessor.java b/common/reactive/src/main/java/io/helidon/common/reactive/MultiDistinctProcessor.java new file mode 100644 index 00000000000..220fdc0ee29 --- /dev/null +++ b/common/reactive/src/main/java/io/helidon/common/reactive/MultiDistinctProcessor.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common.reactive; + +import java.util.HashSet; +import java.util.concurrent.Flow; + +/** + * Filter out all duplicate items. + * + * @param item type + */ +public class MultiDistinctProcessor extends BufferedProcessor implements Multi { + + private final HashSet distinctSet = new HashSet(); + + private MultiDistinctProcessor() { + } + + /** + * Create new {@link MultiDistinctProcessor}. + * + * @param item type + * @return {@link MultiDistinctProcessor} + */ + public static MultiDistinctProcessor create() { + return new MultiDistinctProcessor<>(); + } + + @Override + protected void hookOnCancel(Flow.Subscription subscription) { + subscription.cancel(); + } + + @Override + protected void hookOnNext(T item) { + if (!distinctSet.contains(item)) { + distinctSet.add(item); + submit(item); + } else { + tryRequest(getSubscription().get()); + } + } +} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/MultiDropWhileProcessor.java b/common/reactive/src/main/java/io/helidon/common/reactive/MultiDropWhileProcessor.java new file mode 100644 index 00000000000..cbd80ddea3e --- /dev/null +++ b/common/reactive/src/main/java/io/helidon/common/reactive/MultiDropWhileProcessor.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common.reactive; + +import java.util.function.Predicate; + +/** + * Drop the longest prefix of elements from this stream that satisfy the given predicate. + * + * @param Item type + */ +public class MultiDropWhileProcessor extends BufferedProcessor implements Multi { + private Predicate predicate; + + private boolean foundNotMatching = false; + + private MultiDropWhileProcessor(Predicate predicate) { + this.predicate = predicate; + } + + /** + * Drop the longest prefix of elements from this stream that satisfy the given predicate. + * + * @param Item type + * @param predicate provided predicate to filter stream with + * @return {@link MultiDropWhileProcessor} + */ + public static MultiDropWhileProcessor create(Predicate predicate) { + return new MultiDropWhileProcessor<>(predicate); + } + + @Override + protected void hookOnNext(T item) { + if (foundNotMatching || !predicate.test(item)) { + foundNotMatching = true; + submit(item); + } else { + tryRequest(getSubscription().get()); + } + + } +} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/MultiEmpty.java b/common/reactive/src/main/java/io/helidon/common/reactive/MultiEmpty.java index c23ea4b1868..02678ad83ee 100644 --- a/common/reactive/src/main/java/io/helidon/common/reactive/MultiEmpty.java +++ b/common/reactive/src/main/java/io/helidon/common/reactive/MultiEmpty.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/MultiError.java b/common/reactive/src/main/java/io/helidon/common/reactive/MultiError.java index b9b8427776c..d4ad542adf4 100644 --- a/common/reactive/src/main/java/io/helidon/common/reactive/MultiError.java +++ b/common/reactive/src/main/java/io/helidon/common/reactive/MultiError.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.helidon.common.reactive; import java.util.Objects; +import java.util.concurrent.Flow; import java.util.concurrent.Flow.Publisher; import java.util.concurrent.Flow.Subscriber; @@ -29,13 +30,27 @@ final class MultiError implements Multi { private final Throwable error; - MultiError(Throwable error) { + private MultiError(Throwable error) { this.error = Objects.requireNonNull(error, "error"); } + static MultiError create(Throwable error) { + return new MultiError(error); + } + @Override public void subscribe(Subscriber subscriber) { - subscriber.onSubscribe(EmptySubscription.INSTANCE); + subscriber.onSubscribe(new Flow.Subscription() { + @Override + public void request(long n) { + subscriber.onError(error); + } + + @Override + public void cancel() { + + } + }); subscriber.onError(error); } } diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/MultiFilterProcessor.java b/common/reactive/src/main/java/io/helidon/common/reactive/MultiFilterProcessor.java new file mode 100644 index 00000000000..f417fea8f06 --- /dev/null +++ b/common/reactive/src/main/java/io/helidon/common/reactive/MultiFilterProcessor.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common.reactive; + +import java.util.function.Predicate; + +/** + * Processor filtering stream with supplied predicate. + * + * @param both input/output type + */ +public class MultiFilterProcessor extends BufferedProcessor implements Multi { + + private Predicate predicate; + + private MultiFilterProcessor(Predicate predicate) { + this.predicate = predicate; + } + + /** + * Processor filtering stream with supplied predicate. + * + * @param predicate provided predicate to filter stream with + * @param both input/output type + * @return {@link MultiFilterProcessor} + */ + public static MultiFilterProcessor create(Predicate predicate) { + return new MultiFilterProcessor<>(predicate); + } + + @Override + protected void hookOnNext(T item) { + if (predicate.test(item)) { + submit(item); + } else { + tryRequest(getSubscription().get()); + } + } +} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/MultiFirstProcessor.java b/common/reactive/src/main/java/io/helidon/common/reactive/MultiFirstProcessor.java index ae2a6185b5d..9ab96113fc4 100644 --- a/common/reactive/src/main/java/io/helidon/common/reactive/MultiFirstProcessor.java +++ b/common/reactive/src/main/java/io/helidon/common/reactive/MultiFirstProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,31 @@ */ package io.helidon.common.reactive; +import java.util.concurrent.atomic.AtomicBoolean; + /** * Processor of {@code Multi} to {@code Single}. + * * @param item type */ final class MultiFirstProcessor extends BaseProcessor implements Single { + private AtomicBoolean nextCalled = new AtomicBoolean(false); + + private MultiFirstProcessor() { + } + + static MultiFirstProcessor create() { + return new MultiFirstProcessor<>(); + } + + @Override + public void onNext(T item) { + if (!nextCalled.getAndSet(true)) { + super.onNext(item); + } + } + @Override protected void hookOnNext(T item) { submit(item); diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/MultiFlatMapProcessor.java b/common/reactive/src/main/java/io/helidon/common/reactive/MultiFlatMapProcessor.java new file mode 100644 index 00000000000..bbefe5dd857 --- /dev/null +++ b/common/reactive/src/main/java/io/helidon/common/reactive/MultiFlatMapProcessor.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common.reactive; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Flow; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; + +/** + * Flatten the elements emitted by publishers produced by the mapper function to this stream. + * + * @param item type + */ +public class MultiFlatMapProcessor implements Flow.Processor, Multi { + + private static final int DEFAULT_BUFFER_SIZE = 64; + + private Function> mapper; + private SubscriberReference subscriber; + private Flow.Subscription subscription; + private RequestedCounter requestCounter = new RequestedCounter(); + private Flow.Subscription innerSubscription; + private AtomicBoolean onCompleteReceivedAlready = new AtomicBoolean(false); + private PublisherBuffer buffer = new PublisherBuffer<>(); + private Optional error = Optional.empty(); + + private MultiFlatMapProcessor() { + } + + /** + * Create new {@link MultiFlatMapProcessor} with item to {@link java.lang.Iterable} mapper. + * + * @param mapper to provide iterable for every item from upstream + * @param item type + * @return {@link MultiFlatMapProcessor} + */ + @SuppressWarnings("unchecked") + public static MultiFlatMapProcessor fromIterableMapper(Function> mapper) { + MultiFlatMapProcessor flatMapProcessor = new MultiFlatMapProcessor<>(); + flatMapProcessor.mapper = o -> (Multi) Multi.from(mapper.apply(o)); + return flatMapProcessor; + } + + /** + * Create new {@link MultiFlatMapProcessor} with item to {@link java.util.concurrent.Flow.Publisher} mapper. + * + * @param mapper to provide iterable for every item from upstream + * @param item type + * @return {@link MultiFlatMapProcessor} + */ + @SuppressWarnings("unchecked") + public static MultiFlatMapProcessor fromPublisherMapper(Function> mapper) { + Function> publisherMapper = (Function>) mapper; + MultiFlatMapProcessor flatMapProcessor = new MultiFlatMapProcessor(); + flatMapProcessor.mapper = t -> (Flow.Publisher) publisherMapper.apply(t); + return flatMapProcessor; + } + + private class FlatMapSubscription implements Flow.Subscription { + @Override + public void request(long n) { + if (buffer.isComplete() || Objects.isNull(innerSubscription)) { + subscription.request(n); + } else { + requestCounter.increment(n, MultiFlatMapProcessor.this::onError); + innerSubscription.request(n); + } + } + + @Override + public void cancel() { + subscription.cancel(); + Optional.ofNullable(innerSubscription).ifPresent(Flow.Subscription::cancel); + // https://github.com/reactive-streams/reactive-streams-jvm#3.13 + subscriber.releaseReference(); + } + } + + @Override + public void subscribe(Flow.Subscriber subscriber) { + this.subscriber = SubscriberReference.create(subscriber); + if (Objects.nonNull(this.subscription)) { + subscriber.onSubscribe(new FlatMapSubscription()); + } + error.ifPresent(subscriber::onError); + } + + @Override + public void onSubscribe(Flow.Subscription subscription) { + if (Objects.nonNull(this.subscription)) { + subscription.cancel(); + return; + } + this.subscription = subscription; + if (Objects.nonNull(subscriber)) { + subscriber.onSubscribe(new FlatMapSubscription()); + } + } + + @Override + public void onNext(T o) { + Objects.requireNonNull(o); + try { + buffer.offer(o); + } catch (Throwable t) { + onError(t); + } + } + + @Override + public void onError(Throwable t) { + this.error = Optional.of(t); + if (Objects.nonNull(subscriber)) { + subscriber.onError(t); + } + } + + @Override + public void onComplete() { + onCompleteReceivedAlready.set(true); + if (buffer.isComplete()) { + //Have to wait for all Publishers to be finished + subscriber.onComplete(); + } + } + + private class PublisherBuffer { + + private int bufferSize = Integer.parseInt( + System.getProperty("helidon.common.reactive.flatMap.buffer.size", String.valueOf(DEFAULT_BUFFER_SIZE))); + private BlockingQueue buffer = new ArrayBlockingQueue<>(bufferSize); + private InnerSubscriber lastSubscriber = null; + + public boolean isComplete() { + return Objects.isNull(lastSubscriber) || (lastSubscriber.isDone() && buffer.isEmpty()); + } + + public void tryNext() { + U nextItem = buffer.poll(); + if (Objects.nonNull(nextItem)) { + lastSubscriber = executeMapper(nextItem); + } else if (onCompleteReceivedAlready.get()) { + // Received onComplete and all Publishers are done + subscriber.onComplete(); + } + } + + public void offer(U o) { + if (buffer.isEmpty() && (Objects.isNull(lastSubscriber) || lastSubscriber.isDone())) { + lastSubscriber = executeMapper(o); + } else { + buffer.add(o); + } + } + + @SuppressWarnings("unchecked") + public InnerSubscriber executeMapper(U item) { + InnerSubscriber innerSubscriber = null; + try { + innerSubscriber = new InnerSubscriber<>(); + innerSubscriber.whenComplete(this::tryNext); + mapper.apply((T) item).subscribe(innerSubscriber); + } catch (Throwable t) { + subscription.cancel(); + subscriber.onError(t); + } + return innerSubscriber; + } + } + + private class InnerSubscriber implements Flow.Subscriber { + + private AtomicBoolean subscriptionAcked = new AtomicBoolean(false); + private AtomicBoolean done = new AtomicBoolean(false); + + private Optional whenCompleteObserver = Optional.empty(); + + @Override + public void onSubscribe(Flow.Subscription innerSubscription) { + Objects.requireNonNull(innerSubscription); + if (subscriptionAcked.get()) { + innerSubscription.cancel(); + return; + } + subscriptionAcked.set(true); + MultiFlatMapProcessor.this.innerSubscription = innerSubscription; + innerSubscription.request(Long.MAX_VALUE); + } + + @Override + @SuppressWarnings("unchecked") + public void onNext(R o) { + Objects.requireNonNull(o); + MultiFlatMapProcessor.this.subscriber.onNext((T) o); + //just counting leftovers + requestCounter.tryDecrement(); + } + + @Override + public void onError(Throwable t) { + Objects.requireNonNull(t); + MultiFlatMapProcessor.this.subscription.cancel(); + MultiFlatMapProcessor.this.onError(t); + } + + @Override + public void onComplete() { + done.set(true); + whenCompleteObserver.ifPresent(Runnable::run); + long requestCount = requestCounter.get(); + if (requestCount > 0) { + subscription.request(requestCount); + } + } + + private void whenComplete(Runnable whenCompleteObserver) { + this.whenCompleteObserver = Optional.of(whenCompleteObserver); + } + + private boolean isDone() { + return done.get(); + } + } +} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/MultiFromPublisher.java b/common/reactive/src/main/java/io/helidon/common/reactive/MultiFromPublisher.java index 9b6494120d2..7491b4b4f91 100644 --- a/common/reactive/src/main/java/io/helidon/common/reactive/MultiFromPublisher.java +++ b/common/reactive/src/main/java/io/helidon/common/reactive/MultiFromPublisher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/MultiLimitProcessor.java b/common/reactive/src/main/java/io/helidon/common/reactive/MultiLimitProcessor.java new file mode 100644 index 00000000000..22b0053d105 --- /dev/null +++ b/common/reactive/src/main/java/io/helidon/common/reactive/MultiLimitProcessor.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common.reactive; + +import java.util.concurrent.Flow; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Let pass only specified number of items. + * + * @param both input/output type + */ +public class MultiLimitProcessor extends BufferedProcessor implements Multi { + + private final AtomicLong counter; + + private MultiLimitProcessor(Long limit) { + counter = new AtomicLong(limit); + } + + /** + * Processor with specified number of allowed items. + * + * @param limit number of items to pass + * @param both input/output type + * @return {@link MultiLimitProcessor} + */ + public static MultiLimitProcessor create(Long limit) { + return new MultiLimitProcessor<>(limit); + } + + @Override + public void subscribe(Flow.Subscriber s) { + super.subscribe(s); + if (counter.get() == 0L) { + tryComplete(); + } + } + + @Override + public void onError(Throwable ex) { + if (0 < this.counter.get()) { + super.onError(ex); + } else { + tryComplete(); + } + } + + @Override + protected void hookOnNext(T item) { + long actCounter = this.counter.getAndDecrement(); + if (0 < actCounter) { + submit(item); + } else { + getSubscription().ifPresent(Flow.Subscription::cancel); + tryComplete(); + } + } + + @Override + public String toString() { + return "LimitProcessor{" + "counter=" + counter + '}'; + } +} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/MultiMappingProcessor.java b/common/reactive/src/main/java/io/helidon/common/reactive/MultiMapProcessor.java similarity index 55% rename from common/reactive/src/main/java/io/helidon/common/reactive/MultiMappingProcessor.java rename to common/reactive/src/main/java/io/helidon/common/reactive/MultiMapProcessor.java index 2fed3725f9d..5cf1b9a96ac 100644 --- a/common/reactive/src/main/java/io/helidon/common/reactive/MultiMappingProcessor.java +++ b/common/reactive/src/main/java/io/helidon/common/reactive/MultiMapProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.helidon.common.reactive; import java.util.Objects; +import java.util.concurrent.Flow; import java.util.concurrent.Flow.Publisher; import io.helidon.common.mapper.Mapper; @@ -26,19 +27,37 @@ * @param subscribed type * @param published type */ -final class MultiMappingProcessor extends BaseProcessor implements Multi { +public final class MultiMapProcessor extends BufferedProcessor implements Multi { private final Mapper mapper; - MultiMappingProcessor(Mapper mapper) { + private MultiMapProcessor(Mapper mapper) { this.mapper = Objects.requireNonNull(mapper, "mapper is null!"); } + /** + * Processor of {@link Publisher} to {@link Single} that publishes and maps each received item. + * + * @param mapper supplied for all items to be mapped with + * @param subscribed type + * @param published type + * @return {@link MultiMapProcessor} + */ + public static MultiMapProcessor create(Mapper mapper) { + return new MultiMapProcessor(mapper); + } + + @Override + protected void hookOnCancel(Flow.Subscription subscription) { + subscription.cancel(); + } + @Override protected void hookOnNext(T item) { U value = mapper.map(item); if (value == null) { - onError(new IllegalStateException("Mapper returned a null value")); + getSubscription().ifPresent(Flow.Subscription::cancel); + onError(new NullPointerException("Mapper returned a null value")); } else { submit(value); } diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/MultiOnErrorResumeProcessor.java b/common/reactive/src/main/java/io/helidon/common/reactive/MultiOnErrorResumeProcessor.java new file mode 100644 index 00000000000..62c5049764f --- /dev/null +++ b/common/reactive/src/main/java/io/helidon/common/reactive/MultiOnErrorResumeProcessor.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common.reactive; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.Flow; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +/** + * Resume stream from supplied publisher if onError signal is intercepted. + * + * @param item type + */ +public class MultiOnErrorResumeProcessor extends BufferedProcessor implements Multi { + + private Function supplier; + private Function> publisherSupplier; + private AtomicReference> onErrorPublisherSubscription = new AtomicReference<>(Optional.empty()); + + private MultiOnErrorResumeProcessor() { + } + + /** + * Create new {@link MultiOnErrorResumeProcessor} with supplier for item to submit after error is intercepted. + * + * @param supplier for item to submit after error is intercepted + * @param item type + * @return new {@link MultiOnErrorResumeProcessor} + */ + @SuppressWarnings("unchecked") + public static MultiOnErrorResumeProcessor resume(Function supplier) { + MultiOnErrorResumeProcessor processor = new MultiOnErrorResumeProcessor<>(); + processor.supplier = (Function) supplier; + return processor; + } + + /** + * Create new {@link MultiOnErrorResumeProcessor} with supplier for {@link java.util.concurrent.Flow.Publisher} + * to resume stream after error is intercepted. + * + * @param supplier or {@link java.util.concurrent.Flow.Publisher} + * to resume stream after error is intercepted + * @param item type + * @return new {@link MultiOnErrorResumeProcessor} + */ + public static MultiOnErrorResumeProcessor resumeWith(Function> supplier) { + MultiOnErrorResumeProcessor processor = new MultiOnErrorResumeProcessor<>(); + processor.publisherSupplier = supplier; + return processor; + } + + @Override + protected void tryRequest(Flow.Subscription subscription) { + super.tryRequest(onErrorPublisherSubscription.get() + .orElse(subscription)); + } + + @Override + protected void hookOnNext(T item) { + super.submit(item); + } + + @Override + public void onError(Throwable ex) { + Objects.requireNonNull(ex); + try { + if (Objects.nonNull(supplier)) { + + submit(supplier.apply(ex)); + tryComplete(); + + } else { + publisherSupplier.apply(ex).subscribe(new Flow.Subscriber() { + + @Override + public void onSubscribe(Flow.Subscription subscription) { + Objects.requireNonNull(subscription); + onErrorPublisherSubscription.set(Optional.of(subscription)); + if (getRequestedCounter().get() > 0) { + subscription.request(getRequestedCounter().get()); + } + } + + @Override + public void onNext(T t) { + submit(t); + } + + @Override + public void onError(Throwable t) { + Objects.requireNonNull(t); + fail(t); + } + + @Override + public void onComplete() { + MultiOnErrorResumeProcessor.this.onComplete(); + onErrorPublisherSubscription.set(Optional.empty()); + } + }); + } + } catch (Throwable t) { + onErrorPublisherSubscription.get().ifPresent(Flow.Subscription::cancel); + fail(t); + } + } + + @Override + protected void hookOnCancel(Flow.Subscription subscription) { + subscription.cancel(); + onErrorPublisherSubscription.get().ifPresent(Flow.Subscription::cancel); + } +} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/MultiPeekProcessor.java b/common/reactive/src/main/java/io/helidon/common/reactive/MultiPeekProcessor.java new file mode 100644 index 00000000000..0169b935bd5 --- /dev/null +++ b/common/reactive/src/main/java/io/helidon/common/reactive/MultiPeekProcessor.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common.reactive; + +import java.util.function.Consumer; + +/** + * Invoke supplied consumer for every item in the stream. + * + * @param both input/output type + */ +public class MultiPeekProcessor extends BufferedProcessor implements Multi { + + private Consumer consumer; + + private MultiPeekProcessor(Consumer consumer) { + this.consumer = consumer; + } + + /** + * Invoke supplied consumer for every item in the stream. + * + * @param consumer supplied consumer to be invoke for every item + * @param both input/output type + * @return {@link MultiPeekProcessor} + */ + public static MultiPeekProcessor create(Consumer consumer) { + return new MultiPeekProcessor<>(consumer); + } + + @Override + protected void hookOnNext(T item) { + consumer.accept(item); + submit(item); + } + + @Override + public String toString() { + return "PeekProcessor{" + "consumer=" + consumer + '}'; + } +} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/MultiSkipProcessor.java b/common/reactive/src/main/java/io/helidon/common/reactive/MultiSkipProcessor.java new file mode 100644 index 00000000000..ac49095f55b --- /dev/null +++ b/common/reactive/src/main/java/io/helidon/common/reactive/MultiSkipProcessor.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common.reactive; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * Skip first n items, all the others are emitted. + * + * @param item type + */ +public class MultiSkipProcessor extends BufferedProcessor implements Multi { + + private final AtomicLong counter; + + private MultiSkipProcessor(Long skip) { + counter = new AtomicLong(skip); + } + + /** + * Create new {@link MultiSkipProcessor}. + * + * @param skip number of items to be skipped + * @param item type + * @return {@link MultiSkipProcessor} + */ + public static MultiSkipProcessor create(Long skip) { + return new MultiSkipProcessor(skip); + } + + @Override + protected void hookOnNext(T item) { + long actCounter = this.counter.getAndDecrement(); + if (0 >= actCounter) { + submit(item); + } else { + getRequestedCounter().tryDecrement(); + request(1); + } + } +} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/MultiTakeWhileProcessor.java b/common/reactive/src/main/java/io/helidon/common/reactive/MultiTakeWhileProcessor.java new file mode 100644 index 00000000000..51525bc6aca --- /dev/null +++ b/common/reactive/src/main/java/io/helidon/common/reactive/MultiTakeWhileProcessor.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common.reactive; + +import java.util.concurrent.Flow; +import java.util.function.Predicate; + +/** + * Take the longest prefix of elements from this stream that satisfy the given predicate. + * + * @param Item type + */ +public class MultiTakeWhileProcessor extends BufferedProcessor implements Multi { + private Predicate predicate; + + private MultiTakeWhileProcessor(Predicate predicate) { + this.predicate = predicate; + } + + /** + * Create new {@link MultiTakeWhileProcessor}. + * + * @param predicate provided predicate to filter stream with + * @param Item type + * @return {@link MultiTakeWhileProcessor} + */ + public static MultiTakeWhileProcessor create(Predicate predicate) { + return new MultiTakeWhileProcessor<>(predicate); + } + + @Override + protected void hookOnNext(T item) { + if (predicate.test(item)) { + submit(item); + } else { + getSubscription().ifPresent(Flow.Subscription::cancel); + tryComplete(); + } + } +} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/MultiTappedProcessor.java b/common/reactive/src/main/java/io/helidon/common/reactive/MultiTappedProcessor.java new file mode 100644 index 00000000000..b149974540a --- /dev/null +++ b/common/reactive/src/main/java/io/helidon/common/reactive/MultiTappedProcessor.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common.reactive; + +import java.util.Optional; +import java.util.concurrent.Flow; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Processor executing provided functions on passing signals onNext, onError, onComplete, onCancel. + * + * @param Type of the processed items. + */ +public class MultiTappedProcessor extends BufferedProcessor implements Multi { + + private Optional> onNextFunction = Optional.empty(); + private Optional> onErrorConsumer = Optional.empty(); + private Optional onCompleteRunnable = Optional.empty(); + private Optional> onCancelConsumer = Optional.empty(); + + private MultiTappedProcessor() { + } + + /** + * Create new processor with no functions to execute when signals are intercepted. + * + * @param Type of the processed items. + * @return Brand new {@link MultiTappedProcessor} + */ + public static MultiTappedProcessor create() { + return new MultiTappedProcessor<>(); + } + + /** + * Set {@link java.util.function.Function} to be executed when onNext signal is intercepted. + * + * @param function Function to be invoked. + * @return This {@link MultiTappedProcessor} + */ + public MultiTappedProcessor onNext(Function function) { + onNextFunction = Optional.ofNullable(function); + return this; + } + + /** + * Set {@link java.util.function.Consumer} to be executed when onError signal is intercepted. + * + * @param consumer Consumer to be executed when onError signal is intercepted, + * argument is intercepted {@link java.lang.Throwable}. + * @return This {@link MultiTappedProcessor} + */ + public MultiTappedProcessor onError(Consumer consumer) { + onErrorConsumer = Optional.ofNullable(consumer); + return this; + } + + /** + * Set {@link java.lang.Runnable} to be executed when onComplete signal is intercepted. + * + * @param runnable {@link java.lang.Runnable} to be executed. + * @return This {@link MultiTappedProcessor} + */ + public MultiTappedProcessor onComplete(Runnable runnable) { + onCompleteRunnable = Optional.ofNullable(runnable); + return this; + } + + /** + * Set consumer to be executed when onCancel signal is intercepted. + * + * @param consumer Consumer to be executed when onCancel signal is intercepted, + * argument is intercepted {@link java.util.concurrent.Flow.Subscription}. + * @return This {@link MultiTappedProcessor} + */ + public MultiTappedProcessor onCancel(Consumer consumer) { + onCancelConsumer = Optional.ofNullable(consumer); + return this; + } + + @Override + protected void hookOnNext(R item) { + submit(onNextFunction.map(f -> f.apply(item)).orElse(item)); + } + + @Override + protected void hookOnError(Throwable error) { + onErrorConsumer.ifPresent(c -> c.accept(error)); + super.hookOnError(error); + } + + @Override + protected void hookOnComplete() { + onCompleteRunnable.ifPresent(Runnable::run); + super.hookOnComplete(); + } + + @Override + protected void hookOnCancel(Flow.Subscription subscription) { + onCancelConsumer.ifPresent(c -> c.accept(subscription)); + subscription.cancel(); + } +} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/RequestedCounter.java b/common/reactive/src/main/java/io/helidon/common/reactive/RequestedCounter.java index 6d602e8052a..d0fa534f007 100644 --- a/common/reactive/src/main/java/io/helidon/common/reactive/RequestedCounter.java +++ b/common/reactive/src/main/java/io/helidon/common/reactive/RequestedCounter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,8 +37,7 @@ public class RequestedCounter { * process errors */ public void increment(long increment, Consumer errorHandler) { - if (increment <= 0) { - errorHandler.accept(new IllegalArgumentException("Unsupported requested event increment: " + increment)); + if (!StreamValidationUtils.checkRequestParam(increment, errorHandler)) { return; } diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/StreamValidationUtils.java b/common/reactive/src/main/java/io/helidon/common/reactive/StreamValidationUtils.java new file mode 100644 index 00000000000..a2491165634 --- /dev/null +++ b/common/reactive/src/main/java/io/helidon/common/reactive/StreamValidationUtils.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common.reactive; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * Helper methods for stream validation. + */ +public class StreamValidationUtils { + + private StreamValidationUtils() { + } + + /** + * Validation of Reactive Streams Specification for JVM rule 3.3. + *
+ * {@code Subscription.request} MUST place an upper bound on possible synchronous + * recursion between {@code Publisher} and {@code Subscriber}. + * + * @param maxDepth maximal expected recursion depth + * @param onExceeded called if recursion is deeper than maxDepth, + * provided with actual depth and spec compliant exception. + * @param payload type of the subscriber + * @return true if valid + * @see reactive-streams/reactive-streams-jvm#3.3 + */ + public static boolean checkRecursionDepth(int maxDepth, BiConsumer onExceeded) { + Long recursionDepth = getRecursionDepth(); + if (recursionDepth > maxDepth) { + Optional.of(onExceeded) + .ifPresent(onExc -> onExc + .accept(recursionDepth, new IllegalCallerException(String + .format("Recursion depth exceeded, max depth expected %d but actual is %d, rule 3.3", + maxDepth, recursionDepth)))); + return false; + } + return true; + } + + /** + * Validation of Reactive Streams Specification for JVM rule 3.9. + *
+ * While the {@code Subscription} is not cancelled, {@code Subscription.request(long n)} + * MUST signal onError with a {@link java.lang.IllegalArgumentException} if the argument is <= 0. + * The cause message SHOULD explain that non-positive request signals are illegal. + * + * @param requestParam number of requested items to be validated. + * @param onExceeded called if request param invalid provided with spec compliant exception. + * @return true if requested parameter is valid + * @see reactive-streams/reactive-streams-jvm#3.9 + */ + public static boolean checkRequestParam(long requestParam, Consumer onExceeded) { + if (requestParam <= 0) { + Optional.of(onExceeded) + .ifPresent(onExc -> onExc + .accept(new IllegalArgumentException(String + .format("Non-positive subscription request %d, rule 3.9", requestParam)))); + return false; + } + return true; + } + + + static Long getRecursionDepth() { + StackTraceElement parentElement = StackWalker.getInstance() + .walk(stackFrameStream -> stackFrameStream.skip(1).findFirst()) + .get() + .toStackTraceElement(); + return StackWalker.getInstance() + .walk(ss -> ss + .map(StackWalker.StackFrame::toStackTraceElement) + .filter(el -> stackTraceElementEquals(el, parentElement)) + .count()); + } + + static boolean stackTraceElementEquals(StackTraceElement a, StackTraceElement b) { + return Objects.equals(a.getClassLoaderName(), b.getClassLoaderName()) + && Objects.equals(a.getModuleName(), b.getModuleName()) + && Objects.equals(a.getModuleVersion(), b.getModuleVersion()) + && Objects.equals(a.getClassName(), b.getClassName()) + && Objects.equals(a.getMethodName(), b.getMethodName()); + + } +} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/Subscribable.java b/common/reactive/src/main/java/io/helidon/common/reactive/Subscribable.java index d0c0d049959..d5741a477d8 100644 --- a/common/reactive/src/main/java/io/helidon/common/reactive/Subscribable.java +++ b/common/reactive/src/main/java/io/helidon/common/reactive/Subscribable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.util.concurrent.Flow; import java.util.concurrent.Flow.Publisher; import java.util.function.Consumer; +import java.util.function.Function; /** * Decorated publisher that allows subscribing to individual events with java functions. @@ -68,4 +69,69 @@ default void subscribe(Consumer consumer, Consumer this.subscribe(new FunctionalSubscriber<>(consumer, errorConsumer, completeConsumer, subscriptionConsumer)); } + + /** + * Executes given {@link java.lang.Runnable} when any of signals onComplete, onCancel or onError is received. + * + * @param onTerminate {@link java.lang.Runnable} to be executed. + * @return Multi + */ + default Multi onTerminate(Runnable onTerminate) { + MultiTappedProcessor processor = MultiTappedProcessor.create() + .onComplete(onTerminate) + .onCancel((s) -> onTerminate.run()) + .onError((t) -> onTerminate.run()); + this.subscribe(processor); + return processor; + } + + /** + * Executes given {@link java.lang.Runnable} when onComplete signal is received. + * + * @param onTerminate {@link java.lang.Runnable} to be executed. + * @return Multi + */ + default Multi onComplete(Runnable onTerminate) { + MultiTappedProcessor processor = MultiTappedProcessor.create() + .onComplete(onTerminate); + this.subscribe(processor); + return processor; + } + + /** + * Executes given {@link java.lang.Runnable} when onError signal is received. + * + * @param onErrorConsumer {@link java.lang.Runnable} to be executed. + * @return Multi + */ + default Multi onError(Consumer onErrorConsumer) { + MultiTappedProcessor processor = MultiTappedProcessor.create() + .onError(onErrorConsumer); + this.subscribe(processor); + return processor; + } + + /** + * {@link java.util.function.Function} providing one item to be submitted as onNext in case of onError signal is received. + * + * @param onError Function receiving {@link java.lang.Throwable} as argument and producing one item to resume stream with. + * @return Multi + */ + default Multi onErrorResume(Function onError) { + MultiOnErrorResumeProcessor processor = MultiOnErrorResumeProcessor.resume(onError); + this.subscribe(processor); + return processor; + } + + /** + * Resume stream from supplied publisher if onError signal is intercepted. + * + * @param onError supplier of new stream publisher + * @return Multi + */ + default Multi onErrorResumeWith(Function> onError) { + MultiOnErrorResumeProcessor processor = MultiOnErrorResumeProcessor.resumeWith(onError); + this.subscribe(processor); + return processor; + } } diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/SubscriberReference.java b/common/reactive/src/main/java/io/helidon/common/reactive/SubscriberReference.java new file mode 100644 index 00000000000..8ca5baf1af8 --- /dev/null +++ b/common/reactive/src/main/java/io/helidon/common/reactive/SubscriberReference.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common.reactive; + +import java.util.Optional; +import java.util.concurrent.Flow; + +class SubscriberReference implements Flow.Subscriber { + private Optional> subscriber; + + private SubscriberReference(Flow.Subscriber subscriber) { + this.subscriber = Optional.of(subscriber); + } + + static SubscriberReference create(Flow.Subscriber subscriber) { + return new SubscriberReference<>(subscriber); + } + + void releaseReference() { + this.subscriber = Optional.empty(); + } + + @Override + public void onSubscribe(Flow.Subscription subscription) { + subscriber.ifPresent(s -> s.onSubscribe(subscription)); + } + + @Override + public void onNext(T item) { + subscriber.ifPresent(s -> s.onNext(item)); + } + + @Override + public void onError(Throwable throwable) { + subscriber.ifPresent(s -> s.onError(throwable)); + } + + @Override + public void onComplete() { + subscriber.ifPresent(Flow.Subscriber::onComplete); + } +} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/CloseableSupport.java b/common/reactive/src/main/java/io/helidon/common/reactive/valve/CloseableSupport.java deleted file mode 100644 index 8ba3d91415f..00000000000 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/CloseableSupport.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -/** - * Can be closed and has information about the state. - *

- * The goal is to provide just enough synchronisation. - */ -class CloseableSupport implements AutoCloseable { - - private boolean closed = false; - private volatile boolean closedVolatile = false; - - @Override - public void close() { - closed = true; - closedVolatile = true; - } - - /** - * Returns {@code true} if it is closed. - * - * @return {@code true} if it is closed - */ - boolean closed() { - return closed || closedVolatile; - } -} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/DetachedValve.java b/common/reactive/src/main/java/io/helidon/common/reactive/valve/DetachedValve.java deleted file mode 100644 index 2ca5f6bd7e8..00000000000 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/DetachedValve.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -class DetachedValve implements Valve { - - private static final int INTERNAL_INDEX = 0; - private static final int EXTERNAL_INDEX = 1; - - private final boolean[] paused = new boolean[] {false, false}; - - private final Lock lock = new ReentrantLock(); - private final Valve delegate; - private final ExecutorService executorService; - - DetachedValve(Valve delegate, ExecutorService executorService) { - this.delegate = delegate; - this.executorService = executorService; - } - - @Override - public void handle(BiConsumer onData, Consumer onError, Runnable onComplete) { - delegate.handle((t, p) -> { - pause(INTERNAL_INDEX); - CompletableFuture.runAsync(() -> onData.accept(t, this), executorService) - .whenComplete((vd, thr) -> { - if (thr == null) { - resume(INTERNAL_INDEX); - } else { - executorService.submit(() -> onError.accept(thr)); - } - }); - }, - t -> executorService.submit(() -> onError.accept(t)), - () -> executorService.submit(onComplete)); - } - - private void pause(int index) { - lock.lock(); - try { - boolean callIt = !paused[0] && !paused[1]; - paused[index] = true; - if (callIt) { - delegate.pause(); - } - } finally { - lock.unlock(); - } - } - - private void resume(int index) { - lock.lock(); - try { - boolean callIt = paused[index] && !paused[index == 0 ? 1 : 0]; - paused[index] = false; - if (callIt) { - delegate.resume(); - } - } finally { - lock.unlock(); - } - } - - @Override - public void pause() { - pause(EXTERNAL_INDEX); - } - - @Override - public void resume() { - resume(EXTERNAL_INDEX); - } -} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/EmptyValve.java b/common/reactive/src/main/java/io/helidon/common/reactive/valve/EmptyValve.java deleted file mode 100644 index 113549c99df..00000000000 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/EmptyValve.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -/** - * Represents a Valve which is empty. - *

- * For the performance sake the Valve accepts unlimited number of handlers. - * Each complete handler is called as soon as registered. - */ -class EmptyValve implements Valve { - - @Override - public void handle(BiConsumer onData, Consumer onError, Runnable onComplete) { - if (onComplete != null) { - onComplete.run(); - } - } - - @Override - public void pause() { - // No-op - } - - @Override - public void resume() { - // No-op - } -} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/InputStreamValve.java b/common/reactive/src/main/java/io/helidon/common/reactive/valve/InputStreamValve.java deleted file mode 100644 index 3802fe4c4bd..00000000000 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/InputStreamValve.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.WritableByteChannel; -import java.nio.charset.Charset; -import java.util.concurrent.ExecutorService; -import java.util.stream.Collector; -import java.util.stream.Collectors; - -/** - * The InputStreamValve is a {@link ByteBuffer} based {@link Valve} that transforms - * a possibly blocking {@link InputStream} into the Valve. - */ -public class InputStreamValve extends RetryingPausableRegistry implements Valve { - private final InputStream stream; - private final int bufferSize; - - InputStreamValve(InputStream stream, int bufferSize) { - this.stream = stream; - this.bufferSize = bufferSize; - } - - @Override - protected ByteBuffer moreData() throws Throwable { - byte[] bytes = new byte[bufferSize]; - - int len = stream.read(bytes); - return len != -1 ? ByteBuffer.wrap(bytes, 0, len) : null; - } - - static class InputStreamExecutorValve extends InputStreamValve { - - private final ExecutorService executorService; - - InputStreamExecutorValve(InputStream stream, int bufferSize, ExecutorService executorService) { - super(stream, bufferSize); - this.executorService = executorService; - } - - @Override - protected void tryProcess() { - executorService.submit(() -> { - super.tryProcess(); - }); - } - } - - /** - * A collector of {@link ByteBuffer} instances into a {@link String} of the provided - * charset. - * - * @param charset the desired charset of the returned string - * @return a string representation of the collected byte buffers - */ - public static Collector byteBufferStringCollector(Charset charset) { - return Collectors.collectingAndThen(byteBufferByteArrayCollector(), bytes -> new String(bytes, charset)); - } - - /** - * A collector of {@link ByteBuffer} instances into a single {@link ByteBuffer} instance. - * - * @return a single byte buffer from the collected byte buffers - */ - public static Collector byteBuffer2Collector() { - return Collectors.collectingAndThen(byteBufferByteArrayCollector(), ByteBuffer::wrap); - } - - /** - * A collector of {@link ByteBuffer} instances into a single byte array. - * - * @return a single byte array from the collected byte buffers - */ - public static Collector byteBufferByteArrayCollector() { - - return Collector.of(ByteArrayOutputStream::new, - (stream, byteBuffer) -> { - try { - synchronized (stream) { - WritableByteChannel channel = Channels.newChannel(stream); - channel.write(byteBuffer); - } - } catch (IOException e) { - // not expected to be thrown because we're operating in memory only - throw new IllegalStateException("This exception is never expected.", e); - } - }, - (stream, stream2) -> { - try { - synchronized (stream) { - stream2.writeTo(stream); - } - return stream; - } catch (IOException e) { - // not expected to be thrown because we're operating in memory only - throw new IllegalStateException("This exception is never expected.", e); - } - }, - ByteArrayOutputStream::toByteArray); - } - -} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/PausableRegistry.java b/common/reactive/src/main/java/io/helidon/common/reactive/valve/PausableRegistry.java deleted file mode 100644 index 3da2cbcf17e..00000000000 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/PausableRegistry.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.Objects; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Supports {@link Valve} implementation by providing single handler registry - * and pause / resume functionality with facility tryProcess reservation. - */ -abstract class PausableRegistry implements Pausable { - - private static final Logger LOGGER = Logger.getLogger(PausableRegistry.class.getName()); - - private final ReentrantLock lock = new ReentrantLock(); - - private volatile BiConsumer onData; - private volatile Consumer onError; - private volatile Runnable onComplete; - - private volatile boolean paused = false; - private boolean processing = false; - - @Override - public void pause() { - paused = true; - } - - @Override - public void resume() { - paused = false; - tryProcess(); - } - - /** - * Implements item handling / processing. Implementation can use {@link #canProcess()} and {@link #canContinueProcessing()} - * method to ensure, that processing is done by a single thread at a time. - */ - protected abstract void tryProcess(); - - public void handle(BiConsumer onData, Consumer onError, Runnable onComplete) { - Objects.requireNonNull(onData, "Parameter onData is null!"); - synchronized (this) { - if (this.onData != null) { - throw new IllegalStateException("Handler is already registered!"); - } - this.onData = onData; - this.onError = onError; - this.onComplete = onComplete; - } - resume(); - } - - /** - * Implementation of {@link #tryProcess()} method should call this to reserve initial handle processing (if possible). - * The same method should call {@link #canContinueProcessing()} before every iteration to be sure, that handle processing - * should continue. - * - * @return {@code true} only if method can process (handle) item - */ - protected boolean canProcess() { - if (onData == null) { - return false; - } - lock.lock(); - try { - if (paused || processing) { - return false; - } else { - processing = true; - return true; - } - } finally { - lock.unlock(); - } - } - - /** - * Implementation of {@link #tryProcess()} which initially was accepted by {@link #canProcess()} should call this method - * before every iteration to be sure, that processing can continue (is not paused). - * - * @return {@code true} only if method can continue with handle processing - */ - protected boolean canContinueProcessing() { - if (paused) { - lock.lock(); - try { - processing = false; - } finally { - lock.unlock(); - } - return false; - } else { - return true; - } - } - - protected boolean paused() { - return paused; - } - - protected void releaseProcessing() { - lock.lock(); - try { - processing = false; - } finally { - lock.unlock(); - } - } - - protected void handleError(Throwable thr) { - if (onError != null) { - onError.accept(thr); - } else { - LOGGER.log(Level.WARNING, "Unhandled throwable!", thr); - } - } - - protected BiConsumer getOnData() { - return onData; - } - - protected Consumer getOnError() { - return onError; - } - - protected Runnable getOnComplete() { - return onComplete; - } -} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/PublisherValve.java b/common/reactive/src/main/java/io/helidon/common/reactive/valve/PublisherValve.java deleted file mode 100644 index dd108681d51..00000000000 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/PublisherValve.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.Objects; -import java.util.concurrent.Flow; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * The {@link Valve} implementation on top of {@link java.util.concurrent.Flow.Publisher}. - * - * @param Type of {@code Valve} and {@code Publisher} items - */ -class PublisherValve implements Valve { - - private static final Logger LOGGER = Logger.getLogger(PublisherValve.class.getName()); - - private final ReentrantLock lock = new ReentrantLock(); - private final Flow.Publisher publisher; - - private volatile Subscriber subscriber; - private volatile boolean paused = false; - - private boolean recordedDemand = false; - - /** - * Creates new instance. - * - * @param publisher a publisher as a base of this {@code Valve} - */ - PublisherValve(Flow.Publisher publisher) { - Objects.requireNonNull(publisher, "Parameter 'publisher' is null!"); - this.publisher = publisher; - } - - @Override - public void handle(BiConsumer onData, Consumer onError, Runnable onComplete) { - synchronized (this) { - if (this.subscriber != null) { - throw new IllegalStateException("Handler is already registered!"); - } - this.subscriber = new Subscriber(onData, onError, onComplete); - } - this.paused = false; - publisher.subscribe(this.subscriber); - } - - @Override - public void pause() { - lock.lock(); - try { - this.paused = true; - } finally { - lock.unlock(); - } - } - - @Override - public void resume() { - boolean processDemand = false; - lock.lock(); - try { - if (paused && subscriber != null) { - paused = false; - if (recordedDemand) { - processDemand = true; - recordedDemand = false; - } - } - } finally { - lock.unlock(); - if (processDemand) { - subscriber.subscription.request(1); - } - } - } - - private boolean recordDemand() { - lock.lock(); - try { - if (paused) { - this.recordedDemand = true; - return true; - } else { - return false; - } - } finally { - lock.unlock(); - } - } - - private class Subscriber implements Flow.Subscriber { - - private final BiConsumer onData; - private final Consumer onError; - private final Runnable onComplete; - - private volatile Flow.Subscription subscription; - - Subscriber(BiConsumer onData, Consumer onError, Runnable onComplete) { - Objects.requireNonNull(onData, "Parameter 'onData' is null!"); - this.onData = onData; - this.onError = onError; - this.onComplete = onComplete; - } - - @Override - public void onSubscribe(Flow.Subscription subscription) { - this.subscription = subscription; - subscription.request(1); - } - - @Override - public void onNext(T item) { - onData.accept(item, PublisherValve.this); - if (!paused || !recordDemand()) { - subscription.request(1); - } - } - - @Override - public void onError(Throwable throwable) { - if (onError != null) { - onError.accept(throwable); - } else { - LOGGER.log(Level.WARNING, "Unhandled throwable!", throwable); - } - } - - @Override - public void onComplete() { - if (onComplete != null) { - onComplete.run(); - } - } - } -} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/RetryingPausableRegistry.java b/common/reactive/src/main/java/io/helidon/common/reactive/valve/RetryingPausableRegistry.java deleted file mode 100644 index f6064557485..00000000000 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/RetryingPausableRegistry.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.function.BiConsumer; - -/** - * The RetryingPausableRegistry. - */ -abstract class RetryingPausableRegistry extends PausableRegistry { - @Override - protected void tryProcess() { - if (canProcess()) { - try { - BiConsumer onData = getOnData(); - boolean breakByPause = false; - T data; - while ((data = moreData()) != null) { - onData.accept(data, this); - if (!canContinueProcessing()) { - breakByPause = true; - break; - } - } - if (!breakByPause && getOnComplete() != null) { - getOnComplete().run(); - } - } catch (Throwable thr) { - handleError(thr); - releaseProcessing(); - } - } - } - - protected abstract T moreData() throws Throwable; -} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/Tank.java b/common/reactive/src/main/java/io/helidon/common/reactive/valve/Tank.java deleted file mode 100644 index 0ea84e6431a..00000000000 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/Tank.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.Collection; -import java.util.Iterator; -import java.util.Objects; -import java.util.Queue; -import java.util.Spliterator; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Predicate; - -/** - * Tank of events is a closeable FIFO queue with a limited size implementing {@link Valve} reactive API. - * - * @param a type of items produced by {@code Valve} API - */ -public class Tank implements Valve, BlockingQueue, AutoCloseable { - - private final int capacity; - private final CloseableSupport closeableSupport = new CloseableSupport(); - private final Queue drainHandlers = new LinkedBlockingDeque<>(); - private final PausableRegistry registry = new PausableRegistry() { - @Override - protected void tryProcess() { - Tank.this.tryProcess(); - } - }; - private final ThreadLocal inDrainHandler = ThreadLocal.withInitial(() -> Boolean.FALSE); - private final ArrayBlockingQueue queue; - - /** - * Creates new instance. - * - * @param capacity the capacity of this queue - */ - public Tank(int capacity) { - this.capacity = capacity; - queue = new ArrayBlockingQueue<>(capacity, true); - } - - /** - * Provided handler is called a single time when internal capacity is maximally half full and instance is not closed. - * - * @param drainHandler an handler of drain event - * @throws NullPointerException if {@code drainHandler} is {@code null} - */ - public void whenDrain(Runnable drainHandler) { - Objects.requireNonNull(drainHandler, "Parameter 'drainHandler' is null!"); - checkClosed(); - if (!inDrainHandler.get() && remainingCapacity() >= (capacity / 2)) { - inDrainHandler.set(true); - try { - drainHandler.run(); - } finally { - inDrainHandler.set(false); - } - } else { - drainHandlers.add(drainHandler); - } - } - - // ----- Valve implementation - - @Override - public void pause() { - registry.pause(); - } - - @Override - public void resume() { - registry.resume(); - } - - @Override - public void handle(BiConsumer onData, Consumer onError, Runnable onComplete) { - registry.handle(onData, onError, onComplete); - } - - private void tryProcess() { - if (registry.canProcess()) { - boolean breakByPause = false; - try { - BiConsumer onData = registry.getOnData(); - T t; - while ((t = poll()) != null) { - onData.accept(t, this); - if (registry.paused()) { - breakByPause = true; - break; - } - } - } catch (Exception e) { - registry.handleError(e); - } finally { - if (!breakByPause && closeableSupport.closed()) { - // Handle close - Runnable onComplete = registry.getOnComplete(); - if (onComplete != null) { - onComplete.run(); - } - } - registry.releaseProcessing(); - } - processDrainHandlers(); - } - } - - private void processDrainHandlers() { - while (!inDrainHandler.get() && !closeableSupport.closed() && remainingCapacity() >= (capacity / 2)) { - Runnable hndlr = drainHandlers.poll(); - if (hndlr != null) { - inDrainHandler.set(true); - try { - hndlr.run(); - } finally { - inDrainHandler.set(false); - } - } else { - break; - } - } - } - - // ----- AutoCloseable - - @Override - public void close() { - closeableSupport.close(); - tryProcess(); - } - - private void checkClosed() { - if (closeableSupport.closed()) { - throw new IllegalStateException("Tank instance is closed!"); - } - } - - // ----- Insert methods - - @Override - public boolean add(T t) { - checkClosed(); - boolean result = queue.add(t); - tryProcess(); - return result; - } - - @Override - public boolean addAll(Collection c) { - checkClosed(); - boolean result = queue.addAll(c); - tryProcess(); - return result; - } - - @Override - public boolean offer(T t) { - if (closeableSupport.closed()) { - return false; - } - boolean result = queue.offer(t); - if (result) { - tryProcess(); - } - return result; - } - - /** - * Inserts the specified element at the tail of this queue, waiting - * for space to become available if the queue is full. - * - * @throws InterruptedException {@inheritDoc} - * @throws NullPointerException {@inheritDoc} - * @throws IllegalArgumentException if Tank is closed - */ - @Override - public void put(T t) throws InterruptedException { - checkClosed(); - queue.put(t); - tryProcess(); - } - - @Override - public boolean offer(T t, long timeout, TimeUnit unit) throws InterruptedException { - if (closeableSupport.closed()) { - return false; - } - boolean result = queue.offer(t, timeout, unit); - if (result) { - tryProcess(); - } - return result; - } - - // ----- Remove methods - - @Override - public void clear() { - queue.clear(); - } - - @Override - public T poll() { - T t = queue.poll(); - if (t != null) { - processDrainHandlers(); - } - return t; - } - - @Override - public T take() throws InterruptedException { - T t = queue.take(); - processDrainHandlers(); - return t; - } - - @Override - public T poll(long timeout, TimeUnit unit) throws InterruptedException { - T t = queue.poll(timeout, unit); - if (t != null) { - processDrainHandlers(); - } - return t; - } - - @Override - public boolean remove(Object o) { - boolean result = queue.remove(o); - if (result) { - processDrainHandlers(); - } - return result; - } - - @Override - public int drainTo(Collection c) { - int result = queue.drainTo(c); - if (result > 0) { - processDrainHandlers(); - } - return result; - } - - @Override - public int drainTo(Collection c, int maxElements) { - int result = queue.drainTo(c, maxElements); - if (result > 0) { - processDrainHandlers(); - } - return result; - } - - @Override - public boolean removeIf(Predicate filter) { - boolean result = queue.removeIf(filter); - if (result) { - processDrainHandlers(); - } - return result; - } - - @Override - public boolean removeAll(Collection c) { - boolean result = queue.removeAll(c); - if (result) { - processDrainHandlers(); - } - return result; - } - - @Override - public boolean retainAll(Collection c) { - boolean result = queue.retainAll(c); - if (result) { - processDrainHandlers(); - } - return result; - } - - @Override - public T remove() { - T t = queue.remove(); - if (t != null) { - processDrainHandlers(); - } - return t; - } - - // ----- Query methods (delegated only) - - @Override - public T element() { - return queue.element(); - } - - @Override - public T peek() { - return queue.peek(); - } - - @Override - public int size() { - return queue.size(); - } - - @Override - public boolean isEmpty() { - return queue.isEmpty(); - } - - @Override - public int remainingCapacity() { - return queue.remainingCapacity(); - } - - @Override - public boolean containsAll(Collection c) { - return false; - } - - @Override - public boolean contains(Object o) { - return queue.contains(o); - } - - @Override - public Object[] toArray() { - return queue.toArray(); - } - - @Override - public T1[] toArray(T1[] a) { - return queue.toArray(a); - } - - @Override - public Iterator iterator() { - return queue.iterator(); - } - - @Override - public Spliterator spliterator() { - return queue.spliterator(); - } - - @Override - public void forEach(Consumer action) { - queue.forEach(action); - } -} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/UnorderedCollectorSupport.java b/common/reactive/src/main/java/io/helidon/common/reactive/valve/UnorderedCollectorSupport.java deleted file mode 100644 index d11247e3e53..00000000000 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/UnorderedCollectorSupport.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.function.BinaryOperator; -import java.util.stream.Collector; - -/** - * Use {@link java.util.stream.Collector} with {@code UNORDERED} characteristic to - * collect data from {@link Valve}. - *

- * Implementation use {@link ThreadLocal} to decrease synchronization demand. - */ -class UnorderedCollectorSupport { - - private static final int CONCURRENCY_LIMIT = 256; - - private final Collector collector; - private final CompletableFuture resultFuture = new CompletableFuture<>(); - private final ThreadLocal intermediate = new ThreadLocal<>(); - private final List intermediates = new ArrayList<>(16); - - private A sharedIntermediate; - - /** - * Creates new instance. - * - * @param collector a collector with {@code UNORDERED} characteristic to use - * @throws NullPointerException if {@code collector} parameter is {@code null} - * @throws IllegalArgumentException if {@code collector} doesn't have {@code UNORDERED} characteristic. - */ - UnorderedCollectorSupport(Collector collector) { - Objects.requireNonNull(collector, "Parameter 'collector' is null"); - if (!collector.characteristics().contains(Collector.Characteristics.UNORDERED)) { - throw new IllegalArgumentException("Collector parameter must have 'UNORDERED' characteristic."); - } - this.collector = collector; - } - - /** - * Returns a result which will be completed when {@link #complete()} or {@link #completeExceptionally(Throwable)} - * is called. - * - * @return a completion stage of the result - */ - CompletionStage getResult() { - return resultFuture; - } - - /** - * Add an item using collector. - * - * @param item a item to add - */ - void add(T item) { - A a = intermediate.get(); - if (a == null) { - synchronized (intermediates) { - if (intermediates.size() < CONCURRENCY_LIMIT) { - // Add an intermediate - a = collector.supplier().get(); - intermediate.set(a); - intermediates.add(a); - } else { - if (sharedIntermediate == null) { - sharedIntermediate = collector.supplier().get(); - intermediates.add(sharedIntermediate); - } - collector.accumulator().accept(sharedIntermediate, item); - return; - } - } - } - // Add item - collector.accumulator().accept(a, item); - } - - @SuppressWarnings("unchecked") - void complete() { - synchronized (intermediates) { - BinaryOperator combiner = collector.combiner(); - A a = intermediates.isEmpty() ? collector.supplier().get() : intermediates.get(0); - for (int i = 1; i < intermediates.size(); i++) { - a = combiner.apply(a, intermediates.get(i)); - } - if (collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { - resultFuture.complete((R) a); - } else { - resultFuture.complete(collector.finisher().apply(a)); - } - } - } - - void completeExceptionally(Throwable t) { - resultFuture.completeExceptionally(t); - } -} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/Valve.java b/common/reactive/src/main/java/io/helidon/common/reactive/valve/Valve.java deleted file mode 100644 index 96e35349af2..00000000000 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/Valve.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Flow; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collector; - -/** - * Represents a reactive source of data which can be {@link #pause() paused} and {@link #resume() resumed}. - * - * @param a type of items produced by {@code Valve} API - */ -public interface Valve extends Pausable { - - /** - * Register data handlers (callbacks). - * - * @param onData a callback for data chunks and {@link Pausable} representing a faucet of this {@link Valve}. - * @param onError a callback of errors or {@code null} - * @param onComplete a callback for completion event or {@code null} - * @throws NullPointerException if {@code onData} parameter is {@code null} - * @throws IllegalStateException if cannot register new callback. For example if instance accepts only a single handler. - */ - void handle(BiConsumer onData, Consumer onError, Runnable onComplete); - - /** - * Register data handlers (callbacks). - * - * @param onData a callback for data chunks and {@link Pausable} representing a faucet of this {@link Valve}. - * @throws NullPointerException if {@code onData} parameter is {@code null} - * @throws IllegalStateException if cannot register new callback. For example if instance accepts only a single handler. - */ - default void handle(BiConsumer onData) { - handle(onData, null, null); - } - - /** - * Register data handlers (callbacks). - * - * @param onData a callback for data chunks and {@link Pausable} representing a faucet of this {@link Valve}. - * @param onError a callback of errors or {@code null} - * @throws NullPointerException if {@code onData} parameter is {@code null} - * @throws IllegalStateException if cannot register new callback. For example if instance accepts only a single handler. - */ - default void handle(BiConsumer onData, Consumer onError) { - handle(onData, onError, null); - } - - /** - * Register data handlers (callbacks). - * - * @param onData a callback for data chunks. - * @param onError a callback of errors or {@code null} - * @param onComplete a callback for completion event or {@code null} - * @throws NullPointerException if {@code onData} parameter is {@code null} - * @throws IllegalStateException if cannot register new callback. For example if instance accepts only a single handler. - */ - default void handle(Consumer onData, Consumer onError, Runnable onComplete) { - handle((t, p) -> onData.accept(t), onError, onComplete); - } - - /** - * Register data handlers (callbacks). - * - * @param onData a callback for data chunks. - * @throws NullPointerException if {@code onData} parameter is {@code null} - * @throws IllegalStateException if cannot register new callback. For example if instance accepts only a single handler. - */ - default void handle(Consumer onData) { - handle(onData, null, null); - } - - /** - * Register data handlers (callbacks). - * - * @param onData a callback for data chunks. - * @param onError a callback of errors or {@code null} - * @throws NullPointerException if {@code onData} parameter is {@code null} - * @throws IllegalStateException if cannot register new callback. For example if instance accepts only a single handler. - */ - default void handle(Consumer onData, Consumer onError) { - handle(onData, onError, null); - } - - /** - * Returns a {@link Valve} consisting of the results of applying the given function to the elements of this {@link Valve}. - * - * @param the element type of the new {@link Valve} - * @param mapper a stateless function to apply to each element - * @return the new {@code Valve} - */ - default Valve map(Function mapper) { - return new ValveFilter<>(this, onData -> (t, p) -> onData.accept(mapper.apply(t), p)); - } - - /** - * Returns new {@code Valve} instance which combines all results into a single Valve. - *

- * If provided {@code mapFunction} returns {@code null} then this result is skipped. - * - * @param mapFunction maps an element into new {@link Valve} instance. - * @param the element type of the new {@link Valve} - * @return the new {@code Valve} - */ - default Valve flatMap(Function> mapFunction) { - throw new UnsupportedOperationException("Not implemented!"); - } - - /** - * Returns a {@link Valve} consisting of the elements of this {@link Valve} that match the given predicate. - * - * @param predicate a stateless predicate to apply to each element to determine if it should be included - * @return the new {@code Valve} - */ - default Valve filter(Predicate predicate) { - return new ValveFilter<>(this, onData -> (t, p) -> { - if (predicate.test(t)) { - onData.accept(t, p); - } - }); - } - - /** - * Returns a {@link Valve} consisting of the elements of this {@link Valve}, additionally - * performing the provided action on each element as elements are consumed from the resulting {@link Valve}. - * - * @param action an action to perform on the elements as they are consumed from the {@code Valve} - * @return the new {@code Valve} - */ - default Valve peek(Consumer action) { - return new ValveFilter<>(this, onData -> (element, p) -> { - action.accept(element); - onData.accept(element, p); - }); - } - - /** - * Returns a {@link CompletionStage} which will be completed when this {@link Valve} is completed and result is a collected - * value. - *

- * Result completes exceptionally if this {@code Valve} completes exceptionally. - * - * @param collector a collector to use - * @param the intermediate accumulation type of the {@code Collector} - * @param the type of the result - * @return a completion stage of collected result - */ - default CompletionStage collect(Collector collector) { - Set characteristics = collector.characteristics(); - if (characteristics.contains(Collector.Characteristics.CONCURRENT)) { - A intermediateCollection = collector.supplier().get(); - BiConsumer accumulator = collector.accumulator(); - CompletableFuture result = new CompletableFuture<>(); - handle(item -> accumulator.accept(intermediateCollection, item), - result::completeExceptionally, - () -> result.complete(collector.finisher().apply(intermediateCollection))); - return result; - } else if (characteristics.contains(Collector.Characteristics.UNORDERED)) { - UnorderedCollectorSupport support = new UnorderedCollectorSupport<>(collector); - handle(support::add, support::completeExceptionally, support::complete); - return support.getResult(); - } else { - A intermediateCollection = collector.supplier().get(); - BiConsumer accumulator = collector.accumulator(); - CompletableFuture result = new CompletableFuture<>(); - handle(item -> { - synchronized (intermediateCollection) { - accumulator.accept(intermediateCollection, item); - } - }, - result::completeExceptionally, - () -> result.complete(collector.finisher().apply(intermediateCollection))); - return result; - } - } - - /** - * Transforms this {@code Valve} into {@link java.util.concurrent.Flow.Publisher} representation. Resulting {@code - * Publisher} - * accepts only single {@link java.util.concurrent.Flow.Subscriber}. - * - * @return a {@code Publisher} representation - */ - default Flow.Publisher toPublisher() { - return new ValvePublisher<>(this); - } - - /** - * Returns new {@code Valve} which defer all handlers to provided {@link ExecutorService}. Each data chunk will - * be still called sequentially. - * - * @param executorService an executor service to use - * @return a new {@code Valve} instance - * @throws NullPointerException if {@code executorService} parameter is {@code null}. - */ - default Valve executeOn(ExecutorService executorService) { - Objects.requireNonNull(executorService, "Parameter 'executorService' is null!"); - return new DetachedValve<>(this, executorService); - } - - /** - * Transforms this {@code Valve} into {@link ValveIterator} representation. It transforms reactive approach to the blocking - * polling {@link java.util.Iterator} API. - * - * @return an iterator instance - * @throws IllegalStateException if this instance has already registered handlers - */ - default ValveIterator toIterator() { - return new ValveIterator<>(this); - } -} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/ValveFilter.java b/common/reactive/src/main/java/io/helidon/common/reactive/valve/ValveFilter.java deleted file mode 100644 index 9052658ef55..00000000000 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/ValveFilter.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; - -/** - * Delegate filter for {@link Valve}. - */ -class ValveFilter implements Valve { - - private final Valve delegate; - private final Function, BiConsumer> filteringFunction; - - ValveFilter(Valve delegate, - Function, BiConsumer> filteringFunction) { - this.delegate = delegate; - this.filteringFunction = filteringFunction; - } - - @Override - public void pause() { - delegate.pause(); - } - - @Override - public void resume() { - delegate.resume(); - } - - @Override - public void handle(BiConsumer onData, Consumer onError, Runnable onComplete) { - delegate.handle(filteringFunction.apply(onData), onError, onComplete); - } -} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/ValveIterator.java b/common/reactive/src/main/java/io/helidon/common/reactive/valve/ValveIterator.java deleted file mode 100644 index a14f084dd3b..00000000000 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/ValveIterator.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.logging.Logger; - -/** - * Transforms reactive {@link Valve} into a blocking {@link Iterator}. - *

- * If the original {@code Valve} ends with {@code Throwable} then this iterator simply ends and original cause can be get - * using {@link #getThrowable()} method. - * - * @param Type of elements - */ -public class ValveIterator implements Iterator { - - private static final Logger LOGGER = Logger.getLogger(ValveIterator.class.getName()); - - private final Object lock = new Object(); - private final Valve valve; - private boolean closed = false; - private T nextItem; - private volatile Throwable throwable; - - ValveIterator(Valve valve) { - this.valve = valve; - valve.handle((t, p) -> { - synchronized (lock) { - nextItem = t; - p.pause(); - lock.notifyAll(); - } - }, throwable -> { - this.throwable = throwable; - close(); - }, this::close); - } - - private void close() { - synchronized (lock) { - closed = true; - lock.notifyAll(); - } - } - - @Override - public boolean hasNext() { - synchronized (lock) { - if (nextItem != null) { - return true; - } - if (closed) { - return false; - } - while (true) { - valve.resume(); - if (nextItem != null) { - return true; - } - if (closed) { - return false; - } - try { - lock.wait(); - if (nextItem != null) { - return true; - } - if (closed) { - return false; - } - } catch (InterruptedException e) { - this.closed = true; - this.throwable = e; - return false; - } - } - } - } - - @Override - public T next() { - synchronized (lock) { - if (hasNext()) { - T result = nextItem; - nextItem = null; - return result; - } else { - throw new NoSuchElementException("No more elements. Original Valve is closed!"); - } - } - } - - /** - * If original {@link Valve} ends with error then this method returns cause of such error. - * - * @return the cause of {@code Valve} error or {@code null} - */ - public Throwable getThrowable() { - return throwable; - } -} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/ValvePublisher.java b/common/reactive/src/main/java/io/helidon/common/reactive/valve/ValvePublisher.java deleted file mode 100644 index fef0b6b3dd6..00000000000 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/ValvePublisher.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.concurrent.Flow; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * The ValvePublisher provides {@link java.util.concurrent.Flow.Publisher} based API for the {@link Valve}. - * This publisher accepts only a single subscriber. - * - * @param the type of items to be published - */ -class ValvePublisher implements Flow.Publisher { - - private final Valve valve; - private final ReentrantReadWriteLock.WriteLock pausableFeederNullLock = new ReentrantReadWriteLock().writeLock(); - - private volatile Flow.Subscriber singleSubscriber; - private volatile PausableFeeder pausableFeeder; - - /** - * Creates a {@link java.util.concurrent.Flow.Publisher} wrapping a provided {@link Valve}. - * Depending on the Valve implementation, only the first {@link java.util.concurrent.Flow.Subscriber} - * (subscribed to any number of such created publishers for a single {@link Valve} instance) that calls - * {@link java.util.concurrent.Flow.Subscription#request(long)} will be able to consume the produced items. - * - * @param valve the valve to wrap - */ - ValvePublisher(Valve valve) { - this.valve = valve; - } - - @Override - public void subscribe(Flow.Subscriber subscriber) { - - synchronized (this) { - if (this.singleSubscriber != null) { - subscriber.onError(new IllegalStateException("Multiple subscribers aren't allowed!")); - return; - } - this.singleSubscriber = subscriber; - } - - this.singleSubscriber.onSubscribe(new Flow.Subscription() { - @Override - public void request(long n) { - - if (n <= 0) { - subscriber.onError(new IllegalArgumentException("Requested illegal item count: " + 0)); - return; - } - - if (pausableFeeder != null) { - // a standard release - pausableFeeder.release(n); - } else { - try { - pausableFeederNullLock.lock(); - - if (pausableFeeder == null) { - // the first item is always emitted, as such we set one less - pausableFeeder = new PausableFeeder(n - 1, valve); - - handleValve(); - } else { - // pausableFeeder actually is not null, do a standard release - pausableFeeder.release(n); - } - } finally { - pausableFeederNullLock.unlock(); - } - } - } - - @Override - public void cancel() { - valve.pause(); - } - }); - } - - private void handleValve() { - valve.handle((data) -> { - singleSubscriber.onNext(data); - pausableFeeder.acquire(); - }, - throwable -> singleSubscriber.onError(new IllegalStateException( - "Valve to Publisher in an error.", - throwable)), - singleSubscriber::onComplete); - } - - private static class PausableFeeder { - private final Pausable pausable; - private final ReentrantReadWriteLock.WriteLock lock = new ReentrantReadWriteLock().writeLock(); - - private volatile long count; - - PausableFeeder(long count, Pausable pausable) { - this.count = count; - this.pausable = pausable; - } - - private void acquire() { - try { - lock.lock(); - - count = count == Long.MAX_VALUE - ? count - : count == 0 ? 0 : count - 1; - - if (count == 0) { - pausable.pause(); - } - } finally { - lock.unlock(); - } - } - - private void release(long n) { - try { - lock.lock(); - - long r = count + n; - // HD 2-12 Overflow iff both arguments have the opposite sign of the result; inspired by Math.addExact(long, long) - count = r == Long.MAX_VALUE || ((count ^ r) & (n ^ r)) < 0 - // unbounded reached - ? Long.MAX_VALUE - : count + n; - - pausable.resume(); - } finally { - lock.unlock(); - } - } - } -} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/Valves.java b/common/reactive/src/main/java/io/helidon/common/reactive/valve/Valves.java deleted file mode 100644 index b100ee5db36..00000000000 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/Valves.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Flow; - -/** - * An utility class for {@link Valve} interface. - */ -public class Valves { - - @SuppressWarnings("rawtypes") - private static final Valve EMPTY = new EmptyValve<>(); - - private Valves() {} - - /** - * Creates a {@link Valve} instance from provided array. - *

- * If {@code t array} parameter is {@code null} then returns an empty {@link Valve}. - * - * @param t an array to provide as a {@link Valve} - * @param a type of the array items - * @return the new instance - */ - @SafeVarargs - @SuppressWarnings("varargs") - public static Valve from(T... t) { - if (t == null || t.length == 0) { - return empty(); - } - return from(Arrays.asList(t)); - } - - /** - * Creates a {@link Valve} instance from the provided {@link Iterable}. - *

- * If {@code iterable} parameter is {@code null} then returns an empty {@link Valve}. - * - * @param iterable an iterable to provide as a {@link Valve} - * @param a type of iterable items - * @return the new instance - */ - public static Valve from(Iterable iterable) { - if (iterable == null) { - return empty(); - } - return new IteratorValve<>(iterable.iterator()); - } - - /** - * Creates a {@link ByteBuffer} based {@link Valve} instance from the provided {@link InputStream}. - * Each byte buffer will have the provided capacity. - *

- * Each byte buffer uses a newly allocated memory and as such no pooling is performed. - * - * @param stream the input stream to create the {@link Valve} from - * @param bufferCapacity the capacity of each buffer of bytes - * @return the new instance - */ - public static Valve from(InputStream stream, int bufferCapacity) { - return from(stream, bufferCapacity, null); - } - - /** - * Creates a {@link ByteBuffer} based {@link Valve} instance from the provided {@link InputStream}. - * Each byte buffer will have the provided capacity. - *

- * Each byte buffer uses a newly allocated memory and as such no pooling is performed. - * - * @param stream the input stream to create the {@link Valve} from - * @param bufferCapacity the capacity of each buffer of bytes - * @param executorService the executor service to use for an execution of the {@link InputStream#read()} - * (and its overloads) operations that are blocking by its nature. - * @return the new instance - */ - public static Valve from(InputStream stream, int bufferCapacity, ExecutorService executorService) { - if (stream == null) { - return empty(); - } - if (executorService != null) { - return new InputStreamValve.InputStreamExecutorValve(stream, bufferCapacity, executorService); - } else { - return new InputStreamValve(stream, bufferCapacity); - } - } - - /** - * Creates a {@link Valve} instance from provided {@link java.util.concurrent.Flow.Publisher}. - *

- * If {@code publisher} parameter is {@code null} then returns an empty {@link Valve}. - * - * @param publisher a publisher to provide as a {@link Valve} - * @param a type of published items - * @return the new instance - */ - public static Valve from(Flow.Publisher publisher) { - if (publisher == null) { - return empty(); - } - return new PublisherValve<>(publisher); - } - - /** - * Returns an empty {@link Valve} - instance, which report complete as soon as handler is registered. - *

- * For performance reason, this particular Valve accepts any amount of handlers. - * - * @param type of the item (which is not there :-) ) - * @return singleton instance - */ - @SuppressWarnings("unchecked") - public static Valve empty() { - return (Valve) EMPTY; - } - -} diff --git a/common/reactive/src/main/java/module-info.java b/common/reactive/src/main/java/module-info.java index 8d2a50f3126..0e5960b735c 100644 --- a/common/reactive/src/main/java/module-info.java +++ b/common/reactive/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,5 +25,4 @@ requires io.helidon.common.mapper; exports io.helidon.common.reactive; - exports io.helidon.common.reactive.valve; } diff --git a/common/reactive/src/test/java/io/helidon/common/reactive/BaseProcessorTest.java b/common/reactive/src/test/java/io/helidon/common/reactive/BaseProcessorTest.java index c64ac45a612..46fdf05b722 100644 --- a/common/reactive/src/test/java/io/helidon/common/reactive/BaseProcessorTest.java +++ b/common/reactive/src/test/java/io/helidon/common/reactive/BaseProcessorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.util.concurrent.Flow.Subscriber; import java.util.concurrent.Flow.Subscription; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.equalTo; @@ -236,21 +237,6 @@ public void testDoubleSubscribe() { assertThat(subscriber2.getSubcription(), is(nullValue())); } - @Test - public void testSubscriptionNotCanceled() { - TestProcessor processor = new TestProcessor<>(); - TestSubscription subscription = new TestSubscription(); - processor.onSubscribe(subscription); - TestSubscriber subscriber = new TestSubscriber() { - @Override - public void onSubscribe(Subscription subscription) { - subscription.cancel(); - } - }; - processor.subscribe(subscriber); - assertThat(subscription.canceled, is(equalTo(false))); - } - @Test public void testNotEnoughRequestToSubmit() { TestProcessor processor = new TestProcessor<>(); diff --git a/common/reactive/src/test/java/io/helidon/common/reactive/MultiTest.java b/common/reactive/src/test/java/io/helidon/common/reactive/MultiTest.java index dcf423091d0..cccf4382077 100644 --- a/common/reactive/src/test/java/io/helidon/common/reactive/MultiTest.java +++ b/common/reactive/src/test/java/io/helidon/common/reactive/MultiTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,20 @@ */ package io.helidon.common.reactive; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Flow.Subscription; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; import io.helidon.common.mapper.Mapper; -import org.junit.jupiter.api.Test; - import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.CoreMatchers.instanceOf; @@ -32,6 +38,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; +import org.junit.jupiter.api.Test; + /** * {@link MultiTest} test. */ @@ -234,17 +242,222 @@ public String map(String item) { @Test public void testMapBadMapperNullValue() { MultiTestSubscriber subscriber = new MultiTestSubscriber<>(); - Multi.just("foo", "bar").map(new Mapper() { - @Override - public String map(String item) { - return null; - } - }).subscribe(subscriber); + Multi.just("foo", "bar").map((Mapper) item -> null).subscribe(subscriber); assertThat(subscriber.isComplete(), is(equalTo(false))); - assertThat(subscriber.getLastError(), is(instanceOf(IllegalStateException.class))); + assertThat(subscriber.getLastError(), is(instanceOf(NullPointerException.class))); assertThat(subscriber.getItems(), is(empty())); } + @Test + void testPeekInt() { + AtomicInteger sum1 = new AtomicInteger(); + AtomicInteger sum2 = new AtomicInteger(); + Multi.just(1, 2, 3) + .peek(sum1::addAndGet) + .forEach(sum2::addAndGet); + + assertThat(sum1.get(), is(equalTo(1 + 2 + 3))); + assertThat(sum1.get(), is(equalTo(sum2.get()))); + } + + @Test + void testPeekString() { + StringBuilder sbBefore = new StringBuilder(); + AtomicInteger sum = new AtomicInteger(); + Multi.just("1", "2", "3") + .peek(sbBefore::append) + .map(Integer::parseInt) + .forEach(sum::addAndGet); + assertThat(sbBefore.toString(), is(equalTo("123"))); + assertThat(sum.get(), is(equalTo(1 + 2 + 3))); + } + + @Test + void testFilter() { + StringBuilder sbBefore = new StringBuilder(); + AtomicInteger sum = new AtomicInteger(); + Multi.just("1", "2", "3") + .peek(sbBefore::append) + .map(Integer::parseInt) + .filter(i -> i != 2) + .forEach(sum::addAndGet); + assertThat(sbBefore.toString(), is(equalTo("123"))); + assertThat(sum.get(), is(equalTo(1 + 3))); + } + + @Test + void testLimit() { + final List TEST_DATA = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 9); + final long TEST_LIMIT = 3; + final int EXPECTED_SUM = 1 + 2 + 3; + + AtomicInteger multiSum1 = new AtomicInteger(); + AtomicInteger multiSum2 = new AtomicInteger(); + + Multi.just(TEST_DATA) + .limit(TEST_LIMIT) + .peek(multiSum1::addAndGet) + .forEach(multiSum2::addAndGet); + + AtomicInteger streamSum1 = new AtomicInteger(); + AtomicInteger streamSum2 = new AtomicInteger(); + + TEST_DATA.stream() + .limit(TEST_LIMIT) + .peek(streamSum1::addAndGet) + .forEach(streamSum2::addAndGet); + + assertThat(multiSum2.get(), is(equalTo(EXPECTED_SUM))); + assertThat(streamSum1.get(), is(equalTo(EXPECTED_SUM))); + assertThat(streamSum2.get(), is(equalTo(EXPECTED_SUM))); + } + + @Test + void testSkip() throws ExecutionException, InterruptedException { + final List TEST_DATA = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 9); + final long TEST_SKIP = 3; + final List EXPECTED = Arrays.asList(4, 5, 6, 7, 9); + + List result = Multi.just(TEST_DATA) + .skip(TEST_SKIP) + .collectList().get(); + + assertThat(result, is(equalTo(EXPECTED))); + } + + @Test + void testTakeWhile() throws ExecutionException, InterruptedException { + final List TEST_DATA = Arrays.asList(1, 2, 3, 4, 3, 2, 1, 0); + final List EXPECTED = Arrays.asList(1, 2, 3); + + List result = Multi.just(TEST_DATA) + .takeWhile(i -> i < 4) + .collectList().get(); + + assertThat(result, is(equalTo(EXPECTED))); + } + + @Test + void testDropWhile() throws ExecutionException, InterruptedException { + final List TEST_DATA = Arrays.asList(1, 2, 3, 4, 3, 2, 1, 0); + final List EXPECTED = Arrays.asList(4, 3, 2, 1, 0); + + List result = Multi.just(TEST_DATA) + .dropWhile(i -> i < 4) + .collectList().get(); + + assertThat(result, is(equalTo(EXPECTED))); + } + + + @Test + void testOnErrorResumeWith() throws ExecutionException, InterruptedException, TimeoutException { + List result = Multi.error(new RuntimeException()) + .onErrorResumeWith(throwable -> Multi.just(1, 2, 3)) + .collectList() + .get(100, TimeUnit.MILLISECONDS); + + assertThat(result, is(equalTo(List.of(1, 2, 3)))); + } + + @Test + void testFlatMap() throws ExecutionException, InterruptedException { + final List TEST_DATA = Arrays.asList("abc", "xyz"); + final List EXPECTED = Arrays.asList("a", "b", "c", "x", "y", "z"); + List result = Multi.just(TEST_DATA) + .flatMap(s -> Multi.just(s.chars().mapToObj(Character::toString).collect(Collectors.toList()))) + .collectList() + .get(); + assertThat(result, is(equalTo(EXPECTED))); + } + + @Test + void testFlatMapIterable() throws ExecutionException, InterruptedException { + final List TEST_DATA = Arrays.asList("abc", "xyz"); + final List EXPECTED = Arrays.asList("a", "b", "c", "x", "y", "z"); + List result = Multi.just(TEST_DATA) + .flatMapIterable(s -> s.chars().mapToObj(Character::toString).collect(Collectors.toList())) + .collectList() + .get(); + assertThat(result, is(equalTo(EXPECTED))); + } + + @Test + void testOnErrorResume() throws ExecutionException, InterruptedException, TimeoutException { + Integer result = Multi.error(new RuntimeException()) + .onErrorResume(throwable -> 1) + .first() + .get(100, TimeUnit.MILLISECONDS); + + assertThat(result, is(equalTo(1))); + } + + @Test + void testOnError() throws ExecutionException, InterruptedException, TimeoutException { + CompletableFuture beenError = new CompletableFuture<>(); + Multi.error(new RuntimeException()) + .onError(beenError::complete) + .collectList(); + + beenError.get(1, TimeUnit.SECONDS); + } + + @Test + void testOnComplete() throws ExecutionException, InterruptedException, TimeoutException { + CompletableFuture completed = new CompletableFuture<>(); + Multi.just(1, 2, 3) + .onComplete(() -> completed.complete(null)) + .collectList() + .get(100, TimeUnit.MILLISECONDS); + + completed.get(1, TimeUnit.SECONDS); + } + + @Test + void testOnTerminate() throws ExecutionException, InterruptedException, TimeoutException { + CompletableFuture completed = new CompletableFuture<>(); + CompletableFuture beenError = new CompletableFuture<>(); + + Multi.just(1, 2, 3) + .onTerminate(() -> completed.complete(null)) + .first() + .get(100, TimeUnit.MILLISECONDS); + + Multi.error(new RuntimeException()) + .onTerminate(() -> beenError.complete(null)) + .first(); + + beenError.get(1, TimeUnit.SECONDS); + completed.get(1, TimeUnit.SECONDS); + } + + @Test + void testConcat() throws ExecutionException, InterruptedException { + final List TEST_DATA_1 = Arrays.asList(1, 2, 3, 4, 3, 2, 1, 0); + final List TEST_DATA_2 = Arrays.asList(11, 12, 13, 14, 13, 12, 11, 10); + final List EXPECTED = Stream.concat(TEST_DATA_1.stream(), TEST_DATA_2.stream()) + .collect(Collectors.toList()); + + List result = Multi + .concat(Multi.from(TEST_DATA_1), Multi.just(TEST_DATA_2)) + .collectList() + .get(); + + assertThat(result, is(equalTo(EXPECTED))); + } + + @Test + void distinct() throws ExecutionException, InterruptedException { + final List TEST_DATA = Arrays.asList(1, 2, 1, 2, 3, 2, 1, 3); + final List EXPECTED = Arrays.asList(1, 2, 3); + + List result = Multi.just(TEST_DATA) + .distinct() + .collectList().get(); + + assertThat(result, is(equalTo(EXPECTED))); + } + private static class MultiTestSubscriber extends TestSubscriber { @Override diff --git a/common/reactive/src/test/java/io/helidon/common/reactive/RecursionDepthTest.java b/common/reactive/src/test/java/io/helidon/common/reactive/RecursionDepthTest.java new file mode 100644 index 00000000000..66eaaebe3cc --- /dev/null +++ b/common/reactive/src/test/java/io/helidon/common/reactive/RecursionDepthTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.common.reactive; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class RecursionDepthTest { + @Test + void depthTest() { + assertFalse(StreamValidationUtils.getRecursionDepth() > 1); + recursionMethod(4, new AtomicInteger(5), Assertions::assertTrue); + recursionMethod(1, new AtomicInteger(1), Assertions::assertFalse); + recursionMethod(10, new AtomicInteger(9), Assertions::assertFalse); + recursionMethod(15, new AtomicInteger(20), Assertions::assertTrue); + } + + void recursionMethod(int maxDepth, AtomicInteger counter, Consumer runInDepth) { + if (counter.decrementAndGet() == 0) { + runInDepth.accept(StreamValidationUtils.getRecursionDepth() > maxDepth); + return; + } + recursionMethod(maxDepth, counter, runInDepth); + } +} diff --git a/common/reactive/src/test/java/io/helidon/common/reactive/valve/CloseableSupportTest.java b/common/reactive/src/test/java/io/helidon/common/reactive/valve/CloseableSupportTest.java deleted file mode 100644 index 8ab51aebd48..00000000000 --- a/common/reactive/src/test/java/io/helidon/common/reactive/valve/CloseableSupportTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ForkJoinPool; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -class CloseableSupportTest { - - @Test - void singleThread() { - CloseableSupport cs = new CloseableSupport(); - assertThat("Closeable must not be closed by default", cs.closed(), is(false)); - - cs.close(); - assertThat("Closeable must be closed when close is callded", cs.closed(), is(true)); - } - - @Test - void otherThread() throws ExecutionException, InterruptedException { - CloseableSupport cs = new CloseableSupport(); - assertThat("Closeable must not be closed by default", cs.closed(), is(false)); - ForkJoinPool.commonPool().submit(cs::close).get(); - assertThat("Closeable must be closed when close is callded", cs.closed(), is(true)); - } -} diff --git a/common/reactive/src/test/java/io/helidon/common/reactive/valve/InputStreamValveTest.java b/common/reactive/src/test/java/io/helidon/common/reactive/valve/InputStreamValveTest.java deleted file mode 100644 index 3cfc89f971a..00000000000 --- a/common/reactive/src/test/java/io/helidon/common/reactive/valve/InputStreamValveTest.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Function; - -import org.hamcrest.Matcher; -import org.hamcrest.collection.IsCollectionWithSize; -import org.hamcrest.core.AllOf; -import org.hamcrest.core.AnyOf; -import org.hamcrest.core.Every; -import org.hamcrest.core.Is; -import org.hamcrest.core.IsCollectionContaining; -import org.hamcrest.core.StringStartsWith; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -/** - * The InputStreamValveTest. - */ -class InputStreamValveTest { - - private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor(r -> new Thread(r) {{ - setName("test-thread"); - }}); - private static final int LARGE_DATA_YIELDING_SIZE = 10000; - private static final int LARGE_DATA_SIZE = 100000; - - @Test - void sameThreadAscii() throws Exception { - String string = "qwertyuiopasdfghjklzxcvbnm1234567890"; - - verifyFromString(string, StandardCharsets.US_ASCII); - } - - @Test - void sameThreadLargeAscii() throws Exception { - String string = "qwertyuiopasdfghjklzxcvbnm1234567890"; - StringBuilder stringBuilder = new StringBuilder(LARGE_DATA_SIZE * string.length()); - for (int i = 0; i < LARGE_DATA_SIZE; i++) { - stringBuilder.append(string); - } - verifyFromString(stringBuilder.toString(), StandardCharsets.US_ASCII, null, 8 * 1024, 441, "main"); - } - - @Test - void sameThreadResumedYieldingLargeAscii() throws Exception { - verifyOnExecutorService(null); - } - - @Test - void onExecutorYieldingLargeAscii() throws Exception { - AtomicLong counter = new AtomicLong(1); - verifyOnExecutorService(Executors.newFixedThreadPool(16, - r -> new Thread(r::run, - "test-thread-" + counter - .getAndIncrement()))); - } - - private void verifyOnExecutorService(ExecutorService executorService) throws Exception { - String string = "qwertyuiopasdfghjklzxcvbnm1234567890"; - StringBuilder stringBuilder = new StringBuilder(LARGE_DATA_YIELDING_SIZE * string.length()); - for (int i = 0; i < LARGE_DATA_YIELDING_SIZE; i++) { - stringBuilder.append(string); - } - - AtomicBoolean completed = new AtomicBoolean(false); - - Function> valveFunction = stream -> { - Valve valve; - if (executorService != null) { - valve = new InputStreamValve - .InputStreamExecutorValve(stream, 1024, executorService) { - @Override - protected ByteBuffer moreData() throws Throwable { - Thread.yield(); - return super.moreData(); - } - }; - } else { - valve = new InputStreamValve(stream, 1024) { - @Override - protected ByteBuffer moreData() throws Throwable { - Thread.yield(); - return super.moreData(); - } - }; - } - - Executors.newSingleThreadExecutor(r -> new Thread(r, "test-thread-resuming")).submit(() -> { - while (!completed.get()) { - valve.pause(); - Thread.yield(); - valve.resume(); - Thread.yield(); - } - }); - - return valve; - }; - try { - Matcher itemMatcher = StringStartsWith.startsWith("test-thread-"); - if (executorService == null) { - itemMatcher = AnyOf.anyOf(itemMatcher, Is.is("main")); - } - verifyFromString(stringBuilder.toString(), StandardCharsets.US_ASCII, 353, - valveFunction, Every.everyItem(itemMatcher)); - } finally { - completed.set(true); - } - } - - @Test - void onExecutorAscii() throws Exception { - String string = "qwertyuiopasdfghjklzxcvbnm1234567890"; - - verifyFromString(string, StandardCharsets.US_ASCII, EXECUTOR_SERVICE, 64, 2, "test-thread"); - } - - @Test - void sameThreadUTF_8() throws Exception { - String string = "asdf+ěščŘŽÝÁ"; - - verifyFromString(string, StandardCharsets.UTF_8); - } - - private void verifyFromString(String string, - Charset charset, - ExecutorService executorService, - int bufferSize, - int expectedCallCount, - String... threadNames) throws Exception { - verifyFromString(string, - charset, - expectedCallCount, - stream -> Valves.from(stream, bufferSize, executorService), - threadNames); - } - - private void verifyFromString(String string, final Charset charset) throws Exception { - verifyFromString(string, charset, 2, stream -> Valves.from(stream, 64, null), "main"); - } - - @SuppressWarnings("unchecked") - private void verifyFromString(String string, - final Charset charset, - int expected, - Function> valveFunction, - String... items) throws Exception { - verifyFromString(string, charset, expected, valveFunction, - AllOf.allOf((Matcher) IsCollectionWithSize.hasSize(items.length), - IsCollectionContaining.hasItems(items))); - } - - @SuppressWarnings("unchecked") - private void verifyFromString(String string, - final Charset charset, - int expected, - Function> valveFunction, - Matcher matcher) throws Exception { - AtomicLong readCounter = new AtomicLong(); - Set threadNames = new HashSet<>(); - - ByteArrayInputStream stream = new ByteArrayInputStream(string.getBytes(charset)) { - @Override - public int read(byte[] b) throws IOException { - readCounter.incrementAndGet(); - threadNames.add(Thread.currentThread().getName()); - - return super.read(b); - } - }; - - Valve valve = valveFunction.apply(stream); - - String result = valve.collect(InputStreamValve.byteBufferStringCollector(charset)) - .toCompletableFuture() - .get(); - - assertThat(result, is(string)); - assertThat(readCounter.intValue(), is(expected)); - - assertThat("Unexpected thread names: " + threadNames, threadNames, matcher); - } - -} diff --git a/common/reactive/src/test/java/io/helidon/common/reactive/valve/IteratorValveTest.java b/common/reactive/src/test/java/io/helidon/common/reactive/valve/IteratorValveTest.java deleted file mode 100644 index 860298df050..00000000000 --- a/common/reactive/src/test/java/io/helidon/common/reactive/valve/IteratorValveTest.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -/** - * Tests {@link IteratorValve}. - */ -public class IteratorValveTest { - - private static final String ALPHABET = "abcdeghijklmnopqrstuvwxyz"; - - private Collection toCollection(String str) { - Collection result = new ArrayList<>(str.length()); - for (int i = 0; i < str.length(); i++) { - result.add(str.charAt(i)); - } - return result; - } - - @Test - public void nonStop() throws Exception { - IteratorValve valve = new IteratorValve<>(toCollection(ALPHABET).iterator()); - AssertableStringBuilder asb = new AssertableStringBuilder(); - valve.handle(asb::append, asb::onError, asb::onDone); - asb.awaitAndAssert(ALPHABET); - } - - @Test - public void pauseAndResumeInside() throws Exception { - IteratorValve valve = new IteratorValve<>(toCollection(ALPHABET).iterator()); - AssertableStringBuilder asb = new AssertableStringBuilder(); - CountDownLatch halfLatch = new CountDownLatch(1); - valve.handle(ch -> { - if (ch.equals('m')) { - valve.pause(); - halfLatch.countDown(); - } - asb.append(ch); - }, asb::onError, asb::onDone); - if (!halfLatch.await(5, TimeUnit.SECONDS)) { - throw new AssertionError("Timeout"); - } - valve.resume(); - asb.awaitAndAssert(ALPHABET); - } - - void neco() { - boolean sex = Math.random() > 0.5; - boolean java = Math.random() > 0.5; - boolean rock = Math.random() > 0.5; - boolean roll = Math.random() > 0.5; - - if (sex && java && (rock & roll)) { - System.out.println("vole"); - } - } - - @Test - public void pauseExternalResume() throws Exception { - IteratorValve valve = new IteratorValve<>(toCollection(ALPHABET).iterator()); - AssertableStringBuilder asb = new AssertableStringBuilder(); - valve.handle(ch -> { - valve.pause(); - asb.append(ch); - valve.resume(); - }, asb::onError, asb::onDone); - asb.awaitAndAssert(ALPHABET); - } - - static class AssertableStringBuilder extends Finisher { - - StringBuilder stringBuilder = new StringBuilder(); - - void append(Character ch) { - stringBuilder.append(ch); - } - - void awaitAndAssert(String expected) throws Exception{ - await(); - assertThat(stringBuilder.toString(), is(expected)); - } - } - - static class Finisher { - - private CountDownLatch latch = new CountDownLatch(1); - private volatile Throwable throwable; - - void onError(Throwable thr) { - throwable = thr; - onDone(); - } - - void onDone() { - latch.countDown(); - } - - void await() throws Exception { - if (!latch.await(5, TimeUnit.SECONDS)) { - throw new AssertionError("Timeout"); - } - if (throwable != null) { - if (throwable instanceof Exception) { - throw (Exception) throwable; - } else { - throw new AssertionError("Execution issue", throwable); - } - } - } - - } -} diff --git a/common/reactive/src/test/java/io/helidon/common/reactive/valve/PausableRegistryTest.java b/common/reactive/src/test/java/io/helidon/common/reactive/valve/PausableRegistryTest.java deleted file mode 100644 index 24b4cc6f81f..00000000000 --- a/common/reactive/src/test/java/io/helidon/common/reactive/valve/PausableRegistryTest.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.io.IOException; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -class PausableRegistryTest { - - @Test - void doubleHandlers() { - CountingRegistry reg = new CountingRegistry<>(); - reg.handle((data, psb) -> {}, null, null); - assertThrows(IllegalStateException.class, () -> reg.handle((data, psb) -> { - }, null, null)); - } - - @Test - void resumeCallsTryProcess() { - CountingRegistry reg = new CountingRegistry(); - reg.resume(); - assertThat(reg.counter.get(), is(1)); - reg.resume(); - assertThat(reg.counter.get(), is(2)); - } - - @Test - void noProcessIfNoHandler() { - CountingRegistry reg = new CountingRegistry<>(); - assertThat(reg.canProcess(), is(false)); - reg.resume(); - assertThat(reg.canProcess(), is(false)); - reg.handle((data, psb) -> {}, null, null); - assertThat(reg.canProcess(), is(true)); - assertThat(reg.paused(), is(false)); - assertThat(reg.canContinueProcessing(), is(true)); - } - - @Test - void processByHandlerRegistration() { - CountingRegistry reg = new CountingRegistry<>(); - assertThat(reg.counter.get(), is(0)); - reg.handle((data, psb) -> {}, null, null); - assertThat(reg.counter.get(), is(1)); - } - - @Test - void pauseResume() { - CountingRegistry reg = new CountingRegistry<>(); - reg.handle((data, psb) -> {}, null, null); - assertThat(reg.canProcess(), is(true)); - assertThat(reg.paused(), is(false)); - assertThat(reg.canContinueProcessing(), is(true)); - reg.pause(); - assertThat(reg.canContinueProcessing(), is(false)); - assertThat(reg.paused(), is(true)); - assertThat(reg.canProcess(), is(false)); - reg.resume(); - assertThat(reg.canProcess(), is(true)); - assertThat(reg.paused(), is(false)); - assertThat(reg.canContinueProcessing(), is(true)); - } - - @Test - void notTwoProcessors() { - CountingRegistry reg = new CountingRegistry<>(); - reg.handle((data, psb) -> {}, null, null); - assertThat(reg.canProcess(), is(true)); - assertThat(reg.canProcess(), is(false)); - reg.releaseProcessing(); - assertThat(reg.canProcess(), is(true)); - } - - @Test - void reportError() { - AtomicReference ref = new AtomicReference<>(); - CountingRegistry reg = new CountingRegistry<>(); - reg.handle((data, psb) -> {}, ref::set, null); - reg.handleError(new IOException()); - assertThat(ref.get(), notNullValue()); - assertThat(ref.get(), instanceOf(IOException.class)); - } - - @Test - void reportErrorIfNoRegisteredErrorHandler() { - CountingRegistry reg = new CountingRegistry<>(); - reg.handle((data, psb) -> {}, null, null); - reg.handleError(new Exception("Just for test!")); - } - - static class CountingRegistry extends PausableRegistry { - - private final AtomicInteger counter = new AtomicInteger(0); - - @Override - protected void tryProcess() { - counter.incrementAndGet(); - } - } -} diff --git a/common/reactive/src/test/java/io/helidon/common/reactive/valve/PublisherValveTest.java b/common/reactive/src/test/java/io/helidon/common/reactive/valve/PublisherValveTest.java deleted file mode 100644 index 162b864e085..00000000000 --- a/common/reactive/src/test/java/io/helidon/common/reactive/valve/PublisherValveTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import io.helidon.common.reactive.SubmissionPublisher; - -import org.junit.jupiter.api.Test; - -import static io.helidon.common.reactive.valve.TestUtils.generate; -import static io.helidon.common.reactive.valve.TestUtils.generateList; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -class PublisherValveTest { - - @Test - void publish() throws Exception { - SubmissionPublisher pub = new SubmissionPublisher<>(); - PublisherValve valve = new PublisherValve<>(pub); - CompletableFuture> cf = valve.collect(Collectors.toList()).toCompletableFuture(); - generate(0, 10, pub::submit); - pub.close(); - assertThat(cf.get(), is(generateList(0, 10))); - } - - @Test - void twoHandlers() { - List buffer = Collections.synchronizedList(new ArrayList<>(10)); - SubmissionPublisher pub = new SubmissionPublisher<>(); - PublisherValve valve = new PublisherValve<>(pub); - valve.handle((Consumer) buffer::add); - assertThrows(IllegalStateException.class, () -> valve.handle((Consumer) buffer::add)); - } - - @Test - void pauseResume() throws Exception { - List buffer = Collections.synchronizedList(new ArrayList<>(10)); - CountDownLatch latch = new CountDownLatch(1); - CountDownLatch doneLatch = new CountDownLatch(1); - SubmissionPublisher pub = new SubmissionPublisher<>(); - Valve valve = Valves.from(pub); - valve.handle((i, p) -> { - buffer.add(i); - if (i == 5) { - p.pause(); - latch.countDown(); - } - }, null, doneLatch::countDown); - generate(0, 10, pub::submit); - pub.close(); - if (!latch.await(10, TimeUnit.SECONDS)) { - throw new AssertionError("Wait timeout!"); - } - assertThat(buffer, is(generateList(0, 6))); - assertThat(doneLatch.getCount(), is(1L)); - valve.resume(); - if (!doneLatch.await(10, TimeUnit.SECONDS)) { - throw new AssertionError("Wait timeout!"); - } - assertThat(buffer, is(generateList(0, 10))); - } -} diff --git a/common/reactive/src/test/java/io/helidon/common/reactive/valve/TankTest.java b/common/reactive/src/test/java/io/helidon/common/reactive/valve/TankTest.java deleted file mode 100644 index 21ed4d135b7..00000000000 --- a/common/reactive/src/test/java/io/helidon/common/reactive/valve/TankTest.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.ForkJoinTask; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import static io.helidon.common.reactive.valve.TestUtils.generate; -import static io.helidon.common.reactive.valve.TestUtils.generateList; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -class TankTest { - - @Test - void readPrefilled() throws Exception { - Tank tank = new Tank<>(100); - generate(0, 30, tank::add); - CompletableFuture> cf = tank.collect(Collectors.toList()).toCompletableFuture(); - assertThat(cf.isDone(), is(false)); - - tank.close(); - assertThat(cf.get(), is(generateList(0, 30))); - } - - @Test - void diferentTypeOfInsert() throws Exception { - Tank tank = new Tank<>(30); - generate(0, 30, tank::add); - CompletableFuture> cf = tank.collect(Collectors.toList()).toCompletableFuture(); - tank.add(30); - assertThat(tank.offer(31), is(true)); - tank.put(32); - tank.addAll(generateList(33, 40)); - tank.close(); - assertThat(cf.get(), is(generateList(0, 40))); - } - - @Test - void pauseResume1() throws Exception { - Tank tank = new Tank<>(300); - generate(0, 30, tank::add); - CompletableFuture> cf = tank.filter(i -> { - if (i == 10) { - tank.pause(); - } - return true; - }) - .collect(Collectors.toList()) - .toCompletableFuture(); - generate(30, 40, tank::add); - tank.resume(); - generate(40, 50, tank::add); - tank.close(); - assertThat(cf.get(), is(generateList(0, 50))); - } - - @Test - void pauseResume2() throws Exception { - Tank tank = new Tank<>(300); - CompletableFuture> cf = tank.filter(i -> { - if (i == 5) { - tank.pause(); - } - return true; - }) - .collect(Collectors.toList()) - .toCompletableFuture(); - generate(0, 10, tank::add); - tank.resume(); - generate(10, 20, tank::add); - tank.close(); - assertThat(cf.get(), is(generateList(0, 20))); - } - - @Test - void offerToFull() throws Exception { - Tank tank = new Tank<>(10); - generate(0, 10, tank::add); - assertThat(tank.offer(10), is(false)); - ForkJoinTask f = ForkJoinPool.commonPool() - .submit(() -> tank.offer(10, 10, TimeUnit.SECONDS)); - CompletableFuture> cf = tank.collect(Collectors.toList()).toCompletableFuture(); - assertThat(f.get(), is(true)); - assertThat(tank.offer(11), is(true)); - - tank.close(); - assertThat(cf.get(), is(generateList(0, 12))); - } - - @Test - void noInsertAfterClose() throws Exception { - Tank tank = new Tank<>(100); - generate(0, 10, tank::add); - tank.close(); - assertThat(tank.offer(10), is(false)); - assertThrows(IllegalStateException.class, () -> tank.add(11)); - assertThrows(IllegalStateException.class, () -> tank.put(12)); - CompletableFuture> cf = tank.collect(Collectors.toList()).toCompletableFuture(); - assertThat(cf.get(), is(generateList(0, 10))); - } - - @Test - void insertFromDrainHandler() throws Exception { - Tank tank = new Tank<>(100); - CompletableFuture> cf = tank.collect(Collectors.toList()).toCompletableFuture(); - tank.whenDrain(() -> generate(0, 10, tank::add)); - tank.close(); - assertThat(cf.get(), is(generateList(0, 10))); - } - - @Test - void insertFromDrainHandlerToFull() throws Exception { - Tank tank = new Tank<>(10); - generate(0, 10, tank::add); - tank.whenDrain(() -> generate(10, 15, tank::add)); - CompletableFuture> cf = tank.collect(Collectors.toList()).toCompletableFuture(); - tank.close(); - assertThat(cf.get(), is(generateList(0, 15))); - } - -} diff --git a/common/reactive/src/test/java/io/helidon/common/reactive/valve/TestUtils.java b/common/reactive/src/test/java/io/helidon/common/reactive/valve/TestUtils.java deleted file mode 100644 index 0090ba5e268..00000000000 --- a/common/reactive/src/test/java/io/helidon/common/reactive/valve/TestUtils.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -class TestUtils { - - static void generate(int from, int to, Consumer consumer) { - for (int i = from; i < to; i++) { - consumer.accept(i); - } - } - - static List generateList(int from, int to) { - ArrayList result = new ArrayList<>(to - from); - for (int i = from; i < to; i++) { - result.add(i); - } - return result; - } -} diff --git a/common/reactive/src/test/java/io/helidon/common/reactive/valve/UnorderedCollectorSupportTest.java b/common/reactive/src/test/java/io/helidon/common/reactive/valve/UnorderedCollectorSupportTest.java deleted file mode 100644 index 2ad25658afa..00000000000 --- a/common/reactive/src/test/java/io/helidon/common/reactive/valve/UnorderedCollectorSupportTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -/** - * Tests {@link UnorderedCollectorSupport}. - */ -class UnorderedCollectorSupportTest { - - private void fillAndTest(int threadsCount, int valuesPerThread) throws Exception { - UnorderedCollectorSupport> support = new UnorderedCollectorSupport<>(Collectors.toSet()); - CountDownLatch latch = new CountDownLatch(threadsCount); - for (int i = 0; i < threadsCount; i++) { - int base = i * valuesPerThread; - new Thread(() -> { - for (int j = 0; j < valuesPerThread; j++) { - support.add(base + j); - } - latch.countDown(); - }).start(); - } - if (!latch.await(1, TimeUnit.MINUTES)) { - throw new AssertionError("Timeout!"); - } - support.complete(); - Set result = support.getResult() - .toCompletableFuture() - .get(10, TimeUnit.SECONDS); - assertThat(result.size(), is(threadsCount * valuesPerThread)); - } - - @Test - void singleThread() throws Exception { - fillAndTest(1, 100_000); - } - - @Test - void eightThreads() throws Exception { - fillAndTest(8, 400_000); - } - - @Test - void threeHundredsThreads() throws Exception { - fillAndTest(300, 20_000); - } -} diff --git a/common/reactive/src/test/java/io/helidon/common/reactive/valve/ValveIteratorTest.java b/common/reactive/src/test/java/io/helidon/common/reactive/valve/ValveIteratorTest.java deleted file mode 100644 index 0078d0552ba..00000000000 --- a/common/reactive/src/test/java/io/helidon/common/reactive/valve/ValveIteratorTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.NoSuchElementException; -import java.util.concurrent.ForkJoinPool; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -class ValveIteratorTest { - - @Test - void standard() { - Valve valve = Valves.from(ValveTest.LIST_0_100); - assertToIterator(ValveTest.LIST_0_100, valve); - } - - @Test - void async() { - Valve valve = Valves.from(ValveTest.LIST_0_10).executeOn(ForkJoinPool.commonPool()); - assertToIterator(ValveTest.LIST_0_10, valve); - } - - @Test - void failing() { - Valve valve = Valves.from(ValveTest.LIST_0_10) - .peek(i -> { - if (i == 5) { - throw new IllegalStateException("Test exception"); - } - }); - ValveIterator iterator = valve.toIterator(); - int lastValue = -1; - while (iterator.hasNext()) { - lastValue = iterator.next(); - } - assertThat(lastValue, is(4)); - - assertThat(iterator.getThrowable(), notNullValue()); - assertThat(iterator.getThrowable().getMessage(), is("Test exception")); - } - - @Test - void multipleHasNext() { - ValveIterator iterator = Valves.from(ValveTest.LIST_0_5).toIterator(); - while (iterator.hasNext()) { - iterator.next(); - } - assertThat(iterator.hasNext(), is(false)); - assertThat(iterator.hasNext(), is(false)); - } - - @Test - void nextAfterFinished() { - ValveIterator iterator = Valves.from(ValveTest.LIST_0_5).toIterator(); - while (iterator.hasNext()) { - iterator.next(); - } - assertThrows(NoSuchElementException.class, iterator::next); - } - - private void assertToIterator(Collection data, Valve valve) { - Collection result = new ArrayList<>(data.size()); - for (C item : toIterable(valve)) { - result.add(item); - } - assertThat(result, is(data)); - } - - private static Iterable toIterable(Valve valve) { - return valve::toIterator; - } -} diff --git a/common/reactive/src/test/java/io/helidon/common/reactive/valve/ValvePublisherTest.java b/common/reactive/src/test/java/io/helidon/common/reactive/valve/ValvePublisherTest.java deleted file mode 100644 index 7e534dda711..00000000000 --- a/common/reactive/src/test/java/io/helidon/common/reactive/valve/ValvePublisherTest.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Flow.Subscriber; -import java.util.concurrent.Flow.Subscription; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import io.helidon.common.reactive.Collector; -import io.helidon.common.reactive.Multi; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.hasItems; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.fail; - -/** - * The ValvePublisherTest. - */ -class ValvePublisherTest { - - @Test - void simpleTest() throws Exception { - List list = Multi.from(Valves.from(1, 2, 3, 4).toPublisher()) - .collectList() - .get(10, TimeUnit.SECONDS); - - assertThat(list, hasItems(1, 2, 3, 4)); - } - - @Test - void continuous() { - StringBuilder sb = new StringBuilder(); - Tank integerTank = new Tank<>(10); - - Multi.from(integerTank.toPublisher()) - .subscribe(sb::append); - - integerTank.add(1); - integerTank.add(2); - assertThat(sb.toString(), is("12")); - - integerTank.add(3); - integerTank.add(4); - assertThat(sb.toString(), is("1234")); - } - - @Test - void publisher() { - final StringBuilder sb = new StringBuilder(); - Tank integerTank = new Tank<>(10); - - final AtomicReference subscriptionRef = new AtomicReference<>(); - - integerTank.toPublisher().subscribe(new Subscriber() { - @Override - public void onSubscribe(Subscription subscription) { - subscriptionRef.set(subscription); - } - - @Override - public void onNext(Integer item) { - sb.append(item); - } - - @Override - public void onError(Throwable throwable) { - fail("Not expected: " + throwable); - } - - @Override - public void onComplete() { - sb.append("$"); - } - }); - - integerTank.add(1); - integerTank.add(2); - - assertThat(sb.toString(), is("")); - - subscriptionRef.get().request(1); - assertThat(sb.toString(), is("1")); - - subscriptionRef.get().request(2); - integerTank.add(3); - integerTank.add(4); - assertThat(sb.toString(), is("123")); - - integerTank.add(5); - assertThat(sb.toString(), is("123")); - subscriptionRef.get().request(2); - assertThat(sb.toString(), is("12345")); - - // request additional 2 more ahead - subscriptionRef.get().request(2); - assertThat(sb.toString(), is("12345")); - integerTank.add(6); - assertThat(sb.toString(), is("123456")); - integerTank.add(7); - assertThat(sb.toString(), is("1234567")); - - // TODO webserver#22 close itself doesn't complete the subscriber; change the test once the issue is solved - integerTank.close(); - assertThat(sb.toString(), is("1234567")); - subscriptionRef.get().request(1); - assertThat(sb.toString(), is("1234567$")); - } - - @Test - void onNextThrowsException() { - final AtomicReference exception = new AtomicReference<>(); - Tank integerTank = new Tank<>(10); - - final AtomicReference subscriptionRef = new AtomicReference<>(); - - integerTank.toPublisher().subscribe(new Subscriber() { - @Override - public void onSubscribe(Subscription subscription) { - subscriptionRef.set(subscription); - } - - @Override - public void onNext(Integer item) { - throw new RuntimeException("Exception in onNext()"); - } - - @Override - public void onError(Throwable throwable) { - exception.set(throwable); - } - - @Override - public void onComplete() { - fail("onComplete not expected"); - } - }); - - integerTank.add(1); - subscriptionRef.get().request(1); - - assertThat(exception.get().getMessage(), containsString("Valve to Publisher in an error")); - } - - @Test - void multipleSubscribers() throws Exception { - Tank stringTank = new Tank<>(10); - - stringTank.addAll(List.of("1", "2", "3")); - stringTank.close(); - - Multi multi = Multi.from(stringTank.toPublisher()); - assertThat(multi.collect(new StringCollector<>()).get(10, TimeUnit.SECONDS), is("123")); - - try { - multi.collect(new StringCollector<>()).get(10, TimeUnit.SECONDS); - fail("Should have thrown an exception!"); - } catch (ExecutionException e) { - assertThat(e.getCause(), is(notNullValue())); - assertThat(e.getCause(), is(instanceOf(IllegalStateException.class))); - assertThat(e.getCause().getMessage(), containsString("Multiple subscribers aren't allowed")); - } - } - - @Test - void multiplePublishers() throws Exception { - Tank stringTank = new Tank<>(10); - - stringTank.addAll(List.of("1", "2", "3")); - stringTank.close(); - - assertThat(Multi.from(stringTank.toPublisher()).collect(new StringCollector<>()).get(10, TimeUnit.SECONDS), is("123")); - - try { - Multi.from(stringTank.toPublisher()).collect(new StringCollector<>()).get(10, TimeUnit.SECONDS); - fail("Should have thrown an exception!"); - } catch (ExecutionException e) { - assertThat(e.getCause(), is(notNullValue())); - assertThat(e.getCause(), is(instanceOf(IllegalStateException.class))); - assertThat(e.getCause().getMessage(), containsString("Handler is already registered")); - } - } - - private static final class StringCollector implements Collector { - - private final StringBuilder sb; - - StringCollector() { - this.sb = new StringBuilder(); - } - - @Override - public String value() { - return sb.toString(); - } - - @Override - public void collect(T item) { - sb.append(item.toString()); - } - } -} diff --git a/common/reactive/src/test/java/io/helidon/common/reactive/valve/ValveTest.java b/common/reactive/src/test/java/io/helidon/common/reactive/valve/ValveTest.java deleted file mode 100644 index 27ba1d47b5c..00000000000 --- a/common/reactive/src/test/java/io/helidon/common/reactive/valve/ValveTest.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; - -class ValveTest { - - static final List LIST_0_100 = new ArrayList<>(100); - static final List LIST_0_10 = new ArrayList<>(10); - static final List LIST_0_5 = new ArrayList<>(5); - static { - for (int i = 0; i < 100; i++) { - LIST_0_100.add(i); - } - for (int i = 0; i < 10; i++) { - LIST_0_10.add(i); - } - for (int i = 0; i < 5; i++) { - LIST_0_5.add(i); - } - } - - @Test - void testHandle() { - List buffer = Collections.synchronizedList(new ArrayList<>(10)); - AtomicReference thrRef = new AtomicReference<>(); - AtomicReference resultRef = new AtomicReference<>(false); - // --- Basic consumer - // Simple - Valves.from(LIST_0_10).handle((Consumer) buffer::add); - assertThat(buffer, is(LIST_0_10)); - buffer.clear(); - // Two Params - Valves.from(LIST_0_10).handle((Consumer) buffer::add, thrRef::set); - assertThat(buffer, is(LIST_0_10)); - assertThat(thrRef.get(), nullValue()); - buffer.clear(); - // Three Params - Valves.from(LIST_0_10).handle((Consumer) buffer::add, thrRef::set, () -> resultRef.set(true)); - assertThat(buffer, is(LIST_0_10)); - assertThat(thrRef.get(), nullValue()); - assertThat(resultRef.get(), is(true)); - buffer.clear(); - resultRef.set(false); - - // --- Pausable consumer - // Simple - Valve valve = Valves.from(LIST_0_10); - valve.handle((i, p) -> { - if (i == 4) { - p.pause(); - } - buffer.add(i); - }); - assertThat(buffer, is(LIST_0_5)); - valve.resume(); - assertThat(buffer, is(LIST_0_10)); - buffer.clear(); - // Two Params - valve = Valves.from(LIST_0_10); - valve.handle((i, p) -> { - if (i == 4) { - p.pause(); - } - buffer.add(i); - }, thrRef::set); - assertThat(buffer, is(LIST_0_5)); - valve.resume(); - assertThat(buffer, is(LIST_0_10)); - assertThat(thrRef.get(), nullValue()); - buffer.clear(); - // Three Params - valve = Valves.from(LIST_0_10); - valve.handle((i, p) -> { - if (i == 4) { - p.pause(); - } - buffer.add(i); - }, thrRef::set, () -> resultRef.set(true)); - assertThat(buffer, is(LIST_0_5)); - valve.resume(); - assertThat(buffer, is(LIST_0_10)); - assertThat(thrRef.get(), nullValue()); - assertThat(resultRef.get(), is(true)); - buffer.clear(); - resultRef.set(false); - } - - @Test - void filter() throws Exception { - List result = Valves.from(LIST_0_10) - .filter(i -> i < 5) - .collect(Collectors.toList()) - .toCompletableFuture() - .get(); - - assertThat(result, is(LIST_0_5)); - } - - @Test - void map() throws Exception { - String result = Valves.from(LIST_0_10) - .map(String::valueOf) - .collect(Collectors.joining()) - .toCompletableFuture() - .get(); - - String expected = LIST_0_10.stream() - .map(String::valueOf) - .collect(Collectors.joining()); - - assertThat(result, is(expected)); - } - - @Test - void peek() throws Exception { - List buffer = Collections.synchronizedList(new ArrayList<>(10)); - List result = Valves.from(LIST_0_10) - .peek(buffer::add) - .collect(Collectors.toList()) - .toCompletableFuture() - .get(); - assertThat(buffer, is(LIST_0_10)); - assertThat(result, is(LIST_0_10)); - } - - @Test - void onExecutorService() throws Exception { - Set threadNames = Collections.synchronizedSet(new TreeSet<>()); - threadNames.add(Thread.currentThread().getName()); - List result = Valves.from(LIST_0_100) - .executeOn(ForkJoinPool.commonPool()) - .peek(i -> threadNames.add(Thread.currentThread().getName())) - .collect(Collectors.toList()) - .toCompletableFuture() - .get(); - - assertThat(result, is(LIST_0_100)); - assertThat(threadNames.size(), greaterThan(1)); - } -} diff --git a/common/reactive/src/test/java/io/helidon/common/reactive/valve/ValvesTest.java b/common/reactive/src/test/java/io/helidon/common/reactive/valve/ValvesTest.java deleted file mode 100644 index 437407283a9..00000000000 --- a/common/reactive/src/test/java/io/helidon/common/reactive/valve/ValvesTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. - * - * Licensed 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 io.helidon.common.reactive.valve; - -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -class ValvesTest { - - @Test - void fromIterable() throws Exception { - List list = List.of("a", "b", "c", "d", "e", "f", "g"); - String s = Valves.from(list).collect(Collectors.joining()).toCompletableFuture().get(); - assertThat(s, is("abcdefg")); - } - - @Test - void fromNullIterable() throws Exception { - String s = Valves.from((Iterable) null).collect(Collectors.joining()).toCompletableFuture().get(); - assertThat(s, is("")); - } - - @Test - void fromArray() throws Exception { - String[] array = {"a", "b", "c", "d", "e", "f", "g"}; - String s = Valves.from(array).collect(Collectors.joining()).toCompletableFuture().get(); - assertThat(s, is("abcdefg")); - } - - @Test - void fromNullArray() throws Exception { - String[] array = null; - String s = Valves.from(array).collect(Collectors.joining()).toCompletableFuture().get(); - assertThat(s, is("")); - } - - @Test - void empty() throws Exception { - Valve valve = Valves.empty(); - String s = valve.collect(Collectors.joining()).toCompletableFuture().get(); - assertThat(s, is("")); - } -} diff --git a/messaging/kafka/pom.xml b/messaging/kafka/pom.xml new file mode 100644 index 00000000000..c35e50653f7 --- /dev/null +++ b/messaging/kafka/pom.xml @@ -0,0 +1,175 @@ + + + + + + 4.0.0 + + + io.helidon.messaging + helidon-messaging-project + 1.3.2-SNAPSHOT + + + io.helidon.messaging.connectors + kafka-connector + 1.3.2-SNAPSHOT + jar + Helidon Kafka Connector + + + 8 + 8 + UTF-8 + + + + + javax.enterprise + cdi-api + provided + + + + javax.activation + javax.activation-api + provided + + + + org.eclipse.microprofile.reactive.messaging + microprofile-reactive-messaging-api + 1.0 + + + + io.helidon.microprofile.config + helidon-microprofile-config + + + io.helidon.microprofile.server + helidon-microprofile-server + + + io.helidon.microprofile + helidon-microprofile-reactive-streams + 1.3.2-SNAPSHOT + + + io.helidon.microprofile + helidon-microprofile-messaging + 1.3.2-SNAPSHOT + + + org.jboss.weld.se + weld-se-core + + + org.slf4j + slf4j-api + + + + org.slf4j + slf4j-jdk14 + + + io.helidon.microprofile.bundles + internal-test-libs + test + + + org.jboss.weld + weld-junit5 + test + + + + org.slf4j + slf4j-jdk14 + 1.7.28 + test + + + com.salesforce.kafka.test + kafka-junit5 + 3.1.1 + test + + + org.apache.kafka + kafka-clients + 2.3.0 + + + org.apache.kafka + kafka_2.11 + 2.3.0 + + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${version.plugin.checkstyle} + + ${project.build.sourceDirectory} + + + + com.puppycrawl.tools + checkstyle + ${version.lib.checkstyle} + + + com.sun + tools + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + java.util.logging.config.file + src/test/resources/logging.properties + + + + + + + + diff --git a/messaging/kafka/src/main/java/io/helidon/messaging/kafka/InvalidKafkaConsumerState.java b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/InvalidKafkaConsumerState.java new file mode 100644 index 00000000000..702d8311cdb --- /dev/null +++ b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/InvalidKafkaConsumerState.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.messaging.kafka; + +/** + * Runtime exception for Consumer validation errors. + */ +class InvalidKafkaConsumerState extends RuntimeException { + + /** + * Creates {@link io.helidon.messaging.kafka.InvalidKafkaConsumerState} runtime exception. + * + * @param message the message + */ + InvalidKafkaConsumerState(String message) { + super(message); + } +} diff --git a/messaging/kafka/src/main/java/io/helidon/messaging/kafka/KafkaConfigProperties.java b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/KafkaConfigProperties.java new file mode 100644 index 00000000000..409c8ef4a6a --- /dev/null +++ b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/KafkaConfigProperties.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.messaging.kafka; + +import java.util.Arrays; +import java.util.List; +import java.util.Properties; +import java.util.stream.Collectors; + +import io.helidon.config.Config; + +/** + * Prepare Kafka properties from Helidon {@link io.helidon.config.Config Config}. + * Configuration format as specified in the MicroProfile Reactive Messaging + * Specification https://github.com/eclipse/microprofile-reactive-messaging + * + *

+ * See example with YAML configuration: + *

{@code
+ * mp.messaging:
+ *   incoming:
+ *     test-channel:
+ *       bootstrap.servers: localhost:9092
+ *       topic: graph-done
+ *       key.deserializer: org.apache.kafka.common.serialization.LongDeserializer
+ *       value.deserializer: org.apache.kafka.common.serialization.StringDeserializer
+ *
+ *   outgoing:
+ *     test-channel:
+ *       bootstrap.servers: localhost:9092
+ *       topic: graph-done
+ *       key.serializer: org.apache.kafka.common.serialization.LongSerializer
+ *       value.serializer: org.apache.kafka.common.serialization.StringSerializer
+ *
+ * }
+ *

+ * + * @see io.helidon.config.Config + */ +class KafkaConfigProperties extends Properties { + + /** + * Topic or topics delimited by commas. + */ + static final String TOPIC_NAME = "topic"; + + /** + * Consumer group id. + */ + static final String GROUP_ID = "group.id"; + + /** + * Prepare Kafka properties from Helidon {@link io.helidon.config.Config Config}, + * underscores in keys are translated to dots. + * + * @param config parent config of kafka key + */ + KafkaConfigProperties(Config config) { + config.asNodeList().get().forEach(this::addProperty); + } + + /** + * Split comma separated topic names. + * + * @return list of topic names + */ + public List getTopicNameList() { + return Arrays.stream(getProperty(TOPIC_NAME) + .split(",")) + .map(String::trim) + .collect(Collectors.toList()); + } + + private void addProperty(Config c) { + String key = c.traverse().map(m -> m.key().parent().name() + "." + m.key().name()) + .collect(Collectors.joining(".")); + if (key.isEmpty()) { + key = c.key().name(); + } + String value; + if (c.hasValue()) { + value = c.asString().get(); + } else { + value = c.traverse(v -> v.type() == Config.Type.VALUE) + .map(Config::asString) + .map(v -> v.orElse("")) + .findFirst() + .orElse(""); + } + this.setProperty(key, value); + } +} diff --git a/messaging/kafka/src/main/java/io/helidon/messaging/kafka/PartitionsAssignedLatch.java b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/PartitionsAssignedLatch.java new file mode 100644 index 00000000000..937f2f9c2e5 --- /dev/null +++ b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/PartitionsAssignedLatch.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.messaging.kafka; + +import java.util.Collection; +import java.util.concurrent.CountDownLatch; + +import org.apache.kafka.clients.consumer.ConsumerRebalanceListener; +import org.apache.kafka.common.TopicPartition; + +/** + * Waiting latch for partition assigment, after that is consumer ready to receive. + */ +public class PartitionsAssignedLatch extends CountDownLatch implements ConsumerRebalanceListener { + + PartitionsAssignedLatch() { + super(1); + } + + @Override + public void onPartitionsRevoked(Collection partitions) { + // Do nothing + } + + @Override + public void onPartitionsAssigned(Collection partitions) { + this.countDown(); + } +} diff --git a/messaging/kafka/src/main/java/io/helidon/messaging/kafka/SimpleKafkaConsumer.java b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/SimpleKafkaConsumer.java new file mode 100644 index 00000000000..334786102f8 --- /dev/null +++ b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/SimpleKafkaConsumer.java @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.messaging.kafka; + +import java.io.Closeable; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; + +import io.helidon.common.context.Context; +import io.helidon.common.context.Contexts; +import io.helidon.config.Config; +import io.helidon.messaging.kafka.connector.KafkaMessage; +import io.helidon.messaging.kafka.connector.SimplePublisher; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.apache.kafka.common.errors.WakeupException; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Subscription; + +/** + * Simple Kafka consumer covering basic use-cases. + * Configurable by Helidon {@link io.helidon.config.Config Config}, + * For more info about configuration see {@link KafkaConfigProperties} + *

+ * Usage: + *

{@code
+ *   try (SimpleKafkaConsumer c = new SimpleKafkaConsumer<>("test-channel", Config.create())) {
+ *         c.consumeAsync(r -> System.out.println(r.value()));
+ *   }
+ * }
+ * + * @param Key type + * @param Value type + * @see KafkaConfigProperties + * @see io.helidon.config.Config + */ +public class SimpleKafkaConsumer implements Closeable { + + private static final Logger LOGGER = Logger.getLogger(SimpleKafkaConsumer.class.getName()); + private final KafkaConfigProperties properties; + + private AtomicBoolean closed = new AtomicBoolean(false); + private PartitionsAssignedLatch partitionsAssignedLatch = new PartitionsAssignedLatch(); + private String consumerId; + private ExecutorService executorService; + private ExecutorService externalExecutorService; + private List topicNameList; + private KafkaConsumer consumer; + + private final LinkedList> backPressureBuffer = new LinkedList<>(); + private ArrayList> ackFutures = new ArrayList<>(); + + /** + * Kafka consumer created from {@link io.helidon.config.Config config} + * see configuration {@link KafkaConfigProperties example}. + * + * @param channelName key in configuration + * @param config Helidon {@link io.helidon.config.Config config} + * @see KafkaConfigProperties + * @see io.helidon.config.Config + */ + public SimpleKafkaConsumer(String channelName, Config config) { + this(channelName, config, null); + } + + /** + * Kafka consumer created from {@link io.helidon.config.Config config} + * see configuration {@link KafkaConfigProperties example}. + * + * @param channelName key in configuration + * @param config Helidon {@link io.helidon.config.Config config} + * @param consumerGroupId Custom group.id, can be null, overrides group.id from configuration + * @see KafkaConfigProperties + * @see io.helidon.config.Config + */ + public SimpleKafkaConsumer(String channelName, Config config, String consumerGroupId) { + properties = new KafkaConfigProperties(config.get("mp.messaging.incoming").get(channelName)); + properties.setProperty(KafkaConfigProperties.GROUP_ID, getOrGenerateGroupId(consumerGroupId)); + this.topicNameList = properties.getTopicNameList(); + this.consumerId = channelName; + consumer = new KafkaConsumer<>(properties); + } + + /** + * Kafka consumer created from {@link io.helidon.config.Config config} + * see configuration {@link KafkaConfigProperties example}. + * + * @param config Helidon {@link io.helidon.config.Config config} + */ + public SimpleKafkaConsumer(Config config) { + properties = new KafkaConfigProperties(config); + properties.setProperty(KafkaConfigProperties.GROUP_ID, getOrGenerateGroupId(null)); + this.topicNameList = properties.getTopicNameList(); + this.consumerId = null; + consumer = new KafkaConsumer<>(properties); + } + + /** + * Execute supplied consumer for each received record. + * + * @param function to be executed for each received record + * @return {@link java.util.concurrent.Future} + */ + public Future consumeAsync(Consumer> function) { + return this.consumeAsync(Executors.newWorkStealingPool(), null, function); + } + + /** + * Execute supplied consumer by provided executor service for each received record. + * + * @param executorService Custom executor service used for spinning up polling thread and record consuming threads + * @param customTopics Can be null, list of topics appended to the list from configuration + * @param function Consumer method executed in new thread for each received record + * @return The Future's get method will return null when consumer is closed + */ + public Future consumeAsync(ExecutorService executorService, List customTopics, + Consumer> function) { + LOGGER.info(String.format("Initiating kafka consumer %s listening to topics: %s with groupId: %s", + consumerId, topicNameList, properties.getProperty(KafkaConfigProperties.GROUP_ID))); + + List mergedTopics = new ArrayList<>(); + mergedTopics.addAll(properties.getTopicNameList()); + mergedTopics.addAll(Optional.ofNullable(customTopics).orElse(Collections.emptyList())); + + if (mergedTopics.isEmpty()) { + throw new InvalidKafkaConsumerState("No topic names provided in configuration or by parameter."); + } + + validateConsumer(); + this.executorService = executorService; + return executorService.submit(() -> { + consumer.subscribe(mergedTopics, partitionsAssignedLatch); + try { + while (!closed.get()) { + ConsumerRecords consumerRecords = consumer.poll(Duration.ofSeconds(5)); + consumerRecords.forEach(cr -> executorService.execute(() -> function.accept(cr))); + } + } catch (WakeupException ex) { + if (!closed.get()) { + throw ex; + } + } finally { + LOGGER.info("Closing consumer" + consumerId); + consumer.close(); + } + }); + } + + /** + * Create publisher builder. + * + * @param executorService {@link java.util.concurrent.ExecutorService} + * @return {@link org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder} + */ + public PublisherBuilder> createPushPublisherBuilder(ExecutorService executorService) { + validateConsumer(); + this.externalExecutorService = executorService; + return ReactiveStreams.fromPublisher(new SimplePublisher(subscriber -> { + subscriber.onSubscribe(new Subscription() { + @Override + public void request(long n) { + LOGGER.log(Level.FINE, "Pushing Kafka consumer doesn't support requests."); + } + + @Override + public void cancel() { + SimpleKafkaConsumer.this.close(); + } + }); + externalExecutorService.submit(() -> { + consumer.subscribe(topicNameList, partitionsAssignedLatch); + try { + while (!closed.get()) { + synchronized (backPressureBuffer) { + waitForAcksAndPoll(); + if (backPressureBuffer.isEmpty()) continue; + ConsumerRecord cr = backPressureBuffer.poll(); + KafkaMessage kafkaMessage = new KafkaMessage<>(cr); + ackFutures.add(kafkaMessage.getAckFuture()); + runInNewContext(() -> subscriber.onNext(kafkaMessage)); + } + } + } catch (WakeupException ex) { + if (!closed.get()) { + throw ex; + } + } finally { + LOGGER.info("Closing consumer" + consumerId); + consumer.close(); + } + }); + })); + } + + /** + * Naive impl of back pressure wise lazy poll. + * Wait for the last batch of records to be acknowledged before commit and another poll. + */ + private void waitForAcksAndPoll() { + if (backPressureBuffer.isEmpty()) { + try { + if (!ackFutures.isEmpty()) { + CompletableFuture.allOf(ackFutures.toArray(new CompletableFuture[0])).get(); + consumer.commitSync(); + } + consumer.poll(Duration.ofSeconds(1)).forEach(backPressureBuffer::add); + } catch (InterruptedException | ExecutionException e) { + LOGGER.log(Level.SEVERE, "Error when waiting for all polled records acknowledgements.", e); + } + + } + } + + private void validateConsumer() { + if (this.closed.get()) { + throw new InvalidKafkaConsumerState("Invalid consumer state, already closed"); + } + if (this.executorService != null) { + throw new InvalidKafkaConsumerState("Invalid consumer state, already consuming"); + } + } + + /** + * Blocks current thread until partitions are assigned, + * since when is consumer effectively ready to receive. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the timeout argument + * @throws java.lang.InterruptedException if the current thread is interrupted while waiting + * @throws java.util.concurrent.TimeoutException if the timeout is reached + */ + public void waitForPartitionAssigment(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { + if (!partitionsAssignedLatch.await(timeout, unit)) { + throw new TimeoutException("Timeout for subscription reached"); + } + } + + /** + * Close consumer gracefully. Stops polling loop, + * wakes possible blocked poll and shuts down executor service. + */ + @Override + public void close() { + this.closed.set(true); + this.consumer.wakeup(); + Optional.ofNullable(this.executorService).ifPresent(ExecutorService::shutdown); + } + + /** + * Use supplied customGroupId if not null + * or take it from configuration if exist + * or generate random in this order. + * + * @param customGroupId custom group.id, overrides group.id from configuration + * @return returns or generate new groupId + */ + protected String getOrGenerateGroupId(String customGroupId) { + return Optional.ofNullable(customGroupId) + .orElse(Optional.ofNullable(properties.getProperty(KafkaConfigProperties.GROUP_ID)) + .orElse(UUID.randomUUID().toString())); + } + + //Move to messaging incoming connector + private void runInNewContext(Runnable runnable) { + Context parentContext = Context.create(); + Context context = Context + .builder() + .parent(parentContext) + .id(String.format("%s:message-%s", parentContext.id(), UUID.randomUUID().toString())) + .build(); + Contexts.runInContext(context, runnable); + } + +} diff --git a/messaging/kafka/src/main/java/io/helidon/messaging/kafka/SimpleKafkaProducer.java b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/SimpleKafkaProducer.java new file mode 100644 index 00000000000..1f345697974 --- /dev/null +++ b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/SimpleKafkaProducer.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.messaging.kafka; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.logging.Logger; + +import io.helidon.config.Config; + +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.clients.producer.RecordMetadata; +import org.apache.kafka.common.header.Header; + +/** + * Simple Kafka producer covering basic use-cases. + * Configurable by Helidon {@link io.helidon.config.Config Config}, + * For more info about configuration see {@link KafkaConfigProperties}. + *

+ * Usage: + *

{@code new SimpleKafkaProducer("job-done-producer", Config.create())
+ *             .produce("Hello world!");
+ * }
+ * + * @param Key type + * @param Value type + * @see KafkaConfigProperties + * @see io.helidon.config.Config + */ +public class SimpleKafkaProducer implements Closeable { + + private static final Logger LOGGER = Logger.getLogger(SimpleKafkaProducer.class.getName()); + private final KafkaConfigProperties properties; + + private KafkaProducer producer; + + /** + * Kafka producer created from {@link io.helidon.config.Config config} under kafka-producerId, + * see configuration {@link KafkaConfigProperties example}. + * + * @param producerId key in configuration + * @param config Helidon {@link io.helidon.config.Config config} + * @see KafkaConfigProperties + * @see io.helidon.config.Config + */ + public SimpleKafkaProducer(String producerId, Config config) { + properties = new KafkaConfigProperties(config.get("mp.messaging.outgoing").get(producerId)); + producer = new KafkaProducer<>(properties); + } + + /** + * Kafka producer created from {@link io.helidon.config.Config config} under kafka-producerId, + * see configuration {@link KafkaConfigProperties example}. + * + * @param config Helidon {@link io.helidon.config.Config config} + */ + public SimpleKafkaProducer(Config config) { + properties = new KafkaConfigProperties(config); + producer = new KafkaProducer<>(properties); + } + + /** + * Send record to all provided topics, + * blocking until all records are acknowledged by broker. + * + * @param value Will be serialized by value.serializer class + * defined in {@link KafkaConfigProperties configuration} + * @return Server acknowledged metadata about sent topics + */ + public List produce(V value) { + List> futureRecords = + this.produceAsync(null, null, null, null, value, null); + List metadataList = new ArrayList<>(futureRecords.size()); + + for (Future future : futureRecords) { + try { + metadataList.add(future.get()); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException("Failed to send topic", e); + } + } + return metadataList; + } + + /** + * Produce asynchronously. + * + * @param value value to be produced + * @return list of futures + */ + public List> produceAsync(V value) { + return this.produceAsync(null, null, null, null, value, null); + } + + /** + * Send record to all provided topics, don't wait for server acknowledgement. + * + * @param customTopics Can be null, list of topics appended to the list from configuration, + * record will be sent to all topics iteratively + * @param partition Can be null, if key is also null topic is sent to random partition + * @param timestamp Can be null System.currentTimeMillis() is used then + * @param key Can be null, if not, topics are grouped to partitions by key + * @param value Will be serialized by value.serializer class defined in configuration + * @param headers Can be null, custom headers for additional meta information if needed + * @return Futures of server acknowledged metadata about sent topics + */ + public List> produceAsync(List customTopics, + Integer partition, + Long timestamp, + K key, + V value, + Iterable
headers) { + + List mergedTopics = new ArrayList<>(); + mergedTopics.addAll(properties.getTopicNameList()); + mergedTopics.addAll(Optional.ofNullable(customTopics).orElse(Collections.emptyList())); + + if (mergedTopics.isEmpty()) { + LOGGER.warning("No topic names provided in configuration or by parameter. Nothing sent."); + return Collections.emptyList(); + } + + List> recordMetadataFutures = new ArrayList<>(mergedTopics.size()); + + for (String topic : mergedTopics) { + ProducerRecord record = new ProducerRecord<>(topic, partition, timestamp, key, value, headers); + LOGGER.fine(String.format("Sending topic: %s to partition %d", topic, partition)); + recordMetadataFutures.add(producer.send(record)); + } + return recordMetadataFutures; + } + + @Override + public void close() { + producer.close(); + } +} diff --git a/messaging/kafka/src/main/java/io/helidon/messaging/kafka/connector/KafkaConnectorFactory.java b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/connector/KafkaConnectorFactory.java new file mode 100644 index 00000000000..7b54d365806 --- /dev/null +++ b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/connector/KafkaConnectorFactory.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.messaging.kafka.connector; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.BeforeDestroyed; +import javax.enterprise.event.Observes; + +import io.helidon.common.configurable.ThreadPoolSupplier; +import io.helidon.config.Config; +import io.helidon.messaging.kafka.SimpleKafkaConsumer; +import io.helidon.microprofile.config.MpConfig; + +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.spi.Connector; +import org.eclipse.microprofile.reactive.messaging.spi.IncomingConnectorFactory; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; + +/** + * Partial implementation of Connector as described in the MicroProfile Reactive Messaging Specification. + */ +@ApplicationScoped +@Connector(KafkaConnectorFactory.CONNECTOR_NAME) +public class KafkaConnectorFactory implements IncomingConnectorFactory { + + /** + * Microprofile messaging Kafka connector name. + */ + public static final String CONNECTOR_NAME = "helidon-kafka"; + + private List> consumers = new CopyOnWriteArrayList<>(); + private ThreadPoolSupplier threadPoolSupplier = null; + + /** + * Called when container is terminated. + * + * @param event termination event + */ + public void terminate(@Observes @BeforeDestroyed(ApplicationScoped.class) Object event) { + consumers.forEach(SimpleKafkaConsumer::close); + } + + public List> getConsumers() { + return consumers; + } + + @Override + public PublisherBuilder> getPublisherBuilder(org.eclipse.microprofile.config.Config config) { + Config helidonConfig = ((MpConfig) config).helidonConfig(); + SimpleKafkaConsumer simpleKafkaConsumer = new SimpleKafkaConsumer<>(helidonConfig); + consumers.add(simpleKafkaConsumer); + return simpleKafkaConsumer.createPushPublisherBuilder(getThreadPoolSupplier(helidonConfig).get()); + } + + private ThreadPoolSupplier getThreadPoolSupplier(Config config) { + synchronized (this) { + if (this.threadPoolSupplier != null) { + return this.threadPoolSupplier; + } + this.threadPoolSupplier = ThreadPoolSupplier.create(config.get("executor-service")); + return threadPoolSupplier; + } + } +} diff --git a/messaging/kafka/src/main/java/io/helidon/messaging/kafka/connector/KafkaMessage.java b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/connector/KafkaMessage.java new file mode 100644 index 00000000000..c0da9843c1e --- /dev/null +++ b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/connector/KafkaMessage.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.messaging.kafka.connector; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.eclipse.microprofile.reactive.messaging.Message; + +/** + * Kafka specific MP messaging message. + * + * @param kafka record key type + * @param kafka record value type + */ +public class KafkaMessage implements Message> { + + private ConsumerRecord consumerRecord; + private CompletableFuture ackFuture = new CompletableFuture<>(); + + /** + * Kafka specific MP messaging message. + * + * @param consumerRecord {@link org.apache.kafka.clients.consumer.ConsumerRecord} + */ + public KafkaMessage(ConsumerRecord consumerRecord) { + this.consumerRecord = consumerRecord; + } + + @Override + public ConsumerRecord getPayload() { + return consumerRecord; + } + + public CompletableFuture getAckFuture() { + return ackFuture; + } + + @Override + public CompletionStage ack() { + ackFuture.complete(null); + return ackFuture; + } + + @Override + @SuppressWarnings("unchecked") + public C unwrap(Class unwrapType) { + if (consumerRecord.getClass().isAssignableFrom(unwrapType)) { + return (C) consumerRecord; + } else { + throw new IllegalArgumentException("Can't unwrap to " + unwrapType.getName()); + } + } +} diff --git a/messaging/kafka/src/main/java/io/helidon/messaging/kafka/connector/SimplePublisher.java b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/connector/SimplePublisher.java new file mode 100644 index 00000000000..8e442f710cc --- /dev/null +++ b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/connector/SimplePublisher.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.messaging.kafka.connector; + +import java.util.function.Consumer; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +/** + * Reactive streams publisher using {@link java.util.function.Consumer} instead of reactive streams. + * + * @param kafka record key type + * @param kafka record value type + */ +public class SimplePublisher implements Publisher> { + + private Consumer>> publisher; + + /** + * Create new Reactive Streams publisher using {@link java.util.function.Consumer} instead of reactive streams. + * + * @param publisher {@link java.util.function.Consumer} + */ + public SimplePublisher(Consumer>> publisher) { + this.publisher = publisher; + } + + @Override + public void subscribe(Subscriber> s) { + publisher.accept(s); + } +} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/package-info.java b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/connector/package-info.java similarity index 61% rename from common/reactive/src/main/java/io/helidon/common/reactive/valve/package-info.java rename to messaging/kafka/src/main/java/io/helidon/messaging/kafka/connector/package-info.java index fd4f83f1840..4eca2c95b5d 100644 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/package-info.java +++ b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/connector/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -12,14 +12,10 @@ * 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. + * */ /** - * Reactive utilities for Helidon projects. - * - * @see io.helidon.common.reactive.valve.Valve - * @see io.helidon.common.reactive.valve.Valves - * @see io.helidon.common.reactive.valve.InputStreamValve - * @see io.helidon.common.reactive.valve.Tank + * Microprofile messaging Kafka connector. */ -package io.helidon.common.reactive.valve; +package io.helidon.messaging.kafka.connector; diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/Pausable.java b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/package-info.java similarity index 58% rename from common/reactive/src/main/java/io/helidon/common/reactive/valve/Pausable.java rename to messaging/kafka/src/main/java/io/helidon/messaging/kafka/package-info.java index dc2cbd249d7..bf2804f1e62 100644 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/Pausable.java +++ b/messaging/kafka/src/main/java/io/helidon/messaging/kafka/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -12,22 +12,10 @@ * 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 io.helidon.common.reactive.valve; - /** - * A simple {@link #pause() pause} / {@link #resume() resume} interface. + * Kafka connector. */ -public interface Pausable { - - /** - * Pause data chunks flow until {@link #resume()}. - */ - void pause(); - - /** - * Resume data chunks flow after {@link #pause()}. - */ - void resume(); -} +package io.helidon.messaging.kafka; diff --git a/messaging/kafka/src/test/java/io/helidon/messaging/kafka/SimpleKafkaTest.java b/messaging/kafka/src/test/java/io/helidon/messaging/kafka/SimpleKafkaTest.java new file mode 100644 index 00000000000..279285163ba --- /dev/null +++ b/messaging/kafka/src/test/java/io/helidon/messaging/kafka/SimpleKafkaTest.java @@ -0,0 +1,178 @@ + +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.messaging.kafka; + +import com.salesforce.kafka.test.junit5.SharedKafkaTestResource; +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import org.apache.kafka.clients.producer.RecordMetadata; +import org.apache.kafka.common.serialization.LongDeserializer; +import org.apache.kafka.common.serialization.LongSerializer; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + + +public class SimpleKafkaTest { + + public static final String TEST_PRODUCER = "test-producer"; + public static final String TEST_CONSUMER_1 = "test-consumer-1"; + public static final String TEST_CONSUMER_2 = "test-consumer-2"; + public static final String TEST_MESSAGE = "this is a test message"; + + @RegisterExtension + public static final SharedKafkaTestResource kafkaResource = new SharedKafkaTestResource(); + public static final String TEST_TOPIC = "graph-done"; + + @BeforeAll + static void setUp() { + kafkaResource.getKafkaTestUtils().createTopic(TEST_TOPIC, 10, (short) 1); + } + + @Test + public void sendAndReceive() throws ExecutionException, InterruptedException, TimeoutException { + Properties p = new Properties(); + p.setProperty("mp.messaging.outgoing." + TEST_PRODUCER + ".bootstrap.servers", kafkaResource.getKafkaConnectString()); + p.setProperty("mp.messaging.outgoing." + TEST_PRODUCER + ".topic", TEST_TOPIC); + p.setProperty("mp.messaging.outgoing." + TEST_PRODUCER + ".key.serializer", LongSerializer.class.getName()); + p.setProperty("mp.messaging.outgoing." + TEST_PRODUCER + ".value.serializer", StringSerializer.class.getName()); + p.setProperty("mp.messaging.incoming." + TEST_CONSUMER_1 + ".bootstrap.servers", kafkaResource.getKafkaConnectString()); + p.setProperty("mp.messaging.incoming." + TEST_CONSUMER_1 + ".topic", TEST_TOPIC); + p.setProperty("mp.messaging.incoming." + TEST_CONSUMER_1 + ".key.deserializer", LongDeserializer.class.getName()); + p.setProperty("mp.messaging.incoming." + TEST_CONSUMER_1 + ".value.deserializer", StringDeserializer.class.getName()); + Config config = Config.builder() + .sources(ConfigSources.create(p)) + .build(); + + // Consumer + SimpleKafkaConsumer consumer = new SimpleKafkaConsumer<>(TEST_CONSUMER_1, config); + Future consumerClosedFuture = consumer.consumeAsync(r -> { + assertEquals(TEST_MESSAGE, r.value()); + consumer.close(); + }); + + consumer.waitForPartitionAssigment(10, TimeUnit.SECONDS); + + // Producer + SimpleKafkaProducer producer = new SimpleKafkaProducer<>(TEST_PRODUCER, config); + producer.produceAsync(TEST_MESSAGE); + + try { + consumerClosedFuture.get(10, TimeUnit.SECONDS); + producer.close(); + } catch (TimeoutException e) { + fail("Didn't receive test message in time"); + } + } + + @Test + public void queueBySameConsumerGroup() throws ExecutionException, InterruptedException, TimeoutException { + final String TEST_GROUP = "XXX"; + + Properties p = new Properties(); + p.setProperty("mp.messaging.outgoing." + TEST_PRODUCER + ".bootstrap.servers", kafkaResource.getKafkaConnectString()); + p.setProperty("mp.messaging.outgoing." + TEST_PRODUCER + ".topic", TEST_TOPIC); + p.setProperty("mp.messaging.outgoing." + TEST_PRODUCER + ".key.serializer", LongSerializer.class.getName()); + p.setProperty("mp.messaging.outgoing." + TEST_PRODUCER + ".value.serializer", StringSerializer.class.getName()); + p.setProperty("mp.messaging.incoming." + TEST_CONSUMER_2 + ".bootstrap.servers", kafkaResource.getKafkaConnectString()); + p.setProperty("mp.messaging.incoming." + TEST_CONSUMER_2 + ".topic", TEST_TOPIC); + p.setProperty("mp.messaging.incoming." + TEST_CONSUMER_2 + ".group.id", TEST_GROUP); + p.setProperty("mp.messaging.incoming." + TEST_CONSUMER_2 + ".key.deserializer", LongDeserializer.class.getName()); + p.setProperty("mp.messaging.incoming." + TEST_CONSUMER_2 + ".value.deserializer", StringDeserializer.class.getName()); + p.setProperty("mp.messaging.incoming." + TEST_CONSUMER_1 + ".bootstrap.servers", kafkaResource.getKafkaConnectString()); + p.setProperty("mp.messaging.incoming." + TEST_CONSUMER_1 + ".topic", TEST_TOPIC); + p.setProperty("mp.messaging.incoming." + TEST_CONSUMER_1 + ".group.id", TEST_GROUP); + p.setProperty("mp.messaging.incoming." + TEST_CONSUMER_1 + ".key.deserializer", LongDeserializer.class.getName()); + p.setProperty("mp.messaging.incoming." + TEST_CONSUMER_1 + ".value.deserializer", StringDeserializer.class.getName()); + Config config = Config.builder() + .sources(ConfigSources.create(p)) + .build(); + + List receiviedByConsumer1 = Collections.synchronizedList(new ArrayList<>(4)); + List receiviedByConsumer2 = Collections.synchronizedList(new ArrayList<>(4)); + + CountDownLatch messagesCountingLatch = new CountDownLatch(4); + + // Consumer 1 + SimpleKafkaConsumer consumer1 = new SimpleKafkaConsumer<>(TEST_CONSUMER_1, config); + consumer1.consumeAsync(r -> { + messagesCountingLatch.countDown(); + receiviedByConsumer1.add(r.value()); + }); + + // Consumer 2 + SimpleKafkaConsumer consumer2 = new SimpleKafkaConsumer<>(TEST_CONSUMER_2, config); + consumer2.consumeAsync(r -> { + messagesCountingLatch.countDown(); + receiviedByConsumer2.add(r.value()); + }); + + // Wait till all consumers are ready + consumer1.waitForPartitionAssigment(10, TimeUnit.SECONDS); + consumer2.waitForPartitionAssigment(10, TimeUnit.SECONDS); + + // Producer + SimpleKafkaProducer producer = new SimpleKafkaProducer<>(TEST_PRODUCER, config); + List> producerFutures = new ArrayList<>(4); + producerFutures.addAll(producer.produceAsync(TEST_MESSAGE + 1)); + producerFutures.addAll(producer.produceAsync(TEST_MESSAGE + 2)); + producerFutures.addAll(producer.produceAsync(TEST_MESSAGE + 3)); + producerFutures.addAll(producer.produceAsync(TEST_MESSAGE + 4)); + + // Wait for all sent(this is example usage, sent doesn't mean delivered) + producerFutures.forEach(f -> { + try { + f.get(); + } catch (InterruptedException | ExecutionException e) { + fail(e); + } + }); + + // Wait till 4 records are delivered + assertTrue(messagesCountingLatch.await(10, TimeUnit.SECONDS) + , "All messages not delivered in time"); + + consumer1.close(); + consumer2.close(); + producer.close(); + + assertFalse(receiviedByConsumer1.isEmpty()); + assertFalse(receiviedByConsumer2.isEmpty()); + assertTrue(receiviedByConsumer1.stream().noneMatch(receiviedByConsumer2::contains)); + assertTrue(receiviedByConsumer2.stream().noneMatch(receiviedByConsumer1::contains)); + } + +} diff --git a/messaging/kafka/src/test/java/io/helidon/messaging/kafka/kafka/KafkaCdiExtensionTest.java b/messaging/kafka/src/test/java/io/helidon/messaging/kafka/kafka/KafkaCdiExtensionTest.java new file mode 100644 index 00000000000..e7e5170c580 --- /dev/null +++ b/messaging/kafka/src/test/java/io/helidon/messaging/kafka/kafka/KafkaCdiExtensionTest.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.messaging.kafka.kafka; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; + +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; + +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import io.helidon.messaging.kafka.SimpleKafkaProducer; +import io.helidon.messaging.kafka.connector.KafkaConnectorFactory; +import io.helidon.microprofile.config.MpConfig; +import io.helidon.microprofile.config.MpConfigProviderResolver; +import io.helidon.microprofile.messaging.MessagingCdiExtension; +import io.helidon.microprofile.server.Server; + +import static io.helidon.common.CollectionsHelper.mapOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import com.salesforce.kafka.test.junit5.SharedKafkaTestResource; +import org.apache.kafka.clients.producer.RecordMetadata; +import org.apache.kafka.common.serialization.LongDeserializer; +import org.apache.kafka.common.serialization.LongSerializer; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.eclipse.microprofile.reactive.messaging.spi.Connector; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class KafkaCdiExtensionTest { + + protected SeContainer cdiContainer; + + protected static final Connector KAFKA_CONNECTOR_LITERAL = new Connector() { + + @Override + public Class annotationType() { + return Connector.class; + } + + @Override + public String value() { + return KafkaConnectorFactory.CONNECTOR_NAME; + } + }; + + @RegisterExtension + public static final SharedKafkaTestResource kafkaResource = new SharedKafkaTestResource(); + public static final String TEST_TOPIC = "graph-done"; + public static final String TEST_MESSAGE = "this is first test message"; + + protected Map cdiConfig() { + Map p = new HashMap<>(); + p.putAll(mapOf( + "mp.messaging.incoming.test-channel-1.connector", KafkaConnectorFactory.CONNECTOR_NAME, + "mp.messaging.incoming.test-channel-1.bootstrap.servers", kafkaResource.getKafkaConnectString(), + "mp.messaging.incoming.test-channel-1.topic", TEST_TOPIC, + "mp.messaging.incoming.test-channel-1.key.deserializer", LongDeserializer.class.getName(), + "mp.messaging.incoming.test-channel-1.value.deserializer", StringDeserializer.class.getName())); + p.putAll(mapOf( + "mp.messaging.incoming.test-channel-2.connector", KafkaConnectorFactory.CONNECTOR_NAME, + "mp.messaging.incoming.test-channel-2.bootstrap.servers", kafkaResource.getKafkaConnectString(), + "mp.messaging.incoming.test-channel-2.topic", TEST_TOPIC, + "mp.messaging.incoming.test-channel-2.key.deserializer", LongDeserializer.class.getName(), + "mp.messaging.incoming.test-channel-2.value.deserializer", StringDeserializer.class.getName()) + ); + return p; + } + + @BeforeAll + static void prepareTopics() { + kafkaResource.getKafkaTestUtils().createTopic(TEST_TOPIC, 10, (short) 1); + } + + @BeforeEach + void setUp() { + Set> classes = new HashSet<>(); + classes.add(KafkaConnectorFactory.class); + classes.add(KafkaConsumingBean.class); + classes.add(MessagingCdiExtension.class); + + Map p = new HashMap<>(cdiConfig()); + System.out.println("Starting container ..."); + cdiContainer = startCdiContainer(p, classes); + assertTrue(cdiContainer.isRunning()); + //Wait till consumers are ready + forEachBean(KafkaConnectorFactory.class, KAFKA_CONNECTOR_LITERAL, b -> b.getConsumers().forEach(c -> { + try { + c.waitForPartitionAssigment(10, TimeUnit.SECONDS); + } catch (InterruptedException | TimeoutException e) { + fail(e); + } + })); + } + + @AfterEach + public void tearDown() { + if (cdiContainer != null) { + cdiContainer.close(); + } + } + + @Test + void incomingKafkaTest() throws InterruptedException { + // Producer + Map p = mapOf( + "mp.messaging.outgoing.test-channel.bootstrap.servers", kafkaResource.getKafkaConnectString(), + "mp.messaging.outgoing.test-channel.topic", TEST_TOPIC, + "mp.messaging.outgoing.test-channel.key.serializer", LongSerializer.class.getName(), + "mp.messaging.outgoing.test-channel.value.serializer", StringSerializer.class.getName() + ); + + Config config = Config.builder() + .sources(ConfigSources.create(p)) + .build(); + + SimpleKafkaProducer producer = new SimpleKafkaProducer<>(config.get("mp.messaging.outgoing.test-channel")); + List> producerFutures = new ArrayList<>(KafkaConsumingBean.TEST_DATA.size()); + + //Send all test messages(async send means order is not guaranteed) + KafkaConsumingBean.TEST_DATA.forEach(msg -> producerFutures.addAll(producer.produceAsync(msg))); + + // Wait for all sent(this is example usage, sent doesn't mean delivered) + producerFutures.forEach(f -> { + try { + f.get(); + } catch (InterruptedException | ExecutionException e) { + fail(e); + } + }); + + // Wait till 3 records are delivered + assertTrue(KafkaConsumingBean.testChannelLatch.await(15, TimeUnit.SECONDS) + , "All messages not delivered in time, number of unreceived messages: " + + KafkaConsumingBean.testChannelLatch.getCount()); + producer.close(); + } + + private void forEachBean(Class beanType, Annotation annotation, Consumer consumer) { + cdiContainer.select(beanType, annotation).stream().forEach(consumer); + } + + private static SeContainer startCdiContainer(Map p, Set> beanClasses) { + Config config = Config.builder() + .sources(ConfigSources.create(p)) + .build(); + + final Server.Builder builder = Server.builder(); + assertNotNull(builder); + builder.config(config); + MpConfigProviderResolver.instance() + .registerConfig(MpConfig.builder() + .config(config).build(), + Thread.currentThread().getContextClassLoader()); + final SeContainerInitializer initializer = SeContainerInitializer.newInstance(); + assertNotNull(initializer); + initializer.addBeanClasses(beanClasses.toArray(new Class[0])); + return initializer.initialize(); + } +} \ No newline at end of file diff --git a/messaging/kafka/src/test/java/io/helidon/messaging/kafka/kafka/KafkaConsumingBean.java b/messaging/kafka/src/test/java/io/helidon/messaging/kafka/kafka/KafkaConsumingBean.java new file mode 100644 index 00000000000..c59b11eca9d --- /dev/null +++ b/messaging/kafka/src/test/java/io/helidon/messaging/kafka/kafka/KafkaConsumingBean.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.messaging.kafka.kafka; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CountDownLatch; + +import javax.enterprise.context.ApplicationScoped; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; + +@ApplicationScoped +public class KafkaConsumingBean { + + static Set TEST_DATA = new HashSet<>(Arrays.asList("test1", "test2", "test3")); + static CountDownLatch testChannelLatch = new CountDownLatch(TEST_DATA.size() /* 2*/); + + @Incoming("test-channel-1") + @Acknowledgment(Acknowledgment.Strategy.MANUAL) + public CompletionStage receiveMPMessage(Message> msg) { + assertTrue(TEST_DATA.contains(msg.getPayload().value())); + testChannelLatch.countDown(); + msg.ack(); + return CompletableFuture.completedFuture(null); + } + +// @Incoming("test-channel-2") +// public void receiveKafkaConsumerRecord(ConsumerRecord msg) { +// assertTrue(TEST_DATA.contains(msg.value())); +// testChannelLatch.countDown(); +// } +} diff --git a/messaging/kafka/src/test/java/io/helidon/messaging/kafka/kafka/KafkaProducingConsumingBean.java b/messaging/kafka/src/test/java/io/helidon/messaging/kafka/kafka/KafkaProducingConsumingBean.java new file mode 100644 index 00000000000..d6c9544c49e --- /dev/null +++ b/messaging/kafka/src/test/java/io/helidon/messaging/kafka/kafka/KafkaProducingConsumingBean.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.messaging.kafka.kafka; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ApplicationScoped +public class KafkaProducingConsumingBean { + + public static Set TEST_DATA = new HashSet<>(Arrays.asList("test1", "test2", "test3")); + //Two methods -> two consumers of same topic means twice as much received messages + public static CountDownLatch testChannelLatch = new CountDownLatch(TEST_DATA.size() * 2); + + + @Outgoing("kafka-selfcall-channel") + public void produceToSelf(ConsumerRecord msg) { + assertTrue(TEST_DATA.contains(msg.value())); + testChannelLatch.countDown(); + } + + @Incoming("kafka-selfcall-channel") + public void receiveFromSelf(ConsumerRecord msg) { + assertTrue(TEST_DATA.contains(msg.value())); + testChannelLatch.countDown(); + } + +} diff --git a/messaging/kafka/src/test/resources/application.yaml b/messaging/kafka/src/test/resources/application.yaml new file mode 100644 index 00000000000..d9ce9c99995 --- /dev/null +++ b/messaging/kafka/src/test/resources/application.yaml @@ -0,0 +1,28 @@ +# +# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# +# Licensed 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. +# + +mp.messaging: + incoming: + test-channel: + connector: helidon-kafka + bootstrap.servers: localhost:9092 + topic: graph-done + key.deserializer: org.apache.kafka.common.serialization.LongDeserializer + value.deserializer: org.apache.kafka.common.serialization.StringDeserializer + +mp.messaging.connector: + helidon-kafka: + bootstrap.servers: localhost:9092 diff --git a/messaging/kafka/src/test/resources/logging.properties b/messaging/kafka/src/test/resources/logging.properties new file mode 100644 index 00000000000..fba4a4e5802 --- /dev/null +++ b/messaging/kafka/src/test/resources/logging.properties @@ -0,0 +1,31 @@ +# +# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# +# Licensed 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. +# + +handlers=java.util.logging.ConsoleHandler +java.util.logging.SimpleFormatter.format=%1$tH:%1$tM:%1$tS %4$s %3$s: %5$s%6$s%n +java.util.logging.ConsoleHandler.level=INFO +.level=INFO + +# Known issue with meta.properties in embedded kafka server +#kafka.server.BrokerMetadataCheckpoint.level=SEVERE +# Hide whole configuration print-out +#org.apache.kafka.clients.producer.ProducerConfig.level=WARNING +#org.apache.kafka.clients.consumer.ConsumerConfig.level=WARNING +# Embedded kafka server exhausting logs +#kafka.level=WARNING +#org.apache.kafka.level=WARNING +#org.apache.zookeeper.level=SEVERE +#com.salesforce.kafka.level=SEVERE diff --git a/messaging/pom.xml b/messaging/pom.xml new file mode 100644 index 00000000000..ec2e7028898 --- /dev/null +++ b/messaging/pom.xml @@ -0,0 +1,36 @@ + + + + + 4.0.0 + + io.helidon + helidon-project + 1.3.2-SNAPSHOT + + pom + io.helidon.messaging + helidon-messaging-project + Helidon Messaging Project + + + kafka + + diff --git a/microprofile/messaging/pom.xml b/microprofile/messaging/pom.xml new file mode 100644 index 00000000000..789f16b4c0d --- /dev/null +++ b/microprofile/messaging/pom.xml @@ -0,0 +1,72 @@ + + + + + 4.0.0 + + io.helidon.microprofile + helidon-microprofile-project + 2.0-SNAPSHOT + + + helidon-microprofile-messaging + Helidon MicroProfile Reactive Messaging + + Helidon MicroProfile Reactive Messaging 1.0 + + + + + org.eclipse.microprofile.reactive.messaging + microprofile-reactive-messaging-api + 1.0 + + + io.helidon.microprofile.config + helidon-microprofile-config + + + io.helidon.microprofile.server + helidon-microprofile-server + ${helidon.version} + + + io.helidon.microprofile + helidon-microprofile-reactive-streams + ${helidon.version} + + + io.helidon.microprofile.bundles + internal-test-libs + test + + + org.jboss.weld + weld-junit5 + test + + + + org.slf4j + slf4j-jdk14 + 1.7.28 + test + + + diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/MessagingCdiExtension.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/MessagingCdiExtension.java new file mode 100644 index 00000000000..03b34ac60b7 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/MessagingCdiExtension.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging; + +import java.util.logging.Logger; + +import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.AfterDeploymentValidation; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.Extension; +import javax.enterprise.inject.spi.ProcessAnnotatedType; +import javax.enterprise.inject.spi.ProcessManagedBean; +import javax.enterprise.inject.spi.WithAnnotations; + +import io.helidon.microprofile.messaging.channel.ChannelRouter; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.messaging.spi.Connector; + +/** + * MicroProfile Reactive Messaging CDI Extension. + */ +public class MessagingCdiExtension implements Extension { + private static final Logger LOGGER = Logger.getLogger(MessagingCdiExtension.class.getName()); + + private ChannelRouter channelRouter = new ChannelRouter(); + + private void registerChannelMethods( + @Observes + @WithAnnotations({Incoming.class, Outgoing.class}) ProcessAnnotatedType pat) { + // Lookup channel methods + pat.getAnnotatedType().getMethods().forEach(m -> channelRouter.registerMethod(m)); + } + + private void onProcessBean(@Observes ProcessManagedBean event) { + // Lookup connectors + if (null != event.getAnnotatedBeanClass().getAnnotation(Connector.class)) { + channelRouter.registerConnectorFactory(event.getBean()); + } + // Gather bean references + channelRouter.registerBeanReference(event.getBean()); + } + + private void makeConnections(@Observes AfterDeploymentValidation event, BeanManager beanManager) { + LOGGER.info("Final connect"); + // Subscribe subscribers and publish publishers + channelRouter.connect(beanManager); + LOGGER.info("All connected"); + } + +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/MessagingStreamException.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/MessagingStreamException.java new file mode 100644 index 00000000000..efec7334fcf --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/MessagingStreamException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging; + +/** + * Wrapper for all exceptions raised during stream passage. + */ +public class MessagingStreamException extends RuntimeException { + + /** + * Create new {@link MessagingStreamException}. + * + * @param cause wrapped exception + */ + public MessagingStreamException(Throwable cause) { + super(cause); + } +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/NoConnectorFoundException.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/NoConnectorFoundException.java new file mode 100644 index 00000000000..ec1f91320d8 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/NoConnectorFoundException.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging; + +import javax.enterprise.inject.spi.DeploymentException; + +/** + * Raised when no connector of given name has been found. + */ +public class NoConnectorFoundException extends DeploymentException { + + /** + * Create new {@link NoConnectorFoundException}. + * + * @param connectorName name of the connector + */ + public NoConnectorFoundException(String connectorName) { + super(String.format("No connector %s found!", connectorName)); + } +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/NotConnectableChannelException.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/NotConnectableChannelException.java new file mode 100644 index 00000000000..0b046a6393f --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/NotConnectableChannelException.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging; + +import javax.enterprise.inject.spi.DeploymentException; + +/** + * Raised when channel hasn't candidate method or connector from both sides. + */ +public class NotConnectableChannelException extends DeploymentException { + + /** + * Create new {@link NotConnectableChannelException}. + * + * @param channelName name of un-connectable channel + * @param type incoming or outgoing {@link NotConnectableChannelException.Type} + */ + public NotConnectableChannelException(String channelName, Type type) { + super(composeMessage(channelName, type)); + } + + private static String composeMessage(String channelName, Type type) { + return String.format("No %s method or connector for channel %s found!", type, channelName); + } + + /** + * Incoming or outgoing method/connector. + */ + public enum Type { + /** + * Incoming method or connector. + */ + INCOMING, + /** + * Outgoing method or connector. + */ + OUTGOING; + + @Override + public String toString() { + return name().toLowerCase(); + } + } +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/AbstractMethod.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/AbstractMethod.java new file mode 100644 index 00000000000..0b5823f1b4b --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/AbstractMethod.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.lang.reflect.Method; +import java.util.Optional; + +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; + +import io.helidon.config.Config; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; + +abstract class AbstractMethod { + + private String incomingChannelName; + private String outgoingChannelName; + + private Bean bean; + private Method method; + private Object beanInstance; + private MethodSignatureType type; + private Acknowledgment.Strategy ackStrategy; + + + AbstractMethod(Method method) { + this.method = method; + type = MethodSignatureResolver.create(method).resolve(); + resolveAckStrategy(); + } + + void validate() { + Optional.ofNullable(method.getAnnotation(Acknowledgment.class)) + .map(Acknowledgment::value) + .filter(s -> !type.getSupportedAckStrategies().contains(s)) + .ifPresent(strategy -> { + throw new RuntimeException( + String.format("Acknowledgment strategy %s is not supported for method signature: %s", + strategy, type)); + }); + } + + public void init(BeanManager beanManager, Config config) { + this.beanInstance = ChannelRouter.lookup(bean, beanManager); + } + + public Method getMethod() { + return method; + } + + Object getBeanInstance() { + return beanInstance; + } + + void setDeclaringBean(Bean bean) { + this.bean = bean; + } + + Class getDeclaringType() { + return method.getDeclaringClass(); + } + + String getIncomingChannelName() { + return incomingChannelName; + } + + String getOutgoingChannelName() { + return outgoingChannelName; + } + + void setIncomingChannelName(String incomingChannelName) { + this.incomingChannelName = incomingChannelName; + } + + void setOutgoingChannelName(String outgoingChannelName) { + this.outgoingChannelName = outgoingChannelName; + } + + public MethodSignatureType getType() { + return type; + } + + public void setType(MethodSignatureType type) { + this.type = type; + } + + public Acknowledgment.Strategy getAckStrategy() { + return ackStrategy; + } + + private void resolveAckStrategy() { + ackStrategy = + Optional.ofNullable(method.getAnnotation(Acknowledgment.class)) + .map(Acknowledgment::value) + .orElse(type.getDefaultAckType()); + } +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/ChannelRouter.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/ChannelRouter.java new file mode 100644 index 00000000000..6ee9e2387f4 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/ChannelRouter.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.AnnotatedMethod; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.DeploymentException; + +import io.helidon.config.Config; +import io.helidon.microprofile.messaging.connector.IncomingConnector; +import io.helidon.microprofile.messaging.connector.OutgoingConnector; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.messaging.spi.Connector; +import org.eclipse.microprofile.reactive.messaging.spi.IncomingConnectorFactory; +import org.eclipse.microprofile.reactive.messaging.spi.OutgoingConnectorFactory; + +/** + * Orchestrator for all found channels, methods and connectors. + */ +public class ChannelRouter { + private Config config = (Config) ConfigProvider.getConfig(); + + private List connectableBeanMethods = new ArrayList<>(); + + private Map channelMap = new HashMap<>(); + private Map incomingConnectorMap = new HashMap<>(); + private Map outgoingConnectorMap = new HashMap<>(); + + private List> incomingConnectorFactoryList = new ArrayList<>(); + private List> outgoingConnectorFactoryList = new ArrayList<>(); + private BeanManager beanManager; + + /** + * Register bean reference with at least one annotated messaging method method. + * + * @param bean {@link javax.enterprise.inject.spi.Bean} with messaging methods reference + * @see org.eclipse.microprofile.reactive.messaging.Incoming + * @see org.eclipse.microprofile.reactive.messaging.Outgoing + */ + public void registerBeanReference(Bean bean) { + connectableBeanMethods.stream() + .filter(m -> m.getDeclaringType() == bean.getBeanClass()) + .forEach(m -> m.setDeclaringBean(bean)); + } + + /** + * Connect all discovered channel graphs. + * + * @param beanManager {@link javax.enterprise.inject.spi.BeanManager} for looking-up bean instances of discovered methods + */ + public void connect(BeanManager beanManager) { + this.beanManager = beanManager; + //Needs to be initialized before connecting, + // fast publishers would call onNext before all bean references are resolved + incomingConnectorFactoryList.forEach(this::addOutgoingConnector); + outgoingConnectorFactoryList.forEach(this::addIncomingConnector); + connectableBeanMethods.forEach(m -> m.init(beanManager, config)); + + channelMap.values().forEach(UniversalChannel::findConnectors); + channelMap.values().stream().filter(UniversalChannel::isLastInChain).forEach(UniversalChannel::connect); + } + + /** + * Register messaging method. + * + * @param m {@link javax.enterprise.inject.spi.AnnotatedMethod} + * with {@link org.eclipse.microprofile.reactive.messaging.Incoming @Incoming} + * or {@link org.eclipse.microprofile.reactive.messaging.Outgoing @Outgoing} annotation + */ + public void registerMethod(AnnotatedMethod m) { + if (m.isAnnotationPresent(Incoming.class) && m.isAnnotationPresent(Outgoing.class)) { + this.addProcessorMethod(m); + } else if (m.isAnnotationPresent(Incoming.class)) { + this.addIncomingMethod(m); + } else if (m.isAnnotationPresent(Outgoing.class)) { + this.addOutgoingMethod(m); + } + } + + /** + * Register connector bean, can be recognized as a bean implementing + * {@link org.eclipse.microprofile.reactive.messaging.spi.IncomingConnectorFactory} + * or {@link org.eclipse.microprofile.reactive.messaging.spi.OutgoingConnectorFactory} + * or both with annotation {@link org.eclipse.microprofile.reactive.messaging.spi.Connector}. + * + * @param bean connector bean + */ + public void registerConnectorFactory(Bean bean) { + Class beanType = bean.getBeanClass(); + Connector annotation = beanType.getAnnotation(Connector.class); + if (IncomingConnectorFactory.class.isAssignableFrom(beanType) && null != annotation) { + incomingConnectorFactoryList.add(bean); + } + if (OutgoingConnectorFactory.class.isAssignableFrom(beanType) && null != annotation) { + outgoingConnectorFactoryList.add(bean); + } + } + + public Config getConfig() { + return config; + } + + Optional getIncomingConnector(String connectorName) { + return Optional.ofNullable(incomingConnectorMap.get(connectorName)); + } + + Optional getOutgoingConnector(String connectorName) { + return Optional.ofNullable(outgoingConnectorMap.get(connectorName)); + } + + private void addIncomingConnector(Bean bean) { + OutgoingConnectorFactory outgoingConnectorFactory = lookup(bean, beanManager); + String connectorName = bean.getBeanClass().getAnnotation(Connector.class).value(); + IncomingConnector incomingConnector = new IncomingConnector(connectorName, outgoingConnectorFactory, this); + incomingConnectorMap.put(connectorName, incomingConnector); + } + + private void addOutgoingConnector(Bean bean) { + IncomingConnectorFactory incomingConnectorFactory = lookup(bean, beanManager); + String connectorName = bean.getBeanClass().getAnnotation(Connector.class).value(); + OutgoingConnector outgoingConnector = new OutgoingConnector(connectorName, incomingConnectorFactory, this); + outgoingConnectorMap.put(connectorName, outgoingConnector); + } + + private void addIncomingMethod(AnnotatedMethod m) { + IncomingMethod incomingMethod = new IncomingMethod(m); + incomingMethod.validate(); + + String channelName = incomingMethod.getIncomingChannelName(); + + UniversalChannel universalChannel = getOrCreateChannel(channelName); + universalChannel.setIncoming(incomingMethod); + + connectableBeanMethods.add(incomingMethod); + } + + private void addOutgoingMethod(AnnotatedMethod m) { + OutgoingMethod outgoingMethod = new OutgoingMethod(m); + outgoingMethod.validate(); + + String channelName = outgoingMethod.getOutgoingChannelName(); + + UniversalChannel universalChannel = getOrCreateChannel(channelName); + universalChannel.setOutgoing(outgoingMethod); + + connectableBeanMethods.add(outgoingMethod); + } + + private void addProcessorMethod(AnnotatedMethod m) { + ProcessorMethod channelMethod = new ProcessorMethod(m); + channelMethod.validate(); + + String incomingChannelName = channelMethod.getIncomingChannelName(); + String outgoingChannelName = channelMethod.getOutgoingChannelName(); + + UniversalChannel incomingUniversalChannel = getOrCreateChannel(incomingChannelName); + incomingUniversalChannel.setIncoming(channelMethod); + + UniversalChannel outgoingUniversalChannel = getOrCreateChannel(outgoingChannelName); + outgoingUniversalChannel.setOutgoing(channelMethod); + + connectableBeanMethods.add(channelMethod); + } + + private UniversalChannel getOrCreateChannel(String channelName) { + UniversalChannel universalChannel = channelMap.get(channelName); + if (universalChannel == null) { + universalChannel = new UniversalChannel(this); + channelMap.put(channelName, universalChannel); + } + return universalChannel; + } + + @SuppressWarnings("unchecked") + static T lookup(Bean bean, BeanManager beanManager) { + javax.enterprise.context.spi.Context context = beanManager.getContext(bean.getScope()); + Object instance = context.get(bean); + if (instance == null) { + CreationalContext creationalContext = beanManager.createCreationalContext(bean); + instance = beanManager.getReference(bean, bean.getBeanClass(), creationalContext); + } + if (instance == null) { + throw new DeploymentException("Instance of bean " + bean.getName() + " not found"); + } + return (T) instance; + } +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/IncomingMethod.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/IncomingMethod.java new file mode 100644 index 00000000000..f2f0aaf1c5d --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/IncomingMethod.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.lang.reflect.InvocationTargetException; + +import javax.enterprise.inject.spi.AnnotatedMethod; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.DeploymentException; + +import io.helidon.config.Config; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.reactivestreams.Subscriber; + +/** + * Subscriber method with reference to processor method. + *

Example: + *

{@code
+ *     @Incoming("channel-name")
+ *     public void exampleIncomingMethod(String msg) {
+ *         ...
+ *     }
+ * }
+ */ +class IncomingMethod extends AbstractMethod { + + private Subscriber subscriber; + + IncomingMethod(AnnotatedMethod method) { + super(method.getJavaMember()); + super.setIncomingChannelName(method.getAnnotation(Incoming.class).value()); + } + + void validate() { + super.validate(); + if (getIncomingChannelName() == null || getIncomingChannelName().trim().isEmpty()) { + throw new DeploymentException(String + .format("Missing channel name in annotation @Incoming on method %s", getMethod().toString())); + } + } + + @Override + @SuppressWarnings("unchecked") + public void init(BeanManager beanManager, Config config) { + super.init(beanManager, config); + if (getType().isInvokeAtAssembly()) { + try { + switch (getType()) { + case INCOMING_SUBSCRIBER_MSG_2_VOID: + Subscriber originalMsgSubscriber = (Subscriber) getMethod().invoke(getBeanInstance()); + subscriber = new ProxySubscriber(this, originalMsgSubscriber); + break; + case INCOMING_SUBSCRIBER_PAYL_2_VOID: + Subscriber originalPaylSubscriber = (Subscriber) getMethod().invoke(getBeanInstance()); + Subscriber unwrappedSubscriber = UnwrapProcessor.of(this.getMethod(), originalPaylSubscriber); + subscriber = new ProxySubscriber(this, unwrappedSubscriber); + break; + case INCOMING_SUBSCRIBER_BUILDER_MSG_2_VOID: + case INCOMING_SUBSCRIBER_BUILDER_PAYL_2_VOID: + SubscriberBuilder originalSubscriberBuilder = (SubscriberBuilder) getMethod().invoke(getBeanInstance()); + Subscriber unwrappedBuilder = UnwrapProcessor.of(this.getMethod(), originalSubscriberBuilder.build()); + subscriber = new ProxySubscriber(this, unwrappedBuilder); + break; + default: + throw new UnsupportedOperationException(String + .format("Not implemented signature %s", getType())); + } + } catch (IllegalAccessException | InvocationTargetException e) { + throw new DeploymentException(e); + } + } else { + // Invoke on each message subscriber + subscriber = new InternalSubscriber(this); + } + } + + public Subscriber getSubscriber() { + return subscriber; + } + +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/InternalProcessor.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/InternalProcessor.java new file mode 100644 index 00000000000..d4069b26420 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/InternalProcessor.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +/** + * Processor calling underlined messaging method for every received item. + *

+ * Example: + *

{@code
+ *      @Incoming("channel-one")
+ *      @Outgoing("channel-two")
+ *      public String process2(String msg) {
+ *          return msg.toLowerCase();
+ *      }
+ * }
+ */ +class InternalProcessor implements Processor { + + + private ProcessorMethod processorMethod; + private Subscriber subscriber; + + InternalProcessor(ProcessorMethod processorMethod) { + this.processorMethod = processorMethod; + } + + @Override + public void subscribe(Subscriber s) { + subscriber = s; + } + + @Override + public void onSubscribe(Subscription s) { + subscriber.onSubscribe(s); + } + + @Override + @SuppressWarnings("unchecked") + public void onNext(Object incomingValue) { + try { + Method method = processorMethod.getMethod(); + //Params size is already validated by ProcessorMethod + Class paramType = method.getParameterTypes()[0]; + Object processedValue = method.invoke(processorMethod.getBeanInstance(), + preProcess(incomingValue, paramType)); + //Method returns publisher, time for flattening its PROCESSOR_MSG_2_PUBLISHER or *_BUILDER + if (processedValue instanceof Publisher || processedValue instanceof PublisherBuilder) { + //Flatten, we are sure its invoke on every request method now + PublisherBuilder publisherBuilder; + if (processedValue instanceof Publisher) { + publisherBuilder = ReactiveStreams.fromPublisher((Publisher) processedValue); + } else { + publisherBuilder = (PublisherBuilder) processedValue; + } + publisherBuilder.forEach(subVal -> { + try { + subscriber.onNext(postProcess(incomingValue, subVal)); + } catch (ExecutionException | InterruptedException e) { + subscriber.onError(e); + } + }).run(); + } else { + subscriber.onNext(postProcess(incomingValue, processedValue)); + } + } catch (IllegalAccessException | InvocationTargetException | ExecutionException | InterruptedException e) { + subscriber.onError(e); + } + } + + @SuppressWarnings("unchecked") + private Object preProcess(Object incomingValue, Class expectedParamType) throws ExecutionException, InterruptedException { + if (processorMethod.getAckStrategy().equals(Acknowledgment.Strategy.PRE_PROCESSING) + && incomingValue instanceof Message) { + Message incomingMessage = (Message) incomingValue; + incomingMessage.ack().toCompletableFuture().complete(incomingMessage.getPayload()); + } + + return MessageUtils.unwrap(incomingValue, expectedParamType); + } + + @SuppressWarnings("unchecked") + private Object postProcess(Object incomingValue, Object outgoingValue) throws ExecutionException, InterruptedException { + if (outgoingValue instanceof CompletionStage) { + //Wait for completable stages to finish, yes it means to block see the spec + outgoingValue = ((CompletionStage) outgoingValue).toCompletableFuture().get(); + } + + Message wrappedOutgoing = (Message) MessageUtils.unwrap(outgoingValue, Message.class); + if (processorMethod.getAckStrategy().equals(Acknowledgment.Strategy.POST_PROCESSING)) { + Message wrappedIncoming = (Message) MessageUtils.unwrap(incomingValue, Message.class); + wrappedOutgoing = (Message) MessageUtils.unwrap(outgoingValue, Message.class, wrappedIncoming::ack); + } + return wrappedOutgoing; + } + + @Override + public void onError(Throwable t) { + subscriber.onError(t); + } + + @Override + public void onComplete() { + subscriber.onComplete(); + } +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/InternalPublisher.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/InternalPublisher.java new file mode 100644 index 00000000000..5c780b9bae9 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/InternalPublisher.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +/** + * Publisher calling underlined messaging method for every requested item. + */ +class InternalPublisher implements Publisher, Subscription { + + private Method method; + private Object beanInstance; + private Subscriber subscriber; + private AtomicBoolean closed = new AtomicBoolean(false); + + InternalPublisher(Method method, Object beanInstance) { + this.method = method; + this.beanInstance = beanInstance; + } + + @Override + public void subscribe(Subscriber s) { + subscriber = s; + subscriber.onSubscribe(this); + } + + @Override + public void request(long n) { + try { + for (long i = 0; i < n && !closed.get(); i++) { + Object result = method.invoke(beanInstance); + if (result instanceof CompletionStage) { + CompletionStage completionStage = (CompletionStage) result; + subscriber.onNext(completionStage.toCompletableFuture().get()); + } else { + subscriber.onNext(result); + } + } + } catch (IllegalAccessException | InvocationTargetException | InterruptedException | ExecutionException e) { + subscriber.onError(e); + } + } + + @Override + public void cancel() { + closed.set(true); + } +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/InternalSubscriber.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/InternalSubscriber.java new file mode 100644 index 00000000000..2e819f882be --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/InternalSubscriber.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.lang.reflect.Method; +import java.util.Objects; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; + +import io.helidon.microprofile.messaging.MessagingStreamException; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +/** + * Publisher calling underlined messaging method for every received item. + */ +class InternalSubscriber implements Subscriber { + + private Subscription subscription; + private IncomingMethod incomingMethod; + + InternalSubscriber(IncomingMethod incomingMethod) { + this.incomingMethod = incomingMethod; + } + + @Override + public void onSubscribe(Subscription s) { + subscription = s; + // request one by one + subscription.request(1); + } + + @Override + @SuppressWarnings("unchecked") + public void onNext(Object message) { + Method method = incomingMethod.getMethod(); + try { + Class paramType = method.getParameterTypes()[0]; + Object preProcessedMessage = preProcess(message, paramType); + Object methodResult = method.invoke(incomingMethod.getBeanInstance(), preProcessedMessage); + postProcess(message, methodResult); + subscription.request(1); + } catch (Exception e) { + // Notify publisher to stop sending + subscription.cancel(); + throw new MessagingStreamException(e); + } + } + + @SuppressWarnings("unchecked") + private Object preProcess(Object incomingValue, Class expectedParamType) throws ExecutionException, InterruptedException { + if (incomingMethod.getAckStrategy().equals(Acknowledgment.Strategy.PRE_PROCESSING) + && incomingValue instanceof Message) { + Message incomingMessage = (Message) incomingValue; + incomingMessage.ack().toCompletableFuture().complete(incomingMessage.getPayload()); + } + + return MessageUtils.unwrap(incomingValue, expectedParamType); + } + + @SuppressWarnings("unchecked") + private void postProcess(Object incomingValue, Object outgoingValue) throws ExecutionException, InterruptedException { + if (incomingMethod.getAckStrategy().equals(Acknowledgment.Strategy.POST_PROCESSING) + && incomingValue instanceof Message) { + + Message incomingMessage = (Message) incomingValue; + if (Objects.nonNull(outgoingValue) && outgoingValue instanceof CompletionStage) { + CompletionStage completionStage = (CompletionStage) outgoingValue; + Object result = completionStage.toCompletableFuture().get(); + incomingMessage.ack().toCompletableFuture().complete(result); + + } else { + // returns void + incomingMessage.ack().toCompletableFuture().complete(null); + } + } else if (Objects.nonNull(outgoingValue) + && outgoingValue instanceof CompletionStage) { + CompletionStage completionStage = (CompletionStage) outgoingValue; + completionStage.toCompletableFuture().get(); + } + } + + @Override + public void onError(Throwable t) { + throw new MessagingStreamException(t); + } + + @Override + public void onComplete() { + + } + +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/MessageUtils.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/MessageUtils.java new file mode 100644 index 00000000000..be7eda8c14d --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/MessageUtils.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.security.InvalidParameterException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; + +import javax.enterprise.inject.spi.DeploymentException; + +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +class MessageUtils { + + private MessageUtils() { + } + + /** + * Unwrap values to expected types. + *
+ * Examples: + *
{@code
+     * Message>>
+     * Message>
+     * CompletableFuture>
+     * Message
+     * }
+ * + * @param value value for unwrap + * @param type expected type + * @return unwrapped value + * @throws ExecutionException can happen when unwrapping completable + * @throws InterruptedException can happen when unwrapping completable + */ + static Object unwrap(Object value, Class type) throws ExecutionException, InterruptedException { + return unwrap(value, type, () -> CompletableFuture.completedFuture((Void) null)); + } + + /** + * Unwrap values to expected types. + *
+ * Examples: + *
{@code
+     * Message>>
+     * Message>
+     * CompletableFuture>
+     * Message
+     * }
+ * + * @param value value for unwrap + * @param type expected type + * @param onAck {@link java.util.function.Supplier} in case of message wrapping is used for completion stage inferring + * @return unwrapped value + * @throws ExecutionException can happen when unwrapping completable + * @throws InterruptedException can happen when unwrapping completable + */ + static Object unwrap(Object value, Class type, Supplier> onAck) + throws ExecutionException, InterruptedException { + if (type.equals(Message.class)) { + if (value instanceof Message) { + return value; + } else { + return Message.of(value, onAck); + } + } else { + if (value instanceof Message) { + return ((Message) value).getPayload(); + } else { + return value; + } + } + } + + /** + * Same as {@link io.helidon.microprofile.messaging.channel.MessageUtils#unwrap(java.lang.Object, java.lang.Class)}. + * But extracts expected type from method reflexively. + * + * @param value to unwrap + * @param method to extract expected type from + * @return unwrapped value + * @throws ExecutionException can happen when unwrapping completable + * @throws InterruptedException can happen when unwrapping completable + */ + static Object unwrap(Object value, Method method) throws ExecutionException, InterruptedException { + if (isMessageType(method)) { + return unwrap(value, Message.class); + } + return unwrap(value, getFirstGenericType(method)); + } + + /** + * Check if expected type is {@link org.eclipse.microprofile.reactive.messaging.Message}. + * + * @param method {@link java.lang.reflect.Method} to check + * @return true is expected type of method is {@link org.eclipse.microprofile.reactive.messaging.Message} + */ + static boolean isMessageType(Method method) { + Type returnType = method.getGenericReturnType(); + ParameterizedType parameterizedType = (ParameterizedType) returnType; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + + if (SubscriberBuilder.class.equals(method.getReturnType())) { + if (actualTypeArguments.length != 2) { + throw new DeploymentException("Invalid method return type " + method); + } + return isMessageType(actualTypeArguments[0]); + + } else if (Subscriber.class.equals(method.getReturnType())) { + if (actualTypeArguments.length != 1) { + throw new DeploymentException("Invalid method return type " + method); + } + return isMessageType(actualTypeArguments[0]); + + } else if (Processor.class.equals(method.getReturnType())) { + return isMessageType(actualTypeArguments[0]); + + } else if (ProcessorBuilder.class.equals(method.getReturnType())) { + return isMessageType(actualTypeArguments[0]); + + } else if (PublisherBuilder.class.equals(method.getReturnType())) { + return isMessageType(actualTypeArguments[0]); + + } else if (Publisher.class.equals(method.getReturnType())) { + return isMessageType(actualTypeArguments[0]); + + } + throw new InvalidParameterException("Unsupported method for unwrapping " + method); + } + + private static boolean isMessageType(Type type) { + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + return Message.class.equals(parameterizedType.getRawType()); + } + return false; + } + + private static Class getFirstGenericType(Method method) { + Type returnType = method.getGenericReturnType(); + ParameterizedType parameterizedType = (ParameterizedType) returnType; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + Type type = actualTypeArguments[0]; + if (type instanceof ParameterizedType) { + ParameterizedType firstParameterizedType = (ParameterizedType) type; + return (Class) firstParameterizedType.getRawType(); + } + return (Class) type; + } + +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/MethodSignatureResolver.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/MethodSignatureResolver.java new file mode 100644 index 00000000000..9150e8c3961 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/MethodSignatureResolver.java @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +import java.util.Objects; +import java.util.concurrent.CompletionStage; + +import javax.enterprise.inject.spi.DeploymentException; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +/** + * Method signature resolving utility, returns {@link MethodSignatureType} for any recognized signature. + * Throws {@link javax.enterprise.inject.spi.DeploymentException} for any un-recognized signature. + */ +public final class MethodSignatureResolver { + private final Class returnType; + private final Type genericReturnType; + private final Class[] parameterTypes; + private final Type[] genericParameterTypes; + private Method method; + + private MethodSignatureResolver(Method method) { + this.method = method; + returnType = method.getReturnType(); + genericReturnType = method.getGenericReturnType(); + parameterTypes = method.getParameterTypes(); + genericParameterTypes = method.getGenericParameterTypes(); + } + + /** + * Method signature resolving utility, returns {@link MethodSignatureType} for any recognized signature. + * + * @param method {@link java.lang.reflect.Method} to be resolved + * @return {@link MethodSignatureResolver} + */ + public static MethodSignatureResolver create(Method method) { + return new MethodSignatureResolver(method); + } + + /** + * Returns {@link MethodSignatureType} for any recognized signature. + * Throws {@link javax.enterprise.inject.spi.DeploymentException} + * for any un-recognized signature. + * + * @return {@link io.helidon.microprofile.messaging.channel.MethodSignatureType} + * of recognized signature + * @throws javax.enterprise.inject.spi.DeploymentException for un-recognized signature + */ + public MethodSignatureType resolve() { + // INCOMING METHODS + if (returnsClassWithGenericParams(CompletionStage.class, MsgType.PAYLOAD) && hasFirstParam(MsgType.MESSAGE)) { + // CompletionStage method(Message msg) + return MethodSignatureType.INCOMING_COMPLETION_STAGE_2_MSG; + } + if (returnsClassWithGenericParams(CompletionStage.class, MsgType.PAYLOAD) && hasFirstParam(MsgType.PAYLOAD) + && isIncoming()) { + // CompletionStage method(I payload) + return MethodSignatureType.INCOMING_COMPLETION_STAGE_2_PAYL; + } + if (hasNoParams() && returnsClassWithGenericParams(SubscriberBuilder.class, MsgType.MESSAGE)) { + // SubscriberBuilder> method() + return MethodSignatureType.INCOMING_SUBSCRIBER_BUILDER_MSG_2_VOID; + } + if (hasNoParams() && returnsClassWithGenericParams(SubscriberBuilder.class, MsgType.PAYLOAD)) { + // SubscriberBuilder method() + return MethodSignatureType.INCOMING_SUBSCRIBER_BUILDER_PAYL_2_VOID; + } + if (hasNoParams() && returnsClassWithGenericParams(Subscriber.class, MsgType.MESSAGE) + && isIncoming()) { + // Subscriber> method() + return MethodSignatureType.INCOMING_SUBSCRIBER_MSG_2_VOID; + } + if (hasNoParams() && returnsClassWithGenericParams(Subscriber.class, MsgType.PAYLOAD) + && isIncoming()) { + // Subscriber method() + return MethodSignatureType.INCOMING_SUBSCRIBER_PAYL_2_VOID; + } + if (returnsVoid() && hasFirstParam(MsgType.PAYLOAD)) { + // void method(I payload) + return MethodSignatureType.INCOMING_VOID_2_PAYL; + } + // PROCESSOR METHODS + if (returnsClassWithGenericParams(CompletionStage.class, MsgType.MESSAGE) && hasFirstParam(MsgType.MESSAGE)) { + // CompletionStage> method(Message msg) + return MethodSignatureType.PROCESSOR_COMPL_STAGE_MSG_2_MSG; + } + if (returnsClassWithGenericParams(CompletionStage.class, MsgType.PAYLOAD) && hasFirstParam(MsgType.PAYLOAD)) { + // CompletionStage method(I payload) + return MethodSignatureType.PROCESSOR_COMPL_STAGE_PAYL_2_PAYL; + } + if (hasNoParams() && returnsClassWithGenericParams(ProcessorBuilder.class, MsgType.MESSAGE)) { + // ProcessorBuilder, Message> method(); + return MethodSignatureType.PROCESSOR_PROCESSOR_BUILDER_MSG_2_VOID; + } + if (hasNoParams() && returnsClassWithGenericParams(ProcessorBuilder.class, MsgType.PAYLOAD)) { + // ProcessorBuilder method(); + return MethodSignatureType.PROCESSOR_PROCESSOR_BUILDER_PAYL_2_VOID; + } + if (hasNoParams() && returnsClassWithGenericParams(Processor.class, MsgType.MESSAGE)) { + // Processor, Message> method(); + return MethodSignatureType.PROCESSOR_PROCESSOR_MSG_2_VOID; + } + if (hasNoParams() && returnsClassWithGenericParams(Processor.class, MsgType.PAYLOAD)) { + // Processor method(); + return MethodSignatureType.PROCESSOR_PROCESSOR_PAYL_2_VOID; + } + if (returnsClassWithGenericParams(PublisherBuilder.class, MsgType.MESSAGE) + && hasFirstParamClassWithGeneric(PublisherBuilder.class, MsgType.MESSAGE)) { + // PublisherBuilder> method(PublisherBuilder> pub); + return MethodSignatureType.PROCESSOR_PUBLISHER_BUILDER_MSG_2_PUBLISHER_BUILDER_MSG; + } + if (returnsClassWithGenericParams(PublisherBuilder.class, MsgType.PAYLOAD) + && hasFirstParamClassWithGeneric(PublisherBuilder.class, MsgType.PAYLOAD)) { + // PublisherBuilder method(PublisherBuilder pub); + return MethodSignatureType.PROCESSOR_PUBLISHER_BUILDER_PAYL_2_PUBLISHER_BUILDER_PAYL; + } + if (returnsClassWithGenericParams(PublisherBuilder.class, MsgType.MESSAGE) && hasFirstParam(MsgType.MESSAGE)) { + // PublisherBuilder> method(Messagemsg); + return MethodSignatureType.PROCESSOR_PUBLISHER_BUILDER_MSG_2_MSG; + } + if (returnsClassWithGenericParams(PublisherBuilder.class, MsgType.PAYLOAD) && hasFirstParam(MsgType.PAYLOAD)) { + // PublisherBuilder method( msg); + return MethodSignatureType.PROCESSOR_PUBLISHER_BUILDER_PAYL_2_PAYL; + } + if (returnsClassWithGenericParams(Publisher.class, MsgType.MESSAGE) + && hasFirstParamClassWithGeneric(Publisher.class, MsgType.MESSAGE)) { + // Publisher> method(Publisher> pub); + return MethodSignatureType.PROCESSOR_PUBLISHER_MSG_2_PUBLISHER_MSG; + } + if (returnsClassWithGenericParams(Publisher.class, MsgType.PAYLOAD) + && hasFirstParamClassWithGeneric(Publisher.class, MsgType.PAYLOAD)) { + // Publisher method(Publisher pub); + return MethodSignatureType.PROCESSOR_PUBLISHER_PAYL_2_PUBLISHER_PAYL; + } + if (returnsClassWithGenericParams(Publisher.class, MsgType.MESSAGE) && hasFirstParam(MsgType.MESSAGE)) { + // Publisher> method(Messagemsg); + return MethodSignatureType.PROCESSOR_PUBLISHER_MSG_2_MSG; + } + if (returnsClassWithGenericParams(Publisher.class, MsgType.PAYLOAD) && hasFirstParam(MsgType.PAYLOAD)) { + // Publisher method(I payload); + return MethodSignatureType.PROCESSOR_PUBLISHER_PAYL_2_PAYL; + } + if (returns(MsgType.MESSAGE) && hasFirstParam(MsgType.MESSAGE)) { + // Message method(Message msg) + return MethodSignatureType.PROCESSOR_MSG_2_MSG; + } + if (returns(MsgType.PAYLOAD) && hasFirstParam(MsgType.PAYLOAD)) { + // O method(I payload) + return MethodSignatureType.PROCESSOR_PAYL_2_PAYL; + } + // OUTGOING METHODS + if (hasNoParams() && returnsClassWithGenericParams(CompletionStage.class, MsgType.MESSAGE)) { + // CompletionStage> method() + return MethodSignatureType.OUTGOING_COMPLETION_STAGE_MSG_2_VOID; + } + if (hasNoParams() && returnsClassWithGenericParams(CompletionStage.class, MsgType.PAYLOAD)) { + // CompletionStage method() + return MethodSignatureType.OUTGOING_COMPLETION_STAGE_PAYL_2_VOID; + } + if (hasNoParams() && returnsClassWithGenericParams(Publisher.class, MsgType.MESSAGE)) { + // Publisher> method() + return MethodSignatureType.OUTGOING_PUBLISHER_MSG_2_VOID; + } + if (hasNoParams() && returnsClassWithGenericParams(Publisher.class, MsgType.PAYLOAD)) { + // Publisher method() + return MethodSignatureType.OUTGOING_PUBLISHER_PAYL_2_VOID; + } + if (hasNoParams() && returnsClassWithGenericParams(PublisherBuilder.class, MsgType.MESSAGE)) { + // PublisherBuilder> method() + return MethodSignatureType.OUTGOING_PUBLISHER_BUILDER_MSG_2_VOID; + } + if (hasNoParams() && returnsClassWithGenericParams(PublisherBuilder.class, MsgType.PAYLOAD)) { + // PublisherBuilder method() + return MethodSignatureType.OUTGOING_PUBLISHER_BUILDER_PAYL_2_VOID; + } + if (hasNoParams() && returns(MsgType.MESSAGE)) { + // Message method() + return MethodSignatureType.OUTGOING_MSG_2_VOID; + } + if (hasNoParams() && returns(MsgType.PAYLOAD)) { + // U method() + return MethodSignatureType.OUTGOING_PAYL_2_VOID; + } + // Remove when TCK issue is solved https://github.com/eclipse/microprofile-reactive-messaging/issues/79 + // see io.helidon.microprofile.messaging.inner.BadSignaturePublisherPayloadBean + if (returns(MsgType.PAYLOAD) && hasFirstParam(MsgType.PAYLOAD)) { + // O method(I payload) + return MethodSignatureType.INCOMING_VOID_2_PAYL; + } + // END OF BLOCK FOR REMOVE + throw new DeploymentException("Unsupported method signature " + method); + } + + private boolean hasNoParams() { + return method.getParameterCount() == 0; + } + + private boolean returnsVoid() { + return Void.TYPE.equals(returnType); + } + + private boolean returns(MsgType msgType) { + if (returnsVoid()) return false; + return msgType == MsgType.MESSAGE && Message.class.isAssignableFrom(returnType) + || msgType == MsgType.PAYLOAD && !Message.class.isAssignableFrom(returnType); + } + + private boolean hasFirstParam(MsgType msgType) { + if (hasNoParams()) return false; + Class firstParam = parameterTypes[0]; + return msgType == MsgType.MESSAGE && Message.class.isAssignableFrom(firstParam) + || msgType == MsgType.PAYLOAD && !Message.class.equals(firstParam); + } + + private boolean hasFirstParamClassWithGeneric(Class clazz, MsgType msgType) { + if (hasNoParams()) return false; + if (!clazz.isAssignableFrom(parameterTypes[0])) return false; + + Type firstParam = genericParameterTypes[0]; + if (!(firstParam instanceof ParameterizedType)) return false; + + ParameterizedType paramReturnType = (ParameterizedType) firstParam; + Type[] actualTypeArguments = paramReturnType.getActualTypeArguments(); + + if (msgType == MsgType.MESSAGE) { + if (!(actualTypeArguments[0] instanceof ParameterizedType)) return false; + return Message.class.equals(((ParameterizedType) actualTypeArguments[0]).getRawType()); + } else { + return !Message.class.equals(firstParam); + } + } + + private boolean returnsClassWithGenericParams(Class clazz, MsgType msgType) { + if (returnsVoid()) return false; + if (!clazz.isAssignableFrom(returnType)) return false; + if (!(genericReturnType instanceof ParameterizedType)) return false; + + ParameterizedType paramReturnType = (ParameterizedType) genericReturnType; + Type[] actualTypeArguments = paramReturnType.getActualTypeArguments(); + + if (actualTypeArguments.length == 0) return false; + + if (msgType == MsgType.MESSAGE) { + if (!(actualTypeArguments[0] instanceof ParameterizedType)) return false; + return Message.class.equals(((ParameterizedType) actualTypeArguments[0]).getRawType()); + } else if (msgType == MsgType.WILDCARD) { + return actualTypeArguments[0] instanceof WildcardType; + } else { + if ((actualTypeArguments[0] instanceof ParameterizedType)) { + if (Message.class.equals(((ParameterizedType) actualTypeArguments[0]).getRawType())) return false; + } + return !Message.class.equals(actualTypeArguments[0]); + } + } + + private boolean isIncoming() { + return hasAnnotation(Incoming.class) && !hasAnnotation(Outgoing.class); + } + + private boolean isOutgoing() { + return !hasAnnotation(Incoming.class) && hasAnnotation(Outgoing.class); + } + + private boolean isProcessor() { + return hasAnnotation(Incoming.class) && hasAnnotation(Outgoing.class); + } + + private boolean hasAnnotation(Class clazz) { + T annotation = method.getAnnotation(clazz); + return Objects.nonNull(annotation); + } + + private enum MsgType { + MESSAGE, PAYLOAD, WILDCARD; + } +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/MethodSignatureType.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/MethodSignatureType.java new file mode 100644 index 00000000000..adc64ba82d5 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/MethodSignatureType.java @@ -0,0 +1,501 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; + +/** + * Supported method signatures as described in the MicroProfile Reactive Messaging Specification. + */ +public enum MethodSignatureType { + /** + * Processor method signature type. + *
+ * Invoke at: assembly time + *
+ *
Processor<Message<I>, Message<O>> method();
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, PRE_PROCESSING, MANUAL
  • + *
+ */ + PROCESSOR_PROCESSOR_MSG_2_VOID(true, Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.MANUAL + ), + /** + * Processor method signature type. + *
+ * Invoke at: assembly time + *
+ *
Processor<I, O> method();
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, PRE_PROCESSING
  • + *
+ */ + PROCESSOR_PROCESSOR_PAYL_2_VOID(true, Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.PRE_PROCESSING + ), + /** + * Processor method signature type. + *
+ * Invoke at: Assembly time - + *
ProcessorBuilder<Message<I>, Message<O>> method();
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, PRE_PROCESSING, MANUAL
  • + *
+ */ + PROCESSOR_PROCESSOR_BUILDER_MSG_2_VOID(true, Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.MANUAL + ), + /** + * Processor method signature type. + *
+ * Invoke at: Assembly time - + *
ProcessorBuilder<I, O> method();
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, PRE_PROCESSING
  • + *
+ */ + PROCESSOR_PROCESSOR_BUILDER_PAYL_2_VOID(true, Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.PRE_PROCESSING + ), + /** + * Processor method signature type. + *
+ * Invoke at: assembly time + *
Publisher<Message<O>> method(Publisher<Message<I>> pub);
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, MANUAL, PRE_PROCESSING
  • + *
+ */ + PROCESSOR_PUBLISHER_MSG_2_PUBLISHER_MSG(true, Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.MANUAL, + Acknowledgment.Strategy.PRE_PROCESSING + ), + /** + * Processor method signature type. + *
+ * Invoke at: assembly time + *
Publisher<O> method(Publisher<I> pub);
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, PRE_PROCESSING
  • + *
+ */ + PROCESSOR_PUBLISHER_PAYL_2_PUBLISHER_PAYL(true, Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.PRE_PROCESSING + ), + /** + * Processor method signature type. + *
+ * Invoke at: assembly time + *
PublisherBuilder<Message<O>> method(PublisherBuilder<Message<I>> pub);
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, MANUAL, PRE_PROCESSING
  • + *
+ */ + PROCESSOR_PUBLISHER_BUILDER_MSG_2_PUBLISHER_BUILDER_MSG(true, Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.MANUAL, + Acknowledgment.Strategy.PRE_PROCESSING + ), + /** + * Processor method signature type. + *
+ * Invoke at: assembly time + *
PublisherBuilder<O> method(PublisherBuilder<I> pub);
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, PRE_PROCESSING
  • + *
+ */ + PROCESSOR_PUBLISHER_BUILDER_PAYL_2_PUBLISHER_BUILDER_PAYL(true, Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.PRE_PROCESSING + ), + /** + * Processor method signature type. + *
+ * Invoke at: every incoming + *
Publisher<Message<O>> method(Message<I> msg);
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, MANUAL, PRE_PROCESSING
  • + *
+ */ + PROCESSOR_PUBLISHER_MSG_2_MSG(false, Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.MANUAL, + Acknowledgment.Strategy.PRE_PROCESSING + ), + /** + * Processor method signature type. + *
+ * Invoke at: every incoming + *
Publisher<O> method(I payload);
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, PRE_PROCESSING
  • + *
+ */ + PROCESSOR_PUBLISHER_PAYL_2_PAYL(false, Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.PRE_PROCESSING + ), + /** + * Processor method signature type. + *
+ * Invoke at: every incoming + *
PublisherBuilder<Message<O>> method(Message<I> msg);
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, MANUAL, PRE_PROCESSING
  • + *
+ */ + PROCESSOR_PUBLISHER_BUILDER_MSG_2_MSG(false, Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.MANUAL, + Acknowledgment.Strategy.PRE_PROCESSING + ), + /** + * Processor method signature type. + *
+ * Invoke at: every incoming + *
PublisherBuilder<O> method(I payload);
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, PRE_PROCESSING
  • + *
+ */ + PROCESSOR_PUBLISHER_BUILDER_PAYL_2_PAYL(false, Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.PRE_PROCESSING + ), + /** + * Processor method signature type. + *
+ * Invoke at: every incoming + *
Message<O> method(Message<I> msg)
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, MANUAL, PRE_PROCESSING
  • + *
+ */ + PROCESSOR_MSG_2_MSG(false, Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.MANUAL, + Acknowledgment.Strategy.PRE_PROCESSING + ), + /** + * Processor method signature type. + *
+ * Invoke at: every incoming + *
O method(I payload)
+ *
    + *
  • Default acknowledgment strategy: POST_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, PRE_PROCESSING, POST_PROCESSING
  • + *
+ */ + PROCESSOR_PAYL_2_PAYL(false, Acknowledgment.Strategy.POST_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.POST_PROCESSING + ), + /** + * Processor method signature type. + *
+ * Invoke at: every incoming + *
CompletionStage<Message<O>> method(Message<I> msg)
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, MANUAL, PRE_PROCESSING
  • + *
+ */ + PROCESSOR_COMPL_STAGE_MSG_2_MSG(false, Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.MANUAL, + Acknowledgment.Strategy.PRE_PROCESSING + ), + /** + * Processor method signature type. + *
+ * Invoke at: every incoming + *
CompletionStage<O> method(I payload)
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, PRE_PROCESSING
  • + *
+ */ + PROCESSOR_COMPL_STAGE_PAYL_2_PAYL(false, Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.PRE_PROCESSING + ), + + + /** + * Subscriber method signature type. + *
+ * Invoke at: assembly time + *
Subscriber<Message<I>> method()
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, MANUAL, PRE_PROCESSING, POST_PROCESSING
  • + *
+ */ + INCOMING_SUBSCRIBER_MSG_2_VOID(true, Acknowledgment.Strategy.POST_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.MANUAL, + Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.POST_PROCESSING + ), + + /** + * Subscriber method signature type. + *
+ * Invoke at: assembly time + *
Subscriber<I> method()
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, PRE_PROCESSING, POST_PROCESSING
  • + *
+ */ + INCOMING_SUBSCRIBER_PAYL_2_VOID(true, Acknowledgment.Strategy.POST_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.POST_PROCESSING + ), + /** + * Subscriber method signature type. + *
+ * Invoke at: assembly time + *
SubscriberBuilder<Message<I>> method()
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, MANUAL, PRE_PROCESSING, POST_PROCESSING
  • + *
+ */ + INCOMING_SUBSCRIBER_BUILDER_MSG_2_VOID(true, Acknowledgment.Strategy.POST_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.MANUAL, + Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.POST_PROCESSING + ), + /** + * Subscriber method signature type. + *
+ * Invoke at: assembly time + *
SubscriberBuilder<I> method()
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, PRE_PROCESSING, POST_PROCESSING
  • + *
+ */ + INCOMING_SUBSCRIBER_BUILDER_PAYL_2_VOID(true, Acknowledgment.Strategy.POST_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.POST_PROCESSING + ), + /** + * Subscriber method signature type. + *
+ * Invoke at: every incoming + *
void method(I payload)
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, PRE_PROCESSING, POST_PROCESSING
  • + *
+ */ + INCOMING_VOID_2_PAYL(false, Acknowledgment.Strategy.POST_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.POST_PROCESSING + ), + /** + * Subscriber method signature type. + *
+ * Invoke at: every incoming + *
CompletionStage<?> method(Message<I> msg)
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, MANUAL, PRE_PROCESSING, POST_PROCESSING
  • + *
+ */ + INCOMING_COMPLETION_STAGE_2_MSG(false, Acknowledgment.Strategy.POST_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.MANUAL, + Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.POST_PROCESSING + ), + /** + * Subscriber method signature type. + *
+ * Invoke at: every incoming + *
CompletionStage<?> method(I payload)
+ *
    + *
  • Default acknowledgment strategy: PRE_PROCESSING
  • + *
  • Supported acknowledgment strategies: NONE, PRE_PROCESSING, POST_PROCESSING
  • + *
+ */ + INCOMING_COMPLETION_STAGE_2_PAYL(false, Acknowledgment.Strategy.POST_PROCESSING, + Acknowledgment.Strategy.NONE, + Acknowledgment.Strategy.PRE_PROCESSING, + Acknowledgment.Strategy.POST_PROCESSING + ), + + /** + * Publisher method signature type. + *
+ * Invoke at: assembly time + *
Publisher<Message<U<< method()
+ */ + OUTGOING_PUBLISHER_MSG_2_VOID(true, null), + + /** + * Publisher method signature type. + *
+ * Invoke at: assembly time + *
Publisher<U< method()
+ */ + OUTGOING_PUBLISHER_PAYL_2_VOID(true, null), + + /** + * Publisher method signature type. + *
+ * Invoke at: assembly time + *
PublisherBuilder<Message<U<< method()
+ */ + OUTGOING_PUBLISHER_BUILDER_MSG_2_VOID(true, null), + + /** + * Publisher method signature type. + *
+ * Invoke at: assembly time + *
PublisherBuilder<U< method()
+ */ + OUTGOING_PUBLISHER_BUILDER_PAYL_2_VOID(true, null), + + /** + * Publisher method signature type. + *
+ * Invoke at: Each request made by subscriber + *
Message<U< method()
+ *
+ * Produces an infinite stream of Message associated with the + * channel channel. The result is a CompletionStage. The method should not be + * called by the reactive messaging implementation until the CompletionStage + * returned previously is completed. + */ + OUTGOING_MSG_2_VOID(false, null), + + /** + * Publisher method signature type. + *
+ * Invoke at: Each request made by subscriber + *
U method()
+ *
+ * Produces an infinite stream of Message associated with the + * channel channel. The result is a CompletionStage. The method should not be + * called by the reactive messaging implementation until the CompletionStage + * returned previously is completed. + */ + OUTGOING_PAYL_2_VOID(false, null), + + /** + * Publisher method signature type. + *
+ * Invoke at: Each request made by subscriber + *
CompletionStage<Message<U<< method()
+ *
+ * Produces an infinite stream of Message associated with the + * channel channel. The result is a CompletionStage. The method should not be + * called by the reactive messaging implementation until the CompletionStage + * returned previously is completed. + */ + OUTGOING_COMPLETION_STAGE_MSG_2_VOID(false, null), + + /** + * Publisher method signature type. + *
+ * Invoke at: Each request made by subscriber + *
CompletionStage<U< method()
+ *
+ * Produces an infinite stream of Message associated with the + * channel channel. The result is a CompletionStage. The method should not be + * called by the reactive messaging implementation until the CompletionStage + * returned previously is completed. + */ + OUTGOING_COMPLETION_STAGE_PAYL_2_VOID(false, null); + + private boolean invokeAtAssembly; + private Acknowledgment.Strategy defaultAckType; + private Set supportedAckStrategies; + + MethodSignatureType(boolean invokeAtAssembly, + Acknowledgment.Strategy defaultAckType, + Acknowledgment.Strategy... supportedAckTypes) { + this.invokeAtAssembly = invokeAtAssembly; + this.defaultAckType = defaultAckType; + this.supportedAckStrategies = new HashSet<>(Arrays.asList(supportedAckTypes)); + } + + /** + * Method signatures which should be invoked at assembly(those registering publishers/processors/subscribers) are marked with true, + * to distinguish them from those which should be invoked for every item in the stream. + * + * @return {@code true} if should be invoked at assembly + */ + public boolean isInvokeAtAssembly() { + return invokeAtAssembly; + } + + /** + * Return set of supported acknowledgment strategies. + * + * @return Set of {@link org.eclipse.microprofile.reactive.messaging.Acknowledgment.Strategy} + */ + public Set getSupportedAckStrategies() { + return supportedAckStrategies; + } + + /** + * Default {@link org.eclipse.microprofile.reactive.messaging.Acknowledgment.Strategy} + * if nothing was set by {@link org.eclipse.microprofile.reactive.messaging.Acknowledgment}. + * + * @return Default {@link org.eclipse.microprofile.reactive.messaging.Acknowledgment.Strategy} + */ + public Acknowledgment.Strategy getDefaultAckType() { + return defaultAckType; + } +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/OutgoingMethod.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/OutgoingMethod.java new file mode 100644 index 00000000000..ceb3910d194 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/OutgoingMethod.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.lang.reflect.InvocationTargetException; + +import javax.enterprise.inject.spi.AnnotatedMethod; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.DeploymentException; + +import io.helidon.config.Config; + +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.reactivestreams.Publisher; + +class OutgoingMethod extends AbstractMethod { + + private Publisher publisher; + + OutgoingMethod(AnnotatedMethod method) { + super(method.getJavaMember()); + super.setOutgoingChannelName(method.getAnnotation(Outgoing.class).value()); + } + + @Override + public void init(BeanManager beanManager, Config config) { + super.init(beanManager, config); + if (getType().isInvokeAtAssembly()) { + try { + switch (getType()) { + case OUTGOING_PUBLISHER_MSG_2_VOID: + case OUTGOING_PUBLISHER_PAYL_2_VOID: + publisher = (Publisher) getMethod().invoke(getBeanInstance()); + break; + case OUTGOING_PUBLISHER_BUILDER_MSG_2_VOID: + case OUTGOING_PUBLISHER_BUILDER_PAYL_2_VOID: + publisher = ((PublisherBuilder) getMethod().invoke(getBeanInstance())).buildRs(); + break; + default: + throw new UnsupportedOperationException(String + .format("Not implemented signature %s", getType())); + } + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } else { + // Invoke on each request publisher + publisher = new InternalPublisher(getMethod(), getBeanInstance()); + } + } + + void validate() { + super.validate(); + if (getOutgoingChannelName() == null || getOutgoingChannelName().trim().isEmpty()) { + throw new DeploymentException(String + .format("Missing channel name in annotation @Outgoing, method: %s", getMethod())); + } + if (getMethod().getReturnType().equals(Void.TYPE)) { + throw new DeploymentException(String + .format("Method annotated as @Outgoing channel cannot have return type void, method: %s", getMethod())); + } + } + + public Publisher getPublisher() { + return publisher; + } + +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/ProcessorMethod.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/ProcessorMethod.java new file mode 100644 index 00000000000..be630358ee3 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/ProcessorMethod.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import javax.enterprise.inject.spi.AnnotatedMethod; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.DeploymentException; + +import io.helidon.config.Config; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.reactivestreams.Processor; + +class ProcessorMethod extends AbstractMethod { + + private Processor processor; + private UniversalChannel outgoingChannel; + + ProcessorMethod(AnnotatedMethod method) { + super(method.getJavaMember()); + super.setIncomingChannelName(method.getAnnotation(Incoming.class).value()); + super.setOutgoingChannelName(method.getAnnotation(Outgoing.class).value()); + } + + @Override + public void init(BeanManager beanManager, Config config) { + super.init(beanManager, config); + if (getType().isInvokeAtAssembly()) { + processor = new ProxyProcessor(this); + } else { + // Create brand new subscriber + processor = new InternalProcessor(this); + } + } + + @Override + public void validate() { + super.validate(); + if (getIncomingChannelName() == null || getIncomingChannelName().trim().isEmpty()) { + throw new DeploymentException(String + .format("Missing channel name in annotation @Incoming on method %s", getMethod())); + } + if (getOutgoingChannelName() == null || getOutgoingChannelName().trim().isEmpty()) { + throw new DeploymentException(String + .format("Missing channel name in annotation @Outgoing on method %s", getMethod())); + } + if (this.getMethod().getParameterTypes().length > 1) { + throw new DeploymentException("Bad processor method signature, " + + "wrong number of parameters, only one or none allowed." + + getMethod()); + } + } + + public Processor getProcessor() { + return processor; + } + + UniversalChannel getOutgoingChannel() { + return outgoingChannel; + } + + void setOutgoingChannel(UniversalChannel outgoingChannel) { + this.outgoingChannel = outgoingChannel; + } + +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/ProxyProcessor.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/ProxyProcessor.java new file mode 100644 index 00000000000..fef672993e8 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/ProxyProcessor.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.ExecutionException; + +import javax.enterprise.inject.spi.DeploymentException; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +/** + * Passes publisher to processor method. ex: + *
{@code
+ *     @Incoming("inner-processor")
+ *     @Outgoing("inner-processor-2")
+ *     public PublisherBuilder process(PublisherBuilder msg) {
+ *         return msg;
+ *     }
+ * }
+ */ +class ProxyProcessor implements Processor { + + private final ProcessorMethod processorMethod; + private final Publisher publisher; + private Subscriber subscriber; + private Processor processor; + private boolean subscribed = false; + + @SuppressWarnings("unchecked") + ProxyProcessor(ProcessorMethod processorMethod) { + this.processorMethod = processorMethod; + try { + switch (processorMethod.getType()) { + case PROCESSOR_PUBLISHER_BUILDER_MSG_2_PUBLISHER_BUILDER_MSG: + case PROCESSOR_PUBLISHER_BUILDER_PAYL_2_PUBLISHER_BUILDER_PAYL: + PublisherBuilder paramPublisherBuilder = ReactiveStreams.fromPublisher(this); + publisher = ((PublisherBuilder) processorMethod + .getMethod() + .invoke(processorMethod.getBeanInstance(), paramPublisherBuilder)).buildRs(); + break; + case PROCESSOR_PUBLISHER_MSG_2_PUBLISHER_MSG: + case PROCESSOR_PUBLISHER_PAYL_2_PUBLISHER_PAYL: + publisher = ((Publisher) processorMethod + .getMethod() + .invoke(processorMethod.getBeanInstance(), this)); + break; + case PROCESSOR_PROCESSOR_BUILDER_MSG_2_VOID: + case PROCESSOR_PROCESSOR_BUILDER_PAYL_2_VOID: + processor = ((ProcessorBuilder) processorMethod + .getMethod() + .invoke(processorMethod.getBeanInstance())).buildRs(); + publisher = processor; + break; + case PROCESSOR_PROCESSOR_MSG_2_VOID: + case PROCESSOR_PROCESSOR_PAYL_2_VOID: + processor = ((Processor) processorMethod + .getMethod() + .invoke(processorMethod.getBeanInstance())); + publisher = processor; + break; + default: + throw new UnsupportedOperationException("Unknown signature type " + processorMethod.getType()); + } + } catch (IllegalAccessException | InvocationTargetException e) { + throw new DeploymentException(e); + } + + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(Subscriber s) { + if (processor != null) { + // Backed by real processor + processor.subscribe(s); + subscriber = processor; + } else if (!subscribed && publisher != null) { + // Backed by publisher + subscribed = true; + publisher.subscribe(s); + } else { + subscriber = s; + } + } + + @Override + public void onSubscribe(Subscription s) { + subscriber.onSubscribe(s); + } + + @Override + @SuppressWarnings("unchecked") + public void onNext(Object o) { + try { + preProcess(o); + subscriber.onNext(MessageUtils.unwrap(o, this.processorMethod.getMethod())); + } catch (ExecutionException | InterruptedException e) { + onError(e); + } + } + + @Override + public void onError(Throwable t) { + subscriber.onError(t); + } + + @Override + public void onComplete() { + subscriber.onComplete(); + } + + @SuppressWarnings("unchecked") + private void preProcess(Object incomingValue) { + if (processorMethod.getAckStrategy().equals(Acknowledgment.Strategy.PRE_PROCESSING) + && incomingValue instanceof Message) { + Message incomingMessage = (Message) incomingValue; + incomingMessage.ack().toCompletableFuture().complete(incomingMessage.getPayload()); + } + } +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/ProxySubscriber.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/ProxySubscriber.java new file mode 100644 index 00000000000..3756ac00fa0 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/ProxySubscriber.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +/** + * Subscriber wrapper used to invoke pre-process and post-process logic. + * + * @param type of the subscriber value + */ +class ProxySubscriber implements Subscriber { + + private IncomingMethod method; + private Subscriber originalSubscriber; + + ProxySubscriber(IncomingMethod method, Subscriber originalSubscriber) { + this.method = method; + this.originalSubscriber = originalSubscriber; + } + + @Override + public void onSubscribe(Subscription s) { + originalSubscriber.onSubscribe(s); + } + + @Override + public void onNext(T o) { + originalSubscriber.onNext(preProcess(o)); + postProcess(o); + } + + @Override + public void onError(Throwable t) { + originalSubscriber.onError(t); + } + + @Override + public void onComplete() { + originalSubscriber.onComplete(); + } + + @SuppressWarnings("unchecked") + private T preProcess(T incomingValue) { + if (method.getAckStrategy().equals(Acknowledgment.Strategy.PRE_PROCESSING) + && incomingValue instanceof Message) { + Message incomingMessage = (Message) incomingValue; + incomingMessage.ack().toCompletableFuture().complete(incomingMessage.getPayload()); + } + + return incomingValue; + } + + @SuppressWarnings("unchecked") + private void postProcess(T incomingValue) { + if (method.getAckStrategy().equals(Acknowledgment.Strategy.POST_PROCESSING) + && incomingValue instanceof Message) { + Message incomingMessage = (Message) incomingValue; + incomingMessage.ack().toCompletableFuture().complete(null); + } + } +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/UniversalChannel.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/UniversalChannel.java new file mode 100644 index 00000000000..aea8ceebc3d --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/UniversalChannel.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.util.Optional; +import java.util.logging.Logger; + +import io.helidon.config.Config; +import io.helidon.config.ConfigValue; +import io.helidon.microprofile.messaging.NoConnectorFoundException; +import io.helidon.microprofile.messaging.NotConnectableChannelException; +import io.helidon.microprofile.messaging.connector.IncomingConnector; +import io.helidon.microprofile.messaging.connector.OutgoingConnector; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +class UniversalChannel { + + private static final Logger LOGGER = Logger.getLogger(UniversalChannel.class.getName()); + + private String name; + private IncomingConnector incomingConnector; + private ProcessorMethod incomingProcessorMethod; + private IncomingMethod incomingMethod; + private OutgoingMethod outgoingMethod; + private OutgoingConnector outgoingConnector; + private ProcessorMethod outgoingProcessorMethod; + private Publisher publisher; + private Config config; + private ChannelRouter router; + private Optional upstreamChannel = Optional.empty(); + + UniversalChannel(ChannelRouter router) { + this.router = router; + this.config = router.getConfig(); + } + + void setIncoming(IncomingMethod incomingMethod) { + this.name = incomingMethod.getIncomingChannelName(); + this.incomingMethod = incomingMethod; + } + + void setIncoming(ProcessorMethod processorMethod) { + this.name = processorMethod.getIncomingChannelName(); + this.incomingProcessorMethod = processorMethod; + this.incomingProcessorMethod.setOutgoingChannel(this); + } + + void setOutgoing(ProcessorMethod processorMethod) { + this.name = processorMethod.getOutgoingChannelName(); + this.outgoingProcessorMethod = processorMethod; + } + + void setOutgoing(OutgoingMethod outgoingMethod) { + this.name = outgoingMethod.getOutgoingChannelName(); + this.outgoingMethod = outgoingMethod; + } + + @SuppressWarnings("unchecked") + void connect() { + StringBuilder connectMessage = new StringBuilder("Connecting channel ") + .append(name).append(" with outgoing method "); + + if (outgoingMethod != null) { + publisher = outgoingMethod.getPublisher(); + connectMessage.append(outgoingMethod.getMethod().getName()); + + } else if (outgoingProcessorMethod != null) { + publisher = outgoingProcessorMethod.getProcessor(); + upstreamChannel = Optional.of(outgoingProcessorMethod.getOutgoingChannel()); + connectMessage.append(outgoingProcessorMethod.getMethod().getName()); + + } else if (outgoingConnector != null) { + publisher = outgoingConnector.getPublisher(name); + connectMessage.append(outgoingConnector.getConnectorName()); + } else { + LOGGER.severe(connectMessage.append("and no outgoing method found!").toString()); + throw new NotConnectableChannelException(name, NotConnectableChannelException.Type.OUTGOING); + } + + connectMessage.append(" and incoming method "); + + Subscriber subscriber1; + if (incomingMethod != null) { + subscriber1 = incomingMethod.getSubscriber(); + connectMessage.append(incomingMethod.getMethod().getName()); + publisher.subscribe(subscriber1); + //Continue connecting processor chain + upstreamChannel.ifPresent(UniversalChannel::connect); + + } else if (incomingProcessorMethod != null) { + subscriber1 = incomingProcessorMethod.getProcessor(); + connectMessage.append(incomingProcessorMethod.getMethod().getName()); + publisher.subscribe(subscriber1); + //Continue connecting processor chain + upstreamChannel.ifPresent(UniversalChannel::connect); + + } else if (incomingConnector != null) { + subscriber1 = incomingConnector.getSubscriber(name); + connectMessage.append(incomingConnector.getConnectorName()); + publisher.subscribe(subscriber1); + //Continue connecting processor chain + upstreamChannel.ifPresent(UniversalChannel::connect); + + } else { + LOGGER.severe(connectMessage.append("and no incoming method found!").toString()); + throw new NotConnectableChannelException(name, NotConnectableChannelException.Type.INCOMING); + } + } + + boolean isLastInChain() { + return incomingProcessorMethod == null; + } + + void findConnectors() { + //Looks suspicious but incoming connector configured for outgoing channel is ok + ConfigValue incomingConnectorName = config.get("mp.messaging.outgoing").get(name).get("connector").asString(); + ConfigValue outgoingConnectorName = config.get("mp.messaging.incoming").get(name).get("connector").asString(); + if (incomingConnectorName.isPresent()) { + incomingConnector = router.getIncomingConnector(incomingConnectorName.get()) + .orElseThrow(() -> new NoConnectorFoundException(incomingConnectorName.get())); + } + if (outgoingConnectorName.isPresent()) { + outgoingConnector = router.getOutgoingConnector(outgoingConnectorName.get()) + .orElseThrow(() -> new NoConnectorFoundException(outgoingConnectorName.get())); + } + } + +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/UnwrapProcessor.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/UnwrapProcessor.java new file mode 100644 index 00000000000..9855cdd9371 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/UnwrapProcessor.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.lang.reflect.Method; +import java.util.concurrent.ExecutionException; + +import org.reactivestreams.Processor; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +/** + * Unwrap Message payload if incoming method Publisher or Publisher builder + * has generic return type different than Message. + */ +class UnwrapProcessor implements Processor { + + private Method method; + private Subscriber subscriber; + + UnwrapProcessor() { + } + + static UnwrapProcessor of(Method method, Subscriber subscriber) { + UnwrapProcessor unwrapProcessor = new UnwrapProcessor(); + unwrapProcessor.subscribe(subscriber); + unwrapProcessor.setMethod(method); + return unwrapProcessor; + } + + Object unwrap(Object o) throws ExecutionException, InterruptedException { + return MessageUtils.unwrap(o, method); + } + + + @Override + public void subscribe(Subscriber subscriber) { + this.subscriber = subscriber; + } + + @Override + public void onSubscribe(Subscription s) { + subscriber.onSubscribe(s); + } + + @Override + public void onNext(Object o) { + try { + subscriber.onNext(unwrap(o)); + } catch (ExecutionException | InterruptedException e) { + onError(e); + } + } + + @Override + public void onError(Throwable t) { + subscriber.onError(t); + } + + @Override + public void onComplete() { + subscriber.onComplete(); + } + + void setMethod(Method method) { + this.method = method; + } +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/package-info.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/package-info.java new file mode 100644 index 00000000000..82ba9d32549 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/channel/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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. + * + */ + +/** + * MicroProfile Reactive Messaging channels and bean methods abstraction model + * orchestrated by {@link io.helidon.microprofile.messaging.channel.ChannelRouter}. + */ +package io.helidon.microprofile.messaging.channel; diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/AdHocConfigBuilder.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/AdHocConfigBuilder.java new file mode 100644 index 00000000000..f4e17760227 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/AdHocConfigBuilder.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.connector; + +import java.util.Map; +import java.util.Properties; + +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; + +/** + * + */ +class AdHocConfigBuilder { + private Config config; + private Properties properties = new Properties(); + + private AdHocConfigBuilder(Config config) { + this.config = config.detach(); + } + + static AdHocConfigBuilder from(Config config) { + return new AdHocConfigBuilder(config); + } + + AdHocConfigBuilder put(String key, String value) { + properties.setProperty(key, value); + return this; + } + + AdHocConfigBuilder putAll(Config configToPut) { + properties.putAll(configToPut.detach().asMap().orElse(Map.of())); + return this; + } + + org.eclipse.microprofile.config.Config build() { + Config newConfig = Config.builder(ConfigSources.create(properties), ConfigSources.create(config)) + .disableEnvironmentVariablesSource() + .disableSystemPropertiesSource() + .build(); + return (org.eclipse.microprofile.config.Config) newConfig; + } +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/ConfigurableConnector.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/ConfigurableConnector.java new file mode 100644 index 00000000000..f7329e82e8c --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/ConfigurableConnector.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.connector; + +import javax.enterprise.inject.spi.DeploymentException; + +import io.helidon.config.Config; +import io.helidon.config.ConfigValue; + +import org.eclipse.microprofile.reactive.messaging.spi.ConnectorFactory; + +interface ConfigurableConnector { + + String getConnectorName(); + + Config getRootConfig(); + + Config getChannelsConfig(); + + default org.eclipse.microprofile.config.Config getConnectorConfig(String channelName) { + Config channelConfig = getChannelsConfig() + .get(channelName); + ConfigValue connectorName = channelConfig + .get("connector") + .asString(); + + if (!connectorName.isPresent()) { + throw new DeploymentException(String + .format("No connector configured for channel %s", channelName)); + } + if (!connectorName.get().equals(getConnectorName())) { + throw new DeploymentException(String + .format("Connector name miss match for channel%s", channelName)); + } + + Config connectorConfig = getRootConfig() + .get("mp.messaging.connector") + .get(connectorName.get()); + + return AdHocConfigBuilder + .from(channelConfig) + //It seams useless but its required by the spec + .put(ConnectorFactory.CHANNEL_NAME_ATTRIBUTE, channelName) + .putAll(connectorConfig) + .build(); + } +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/IncomingConnector.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/IncomingConnector.java new file mode 100644 index 00000000000..1d20bc004bc --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/IncomingConnector.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.connector; + +import java.util.HashMap; +import java.util.Map; + +import io.helidon.config.Config; +import io.helidon.microprofile.messaging.channel.ChannelRouter; + +import org.eclipse.microprofile.reactive.messaging.spi.OutgoingConnectorFactory; +import org.reactivestreams.Subscriber; + +/** + * Connector as defined in configuration. + *
+ *
{@code
+ * mp.messaging.incoming.[channel-name].connector=[connector-name]
+ * ...
+ * mp.messaging.outgoing.[channel-name].connector=[connector-name]
+ * ...
+ * mp.messaging.connector.[connector-name].[attribute]=[value]
+ * ...
+ * }
+ */ +public class IncomingConnector implements SubscribingConnector { + + private final Config config; + private String connectorName; + private OutgoingConnectorFactory connectorFactory; + private Map subscriberMap = new HashMap<>(); + + /** + * Create new {@link IncomingConnector}. + * + * @param connectorName {@code [connector-name]} as defined in config + * @param connectorFactory actual instance of connector bean found in cdi context + * with annotation {@link org.eclipse.microprofile.reactive.messaging.spi.Connector} + * @param router {@link io.helidon.microprofile.messaging.channel.ChannelRouter} main orchestrator with root config + */ + public IncomingConnector(String connectorName, OutgoingConnectorFactory connectorFactory, ChannelRouter router) { + this.connectorName = connectorName; + this.connectorFactory = connectorFactory; + this.config = router.getConfig(); + } + + @Override + public Subscriber getSubscriber(String channelName) { + Subscriber subscriber = subscriberMap.get(channelName); + if (subscriber == null) { + subscriber = connectorFactory.getSubscriberBuilder(getConnectorConfig(channelName)).build(); + subscriberMap.put(channelName, subscriber); + } + return subscriber; + } + + @Override + public String getConnectorName() { + return connectorName; + } + + @Override + public Config getRootConfig() { + return config; + } + + +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/OutgoingConnector.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/OutgoingConnector.java new file mode 100644 index 00000000000..bb930080862 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/OutgoingConnector.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.connector; + +import java.util.HashMap; +import java.util.Map; + +import io.helidon.config.Config; +import io.helidon.microprofile.messaging.channel.ChannelRouter; + +import org.eclipse.microprofile.reactive.messaging.spi.IncomingConnectorFactory; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +/** + * Connector as defined in configuration. + *
+ *
{@code
+ * mp.messaging.incoming.[channel-name].connector=[connector-name]
+ * ...
+ * mp.messaging.outgoing.[channel-name].connector=[connector-name]
+ * ...
+ * mp.messaging.connector.[connector-name].[attribute]=[value]
+ * ...
+ * }
+ */ +public class OutgoingConnector implements PublishingConnector { + + private final Config config; + private String connectorName; + private IncomingConnectorFactory connectorFactory; + private Map publisherMap = new HashMap<>(); + + /** + * Create new {@link OutgoingConnector}. + * + * @param connectorName {@code [connector-name]} as defined in config + * @param connectorFactory actual instance of connector bean found in cdi context + * with annotation {@link org.eclipse.microprofile.reactive.messaging.spi.Connector} + * @param router {@link io.helidon.microprofile.messaging.channel.ChannelRouter} main orchestrator with root config + */ + public OutgoingConnector(String connectorName, IncomingConnectorFactory connectorFactory, ChannelRouter router) { + this.connectorName = connectorName; + this.connectorFactory = connectorFactory; + this.config = router.getConfig(); + } + + @Override + public String getConnectorName() { + return connectorName; + } + + @Override + public Config getRootConfig() { + return config; + } + + @Override + public Publisher getPublisher(String channelName) { + Publisher publisher = publisherMap.get(channelName); + if (publisher == null) { + publisher = connectorFactory + .getPublisherBuilder(getConnectorConfig(channelName)) + .buildRs(); + publisherMap.put(channelName, publisher); + } + return publisher; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(String channelName, Subscriber subscriber) { + getPublisher(channelName).subscribe(subscriber); + } +} diff --git a/common/reactive/src/main/java/io/helidon/common/reactive/valve/IteratorValve.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/PublishingConnector.java similarity index 52% rename from common/reactive/src/main/java/io/helidon/common/reactive/valve/IteratorValve.java rename to microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/PublishingConnector.java index 1dcd05042e7..991b79857d5 100644 --- a/common/reactive/src/main/java/io/helidon/common/reactive/valve/IteratorValve.java +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/PublishingConnector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -12,25 +12,24 @@ * 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 io.helidon.common.reactive.valve; - -import java.util.Iterator; +package io.helidon.microprofile.messaging.connector; -/** - * The {@link Valve} implementation for {@link Iterator}. - */ -class IteratorValve extends RetryingPausableRegistry implements Valve { +import io.helidon.config.Config; - private final Iterator iterator; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; - IteratorValve(Iterator iterator) { - this.iterator = iterator; - } +interface PublishingConnector extends ConfigurableConnector { @Override - protected T moreData() { - return iterator.hasNext() ? iterator.next() : null; + default Config getChannelsConfig() { + return getRootConfig().get("mp.messaging.incoming"); } + + Publisher getPublisher(String channelName); + + void subscribe(String channelName, Subscriber subscriber); } diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/SubscribingConnector.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/SubscribingConnector.java new file mode 100644 index 00000000000..dad6e3a8866 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/SubscribingConnector.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.connector; + +import io.helidon.config.Config; + +import org.reactivestreams.Subscriber; + +interface SubscribingConnector extends ConfigurableConnector { + + @Override + default Config getChannelsConfig() { + return getRootConfig().get("mp.messaging.outgoing"); + } + + Subscriber getSubscriber(String channelName); +} diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/package-info.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/package-info.java new file mode 100644 index 00000000000..7d7837deac0 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/connector/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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. + * + */ + +/** + * MicroProfile Reactive Messaging connectors abstraction model orchestrated by + * {@link io.helidon.microprofile.messaging.channel.ChannelRouter}. + */ +package io.helidon.microprofile.messaging.connector; diff --git a/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/package-info.java b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/package-info.java new file mode 100644 index 00000000000..a0ac1f76f72 --- /dev/null +++ b/microprofile/messaging/src/main/java/io/helidon/microprofile/messaging/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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. + * + */ + +/** + * MicroProfile Reactive Messaging implementation. + */ +package io.helidon.microprofile.messaging; diff --git a/microprofile/messaging/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/microprofile/messaging/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension new file mode 100644 index 00000000000..463018c73d9 --- /dev/null +++ b/microprofile/messaging/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -0,0 +1,17 @@ +# +# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# +# Licensed 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. +# + +io.helidon.microprofile.messaging.MessagingCdiExtension \ No newline at end of file diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/AbstractCDITest.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/AbstractCDITest.java new file mode 100644 index 00000000000..c98e9a34a61 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/AbstractCDITest.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.logging.LogManager; +import java.util.stream.Collectors; + +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; + +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import io.helidon.microprofile.server.Server; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +public abstract class AbstractCDITest { + + static { + try (InputStream is = AbstractCDITest.class.getResourceAsStream("/logging.properties")) { + LogManager.getLogManager().readConfiguration(is); + } catch (IOException e) { + fail(e); + } + } + + protected SeContainer cdiContainer; + + protected Map cdiConfig() { + return Collections.emptyMap(); + } + + protected void cdiBeanClasses(Set> classes) { + + } + + @BeforeEach + public void setUp() { + Set> classes = new HashSet<>(); + cdiBeanClasses(classes); + Map p = new HashMap<>(cdiConfig()); + cdiContainer = startCdiContainer(p, classes); + } + + @AfterEach + public void tearDown() { + if (cdiContainer != null) { + cdiContainer.close(); + } + } + + + protected void forEachBean(Class beanType, Annotation annotation, Consumer consumer) { + cdiContainer.select(beanType, annotation).stream().forEach(consumer); + } + + protected void assertAllReceived(CountableTestBean bean) { + try { + assertTrue(bean.getTestLatch().await(2, TimeUnit.SECONDS) + , "All messages not delivered in time, number of unreceived messages: " + + bean.getTestLatch().getCount()); + } catch (InterruptedException e) { + fail(e); + } + } + + protected static SeContainer startCdiContainer(Map p, Class... beanClasses) { + return startCdiContainer(p, new HashSet<>(Arrays.asList(beanClasses))); + } + + private static SeContainer startCdiContainer(Map p, Set> beanClasses) { + Config config = Config.builder() + .sources(ConfigSources.create(p)) + .build(); + + final Server.Builder builder = Server.builder(); + assertNotNull(builder); + builder.config(config); + ConfigProviderResolver.instance() + .registerConfig((org.eclipse.microprofile.config.Config) config, Thread.currentThread().getContextClassLoader()); + final SeContainerInitializer initializer = SeContainerInitializer.newInstance(); + assertNotNull(initializer); + initializer.addBeanClasses(beanClasses.toArray(new Class[0])); + return initializer.initialize(); + } + + protected static final class CdiTestCase { + private String name; + private Class[] clazzes; + + private CdiTestCase(String name, Class... clazzes) { + this.name = name; + this.clazzes = clazzes; + } + + public static CdiTestCase from(Class clazz) { + return new CdiTestCase(clazz.getSimpleName(), clazz); + } + + public static CdiTestCase from(String name, Class... clazzes) { + return new CdiTestCase(name, clazzes); + } + + @Override + public String toString() { + return name; + } + + public Class[] getClazzes() { + return clazzes; + } + + public Optional> getExpectedThrowable() { + return Arrays.stream(clazzes) + .filter(c -> c.getAnnotation(AssertThrowException.class) != null) + .map(c -> c.getAnnotation(AssertThrowException.class).value()) + .findFirst(); + } + + @SuppressWarnings("unchecked") + public List> getCountableBeanClasses() { + return Arrays.stream(clazzes) + .filter(CountableTestBean.class::isAssignableFrom) + .map(c -> (Class) c) + .collect(Collectors.toList()); + } + + @SuppressWarnings("unchecked") + public List> getCompletableBeanClasses() { + return Arrays.stream(clazzes) + .filter(AssertableTestBean.class::isAssignableFrom) + .map(c -> (Class) c) + .collect(Collectors.toList()); + } + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/AssertThrowException.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/AssertThrowException.java new file mode 100644 index 00000000000..06962e6dc8f --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/AssertThrowException.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target({TYPE}) +@Retention(RUNTIME) +public @interface AssertThrowException { + Class value(); +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/AssertableTestBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/AssertableTestBean.java new file mode 100644 index 00000000000..6c7a1b83ae5 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/AssertableTestBean.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public interface AssertableTestBean { + public static Set TEST_DATA = new HashSet<>(Arrays.asList("teST1", "TEst2", "tESt3")); + + void assertValid(); +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/CountableTestBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/CountableTestBean.java new file mode 100644 index 00000000000..9032cff2959 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/CountableTestBean.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging; + +import java.util.concurrent.CountDownLatch; + +public interface CountableTestBean { + CountDownLatch getTestLatch(); +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/channel/MessageUtilsTest.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/channel/MessageUtilsTest.java new file mode 100644 index 00000000000..b3a7e5d04b1 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/channel/MessageUtilsTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.concurrent.ExecutionException; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.microprofile.reactive.messaging.Message; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class MessageUtilsTest { + + @SuppressWarnings("unchecked") + static Stream testSource() { + return Stream.of( + + Tuple.of(5L, Long.class), + Tuple.of(5L, Message.class), + Tuple.of(5, Integer.class), + Tuple.of(5, Message.class), + Tuple.of(Double.parseDouble("50"), Double.class), + Tuple.of(Double.parseDouble("50"), Message.class), + Tuple.of(BigInteger.TEN, BigInteger.class), + Tuple.of(BigInteger.TEN, Message.class), + Tuple.of("test", String.class), + Tuple.of("test", Message.class), + Tuple.of(Message.of("test"), String.class), + Tuple.of(Message.of("test"), Message.class), + Tuple.of(Message.of(5L), Long.class), + Tuple.of(Message.of(5), Integer.class), + Tuple.of(Message.of(BigInteger.TEN), BigInteger.class), + Tuple.of(Message.of(BigDecimal.TEN), BigDecimal.class) + + ); + } + + @ParameterizedTest + @MethodSource("testSource") + void wrapperTest(Tuple tuple) throws ExecutionException, InterruptedException { + assertExpectedType(tuple.value, tuple.type); + } + + private static void assertExpectedType(Object value, Class type) throws ExecutionException, InterruptedException { + Object unwrapped = MessageUtils.unwrap(value, type); + assertTrue(type.isAssignableFrom(unwrapped.getClass()), + String.format("Expected value of type %s got %s instead", type.getSimpleName(), value.getClass().getSimpleName())); + } + + private static class Tuple { + private Object value; + private Class type; + + private Tuple(Object value, Class clazz) { + this.value = value; + this.type = clazz; + } + + static Tuple of(Object o, Class clazz) { + return new Tuple(o, clazz); + } + + public Object getValue() { + return value; + } + + public Class getType() { + return type; + } + + @Override + public String toString() { + return value.getClass().getSimpleName() + " -> " + type.getSimpleName(); + } + } +} \ No newline at end of file diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/channel/MethodSignatureResolverTest.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/channel/MethodSignatureResolverTest.java new file mode 100644 index 00000000000..9ed5b8fd02b --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/channel/MethodSignatureResolverTest.java @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletionStage; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.helidon.microprofile.reactive.hybrid.HybridPublisher; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +class MethodSignatureResolverTest { + + @Incoming("in-channel-name") + @ExpectedSignatureType(MethodSignatureType.INCOMING_COMPLETION_STAGE_2_MSG) + CompletionStage incoming_completion_stage_2_msg(Message msg) { + return null; + } + + @Incoming("in-channel-name") + @ExpectedSignatureType(MethodSignatureType.INCOMING_COMPLETION_STAGE_2_PAYL) + CompletionStage incoming_completion_stage_2_payl(String payload) { + return null; + } + + @Incoming("in-channel-name") + @ExpectedSignatureType(MethodSignatureType.INCOMING_SUBSCRIBER_BUILDER_MSG_2_VOID) + SubscriberBuilder, Void> incoming_subscriber_builder_msg_2_void() { + return null; + } + + @Incoming("in-channel-name") + @ExpectedSignatureType(MethodSignatureType.INCOMING_SUBSCRIBER_BUILDER_PAYL_2_VOID) + SubscriberBuilder incoming_subscriber_builder_payl_2_void() { + return null; + } + + @Incoming("in-channel-name") + @ExpectedSignatureType(MethodSignatureType.INCOMING_SUBSCRIBER_MSG_2_VOID) + Subscriber> incoming_subscriber_msg_2_void() { + return null; + } + + @Incoming("in-channel-name") + @ExpectedSignatureType(MethodSignatureType.INCOMING_SUBSCRIBER_PAYL_2_VOID) + Subscriber incoming_subscriber_payl_2_void() { + return null; + } + + @Incoming("in-channel-name") + @ExpectedSignatureType(MethodSignatureType.INCOMING_VOID_2_PAYL) + void incoming_void_2_payl(String payload) { + } + + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.OUTGOING_PUBLISHER_BUILDER_MSG_2_VOID) + PublisherBuilder> outgoing_publisher_builder_msg_2_void() { + return null; + } + + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.OUTGOING_PUBLISHER_BUILDER_PAYL_2_VOID) + PublisherBuilder outgoing_publisher_builder_payl_2_void() { + return null; + } + + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.OUTGOING_PUBLISHER_MSG_2_VOID) + Publisher> outgoing_publisher_msg_2_void() { + return null; + } + + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.OUTGOING_PUBLISHER_PAYL_2_VOID) + Publisher outgoing_publisher_payl_2_void() { + return null; + } + + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.OUTGOING_COMPLETION_STAGE_MSG_2_VOID) + CompletionStage> outgoing_completion_stage_msg_2_void() { + return null; + } + + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.OUTGOING_COMPLETION_STAGE_PAYL_2_VOID) + CompletionStage outgoing_completion_stage_payl_2_void() { + return null; + } + + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.OUTGOING_MSG_2_VOID) + Message outgoing_msg_2_void() { + return null; + } + + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.OUTGOING_PAYL_2_VOID) + String outgoing_payl_2_void() { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_PROCESSOR_BUILDER_MSG_2_VOID) + ProcessorBuilder, Message> processor_processor_builder_msg_2_void() { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_PROCESSOR_BUILDER_PAYL_2_VOID) + ProcessorBuilder processor_processor_builder_payl_2_void() { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_PROCESSOR_MSG_2_VOID) + Processor, Message> processor_processor_msg_2_void() { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_PROCESSOR_PAYL_2_VOID) + Processor processor_processor_payl_2_void() { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_PUBLISHER_BUILDER_MSG_2_PUBLISHER_BUILDER_MSG) + PublisherBuilder> processor_publisher_builder_msg_2_publisher_builder_msg(PublisherBuilder> pub) { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_PUBLISHER_BUILDER_PAYL_2_PUBLISHER_BUILDER_PAYL) + PublisherBuilder processor_publisher_builder_payl_2_publisher_builder_payl(PublisherBuilder pub) { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_PUBLISHER_BUILDER_MSG_2_MSG) + PublisherBuilder> processor_publisher_builder_msg_2_msg(Message msg) { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_PUBLISHER_BUILDER_PAYL_2_PAYL) + PublisherBuilder processor_publisher_builder_payl_2_payl(String payload) { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_PUBLISHER_MSG_2_PUBLISHER_MSG) + Publisher> processor_publisher_msg_2_publisher_msg(Publisher> pub) { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_PUBLISHER_PAYL_2_PUBLISHER_PAYL) + Publisher processor_publisher_payl_2_publisher_payl(Publisher pub) { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_PUBLISHER_MSG_2_MSG) + Publisher> processor_publisher_msg_2_msg(Message msg) { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_PUBLISHER_PAYL_2_PAYL) + Publisher processor_publisher_payl_2_payl(String payload) { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_COMPL_STAGE_MSG_2_MSG) + CompletionStage> processor_compl_stage_msg_2_msg(Message msg) { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_COMPL_STAGE_PAYL_2_PAYL) + CompletionStage processor_compl_stage_payl_2_payl(String payload) { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_MSG_2_MSG) + Message processor_msg_2_msg(Message msg) { + return null; + } + + @Incoming("in-channel-name") + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.PROCESSOR_PAYL_2_PAYL) + String processor_payl_2_payl(String payload) { + return null; + } + + @Outgoing("out-channel-name") + @ExpectedSignatureType(MethodSignatureType.OUTGOING_PUBLISHER_PAYL_2_VOID) + public HybridPublisher extendedPublisher() { + return null; + } + + private static Stream locateTestMethods() { + return Arrays.stream(MethodSignatureResolverTest.class.getDeclaredMethods()) + .filter(m -> Objects.nonNull(m.getAnnotation(ExpectedSignatureType.class))) + .sorted(Comparator.comparing(Method::getName)) + .map(MethodTestCase::new); + } + + @ParameterizedTest + @MethodSource("locateTestMethods") + void signatureResolving(MethodTestCase testCase) { + assertEquals(testCase.expectedType, MethodSignatureResolver.create(testCase.m).resolve()); + } + + @Test + void testSignatureResolvingCoverage() { + Set testedTypes = locateTestMethods().map(m -> m.expectedType).collect(Collectors.toSet()); + Set unTestedTypes = Arrays.stream(MethodSignatureType.values()) + .filter(o -> !testedTypes.contains(o)) + .map(MethodSignatureType::name) + .collect(Collectors.toSet()); + if (!unTestedTypes.isEmpty()) { + fail("No test found for signature types: \n" + String.join("\n", unTestedTypes)); + } + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + private @interface ExpectedSignatureType { + MethodSignatureType value(); + } + + private static class MethodTestCase { + private final MethodSignatureType expectedType; + private final Method m; + + MethodTestCase(Method m) { + this.m = m; + this.expectedType = m.getAnnotation(ExpectedSignatureType.class).value(); + } + + @Override + public String toString() { + return expectedType.name(); + } + } +} \ No newline at end of file diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/channel/SignatureTypeConsistencyTest.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/channel/SignatureTypeConsistencyTest.java new file mode 100644 index 00000000000..610a2b8318d --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/channel/SignatureTypeConsistencyTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class SignatureTypeConsistencyTest { + + /** + * To keep consistency between {@link io.helidon.microprofile.messaging.channel.MethodSignatureType} + * and {@link MethodSignatureResolver} + */ + @Test + void isTypeUsedByResolver() throws IOException { + String srcFileName = String.format("%s.java", MethodSignatureResolver.class.getName().replaceAll("\\.", File.separator)); + Path resolverSrcPath = Paths.get("src", "main", "java", srcFileName); + String resolverSrc = new String(Files.readAllBytes(resolverSrcPath)); + + Arrays.stream(MethodSignatureType.values()) + .map(signatureType -> MethodSignatureType.class.getSimpleName() + "." + signatureType.name()) + .filter(token -> !resolverSrc.contains(token)) + .map(token -> String.format("Unused signature type, token %s not found in file %s", token, resolverSrcPath)) + .forEach(Assertions::fail); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/channel/UnwrapProcessorTest.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/channel/UnwrapProcessorTest.java new file mode 100644 index 00000000000..11e0dead413 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/channel/UnwrapProcessorTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.channel; + + +import java.lang.reflect.Method; +import java.util.concurrent.ExecutionException; +import java.util.stream.Stream; + + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.reactivestreams.Subscriber; + +public class UnwrapProcessorTest { + + public SubscriberBuilder testMethodSubscriberBuilderString() { + return ReactiveStreams.builder().forEach(System.out::println); + } + + public SubscriberBuilder, Void> testMethodSubscriberBuilderMessage() { + return ReactiveStreams.>builder().forEach(System.out::println); + } + + public Subscriber testMethodSubscriberString() { + return ReactiveStreams.builder().forEach(System.out::println).build(); + } + + public Subscriber> testMethodSubscriberMessage() { + return ReactiveStreams.>builder().forEach(System.out::println).build(); + } + + static Stream methodSource() { + return Stream.of(UnwrapProcessorTest.class.getDeclaredMethods()) + .filter(m -> m.getName().startsWith("testMethod")); + } + + @ParameterizedTest + @MethodSource("methodSource") + void innerChannelBeanTest(Method method) throws ExecutionException, InterruptedException { + UnwrapProcessor unwrapProcessor = new UnwrapProcessor(); + unwrapProcessor.setMethod(method); + Object unwrappedValue = unwrapProcessor.unwrap(Message.of("test")); + if (method.getName().endsWith("Message")) { + Assertions.assertTrue(MessageUtils.isMessageType(method)); + assertTrue(unwrappedValue instanceof Message); + } else { + assertFalse(MessageUtils.isMessageType(method)); + assertFalse(unwrappedValue instanceof Message); + } + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/AdHocConfigBuilderTest.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/AdHocConfigBuilderTest.java new file mode 100644 index 00000000000..c5aae551091 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/AdHocConfigBuilderTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.connector; + +import java.util.Map; + +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class AdHocConfigBuilderTest { + + private static final String TEST_TOPIC_CONFIG = "TEST_TOPIC_CONFIG"; + private static final String TEST_TOPIC_CUSTOM = "TEST_TOPIC_CUSTOM"; + private static final String TEST_KEY = "TEST_KEY"; + private static final String ADDITION_ATTR_1 = "addition-attr1"; + private static final String ADDITION_ATTR_2 = "addition-attr2"; + private static final String ADDITION_ATTR_1_VALUE = "addition-attr1-value"; + private static final String ADDITION_ATTR_2_VALUE = "addition-attr2-value"; + private static final String TEST_CONNECTOR = "test-connector"; + + @Test + void currentContext() { + Map propMap = Map.of( + "mp.messaging.outcoming.test-channel.key.serializer", AdHocConfigBuilderTest.class.getName() + ); + + Config config = Config.builder() + .sources(ConfigSources.create(propMap)) + .build(); + + org.eclipse.microprofile.config.Config c = AdHocConfigBuilder + .from(config.get("mp.messaging.outcoming.test-channel")) + .put(TEST_KEY, TEST_TOPIC_CUSTOM) + .build(); + + assertEquals(TEST_TOPIC_CUSTOM, c.getValue(TEST_KEY, String.class)); + assertEquals(AdHocConfigBuilderTest.class.getName(), c.getValue("key.serializer", String.class)); + } + + @Test + void customValueOverride() { + Map propMap = Map.of( + "mp.messaging.outcoming.test-channel." + TEST_KEY, TEST_TOPIC_CONFIG, + "mp.messaging.outcoming.test-channel.key.serializer", AdHocConfigBuilderTest.class.getName() + ); + + Config config = Config.builder() + .sources(ConfigSources.create(propMap)) + .build(); + + org.eclipse.microprofile.config.Config c = AdHocConfigBuilder + .from(config.get("mp.messaging.outcoming.test-channel")) + .put(TEST_KEY, TEST_TOPIC_CUSTOM) + .build(); + + assertEquals(TEST_TOPIC_CUSTOM, c.getValue(TEST_KEY, String.class)); + } + + @Test + void putAllTest() { + Map propMap = Map.of( + "mp.messaging.outcoming.test-channel." + TEST_KEY, TEST_TOPIC_CONFIG + ); + + Map propMap2 = Map.of( + "mp.messaging.connector." + TEST_CONNECTOR + "." + ADDITION_ATTR_1, ADDITION_ATTR_1_VALUE, + "mp.messaging.connector." + TEST_CONNECTOR + "." + ADDITION_ATTR_2, ADDITION_ATTR_2_VALUE + ); + + Config config = Config.builder(ConfigSources.create(propMap)) + .disableEnvironmentVariablesSource() + .disableSystemPropertiesSource() + .build(); + Config config2 = Config.builder(ConfigSources.create(propMap2)) + .disableEnvironmentVariablesSource() + .disableSystemPropertiesSource() + .build(); + + org.eclipse.microprofile.config.Config c = AdHocConfigBuilder + .from(config.get("mp.messaging.outcoming.test-channel")) + .putAll(config2.get("mp.messaging.connector." + TEST_CONNECTOR)) + .build(); + + assertEquals(ADDITION_ATTR_1_VALUE, c.getValue(ADDITION_ATTR_1, String.class)); + assertEquals(ADDITION_ATTR_2_VALUE, c.getValue(ADDITION_ATTR_2, String.class)); + } +} \ No newline at end of file diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/ConnectedBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/ConnectedBean.java new file mode 100644 index 00000000000..2016faefb34 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/ConnectedBean.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.connector; + +import org.eclipse.microprofile.reactive.messaging.Incoming; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ApplicationScoped +public class ConnectedBean { + + public static final CountDownLatch LATCH = new CountDownLatch(IterableConnector.TEST_DATA.length); + + @Incoming("iterable-channel-in") + public void receiveMethod(String msg) { + assertTrue(Arrays.asList(IterableConnector.TEST_DATA).contains(msg)); + LATCH.countDown(); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/ConnectedOnlyProcessorBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/ConnectedOnlyProcessorBean.java new file mode 100644 index 00000000000..28cbe0d6bb0 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/ConnectedOnlyProcessorBean.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.connector; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class ConnectedOnlyProcessorBean { + + @Incoming("iterable-channel-in") + @Outgoing("iterable-channel-out") + public String process(String msg) { + return msg; + } + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/ConnectedProcessorBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/ConnectedProcessorBean.java new file mode 100644 index 00000000000..331b7dea5a4 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/ConnectedProcessorBean.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.connector; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.Arrays; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ApplicationScoped +public class ConnectedProcessorBean { + + public static final CountDownLatch LATCH = new CountDownLatch(IterableConnector.TEST_DATA.length); + private static final Set PROCESSED_DATA = + Arrays.stream(IterableConnector.TEST_DATA) + .map(ConnectedProcessorBean::reverseString) + .collect(Collectors.toSet()); + + @Incoming("iterable-channel-in") + @Outgoing("inner-channel") + public String process(String msg) { + return reverseString(msg); + } + + @Incoming("inner-channel") + public void receive(String msg) { + assertTrue(PROCESSED_DATA.contains(msg)); + LATCH.countDown(); + } + + private static String reverseString(String msg) { + return new StringBuilder(msg).reverse().toString(); + } + + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/ConnectorTest.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/ConnectorTest.java new file mode 100644 index 00000000000..6fb312846ec --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/ConnectorTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.connector; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.enterprise.inject.spi.DeploymentException; + +import io.helidon.microprofile.messaging.AbstractCDITest; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class ConnectorTest extends AbstractCDITest { + + @Override + public void setUp() { + //Starting container manually + } + + @Test + void connectorTest() throws InterruptedException { + cdiContainer = startCdiContainer( + Map.of("mp.messaging.incoming.iterable-channel-in.connector", "iterable-connector"), + IterableConnector.class, + ConnectedBean.class); + assertTrue(ConnectedBean.LATCH.await(2, TimeUnit.SECONDS)); + } + + @Test + void connectorWithProcessorTest() throws InterruptedException { + cdiContainer = startCdiContainer( + Map.of("mp.messaging.incoming.iterable-channel-in.connector", "iterable-connector"), + IterableConnector.class, + ConnectedProcessorBean.class); + assertTrue(ConnectedProcessorBean.LATCH.await(2, TimeUnit.SECONDS)); + } + + @Test + void connectorWithProcessorOnlyTest() throws InterruptedException { + Map p = Map.of( + "mp.messaging.incoming.iterable-channel-in.connector", "iterable-connector", + "mp.messaging.outgoing.iterable-channel-out.connector", "iterable-connector"); + cdiContainer = startCdiContainer(p, IterableConnector.class, ConnectedOnlyProcessorBean.class); + assertTrue(IterableConnector.LATCH.await(2, TimeUnit.SECONDS)); + } + + @Test + void missingConnectorTest() { + assertThrows(DeploymentException.class, () -> + cdiContainer = startCdiContainer( + Map.of("mp.messaging.incoming.iterable-channel-in.connector", "iterable-connector"), + ConnectedBean.class)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/IterableConnector.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/IterableConnector.java new file mode 100644 index 00000000000..935492b4c2c --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/connector/IterableConnector.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.connector; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.spi.Connector; +import org.eclipse.microprofile.reactive.messaging.spi.IncomingConnectorFactory; +import org.eclipse.microprofile.reactive.messaging.spi.OutgoingConnectorFactory; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.Arrays; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ApplicationScoped +@Connector("iterable-connector") +public class IterableConnector implements IncomingConnectorFactory, OutgoingConnectorFactory { + + public static final String[] TEST_DATA = {"test1", "test2", "test3"}; + public static final CountDownLatch LATCH = new CountDownLatch(TEST_DATA.length); + private static final Set PROCESSED_DATA = + Arrays.stream(IterableConnector.TEST_DATA).collect(Collectors.toSet()); + + @Override + public PublisherBuilder> getPublisherBuilder(Config config) { + //TODO: use ReactiveStreams.of().map when engine is ready(supports more than one stage) + return ReactiveStreams.fromIterable(Arrays.stream(TEST_DATA).map(Message::of).collect(Collectors.toSet())); + } + + @Override + public SubscriberBuilder, Void> getSubscriberBuilder(Config config) { + return ReactiveStreams.>builder().forEach(m -> { + assertTrue(PROCESSED_DATA.contains(m.getPayload())); + LATCH.countDown(); + }); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/AbstractShapeTestBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/AbstractShapeTestBean.java new file mode 100644 index 00000000000..cfeccac854a --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/AbstractShapeTestBean.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import io.helidon.microprofile.messaging.CountableTestBean; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; + +public abstract class AbstractShapeTestBean implements CountableTestBean { + + public static Set TEST_DATA = new HashSet<>(Arrays.asList("teST1", "TEst2", "tESt3")); + public static Set TEST_INT_DATA = new HashSet<>(Arrays.asList(1, 2, 3)); + + public static CountDownLatch testLatch = new CountDownLatch(TEST_DATA.size()); + + @Override + public CountDownLatch getTestLatch() { + return testLatch; + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/BadSignaturePublisherPayloadBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/BadSignaturePublisherPayloadBean.java new file mode 100644 index 00000000000..af325a890f7 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/BadSignaturePublisherPayloadBean.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import io.helidon.microprofile.messaging.AssertThrowException; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +//TODO: Uncomment when TCK issue is solved https://github.com/eclipse/microprofile-reactive-messaging/issues/79 +//@AssertThrowException(Exception.class) +public class BadSignaturePublisherPayloadBean extends AbstractShapeTestBean { + + @Outgoing("string-payload") + public Publisher> sourceForStringPayload() { + return ReactiveStreams.fromIterable(TEST_DATA).map(Message::of).buildRs(); + } + + @Incoming("string-payload") + public String consumePayloadsAndReturnSomething(String payload) { + testLatch.countDown(); + return payload; + } + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ByRequestProcessorV1Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ByRequestProcessorV1Bean.java new file mode 100644 index 00000000000..ecfee767f6d --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ByRequestProcessorV1Bean.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import io.helidon.microprofile.reactive.MultiRS; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.concurrent.CountDownLatch; +import java.util.stream.IntStream; + +@ApplicationScoped +public class ByRequestProcessorV1Bean { + + public static CountDownLatch testLatch = new CountDownLatch(10); + + @Outgoing("inner-processor") + public Publisher produceMessage() { + return MultiRS.just(IntStream.range(0, 10).boxed()); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + public int process(int i) { + return i++; + } + + @Incoming("inner-consumer") + public void receiveMessage(int i) { + testLatch.countDown(); + } + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ByRequestProcessorV2Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ByRequestProcessorV2Bean.java new file mode 100644 index 00000000000..73f9cdca757 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ByRequestProcessorV2Bean.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import io.helidon.microprofile.messaging.CountableTestBean; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.concurrent.CountDownLatch; + +@ApplicationScoped +public class ByRequestProcessorV2Bean implements CountableTestBean { + + public static CountDownLatch testLatch = new CountDownLatch(10); + + @Outgoing("publisher-synchronous-payload") + public PublisherBuilder streamForProcessorOfPayloads() { + return ReactiveStreams.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + } + + @Incoming("publisher-synchronous-payload") + @Outgoing("synchronous-payload") + public String payloadSynchronous(int value) { + return Integer.toString(value + 1); + } + + @Incoming("synchronous-payload") + public void getMessgesFromProcessorOfPayloads(String value) { + getTestLatch().countDown(); + } + + @Override + public CountDownLatch getTestLatch() { + return testLatch; + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ByRequestProcessorV3Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ByRequestProcessorV3Bean.java new file mode 100644 index 00000000000..d38c5f930e7 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ByRequestProcessorV3Bean.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import io.helidon.microprofile.messaging.CountableTestBean; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.concurrent.CountDownLatch; + +@ApplicationScoped +public class ByRequestProcessorV3Bean implements CountableTestBean { + + public static CountDownLatch testLatch = new CountDownLatch(10); + + @Outgoing("publisher-synchronous-message") + public PublisherBuilder streamForProcessorOfMessages() { + return ReactiveStreams.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + } + + @Incoming("publisher-synchronous-message") + @Outgoing("synchronous-message") + public Message messageSynchronous(Message message) { + return Message.of(Integer.toString(message.getPayload() + 1)); + } + + @Incoming("synchronous-message") + public void getMessgesFromProcessorOfMessages(String value) { + getTestLatch().countDown(); + } + + @Override + public CountDownLatch getTestLatch() { + return testLatch; + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ByRequestProcessorV4Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ByRequestProcessorV4Bean.java new file mode 100644 index 00000000000..3c2d6512fbe --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ByRequestProcessorV4Bean.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import io.helidon.microprofile.messaging.CountableTestBean; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; + +@ApplicationScoped +public class ByRequestProcessorV4Bean implements CountableTestBean { + + public static CountDownLatch testLatch = new CountDownLatch(10); + + @Outgoing("publisher-asynchronous-message") + public PublisherBuilder streamForProcessorBuilderOfMessages() { + return ReactiveStreams.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + } + + @Incoming("publisher-asynchronous-message") + @Outgoing("asynchronous-message") + public CompletionStage> messageAsynchronous(Message message) { + return CompletableFuture.supplyAsync(() -> Message.of(Integer.toString(message.getPayload() + 1)), Executors.newSingleThreadExecutor()); + } + + @Incoming("asynchronous-message") + public void getMessgesFromProcessorBuilderOfMessages(String value) { + getTestLatch().countDown(); + } + + @Override + public CountDownLatch getTestLatch() { + return testLatch; + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ByRequestProcessorV5Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ByRequestProcessorV5Bean.java new file mode 100644 index 00000000000..5f275ffc652 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ByRequestProcessorV5Bean.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import io.helidon.microprofile.messaging.CountableTestBean; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; + +@ApplicationScoped +public class ByRequestProcessorV5Bean implements CountableTestBean { + + public static CountDownLatch testLatch = new CountDownLatch(10); + + @Outgoing("publisher-asynchronous-payload") + public PublisherBuilder streamForProcessorBuilderOfPayloads() { + return ReactiveStreams.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + } + + @Incoming("publisher-asynchronous-payload") + @Outgoing("asynchronous-payload") + public CompletionStage payloadAsynchronous(int value) { + return CompletableFuture.supplyAsync(() -> Integer.toString(value + 1), Executors.newSingleThreadExecutor()); + } + + @Incoming("asynchronous-payload") + public void getMessgesFromProcessorBuilderOfPayloads(String value) { + getTestLatch().countDown(); + } + + @Override + public CountDownLatch getTestLatch() { + return testLatch; + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/CompletionStageV1Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/CompletionStageV1Bean.java new file mode 100644 index 00000000000..1ccc73cd32a --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/CompletionStageV1Bean.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Subscriber; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +@ApplicationScoped +public class CompletionStageV1Bean extends AbstractShapeTestBean { + + AtomicInteger testSequence = new AtomicInteger(); + + @Outgoing("generator-payload-async") + public CompletionStage getPayloadAsync() { + return CompletableFuture.supplyAsync(() -> testSequence.incrementAndGet(), Executors.newSingleThreadExecutor()); + } + +// @Outgoing("generator-payload-async") +// public Integer getPayloadAsyncs() { +// return testSequence.incrementAndGet(); +// } + + @Incoming("generator-payload-async") + public Subscriber getFromInfiniteAsyncPayloadGenerator() { + return ReactiveStreams.builder() + .limit(TEST_DATA.size()) + .forEach(s -> getTestLatch().countDown()) + .build(); + } + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/InnerChannelTest.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/InnerChannelTest.java new file mode 100644 index 00000000000..8328235b646 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/InnerChannelTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import java.util.Collections; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.spi.CDI; + +import io.helidon.microprofile.messaging.AbstractCDITest; +import io.helidon.microprofile.messaging.AssertableTestBean; +import io.helidon.microprofile.messaging.CountableTestBean; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.platform.commons.util.ClassFilter; +import org.junit.platform.commons.util.ReflectionUtils; + +public class InnerChannelTest extends AbstractCDITest { + + @Override + public void setUp() { + //Starting container manually + } + + static Stream testCaseSource() { + return ReflectionUtils + .findAllClassesInPackage( + InnerChannelTest.class.getPackage().getName(), + ClassFilter.of(c -> Objects.nonNull(c.getAnnotation(ApplicationScoped.class)))) + .stream().map(CdiTestCase::from); + } + + @ParameterizedTest + @MethodSource("testCaseSource") + void innerChannelBeanTest(CdiTestCase testCase) { + Optional> expectedThrowable = testCase.getExpectedThrowable(); + if (expectedThrowable.isPresent()) { + assertThrows(expectedThrowable.get(), () -> + cdiContainer = startCdiContainer(Collections.emptyMap(), testCase.getClazzes())); + } else { + cdiContainer = startCdiContainer(Collections.emptyMap(), testCase.getClazzes()); + testCase.getCountableBeanClasses().forEach(c -> { + CountableTestBean countableTestBean = CDI.current().select(c).get(); + // Wait till all messages are delivered + assertAllReceived(countableTestBean); + }); + testCase.getCompletableBeanClasses().forEach(c -> { + AssertableTestBean assertableTestBean = CDI.current().select(c).get(); + assertableTestBean.assertValid(); + }); + } + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/InnerProcessorBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/InnerProcessorBean.java new file mode 100644 index 00000000000..e1759f93acc --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/InnerProcessorBean.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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. + */ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import io.helidon.microprofile.messaging.CountableTestBean; +import io.helidon.microprofile.reactive.MultiRS; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; + +@ApplicationScoped +public class InnerProcessorBean implements CountableTestBean { + + public static Set TEST_DATA = new HashSet<>(Arrays.asList("test1", "test2", "test3")); + public static Set EXPECTED_DATA = TEST_DATA.stream() + .map(String::toUpperCase) + .collect(Collectors.toSet()); + public static CountDownLatch testLatch = new CountDownLatch(TEST_DATA.size()); + + @Outgoing("inner-processor") + public Publisher produceMessage() { + return MultiRS.just(TEST_DATA.stream()); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + public String process(String msg) { + return msg.toUpperCase(); + } + + @Incoming("inner-consumer") + public void receiveMessage(String msg) { + if (EXPECTED_DATA.contains(msg)) { + testLatch.countDown(); + } + } + + @Override + public CountDownLatch getTestLatch() { + return testLatch; + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/InternalChannelsBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/InternalChannelsBean.java new file mode 100644 index 00000000000..eb7442ef351 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/InternalChannelsBean.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import io.helidon.microprofile.messaging.CountableTestBean; +import io.helidon.microprofile.reactive.MultiRS; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; + +@ApplicationScoped +public class InternalChannelsBean implements CountableTestBean { + + private static Set TEST_DATA = new HashSet<>(Arrays.asList("test1", "test2")); + public static CountDownLatch testLatch = new CountDownLatch(TEST_DATA.size()); + + @Outgoing("intenal-publisher-string") + public Publisher produceMessage() { + return MultiRS.just(TEST_DATA.stream()); + } + + @Incoming("intenal-publisher-string") + public void receiveMethod(String msg) { + if (TEST_DATA.contains(msg)) { + testLatch.countDown(); + } + } + + @Override + public CountDownLatch getTestLatch() { + return testLatch; + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/MultipleProcessorBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/MultipleProcessorBean.java new file mode 100644 index 00000000000..bd3854a48a0 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/MultipleProcessorBean.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import io.helidon.microprofile.messaging.CountableTestBean; +import io.helidon.microprofile.reactive.MultiRS; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; + +@ApplicationScoped +public class MultipleProcessorBean implements CountableTestBean { + public static Set TEST_DATA = new HashSet<>(Arrays.asList("teST1", "TEst2", "tESt3")); + public static Set EXPECTED_DATA = TEST_DATA.stream() + .map(String::toLowerCase) + .map(s -> s + "-processed") + .collect(Collectors.toSet()); + public static CountDownLatch testLatch = new CountDownLatch(TEST_DATA.size()); + + @Outgoing("inner-processor") + public Publisher produceMessage() { + return MultiRS.just(TEST_DATA.stream()); + } + + @Incoming("inner-processor") + @Outgoing("inner-processor-2") + public String process(String msg) { + return msg.toLowerCase(); + } + + @Incoming("inner-processor-2") + @Outgoing("inner-consumer") + public String process2(String msg) { + return msg + "-processed"; + } + + @Incoming("inner-consumer") + public void receiveMessage(String msg) { + if (EXPECTED_DATA.contains(msg)) { + testLatch.countDown(); + } + } + + @Override + public CountDownLatch getTestLatch() { + return testLatch; + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/MultipleTypeProcessorChainV1Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/MultipleTypeProcessorChainV1Bean.java new file mode 100644 index 00000000000..e3156af2ec4 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/MultipleTypeProcessorChainV1Bean.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import io.helidon.microprofile.messaging.CountableTestBean; +import io.helidon.microprofile.reactive.MultiRS; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; + +@ApplicationScoped +public class MultipleTypeProcessorChainV1Bean implements CountableTestBean { + public static Set TEST_DATA = new HashSet<>(Arrays.asList("teST1", "TEst2", "tESt3")); + public static Set EXPECTED_DATA = TEST_DATA.stream() + .map(String::toLowerCase) + .map(s -> s + "-processed") + .collect(Collectors.toSet()); + public static CountDownLatch testLatch = new CountDownLatch(TEST_DATA.size()); + + @Outgoing("inner-processor") + public Publisher produceMessage() { + return MultiRS.just(TEST_DATA.stream()); + } + + @Incoming("inner-processor") + @Outgoing("inner-processor-2") + public PublisherBuilder process(PublisherBuilder msg) { + return msg.map(s -> s.toLowerCase()); + } + + @Incoming("inner-processor-2") + @Outgoing("inner-consumer") + public String process2(String msg) { + return msg + "-processed"; + } + + @Incoming("inner-consumer") + public void receiveMessage(String msg) { + if (EXPECTED_DATA.contains(msg)) { + testLatch.countDown(); + } + } + + @Override + public CountDownLatch getTestLatch() { + return testLatch; + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/MultipleTypeProcessorChainV2Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/MultipleTypeProcessorChainV2Bean.java new file mode 100644 index 00000000000..7b1f33806f4 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/MultipleTypeProcessorChainV2Bean.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import io.helidon.microprofile.messaging.CountableTestBean; +import io.helidon.microprofile.reactive.MultiRS; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; + +@ApplicationScoped +public class MultipleTypeProcessorChainV2Bean implements CountableTestBean { + public static Set TEST_DATA = new HashSet<>(Arrays.asList("teST1", "TEst2", "tESt3")); + public static Set EXPECTED_DATA = TEST_DATA.stream() + .map(String::toLowerCase) + .map(s -> s + "-processed") + .collect(Collectors.toSet()); + public static CountDownLatch testLatch = new CountDownLatch(TEST_DATA.size()); + + @Outgoing("inner-processor") + public Publisher produceMessage() { + return MultiRS.just(TEST_DATA.stream()); + } + + @Incoming("inner-processor") + @Outgoing("inner-processor-2") + public String toUpperCase(String payload) { + return payload; + } + + @Incoming("inner-processor-2") + @Outgoing("inner-processor-3") + public PublisherBuilder process(PublisherBuilder msg) { + return msg.map(s -> s.toLowerCase()); + } + + @Incoming("inner-processor-3") + @Outgoing("inner-consumer") + public String process2(String msg) { + return msg + "-processed"; + } + + @Incoming("inner-consumer") + public void receiveMessage(String msg) { + if (EXPECTED_DATA.contains(msg)) { + testLatch.countDown(); + } + } + + @Override + public CountDownLatch getTestLatch() { + return testLatch; + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/NotConnectedIncommingChannelBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/NotConnectedIncommingChannelBean.java new file mode 100644 index 00000000000..0624d61c909 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/NotConnectedIncommingChannelBean.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import io.helidon.microprofile.messaging.AssertThrowException; +import org.eclipse.microprofile.reactive.messaging.Incoming; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +@AssertThrowException(Exception.class) +public class NotConnectedIncommingChannelBean { + + @Incoming("not-existing-channel") + public void receiveMethod(String msg) { + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/NotConnectedOutgoingChannelBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/NotConnectedOutgoingChannelBean.java new file mode 100644 index 00000000000..7ce4a9e91fb --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/NotConnectedOutgoingChannelBean.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import io.helidon.microprofile.messaging.AssertThrowException; +import io.helidon.microprofile.reactive.MultiRS; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +@AssertThrowException(Exception.class) +public class NotConnectedOutgoingChannelBean { + + @Outgoing("not-existing-channel") + public Publisher produceMessage() { + return MultiRS.just("t1", "t2"); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ProcessorBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ProcessorBean.java new file mode 100644 index 00000000000..8ccb2975856 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ProcessorBean.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Processor; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class ProcessorBean extends AbstractShapeTestBean { + + @Outgoing("publisher-for-processor-message") + public PublisherBuilder streamForProcessorOfMessages() { + return ReactiveStreams.of(TEST_INT_DATA.toArray(new Integer[0])); + } + + @Incoming("publisher-for-processor-message") + @Outgoing("processor-message") + public Processor, Message> processorOfMessages() { + return ReactiveStreams.>builder() + .map(Message::getPayload) + .map(i -> i + 1) + .flatMap(i -> ReactiveStreams.of(i, i)) + .map(i -> Integer.toString(i)) + .map(Message::of) + .buildRs(); + } + + @Incoming("processor-message") + public void getMessgesFromProcessorOfMessages(String value) { + getTestLatch().countDown(); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ProcessorBuilderBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ProcessorBuilderBean.java new file mode 100644 index 00000000000..f421fcf4529 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ProcessorBuilderBean.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class ProcessorBuilderBean extends AbstractShapeTestBean { + + @Outgoing("publisher-for-processor-builder-payload") + public PublisherBuilder streamForProcessorBuilderOfPayloads() { + return ReactiveStreams.of(TEST_INT_DATA.toArray(new Integer[0])); + } + + @Incoming("publisher-for-processor-builder-payload") + @Outgoing("processor-builder-payload") + public ProcessorBuilder processorBuilderOfPayloads() { + return ReactiveStreams.builder() + .map(i -> i + 1) + .flatMap(i -> ReactiveStreams.of(i, i)) + .map(i -> Integer.toString(i)); + } + + @Incoming("processor-builder-payload") + public void getMessagesFromProcessorBuilderOfPayloads(String value) { + getTestLatch().countDown(); + } + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/PullForEachBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/PullForEachBean.java new file mode 100644 index 00000000000..8cb88fac561 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/PullForEachBean.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Subscriber; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.concurrent.atomic.AtomicInteger; + +@ApplicationScoped +public class PullForEachBean extends AbstractShapeTestBean { + + AtomicInteger testSequence = new AtomicInteger(); + + @Outgoing("generator-payload-async") + public Integer getPayload() { + return testSequence.incrementAndGet(); + } + + @Incoming("generator-payload-async") + public Subscriber getFromInfinitePayloadGenerator() { + return ReactiveStreams.builder() + .limit(TEST_DATA.size()) + .forEach(s -> getTestLatch().countDown()) + .build(); + } + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/InvalidAckStrategy.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/InvalidAckStrategy.java new file mode 100644 index 00000000000..f5bb652ecb7 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/InvalidAckStrategy.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertThrowException; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +@AssertThrowException(Exception.class) +public class InvalidAckStrategy { + + @Outgoing("inner-processor") + @Acknowledgment(Acknowledgment.Strategy.MANUAL) + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test")).buildRs(); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingMsgManualAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingMsgManualAckBean.java new file mode 100644 index 00000000000..24b4cd36fac --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingMsgManualAckBean.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class IncomingMsgManualAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> ackFuture)).buildRs(); + } + + @Incoming("test-channel") + @Acknowledgment(Acknowledgment.Strategy.MANUAL) + public CompletionStage receiveMessage(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + + CompletionStage ack = msg.ack(); + ack.toCompletableFuture().complete(null); + return CompletableFuture.completedFuture(null); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingMsgNoneAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingMsgNoneAckBean.java new file mode 100644 index 00000000000..a7ba19b2f10 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingMsgNoneAckBean.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class IncomingMsgNoneAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> ackFuture)).buildRs(); + } + + @Incoming("test-channel") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public CompletionStage receiveMessage(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return CompletableFuture.completedFuture(null); + } + + @Override + public void assertValid() { + assertFalse(ackFuture.isDone()); + assertFalse(completedBeforeProcessor.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingMsgPostProcessExplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingMsgPostProcessExplicitAckBean.java new file mode 100644 index 00000000000..ad2b9a16455 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingMsgPostProcessExplicitAckBean.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class IncomingMsgPostProcessExplicitAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> ackFuture)).buildRs(); + } + + @Incoming("test-channel") + @Acknowledgment(Acknowledgment.Strategy.POST_PROCESSING) + public CompletionStage receiveMessage(Message msg) { + return CompletableFuture.completedFuture(null); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingMsgPostProcessImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingMsgPostProcessImplicitAckBean.java new file mode 100644 index 00000000000..b085696c824 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingMsgPostProcessImplicitAckBean.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class IncomingMsgPostProcessImplicitAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> ackFuture)).buildRs(); + } + + @Incoming("test-channel") + public CompletionStage receiveMessage(Message msg) { + return CompletableFuture.completedFuture(null); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingMsgPreAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingMsgPreAckBean.java new file mode 100644 index 00000000000..f638b3865b3 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingMsgPreAckBean.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class IncomingMsgPreAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> ackFuture)).buildRs(); + } + + @Incoming("test-channel") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public CompletionStage receiveMessage(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return CompletableFuture.completedFuture(null); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingPaylNoneAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingPaylNoneAckBean.java new file mode 100644 index 00000000000..44f7d3d8e5b --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingPaylNoneAckBean.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class IncomingPaylNoneAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> ackFuture)).buildRs(); + } + + @Incoming("test-channel") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + } + + @Override + public void assertValid() { + assertFalse(ackFuture.isDone()); + assertFalse(completedBeforeProcessor.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingPaylPostExplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingPaylPostExplicitAckBean.java new file mode 100644 index 00000000000..ec628dff156 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingPaylPostExplicitAckBean.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class IncomingPaylPostExplicitAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedPre = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> ackFuture)).buildRs(); + } + + @Incoming("test-channel") + @Acknowledgment(Acknowledgment.Strategy.POST_PROCESSING) + public void receiveMessage(String msg) { + completedPre.set(ackFuture.isDone()); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedPre.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingPaylPostImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingPaylPostImplicitAckBean.java new file mode 100644 index 00000000000..d4e9eec09f0 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingPaylPostImplicitAckBean.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class IncomingPaylPostImplicitAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedPre = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> ackFuture)).buildRs(); + } + + @Incoming("test-channel") + public void receiveMessage(String msg) { + completedPre.set(ackFuture.isDone()); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedPre.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingPaylPreAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingPaylPreAckBean.java new file mode 100644 index 00000000000..fd81f4c1a1e --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingPaylPreAckBean.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class IncomingPaylPreAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedPre = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> ackFuture)).buildRs(); + } + + @Incoming("test-channel") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public void receiveMessage(String msg) { + completedPre.set(ackFuture.isDone()); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedPre.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderMsgManualAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderMsgManualAckBean.java new file mode 100644 index 00000000000..10f17d3b8b4 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderMsgManualAckBean.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class IncomingSubscriberBuilderMsgManualAckBean implements AssertableTestBean { + + private static final String TEST_MSG = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicBoolean interceptedMessage = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_MSG, () -> { + ackFuture.complete(null); + return CompletableFuture.completedFuture(null); + })).buildRs(); + } + + @Incoming("test-channel") + @Acknowledgment(Acknowledgment.Strategy.MANUAL) + public SubscriberBuilder, Void> receiveMessage() { + return ReactiveStreams.>builder() + .forEach(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + interceptedMessage.set(TEST_MSG.equals(m.getPayload())); + m.ack(); + }); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + assertTrue(interceptedMessage.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderMsgNoneAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderMsgNoneAckBean.java new file mode 100644 index 00000000000..29e430841fc --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderMsgNoneAckBean.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class IncomingSubscriberBuilderMsgNoneAckBean implements AssertableTestBean { + + private static final String TEST_MSG = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicBoolean interceptedMessage = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_MSG, () -> { + ackFuture.complete(null); + return CompletableFuture.completedFuture(null); + })).buildRs(); + } + + @Incoming("test-channel") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public SubscriberBuilder, Void> receiveMessage() { + return ReactiveStreams.>builder() + .forEach(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + interceptedMessage.set(TEST_MSG.equals(m.getPayload())); + }); + } + + @Override + public void assertValid() { + assertFalse(completedBeforeProcessor.get()); + assertFalse(ackFuture.isDone()); + assertTrue(interceptedMessage.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderMsgPostExplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderMsgPostExplicitAckBean.java new file mode 100644 index 00000000000..2f7b20be5bc --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderMsgPostExplicitAckBean.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class IncomingSubscriberBuilderMsgPostExplicitAckBean implements AssertableTestBean { + + private static final String TEST_MSG = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicBoolean interceptedMessage = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_MSG, () -> { + ackFuture.complete(null); + return CompletableFuture.completedFuture(null); + })).buildRs(); + } + + @Incoming("test-channel") + @Acknowledgment(Acknowledgment.Strategy.POST_PROCESSING) + public SubscriberBuilder, Void> receiveMessage() { + return ReactiveStreams.>builder() + .forEach(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + interceptedMessage.set(TEST_MSG.equals(m.getPayload())); + }); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + assertTrue(interceptedMessage.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderMsgPostImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderMsgPostImplicitAckBean.java new file mode 100644 index 00000000000..67d5b1abcbd --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderMsgPostImplicitAckBean.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class IncomingSubscriberBuilderMsgPostImplicitAckBean implements AssertableTestBean { + + private static final String TEST_MSG = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicBoolean interceptedMessage = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_MSG, () -> { + ackFuture.complete(null); + return CompletableFuture.completedFuture(null); + })).buildRs(); + } + + @Incoming("test-channel") + public SubscriberBuilder, Void> receiveMessage() { + return ReactiveStreams.>builder() + .forEach(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + interceptedMessage.set(TEST_MSG.equals(m.getPayload())); + }); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + assertTrue(interceptedMessage.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderMsgPreAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderMsgPreAckBean.java new file mode 100644 index 00000000000..19b3a22bfa6 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderMsgPreAckBean.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class IncomingSubscriberBuilderMsgPreAckBean implements AssertableTestBean { + + private static final String TEST_MSG = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicBoolean interceptedMessage = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_MSG, () -> { + ackFuture.complete(null); + return CompletableFuture.completedFuture(null); + })).buildRs(); + } + + @Incoming("test-channel") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public SubscriberBuilder, Void> receiveMessage() { + return ReactiveStreams.>builder() + .forEach(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + interceptedMessage.set(TEST_MSG.equals(m.getPayload())); + }); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertTrue(interceptedMessage.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderPaylPostImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderPaylPostImplicitAckBean.java new file mode 100644 index 00000000000..ccccaa00ecb --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberBuilderPaylPostImplicitAckBean.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class IncomingSubscriberBuilderPaylPostImplicitAckBean implements AssertableTestBean { + + private static final String TEST_MSG = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicBoolean interceptedMessage = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_MSG, () -> { + ackFuture.complete(null); + return CompletableFuture.completedFuture(null); + })).buildRs(); + } + + @Incoming("test-channel") + public SubscriberBuilder receiveMessage() { + return ReactiveStreams.builder() + .forEach(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + interceptedMessage.set(TEST_MSG.equals(m)); + }); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + assertTrue(interceptedMessage.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberMsgManualAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberMsgManualAckBean.java new file mode 100644 index 00000000000..69acc2cccbb --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberMsgManualAckBean.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +@ApplicationScoped +public class IncomingSubscriberMsgManualAckBean implements AssertableTestBean { + + private static final String TEST_MSG = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicBoolean interceptedMessage = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_MSG, () -> { + ackFuture.complete(null); + return CompletableFuture.completedFuture(null); + })).buildRs(); + } + + @Incoming("test-channel") + @Acknowledgment(Acknowledgment.Strategy.MANUAL) + public Subscriber> receiveMessage() { + return ReactiveStreams.>builder() + .forEach(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + interceptedMessage.set(TEST_MSG.equals(m.getPayload())); + m.ack(); + }).build(); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + assertTrue(interceptedMessage.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberMsgNoneAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberMsgNoneAckBean.java new file mode 100644 index 00000000000..124af70210e --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberMsgNoneAckBean.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +@ApplicationScoped +public class IncomingSubscriberMsgNoneAckBean implements AssertableTestBean { + + private static final String TEST_MSG = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicBoolean interceptedMessage = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_MSG, () -> { + ackFuture.complete(null); + return CompletableFuture.completedFuture(null); + })).buildRs(); + } + + @Incoming("test-channel") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public Subscriber> receiveMessage() { + return ReactiveStreams.>builder() + .forEach(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + interceptedMessage.set(TEST_MSG.equals(m.getPayload())); + }).build(); + } + + @Override + public void assertValid() { + assertFalse(completedBeforeProcessor.get()); + assertFalse(ackFuture.isDone()); + assertTrue(interceptedMessage.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberMsgPostExplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberMsgPostExplicitAckBean.java new file mode 100644 index 00000000000..54c900efcd4 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberMsgPostExplicitAckBean.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +@ApplicationScoped +public class IncomingSubscriberMsgPostExplicitAckBean implements AssertableTestBean { + + private static final String TEST_MSG = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicBoolean interceptedMessage = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_MSG, () -> { + ackFuture.complete(null); + return CompletableFuture.completedFuture(null); + })).buildRs(); + } + + @Incoming("test-channel") + @Acknowledgment(Acknowledgment.Strategy.POST_PROCESSING) + public Subscriber> receiveMessage() { + return ReactiveStreams.>builder() + .forEach(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + interceptedMessage.set(TEST_MSG.equals(m.getPayload())); + }) + .build(); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + assertTrue(interceptedMessage.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberMsgPostImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberMsgPostImplicitAckBean.java new file mode 100644 index 00000000000..8cf5c7f6aaa --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberMsgPostImplicitAckBean.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +@ApplicationScoped +public class IncomingSubscriberMsgPostImplicitAckBean implements AssertableTestBean { + + private static final String TEST_MSG = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicBoolean interceptedMessage = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_MSG, () -> { + ackFuture.complete(null); + return CompletableFuture.completedFuture(null); + })).buildRs(); + } + + @Incoming("test-channel") + public Subscriber> receiveMessage() { + return ReactiveStreams.>builder() + .forEach(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + interceptedMessage.set(TEST_MSG.equals(m.getPayload())); + }) + .build(); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + assertTrue(interceptedMessage.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberMsgPreAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberMsgPreAckBean.java new file mode 100644 index 00000000000..dc5a140b499 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberMsgPreAckBean.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +@ApplicationScoped +public class IncomingSubscriberMsgPreAckBean implements AssertableTestBean { + + private static final String TEST_MSG = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicBoolean interceptedMessage = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_MSG, () -> { + ackFuture.complete(null); + return CompletableFuture.completedFuture(null); + })).buildRs(); + } + + @Incoming("test-channel") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public Subscriber> receiveMessage() { + return ReactiveStreams.>builder() + .forEach(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + interceptedMessage.set(TEST_MSG.equals(m.getPayload())); + }).build(); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertTrue(interceptedMessage.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberPaylPostImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberPaylPostImplicitAckBean.java new file mode 100644 index 00000000000..f389f265159 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/incoming/IncomingSubscriberPaylPostImplicitAckBean.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.incoming; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +@ApplicationScoped +public class IncomingSubscriberPaylPostImplicitAckBean implements AssertableTestBean { + + private static final String TEST_MSG = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicBoolean interceptedMessage = new AtomicBoolean(false); + + @Outgoing("test-channel") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_MSG, () -> { + ackFuture.complete(null); + return CompletableFuture.completedFuture(null); + })).buildRs(); + } + + @Incoming("test-channel") + public Subscriber receiveMessage() { + return ReactiveStreams.builder() + .forEach(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + interceptedMessage.set(TEST_MSG.equals(m)); + }) + .build(); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + assertTrue(interceptedMessage.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2ComplStageManualAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2ComplStageManualAckBean.java new file mode 100644 index 00000000000..e986df2f55d --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2ComplStageManualAckBean.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorMsg2ComplStageManualAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.MANUAL) + public CompletionStage> process(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return CompletableFuture.supplyAsync(() -> Message.of(msg.getPayload(), msg::ack)); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public CompletionStage receiveMessage(Message msg) { + RESULT_DATA.add(msg.getPayload()); + return CompletableFuture.completedFuture(null); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + assertEquals(1, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2ComplStageNoneAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2ComplStageNoneAckBean.java new file mode 100644 index 00000000000..2e00fa07d31 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2ComplStageNoneAckBean.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorMsg2ComplStageNoneAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public CompletionStage> process(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return CompletableFuture.completedFuture(msg) + .thenApply(m -> Message.of(msg.getPayload())); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public CompletionStage receiveMessage(Message msg) { + RESULT_DATA.add(msg.getPayload()); + return CompletableFuture.completedFuture(null); + } + + @Override + public void assertValid() { + assertFalse(ackFuture.isDone()); + assertFalse(completedBeforeProcessor.get()); + assertEquals(1, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2ComplStagePrepAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2ComplStagePrepAckBean.java new file mode 100644 index 00000000000..eec3e020569 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2ComplStagePrepAckBean.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorMsg2ComplStagePrepAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public CompletionStage> process(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return CompletableFuture.supplyAsync(() -> Message.of(msg.getPayload(), msg::ack)); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public CompletionStage receiveMessage(Message msg) { + RESULT_DATA.add(msg.getPayload()); + return CompletableFuture.completedFuture(null); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertEquals(1, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2MsgManualAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2MsgManualAckBean.java new file mode 100644 index 00000000000..f10c77e2822 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2MsgManualAckBean.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorMsg2MsgManualAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.MANUAL) + public Message process(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return Message.of(msg.getPayload(), msg::ack); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public CompletionStage receiveMessage(Message msg) { + return CompletableFuture.completedFuture(null); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2MsgNoneAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2MsgNoneAckBean.java new file mode 100644 index 00000000000..432c35cf64d --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2MsgNoneAckBean.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorMsg2MsgNoneAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public Message process(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return msg; + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public CompletionStage receiveMessage(Message msg) { + return CompletableFuture.completedFuture(null); + } + + @Override + public void assertValid() { + assertFalse(ackFuture.isDone()); + assertFalse(completedBeforeProcessor.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2MsgPrepExplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2MsgPrepExplicitAckBean.java new file mode 100644 index 00000000000..b654160be3a --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2MsgPrepExplicitAckBean.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorMsg2MsgPrepExplicitAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public Message process(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return Message.of(msg.getPayload()); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public CompletionStage receiveMessage(Message msg) { + return CompletableFuture.completedFuture(null); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2MsgPrepImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2MsgPrepImplicitAckBean.java new file mode 100644 index 00000000000..08e7810a6d0 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorMsg2MsgPrepImplicitAckBean.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorMsg2MsgPrepImplicitAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + public Message process(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return Message.of(msg.getPayload()); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public CompletionStage receiveMessage(Message msg) { + return CompletableFuture.completedFuture(null); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPayl2PaylPostProcessExplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPayl2PaylPostProcessExplicitAckBean.java new file mode 100644 index 00000000000..c158ba980cd --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPayl2PaylPostProcessExplicitAckBean.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPayl2PaylPostProcessExplicitAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> ackFuture)).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.POST_PROCESSING) + public String process(String msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return msg.toUpperCase(); + } + + @Incoming("inner-consumer") + public CompletionStage receiveMessage(Message msg) { + return CompletableFuture.completedFuture(null); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPayl2PaylPostProcessImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPayl2PaylPostProcessImplicitAckBean.java new file mode 100644 index 00000000000..dc58f6c6481 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPayl2PaylPostProcessImplicitAckBean.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPayl2PaylPostProcessImplicitAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> ackFuture)).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + public String process(String msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return msg.toUpperCase(); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public CompletionStage receiveMessage(Message msg) { + return CompletableFuture.completedFuture(null); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPayl2PaylPreProcessAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPayl2PaylPreProcessAckBean.java new file mode 100644 index 00000000000..284b17e62ce --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPayl2PaylPreProcessAckBean.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPayl2PaylPreProcessAckBean implements AssertableTestBean { + + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of("test-data", () -> ackFuture)).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public String process(String msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return msg.toUpperCase(); + } + + @Incoming("inner-consumer") + public CompletionStage receiveMessage(Message msg) { + return CompletableFuture.completedFuture(null); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderMsg2MsgManualAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderMsg2MsgManualAckBean.java new file mode 100644 index 00000000000..6abc82656c7 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderMsg2MsgManualAckBean.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorProcessorBuilderMsg2MsgManualAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicReference RESULT_DATA = new AtomicReference<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.MANUAL) + public ProcessorBuilder, Message> process() { + return ReactiveStreams.>builder() + .map(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + return Message.of(m.getPayload(), m::ack); + }); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public void receiveMessage(String msg) { + RESULT_DATA.set(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + assertEquals(TEST_DATA, RESULT_DATA.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderMsg2MsgNoneAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderMsg2MsgNoneAckBean.java new file mode 100644 index 00000000000..c54a011a21c --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderMsg2MsgNoneAckBean.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorProcessorBuilderMsg2MsgNoneAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicReference RESULT_DATA = new AtomicReference<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public ProcessorBuilder, Message> process() { + return ReactiveStreams.>builder() + .peek(m -> completedBeforeProcessor.set(ackFuture.isDone())); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.set(msg); + } + + @Override + public void assertValid() { + assertFalse(ackFuture.isDone()); + assertFalse(completedBeforeProcessor.get()); + assertEquals(TEST_DATA, RESULT_DATA.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderMsg2MsgPrepExplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderMsg2MsgPrepExplicitAckBean.java new file mode 100644 index 00000000000..b6d00594c48 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderMsg2MsgPrepExplicitAckBean.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorProcessorBuilderMsg2MsgPrepExplicitAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicReference RESULT_DATA = new AtomicReference<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public ProcessorBuilder, Message> process() { + return ReactiveStreams.>builder() + .map(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + return Message.of(m.getPayload(), m::ack); + }); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public void receiveMessage(String msg) { + RESULT_DATA.set(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertEquals(TEST_DATA, RESULT_DATA.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderMsg2MsgPrepImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderMsg2MsgPrepImplicitAckBean.java new file mode 100644 index 00000000000..9af06b34fe2 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderMsg2MsgPrepImplicitAckBean.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorProcessorBuilderMsg2MsgPrepImplicitAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicReference RESULT_DATA = new AtomicReference<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + public ProcessorBuilder, Message> process() { + return ReactiveStreams.>builder() + .map(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + return Message.of(m.getPayload(), m::ack); + }); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public void receiveMessage(String msg) { + RESULT_DATA.set(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertEquals(TEST_DATA, RESULT_DATA.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderPayl2PaylNoneAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderPayl2PaylNoneAckBean.java new file mode 100644 index 00000000000..1252d9a7ba6 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderPayl2PaylNoneAckBean.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorProcessorBuilderPayl2PaylNoneAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicReference RESULT_DATA = new AtomicReference<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public ProcessorBuilder process() { + return ReactiveStreams.builder() + .peek(m -> completedBeforeProcessor.set(ackFuture.isDone())); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.set(msg); + } + + @Override + public void assertValid() { + assertFalse(ackFuture.isDone()); + assertFalse(completedBeforeProcessor.get()); + assertEquals(TEST_DATA, RESULT_DATA.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderPayl2PaylPrepImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderPayl2PaylPrepImplicitAckBean.java new file mode 100644 index 00000000000..52945d13e4a --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorBuilderPayl2PaylPrepImplicitAckBean.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorProcessorBuilderPayl2PaylPrepImplicitAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicReference RESULT_DATA = new AtomicReference<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + public ProcessorBuilder process() { + return ReactiveStreams.builder() + .peek(m -> completedBeforeProcessor.set(ackFuture.isDone())); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public void receiveMessage(String msg) { + RESULT_DATA.set(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertEquals(TEST_DATA, RESULT_DATA.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorMsg2MsgManualAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorMsg2MsgManualAckBean.java new file mode 100644 index 00000000000..6d080527af7 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorMsg2MsgManualAckBean.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorProcessorMsg2MsgManualAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicReference RESULT_DATA = new AtomicReference<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.MANUAL) + public Processor, Message> process() { + return ReactiveStreams.>builder() + .map(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + return Message.of(m.getPayload(), m::ack); + }) + .buildRs(); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public void receiveMessage(String msg) { + RESULT_DATA.set(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + assertEquals(TEST_DATA, RESULT_DATA.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorMsg2MsgNoneAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorMsg2MsgNoneAckBean.java new file mode 100644 index 00000000000..4128feb5617 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorMsg2MsgNoneAckBean.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorProcessorMsg2MsgNoneAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicReference RESULT_DATA = new AtomicReference<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public Processor, Message> process() { + return ReactiveStreams.>builder() + .peek(m -> completedBeforeProcessor.set(ackFuture.isDone())) + .buildRs(); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.set(msg); + } + + @Override + public void assertValid() { + assertFalse(ackFuture.isDone()); + assertFalse(completedBeforeProcessor.get()); + assertEquals(TEST_DATA, RESULT_DATA.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorMsg2MsgPrepExplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorMsg2MsgPrepExplicitAckBean.java new file mode 100644 index 00000000000..5a8287165a4 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorMsg2MsgPrepExplicitAckBean.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorProcessorMsg2MsgPrepExplicitAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicReference RESULT_DATA = new AtomicReference<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public Processor, Message> process() { + return ReactiveStreams.>builder() + .map(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + return Message.of(m.getPayload(), m::ack); + }) + .buildRs(); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public void receiveMessage(String msg) { + RESULT_DATA.set(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertEquals(TEST_DATA, RESULT_DATA.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorMsg2MsgPrepImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorMsg2MsgPrepImplicitAckBean.java new file mode 100644 index 00000000000..6beb55c2732 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorMsg2MsgPrepImplicitAckBean.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorProcessorMsg2MsgPrepImplicitAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicReference RESULT_DATA = new AtomicReference<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + public Processor, Message> process() { + return ReactiveStreams.>builder() + .map(m -> { + completedBeforeProcessor.set(ackFuture.isDone()); + return Message.of(m.getPayload(), m::ack); + }) + .buildRs(); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public void receiveMessage(String msg) { + RESULT_DATA.set(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertEquals(TEST_DATA, RESULT_DATA.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorPayl2PaylNoneAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorPayl2PaylNoneAckBean.java new file mode 100644 index 00000000000..a242c0eef01 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorPayl2PaylNoneAckBean.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorProcessorPayl2PaylNoneAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicReference RESULT_DATA = new AtomicReference<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public Processor process() { + return ReactiveStreams.builder() + .peek(m -> completedBeforeProcessor.set(ackFuture.isDone())) + .buildRs(); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.set(msg); + } + + @Override + public void assertValid() { + assertFalse(ackFuture.isDone()); + assertFalse(completedBeforeProcessor.get()); + assertEquals(TEST_DATA, RESULT_DATA.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorPayl2PaylPrepImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorPayl2PaylPrepImplicitAckBean.java new file mode 100644 index 00000000000..97d3cf7273f --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorProcessorPayl2PaylPrepImplicitAckBean.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorProcessorPayl2PaylPrepImplicitAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private AtomicReference RESULT_DATA = new AtomicReference<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + public Processor process() { + return ReactiveStreams.builder() + .peek(m -> completedBeforeProcessor.set(ackFuture.isDone())) + .buildRs(); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public void receiveMessage(String msg) { + RESULT_DATA.set(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertEquals(TEST_DATA, RESULT_DATA.get()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderMsg2MsgManualAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderMsg2MsgManualAckBean.java new file mode 100644 index 00000000000..cea0543e390 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderMsg2MsgManualAckBean.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPublisherBuilderMsg2MsgManualAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.MANUAL) + public PublisherBuilder> process(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + msg.ack(); + return ReactiveStreams.of(Message.of(msg.getPayload()), Message.of(msg.getPayload())); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.add(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + assertEquals(2, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderMsg2MsgNoneAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderMsg2MsgNoneAckBean.java new file mode 100644 index 00000000000..cdfe3f745a6 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderMsg2MsgNoneAckBean.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPublisherBuilderMsg2MsgNoneAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public PublisherBuilder> process(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return ReactiveStreams.of(msg, msg); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.add(msg); + } + + @Override + public void assertValid() { + assertFalse(ackFuture.isDone()); + assertFalse(completedBeforeProcessor.get()); + assertEquals(2, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderMsg2MsgPrepExplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderMsg2MsgPrepExplicitAckBean.java new file mode 100644 index 00000000000..e1d97c3be49 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderMsg2MsgPrepExplicitAckBean.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPublisherBuilderMsg2MsgPrepExplicitAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public PublisherBuilder> process(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return ReactiveStreams.of(msg, msg); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.add(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertEquals(2, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderMsg2MsgPrepImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderMsg2MsgPrepImplicitAckBean.java new file mode 100644 index 00000000000..7c1db9b5ab8 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderMsg2MsgPrepImplicitAckBean.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPublisherBuilderMsg2MsgPrepImplicitAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + public PublisherBuilder> process(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return ReactiveStreams.of(msg, msg); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.add(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertEquals(2, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderPayl2PaylNoneAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderPayl2PaylNoneAckBean.java new file mode 100644 index 00000000000..7571253781b --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderPayl2PaylNoneAckBean.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPublisherBuilderPayl2PaylNoneAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public PublisherBuilder> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public Publisher process(String msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return ReactiveStreams.of(msg, msg).buildRs(); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.add(msg); + } + + @Override + public void assertValid() { + assertFalse(ackFuture.isDone()); + assertFalse(completedBeforeProcessor.get()); + assertEquals(2, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderPayl2PaylPrepExplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderPayl2PaylPrepExplicitAckBean.java new file mode 100644 index 00000000000..6560bd71d94 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderPayl2PaylPrepExplicitAckBean.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPublisherBuilderPayl2PaylPrepExplicitAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public PublisherBuilder process(String msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return ReactiveStreams.of(msg, msg); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.add(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertEquals(2, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderPayl2PaylPrepImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderPayl2PaylPrepImplicitAckBean.java new file mode 100644 index 00000000000..6e17d7d8829 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherBuilderPayl2PaylPrepImplicitAckBean.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPublisherBuilderPayl2PaylPrepImplicitAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + public PublisherBuilder process(String msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return ReactiveStreams.of(msg, msg); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.add(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertEquals(2, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherMsg2MsgManualAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherMsg2MsgManualAckBean.java new file mode 100644 index 00000000000..1e7789af88f --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherMsg2MsgManualAckBean.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPublisherMsg2MsgManualAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.MANUAL) + public Publisher> process(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + msg.ack(); + return ReactiveStreams.of(Message.of(msg.getPayload()), Message.of(msg.getPayload())).buildRs(); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.add(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertFalse(completedBeforeProcessor.get()); + assertEquals(2, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherMsg2MsgNoneAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherMsg2MsgNoneAckBean.java new file mode 100644 index 00000000000..deb7191a471 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherMsg2MsgNoneAckBean.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPublisherMsg2MsgNoneAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public Publisher> process(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return ReactiveStreams.of(msg, msg).buildRs(); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.add(msg); + } + + @Override + public void assertValid() { + assertFalse(ackFuture.isDone()); + assertFalse(completedBeforeProcessor.get()); + assertEquals(2, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherMsg2MsgPrepExplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherMsg2MsgPrepExplicitAckBean.java new file mode 100644 index 00000000000..d309fe87c5f --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherMsg2MsgPrepExplicitAckBean.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPublisherMsg2MsgPrepExplicitAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public Publisher> process(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return ReactiveStreams.of(msg, msg).buildRs(); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.add(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertEquals(2, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherMsg2MsgPrepImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherMsg2MsgPrepImplicitAckBean.java new file mode 100644 index 00000000000..b44f0ad5dc4 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherMsg2MsgPrepImplicitAckBean.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPublisherMsg2MsgPrepImplicitAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + public Publisher> process(Message msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return ReactiveStreams.of(msg, msg).buildRs(); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.add(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertEquals(2, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherPayl2PaylNoneAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherPayl2PaylNoneAckBean.java new file mode 100644 index 00000000000..bf9e835c887 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherPayl2PaylNoneAckBean.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPublisherPayl2PaylNoneAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public Publisher process(String msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return ReactiveStreams.of(msg, msg).buildRs(); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.add(msg); + } + + @Override + public void assertValid() { + assertFalse(ackFuture.isDone()); + assertFalse(completedBeforeProcessor.get()); + assertEquals(2, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherPayl2PaylPrepExplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherPayl2PaylPrepExplicitAckBean.java new file mode 100644 index 00000000000..5fab5e9d3d4 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherPayl2PaylPrepExplicitAckBean.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPublisherPayl2PaylPrepExplicitAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING) + public Publisher process(String msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return ReactiveStreams.of(msg, msg).buildRs(); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.add(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertEquals(2, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherPayl2PaylPrepImplicitAckBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherPayl2PaylPrepImplicitAckBean.java new file mode 100644 index 00000000000..61bbc42e005 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/ack/processor/ProcessorPublisherPayl2PaylPrepImplicitAckBean.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.ack.processor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.messaging.Acknowledgment; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class ProcessorPublisherPayl2PaylPrepImplicitAckBean implements AssertableTestBean { + + public static final String TEST_DATA = "test-data"; + private CompletableFuture ackFuture = new CompletableFuture<>(); + private AtomicBoolean completedBeforeProcessor = new AtomicBoolean(false); + private CopyOnWriteArrayList RESULT_DATA = new CopyOnWriteArrayList<>(); + + @Outgoing("inner-processor") + public Publisher> produceMessage() { + return ReactiveStreams.of(Message.of(TEST_DATA, () -> { + ackFuture.complete(null); + return ackFuture; + })).buildRs(); + } + + @Incoming("inner-processor") + @Outgoing("inner-consumer") + public Publisher process(String msg) { + completedBeforeProcessor.set(ackFuture.isDone()); + return ReactiveStreams.of(msg, msg).buildRs(); + } + + @Incoming("inner-consumer") + @Acknowledgment(Acknowledgment.Strategy.NONE) + public void receiveMessage(String msg) { + RESULT_DATA.add(msg); + } + + @Override + public void assertValid() { + try { + ackFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + assertTrue(completedBeforeProcessor.get()); + assertEquals(2, RESULT_DATA.size()); + RESULT_DATA.forEach(s -> assertEquals(TEST_DATA, s)); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherBuilderTransformerV1Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherBuilderTransformerV1Bean.java new file mode 100644 index 00000000000..e9ab122720b --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherBuilderTransformerV1Bean.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.inner.AbstractShapeTestBean; + +@ApplicationScoped +public class PublisherBuilderTransformerV1Bean extends AbstractShapeTestBean { + + @Outgoing("publisher-for-publisher-builder-message") + public PublisherBuilder streamForProcessorBuilderOfMessages() { + return ReactiveStreams.of(TEST_INT_DATA.toArray(new Integer[0])); + } + + @Incoming("publisher-for-publisher-builder-message") + @Outgoing("publisher-builder-message") + public PublisherBuilder> processorBuilderOfMessages(PublisherBuilder> stream) { + return stream + .map(Message::getPayload) + .map(i -> i + 1) + .flatMap(i -> ReactiveStreams.of(i, i)) + .map(i -> Integer.toString(i)) + .map(Message::of); + } + + @Incoming("publisher-builder-message") + public void getMessgesFromProcessorBuilderOfMessages(String value) { + getTestLatch().countDown(); + } + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherBuilderTransformerV2Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherBuilderTransformerV2Bean.java new file mode 100644 index 00000000000..42463d5cef4 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherBuilderTransformerV2Bean.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.inner.AbstractShapeTestBean; + +@ApplicationScoped +public class PublisherBuilderTransformerV2Bean extends AbstractShapeTestBean { + + @Outgoing("publisher-for-publisher-builder-payload") + public PublisherBuilder streamForProcessorBuilderOfPayloads() { + return ReactiveStreams.of(TEST_INT_DATA.toArray(new Integer[0])); + } + + @Incoming("publisher-for-publisher-builder-payload") + @Outgoing("publisher-builder-payload") + public PublisherBuilder processorBuilderOfPayloads(PublisherBuilder stream) { + return stream + .map(i -> i + 1) + .flatMap(i -> ReactiveStreams.of(i, i)) + .map(i -> Integer.toString(i)); + } + + @Incoming("publisher-builder-payload") + public void getMessgesFromProcessorBuilderOfPayloads(String value) { + getTestLatch().countDown(); + } + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherFromPublisherV1Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherFromPublisherV1Bean.java new file mode 100644 index 00000000000..98efa504447 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherFromPublisherV1Bean.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.inner.AbstractShapeTestBean; + +@ApplicationScoped +public class PublisherFromPublisherV1Bean extends AbstractShapeTestBean { + + @Outgoing("publisher-for-publisher-message") + public PublisherBuilder streamForProcessorOfMessages() { + return ReactiveStreams.of(TEST_INT_DATA.toArray(new Integer[0])); + } + + @Incoming("publisher-for-publisher-message") + @Outgoing("publisher-message") + public Publisher> processorOfMessages(Publisher> stream) { + return ReactiveStreams.fromPublisher(stream) + .map(Message::getPayload) + .map(i -> i + 1) + .flatMap(i -> ReactiveStreams.of(i, i)) + .map(i -> Integer.toString(i)) + .map(Message::of) + .buildRs(); + } + + @Incoming("publisher-message") + public void getMessgesFromProcessorOfMessages(String value) { + getTestLatch().countDown(); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherFromPublisherV2Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherFromPublisherV2Bean.java new file mode 100644 index 00000000000..1203493a7d1 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherFromPublisherV2Bean.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.inner.AbstractShapeTestBean; + +@ApplicationScoped +public class PublisherFromPublisherV2Bean extends AbstractShapeTestBean { + + @Outgoing("publisher-for-publisher-payload") + public PublisherBuilder streamForProcessorOfPayloads() { + return ReactiveStreams.of(TEST_INT_DATA.toArray(new Integer[0])); + } + + @Incoming("publisher-for-publisher-payload") + @Outgoing("publisher-payload") + public Publisher processorOfPayloads(Publisher stream) { + return ReactiveStreams.fromPublisher(stream) + .map(i -> i + 1) + .flatMap(i -> ReactiveStreams.of(i, i)) + .map(i -> Integer.toString(i)) + .buildRs(); + } + + @Incoming("publisher-payload") + public void getMessgesFromProcessorOfPayloads(String value) { + getTestLatch().countDown(); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherPayloadV1Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherPayloadV1Bean.java new file mode 100644 index 00000000000..1657f443a99 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherPayloadV1Bean.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.inner.AbstractShapeTestBean; + +@ApplicationScoped +public class PublisherPayloadV1Bean extends AbstractShapeTestBean { + + @Outgoing("void-payload") + public Publisher> sourceForVoidPayload() { + return ReactiveStreams.fromIterable(TEST_DATA).map(Message::of).buildRs(); + } + + @Incoming("void-payload") + public void consumePayload(String payload) { + testLatch.countDown(); + } + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherPayloadV3Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherPayloadV3Bean.java new file mode 100644 index 00000000000..be0fc3f3d9f --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherPayloadV3Bean.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executors; + +import io.helidon.microprofile.messaging.inner.AbstractShapeTestBean; + +@ApplicationScoped +public class PublisherPayloadV3Bean extends AbstractShapeTestBean { + + @Outgoing("cs-void-message") + public Publisher> sourceForCsVoidMessage() { + return ReactiveStreams.fromIterable(TEST_DATA).map(Message::of).buildRs(); + } + + @Incoming("cs-void-message") + public CompletionStage consumeMessageAndReturnCompletionStageOfVoid(Message message) { + return CompletableFuture.runAsync(() -> testLatch.countDown(), Executors.newSingleThreadExecutor()); + } + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherPayloadV4Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherPayloadV4Bean.java new file mode 100644 index 00000000000..6ab549beb36 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherPayloadV4Bean.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executors; + +import io.helidon.microprofile.messaging.inner.AbstractShapeTestBean; + +@ApplicationScoped +public class PublisherPayloadV4Bean extends AbstractShapeTestBean { + + @Outgoing("cs-void-payload") + public Publisher> sourceForCsVoidPayload() { + return ReactiveStreams.fromIterable(TEST_DATA).map(Message::of).buildRs(); + } + + @Incoming("cs-void-payload") + public CompletionStage consumePayloadAndReturnCompletionStageOfVoid(String payload) { + testLatch.countDown(); + return CompletableFuture.runAsync(() -> testLatch.countDown(), Executors.newSingleThreadExecutor()); + } + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherPayloadV5Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherPayloadV5Bean.java new file mode 100644 index 00000000000..58b790ae2fc --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherPayloadV5Bean.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executors; + +import io.helidon.microprofile.messaging.inner.AbstractShapeTestBean; + +@ApplicationScoped +public class PublisherPayloadV5Bean extends AbstractShapeTestBean { + + @Outgoing("cs-string-message") + public Publisher> sourceForCsStringMessage() { + return ReactiveStreams.fromIterable(TEST_DATA).map(Message::of).buildRs(); + } + + @Incoming("cs-string-message") + public CompletionStage consumeMessageAndReturnCompletionStageOfString(Message message) { + return CompletableFuture.supplyAsync(() -> { + testLatch.countDown(); + return "something"; + }, Executors.newSingleThreadExecutor()); + } + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherPayloadV6Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherPayloadV6Bean.java new file mode 100644 index 00000000000..ecc300afe26 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherPayloadV6Bean.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executors; + +import io.helidon.microprofile.messaging.inner.AbstractShapeTestBean; + +@ApplicationScoped +public class PublisherPayloadV6Bean extends AbstractShapeTestBean { + + @Outgoing("cs-string-payload") + public Publisher> sourceForCsStringPayload() { + return ReactiveStreams.fromIterable(TEST_DATA).map(Message::of).buildRs(); + } + + @Incoming("cs-string-payload") + public CompletionStage consumePayloadAndReturnCompletionStageOfString(String payload) { + return CompletableFuture.supplyAsync(() -> { + testLatch.countDown(); + return "something"; + }, Executors.newSingleThreadExecutor()); + } + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherProcessorV1Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherProcessorV1Bean.java new file mode 100644 index 00000000000..4ae75f31c29 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherProcessorV1Bean.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import io.helidon.microprofile.messaging.CountableTestBean; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.concurrent.CountDownLatch; + +@ApplicationScoped +public class PublisherProcessorV1Bean implements CountableTestBean { + + public static CountDownLatch testLatch = new CountDownLatch(10); + + @Outgoing("publisher-for-processor-publisher-message") + public PublisherBuilder streamForProcessorOfMessages() { + return ReactiveStreams.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + } + + @Incoming("publisher-for-processor-publisher-message") + @Outgoing("processor-publisher-message") + public Publisher> processorOfMessages(Message message) { + return ReactiveStreams.of(message) + .map(Message::getPayload) + .map(i -> i + 1) + .flatMap(i -> ReactiveStreams.of(i, i)) + .map(i -> Integer.toString(i)) + .map(Message::of) + .buildRs(); + } + + @Incoming("processor-publisher-message") + public void getMessgesFromProcessorOfMessages(String value) { + getTestLatch().countDown(); + } + + @Override + public CountDownLatch getTestLatch() { + return testLatch; + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherProcessorV2Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherProcessorV2Bean.java new file mode 100644 index 00000000000..2a06de80f0e --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherProcessorV2Bean.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import io.helidon.microprofile.messaging.CountableTestBean; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.concurrent.CountDownLatch; + +@ApplicationScoped +public class PublisherProcessorV2Bean implements CountableTestBean { + + public static CountDownLatch testLatch = new CountDownLatch(10); + + @Outgoing("publisher-for-processor-publisher-payload") + public PublisherBuilder streamForProcessorOfPayloads() { + return ReactiveStreams.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + } + + @Incoming("publisher-for-processor-publisher-payload") + @Outgoing("processor-publisher-payload") + public Publisher processorOfPayloads(int value) { + return ReactiveStreams.of(value) + .map(i -> i + 1) + .flatMap(i -> ReactiveStreams.of(i, i)) + .map(i -> Integer.toString(i)) + .buildRs(); + } + + @Incoming("processor-publisher-payload") + public void getMessgesFromProcessorOfPayloads(String value) { + getTestLatch().countDown(); + } + + @Override + public CountDownLatch getTestLatch() { + return testLatch; + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherProcessorV3Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherProcessorV3Bean.java new file mode 100644 index 00000000000..5779be3dbd0 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherProcessorV3Bean.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import io.helidon.microprofile.messaging.CountableTestBean; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.concurrent.CountDownLatch; + +@ApplicationScoped +public class PublisherProcessorV3Bean implements CountableTestBean { + + public static CountDownLatch testLatch = new CountDownLatch(10); + + @Outgoing("publisher-for-processor-publisher-builder-message") + public PublisherBuilder streamForProcessorBuilderOfMessages() { + return ReactiveStreams.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + } + + @Incoming("publisher-for-processor-publisher-builder-message") + @Outgoing("processor-publisher-builder-message") + public PublisherBuilder> processorBuilderOfMessages(Message message) { + return ReactiveStreams.of(message) + .map(Message::getPayload) + .map(i -> i + 1) + .flatMap(i -> ReactiveStreams.of(i, i)) + .map(i -> Integer.toString(i)) + .map(Message::of); + } + + @Incoming("processor-publisher-builder-message") + public void getMessgesFromProcessorBuilderOfMessages(String value) { + getTestLatch().countDown(); + } + + @Override + public CountDownLatch getTestLatch() { + return testLatch; + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherProcessorV4Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherProcessorV4Bean.java new file mode 100644 index 00000000000..c53af95dc05 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherProcessorV4Bean.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import io.helidon.microprofile.messaging.CountableTestBean; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; + +import javax.enterprise.context.ApplicationScoped; + +import java.util.concurrent.CountDownLatch; + +@ApplicationScoped +public class PublisherProcessorV4Bean implements CountableTestBean { + + public static CountDownLatch testLatch = new CountDownLatch(10); + + @Outgoing("publisher-for-processor-publisher-builder-payload") + public PublisherBuilder streamForProcessorBuilderOfPayloads() { + return ReactiveStreams.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + } + + @Incoming("publisher-for-processor-publisher-builder-payload") + @Outgoing("processor-publisher-builder-payload") + public PublisherBuilder processorBuilderOfPayloads(int value) { + return ReactiveStreams.of(value) + .map(i -> i + 1) + .flatMap(i -> ReactiveStreams.of(i, i)) + .map(i -> Integer.toString(i)); + } + + @Incoming("processor-publisher-builder-payload") + public void getMessgesFromProcessorBuilderOfPayloads(String value) { + getTestLatch().countDown(); + } + + @Override + public CountDownLatch getTestLatch() { + return testLatch; + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherSubscriberBuilderV1Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherSubscriberBuilderV1Bean.java new file mode 100644 index 00000000000..b9f1650ea63 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherSubscriberBuilderV1Bean.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.inner.AbstractShapeTestBean; + +@ApplicationScoped +public class PublisherSubscriberBuilderV1Bean extends AbstractShapeTestBean { + + @Outgoing("subscriber-builder-message") + public Publisher> sourceForSubscriberBuilderMessage() { + return ReactiveStreams.fromIterable(TEST_DATA).map(Message::of).buildRs(); + } + + @Incoming("subscriber-builder-message") + public SubscriberBuilder, Void> subscriberBuilderOfMessages() { + return ReactiveStreams.>builder() + .forEach(m -> testLatch.countDown()); + } + + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherSubscriberBuilderV2Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherSubscriberBuilderV2Bean.java new file mode 100644 index 00000000000..a510cef9b2d --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherSubscriberBuilderV2Bean.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.reactivestreams.Publisher; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.inner.AbstractShapeTestBean; + +@ApplicationScoped +public class PublisherSubscriberBuilderV2Bean extends AbstractShapeTestBean { + + @Outgoing("subscriber-builder-payload") + public Publisher> sourceForSubscriberBuilderPayload() { + return ReactiveStreams.fromIterable(TEST_DATA).map(Message::of).buildRs(); + } + + @Incoming("subscriber-builder-payload") + public SubscriberBuilder subscriberBuilderOfPayloads() { + return ReactiveStreams.builder().forEach(p -> testLatch.countDown()); + } + + + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherSubscriberV1Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherSubscriberV1Bean.java new file mode 100644 index 00000000000..f9e0dacd649 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherSubscriberV1Bean.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.inner.AbstractShapeTestBean; + +@ApplicationScoped +public class PublisherSubscriberV1Bean extends AbstractShapeTestBean { + + @Outgoing("subscriber-message") + public Publisher> sourceForSubscriberMessage() { + return ReactiveStreams.fromIterable(TEST_DATA).map(Message::of).buildRs(); + } + + @Incoming("subscriber-message") + public Subscriber> subscriberOfMessages() { + return ReactiveStreams.>builder().forEach(m -> testLatch.countDown()).build(); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherSubscriberV2Bean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherSubscriberV2Bean.java new file mode 100644 index 00000000000..00a3364be4b --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/publisher/PublisherSubscriberV2Bean.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.publisher; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.inner.AbstractShapeTestBean; + +@ApplicationScoped +public class PublisherSubscriberV2Bean extends AbstractShapeTestBean { + + @Outgoing("subscriber-payload") + public Publisher> sourceForSubscribePayload() { + return ReactiveStreams.fromIterable(TEST_DATA).map(Message::of).buildRs(); + } + + @Incoming("subscriber-payload") + public Subscriber subscriberOfPayloads() { + return ReactiveStreams.builder().forEach(p -> testLatch.countDown()).build(); + } + + +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToMsgRetComplBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToMsgRetComplBean.java new file mode 100644 index 00000000000..8fb0df58936 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToMsgRetComplBean.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.subscriber; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.Executors; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class SubscriberPublMsgToMsgRetComplBean implements AssertableTestBean { + + CopyOnWriteArraySet RESULT_DATA = new CopyOnWriteArraySet<>(); + + @Outgoing("cs-void-message") + public Publisher> sourceForCsVoidMessage() { + return ReactiveStreams.fromIterable(TEST_DATA) + .map(Message::of) + .buildRs(); + } + + @Incoming("cs-void-message") + public CompletionStage consumeMessageAndReturnCompletionStageOfVoid(Message message) { + return CompletableFuture.runAsync(() -> RESULT_DATA.add(message.getPayload()), Executors.newSingleThreadExecutor()); + } + + @Override + public void assertValid() { + assertTrue(RESULT_DATA.containsAll(TEST_DATA)); + assertEquals(TEST_DATA.size(), RESULT_DATA.size()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToMsgRetComplStringBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToMsgRetComplStringBean.java new file mode 100644 index 00000000000..3fcc412902a --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToMsgRetComplStringBean.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.subscriber; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.Executors; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class SubscriberPublMsgToMsgRetComplStringBean implements AssertableTestBean { + + CopyOnWriteArraySet RESULT_DATA = new CopyOnWriteArraySet<>(); + + @Outgoing("cs-string-message") + public Publisher> sourceForCsStringMessage() { + return ReactiveStreams.fromIterable(TEST_DATA) + .map(Message::of) + .buildRs(); + } + + @Incoming("cs-string-message") + public CompletionStage consumeMessageAndReturnCompletionStageOfString(Message message) { + return CompletableFuture.supplyAsync(() -> { + RESULT_DATA.add(message.getPayload()); + return "test"; + }, Executors.newSingleThreadExecutor()); + } + + @Override + public void assertValid() { + assertTrue(RESULT_DATA.containsAll(TEST_DATA)); + assertEquals(TEST_DATA.size(), RESULT_DATA.size()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToPaylBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToPaylBean.java new file mode 100644 index 00000000000..4759b6aa01d --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToPaylBean.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.subscriber; + +import java.util.concurrent.CopyOnWriteArraySet; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class SubscriberPublMsgToPaylBean implements AssertableTestBean { + + CopyOnWriteArraySet RESULT_DATA = new CopyOnWriteArraySet<>(); + + @Outgoing("void-payload") + public Publisher> sourceForVoidPayload() { + return ReactiveStreams.fromIterable(TEST_DATA) + .map(Message::of) + .buildRs(); + } + + @Incoming("void-payload") + public void consumePayload(String payload) { + RESULT_DATA.add(payload); + } + + @Override + public void assertValid() { + assertTrue(RESULT_DATA.containsAll(TEST_DATA)); + assertEquals(TEST_DATA.size(), RESULT_DATA.size()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToPaylRetComplStringBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToPaylRetComplStringBean.java new file mode 100644 index 00000000000..f371b142a69 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToPaylRetComplStringBean.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.subscriber; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.Executors; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class SubscriberPublMsgToPaylRetComplStringBean implements AssertableTestBean { + + CopyOnWriteArraySet RESULT_DATA = new CopyOnWriteArraySet<>(); + + @Outgoing("cs-string-payload") + public Publisher> sourceForCsStringPayload() { + return ReactiveStreams.fromIterable(TEST_DATA).map(Message::of).buildRs(); + } + + + @Incoming("cs-string-payload") + public CompletionStage consumePayloadAndReturnCompletionStageOfString(String payload) { + return CompletableFuture.supplyAsync(() -> { + RESULT_DATA.add(payload); + return "test"; + }, Executors.newWorkStealingPool()); + } + + @Override + public void assertValid() { + assertTrue(RESULT_DATA.containsAll(TEST_DATA)); + assertEquals(TEST_DATA.size(), RESULT_DATA.size()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToPaylRetComplVoidBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToPaylRetComplVoidBean.java new file mode 100644 index 00000000000..25ef00a0dc4 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToPaylRetComplVoidBean.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.subscriber; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.Executors; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class SubscriberPublMsgToPaylRetComplVoidBean implements AssertableTestBean { + + CopyOnWriteArraySet RESULT_DATA = new CopyOnWriteArraySet<>(); + + @Outgoing("cs-void-payload") + public Publisher> sourceForCsVoidPayload() { + return ReactiveStreams.fromIterable(TEST_DATA) + .map(Message::of) + .buildRs(); + } + + @Incoming("cs-void-payload") + public CompletionStage consumePayloadAndReturnCompletionStageOfVoid(String payload) { + return CompletableFuture.runAsync(() -> RESULT_DATA.add(payload), Executors.newSingleThreadExecutor()); + } + + @Override + public void assertValid() { + assertTrue(RESULT_DATA.containsAll(TEST_DATA)); + assertEquals(TEST_DATA.size(), RESULT_DATA.size()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToPaylRetPaylBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToPaylRetPaylBean.java new file mode 100644 index 00000000000..e09e371b360 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToPaylRetPaylBean.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.subscriber; + +import java.util.concurrent.CopyOnWriteArraySet; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class SubscriberPublMsgToPaylRetPaylBean implements AssertableTestBean { + + CopyOnWriteArraySet RESULT_DATA = new CopyOnWriteArraySet<>(); + + @Outgoing("string-payload") + public Publisher> sourceForStringPayload() { + return ReactiveStreams.fromIterable(TEST_DATA) + .map(Message::of) + .buildRs(); + } + + @Incoming("string-payload") + public String consumePayloadsAndReturnSomething(String payload) { + RESULT_DATA.add(payload); + return payload; + } + + @Override + public void assertValid() { + assertTrue(RESULT_DATA.containsAll(TEST_DATA)); + assertEquals(TEST_DATA.size(), RESULT_DATA.size()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToSubsBuilderPaylBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToSubsBuilderPaylBean.java new file mode 100644 index 00000000000..50137988719 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToSubsBuilderPaylBean.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.subscriber; + +import java.util.concurrent.CopyOnWriteArraySet; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class SubscriberPublMsgToSubsBuilderPaylBean implements AssertableTestBean { + + CopyOnWriteArraySet RESULT_DATA = new CopyOnWriteArraySet<>(); + + @Outgoing("subscriber-builder-payload") + public Publisher> sourceForSubscriberBuilderPayload() { + return ReactiveStreams.fromIterable(TEST_DATA) + .map(Message::of) + .buildRs(); + } + + @Incoming("subscriber-builder-payload") + public SubscriberBuilder subscriberBuilderOfPayloads() { + return ReactiveStreams.builder() + .forEach(p -> RESULT_DATA.add(p)); + } + + @Override + public void assertValid() { + assertTrue(RESULT_DATA.containsAll(TEST_DATA)); + assertEquals(TEST_DATA.size(), RESULT_DATA.size()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToSubsPaylBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToSubsPaylBean.java new file mode 100644 index 00000000000..c49ff01cad9 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublMsgToSubsPaylBean.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.subscriber; + +import java.util.concurrent.CopyOnWriteArraySet; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +@ApplicationScoped +public class SubscriberPublMsgToSubsPaylBean implements AssertableTestBean { + + CopyOnWriteArraySet RESULT_DATA = new CopyOnWriteArraySet<>(); + + @Outgoing("subscriber-payload") + public Publisher> sourceForSubscribePayload() { + return ReactiveStreams.fromIterable(TEST_DATA) + .map(Message::of) + .buildRs(); + } + + @Incoming("subscriber-payload") + public Subscriber subscriberOfPayloads() { + return ReactiveStreams.builder() + .forEach(p -> RESULT_DATA.add(p)) + .build(); + } + + @Override + public void assertValid() { + assertTrue(RESULT_DATA.containsAll(TEST_DATA)); + assertEquals(TEST_DATA.size(), RESULT_DATA.size()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublToSubsBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublToSubsBean.java new file mode 100644 index 00000000000..b5e748daf3e --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublToSubsBean.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.subscriber; + +import java.util.concurrent.CopyOnWriteArraySet; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +@ApplicationScoped +public class SubscriberPublToSubsBean implements AssertableTestBean { + + CopyOnWriteArraySet RESULT_DATA = new CopyOnWriteArraySet<>(); + + @Outgoing("subscriber-message") + public Publisher> sourceForSubscriberMessage() { + return ReactiveStreams.fromIterable(TEST_DATA) + .map(Message::of) + .buildRs(); + } + + @Incoming("subscriber-message") + public Subscriber> subscriberOfMessages() { + return ReactiveStreams.>builder() + .forEach(m -> RESULT_DATA.add(m.getPayload())) + .build(); + } + + @Override + public void assertValid() { + assertTrue(RESULT_DATA.containsAll(TEST_DATA)); + assertEquals(TEST_DATA.size(), RESULT_DATA.size()); + } +} diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublToSubsBuilderBean.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublToSubsBuilderBean.java new file mode 100644 index 00000000000..68350051a79 --- /dev/null +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/inner/subscriber/SubscriberPublToSubsBuilderBean.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.messaging.inner.subscriber; + +import java.util.concurrent.CopyOnWriteArraySet; + +import javax.enterprise.context.ApplicationScoped; + +import io.helidon.microprofile.messaging.AssertableTestBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder; +import org.reactivestreams.Publisher; + +@ApplicationScoped +public class SubscriberPublToSubsBuilderBean implements AssertableTestBean { + + CopyOnWriteArraySet RESULT_DATA = new CopyOnWriteArraySet<>(); + + @Outgoing("subscriber-builder-message") + public Publisher> sourceForSubscriberBuilderMessage() { + return ReactiveStreams.fromIterable(TEST_DATA) + .map(Message::of) + .buildRs(); + } + + @Incoming("subscriber-builder-message") + public SubscriberBuilder, Void> subscriberBuilderOfMessages() { + return ReactiveStreams.>builder() + .forEach(m -> RESULT_DATA.add(m.getPayload())); + } + + @Override + public void assertValid() { + assertTrue(RESULT_DATA.containsAll(TEST_DATA)); + assertEquals(TEST_DATA.size(), RESULT_DATA.size()); + } +} diff --git a/microprofile/messaging/src/test/resources/logging.properties b/microprofile/messaging/src/test/resources/logging.properties new file mode 100644 index 00000000000..6143051b021 --- /dev/null +++ b/microprofile/messaging/src/test/resources/logging.properties @@ -0,0 +1,31 @@ +# +# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# +# Licensed 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. +# + +handlers=java.util.logging.ConsoleHandler +java.util.logging.SimpleFormatter.format=%1$tH:%1$tM:%1$tS %4$s %3$s: %5$s%6$s%n +java.util.logging.ConsoleHandler.level=INFO +.level=INFO + +# Known issue with meta.properties in embedded kafka server +kafka.server.BrokerMetadataCheckpoint.level=SEVERE +# Hide whole configuration print-out +org.apache.kafka.clients.producer.ProducerConfig.level=WARNING +org.apache.kafka.clients.consumer.ConsumerConfig.level=WARNING +# Embedded kafka server exhausting logs +kafka.level=WARNING +org.apache.kafka.level=WARNING +org.apache.zookeeper.level=SEVERE +com.salesforce.kafka.level=SEVERE diff --git a/microprofile/pom.xml b/microprofile/pom.xml index 84b91b91ac2..b2c27dfb927 100644 --- a/microprofile/pom.xml +++ b/microprofile/pom.xml @@ -48,5 +48,7 @@ rest-client access-log grpc + reactive-streams + messaging diff --git a/microprofile/reactive-streams/pom.xml b/microprofile/reactive-streams/pom.xml new file mode 100644 index 00000000000..dea040f1600 --- /dev/null +++ b/microprofile/reactive-streams/pom.xml @@ -0,0 +1,60 @@ + + + + + 4.0.0 + + + io.helidon.microprofile + helidon-microprofile-project + 2.0-SNAPSHOT + + + io.helidon.microprofile + helidon-microprofile-reactive-streams + Helidon Microprofile Reactive Streams Operators + + + Helidon Microprofile Reactive Streams Operators 1.0 + + + + + io.helidon.common + helidon-common-reactive + + + org.eclipse.microprofile.reactive-streams-operators + microprofile-reactive-streams-operators-api + 1.0.1 + + + org.eclipse.microprofile.reactive-streams-operators + microprofile-reactive-streams-operators-core + 1.0.1 + + + org.junit.jupiter + junit-jupiter + RELEASE + test + + + \ No newline at end of file diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/CancelSubscriber.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/CancelSubscriber.java new file mode 100644 index 00000000000..00bb978a93b --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/CancelSubscriber.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Flow; + +import io.helidon.microprofile.reactive.hybrid.HybridSubscriber; + +import org.eclipse.microprofile.reactive.streams.operators.spi.SubscriberWithCompletionStage; +import org.reactivestreams.Subscriber; + +public class CancelSubscriber implements Flow.Subscriber, SubscriberWithCompletionStage> { + + private CompletableFuture> completionStage = new CompletableFuture<>(); + + @Override + public void onSubscribe(Flow.Subscription subscription) { + subscription.cancel(); + this.onComplete(); + } + + @Override + public void onNext(T item) { + Objects.requireNonNull(item); + throw new CancellationException(); + } + + @Override + public void onError(Throwable throwable) { + completionStage.completeExceptionally(throwable); + } + + @Override + public void onComplete() { + if (!completionStage.isDone()) { + completionStage.complete(Optional.empty()); + } + } + + @Override + public CompletionStage> getCompletion() { + return completionStage; + } + + @Override + public Subscriber getSubscriber() { + return HybridSubscriber.from(this); + } +} diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/CollectSubscriber.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/CollectSubscriber.java new file mode 100644 index 00000000000..87c4bad8469 --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/CollectSubscriber.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive; + +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Flow; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; + +import io.helidon.microprofile.reactive.hybrid.HybridProcessor; + +import org.eclipse.microprofile.reactive.streams.operators.spi.Stage; +import org.eclipse.microprofile.reactive.streams.operators.spi.SubscriberWithCompletionStage; +import org.reactivestreams.Processor; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +/** + * Subscriber with preceding processors included, + * automatically makes all downstream subscriptions when its subscribe method is called. + * + * @param type of streamed item + */ +public class CollectSubscriber implements SubscriberWithCompletionStage { + + private final Processor connectingProcessor; + private final BiConsumer accumulator; + private final BinaryOperator combiner; + private Object cumulatedVal; + private final Function finisher; + private Subscriber subscriber; + private CompletableFuture completableFuture = new CompletableFuture<>(); + private Stage.Collect collectStage; + private LinkedList> processorList = new LinkedList<>(); + + + /** + * Subscriber with preceding processors included, + * automatically makes all downstream subscriptions when its subscribe method is called. + * + * @param collectStage {@link org.eclipse.microprofile.reactive.streams.operators.spi.Stage.Collect} + * for example {@link org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder#forEach(java.util.function.Consumer)} + * @param precedingProcessorList ordered list of preceding processors(needed for automatic subscription in case of incomplete graph) + */ + @SuppressWarnings("unchecked") + CollectSubscriber(Stage.Collect collectStage, + List> precedingProcessorList) { + this.collectStage = collectStage; + accumulator = (BiConsumer) collectStage.getCollector().accumulator(); + combiner = (BinaryOperator) collectStage.getCollector().combiner(); + finisher = (Function) collectStage.getCollector().finisher(); + //preceding processors + precedingProcessorList.forEach(fp -> this.processorList.add(HybridProcessor.from(fp))); + subscriber = (Subscriber) prepareSubscriber(); + connectingProcessor = prepareConnectingProcessor(); + } + + @Override + public CompletionStage getCompletion() { + return completableFuture; + } + + @Override + @SuppressWarnings("unchecked") + public Subscriber getSubscriber() { + return (Subscriber) connectingProcessor; + } + + + private Subscriber prepareSubscriber() { + return new Subscriber() { + + private Subscription subscription; + private final AtomicBoolean closed = new AtomicBoolean(false); + + @Override + public void onSubscribe(Subscription s) { + Objects.requireNonNull(s); + // https://github.com/reactive-streams/reactive-streams-jvm#2.5 + if (Objects.nonNull(this.subscription)) { + s.cancel(); + return; + } + this.subscription = s; + try { + cumulatedVal = collectStage.getCollector().supplier().get(); + } catch (Throwable t) { + onError(t); + s.cancel(); + } + subscription.request(Long.MAX_VALUE); + } + + @Override + @SuppressWarnings("unchecked") + public void onNext(Object item) { + Objects.requireNonNull(item); + if (!closed.get()) { + try { + accumulator.accept(cumulatedVal, item); + } catch (Throwable t) { + onError(t); + subscription.cancel(); + } + } + } + + @Override + public void onError(Throwable t) { + Objects.requireNonNull(t); + completableFuture.completeExceptionally(t); + } + + @Override + @SuppressWarnings("unchecked") + public void onComplete() { + closed.set(true); + try { + completableFuture.complete(finisher.apply(cumulatedVal)); + } catch (Throwable t) { + onError(t); + } + } + }; + } + + /** + * Artificial processor, in case of incomplete graph does subscriptions downstream automatically. + * + * @return Artificial {@link org.reactivestreams.Processor} + */ + private Processor prepareConnectingProcessor() { + return new Processor() { + @Override + public void subscribe(Subscriber s) { + processorList.getFirst().subscribe(s); + } + + @Override + public void onSubscribe(Subscription s) { + // This is a time for connecting all pre-processors and subscriber + Processor lastProcessor = null; + for (Processor processor : processorList) { + if (lastProcessor != null) { + lastProcessor.subscribe(processor); + } + lastProcessor = processor; + } + if (!processorList.isEmpty()) { + processorList.getLast().subscribe(subscriber); + // First preprocessor act as subscriber + subscriber = processorList.getFirst(); + } + //No processors just forward to subscriber + subscriber.onSubscribe(s); + } + + @Override + public void onNext(Object o) { + Objects.requireNonNull(o); + subscriber.onNext(o); + } + + @Override + public void onError(Throwable t) { + Objects.requireNonNull(t); + subscriber.onError(t); + } + + @Override + public void onComplete() { + subscriber.onComplete(); + } + }; + } +} diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/CumulativeProcessor.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/CumulativeProcessor.java new file mode 100644 index 00000000000..3c774eb409b --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/CumulativeProcessor.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive; + +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Flow; +import java.util.concurrent.atomic.AtomicBoolean; + +import io.helidon.common.reactive.MultiTappedProcessor; +import io.helidon.microprofile.reactive.hybrid.HybridProcessor; + +import org.reactivestreams.Processor; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +/** + * {@link org.reactivestreams.Processor} wrapping ordered list of {@link org.reactivestreams.Processor}s. + */ +public class CumulativeProcessor implements Processor { + private LinkedList> processorList = new LinkedList<>(); + private Subscription subscription; + private AtomicBoolean chainConnected = new AtomicBoolean(false); + + /** + * Create {@link org.reactivestreams.Processor} wrapping ordered list of {@link java.util.concurrent.Flow.Processor}s. + * + * @param precedingProcessorList ordered list of {@link java.util.concurrent.Flow.Processor}s + */ + CumulativeProcessor(List> precedingProcessorList) { + //preceding processors + precedingProcessorList.forEach(fp -> this.processorList.add(HybridProcessor.from(fp))); + //pass-thru if no processors provided + this.processorList.add(HybridProcessor.from(MultiTappedProcessor.create())); + } + + @Override + public void subscribe(Subscriber s) { + processorList.getLast().subscribe(s); + tryChainSubscribe(); + } + + @Override + public void onSubscribe(Subscription subscription) { + Objects.requireNonNull(subscription); + if (Objects.nonNull(this.subscription)) { + subscription.cancel(); + return; + } + this.subscription = subscription; + tryChainSubscribe(); + processorList.getFirst().onSubscribe(subscription); + } + + private void tryChainSubscribe() { + if (!chainConnected.getAndSet(true)) { + // This is the time for connecting all processors + Processor lastProcessor = null; + for (Processor processor : processorList) { + if (lastProcessor != null) { + lastProcessor.subscribe(processor); + } + lastProcessor = processor; + } + } + } + + @Override + public void onNext(Object o) { + processorList.getFirst().onNext(o); + } + + @Override + public void onError(Throwable t) { + processorList.getFirst().onError(t); + } + + @Override + public void onComplete() { + processorList.getFirst().onComplete(); + } +} diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/ExceptionUtils.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/ExceptionUtils.java new file mode 100644 index 00000000000..93a9a4b8628 --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/ExceptionUtils.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive; + +public class ExceptionUtils { + + public static void throwUncheckedException(Throwable t) { + ExceptionUtils.throwWithTypeErasure(t); + } + + @SuppressWarnings("unchecked") + private static void throwWithTypeErasure(Throwable t) throws T { + throw (T) t; + } +} diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/FindFirstSubscriber.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/FindFirstSubscriber.java new file mode 100644 index 00000000000..fc9e8b62f16 --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/FindFirstSubscriber.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Flow; + +import io.helidon.microprofile.reactive.hybrid.HybridSubscriber; + +import org.eclipse.microprofile.reactive.streams.operators.spi.SubscriberWithCompletionStage; +import org.reactivestreams.Subscriber; + +public class FindFirstSubscriber implements Flow.Subscriber, SubscriberWithCompletionStage> { + private Flow.Subscription subscription; + private CompletableFuture> completionStage = new CompletableFuture<>(); + + @Override + public void onSubscribe(Flow.Subscription subscription) { + Objects.requireNonNull(subscription); + // https://github.com/reactive-streams/reactive-streams-jvm#2.5 + if (Objects.nonNull(this.subscription)) { + subscription.cancel(); + } else { + this.subscription = subscription; + this.subscription.request(1); + } + } + + + @Override + public void onNext(T item) { + subscription.cancel(); + completionStage.complete(Optional.of(item)); + } + + @Override + public void onError(Throwable throwable) { + completionStage.completeExceptionally(throwable); + } + + @Override + public void onComplete() { + if (!completionStage.isDone()) { + completionStage.complete(Optional.empty()); + } + } + + @Override + public CompletionStage> getCompletion() { + return completionStage; + } + + @Override + public Subscriber getSubscriber() { + return HybridSubscriber.from(this); + } +} diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/FlatMapCompletionStageProcessor.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/FlatMapCompletionStageProcessor.java new file mode 100644 index 00000000000..18e396fbf1b --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/FlatMapCompletionStageProcessor.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; + +import org.reactivestreams.Processor; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +public class FlatMapCompletionStageProcessor implements Processor { + + private Subscription upstreamSubscription; + private Optional> downStreamSubscriber = Optional.empty(); + private CSBuffer buffer; + private AtomicBoolean onCompleteReceivedAlready = new AtomicBoolean(false); + + @SuppressWarnings("unchecked") + public FlatMapCompletionStageProcessor(Function> mapper) { + Function> csMapper = (Function>) mapper; + buffer = new CSBuffer<>(csMapper); + } + + @Override + public void subscribe(Subscriber subscriber) { + downStreamSubscriber = Optional.of(subscriber); + if (Objects.nonNull(this.upstreamSubscription)) { + subscriber.onSubscribe(new InnerSubscription()); + } + } + + @Override + public void onSubscribe(Subscription upstreamSubscription) { + if (Objects.nonNull(this.upstreamSubscription)) { + upstreamSubscription.cancel(); + return; + } + this.upstreamSubscription = upstreamSubscription; + downStreamSubscriber.ifPresent(s -> s.onSubscribe(new InnerSubscription())); + } + + private class InnerSubscription implements Subscription { + @Override + public void request(long n) { + upstreamSubscription.request(n); + } + + @Override + public void cancel() { + upstreamSubscription.cancel(); + } + } + + @Override + public void onNext(Object o) { + Objects.requireNonNull(o); + buffer.offer(o); + } + + @Override + public void onError(Throwable t) { + Objects.requireNonNull(t); + downStreamSubscriber.get().onError(t); + } + + @Override + public void onComplete() { + onCompleteReceivedAlready.set(true); + if (buffer.isComplete()) { + //Have to wait for all CS to be finished + downStreamSubscriber.get().onComplete(); + } + } + + private class CSBuffer { + + private BlockingQueue buffer = new ArrayBlockingQueue<>(64); + private Function> mapper; + private CompletableFuture lastCs = null; + private ReentrantLock bufferLock = new ReentrantLock(); + + @SuppressWarnings("unchecked") + public CSBuffer(Function> mapper) { + this.mapper = o -> (CompletionStage) mapper.apply(o); + } + + public boolean isComplete() { + return Objects.isNull(lastCs) || (lastCs.isDone() && buffer.isEmpty()); + } + + @SuppressWarnings("unchecked") + public void tryNext(Object o, Throwable t) { + bufferLock.lock(); + if (Objects.nonNull(t)) { + upstreamSubscription.cancel(); + downStreamSubscriber.get().onError(t); + } + + if (Objects.isNull(o)) { + upstreamSubscription.cancel(); + downStreamSubscriber.get().onError(new NullPointerException()); + } + downStreamSubscriber.get().onNext((U) o); + Object nextItem = buffer.poll(); + if (Objects.nonNull(nextItem)) { + lastCs = executeMapper(nextItem); + lastCs.whenComplete(this::tryNext); + } else if (onCompleteReceivedAlready.get()) { + // Received onComplete and all CS are done + downStreamSubscriber.get().onComplete(); + } + bufferLock.unlock(); + } + + public void offer(Object o) { + bufferLock.lock(); + if (buffer.isEmpty() && (Objects.isNull(lastCs) || lastCs.isDone())) { + lastCs = executeMapper(o); + lastCs.whenComplete(this::tryNext); + } else { + buffer.offer(o); + } + bufferLock.unlock(); + } + + public CompletableFuture executeMapper(Object item) { + CompletableFuture cs; + try { + cs = mapper.apply(item).toCompletableFuture(); + } catch (Throwable t) { + upstreamSubscription.cancel(); + downStreamSubscriber.get().onError(t); + cs = CompletableFuture.failedFuture(t); + } + return cs; + } + } +} diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/FromCompletionStagePublisher.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/FromCompletionStagePublisher.java new file mode 100644 index 00000000000..7ce1b0bc54d --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/FromCompletionStagePublisher.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive; + +import java.util.Objects; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +public class FromCompletionStagePublisher implements Publisher { + + private CompletionStage completionStage; + private volatile boolean nullable; + private volatile boolean cancelled = false; + private AtomicBoolean registered = new AtomicBoolean(false); + private Subscriber subscriber; + + public FromCompletionStagePublisher(CompletionStage completionStage, boolean nullable) { + this.nullable = nullable; + Objects.requireNonNull(completionStage); + this.completionStage = completionStage; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(Subscriber subscriber) { + this.subscriber = subscriber; + subscriber.onSubscribe(new Subscription() { + @Override + public void request(long n) { + registerEmitWhenCompleteOnceAction(); + } + + @Override + public void cancel() { + cancelled = true; + } + }); + } + + @SuppressWarnings("unchecked") + private void registerEmitWhenCompleteOnceAction() { + if (!registered.getAndSet(true)) { + completionStage.whenComplete((item, throwable) -> { + if (Objects.isNull(throwable)) { + emit((T) item); + } else { + subscriber.onError(throwable); + } + }); + } + } + + private void emit(T item) { + if (!cancelled) { + if (Objects.nonNull(item)) { + subscriber.onNext(item); + subscriber.onComplete(); + } else { + if (nullable) { + subscriber.onComplete(); + } else { + subscriber.onError(new NullPointerException("Null in non nullable completion stage.")); + } + } + } + } +} diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/GraphBuilder.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/GraphBuilder.java new file mode 100644 index 00000000000..7795865b589 --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/GraphBuilder.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Flow; +import java.util.function.Consumer; +import java.util.function.Function; + +import io.helidon.common.reactive.BufferedProcessor; +import io.helidon.common.reactive.ConcatPublisher; +import io.helidon.common.reactive.Multi; +import io.helidon.common.reactive.MultiCoupledProcessor; +import io.helidon.common.reactive.MultiDistinctProcessor; +import io.helidon.common.reactive.MultiDropWhileProcessor; +import io.helidon.common.reactive.MultiFilterProcessor; +import io.helidon.common.reactive.MultiFlatMapProcessor; +import io.helidon.common.reactive.MultiLimitProcessor; +import io.helidon.common.reactive.MultiMapProcessor; +import io.helidon.common.reactive.MultiOnErrorResumeProcessor; +import io.helidon.common.reactive.MultiPeekProcessor; +import io.helidon.common.reactive.MultiSkipProcessor; +import io.helidon.common.reactive.MultiTakeWhileProcessor; +import io.helidon.common.reactive.MultiTappedProcessor; +import io.helidon.microprofile.reactive.hybrid.HybridProcessor; +import io.helidon.microprofile.reactive.hybrid.HybridPublisher; +import io.helidon.microprofile.reactive.hybrid.HybridSubscriber; + +import org.eclipse.microprofile.reactive.streams.operators.CompletionSubscriber; +import org.eclipse.microprofile.reactive.streams.operators.spi.Graph; +import org.eclipse.microprofile.reactive.streams.operators.spi.Stage; +import org.eclipse.microprofile.reactive.streams.operators.spi.SubscriberWithCompletionStage; +import org.eclipse.microprofile.reactive.streams.operators.spi.UnsupportedStageException; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +public final class GraphBuilder extends HashMap, Consumer> { + + private Multi multi = null; + private List> processorList = new ArrayList<>(); + private CompletionStage completionStage = null; + private SubscriberWithCompletionStage subscriberWithCompletionStage = null; + + @SuppressWarnings("unchecked") + private GraphBuilder() { + registerStage(Stage.PublisherStage.class, stage -> { + multi = MultiRS.toMulti((Publisher) stage.getRsPublisher()); + }); + registerStage(Stage.Concat.class, stage -> { + HelidonReactiveStreamEngine streamEngine = new HelidonReactiveStreamEngine(); + HybridPublisher firstPublisher = HybridPublisher.from(streamEngine.buildPublisher(stage.getFirst())); + HybridPublisher secondPublisher = HybridPublisher.from(streamEngine.buildPublisher(stage.getSecond())); + multi = new ConcatPublisher<>(firstPublisher, secondPublisher); + }); + registerStage(Stage.Of.class, stage -> { + multi = Multi.from((Iterable) stage.getElements()); + }); + registerStage(Stage.Failed.class, stage -> { + multi = Multi.error(stage.getError()); + }); + registerStage(Stage.FromCompletionStage.class, stage -> { + multi = MultiRS.toMulti(new FromCompletionStagePublisher<>(stage.getCompletionStage(), false)); + }); + registerStage(Stage.FromCompletionStageNullable.class, stage -> { + multi = MultiRS.toMulti(new FromCompletionStagePublisher<>(stage.getCompletionStage(), true)); + }); + registerStage(Stage.Map.class, stage -> { + Function mapper = (Function) stage.getMapper(); + addProcessor(MultiMapProcessor.create(mapper::apply)); + }); + registerStage(Stage.Filter.class, stage -> { + addProcessor(MultiFilterProcessor.create(stage.getPredicate())); + }); + registerStage(Stage.TakeWhile.class, stage -> { + addProcessor(MultiTakeWhileProcessor.create(stage.getPredicate())); + }); + registerStage(Stage.DropWhile.class, stage -> { + addProcessor(MultiDropWhileProcessor.create(stage.getPredicate())); + }); + registerStage(Stage.Peek.class, stage -> { + Consumer peekConsumer = (Consumer) stage.getConsumer(); + addProcessor(MultiPeekProcessor.create(peekConsumer)); + }); + registerStage(Stage.ProcessorStage.class, stage -> { + addProcessor(stage.getRsProcessor()); + }); + registerStage(Stage.Limit.class, stage -> { + addProcessor(MultiLimitProcessor.create(stage.getLimit())); + }); + registerStage(Stage.Skip.class, stage -> { + addProcessor(MultiSkipProcessor.create(stage.getSkip())); + }); + registerStage(Stage.Distinct.class, stage -> { + addProcessor(MultiDistinctProcessor.create()); + }); + registerStage(Stage.FlatMap.class, stage -> { + Function> pubMapper = o -> { + Function oMapper = (Function) stage.getMapper(); + return HybridPublisher.from(new HelidonReactiveStreamEngine().buildPublisher(oMapper.apply(o))); + }; + addProcessor(MultiFlatMapProcessor.fromPublisherMapper(pubMapper)); + }); + registerStage(Stage.FlatMapIterable.class, stage -> { + Function> mapper = (Function>) stage.getMapper(); + addProcessor(MultiFlatMapProcessor.fromIterableMapper(o -> (Iterable) mapper.apply(o))); + }); + registerStage(Stage.FlatMapCompletionStage.class, stage -> { + addProcessor(new FlatMapCompletionStageProcessor(stage.getMapper())); + }); + registerStage(Stage.Coupled.class, stage -> { + Subscriber subscriber = GraphBuilder.create() + .from(stage.getSubscriber()).getSubscriberWithCompletionStage().getSubscriber(); + Publisher publisher = GraphBuilder.create().from(stage.getPublisher()).getPublisher(); + addProcessor(MultiCoupledProcessor.create(HybridSubscriber.from(subscriber), HybridPublisher.from(publisher))); + }); + registerStage(Stage.OnTerminate.class, stage -> { + addProcessor(MultiTappedProcessor.create() + .onComplete(stage.getAction()) + .onCancel((s) -> stage.getAction().run()) + .onError((t) -> stage.getAction().run())); + }); + registerStage(Stage.OnComplete.class, stage -> { + addProcessor(MultiTappedProcessor.create().onComplete(stage.getAction())); + }); + registerStage(Stage.OnError.class, stage -> { + addProcessor(MultiTappedProcessor.create().onError(stage.getConsumer())); + }); + registerStage(Stage.OnErrorResume.class, stage -> { + addProcessor(MultiOnErrorResumeProcessor.resume(stage.getFunction())); + }); + registerStage(Stage.OnErrorResumeWith.class, stage -> { + Function> publisherSupplier = throwable -> + HybridPublisher.from(GraphBuilder.create().from(stage.getFunction().apply(throwable)).getPublisher()); + addProcessor(MultiOnErrorResumeProcessor.resumeWith(publisherSupplier)); + }); + registerStage(Stage.Cancel.class, stage -> { + CancelSubscriber cancelSubscriber = new CancelSubscriber<>(); + subscribe(cancelSubscriber); + this.subscriberWithCompletionStage = cancelSubscriber; + }); + registerStage(Stage.FindFirst.class, stage -> { + FindFirstSubscriber firstSubscriber = new FindFirstSubscriber<>(); + subscribe(firstSubscriber); + this.subscriberWithCompletionStage = firstSubscriber; + }); + registerStage(Stage.SubscriberStage.class, stage -> { + Subscriber subscriber = (Subscriber) stage.getRsSubscriber(); + RedeemingCompletionSubscriber completionSubscriber; + if (subscriber instanceof CompletionSubscriber) { + completionSubscriber = RedeemingCompletionSubscriber + .of(subscriber, ((CompletionSubscriber) subscriber).getCompletion()); + } else { + completionSubscriber = RedeemingCompletionSubscriber.of(subscriber, new CompletableFuture<>()); + } + subscribe(completionSubscriber); + this.subscriberWithCompletionStage = completionSubscriber; + }); + registerStage(Stage.Collect.class, stage -> { + // Foreach + this.subscriberWithCompletionStage = new CollectSubscriber(stage, processorList); + // If producer was supplied + if (multi != null) { + multi.subscribe(HybridSubscriber.from(subscriberWithCompletionStage.getSubscriber())); + } + }); + } + + @SuppressWarnings("unchecked") + private void addProcessor(Processor processor) { + processorList.add(HybridProcessor.from((Processor) processor)); + } + + @SuppressWarnings("unchecked") + private void addProcessor(BufferedProcessor processor) { + processorList.add(HybridProcessor.from((BufferedProcessor) processor)); + } + + @SuppressWarnings("unchecked") + private void addProcessor(Flow.Processor processor) { + processorList.add((Flow.Processor) processor); + } + + public static GraphBuilder create() { + return new GraphBuilder(); + } + + public GraphBuilder from(Graph graph) { + graph.getStages().forEach(this::add); + return this; + } + + public GraphBuilder add(Stage stage) { + Consumer stageConsumer = this.get(stage.getClass()); + + this.keySet() + .stream() + .filter(c -> c.isAssignableFrom(stage.getClass())) + .map(this::get) + .findFirst() + .orElseThrow(() -> new UnsupportedStageException(stage)) + .accept(stage); + + return this; + } + + /** + * Return subscriber from even incomplete graph, + * in case of incomplete graph does subscriptions downstream automatically in the + * {@link CollectSubscriber}. + * + * @param type of items subscriber consumes + * @param type of items subscriber emits + * @return {@link org.eclipse.microprofile.reactive.streams.operators.spi.SubscriberWithCompletionStage} + */ + @SuppressWarnings("unchecked") + SubscriberWithCompletionStage getSubscriberWithCompletionStage() { + return (SubscriberWithCompletionStage) subscriberWithCompletionStage; + } + + /** + * Return {@link java.util.concurrent.CompletionStage} + * either from supplied {@link org.reactivestreams.Subscriber} + * for example by {@link org.reactivestreams.Publisher#subscribe(org.reactivestreams.Subscriber)} + * or from completion stage for example + * {@link org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder#forEach(java.util.function.Consumer)}. + * + * @param type of items subscriber consumes + * @return {@link CollectSubscriber} + */ + @SuppressWarnings("unchecked") + CompletionStage getCompletionStage() { + return (CompletionStage) (completionStage != null ? completionStage : subscriberWithCompletionStage.getCompletion()); + } + + /** + * Return {@link org.reactivestreams.Processor} wrapping all processor stages from processor builder. + *

See example: + *

{@code
+     *   Processor processor = ReactiveStreams.builder()
+     *       .map(i -> i + 1)
+     *       .flatMap(i -> ReactiveStreams.of(i, i))
+     *       .map(i -> Integer.toString(i))
+     *       .buildRs();
+     * }
+ * + * @param type of items subscriber consumes + * @param type of items subscriber emits + * @return {@link org.reactivestreams.Processor} wrapping all processor stages + */ + @SuppressWarnings("unchecked") + Processor getProcessor() { + return (Processor) new CumulativeProcessor(processorList); + } + + /** + * Returns {@link org.reactivestreams.Publisher} made from supplied stages. + *

See example: + *

{@code
+     *   ReactiveStreams
+     *      .of("10", "20", "30")
+     *      .map(a -> a.replaceAll("0", ""))
+     *      .map(Integer::parseInt)
+     *      .buildRs()
+     * }
+ * + * @return {@link org.reactivestreams.Publisher} + */ + @SuppressWarnings("unchecked") + Publisher getPublisher() { + subscribeUpStream(); + return (Publisher) MultiRS.from(multi); + } + + @SuppressWarnings("unchecked") + @Deprecated + private void subscribeUpStream() { + if (multi != null) { + for (Flow.Processor p : processorList) { + multi.subscribe(p); + multi = (Multi) p; + } + } else { + throw new RuntimeException("No producer was supplied"); + } + } + + private void subscribe(Subscriber subscriber) { + CumulativeProcessor cumulativeProcessor = new CumulativeProcessor(processorList); + if (multi != null) { + multi.subscribe(HybridProcessor.from(cumulativeProcessor)); + cumulativeProcessor.subscribe(subscriber); + } + } + + private void subscribe(Flow.Subscriber subscriber) { + subscribe((Subscriber) HybridSubscriber.from(subscriber)); + } + + @SuppressWarnings("unchecked") + private GraphBuilder registerStage(Class stageType, Consumer consumer) { + this.put(stageType, (Consumer) consumer); + return this; + } +} diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/HelidonReactiveStreamEngine.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/HelidonReactiveStreamEngine.java new file mode 100644 index 00000000000..3c9cf5b786e --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/HelidonReactiveStreamEngine.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive; + + +import java.util.concurrent.CompletionStage; + +import org.eclipse.microprofile.reactive.streams.operators.spi.Graph; +import org.eclipse.microprofile.reactive.streams.operators.spi.ReactiveStreamsEngine; +import org.eclipse.microprofile.reactive.streams.operators.spi.SubscriberWithCompletionStage; +import org.eclipse.microprofile.reactive.streams.operators.spi.UnsupportedStageException; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; + +/** + * Implementation of {@link org.reactivestreams Reactive Streams} with operators + * backed by {@link io.helidon.common.reactive Helidon reactive streams}. + * + * @see org.eclipse.microprofile.reactive.streams.operators.spi.ReactiveStreamsEngine + */ +public class HelidonReactiveStreamEngine implements ReactiveStreamsEngine { + + @Override + @SuppressWarnings("unchecked") + public Publisher buildPublisher(Graph graph) throws UnsupportedStageException { + return GraphBuilder.create().from(graph).getPublisher(); + } + + @Override + @SuppressWarnings("unchecked") + public SubscriberWithCompletionStage buildSubscriber(Graph graph) throws UnsupportedStageException { + return GraphBuilder.create().from(graph).getSubscriberWithCompletionStage(); + } + + @Override + @SuppressWarnings("unchecked") + public Processor buildProcessor(Graph graph) throws UnsupportedStageException { + return GraphBuilder.create().from(graph).getProcessor(); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage buildCompletion(Graph graph) throws UnsupportedStageException { + return GraphBuilder.create().from(graph).getCompletionStage(); + } +} + + diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/MultiRS.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/MultiRS.java new file mode 100644 index 00000000000..e7513def756 --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/MultiRS.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive; + +import java.util.concurrent.Flow; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.helidon.common.reactive.Multi; +import io.helidon.microprofile.reactive.hybrid.HybridPublisher; +import io.helidon.microprofile.reactive.hybrid.HybridSubscriber; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +/** + * Conversion methods between {@link io.helidon.common.reactive Helidon reactive streams} and the {@link org.reactivestreams Reactive Streams}. + * Wraps publishers/processors/subscribers to Hybrid {@link io.helidon.microprofile.reactive.hybrid} variants + */ +public interface MultiRS { + + /** + * Create {@link io.helidon.common.reactive Helidon reactive streams} {@link java.util.concurrent.Flow.Subscriber} + * from {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Subscriber}. + * + * @param subscriber {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Subscriber} + * @param type of streamed item + * @return {@link io.helidon.common.reactive Helidon reactive streams} {@link java.util.concurrent.Flow.Subscriber} + */ + static Flow.Subscriber from(Subscriber subscriber) { + return HybridSubscriber.from(subscriber); + } + + /** + * Create {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Subscriber} + * from {@link io.helidon.common.reactive Helidon reactive streams} subscriber. + * + * @param subscriber Helidon {@link io.helidon.common.reactive.Multi} stream {@link java.util.concurrent.Flow.Subscriber} + * @param type of streamed item + * @return {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Subscriber} + */ + static Subscriber from(Flow.Subscriber subscriber) { + return HybridSubscriber.from(subscriber); + } + + /** + * Create {@link io.helidon.common.reactive Helidon reactive streams} {@link java.util.concurrent.Flow.Publisher} + * from {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Publisher}. + * + * @param publisher {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Publisher} + * @param type of streamed item + * @return Multi stream {@link java.util.concurrent.Flow.Publisher} + */ + static Flow.Publisher from(Publisher publisher) { + return HybridPublisher.from(publisher); + } + + /** + * Create {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Publisher} + * from {@link io.helidon.common.reactive.Multi} stream {@link java.util.concurrent.Flow.Publisher}. + * + * @param publisher {@link io.helidon.common.reactive.Multi} stream {@link java.util.concurrent.Flow.Publisher} + * @param type of streamed item + * @return {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Publisher} + */ + static Publisher from(Flow.Publisher publisher) { + return HybridPublisher.from(publisher); + } + + /** + * Create Helidon {@link io.helidon.common.reactive.Multi} stream + * from {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Publisher}. + * + * @param publisher {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Publisher} + * @param type of streamed item + * @return {@link io.helidon.common.reactive.Multi} stream + */ + static Multi toMulti(Publisher publisher) { + return Multi.from(HybridPublisher.from(publisher)); + } + + /** + * Create {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Publisher} + * from Java {@link java.util.stream.Stream}. + * + * @param stream Java {@link java.util.stream.Stream} + * @param type of streamed item + * @return {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Publisher} + */ + static Publisher just(Stream stream) { + return MultiRS.from(Multi.just(stream.collect(Collectors.toList()))); + } + + /** + * Create {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Publisher} + * from items vararg. + * + * @param items items varargs to be streamed + * @param type of streamed items + * @return {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Publisher} + */ + static Publisher just(U... items) { + return MultiRS.from(Multi.just(items)); + } +} diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/RedeemingCompletionSubscriber.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/RedeemingCompletionSubscriber.java new file mode 100644 index 00000000000..62556aee664 --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/RedeemingCompletionSubscriber.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive; + +import java.util.Objects; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletionStage; + +import org.eclipse.microprofile.reactive.streams.operators.spi.SubscriberWithCompletionStage; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +/** + * Replacement for non redeeming {@link org.eclipse.microprofile.reactive.streams.operators.CompletionSubscriber}'s DefaultCompletionSubscriber. + *

+ * + * @param {@link org.reactivestreams.Subscriber} item type + * @param {@link java.util.concurrent.CompletionStage} payload type + * @see microprofile-reactive-streams-operators #129 + */ +class RedeemingCompletionSubscriber implements org.eclipse.microprofile.reactive.streams.operators.CompletionSubscriber, SubscriberWithCompletionStage { + + private final Subscriber subscriber; + private final CompletionStage completion; + + /** + * Create a {@link RedeemingCompletionSubscriber} by combining the given subscriber and completion stage. + * The objects passed to this method should not be associated with more than one stream instance. + * + * @param subscriber subscriber to associate with completion stage + * @param completion completion stage to associate with subscriber + * @return {@link RedeemingCompletionSubscriber} + */ + static RedeemingCompletionSubscriber of(Subscriber subscriber, CompletionStage completion) { + return new RedeemingCompletionSubscriber<>(subscriber, completion); + } + + private RedeemingCompletionSubscriber(Subscriber subscriber, CompletionStage completion) { + this.subscriber = subscriber; + this.completion = completion; + } + + @Override + public CompletionStage getCompletion() { + return completion; + } + + @Override + public Subscriber getSubscriber() { + return this; + } + + @Override + public void onSubscribe(Subscription s) { + Objects.requireNonNull(s); + subscriber.onSubscribe(new Subscription() { + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + completion.toCompletableFuture().completeExceptionally(new CancellationException()); + } + }); + } + + @Override + public void onNext(T t) { + subscriber.onNext(t); + } + + @Override + public void onError(Throwable t) { + subscriber.onError(t); + completion.toCompletableFuture().completeExceptionally(t); + } + + @Override + public void onComplete() { + subscriber.onComplete(); + } +} diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/hybrid/HybridProcessor.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/hybrid/HybridProcessor.java new file mode 100644 index 00000000000..2030156871c --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/hybrid/HybridProcessor.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive.hybrid; + +import java.security.InvalidParameterException; +import java.util.concurrent.Flow; + +import io.helidon.common.reactive.Multi; + +import org.reactivestreams.Processor; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +/** + * Wrapper for {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Processor} + * or {@link io.helidon.common.reactive Helidon reactive streams} {@link java.util.concurrent.Flow.Processor}, + * to be used interchangeably. + * + * @param type of items processor consumes + * @param type of items processor emits + */ +public class HybridProcessor implements Flow.Processor, Processor, Multi { + private Processor reactiveProcessor; + private Flow.Processor flowProcessor; + + private HybridProcessor(Processor processor) { + this.reactiveProcessor = processor; + } + + private HybridProcessor(Flow.Processor processor) { + this.flowProcessor = processor; + } + + /** + * Create new {@link io.helidon.microprofile.reactive.hybrid.HybridProcessor} + * from {@link java.util.concurrent.Flow.Processor}. + * + * @param processor {@link java.util.concurrent.Flow.Processor} to wrap + * @param type of items processor consumes + * @param type of items processor emits + * @return {@link io.helidon.microprofile.reactive.hybrid.HybridProcessor} + * compatible with {@link org.reactivestreams Reactive Streams} + * and {@link io.helidon.common.reactive Helidon reactive streams} + */ + public static HybridProcessor from(Flow.Processor processor) { + return new HybridProcessor(processor); + } + + /** + * Create new {@link io.helidon.microprofile.reactive.hybrid.HybridProcessor} + * from {@link org.reactivestreams.Processor}. + * + * @param processor {@link org.reactivestreams.Processor} to wrap + * @param type of items processor consumes + * @param type of items processor emits + * @return {@link io.helidon.microprofile.reactive.hybrid.HybridProcessor} + * compatible with {@link org.reactivestreams Reactive Streams} + * and {@link io.helidon.common.reactive Helidon reactive streams} + */ + public static HybridProcessor from(Processor processor) { + return new HybridProcessor(processor); + } + + @Override + public void subscribe(Flow.Subscriber subscriber) { + this.subscribe((Subscriber) HybridSubscriber.from(subscriber)); + } + + @Override + public void subscribe(Subscriber s) { + if (reactiveProcessor != null) { + reactiveProcessor.subscribe(s); + } else if (flowProcessor != null) { + flowProcessor.subscribe(HybridSubscriber.from(s)); + } else { + throw new InvalidParameterException("Hybrid processor has no processor"); + } + } + + @Override + public void onSubscribe(Flow.Subscription subscription) { + if (reactiveProcessor != null) { + reactiveProcessor.onSubscribe(HybridSubscription.from(subscription)); + } else if (flowProcessor != null) { + flowProcessor.onSubscribe(subscription); + } else { + throw new InvalidParameterException("Hybrid processor has no processor"); + } + } + + @Override + public void onSubscribe(Subscription s) { + if (reactiveProcessor != null) { + reactiveProcessor.onSubscribe(s); + } else if (flowProcessor != null) { + flowProcessor.onSubscribe(HybridSubscription.from(s)); + } else { + throw new InvalidParameterException("Hybrid processor has no processor"); + } + } + + @Override + public void onNext(T item) { + if (reactiveProcessor != null) { + reactiveProcessor.onNext(item); + } else if (flowProcessor != null) { + flowProcessor.onNext(item); + } else { + throw new InvalidParameterException("Hybrid processor has no processor"); + } + } + + @Override + public void onError(Throwable throwable) { + if (reactiveProcessor != null) { + reactiveProcessor.onError(throwable); + } else if (flowProcessor != null) { + flowProcessor.onError(throwable); + } else { + throw new InvalidParameterException("Hybrid processor has no processor"); + } + } + + @Override + public void onComplete() { + if (reactiveProcessor != null) { + reactiveProcessor.onComplete(); + } else if (flowProcessor != null) { + flowProcessor.onComplete(); + } else { + throw new InvalidParameterException("Hybrid processor has no processor"); + } + } + + @Override + public String toString() { + return reactiveProcessor != null ? reactiveProcessor.toString() : String.valueOf(flowProcessor); + } +} diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/hybrid/HybridPublisher.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/hybrid/HybridPublisher.java new file mode 100644 index 00000000000..479602cbd29 --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/hybrid/HybridPublisher.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive.hybrid; + +import java.util.concurrent.Flow; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +/** + * Wrapper for {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Publisher} + * or {@link io.helidon.common.reactive Helidon reactive streams} {@link java.util.concurrent.Flow.Publisher}, + * to be used interchangeably. + * + * @param type of items + */ +public class HybridPublisher implements Flow.Publisher, Publisher { + + private Flow.Publisher flowPublisher; + private Publisher reactivePublisher; + + private HybridPublisher(Flow.Publisher flowPublisher) { + this.flowPublisher = flowPublisher; + } + + private HybridPublisher(Publisher reactivePublisher) { + this.reactivePublisher = reactivePublisher; + } + + /** + * Create new {@link io.helidon.microprofile.reactive.hybrid.HybridPublisher} + * from {@link java.util.concurrent.Flow.Publisher}. + * + * @param publisher {@link java.util.concurrent.Flow.Publisher} to wrap + * @param type of items + * @return {@link io.helidon.microprofile.reactive.hybrid.HybridPublisher} + * compatible with {@link org.reactivestreams Reactive Streams} + * and {@link io.helidon.common.reactive Helidon reactive streams} + */ + public static HybridPublisher from(Publisher publisher) { + return new HybridPublisher(publisher); + } + + /** + * Create new {@link io.helidon.microprofile.reactive.hybrid.HybridPublisher} + * from {@link org.reactivestreams.Publisher}. + * + * @param publisher {@link org.reactivestreams.Publisher} to wrap + * @param type of items + * @return {@link io.helidon.microprofile.reactive.hybrid.HybridPublisher} + * compatible with {@link org.reactivestreams Reactive Streams} + * and {@link io.helidon.common.reactive Helidon reactive streams} + */ + public static HybridPublisher from(Flow.Publisher publisher) { + return new HybridPublisher(publisher); + } + + @Override + public void subscribe(Flow.Subscriber subscriber) { + reactivePublisher.subscribe(HybridSubscriber.from(subscriber)); + } + + @Override + public void subscribe(Subscriber subscriber) { + flowPublisher.subscribe(HybridSubscriber.from(subscriber)); + } +} diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/hybrid/HybridSubscriber.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/hybrid/HybridSubscriber.java new file mode 100644 index 00000000000..d8fd96bf3c5 --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/hybrid/HybridSubscriber.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive.hybrid; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.Flow; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +/** + * Wrapper for {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Subscriber} + * or {@link io.helidon.common.reactive Helidon reactive streams} {@link java.util.concurrent.Flow.Subscriber}, + * to be used interchangeably. + * + * @param type of items + */ +public class HybridSubscriber implements Flow.Subscriber, Subscriber { + + private Optional> flowSubscriber = Optional.empty(); + private Optional> reactiveSubscriber = Optional.empty(); + private Type type; + + private HybridSubscriber(Flow.Subscriber subscriber) { + this.type = Type.FLOW; + this.flowSubscriber = Optional.of(subscriber); + } + + private HybridSubscriber(Subscriber subscriber) { + this.type = Type.RS; + this.reactiveSubscriber = Optional.of(subscriber); + } + + /** + * Create new {@link io.helidon.microprofile.reactive.hybrid.HybridSubscriber} + * from {@link java.util.concurrent.Flow.Subscriber}. + * + * @param subscriber {@link java.util.concurrent.Flow.Subscriber} to wrap + * @param type of items + * @return {@link io.helidon.microprofile.reactive.hybrid.HybridSubscriber} + * compatible with {@link org.reactivestreams Reactive Streams} + * and {@link io.helidon.common.reactive Helidon reactive streams} + */ + public static HybridSubscriber from(Flow.Subscriber subscriber) { + Objects.requireNonNull(subscriber); + return new HybridSubscriber(subscriber); + } + + /** + * Create new {@link io.helidon.microprofile.reactive.hybrid.HybridSubscriber} + * from {@link org.reactivestreams.Subscriber}. + * + * @param subscriber {@link org.reactivestreams.Subscriber} to wrap + * @param type of items + * @return {@link io.helidon.microprofile.reactive.hybrid.HybridSubscriber} + * compatible with {@link org.reactivestreams Reactive Streams} + * and {@link io.helidon.common.reactive Helidon reactive streams} + */ + public static HybridSubscriber from(Subscriber subscriber) { + Objects.requireNonNull(subscriber); + return new HybridSubscriber(subscriber); + } + + @Override + public void onSubscribe(Flow.Subscription subscription) { + flowSubscriber.ifPresent(s -> s.onSubscribe(HybridSubscription.from(subscription).onCancel(this::releaseReferences))); + reactiveSubscriber.ifPresent(s -> s.onSubscribe(HybridSubscription.from(subscription).onCancel(this::releaseReferences))); + } + + @Override + public void onSubscribe(Subscription subscription) { + Objects.requireNonNull(subscription); + flowSubscriber.ifPresent(s -> s.onSubscribe(HybridSubscription.from(subscription))); + reactiveSubscriber.ifPresent(s -> s.onSubscribe(subscription)); + } + + @Override + public void onNext(T item) { + flowSubscriber.ifPresent(s -> s.onNext(item)); + reactiveSubscriber.ifPresent(s -> s.onNext(item)); + } + + @Override + public void onError(Throwable t) { + flowSubscriber.ifPresent(s -> s.onError(t)); + reactiveSubscriber.ifPresent(s -> s.onError(t)); + } + + @Override + public void onComplete() { + flowSubscriber.ifPresent(Flow.Subscriber::onComplete); + reactiveSubscriber.ifPresent(Subscriber::onComplete); + } + + public void releaseReferences() { + flowSubscriber = Optional.empty(); + reactiveSubscriber = Optional.empty(); + } + + private enum Type { + RS, FLOW + } + +} diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/hybrid/HybridSubscription.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/hybrid/HybridSubscription.java new file mode 100644 index 00000000000..0157fd01215 --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/hybrid/HybridSubscription.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive.hybrid; + +import java.security.InvalidParameterException; +import java.util.Optional; +import java.util.concurrent.Flow; + +import org.reactivestreams.Subscription; + +/** + * Wrapper for {@link org.reactivestreams Reactive Streams} {@link org.reactivestreams.Subscription} + * or {@link io.helidon.common.reactive Helidon reactive streams} {@link java.util.concurrent.Flow.Subscription}, + * to be used interchangeably. + */ +public class HybridSubscription implements Flow.Subscription, Subscription { + + private Flow.Subscription flowSubscription; + private Subscription reactiveSubscription; + private Optional onCancel = Optional.empty(); + + private HybridSubscription(Flow.Subscription flowSubscription) { + this.flowSubscription = flowSubscription; + } + + private HybridSubscription(Subscription reactiveSubscription) { + this.reactiveSubscription = reactiveSubscription; + } + + /** + * Create new {@link HybridSubscription} + * from {@link java.util.concurrent.Flow.Processor}. + * + * @param subscription {@link java.util.concurrent.Flow.Subscription} to wrap + * @return {@link HybridSubscription} + * compatible with {@link org.reactivestreams Reactive Streams} + * and {@link io.helidon.common.reactive Helidon reactive streams} + */ + public static HybridSubscription from(Flow.Subscription subscription) { + return new HybridSubscription(subscription); + } + + /** + * Create new {@link HybridSubscription} + * from {@link java.util.concurrent.Flow.Subscription}. + * + * @param subscription {@link java.util.concurrent.Flow.Subscription} to wrap + * @return {@link HybridSubscription} + * compatible with {@link org.reactivestreams Reactive Streams} + * and {@link io.helidon.common.reactive Helidon reactive streams} + */ + public static HybridSubscription from(Subscription subscription) { + return new HybridSubscription(subscription); + } + + public HybridSubscription onCancel(Runnable runnable) { + this.onCancel = Optional.of(runnable); + return this; + } + + @Override + public void request(long n) { + if (flowSubscription != null) { + flowSubscription.request(n); + } else if (reactiveSubscription != null) { + reactiveSubscription.request(n); + } else { + throw new InvalidParameterException("Hybrid subscription has no subscription"); + } + } + + @Override + public void cancel() { + if (flowSubscription != null) { + flowSubscription.cancel(); + } else if (reactiveSubscription != null) { + reactiveSubscription.cancel(); + } else { + throw new InvalidParameterException("Hybrid subscription has no subscription"); + } + onCancel.ifPresent(Runnable::run); + } +} diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/hybrid/package-info.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/hybrid/package-info.java new file mode 100644 index 00000000000..e4b3613ebe6 --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/hybrid/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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. + * + */ + +/** + * Hybrid variants of publishers/processors/subscribers usable in both apis + * {@link io.helidon.common.reactive Helidon reactive streams} + * and {@link org.reactivestreams Reactive Streams}. + */ +package io.helidon.microprofile.reactive.hybrid; diff --git a/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/package-info.java b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/package-info.java new file mode 100644 index 00000000000..301c0f7aa49 --- /dev/null +++ b/microprofile/reactive-streams/src/main/java/io/helidon/microprofile/reactive/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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. + * + */ + +/** + * Support for MicroProfile Reactive Streams Operators in Helidon MP. + */ +package io.helidon.microprofile.reactive; diff --git a/microprofile/reactive-streams/src/main/resources/META-INF/services/org.eclipse.microprofile.reactive.streams.operators.spi.ReactiveStreamsEngine b/microprofile/reactive-streams/src/main/resources/META-INF/services/org.eclipse.microprofile.reactive.streams.operators.spi.ReactiveStreamsEngine new file mode 100644 index 00000000000..f6dfaa769a0 --- /dev/null +++ b/microprofile/reactive-streams/src/main/resources/META-INF/services/org.eclipse.microprofile.reactive.streams.operators.spi.ReactiveStreamsEngine @@ -0,0 +1,17 @@ +# +# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# +# Licensed 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. +# + +io.helidon.microprofile.reactive.HelidonReactiveStreamEngine \ No newline at end of file diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/AbstractProcessorTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/AbstractProcessorTest.java new file mode 100644 index 00000000000..82764cca1e5 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/AbstractProcessorTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Flow; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.LongStream; + +import io.helidon.common.reactive.MultiTappedProcessor; +import io.helidon.microprofile.reactive.hybrid.HybridProcessor; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; + +public abstract class AbstractProcessorTest { + + @SuppressWarnings("unchecked") + protected Processor getProcessor() { + Flow.Processor processor = (Flow.Processor) MultiTappedProcessor.create(); + return HybridProcessor.from(processor); + } + + protected abstract Processor getFailedProcessor(RuntimeException t); + + protected Publisher getPublisher(long items) { + if (items < 1) { + return new MockPublisher(); + } else { + return ReactiveStreams.fromIterable(LongStream.range(0, items).boxed().collect(Collectors.toList())).buildRs(); + } + } + + private MockPublisher getMockPublisher() { + Publisher pub = getPublisher(-5); + assumeTrue(pub instanceof MockPublisher); + return (MockPublisher) pub; + } + + /** + * https://github.com/reactive-streams/reactive-streams-jvm#1.1 + */ + @Test + void requestCount() { + MockPublisher p = getMockPublisher(); + testProcessor(ReactiveStreams.fromPublisher(p).via(getProcessor()).buildRs(), s -> { + s.expectRequestCount(0); + s.request(1); + p.sendNext(4); + s.expectRequestCount(1); + s.request(1); + s.request(2); + p.sendNext(5); + p.sendNext(6); + p.sendNext(7); + s.expectRequestCount(4); + s.cancel(); + }); + } + + /** + * https://github.com/reactive-streams/reactive-streams-jvm#2.8 + */ + @Test + void nextAfterCancel() { + MockPublisher p = getMockPublisher(); + testProcessor(ReactiveStreams.fromPublisher(p).via(getProcessor()).buildRs(), s -> { + s.request(4); + p.sendNext(2); + s.cancel(); + p.sendNext(4); + s.expectSum(2); + }); + } + + /** + * https://github.com/reactive-streams/reactive-streams-jvm#2.5 + */ + @Test + void cancel2ndSubscription() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture cancelled = new CompletableFuture<>(); + Publisher p = getPublisher(4); + Processor processor = getProcessor(); + testProcessor(ReactiveStreams.fromPublisher(p).via(processor).buildRs(), s -> { + s.request(2); + }); + + processor.onSubscribe(new Subscription() { + @Override + public void request(long n) { + + } + + @Override + public void cancel() { + cancelled.complete(null); + } + }); + + cancelled.get(1, TimeUnit.SECONDS); + } + + + @Test + void onCompletePropagation() { + testProcessor(ReactiveStreams.fromPublisher(getPublisher(3)).via(getProcessor()).buildRs(), s -> { + s.request(1); + s.expectRequestCount(1); + s.request(2); + s.expectRequestCount(3); + s.expectOnComplete(); + }); + } + + @Test + void requestCountProcessorTest() { + testProcessor(ReactiveStreams.fromPublisher(getPublisher(1_000_400L)).via(getProcessor()).buildRs(), s -> { + s.request(15); + s.expectRequestCount(15); + s.request(3); + s.expectRequestCount(18); + }); + } + + @Test + void longOverFlow() { + testProcessor(ReactiveStreams.fromPublisher(getPublisher(1_000_400L)).via(getProcessor()).buildRs(), s -> { + s.cancelAfter(1_000_0L); + s.request(Long.MAX_VALUE - 1); + s.request(Long.MAX_VALUE - 1); + }); + } + + @Test + void cancelOnError() throws InterruptedException, ExecutionException, TimeoutException { + Processor failedProcessor = getFailedProcessor(new TestRuntimeException()); + assumeTrue(Objects.nonNull(failedProcessor)); + CompletableFuture cancelled = new CompletableFuture<>(); + CompletionStage> result = ReactiveStreams.fromPublisher(getPublisher(1_000_000L)) + .onTerminate(() -> cancelled.complete(null)) + .via(failedProcessor) + .toList() + .run(); + cancelled.get(1, TimeUnit.SECONDS); + assertThrows(ExecutionException.class, () -> result.toCompletableFuture().get(1, TimeUnit.SECONDS), TestRuntimeException.TEST_MSG); + } + + @Test + void finiteOnCompleteTest() throws InterruptedException, ExecutionException, TimeoutException { + finiteOnCompleteTest(getProcessor()); + } + + private void finiteOnCompleteTest(Processor processor) + throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture completed = new CompletableFuture<>(); + ReactiveStreams.fromPublisher(getPublisher(3)) + .via(processor) + .onComplete(() -> completed.complete(null)) + .toList().run().toCompletableFuture().get(1, TimeUnit.SECONDS); + + completed.get(1, TimeUnit.SECONDS); + } + + protected void testProcessor(Publisher publisher, + Consumer testBody) { + CountingSubscriber subscriber = new CountingSubscriber(); + publisher.subscribe(subscriber); + testBody.accept(subscriber); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/ConcatProcessorTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/ConcatProcessorTest.java new file mode 100644 index 00000000000..e9a79bdddcb --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/ConcatProcessorTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import java.util.stream.Collectors; +import java.util.stream.LongStream; + +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; + +public class ConcatProcessorTest extends AbstractProcessorTest { + + @Override + protected Publisher getPublisher(long items) { + return ReactiveStreams.concat( + ReactiveStreams.fromIterable(LongStream.range(0, items / 2).boxed().collect(Collectors.toList())), + ReactiveStreams.fromIterable(LongStream.range(items / 2, items).boxed().collect(Collectors.toList())) + ).buildRs(); + } + + @Override + protected Processor getFailedProcessor(RuntimeException t) { + return ReactiveStreams.builder().peek(o -> { + throw new TestRuntimeException(); + }).buildRs(); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/ConsumableSubscriber.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/ConsumableSubscriber.java new file mode 100644 index 00000000000..7d09b381243 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/ConsumableSubscriber.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; + +public class ConsumableSubscriber implements Subscriber { + + private Consumer onNext; + private AtomicLong requestCount = new AtomicLong(1000); + private Subscription subscription; + private final AtomicBoolean closed = new AtomicBoolean(false); + + public ConsumableSubscriber(Consumer onNext) { + this.onNext = onNext; + } + public ConsumableSubscriber(Consumer onNext, long requestCount) { + this.onNext = onNext; + this.requestCount.set(requestCount); + } + + @Override + public void onSubscribe(Subscription s) { + this.subscription = s; + //First chunk request + subscription.request(requestCount.decrementAndGet()); + } + + @Override + public void onNext(T o) { + if (!closed.get()) { + onNext.accept(o); + if(0 == requestCount.decrementAndGet()){ + subscription.cancel(); + } + } + } + + @Override + public void onError(Throwable t) { + throw new RuntimeException(t); + } + + @Override + public void onComplete() { + closed.set(true); + subscription.cancel(); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/CountingSubscriber.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/CountingSubscriber.java new file mode 100644 index 00000000000..21e9a0b63e1 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/CountingSubscriber.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import io.helidon.microprofile.reactive.ExceptionUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +public class CountingSubscriber implements Subscriber { + private Subscription subscription; + public AtomicLong sum = new AtomicLong(0); + public AtomicLong requestCount = new AtomicLong(0); + public CompletableFuture completed = new CompletableFuture<>(); + private AtomicBoolean expectError = new AtomicBoolean(false); + private AtomicLong cancelAfter = new AtomicLong(0); + + @Override + public void onSubscribe(Subscription subscription) { + this.subscription = subscription; + } + + @Override + public void onNext(Long item) { + requestCount.incrementAndGet(); + sum.addAndGet(item); + if (cancelAfter.get() != 0 && requestCount.get() > cancelAfter.get()) { + cancel(); + } + } + + @Override + public void onError(Throwable throwable) { + if (!expectError.get()) { + ExceptionUtils.throwUncheckedException(throwable); + } + } + + @Override + public void onComplete() { + completed.complete(sum); + } + + public void request(long n) { + subscription.request(n); + } + + public void cancel() { + subscription.cancel(); + } + + public void cancelAfter(long max) { + cancelAfter.set(max); + } + + public AtomicLong getSum() { + return sum; + } + + public void expectRequestCount(int n) { + assertEquals(n, requestCount.get(), String.format("Expected %d requests but only %d received.", n, (long)requestCount.get())); + } + + public void expectSum(long n) { + assertEquals(n, sum.get()); + } + + public void expectError() { + expectError.set(true); + } + + public void expectOnComplete() { + try { + request(1); + completed.get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + fail(e); + } + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/CoupledProcessorTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/CoupledProcessorTest.java new file mode 100644 index 00000000000..82f4c85ec1f --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/CoupledProcessorTest.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.LongStream; + +import io.helidon.common.reactive.MultiTappedProcessor; +import io.helidon.microprofile.reactive.hybrid.HybridProcessor; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +public class CoupledProcessorTest extends AbstractProcessorTest { + + @Override + protected Publisher getPublisher(long items) { + return ReactiveStreams.coupled(ReactiveStreams.builder().ignore(), ReactiveStreams.fromIterable( + () -> LongStream.rangeClosed(1, items).boxed().iterator() + )).buildRs(); + } + + @Override + protected Processor getFailedProcessor(RuntimeException t) { + return ReactiveStreams.coupled( + ReactiveStreams.builder().ignore(), + ReactiveStreams.failed(new TestRuntimeException())) + .buildRs(); + } + + @Test + void coupledProcessorAsProcessor() throws InterruptedException, ExecutionException, TimeoutException { + ProcessorBuilder processorBuilder = ReactiveStreams.coupled(ReactiveStreams.builder().ignore(), ReactiveStreams.fromIterable( + () -> LongStream.rangeClosed(1, 5).boxed().iterator() + )); + + List result = ReactiveStreams.of(3L, 2L, 3L) + .via(processorBuilder) + .toList().run().toCompletableFuture().get(1, TimeUnit.SECONDS); + + System.out.println(result); + } + + @Test + void coupledProcessorAsPublisher() throws InterruptedException, ExecutionException, TimeoutException { + Processor processor = ReactiveStreams + .coupled( + ReactiveStreams.builder().ignore(), + ReactiveStreams.fromIterable(() -> LongStream.rangeClosed(1, 3).boxed().iterator()) + ) + .buildRs(); + + List result = ReactiveStreams.fromPublisher(processor) + .toList() + .run() + .toCompletableFuture() + .get(1, TimeUnit.SECONDS); + + System.out.println(result); + } + + @Test + @SuppressWarnings("unchecked") + void spec317() throws InterruptedException, ExecutionException, TimeoutException { + + MockPublisher mp = new MockPublisher(); + Processor sub = new Processor() { + + private Optional subscription = Optional.empty(); + AtomicInteger counter = new AtomicInteger(10); + + @Override + public void onSubscribe(Subscription subscription) { + this.subscription = Optional.of(subscription); + } + + @Override + public void subscribe(Subscriber s) { + //mock request + System.out.println("Requesting " + 1 + " counter: " + counter.get()); + subscription.get().request(1); + } + + @Override + public void onNext(Long o) { + subscription.ifPresent(s -> { + if (counter.getAndDecrement() > 0) { + System.out.println("Requesting " + (Long.MAX_VALUE - 1) + " counter: " + counter.get()); + s.request(Long.MAX_VALUE - 1); + } else { + s.cancel(); + } + }); + } + + @Override + public void onError(Throwable t) { + fail(t); + } + + @Override + public void onComplete() { + + } + }; + HybridProcessor tappedProcessor = HybridProcessor.from(MultiTappedProcessor.create()); + + Processor processor = ReactiveStreams + .coupled( + tappedProcessor, + tappedProcessor + ) + .buildRs(); + + CompletionStage completionStage = ReactiveStreams + .fromPublisher(new IntSequencePublisher()) + .map(Long::valueOf) + .via(processor) + .to(sub) + .run(); + + //signal request 1 to kickoff overflow simulation + sub.subscribe(null); + + //is cancelled afe + assertThrows(CancellationException.class, () -> completionStage.toCompletableFuture().get(3, TimeUnit.SECONDS)); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/EngineTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/EngineTest.java new file mode 100644 index 00000000000..fc4447673a6 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/EngineTest.java @@ -0,0 +1,888 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.StringJoiner; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Flow; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.LongStream; + +import io.helidon.common.reactive.MultiDropWhileProcessor; +import io.helidon.common.reactive.MultiFilterProcessor; +import io.helidon.common.reactive.MultiPeekProcessor; +import io.helidon.common.reactive.MultiSkipProcessor; +import io.helidon.common.reactive.MultiTakeWhileProcessor; +import io.helidon.microprofile.reactive.hybrid.HybridProcessor; +import io.helidon.microprofile.reactive.hybrid.HybridSubscriber; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.microprofile.reactive.streams.operators.CompletionSubscriber; +import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; + +public class EngineTest { + @Test + void fixedItemsWithMap() { + AtomicInteger sum = new AtomicInteger(); + ReactiveStreams + .of("10", "20", "30") + .map(a -> a.replaceAll("0", "")) + .map(Integer::parseInt) + .buildRs() + .subscribe(new ConsumableSubscriber<>(sum::addAndGet)); + assertEquals(1 + 2 + 3, sum.get()); + } + + @Test + void fixedItemsWithFilter() { + AtomicInteger sum = new AtomicInteger(); + ReactiveStreams.of(1, 2, 3, 4, 5) + .filter(x -> (x % 2) == 0) + .buildRs() + .subscribe(new ConsumableSubscriber<>(sum::addAndGet)); + assertTrue((sum.get() % 2) == 0); + } + + @Test + void publisherWithMapAndPeekAndFilter() { + AtomicInteger peekSum = new AtomicInteger(); + AtomicInteger sum = new AtomicInteger(); + IntSequencePublisher intSequencePublisher = new IntSequencePublisher(); + + ReactiveStreams.fromPublisher(intSequencePublisher) + .limit(10) + .filter(x -> x % 2 == 0) + .peek(peekSum::addAndGet) + .map(String::valueOf) + .map(s -> s + "0") + .map(Integer::parseInt) + .buildRs() + .subscribe(new ConsumableSubscriber<>(sum::addAndGet)); + + assertEquals(2 + 4 + 6 + 8 + 10, peekSum.get()); + assertEquals(20 + 40 + 60 + 80 + 100, sum.get()); + } + + @Test + void fromTo() throws ExecutionException, InterruptedException, TimeoutException { + AtomicInteger sum = new AtomicInteger(); + IntSequencePublisher publisher = new IntSequencePublisher(); + ReactiveStreams + .fromPublisher(publisher) + .map(String::valueOf) + .map(i -> i + "-") + .map(s -> s.replaceAll("-", "")) + .map(Integer::parseInt) + .filter(i -> i % 2 == 0) + .limit(5L) + .to(ReactiveStreams.fromSubscriber(new ConsumableSubscriber<>(sum::addAndGet, 10))) + .run(); + assertEquals(2 + 4 + 6 + 8 + 10, sum.get()); + } + + @Test + void limit() { + AtomicInteger sum = new AtomicInteger(); + IntSequencePublisher publisher = new IntSequencePublisher(); + ConsumableSubscriber subscriber = new ConsumableSubscriber<>(sum::addAndGet); + ReactiveStreams + .fromPublisher(publisher) + .peek(System.out::println) + .limit(5) + .peek(System.out::println) + .buildRs() + .subscribe(subscriber); + assertEquals(1 + 2 + 3 + 4 + 5, sum.get()); + } + + @Test + void subscriberCreation() throws ExecutionException, InterruptedException, TimeoutException { + AtomicInteger peekedSum = new AtomicInteger(); + AtomicInteger sum = new AtomicInteger(); + IntSequencePublisher publisher = new IntSequencePublisher(); + CompletionSubscriber subscriber = ReactiveStreams.builder() + .limit(5) + .peek(peekedSum::addAndGet) + .forEach(sum::addAndGet) + .build(); + publisher.subscribe(subscriber); + subscriber.getCompletion().toCompletableFuture().get(1, TimeUnit.SECONDS); + assertEquals(1 + 2 + 3 + 4 + 5, sum.get()); + assertEquals(1 + 2 + 3 + 4 + 5, peekedSum.get()); + } + + @Test + void processorBuilder() { + StringBuilder stringBuffer = new StringBuilder(); + + Publisher publisherBuilder = + ReactiveStreams + .of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + .buildRs(); + + Processor processor = ReactiveStreams.builder() + .map(i -> i + 1) + .flatMap(i -> ReactiveStreams.of(i, i)) + .map(i -> Integer.toString(i)) + .buildRs(); + + ConsumableSubscriber subscriber = new ConsumableSubscriber<>(stringBuffer::append); + + publisherBuilder.subscribe(processor); + processor.subscribe(HybridSubscriber.from(subscriber)); + assertEquals("1122334455667788991010", stringBuffer.toString()); + } + + @Test + void ofForEach() throws ExecutionException, InterruptedException { + AtomicInteger sum = new AtomicInteger(); + ReactiveStreams + .of(3, 4) + .forEach(sum::addAndGet) + .run().toCompletableFuture().get(); + assertEquals(3 + 4, sum.get()); + } + + @Test + void publisherToForEach() { + AtomicInteger sum = new AtomicInteger(); + Publisher publisher = ReactiveStreams.of(3, 4).buildRs(); + ReactiveStreams + .fromPublisher(publisher) + .forEach(sum::addAndGet) + .run(); + assertEquals(3 + 4, sum.get()); + } + + @Test + void concat() throws InterruptedException, ExecutionException, TimeoutException { + final List resultList = new ArrayList<>(); + ReactiveStreams + .concat(ReactiveStreams.of(1, 2, 3), + ReactiveStreams.of(4, 5, 6)) + .forEach(resultList::add).run() + .toCompletableFuture() + .get(1, TimeUnit.SECONDS); + assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6), resultList); + } + + @Test + void concatCancelOnFail() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture cancelled = new CompletableFuture<>(); + + CompletionStage completion = + ReactiveStreams + .concat( + ReactiveStreams.failed(new TestRuntimeException()), + ReactiveStreams.generate(() -> 1) + .onTerminate(() -> cancelled.complete(null)) + ) + .ignore() + .run(); + + cancelled.get(1, TimeUnit.SECONDS); + assertThrows(ExecutionException.class, () -> completion.toCompletableFuture().get(1, TimeUnit.SECONDS)); + } + + @Test + void complStage() throws InterruptedException, ExecutionException, TimeoutException { + final List resultList = new ArrayList<>(); + CompletionStage run = ReactiveStreams.of(1, 2, 3) + .forEach(resultList::add).run(); + run.toCompletableFuture().get(2, TimeUnit.SECONDS); + assertEquals(Arrays.asList(1, 2, 3), resultList); + } + + @Test + void collect() throws ExecutionException, InterruptedException { + assertEquals(ReactiveStreams.of(1, 2, 3) + .collect(() -> new AtomicInteger(0), AtomicInteger::addAndGet + ).run().toCompletableFuture().get().get(), 6); + } + + @Test + void cancel() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture cancelled = new CompletableFuture<>(); + CompletionStage result = ReactiveStreams.fromPublisher(s -> s.onSubscribe(new Subscription() { + @Override + public void request(long n) { + } + + @Override + public void cancel() { + cancelled.complete(null); + } + })) + .cancel() + .run(); + + cancelled.get(1, TimeUnit.SECONDS); + result.toCompletableFuture().get(1, TimeUnit.SECONDS); + } + + @Test + void cancelWithFailures() { + ReactiveStreams + .failed(new TestThrowable()) + .cancel() + .run(); + } + + @Test + void findFirst() throws InterruptedException, ExecutionException, TimeoutException { + Optional result = ReactiveStreams + .of(1, 2, 3) + .findFirst() + .run() + .toCompletableFuture() + .get(1, TimeUnit.SECONDS); + + assertEquals(Integer.valueOf(1), result.get()); + } + + @Test + @Disabled + void failed() { + assertThrows(TestThrowable.class, () -> ReactiveStreams + .failed(new TestThrowable()) + .findFirst() + .run().toCompletableFuture().get(1, TimeUnit.SECONDS)); + } + + @Test + void finisherTest() throws InterruptedException, ExecutionException, TimeoutException { + assertEquals("1, 2, 3", ReactiveStreams + .of("1", "2", "3") + .collect(Collectors.joining(", ")) + .run().toCompletableFuture().get(1, TimeUnit.SECONDS)); + } + + @Test + void collectorExceptionPropagation() { + //supplier + assertThrows(ExecutionException.class, () -> ReactiveStreams.of("1", "2", "3") + .collect(Collector.of( + () -> { + throw new TestRuntimeException(); + }, + StringJoiner::add, + StringJoiner::merge, + StringJoiner::toString + )) + .run().toCompletableFuture().get(1, TimeUnit.SECONDS), TestRuntimeException.TEST_MSG); + //accumulator + assertThrows(ExecutionException.class, () -> ReactiveStreams.of("1", "2", "3") + .collect(Collector.of( + () -> new StringJoiner(","), + (s, t) -> { + throw new TestRuntimeException(); + }, + StringJoiner::merge, + StringJoiner::toString + )) + .run().toCompletableFuture().get(1, TimeUnit.SECONDS), TestRuntimeException.TEST_MSG); + + //finisher + assertThrows(ExecutionException.class, () -> ReactiveStreams.of("1", "2", "3") + .collect(Collector.of( + () -> new StringJoiner(","), + StringJoiner::add, + StringJoiner::merge, + f -> { + throw new TestRuntimeException(); + } + )) + .run().toCompletableFuture().get(1, TimeUnit.SECONDS), TestRuntimeException.TEST_MSG); + } + + + @Test + void onTerminate() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture terminator = new CompletableFuture<>(); + ReactiveStreams + .of("1", "2", "3") + .onTerminate(() -> terminator.complete(null)) + .collect(Collectors.joining(", ")) + .run().toCompletableFuture().get(1, TimeUnit.SECONDS); + terminator.get(1, TimeUnit.SECONDS); + } + + @Test + void publisherWithTerminate() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture terminator = new CompletableFuture<>(); + Publisher publisher = ReactiveStreams.of(1, 2, 3) + .onTerminate(() -> { + terminator.complete(null); + }) + .buildRs(); + + Optional result = ReactiveStreams.fromPublisher(publisher) + .findFirst().run().toCompletableFuture().get(1, TimeUnit.SECONDS); + assertEquals(1, result.get()); + terminator.get(1, TimeUnit.SECONDS); + } + + @Test + void concatCancel() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture cancelled = new CompletableFuture<>(); + CompletionStage completion = ReactiveStreams + .concat( + ReactiveStreams.failed(new TestRuntimeException()), + ReactiveStreams.of(1, 2, 3) + .onTerminate(() -> { + cancelled.complete(null); + }) + ) + .ignore() + .run(); + cancelled.get(1, TimeUnit.SECONDS); + assertThrows(ExecutionException.class, () -> completion.toCompletableFuture().get(1, TimeUnit.SECONDS), TestRuntimeException.TEST_MSG); + } + + @Test + void filter() throws InterruptedException, ExecutionException, TimeoutException { + CompletionStage> cs = ReactiveStreams.of(1, 2, 3, 4, 5, 6) + .filter((i) -> { + return (i & 1) == 1; + }).toList() + .run(); + assertEquals(Arrays.asList(1, 3, 5), cs.toCompletableFuture().get(1, TimeUnit.SECONDS)); + } + + @Test + @Disabled + //TODO: Is this valid scenario? + void publisherToSubscriber() throws InterruptedException, ExecutionException, TimeoutException { + CompletionSubscriber> subscriber = ReactiveStreams.builder() + .limit(5L) + .findFirst() + .build(); + ReactiveStreams.of(1, 2, 3) + .to(subscriber) + .run() + .toCompletableFuture() + .get(1, TimeUnit.SECONDS); + assertEquals(1, subscriber.getCompletion().toCompletableFuture().get(1, TimeUnit.SECONDS).get()); + } + + @Test + void filterExceptionCancelUpstream() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture cancelled = new CompletableFuture<>(); + CompletionStage> result = ReactiveStreams.of(1, 2, 3).onTerminate(() -> { + cancelled.complete(null); + }).filter((foo) -> { + throw new TestRuntimeException(); + }).toList().run(); + cancelled.get(1, TimeUnit.SECONDS); + assertThrows(ExecutionException.class, + () -> result.toCompletableFuture().get(1, TimeUnit.SECONDS), + TestRuntimeException.TEST_MSG); + } + + @Test + void streamOfStreams() throws InterruptedException, ExecutionException, TimeoutException { + List result = ReactiveStreams.of(ReactiveStreams.of(1, 2)) + .flatMap(i -> i) + .toList() + .run().toCompletableFuture() + .get(1, TimeUnit.SECONDS); + + assertEquals(Arrays.asList(1, 2), result); + } + + @Test + void reentrantFlatMapPublisher() throws InterruptedException, ExecutionException, TimeoutException { + ProcessorBuilder, Integer> flatMap = + ReactiveStreams.>builder() + .flatMap(Function.identity()); + assertEquals(Arrays.asList(1, 2), ReactiveStreams.of( + ReactiveStreams.of(1, 2)) + .via(flatMap) + .toList() + .run().toCompletableFuture() + .get(1, TimeUnit.SECONDS)); + assertEquals(Arrays.asList(3, 4), + ReactiveStreams.of(ReactiveStreams.of(3, 4)) + .via(flatMap) + .toList() + .run().toCompletableFuture() + .get(1, TimeUnit.SECONDS)); + } + + @Test + void concatCancelOtherStage() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture cancelled = new CompletableFuture<>(); + + CompletionStage completion = ReactiveStreams.concat( + ReactiveStreams.failed(new TestRuntimeException()), + ReactiveStreams.of(1, 2.3).onTerminate(() -> cancelled.complete(null))) + .ignore() + .run(); + + cancelled.get(1, TimeUnit.SECONDS); + assertThrows(ExecutionException.class, () -> completion.toCompletableFuture().get(1, TimeUnit.SECONDS), TestRuntimeException.TEST_MSG); + } + + @Test + void flatMapExceptionPropagation() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture cancelled = new CompletableFuture<>(); + CompletionStage> result = ReactiveStreams.of(1, 2, 3) + .onTerminate(() -> cancelled.complete(null)) + .flatMap(foo -> { + throw new TestRuntimeException(); + }) + .toList() + .run(); + cancelled.get(1, TimeUnit.SECONDS); + assertThrows(ExecutionException.class, () -> result.toCompletableFuture().get(1, TimeUnit.SECONDS), TestRuntimeException.TEST_MSG); + } + + @Test + void flatMapSubStreamException() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture cancelled = new CompletableFuture<>(); + CompletionStage> result = ReactiveStreams.of(1, 2, 3) + .onTerminate(() -> cancelled.complete(null)) + .flatMap(f -> ReactiveStreams.failed(new TestRuntimeException())) + .toList() + .run(); + cancelled.get(1, TimeUnit.SECONDS); + assertThrows(ExecutionException.class, + () -> result.toCompletableFuture().get(1, TimeUnit.SECONDS), + TestRuntimeException.TEST_MSG); + } + + @Test + void dropWhile() throws InterruptedException, ExecutionException, TimeoutException { + ProcessorBuilder dropWhile = ReactiveStreams.builder() + .dropWhile(i -> i < 3); + + List firstResult = ReactiveStreams.of(1, 2, 3, 4) + .via(dropWhile) + .toList() + .run().toCompletableFuture().get(1, TimeUnit.SECONDS); + + List secondResult = ReactiveStreams.of(0, 1, 6, 7) + .via(dropWhile) + .toList() + .run().toCompletableFuture().get(1, TimeUnit.SECONDS); + + assertEquals(Arrays.asList(3, 4), firstResult); + assertEquals(Arrays.asList(6, 7), secondResult); + } + + @Test + void disctinct() throws InterruptedException, ExecutionException, TimeoutException { + ProcessorBuilder distinct = ReactiveStreams.builder().distinct(); + List firstResult = ReactiveStreams.of(1, 2, 2, 3) + .via(distinct) + .toList() + .run().toCompletableFuture() + .get(1, TimeUnit.SECONDS); + List secondResult = ReactiveStreams.of(3, 3, 4, 5) + .via(distinct) + .toList() + .run().toCompletableFuture() + .get(1, TimeUnit.SECONDS); + assertEquals(Arrays.asList(1, 2, 3), firstResult); + assertEquals(Arrays.asList(3, 4, 5), secondResult); + } + + @Test + void nullInMap() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture cancelled = new CompletableFuture<>(); + CompletionStage> result = ReactiveStreams.of(1, 2, 3) + .onTerminate(() -> cancelled.complete(null)) + .map(t -> null) + .toList().run(); + cancelled.get(1, TimeUnit.SECONDS); + assertThrows(ExecutionException.class, () -> result.toCompletableFuture().get(1, TimeUnit.SECONDS)); + } + + @Test + void fromCompletionStage() throws InterruptedException, ExecutionException, TimeoutException { + assertEquals(Collections.singletonList("TEST"), + ReactiveStreams.fromCompletionStage(CompletableFuture.completedFuture("TEST")) + .toList() + .run().toCompletableFuture().get(1, TimeUnit.SECONDS)); + } + + @Test + void fromCompletionStageWithNullNegative() throws InterruptedException, ExecutionException, TimeoutException { + assertThrows(ExecutionException.class, () -> ReactiveStreams.fromCompletionStage(CompletableFuture.completedFuture(null)) + .toList() + .run().toCompletableFuture().get(1, TimeUnit.SECONDS)); + } + + @Test + void fromCompletionStageWithNullPositive() throws InterruptedException, ExecutionException, TimeoutException { + assertEquals(Optional.empty(), + ReactiveStreams.fromCompletionStageNullable(CompletableFuture.completedFuture(null)) + .findFirst() + .run().toCompletableFuture().get(1, TimeUnit.SECONDS)); + } + + @Test + void coupled() throws InterruptedException, ExecutionException, TimeoutException { + + CompletionSubscriber> subscriber = ReactiveStreams.builder().toList().build(); + Publisher publisher = ReactiveStreams.of("4", "5", "6").buildRs(); + + Processor processor = ReactiveStreams.coupled(subscriber, publisher).buildRs(); +// Processor processor = ReactiveStreams.builder().map(String::valueOf).buildRs(); + + List result = ReactiveStreams.of(1, 2, 3) + .via(processor) + .peek(s -> { + System.out.println(">>>>" + s); + }) + .toList() + .run() + .toCompletableFuture().get(1, TimeUnit.SECONDS); + + subscriber.getCompletion().toCompletableFuture().get(1, TimeUnit.SECONDS); + + assertEquals(Arrays.asList("4", "5", "6"), result); + } + + @Test + void generate() throws InterruptedException, ExecutionException, TimeoutException { + assertEquals(Arrays.asList(4, 4, 4), + ReactiveStreams.generate(() -> 4) + .limit(3L) + .toList() + .run() + .toCompletableFuture() + .get(1, TimeUnit.SECONDS)); + } + + + @Test + void flatMapCancelPropagation() throws InterruptedException, ExecutionException, TimeoutException { + try { + ReactiveStreams.of(1, 2, 3) + .toList() + .run().toCompletableFuture().get(1, TimeUnit.SECONDS); + } catch (ExecutionException ignored) { + //There was a bug with non-reentrant BaseProcessor used in flatMap + } + + CompletableFuture outerCancelled = new CompletableFuture<>(); + CompletableFuture innerCancelled = new CompletableFuture<>(); + ReactiveStreams.generate(() -> 4) + .onTerminate(() -> outerCancelled.complete(null)) + .flatMap(i -> ReactiveStreams.generate(() -> 5) + .onTerminate(() -> innerCancelled.complete(null))) + .limit(5) + .toList() + .run().toCompletableFuture().get(200, TimeUnit.MILLISECONDS); + outerCancelled.get(200, TimeUnit.MILLISECONDS); + innerCancelled.get(200, TimeUnit.MILLISECONDS); + } + + @Test + void flatMap() throws InterruptedException, ExecutionException, TimeoutException { + List result = ReactiveStreams.generate(() -> 4) + .flatMap(i -> ReactiveStreams.of(9, 8, 7)) + .limit(4) + .toList() + .run().toCompletableFuture().get(200, TimeUnit.MILLISECONDS); + + assertEquals(Arrays.asList(9, 8, 7, 9), result); + } + + @Test + void flatMapIterable() throws InterruptedException, ExecutionException, TimeoutException { + List result = ReactiveStreams.of(1, 2, 3) + .flatMapIterable(n -> Arrays.asList(n, n, n)) + .toList() + .run() + .toCompletableFuture() + .get(1, TimeUnit.SECONDS); + assertEquals(Arrays.asList(1, 1, 1, 2, 2, 2, 3, 3, 3), result); + } + + @Test + void flatMapIterableFailOnNull() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture cancelled = new CompletableFuture<>(); + CompletionStage> result = ReactiveStreams.generate(() -> 4).onTerminate(() -> cancelled.complete(null)) + .flatMapIterable(t -> Collections.singletonList(null)) + .toList().run(); + cancelled.get(1, TimeUnit.SECONDS); + assertThrows(ExecutionException.class, () -> result.toCompletableFuture().get(1, TimeUnit.SECONDS)); + } + + @Test + void flatMapCompletionStage() throws InterruptedException, ExecutionException, TimeoutException { + ProcessorBuilder mapper = ReactiveStreams.builder() + .flatMapCompletionStage(i -> CompletableFuture.completedFuture(i + 1)); + List result1 = ReactiveStreams.of(1, 2, 3).via(mapper).toList().run().toCompletableFuture().get(1, TimeUnit.SECONDS); + List result2 = ReactiveStreams.of(4, 5, 6).via(mapper).toList().run().toCompletableFuture().get(1, TimeUnit.SECONDS); + assertEquals(Arrays.asList(2, 3, 4), result1); + assertEquals(Arrays.asList(5, 6, 7), result2); + } + + @Test + void coupledCompleteOnCancel() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture publisherCancelled = new CompletableFuture<>(); + CompletableFuture downstreamCompleted = new CompletableFuture<>(); + + ReactiveStreams + .fromCompletionStage(new CompletableFuture<>()) + .via( + ReactiveStreams + .coupled(ReactiveStreams.builder().cancel(), + ReactiveStreams.fromCompletionStage(new CompletableFuture<>()) + .onTerminate(() -> { + publisherCancelled.complete(null); + })) + ) + .onComplete(() -> downstreamCompleted.complete(null)) + .ignore() + .run(); + + publisherCancelled.get(1, TimeUnit.SECONDS); + downstreamCompleted.get(1, TimeUnit.SECONDS); + } + + @Test + void coupledCompleteUpStreamOnCancel() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture subscriberCompleted = new CompletableFuture<>(); + CompletableFuture upstreamCancelled = new CompletableFuture<>(); + + ReactiveStreams + .fromCompletionStage(new CompletableFuture<>()) + .onTerminate(() -> upstreamCancelled.complete(null)) + .via(ReactiveStreams + .coupled(ReactiveStreams.builder().onComplete(() -> { + subscriberCompleted.complete(null); + }) + .ignore(), + ReactiveStreams + .fromCompletionStage(new CompletableFuture<>()))) + .cancel() + .run(); + + subscriberCompleted.get(1, TimeUnit.SECONDS); + upstreamCancelled.get(1, TimeUnit.SECONDS); + } + + + @Test + void coupledStageReentrant() { + ProcessorBuilder coupled = ReactiveStreams.coupled(ReactiveStreams.builder().ignore(), ReactiveStreams.of(1, 2, 3)); + Supplier> coupledTest = () -> { + try { + return ReactiveStreams + .fromCompletionStage(new CompletableFuture<>()) + .via(coupled) + .toList() + .run() + .toCompletableFuture() + .get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + }; + + IntStream.range(0, 20).forEach(i -> { + assertEquals(Arrays.asList(1, 2, 3), coupledTest.get()); + }); + } + + @Test + void coupledCancelOnPublisherFail() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture subscriberFailed = new CompletableFuture<>(); + CompletableFuture upstreamCancelled = new CompletableFuture<>(); + + ReactiveStreams + .fromCompletionStage(new CompletableFuture<>()) + .onTerminate(() -> upstreamCancelled.complete(null)) + .via( + ReactiveStreams + .coupled(ReactiveStreams + .builder() + .onError(value -> { + subscriberFailed.complete(value); + }) + .ignore(), + ReactiveStreams + .failed(new TestRuntimeException()))) + .ignore() + .run(); + + assertTrue(subscriberFailed.get(1, TimeUnit.SECONDS) instanceof TestRuntimeException); + upstreamCancelled.get(1, TimeUnit.SECONDS); + } + + @Test + @Disabled + void coupledCancelOnUpstreamFail() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture publisherCancelled = new CompletableFuture<>(); + CompletableFuture downstreamFailed = new CompletableFuture<>(); + + ReactiveStreams.failed(new TestRuntimeException()) + .via( + ReactiveStreams.coupled(ReactiveStreams.builder().ignore(), + ReactiveStreams + .fromCompletionStage(new CompletableFuture<>()).onTerminate(() -> publisherCancelled.complete(null))) + ).onError(downstreamFailed::complete) + .ignore() + .run(); + + publisherCancelled.get(1, TimeUnit.SECONDS); + assertTrue(downstreamFailed.get(1, TimeUnit.SECONDS) instanceof TestRuntimeException); + } + + @Test + void limitToZero() throws InterruptedException, ExecutionException, TimeoutException { + assertEquals(Collections.emptyList(), ReactiveStreams + .generate(() -> 4) + .limit(0L) + .toList() + .run() + .toCompletableFuture() + .get(1, TimeUnit.SECONDS)); + } + + @Test + void limitWithZeroCompletesNoMatterRequest() throws InterruptedException, ExecutionException, TimeoutException { + List result = ReactiveStreams.fromPublisher(subscriber -> + subscriber.onSubscribe(new Subscription() { + @Override + public void request(long n) { + } + + @Override + public void cancel() { + } + })) + .limit(0) + .toList() + .run() + .toCompletableFuture() + .get(1, TimeUnit.SECONDS); + assertEquals(Collections.emptyList(), result); + } + + @Test + void mapOnError() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture cancelled = new CompletableFuture<>(); + CompletionStage> result = ReactiveStreams.generate(() -> "test") + .onTerminate(() -> cancelled.complete(null)) + .map(foo -> { + throw new TestRuntimeException(); + }) + .toList() + .run(); + cancelled.get(1, TimeUnit.SECONDS); + assertThrows(ExecutionException.class, () -> result.toCompletableFuture().get(1, TimeUnit.SECONDS), TestRuntimeException.TEST_MSG); + } + + @Test + void finiteStream() throws InterruptedException, ExecutionException, TimeoutException { + finiteOnCompleteTest(MultiPeekProcessor.create(integer -> Function.identity())); + finiteOnCompleteTest(MultiFilterProcessor.create(integer -> true)); + finiteOnCompleteTest(MultiTakeWhileProcessor.create(integer -> true)); + finiteOnCompleteTest(MultiDropWhileProcessor.create(integer -> false)); + finiteOnCompleteTest(MultiSkipProcessor.create(0L)); + } + + @Test + void limitProcessorTest() { + testProcessor(ReactiveStreams.builder().limit(Long.MAX_VALUE).buildRs(), s -> { + s.request(10); + s.expectRequestCount(10); + s.request(2); + s.expectRequestCount(12); + s.expectSum(12 * 4); + }); + } + + @Test + void filterProcessorTest() { + testProcessor(ReactiveStreams.builder().filter(o -> true).buildRs(), s -> { + s.request(15); + s.expectRequestCount(15); + s.request(2); + s.expectRequestCount(17); + s.expectSum(17 * 4); + }); + } + + private void finiteOnCompleteTest(Flow.Processor processor) + throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture completed = new CompletableFuture<>(); + ReactiveStreams.of(1, 2, 3) + .via(HybridProcessor.from(processor)) + .onComplete(() -> completed.complete(null)) + .toList().run().toCompletableFuture().get(1, TimeUnit.SECONDS); + + completed.get(1, TimeUnit.SECONDS); + } + + private void testProcessor(Processor processor, + Consumer testBody) { + CountingSubscriber subscriber = new CountingSubscriber(); + ReactiveStreams.generate(() -> 4L) + .via(processor) + .to(subscriber) + .run(); + testBody.accept(subscriber); + } + + @Test + void name() { + Publisher pub = ReactiveStreams.failed(new Exception("BOOM")) + .onErrorResumeWith( + t -> ReactiveStreams.fromIterable(() -> LongStream.rangeClosed(1, 5).boxed().iterator()) + ) + .buildRs(); + + CountingSubscriber sub = new CountingSubscriber(); + + pub.subscribe(sub); + + sub.request(1); + sub.expectRequestCount(1); + } +} \ No newline at end of file diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/FlatMapCompletionStageProcessorTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/FlatMapCompletionStageProcessorTest.java new file mode 100644 index 00000000000..638ed975487 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/FlatMapCompletionStageProcessorTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Processor; + +public class FlatMapCompletionStageProcessorTest extends AbstractProcessorTest { + @Override + protected Processor getProcessor() { + return ReactiveStreams.builder().flatMapCompletionStage(CompletableFuture::completedFuture).buildRs(); + } + + @Override + protected Processor getFailedProcessor(RuntimeException t) { + return ReactiveStreams.builder().flatMapCompletionStage(i -> { + throw t; + }).buildRs(); + } + + @Test + void futuresMapping() throws InterruptedException, TimeoutException, ExecutionException { + CompletableFuture one = new CompletableFuture<>(); + CompletableFuture two = new CompletableFuture<>(); + CompletableFuture three = new CompletableFuture<>(); + + CompletionStage> result = ReactiveStreams.of(one, two, three) + .flatMapCompletionStage(i -> i) + .toList() + .run(); + + Thread.sleep(100); + + one.complete(1); + two.complete(2); + three.complete(3); + + assertEquals(Arrays.asList(1, 2, 3), result.toCompletableFuture().get(1, TimeUnit.SECONDS)); + } + + @Test + void futuresOrder() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture one = new CompletableFuture<>(); + CompletableFuture two = new CompletableFuture<>(); + CompletableFuture three = new CompletableFuture<>(); + + CompletionStage> result = ReactiveStreams.of(one, two, three) + .flatMapCompletionStage(Function.identity()) + .toList() + .run(); + + three.complete(3); + Thread.sleep(100); + two.complete(2); + Thread.sleep(100); + one.complete(1); + + assertEquals(Arrays.asList(1, 2, 3), result.toCompletableFuture().get(1, TimeUnit.SECONDS)); + } + + @Test + void oneAtTime() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture one = new CompletableFuture<>(); + CompletableFuture two = new CompletableFuture<>(); + CompletableFuture three = new CompletableFuture<>(); + + AtomicInteger concurrentMaps = new AtomicInteger(0); + AtomicLong c = new AtomicLong(0); + CompletionStage> result = ReactiveStreams.of(one, two, three) + .flatMapCompletionStage(i -> { + assertEquals(1, concurrentMaps.incrementAndGet(), ">>" + c.incrementAndGet()); + return i; + }) + .toList() + .run(); + + Thread.sleep(100); + concurrentMaps.decrementAndGet(); + one.complete(1); + Thread.sleep(100); + concurrentMaps.decrementAndGet(); + two.complete(2); + Thread.sleep(100); + concurrentMaps.decrementAndGet(); + three.complete(3); + + assertEquals(Arrays.asList(1, 2, 3), result.toCompletableFuture().get(1, TimeUnit.SECONDS)); + } + + @Test + void failOnNull() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture cancelled = new CompletableFuture<>(); + CompletionStage> result = ReactiveStreams.generate(() -> 4).onTerminate(() -> cancelled.complete(null)) + .flatMapCompletionStage(t -> CompletableFuture.completedFuture(null)) + .toList().run(); + cancelled.get(1, TimeUnit.SECONDS); + assertThrows(ExecutionException.class, () -> result.toCompletableFuture().get(1, TimeUnit.SECONDS)); + } + + @Test + void failedCs() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture cancelled = new CompletableFuture<>(); + CompletionStage> result = ReactiveStreams.generate(() -> 4) + .onTerminate(() -> cancelled.complete(null)) + .flatMapCompletionStage(i -> { + CompletableFuture failed = new CompletableFuture<>(); + failed.completeExceptionally(new TestRuntimeException()); + return failed; + }) + .toList() + .run(); + cancelled.get(1, TimeUnit.SECONDS); + assertThrows(ExecutionException.class, () -> result.toCompletableFuture().get(1, TimeUnit.SECONDS), TestRuntimeException.TEST_MSG); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/FlatMapIterableProcessorTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/FlatMapIterableProcessorTest.java new file mode 100644 index 00000000000..8a296fe3689 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/FlatMapIterableProcessorTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import java.util.List; + +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Processor; + +public class FlatMapIterableProcessorTest extends AbstractProcessorTest { + @Override + protected Processor getProcessor() { + return ReactiveStreams.builder().flatMapIterable(aLong -> List.of(aLong)).buildRs(); + } + + @Override + protected Processor getFailedProcessor(RuntimeException t) { + return ReactiveStreams.builder().flatMapIterable(i -> { + throw t; + }).buildRs(); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/FlatMapPublisherProcessorTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/FlatMapPublisherProcessorTest.java new file mode 100644 index 00000000000..5a0fb6c48b0 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/FlatMapPublisherProcessorTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Processor; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +public class FlatMapPublisherProcessorTest extends AbstractProcessorTest { + @Override + protected Processor getProcessor() { + return ReactiveStreams.builder().flatMap(ReactiveStreams::of).buildRs(); + } + + @Override + protected Processor getFailedProcessor(RuntimeException t) { + return ReactiveStreams.builder().flatMap(i -> { + throw t; + }).buildRs(); + } + + @Test + void onePublisherAtTime() throws InterruptedException, ExecutionException, TimeoutException { + AtomicInteger counter = new AtomicInteger(); + List pubs = Arrays.asList(new MockPublisher(), new MockPublisher()); + List> builders = pubs.stream() + .peek(mockPublisher -> mockPublisher.observeSubscribe(s -> { + assertEquals(1, counter.incrementAndGet(), + "Another publisher already subscribed to!!"); + })) + .map(ReactiveStreams::fromPublisher) + .collect(Collectors.toList()); + + CompletionStage> result = + ReactiveStreams.of(0, 1) + .flatMap(builders::get) + .toList() + .run(); + + counter.decrementAndGet(); + pubs.get(0).sendOnComplete(); + counter.decrementAndGet(); + pubs.get(1).sendOnComplete(); + + result.toCompletableFuture().get(1, TimeUnit.SECONDS); + } + + @Test + void innerProcessorSecondSubscriptionTest() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture cancelled = new CompletableFuture<>(); + Subscription secondSubscription = new Subscription() { + @Override + public void request(long n) { + } + + @Override + public void cancel() { + cancelled.complete(null); + } + }; + + MockPublisher publisher = new MockPublisher(); + final AtomicReference> subs = new AtomicReference<>(); + publisher.observeSubscribe(subscriber -> subs.set((Subscriber) subscriber)); + + CompletionStage> result = + ReactiveStreams.of(0) + .flatMap(integer -> ReactiveStreams.fromPublisher(publisher)) + .toList() + .run(); + + subs.get().onSubscribe(secondSubscription); + publisher.sendOnComplete(); + + cancelled.get(1, TimeUnit.SECONDS); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/IntSequencePublisher.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/IntSequencePublisher.java new file mode 100644 index 00000000000..7d9486575df --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/IntSequencePublisher.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +public class IntSequencePublisher implements Publisher, Subscription { + + private AtomicBoolean closed = new AtomicBoolean(false); + private AtomicInteger sequence = new AtomicInteger(0); + private Subscriber subscriber; + + public IntSequencePublisher() { + } + + @Override + public void subscribe(Subscriber s) { + subscriber = s; + subscriber.onSubscribe(this); + } + + @Override + public void request(long n) { + for (long i = 0; i < n + && !closed.get(); i++) { + subscriber.onNext(sequence.incrementAndGet()); + } + } + + @Override + public void cancel() { + closed.set(true); + subscriber.onComplete(); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MapProcessorTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MapProcessorTest.java new file mode 100644 index 00000000000..479dc8d184a --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MapProcessorTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import java.util.function.Function; + +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Processor; + +public class MapProcessorTest extends AbstractProcessorTest { + @Override + protected Processor getProcessor() { + return ReactiveStreams.builder().map(Function.identity()).buildRs(); + } + + @Override + protected Processor getFailedProcessor(RuntimeException t) { + return ReactiveStreams.builder().map(i -> { + throw t; + }).buildRs(); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MockPublisher.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MockPublisher.java new file mode 100644 index 00000000000..71121090f18 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MockPublisher.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import java.util.Optional; +import java.util.function.Consumer; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +public class MockPublisher implements Publisher { + private Subscriber subscriber; + private Optional>> subscriberObserver = Optional.empty(); + + @Override + public void subscribe(Subscriber subscriber) { + this.subscriber = subscriber; + subscriberObserver.ifPresent(o -> o.accept(subscriber)); + subscriber.onSubscribe(new Subscription() { + @Override + public void request(long n) { + + } + + @Override + public void cancel() { + + } + }); + } + + public void observeSubscribe(Consumer> subscriberObserver) { + this.subscriberObserver = Optional.of(subscriberObserver); + } + + public void sendNext(long value) { + subscriber.onNext(value); + } + + public void sendOnComplete() { + subscriber.onComplete(); + } + + public void sendOnError(Throwable t) { + subscriber.onError(t); + } + + +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiDropWhileProcessorTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiDropWhileProcessorTest.java new file mode 100644 index 00000000000..ef700455401 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiDropWhileProcessorTest.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Processor; + +public class MultiDropWhileProcessorTest extends AbstractProcessorTest { + @Override + protected Processor getProcessor() { + return ReactiveStreams.builder().dropWhile(integer -> false).buildRs(); + } + + @Override + protected Processor getFailedProcessor(RuntimeException t) { + return ReactiveStreams.builder().dropWhile(i -> { + throw t; + }).buildRs(); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiFilterProcessorTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiFilterProcessorTest.java new file mode 100644 index 00000000000..72b217ae906 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiFilterProcessorTest.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Processor; + +public class MultiFilterProcessorTest extends AbstractProcessorTest { + @Override + protected Processor getProcessor() { + return ReactiveStreams.builder().filter(i -> true).buildRs(); + } + + @Override + protected Processor getFailedProcessor(RuntimeException t) { + return ReactiveStreams.builder().filter(i -> { + throw t; + }).buildRs(); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiLimitProcessorTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiLimitProcessorTest.java new file mode 100644 index 00000000000..5cc45baedc2 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiLimitProcessorTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicLong; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Processor; + +public class MultiLimitProcessorTest extends AbstractProcessorTest { + @Override + protected Processor getProcessor() { + return ReactiveStreams.builder().limit(Long.MAX_VALUE).buildRs(); + } + + @Override + protected Processor getFailedProcessor(RuntimeException t) { + return null; + } + + @Test + void ignoreErrorsAfterDone() { + MockPublisher p = new MockPublisher(); + testProcessor(ReactiveStreams.fromPublisher(p).limit(2).buildRs(), s -> { + s.request(4); + p.sendNext(2); + p.sendNext(4); + s.expectSum(6); + p.sendNext(8); + s.expectSum(6); + p.sendOnError(new TestThrowable()); + }); + } + + @Test + void ignoreErrorsAfterDone2() throws InterruptedException, ExecutionException, TimeoutException { + AtomicLong seq = new AtomicLong(0); + List result = ReactiveStreams.generate(seq::incrementAndGet).flatMap((i) -> { + return i == 4 ? ReactiveStreams.failed(new RuntimeException("failed")) : ReactiveStreams.of(i); + }) + .limit(3L) + .toList() + .run() + .toCompletableFuture() + .get(1, TimeUnit.SECONDS); + assertEquals(Arrays.asList(1L, 2L, 3L), result); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiOnErrorResumeProcessorTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiOnErrorResumeProcessorTest.java new file mode 100644 index 00000000000..f14fd760969 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiOnErrorResumeProcessorTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.LongStream; + +import io.helidon.microprofile.reactive.hybrid.HybridSubscriber; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; + +public class MultiOnErrorResumeProcessorTest extends AbstractProcessorTest { + + @Test + void onErrorResume() throws InterruptedException, ExecutionException, TimeoutException { + assertEquals(Collections.singletonList(4), + ReactiveStreams + .generate(() -> 1) + .limit(3L) + .peek(i -> { + throw new TestRuntimeException(); + }) + .onErrorResume(throwable -> 4) + .toList() + .run() + .toCompletableFuture() + .get(1, TimeUnit.SECONDS)); + } + + @Test + void onErrorResumeWith() throws InterruptedException, ExecutionException, TimeoutException { + assertEquals(Arrays.asList(1, 2, 3), + ReactiveStreams + .generate(() -> 1) + .limit(3L) + .peek(i -> { + throw new TestRuntimeException(); + }) + .onErrorResumeWith(throwable -> ReactiveStreams.of(1, 2, 3)) + .toList() + .run() + .toCompletableFuture() + .get(1, TimeUnit.SECONDS)); + } + + + @Test + void onErrorResume2() throws InterruptedException, ExecutionException, TimeoutException { + ReactiveStreams.failed(new TestThrowable()) + .onErrorResumeWith( + t -> ReactiveStreams.of(1, 2, 3) + ) + .forEach(System.out::println).run().toCompletableFuture().get(1, TimeUnit.SECONDS); + } + + @Test + void onErrorResume3() throws InterruptedException, ExecutionException, TimeoutException { + ReactiveStreams.failed(new TestThrowable()) + .onErrorResumeWith( + t -> ReactiveStreams.of(1, 2, 3) + ) + .toList().run().toCompletableFuture().get(1, TimeUnit.SECONDS); + } + + @Override + protected Processor getProcessor() { + return ReactiveStreams.builder() + .onErrorResumeWith(throwable -> ReactiveStreams.of(1L, 2L)) + .buildRs(); + } + + @Override + protected Processor getFailedProcessor(RuntimeException t) { + return ReactiveStreams.builder() + .peek(aLong -> { + throw new RuntimeException(); + }) + .onErrorResumeWith(throwable -> { + throw new TestRuntimeException(); + }) + .buildRs(); + } + + + @Test + void requestCount() { + Publisher pub = ReactiveStreams.failed(new TestThrowable()) + .onErrorResumeWith( + t -> ReactiveStreams.fromIterable(() -> LongStream.rangeClosed(1, 3).boxed().iterator()) + ) + .buildRs(); + CountingSubscriber sub = new CountingSubscriber(); + ReactiveStreams.fromPublisher(pub).buildRs().subscribe(HybridSubscriber.from(sub)); + + sub.request(1); + sub.expectRequestCount(1); + sub.expectSum(1); + sub.request(2); + sub.expectSum(6); + sub.expectRequestCount(3); + sub.expectOnComplete(); + } + + @Test + void requestCount2() { + AtomicLong seq = new AtomicLong(0); + Publisher pub = ReactiveStreams.failed(new TestThrowable()) + .onErrorResumeWith( + t -> ReactiveStreams.generate(seq::incrementAndGet) + ) + .buildRs(); + CountingSubscriber sub = new CountingSubscriber(); + ReactiveStreams.fromPublisher(pub).buildRs().subscribe(HybridSubscriber.from(sub)); + + sub.cancelAfter(100_000L); + sub.request(Long.MAX_VALUE - 1); + sub.request(Long.MAX_VALUE - 1); + } + + @Test + void name() throws InterruptedException, ExecutionException, TimeoutException { + AtomicReference exception = new AtomicReference<>(); + List result = ReactiveStreams.failed(new TestRuntimeException()) + .onErrorResume(err -> { + exception.set(err); + return "foo"; + }) + .toList() + .run().toCompletableFuture().get(1, TimeUnit.SECONDS); + assertEquals(Collections.singletonList("foo"), result); + assertEquals(TestRuntimeException.TEST_MSG, exception.get().getMessage()); + } + + @Test + void requestFinite() { + Publisher pub = ReactiveStreams.failed(new TestThrowable()) + .onErrorResumeWith( + t -> ReactiveStreams.fromIterable(() -> LongStream.rangeClosed(1, 4).boxed().iterator()) + ) + .buildRs(); + CountingSubscriber sub = new CountingSubscriber(); + ReactiveStreams.fromPublisher(pub).buildRs().subscribe(HybridSubscriber.from(sub)); + + sub.request(1); + sub.expectRequestCount(1); + sub.expectSum(1); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiPeekProcessorTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiPeekProcessorTest.java new file mode 100644 index 00000000000..1ffb3ebd6f4 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiPeekProcessorTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import java.util.function.Function; + +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.reactivestreams.Processor; + +public class MultiPeekProcessorTest extends AbstractProcessorTest { + @Override + protected Processor getProcessor() { + return ReactiveStreams.builder().peek(i -> Function.identity()).buildRs(); + } + + @Override + protected Processor getFailedProcessor(RuntimeException t) { + return ReactiveStreams.builder().peek(i -> { + throw t; + }).buildRs(); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiSkipProcessorTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiSkipProcessorTest.java new file mode 100644 index 00000000000..12d4a864788 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiSkipProcessorTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Processor; + +public class MultiSkipProcessorTest extends AbstractProcessorTest { + @Override + protected Processor getProcessor() { + return ReactiveStreams.builder().skip(0).buildRs(); + } + + @Override + protected Processor getFailedProcessor(RuntimeException t) { + return null; + } + + @Test + void skipItems() throws InterruptedException, ExecutionException, TimeoutException { + List result = ReactiveStreams.of(1L, 2L, 3L, 4L) + .peek(System.out::println) + .skip(2) + .toList() + .run().toCompletableFuture().get(1, TimeUnit.SECONDS); + assertEquals(Arrays.asList(3L, 4L), result); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiTakeWhileProcessorTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiTakeWhileProcessorTest.java new file mode 100644 index 00000000000..b08472f615a --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiTakeWhileProcessorTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Processor; + +public class MultiTakeWhileProcessorTest extends AbstractProcessorTest { + @Override + protected Processor getProcessor() { + return ReactiveStreams.builder().takeWhile(i -> true).buildRs(); + } + + @Override + protected Processor getFailedProcessor(RuntimeException t) { + return ReactiveStreams.builder().takeWhile(i -> { + throw t; + }).buildRs(); + } + + @Test + void cancelWhenDone() throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture cancelled = new CompletableFuture<>(); + ReactiveStreams.generate(() -> 4).onTerminate(() -> { + cancelled.complete(null); + }).takeWhile((t) -> false).toList().run(); + cancelled.get(1, TimeUnit.SECONDS); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiTappedProcessorTest.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiTappedProcessorTest.java new file mode 100644 index 00000000000..142118670d0 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/MultiTappedProcessorTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +import io.helidon.common.reactive.MultiTappedProcessor; +import io.helidon.microprofile.reactive.hybrid.HybridProcessor; + +import org.reactivestreams.Processor; + +import java.util.concurrent.Flow; + +public class MultiTappedProcessorTest extends AbstractProcessorTest { + @Override + @SuppressWarnings("unchecked") + protected Processor getProcessor() { + Flow.Processor processor = MultiTappedProcessor.create(); + return HybridProcessor.from(processor); + } + + @Override + @SuppressWarnings("unchecked") + protected Processor getFailedProcessor(RuntimeException t) { + Flow.Processor processor = MultiTappedProcessor.create().onNext(o -> { + throw t; + }); + return HybridProcessor.from(processor); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/TestRuntimeException.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/TestRuntimeException.java new file mode 100644 index 00000000000..60e1f840da1 --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/TestRuntimeException.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +public class TestRuntimeException extends RuntimeException { + public static final String TEST_MSG = TestRuntimeException.class.getSimpleName(); + + public TestRuntimeException() { + super(TEST_MSG); + } +} diff --git a/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/TestThrowable.java b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/TestThrowable.java new file mode 100644 index 00000000000..4062cbbf82c --- /dev/null +++ b/microprofile/reactive-streams/src/test/java/io/helidon/microrofile/reactive/TestThrowable.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microrofile.reactive; + +public class TestThrowable extends Throwable { +} diff --git a/microprofile/tests/tck/pom.xml b/microprofile/tests/tck/pom.xml index 4709b82af46..2bbe7a4bb98 100644 --- a/microprofile/tests/tck/pom.xml +++ b/microprofile/tests/tck/pom.xml @@ -35,6 +35,7 @@ tck-health tck-metrics tck-metrics2 + tck-messaging tck-fault-tolerance tck-jwt-auth tck-openapi diff --git a/microprofile/tests/tck/tck-messaging/pom.xml b/microprofile/tests/tck/tck-messaging/pom.xml new file mode 100644 index 00000000000..83994b129a1 --- /dev/null +++ b/microprofile/tests/tck/tck-messaging/pom.xml @@ -0,0 +1,107 @@ + + + + + 4.0.0 + + io.helidon.microprofile.tests + tck-project + 2.0-SNAPSHOT + + tck-messaging + Helidon Microprofile Tests TCK Messaging + + + + io.helidon.microprofile.tests + helidon-arquillian + ${project.version} + test + + + io.helidon.microprofile.bundles + helidon-microprofile + + + + + io.helidon.microprofile + helidon-microprofile-messaging + 2.0-SNAPSHOT + + + io.helidon.microprofile + helidon-microprofile-reactive-streams + + + + + io.smallrye.reactive + smallrye-reactive-streams-operators + 1.0.10 + + + rxjava + io.reactivex.rxjava2 + + + + + org.eclipse.microprofile.reactive.messaging + microprofile-reactive-messaging-tck + 1.0 + + + rxjava + io.reactivex.rxjava2 + + + + + io.reactivex.rxjava2 + rxjava + 2.2.13 + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + false + + + org.eclipse.microprofile.reactive.messaging:microprofile-reactive-messaging-tck + + + + + + org.apache.maven.surefire + surefire-junit4 + ${version.plugin.surefire} + + + + + + diff --git a/microprofile/tests/tck/tck-messaging/src/main/resources/META-INF/services/org.eclipse.microprofile.reactive.streams.operators.ReactiveStreamsFactory b/microprofile/tests/tck/tck-messaging/src/main/resources/META-INF/services/org.eclipse.microprofile.reactive.streams.operators.ReactiveStreamsFactory new file mode 100644 index 00000000000..8e4c3a64b7e --- /dev/null +++ b/microprofile/tests/tck/tck-messaging/src/main/resources/META-INF/services/org.eclipse.microprofile.reactive.streams.operators.ReactiveStreamsFactory @@ -0,0 +1,17 @@ +# +# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# +# Licensed 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. +# + +org.eclipse.microprofile.reactive.streams.operators.core.ReactiveStreamsFactoryImpl \ No newline at end of file diff --git a/microprofile/tests/tck/tck-messaging/src/test/resources/arquillian.xml b/microprofile/tests/tck/tck-messaging/src/test/resources/arquillian.xml new file mode 100644 index 00000000000..45075f08eb0 --- /dev/null +++ b/microprofile/tests/tck/tck-messaging/src/test/resources/arquillian.xml @@ -0,0 +1,30 @@ + + + + + + + target/deployments + -Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y + + diff --git a/microprofile/tests/tck/tck-reactive-operators/pom.xml b/microprofile/tests/tck/tck-reactive-operators/pom.xml new file mode 100644 index 00000000000..e198096c545 --- /dev/null +++ b/microprofile/tests/tck/tck-reactive-operators/pom.xml @@ -0,0 +1,79 @@ + + + + + 4.0.0 + + io.helidon.microprofile.tests + tck-project + 2.0-SNAPSHOT + + tck-reactive-operators + Helidon Microprofile Tests TCK Reactive Streams Operators + + + + io.helidon.microprofile.tests + helidon-arquillian + ${project.version} + test + + + io.helidon.microprofile.bundles + helidon-microprofile-3.1 + + + + + io.helidon.microprofile + helidon-microprofile-reactive-streams + 2.0-SNAPSHOT + + + + org.eclipse.microprofile.reactive-streams-operators + microprofile-reactive-streams-operators-tck + 1.0.1 + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + false + + + org.eclipse.microprofile.reactive.messaging:microprofile-reactive-messaging-tck + + + + + + org.apache.maven.surefire + surefire-junit4 + ${version.plugin.surefire} + + + + + + diff --git a/microprofile/tests/tck/tck-reactive-operators/src/main/resources/META-INF/services/org.eclipse.microprofile.reactive.streams.operators.ReactiveStreamsFactory b/microprofile/tests/tck/tck-reactive-operators/src/main/resources/META-INF/services/org.eclipse.microprofile.reactive.streams.operators.ReactiveStreamsFactory new file mode 100644 index 00000000000..8e4c3a64b7e --- /dev/null +++ b/microprofile/tests/tck/tck-reactive-operators/src/main/resources/META-INF/services/org.eclipse.microprofile.reactive.streams.operators.ReactiveStreamsFactory @@ -0,0 +1,17 @@ +# +# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# +# Licensed 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. +# + +org.eclipse.microprofile.reactive.streams.operators.core.ReactiveStreamsFactoryImpl \ No newline at end of file diff --git a/microprofile/tests/tck/tck-reactive-operators/src/test/java/io/helidon/microprofile/reactive/HelidonReactiveStreamsTckTest.java b/microprofile/tests/tck/tck-reactive-operators/src/test/java/io/helidon/microprofile/reactive/HelidonReactiveStreamsTckTest.java new file mode 100644 index 00000000000..aadac8e8e47 --- /dev/null +++ b/microprofile/tests/tck/tck-reactive-operators/src/test/java/io/helidon/microprofile/reactive/HelidonReactiveStreamsTckTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.microprofile.reactive; + +import org.eclipse.microprofile.reactive.streams.operators.tck.ReactiveStreamsTck; +import org.reactivestreams.tck.TestEnvironment; + +public class HelidonReactiveStreamsTckTest extends ReactiveStreamsTck { + + public HelidonReactiveStreamsTckTest() { + super(new TestEnvironment(200,200,false)); + } + + @Override + protected HelidonReactiveStreamEngine createEngine() { + return new HelidonReactiveStreamEngine(); + } +} diff --git a/microprofile/tests/tck/tck-reactive-operators/src/test/resources/arquillian.xml b/microprofile/tests/tck/tck-reactive-operators/src/test/resources/arquillian.xml new file mode 100644 index 00000000000..45075f08eb0 --- /dev/null +++ b/microprofile/tests/tck/tck-reactive-operators/src/test/resources/arquillian.xml @@ -0,0 +1,30 @@ + + + + + + + target/deployments + -Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y + + diff --git a/pom.xml b/pom.xml index 8cf1ae927cb..d298ca374d6 100644 --- a/pom.xml +++ b/pom.xml @@ -153,6 +153,7 @@ media webserver security + messaging microprofile tracing bundles diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/HttpRequestScopedPublisher.java b/webserver/webserver/src/main/java/io/helidon/webserver/HttpRequestScopedPublisher.java index 5c41a9565d0..717ff91b7df 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/HttpRequestScopedPublisher.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/HttpRequestScopedPublisher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,11 +44,6 @@ class HttpRequestScopedPublisher extends OriginThreadPublisher