/*
* Copyright 2016 higherfrequencytrading.com
*
* 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 net.openhft.chronicle.engine.query;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.util.SerializableFunction;
import net.openhft.chronicle.core.util.SerializablePredicate;
import net.openhft.chronicle.engine.api.pubsub.Subscriber;
import net.openhft.chronicle.engine.api.query.Query;
import net.openhft.chronicle.engine.api.query.Subscription;
import net.openhft.chronicle.engine.api.query.SubscriptionNotSupported;
import net.openhft.chronicle.engine.api.tree.RequestContext;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.stream.Collector;
import java.util.stream.Stream;
import static java.util.EnumSet.of;
import static java.util.concurrent.TimeUnit.SECONDS;
import static net.openhft.chronicle.engine.api.tree.RequestContext.Operation.BOOTSTRAP;
import static net.openhft.chronicle.engine.api.tree.RequestContext.Operation.END_SUBSCRIPTION_AFTER_BOOTSTRAP;
/**
* @author Rob Austin.
*/
public class RemoteQuery<E> implements Query<E> {
private static final Logger LOG = LoggerFactory.getLogger(RemoteQuery.class);
private final Filter<E> filter = new Filter<>();
private final Subscribable<E> subscribable;
public RemoteQuery(final Subscribable<E> eSubscribable) {
this.subscribable = eSubscribable;
}
@NotNull
@Override
public Query<E> filter(SerializablePredicate<? super E> predicate) {
filter.addFilter(predicate);
return this;
}
@NotNull
@SuppressWarnings("unchecked")
@Override
public <R> Query<R> map(SerializableFunction<? super E, ? extends R> mapper) {
filter.addMap(mapper);
return (Query<R>) this;
}
@NotNull
@SuppressWarnings("unchecked")
@Override
public <R> Query<R> project(Class<R> rClass) {
filter.addProject(rClass);
return (Query<R>) this;
}
@NotNull
@SuppressWarnings("unchecked")
@Override
public <R> Query<R> flatMap(SerializableFunction<? super E, ? extends Query<? extends R>> mapper) {
filter.addFlatMap(mapper);
return (Query<R>) this;
}
@NotNull
@Override
public Stream<E> stream() {
throw new UnsupportedOperationException("todo");
}
@NotNull
@Override
public Subscription subscribe(@NotNull Consumer<? super E> action) {
subscribable.subscribe(
action::accept,
filter,
of(BOOTSTRAP));
return SubscriptionNotSupported.INSTANCE;
}
@Override
public void forEach(@NotNull Consumer<? super E> action) {
forEach2(action);
}
private void forEach2(@NotNull Consumer<? super E> action) {
@NotNull final BlockingQueue<E> queue = new ArrayBlockingQueue<E>(128);
@NotNull final AtomicBoolean finished = new AtomicBoolean();
@NotNull final Subscriber<E> accept = new Subscriber<E>() {
@Override
public void onMessage(E o) {
try {
final boolean offer = queue.offer(o, 20, SECONDS);
synchronized (queue) {
queue.notifyAll();
}
if (!offer) {
Jvm.warn().on(getClass(), "Queue Full");
dumpThreads();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public void onEndOfSubscription() {
finished.set(true);
synchronized (queue) {
queue.notifyAll();
}
}
};
subscribable.subscribe(
accept,
filter,
of(BOOTSTRAP, END_SUBSCRIPTION_AFTER_BOOTSTRAP));
while (!finished.get()) {
try {
final E message = queue.poll();
if (message == null) {
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (queue) {
queue.wait(1000); // 1 SECONDS
}
continue;
}
action.accept(message);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
queue.forEach(action::accept);
}
private void dumpThreads() {
for (@NotNull Map.Entry<Thread, StackTraceElement[]> entry : Thread.getAllStackTraces().entrySet()) {
Thread thread = entry.getKey();
if (thread.getThreadGroup().getName().equals("system"))
continue;
@NotNull StringBuilder sb = new StringBuilder();
sb.append(thread).append(" ").append(thread.getState());
Jvm.trimStackTrace(sb, entry.getValue());
sb.append("\n");
Jvm.warn().on(getClass(), "\n========= THREAD DUMP =========\n" + sb.toString());
}
}
@Override
public <R, A> R collect(@NotNull Collector<? super E, A, R> collector) {
final A container = collector.supplier().get();
forEach(o -> collector.accumulator().accept(container, o));
return collector.finisher().apply(container);
}
public interface Subscribable<E> {
void subscribe(@NotNull Subscriber<E> subscriber,
@NotNull Filter<E> filter,
@NotNull Set<RequestContext.Operation> contextOperations);
}
}