Package net.kuujo.vertigo.io.port.impl

Source Code of net.kuujo.vertigo.io.port.impl.DefaultInputPort

/*
* Copyright 2014 the original author or authors.
*
* 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.kuujo.vertigo.io.port.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.kuujo.vertigo.hook.ComponentHook;
import net.kuujo.vertigo.hook.InputHook;
import net.kuujo.vertigo.io.batch.InputBatch;
import net.kuujo.vertigo.io.connection.InputConnection;
import net.kuujo.vertigo.io.connection.InputConnectionContext;
import net.kuujo.vertigo.io.connection.impl.DefaultInputConnection;
import net.kuujo.vertigo.io.group.InputGroup;
import net.kuujo.vertigo.io.port.InputPort;
import net.kuujo.vertigo.io.port.InputPortContext;
import net.kuujo.vertigo.util.CountingCompletionHandler;
import net.kuujo.vertigo.util.Observer;
import net.kuujo.vertigo.util.Task;
import net.kuujo.vertigo.util.TaskRunner;

import org.vertx.java.core.AsyncResult;
import org.vertx.java.core.Handler;
import org.vertx.java.core.Vertx;
import org.vertx.java.core.impl.DefaultFutureResult;
import org.vertx.java.core.logging.Logger;
import org.vertx.java.core.logging.impl.LoggerFactory;

/**
* Default input port implementation.
*
* @author <a href="http://github.com/kuujo">Jordan Halterman</a>
*/
public class DefaultInputPort implements InputPort, Observer<InputPortContext> {
  private static final Logger log = LoggerFactory.getLogger(DefaultInputPort.class);
  private final Vertx vertx;
  private InputPortContext context;
  private final List<InputConnection> connections = new ArrayList<>();
  private List<InputHook> hooks = new ArrayList<>();
  private final TaskRunner tasks = new TaskRunner();
  @SuppressWarnings("rawtypes")
  private Handler messageHandler;
  private final Set<Handler<InputBatch>> batchHandlers = new HashSet<>();
  private Handler<InputGroup> groupHandler;
  private final Map<String, Handler<InputGroup>> groupHandlers = new HashMap<>();
  private boolean open;
  private boolean paused;

  public DefaultInputPort(Vertx vertx, InputPortContext context) {
    this.vertx = vertx;
    this.context = context;
    this.hooks = context.hooks();
    for (Object hook : context.input().instance().component().hooks()) {
      hooks.add((ComponentHook) hook);
    }
    context.registerObserver(this);
  }

  @Override
  public String name() {
    return context.name();
  }

  @Override
  public Vertx vertx() {
    return vertx;
  }

  @Override
  public void update(InputPortContext context) {
    log.debug(String.format("%s - In port configuration has changed, updating connections", this));

    // Copy the context in order to ensure that future changes via the
    // observer will not effect this update.
    final InputPortContext update = context.copy();

    // All updates are run sequentially to prevent race conditions
    // during configuration changes. Without essentially locking the
    // object, it could be possible that connections are simultaneously
    // added and removed or opened and closed on the object.
    tasks.runTask(new Handler<Task>() {
      @Override
      public void handle(final Task task) {
        // Iterate through existing connections and try to determine
        // whether any of them have been removed from the network.
        Iterator<InputConnection> iter = connections.iterator();
        while (iter.hasNext()) {
          final InputConnection connection = iter.next();
          boolean exists = false;
          for (InputConnectionContext input : update.connections()) {
            if (input.address().equals(connection.address())) {
              exists = true;
              break;
            }
          }

          // If a connection was removed from the network, close
          // and remove the connection regardless of whether the
          // close is actually successful.
          if (!exists) {
            connection.close(new Handler<AsyncResult<Void>>() {
              @Override
              public void handle(AsyncResult<Void> result) {
                if (result.failed()) {
                  log.error(String.format("%s - Failed to close input connection: %s", DefaultInputPort.this, connection));
                } else {
                  log.info(String.format("%s - Closed input connection: %s", DefaultInputPort.this, connection));
                }
              }
            });
            iter.remove();
          }
        }

        // Now try to determine whether any connections were added to the network.
        final List<InputConnection> newConnections = new ArrayList<>();
        for (InputConnectionContext input : update.connections()) {
          boolean exists = false;
          for (InputConnection connection : connections) {
            if (connection.address().equals(input.address())) {
              exists = true;
              break;
            }
          }
          if (!exists) {
            log.debug(String.format("%s - Creating connection: %s", DefaultInputPort.this, input));
            newConnections.add(new DefaultInputConnection(vertx, input));
          }
        }

        // If the port is open then that means connections have already
        // been set up. We need to add the new connections carefully
        // because it's possible that user code may be sending messages
        // on the port. If messages are sent to a connection that's not
        // yet open then an exception will be thrown.
        if (open) {
          final CountingCompletionHandler<Void> counter = new CountingCompletionHandler<Void>(newConnections.size());
          counter.setHandler(new Handler<AsyncResult<Void>>() {
            @Override
            public void handle(AsyncResult<Void> result) {
              task.complete();
            }
          });

          // Start each connection and add the connection to the connections
          // list only once the connection has been successfully opened.
          // The update lock by the task runner will ensure that we don't
          // accidentally open up two of the same connection even if the
          // configuration is updated again.
          for (final InputConnection connection : newConnections) {
            connection.open(new Handler<AsyncResult<Void>>() {
              @Override
              public void handle(AsyncResult<Void> result) {
                if (result.failed()) {
                  log.error(String.format("%s - Failed to open input connection: %s", DefaultInputPort.this, connection));
                } else {
                  log.info(String.format("%s - Opened input connection: %s", DefaultInputPort.this, connection));
                  connections.add(setupConnection(connection));
                }
              }
            });
          }
        } else {
          // If the port's not even open yet then it's okay to just add the
          // connection to the connections list. Once the port is opened it
          // will open the connections.
          for (InputConnection connection : newConnections) {
            connections.add(setupConnection(connection));
          }
          task.complete();
        }
      }
    });
  }

  @Override
  public InputPort pause() {
    paused = true;
    for (InputConnection connection : connections) {
      connection.pause();
    }
    return this;
  }

  @Override
  public InputPort resume() {
    paused = false;
    for (InputConnection connection : connections) {
      connection.resume();
    }
    return this;
  }

  @Override
  @SuppressWarnings({"unchecked", "rawtypes"})
  public InputPort messageHandler(final Handler handler) {
    this.messageHandler = new Handler() {
      @Override
      public void handle(Object message) {
        handler.handle(message);
        for (InputHook hook : hooks) {
          hook.handleReceive(message);
        }
      }
    };
    for (InputConnection connection : connections) {
      connection.messageHandler(messageHandler);
    }
    return this;
  }

  @Override
  public InputPort batchHandler(Handler<InputBatch> handler) {
    if (this.batchHandlers.add(handler)) {
      for (InputConnection connection : connections) {
        connection.batchHandler(handler);
      }
    }
    return this;
  }

  @Override
  public InputPort groupHandler(Handler<InputGroup> handler) {
    this.groupHandler = handler;
    for (InputConnection connection : connections) {
      connection.groupHandler(handler);
    }
    return this;
  }

  @Override
  public InputPort groupHandler(String group, Handler<InputGroup> handler) {
    this.groupHandlers.put(group, handler);
    for (InputConnection connection : connections) {
      connection.groupHandler(group, handler);
    }
    return this;
  }

  @Override
  public InputPort open() {
    return open(null);
  }

  @Override
  public InputPort open(final Handler<AsyncResult<Void>> doneHandler) {
    // Prevent the object from being opened and closed simultaneously
    // by queueing open/close operations as tasks.
    tasks.runTask(new Handler<Task>() {
      @Override
      public void handle(final Task task) {
        if (!open) {
          final CountingCompletionHandler<Void> startCounter = new CountingCompletionHandler<Void>(context.connections().size());
          startCounter.setHandler(new Handler<AsyncResult<Void>>() {
            @Override
            public void handle(AsyncResult<Void> result) {
              if (result.failed()) {
                new DefaultFutureResult<Void>(result.cause()).setHandler(doneHandler);
              } else {
                open = true;
                new DefaultFutureResult<Void>((Void) null).setHandler(doneHandler);
              }
              task.complete();
            }
          });

          // Add all connections from the connection context.
          // Only add connections to the connections list once the connection
          // has been opened. This ensures that we don't attempt to send messages
          // on a close connection.
          connections.clear();
          for (InputConnectionContext connectionContext : context.connections()) {
            final InputConnection connection = new DefaultInputConnection(vertx, connectionContext);
            connection.open(new Handler<AsyncResult<Void>>() {
              @Override
              public void handle(AsyncResult<Void> result) {
                if (result.failed()) {
                  log.error(String.format("%s - Failed to open input connection: %s", DefaultInputPort.this, connection));
                  startCounter.fail(result.cause());
                } else {
                  log.info(String.format("%s - Opened input connection: %s", DefaultInputPort.this, connection));
                  connections.add(setupConnection(connection));
                  startCounter.succeed();
                }
              }
            });
          }
        } else {
          new DefaultFutureResult<Void>((Void) null).setHandler(doneHandler);
          task.complete();
        }
      }
    });
    return this;
  }

  /**
   * Sets up the given connection.
   */
  @SuppressWarnings("unchecked")
  private InputConnection setupConnection(InputConnection connection) {
    log.debug(String.format("%s - Setting up connection: %s", this, connection));
    connection.messageHandler(messageHandler);
    for (Handler<InputBatch> handler : batchHandlers) {
      connection.batchHandler(handler);
    }
    if (groupHandler != null) {
      connection.groupHandler(groupHandler);
    }
    for (Map.Entry<String, Handler<InputGroup>> entry : groupHandlers.entrySet()) {
      connection.groupHandler(entry.getKey(), entry.getValue());
    }
    if (paused) {
      connection.pause();
    }
    return connection;
  }

  @Override
  public void close() {
    close(null);
  }

  @Override
  public void close(final Handler<AsyncResult<Void>> doneHandler) {
    // Prevent the object from being opened and closed simultaneously
    // by queueing open/close operations as tasks.
    tasks.runTask(new Handler<Task>() {
      @Override
      public void handle(final Task task) {
        if (open) {
          final CountingCompletionHandler<Void> stopCounter = new CountingCompletionHandler<Void>(connections.size());
          stopCounter.setHandler(new Handler<AsyncResult<Void>>() {
            @Override
            public void handle(AsyncResult<Void> result) {
              if (result.failed()) {
                new DefaultFutureResult<Void>(result.cause()).setHandler(doneHandler);
              } else {
                connections.clear();
                new DefaultFutureResult<Void>((Void) null).setHandler(doneHandler);
              }
              task.complete();
            }
          });

          for (final InputConnection connection : connections) {
            connection.close(new Handler<AsyncResult<Void>>() {
              @Override
              public void handle(AsyncResult<Void> result) {
                if (result.failed()) {
                  log.warn(String.format("%s - Failed to close input connection: %s", DefaultInputPort.this, connection));
                  stopCounter.fail(result.cause());
                } else {
                  log.info(String.format("%s - Closed input connection: %s", DefaultInputPort.this, connection));
                  stopCounter.succeed();
                }
              }
            });
          }
        } else {
          new DefaultFutureResult<Void>((Void) null).setHandler(doneHandler);
          task.complete();
        }
      }
    });
  }

  @Override
  public String toString() {
    return context.toString();
  }

}
TOP

Related Classes of net.kuujo.vertigo.io.port.impl.DefaultInputPort

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.