com.nextdoor.bender.operation.conditional.ConditionalOperation.java Source code

Java tutorial

Introduction

Here is the source code for com.nextdoor.bender.operation.conditional.ConditionalOperation.java

Source

/*
 * 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 2018 Nextdoor.com, Inc
 *
 */

package com.nextdoor.bender.operation.conditional;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import com.nextdoor.bender.InternalEvent;
import com.nextdoor.bender.operation.FilterOperation;
import com.nextdoor.bender.operation.OperationProcessor;
import com.nextdoor.bender.operation.StreamOperation;
import com.nextdoor.bender.operation.fork.ForkOperation.StreamToQueue;
import com.oath.cyclops.async.adapters.Queue;

public class ConditionalOperation implements StreamOperation {
    private List<Pair<FilterOperation, Queue<InternalEvent>>> filtersAndQueues;
    private final List<Pair<FilterOperation, List<OperationProcessor>>> conditionsAndProcs;
    private final ExecutorService es;
    private final boolean filterNonMatch;

    public ConditionalOperation(List<Pair<FilterOperation, List<OperationProcessor>>> conditionsAndProcs,
            boolean filterNonMatch) {
        this.conditionsAndProcs = conditionsAndProcs;
        this.es = Executors.newFixedThreadPool(conditionsAndProcs.size());
        this.filterNonMatch = filterNonMatch;
    }

    /*-
     * This operation takes in an input Stream of events and checks the event against
     * each condition in an if elseif manner. The first matching condition is send the 
     * event. If no conditions match and filter non-match is specified true then the
     * event is filtered out. If false the event is sent to the output queue. 
     * 
     *           +--------------+
     *           | Input Stream |
     *           +------+-------+
     *                  |
     *                  v
     *       +----------+-----------+
     *       | Input Consumer Thread |
     *       +----------+------------+
     *                  |
     *        +---------+
     *        |
     *        v
     *  +-----+-------+    +-------------+      +-------------+
     *  | Condition 1 | No | Condition 2 |  No  |    Filter   |
     *  |   Filter    +--->+   Filter    +----->+  Non-Match  |
     *  +-----+-------+    +-----+-------+      +------+------+
     *        |                  |                     |
     *    Yes |             Yes  |                     | No
     *        v                  v                     |
     *   +----+-----+       +----+-----+               |
     *   |  Queue 1 |       |  Queue 2 |               |
     *   +----+-----+       +----+-----+               |
     *        |                  |                     |
     *        v                  v                     |
     *    +---+----+         +---+----+                |
     *    | Stream |         | Stream |                |
     *    +---+----+         +---+----+                |
     *        |                  |                     |
     *        v                  v                     |
     *  +-----+------+     +-----+------+              |
     *  | Operations |     | Operations |              |
     *  +-----+------+     +-----+------+              |
     *        |                  |                     |
     *        v                  v                     |
     *   +----+-----+       +----+-----+               |
     *   | Consumer |       | Consumer |               |
     *   |  Thread  |       |  Thread  |               |
     *   +--+-------+       +-----+----+               |
     *      |                     |                    |
     *      |   +--------------+  |                    |
     *      +-->+ Output Queue +<-+--------------------+
     *          +------+-------+
     *                 |
     *                 v
     *     +-----------+------------+
     *     | Output Consumer Thread |
     *     +-----------+------------+
     *                 |
     *                 v
     *         +-------+-------+
     *         | Output Stream |
     *         +---------------+
     *  
     * 
     */
    public Stream<InternalEvent> getOutputStream(Stream<InternalEvent> input) {
        /*
         * outputStreams keeps track of the output Stream of each Condition.
         */
        List<Stream<InternalEvent>> outputStreams = new ArrayList<Stream<InternalEvent>>(
                this.conditionsAndProcs.size());

        /*
         * From a list of operation configurations in each condition construct queues and streams.
         */
        this.filtersAndQueues = new ArrayList<Pair<FilterOperation, Queue<InternalEvent>>>(
                this.conditionsAndProcs.size());
        for (Pair<FilterOperation, List<OperationProcessor>> filterAndProcs : this.conditionsAndProcs) {

            FilterOperation filter = filterAndProcs.getLeft();
            List<OperationProcessor> procs = filterAndProcs.getRight();

            /*
             * Construct a Queue for each conditional. This is the input to each Condition.
             */
            Queue<InternalEvent> queue = new Queue<InternalEvent>(
                    new LinkedBlockingQueue<InternalEvent>(procs.size()));

            this.filtersAndQueues.add(new ImmutablePair<FilterOperation, Queue<InternalEvent>>(filter, queue));

            /*
             * Connect the condition's input Queue with operations. Each operation returns a stream with its
             * operation concatenated on.
             */
            Stream<InternalEvent> conditionInput = queue.jdkStream();
            for (OperationProcessor proc : procs) {
                conditionInput = proc.perform(conditionInput);
            }

            /*
             * Last input is the output.
             */
            outputStreams.add(conditionInput);
        }

        /*
         * Condition Consumer Threads
         * 
         * Combine each condition's output stream and write to the output Queue. When all data is consumed
         * the last condition closes the output Queue.
         */
        Queue<InternalEvent> outputQueue = new Queue<InternalEvent>(
                new LinkedBlockingQueue<InternalEvent>(this.conditionsAndProcs.size()));
        AtomicInteger lock = new AtomicInteger(outputStreams.size());

        outputStreams.forEach(stream -> {
            this.es.execute(new StreamToQueue(stream, outputQueue, lock));
        });

        /*
         * Consume input Stream in a thread and publish to each condition's Queue.
         */
        new Thread(new Runnable() {
            @Override
            public void run() {
                input.forEach(ievent -> {
                    boolean matches = false;

                    for (Pair<FilterOperation, Queue<InternalEvent>> filterAndQueue : filtersAndQueues) {
                        FilterOperation filter = filterAndQueue.getLeft();

                        /*
                         * If event passes the filter offer event to queue.
                         */
                        if (filter.test(ievent)) {
                            filterAndQueue.getRight().offer(ievent);
                            matches = true;
                            break;
                        }
                    }

                    /*
                     * Send to output queue if no case matches
                     */
                    if (!matches && !filterNonMatch) {
                        outputQueue.offer(ievent);
                    }
                });

                /*
                 * Close queues when source queue is consumed.
                 */
                for (Pair<FilterOperation, Queue<InternalEvent>> filterAndQueue : filtersAndQueues) {
                    filterAndQueue.getRight().close();
                }
            }
        }).start();

        return outputQueue.jdkStream();
    }
}