/**
* 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.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Publish a single item as soon as it's available.
*/
public class DelayedMonoPublisher<T> implements Publisher<T> {
private static final Logger LOG = LoggerFactory.getLogger(DelayedMonoPublisher.class);
private ExecutorService workerPool;
private volatile T data;
private volatile Throwable exception;
private List<MonoSubscription> subscriptions = new CopyOnWriteArrayList<>();
private AtomicBoolean flushing = new AtomicBoolean(false);
public DelayedMonoPublisher(ExecutorService workerPool) {
this.workerPool = workerPool;
}
@Override
public void subscribe(Subscriber<? super T> subscriber) {
Objects.requireNonNull(subscriber, "subscriber must not be null");
MonoSubscription sub = new MonoSubscription(subscriber);
subscriptions.add(sub);
subscriber.onSubscribe(sub);
flushCycle();
}
public T getData() {
return data;
}
public void setData(T data) {
Objects.requireNonNull(data, "data must be not null");
if (this.data != null) {
throw new IllegalStateException("data has already been set");
} else if (this.exception != null) {
throw new IllegalStateException("an exception has already been set");
}
this.data = data;
flushCycle();
}
public Throwable getException() {
return exception;
}
public void setException(Throwable exception) {
Objects.requireNonNull(exception, "exception must be not null");
if (this.data != null) {
throw new IllegalStateException("data has already been set");
} else if (this.exception != null) {
throw new IllegalStateException("an exception has already been set");
}
this.exception = exception;
flushCycle();
}
private void flushCycle() {
boolean notRunning = flushing.compareAndSet(false, true);
if (notRunning) {
workerPool.execute(() -> {
try {
List<MonoSubscription> completed = new LinkedList<>();
for (MonoSubscription sub : this.subscriptions) {
sub.flush();
if (sub.isTerminated()) {
completed.add(sub);
}
}
this.subscriptions.removeAll(completed);
} finally {
flushing.set(false);
}
boolean runAgain = false;
for (MonoSubscription sub : this.subscriptions) {
if (sub.isReady()) {
runAgain = true;
break;
}
}
if (runAgain) {
flushCycle();
}
});
}
}
private final class MonoSubscription implements Subscription {
private volatile boolean terminated;
private volatile boolean requested;
private Subscriber<? super T> subscriber;
private MonoSubscription(Subscriber<? super T> subscriber) {
this.subscriber = subscriber;
}
@Override
public void request(long l) {
synchronized (this) {
if (terminated) {
// just ignore the request
return;
}
}
if (l <= 0) {
subscriber.onError(new IllegalArgumentException("3.9"));
synchronized (this) {
terminated = true;
}
} else {
synchronized (this) {
requested = true;
}
}
flushCycle();
}
public void flush() {
synchronized (this) {
if (!isReady()) {
return;
}
terminated = true;
}
if (data != null) {
subscriber.onNext(data);
subscriber.onComplete();
} else {
subscriber.onError(exception);
}
}
public boolean isTerminated() {
return terminated;
}
public boolean isReady() {
return !terminated && requested && (data != null || exception != null);
}
@Override
public synchronized void cancel() {
terminated = true;
}
}
}