/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.component.reactive.streams.engine; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.function.Function; import java.util.function.Supplier; import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.CompositeType; import javax.management.openmbean.OpenDataException; import javax.management.openmbean.OpenType; import javax.management.openmbean.SimpleType; import javax.management.openmbean.TabularData; import javax.management.openmbean.TabularDataSupport; import javax.management.openmbean.TabularType; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.api.management.ManagedOperation; import org.apache.camel.api.management.ManagedResource; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.reactive.streams.ReactiveStreamsCamelSubscriber; import org.apache.camel.component.reactive.streams.ReactiveStreamsConstants; import org.apache.camel.component.reactive.streams.ReactiveStreamsConsumer; import org.apache.camel.component.reactive.streams.ReactiveStreamsHelper; import org.apache.camel.component.reactive.streams.ReactiveStreamsProducer; import org.apache.camel.component.reactive.streams.api.CamelReactiveStreamsService; import org.apache.camel.component.reactive.streams.util.ConvertingPublisher; import org.apache.camel.component.reactive.streams.util.ConvertingSubscriber; import org.apache.camel.component.reactive.streams.util.MonoPublisher; import org.apache.camel.component.reactive.streams.util.UnwrapStreamProcessor; import org.apache.camel.spi.Synchronization; import org.apache.camel.support.ServiceSupport; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.function.Suppliers; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; /** * The default implementation of the reactive streams service. */ @ManagedResource(description = "Managed CamelReactiveStreamsService") public class DefaultCamelReactiveStreamsService extends ServiceSupport implements CamelReactiveStreamsService { private final CamelContext context; private final ReactiveStreamsEngineConfiguration configuration; private final Supplier<UnwrapStreamProcessor> unwrapStreamProcessorSupplier; private ExecutorService workerPool; private final ConcurrentMap<String, CamelPublisher> publishers = new ConcurrentHashMap<>(); private final ConcurrentMap<String, ReactiveStreamsCamelSubscriber> subscribers = new ConcurrentHashMap<>(); private final ConcurrentMap<String, String> publishedUriToStream = new ConcurrentHashMap<>(); private final ConcurrentMap<String, String> requestedUriToStream = new ConcurrentHashMap<>(); public DefaultCamelReactiveStreamsService(CamelContext context, ReactiveStreamsEngineConfiguration configuration) { this.context = context; this.configuration = configuration; this.unwrapStreamProcessorSupplier = Suppliers.memorize(UnwrapStreamProcessor::new); } @Override public String getId() { return ReactiveStreamsConstants.DEFAULT_SERVICE_NAME; } @Override protected void doStart() throws Exception { this.workerPool = context.getExecutorServiceManager().newThreadPool( this, configuration.getThreadPoolName(), configuration.getThreadPoolMinSize(), configuration.getThreadPoolMaxSize() ); } @Override protected void doStop() throws Exception { if (this.workerPool != null) { context.getExecutorServiceManager().shutdownNow(this.workerPool); this.workerPool = null; } } @Override public Publisher<Exchange> fromStream(String name) { return new UnwrappingPublisher(getPayloadPublisher(name)); } @SuppressWarnings("unchecked") public <T> Publisher<T> fromStream(String name, Class<T> cls) { if (Exchange.class.equals(cls)) { return (Publisher<T>) fromStream(name); } return new ConvertingPublisher<T>(fromStream(name), cls); } @Override public ReactiveStreamsCamelSubscriber streamSubscriber(String name) { return subscribers.computeIfAbsent(name, n -> new ReactiveStreamsCamelSubscriber(name)); } @SuppressWarnings("unchecked") public <T> Subscriber<T> streamSubscriber(String name, Class<T> type) { if (Exchange.class.equals(type)) { return (Subscriber<T>) streamSubscriber(name); } return new ConvertingSubscriber<T>(streamSubscriber(name), context); } @Override public void sendCamelExchange(String name, Exchange exchange) { getPayloadPublisher(name).publish(exchange); } @Override public Publisher<Exchange> toStream(String name, Object data) { Exchange exchange = ReactiveStreamsHelper.convertToExchange(context, data); return doRequest(name, exchange); } @Override public Function<?, ? extends Publisher<Exchange>> toStream(String name) { return data -> toStream(name, data); } @Override public <T> Publisher<T> toStream(String name, Object data, Class<T> type) { return new ConvertingPublisher<>(toStream(name, data), type); } protected Publisher<Exchange> doRequest(String name, Exchange data) { ReactiveStreamsConsumer consumer = streamSubscriber(name).getConsumer(); if (consumer == null) { throw new IllegalStateException("No consumers attached to the stream " + name); } DelayedMonoPublisher<Exchange> publisher = new DelayedMonoPublisher<>(this.workerPool); data.addOnCompletion(new Synchronization() { @Override public void onComplete(Exchange exchange) { publisher.setData(exchange); } @Override public void onFailure(Exchange exchange) { Throwable throwable = exchange.getException(); if (throwable == null) { throwable = new IllegalStateException("Unknown Exception"); } publisher.setException(throwable); } }); consumer.process(data, doneSync -> { }); return publisher; } @Override public <T> Function<Object, Publisher<T>> toStream(String name, Class<T> type) { return data -> toStream(name, data, type); } private CamelPublisher getPayloadPublisher(String name) { publishers.computeIfAbsent(name, n -> new CamelPublisher(this.workerPool, this.context, n)); return publishers.get(name); } @Override public Publisher<Exchange> from(String uri) { publishedUriToStream.computeIfAbsent(uri, u -> { try { String uuid = context.getUuidGenerator().generateUuid(); new RouteBuilder() { @Override public void configure() throws Exception { from(u) .to("reactive-streams:" + uuid); } }.addRoutesToCamelContext(context); return uuid; } catch (Exception e) { throw new IllegalStateException("Unable to create source reactive stream from direct URI: " + uri, e); } }); return fromStream(publishedUriToStream.get(uri)); } @Override public <T> Publisher<T> from(String uri, Class<T> type) { return new ConvertingPublisher<T>(from(uri), type); } @Override public Subscriber<Exchange> subscriber(String uri) { try { String uuid = context.getUuidGenerator().generateUuid(); new RouteBuilder() { @Override public void configure() throws Exception { from("reactive-streams:" + uuid) .to(uri); } }.addRoutesToCamelContext(context); return streamSubscriber(uuid); } catch (Exception e) { throw new IllegalStateException("Unable to create source reactive stream towards direct URI: " + uri, e); } } @Override public <T> Subscriber<T> subscriber(String uri, Class<T> type) { return new ConvertingSubscriber<T>(subscriber(uri), context); } @Override public Publisher<Exchange> to(String uri, Object data) { requestedUriToStream.computeIfAbsent(uri, u -> { try { String uuid = context.getUuidGenerator().generateUuid(); new RouteBuilder() { @Override public void configure() throws Exception { from("reactive-streams:" + uuid) .to(u); } }.addRoutesToCamelContext(context); return uuid; } catch (Exception e) { throw new IllegalStateException("Unable to create requested reactive stream from direct URI: " + uri, e); } }); return toStream(requestedUriToStream.get(uri), data); } @Override public Function<Object, Publisher<Exchange>> to(String uri) { return data -> to(uri, data); } @Override public <T> Publisher<T> to(String uri, Object data, Class<T> type) { return new ConvertingPublisher<>(to(uri, data), type); } @Override public <T> Function<Object, Publisher<T>> to(String uri, Class<T> type) { return data -> to(uri, data, type); } @Override public void process(String uri, Function<? super Publisher<Exchange>, ?> processor) { try { new RouteBuilder() { @Override public void configure() throws Exception { from(uri) .process(exchange -> { Exchange copy = exchange.copy(); Object result = processor.apply(new MonoPublisher<>(copy)); exchange.getIn().setBody(result); }) .process(unwrapStreamProcessorSupplier.get()); } }.addRoutesToCamelContext(context); } catch (Exception e) { throw new IllegalStateException("Unable to add reactive stream processor to the direct URI: " + uri, e); } } @Override public <T> void process(String uri, Class<T> type, Function<? super Publisher<T>, ?> processor) { process(uri, exPub -> processor.apply(new ConvertingPublisher<T>(exPub, type))); } @Override public ReactiveStreamsCamelSubscriber attachCamelConsumer(String name, ReactiveStreamsConsumer consumer) { ReactiveStreamsCamelSubscriber subscriber = streamSubscriber(name); subscriber.attachConsumer(consumer); return subscriber; } @Override public void detachCamelConsumer(String name) { streamSubscriber(name).detachConsumer(); } @Override public void attachCamelProducer(String name, ReactiveStreamsProducer producer) { getPayloadPublisher(name).attachProducer(producer); } @Override public void detachCamelProducer(String name) { getPayloadPublisher(name).detachProducer(); } @ManagedOperation(description = "Information about Camel Reactive subscribers") public TabularData camelSubscribers() { try { final TabularData answer = new TabularDataSupport(subscribersTabularType()); subscribers.forEach((k, v) -> { try { String name = k; long inflight = v.getInflightCount(); long requested = v.getRequested(); CompositeType ct = subscribersCompositeType(); CompositeData data = new CompositeDataSupport(ct, new String[] {"name", "inflight", "requested"}, new Object[] {name, inflight, requested}); answer.put(data); } catch (Exception e) { throw ObjectHelper.wrapRuntimeCamelException(e); } }); return answer; } catch (Exception e) { throw ObjectHelper.wrapRuntimeCamelException(e); } } @ManagedOperation(description = "Information about Camel Reactive publishers") public TabularData camelPublishers() { try { final TabularData answer = new TabularDataSupport(publishersTabularType()); publishers.forEach((k, v) -> { try { String name = k; List<CamelSubscription> subscriptions = v.getSubscriptions(); int subscribers = subscriptions.size(); TabularData subscriptionData = new TabularDataSupport(subscriptionsTabularType()); CompositeType subCt = subscriptionsCompositeType(); for (CamelSubscription sub : subscriptions) { String id = sub.getId(); long bufferSize = sub.getBufferSize(); String backpressure = sub.getBackpressureStrategy() != null ? sub.getBackpressureStrategy().name() : ""; CompositeData subData = new CompositeDataSupport(subCt, new String[]{"name", "buffer size", "back pressure"}, new Object[]{id, bufferSize, backpressure}); subscriptionData.put(subData); } CompositeType ct = publishersCompositeType(); CompositeData data = new CompositeDataSupport(ct, new String[] {"name", "subscribers", "subscriptions"}, new Object[] {name, subscribers, subscriptionData}); answer.put(data); } catch (Exception e) { throw ObjectHelper.wrapRuntimeCamelException(e); } }); return answer; } catch (Exception e) { throw ObjectHelper.wrapRuntimeCamelException(e); } } private static TabularType subscribersTabularType() throws OpenDataException { CompositeType ct = subscribersCompositeType(); return new TabularType("subscribers", "Information about Camel Reactive subscribers", ct, new String[]{"name"}); } private static CompositeType subscribersCompositeType() throws OpenDataException { return new CompositeType("subscriptions", "Subscriptions", new String[] {"name", "inflight", "requested"}, new String[] {"Name", "Inflight", "Requested"}, new OpenType[] {SimpleType.STRING, SimpleType.LONG, SimpleType.LONG}); } private static CompositeType publishersCompositeType() throws OpenDataException { return new CompositeType("publishers", "Publishers", new String[] {"name", "subscribers", "subscriptions"}, new String[] {"Name", "Subscribers", "Subscriptions"}, new OpenType[] {SimpleType.STRING, SimpleType.INTEGER, subscriptionsTabularType()}); } private static TabularType subscriptionsTabularType() throws OpenDataException { CompositeType ct = subscriptionsCompositeType(); return new TabularType("subscriptions", "Information about External Reactive subscribers", ct, new String[]{"name"}); } private static CompositeType subscriptionsCompositeType() throws OpenDataException { return new CompositeType("subscriptions", "Subscriptions", new String[] {"name", "buffer size", "back pressure"}, new String[] {"Name", "Buffer Size", "Back Pressure"}, new OpenType[] {SimpleType.STRING, SimpleType.LONG, SimpleType.STRING}); } private static TabularType publishersTabularType() throws OpenDataException { CompositeType ct = publishersCompositeType(); return new TabularType("publishers", "Information about Camel Reactive publishers", ct, new String[]{"name"}); } }