예제 #1
0
  @Override
  @HasPermission(action = UGC_UPDATE, type = SocialPermission.class)
  public UGC update(
      @SecuredObject final String ugcId,
      final String body,
      final String subject,
      final String contextId,
      final Map attributes)
      throws SocialException, UGCNotFound {
    log.debug("logging.ugc.updateUgc", ugcId);
    try {
      final Profile currentProfile = SocialSecurityUtils.getCurrentProfile();
      boolean moderateByMail =
          Boolean.parseBoolean(
              tenantConfigurationService.getProperty(contextId, "moderateByMailEnable").toString());

      if (!ObjectId.isValid(ugcId)) {
        throw new IllegalArgumentException("Given UGC Id is not valid");
      }
      T toUpdate = (T) ugcRepository.findUGC(contextId, ugcId);
      if (toUpdate == null) {
        throw new IllegalArgumentException("UGC with Id " + ugcId + " does not exists");
      }
      if (StringUtils.isNotBlank(body)) {
        toUpdate.setBody(body);
      }
      if (StringUtils.isNotBlank(subject)) {
        toUpdate.setBody(subject);
      }
      pipeline.processUgc(toUpdate);
      if (moderateByMail
          && !SocialSecurityUtils.isProfileModeratorOrAdmin(currentProfile, contextId)) {
        if (toUpdate instanceof SocialUgc) {
          ((SocialUgc) toUpdate).setModerationStatus(ModerationStatus.UNMODERATED);
        }
      }
      ugcRepository.update(ugcId, toUpdate, false, false);
      final SocialEvent<T> event =
          new SocialEvent<>(
              toUpdate,
              SocialSecurityUtils.getCurrentProfile().getId().toString(),
              UGCEvent.UPDATE);
      event.setAttribute("baseUrl", calculateBaseUrl());
      reactor.notify(UGCEvent.UPDATE.getName(), Event.wrap(event));
      if (attributes != null && !attributes.isEmpty()) {
        toUpdate.getAttributes().putAll(attributes);
        // ToDo This should be one query, problem is with deep attributes !!
        setAttributes(toUpdate.getId().toString(), contextId, toUpdate.getAttributes());
        reactor.notify(UGCEvent.UPDATE_ATTRIBUTES, Event.wrap(attributes));
      }
      log.info("logging.ugc.updatedUgc", ugcId);
      return toUpdate;
    } catch (MongoDataException ex) {
      log.error("logging.ugc.unableToUpdateUgc", ex);
      throw new UGCException("Unable to removeWatcher UGC", ex);
    }
  }
예제 #2
0
  private void threadPoolDispatcher() {
    Boundary b = new Boundary();

    // Bind to a Selector using an anonymous object
    Tuple2<Selector, Object> anon = $();

    threadPoolReactor.on(anon.getT1(), b.bind(consumer, 3));

    threadPoolReactor.notify(anon.getT2(), Event.wrap(threadPoolReactor));
    threadPoolReactor.notify(anon.getT2(), Event.wrap(threadPoolReactor));
    threadPoolReactor.notify(anon.getT2(), Event.wrap(threadPoolReactor));

    b.await();
  }
예제 #3
0
 @Override
 @HasPermission(action = UGC_UPDATE, type = SocialPermission.class)
 public FileInfo updateAttachment(
     @SecuredObject final String ugcId,
     final String contextId,
     final String attachmentId,
     final InputStream newAttachment)
     throws UGCException, FileNotFoundException {
   if (!ObjectId.isValid(ugcId)) {
     throw new IllegalArgumentException("Given Ugc Id is not valid");
   }
   if (!ObjectId.isValid(attachmentId)) {
     throw new IllegalArgumentException("Given UGC Id is not valid");
   }
   try {
     T ugc = (T) ugcRepository.findUGC(contextId, ugcId);
     if (ugc == null) {
       throw new IllegalUgcException("Given UGC Id does not exists");
     }
     FileInfo oldInfo = ugcRepository.getFileInfo(attachmentId);
     ugc.getAttachments().remove(oldInfo);
     FileInfo newInfo =
         ugcRepository.updateFile(
             new ObjectId(attachmentId),
             newAttachment,
             oldInfo.getFileName(),
             oldInfo.getContentType(),
             true);
     ugc.getAttachments().add(newInfo);
     ugcRepository.update(ugcId, ugc);
     reactor.notify(
         UGCEvent.DELETE_ATTACHMENT.getName(),
         Event.wrap(new SocialEvent<>(ugcId, attachmentId, UGCEvent.DELETE_ATTACHMENT)));
     reactor.notify(
         UGCEvent.ADD_ATTACHMENT.getName(),
         Event.wrap(
             new SocialEvent<>(
                 ugcId, new InputStream[] {new CloseShieldInputStream(newAttachment)}),
             UGCEvent.ADD_ATTACHMENT));
     return newInfo;
   } catch (MongoDataException e) {
     log.error("logging.ugc.attachmentError");
     throw new UGCException("Unable to removeWatcher Attachment");
   } catch (FileExistsException e) {
     log.error("logging.ugc.attachmentNotFound", attachmentId);
     throw new UGCException("Unable to find attachment with given id", e);
   }
 }
예제 #4
0
 @Override
 @HasPermission(action = UGC_UPDATE, type = SocialPermission.class)
 public void removeAttachment(
     @SecuredObject final String ugcId, final String contextId, final String attachmentId)
     throws UGCException, FileNotFoundException {
   try {
     UGC ugc = ugcRepository.findUGC(contextId, ugcId);
     if (ugc == null) {
       throw new IllegalUgcException("UGC with given Id does not exist");
     }
     if (!ObjectId.isValid(attachmentId)) {
       throw new IllegalArgumentException("Given Attachment id is not valid");
     }
     ObjectId attachmentOid = new ObjectId(attachmentId);
     FileInfo info = ugcRepository.getFileInfo(attachmentOid);
     if (!info.getStoreName().startsWith(File.separator + contextId)) {
       throw new IllegalSocialQueryException(
           "Given Attachment does not belong to the given context");
     }
     ugc.getAttachments().remove(info);
     ugcRepository.deleteFile(attachmentOid);
     ugcRepository.update(ugcId, ugc);
     reactor.notify(
         UGCEvent.DELETE_ATTACHMENT.getName(),
         Event.wrap(new SocialEvent<>(ugcId, attachmentId, UGCEvent.DELETE_ATTACHMENT)));
   } catch (MongoDataException e) {
     log.error("logging.ugc.attachmentToRemove", e, attachmentId);
     throw new UGCException("Unable to save File to UGC");
   }
 }
  /**
   * Accept an image upload via POST and notify a Reactor that the image needs to be thumbnailed.
   * Asynchronously respond to the client when the thumbnailing has completed.
   *
   * @param channel the channel on which to send an HTTP response
   * @param thumbnail a reference to the shared thumbnail path
   * @param reactor the Reactor on which to publish events
   * @return a consumer to handle HTTP requests
   */
  public static Consumer<FullHttpRequest> thumbnailImage(
      NetChannel<FullHttpRequest, FullHttpResponse> channel,
      AtomicReference<Path> thumbnail,
      Reactor reactor) {
    return req -> {
      if (req.getMethod() != HttpMethod.POST) {
        channel.send(badRequest(req.getMethod() + " not supported for this URI"));
        return;
      }

      // write to a temp file
      Path imgIn = null;
      try {
        imgIn = readUpload(req.content());
      } catch (IOException e) {
        throw new IllegalStateException(e.getMessage(), e);
      }

      // Asynchronously thumbnail the image to 250px on the long side
      reactor.sendAndReceive(
          "thumbnail",
          Event.wrap(imgIn),
          ev -> {
            thumbnail.set(ev.getData());
            channel.send(redirect());
          });
    };
  }
예제 #6
0
 @Override
 @HasPermission(action = UGC_UPDATE, type = SocialPermission.class)
 public void setAttributes(
     @SecuredObject final String ugcId, final String contextId, final Map attributes)
     throws SocialException, UGCNotFound {
   log.debug("logging.ugc.addingAttributes", attributes, ugcId, contextId);
   try {
     T toUpdate = (T) ugcRepository.findUGC(contextId, ugcId);
     if (toUpdate == null) {
       throw new UGCNotFound("Unable to found ugc with id " + ugcId);
     }
     final Map attrs = toUpdate.getAttributes();
     attrs.putAll(attrs);
     ugcRepository.setAttributes(ugcId, contextId, attrs);
     final SocialEvent<T> event =
         new SocialEvent<T>(
             ugcId,
             attributes,
             SocialSecurityUtils.getCurrentProfile().getId().toString(),
             UGCEvent.UPDATE_ATTRIBUTES);
     event.setAttribute("baseUrl", calculateBaseUrl());
     reactor.notify(UGCEvent.UPDATE_ATTRIBUTES.getName(), Event.wrap(event));
   } catch (MongoDataException ex) {
     log.debug("logging.ugc.unableToAddAttrs", ex, attributes, ugcId, contextId);
     throw new UGCException("Unable to add Attributes to UGC", ex);
   }
 }
예제 #7
0
  public void publishJokes() throws InterruptedException {
    long start = System.currentTimeMillis();
    AtomicInteger counter = new AtomicInteger();
    for (int i = 0; i < numberOfJokes; i++) {
      reactor.notify("jokes", Event.wrap(counter.getAndIncrement()));
    }
    latch.await();
    long elapsed = System.currentTimeMillis() - start;

    System.out.println("Elapsed time: " + elapsed + "ms");
    System.out.println("Average time per joke: " + elapsed / numberOfJokes + "ms");
  }
  public static void main(String[] args) throws InterruptedException {
    final TradeServer server = new TradeServer();

    // Use a Reactor to dispatch events using the default Dispatcher
    Reactor reactor = new Reactor();

    // Create a single Selector for efficiency
    Selector trade = $("trade.execute");

    // For each Trade event, execute that on the server
    reactor.on(
        trade,
        new Consumer<Event<Trade>>() {
          @Override
          public void accept(Event<Trade> tradeEvent) {
            server.execute(tradeEvent.getData());

            // Since we're async, for this test, use a latch to tell when we're done
            latch.countDown();
          }
        });

    // Start a throughput timer
    startTimer();

    // Publish one event per trade
    for (int i = 0; i < totalTrades; i++) {
      // Pull next randomly-generated Trade from server
      Trade t = server.nextTrade();

      // Notify the Reactor the event is ready to be handled
      reactor.notify(trade, Fn.event(t));
    }

    // Stop throughput timer and output metrics
    endTimer();

    server.stop();
  }
예제 #9
0
  protected void postRun(Reactor reactor) throws InterruptedException {
    awaitConsumer();

    long end = System.currentTimeMillis();
    double elapsed = end - start;
    long throughput = Math.round((selectors * iterations) / (elapsed / 1000));

    log.info(
        reactor.getDispatcher().getClass().getSimpleName()
            + " throughput ("
            + ((long) elapsed)
            + "ms): "
            + throughput
            + "/sec");
  }
예제 #10
0
 public void close(@Nullable final Consumer<Boolean> onClose) {
   reactor.schedule(
       new Consumer<Void>() {
         @Override
         public void accept(Void v) {
           for (Registration<? extends NetChannel<IN, OUT>> reg : getChannels()) {
             if (null == reg) {
               continue;
             }
             doCloseChannel(reg.getObject());
           }
           getChannels().clear();
           doClose(onClose);
         }
       },
       null);
 }
예제 #11
0
  @Override
  @HasPermission(action = UGC_DELETE, type = SocialPermission.class)
  public boolean deleteUgc(final String ugcId, final String contextId) throws SocialException {
    log.debug("logging.ugc.deleteUgc", ugcId);
    try {
      ugcRepository.deleteUgc(ugcId, contextId);
      final SocialEvent<T> event =
          new SocialEvent<T>(
              ugcId, SocialSecurityUtils.getCurrentProfile().getId().toString(), UGCEvent.DELETE);
      event.setAttribute("baseUrl", calculateBaseUrl());

      reactor.notify(UGCEvent.DELETE.getName(), Event.wrap(event));
    } catch (MongoDataException ex) {
      log.error("logging.ugc.deleteUgcError", ex, ugcId, contextId);
      throw new UGCException("Unable to delete UGC", ex);
    }
    return false;
  }
예제 #12
0
  @Override
  @HasPermission(action = UGC_CREATE, type = SocialPermission.class)
  public UGC create(
      final String contextId,
      final String ugcParentId,
      final String targetId,
      final String textContent,
      final String subject,
      final Map attrs,
      final boolean isAnonymous)
      throws SocialException {
    log.debug("logging.ugc.creatingUgc", contextId, targetId, ugcParentId, subject, attrs);
    final UGC template = new UGC(subject, textContent, targetId);

    template.setAnonymousFlag(isAnonymous);
    T newUgc = (T) ugcFactory.newInstance(template);
    newUgc.setAttributes(attrs);
    try {
      if (ObjectId.isValid(ugcParentId)) {
        setupAncestors(newUgc, ugcParentId, contextId);

      } else {
        log.debug("logging.ugc.invalidParentId");
      }
      if (StringUtils.isBlank(contextId)) {
        throw new IllegalArgumentException("context cannot be null");
      }
      pipeline.processUgc(newUgc);
      ugcRepository.save(newUgc);

      final SocialEvent<T> event =
          new SocialEvent<>(
              newUgc, SocialSecurityUtils.getCurrentProfile().getId().toString(), UGCEvent.CREATE);
      event.setAttribute("baseUrl", calculateBaseUrl());
      reactor.notify(UGCEvent.CREATE.getName(), Event.wrap(event));
      setupAutoWatch(targetId, SocialSecurityUtils.getCurrentProfile(), contextId);
      log.info("logging.ugc.created", newUgc);
      return newUgc;
    } catch (MongoDataException ex) {
      log.error("logging.ugc.errorSaving", ex);
      throw new UGCException("Unable to Save UGC");
    }
  }
예제 #13
0
  @Override
  @HasPermission(action = UGC_UPDATE, type = SocialPermission.class)
  public FileInfo addAttachment(
      @SecuredObject final String ugcId,
      final String contextId,
      final InputStream attachment,
      final String fileName,
      final String contentType)
      throws FileExistsException, UGCException {
    String internalFileName =
        File.separator + contextId + File.separator + ugcId + File.separator + fileName;
    try {
      checkForVirus(attachment);
    } catch (IOException | VirusScannerException ex) {
      log.error("logging.ugc.errorScanVirus", ex);
      return null;
    }

    try {
      UGC ugc = ugcRepository.findUGC(contextId, ugcId);
      if (ugc == null) {
        throw new IllegalUgcException("UGC with given Id does not exist");
      }
      FileInfo info = ugcRepository.saveFile(attachment, internalFileName, contentType);
      try {
        info.setFileName(new URLCodec().decode(fileName));
      } catch (DecoderException e) {
        info.setFileName(fileName);
      }
      info.setAttribute("owner", ugcId);
      ugc.getAttachments().add(info);
      ugcRepository.update(ugcId, ugc);
      reactor.notify(
          UGCEvent.ADD_ATTACHMENT.getName(),
          Event.wrap(
              new SocialEvent<>(
                  ugcId, new InputStream[] {new CloseShieldInputStream(attachment)})));
      return info;
    } catch (MongoDataException e) {
      log.error("logging.ugc.unableToSaveAttachment", e, internalFileName);
      throw new UGCException("Unable to save File to UGC");
    }
  }
예제 #14
0
 @Override
 @HasPermission(action = UGC_UPDATE, type = SocialPermission.class)
 public void deleteAttribute(
     @SecuredObject final String ugcId, final String[] attributesName, final String contextId)
     throws SocialException {
   log.debug("logging.ugc.deleteAttributes", attributesName, ugcId);
   try {
     ugcRepository.deleteAttribute(ugcId, contextId, attributesName);
     final SocialEvent<T> event =
         new SocialEvent<T>(
             ugcId,
             SocialSecurityUtils.getCurrentProfile().getId().toString(),
             UGCEvent.DELETE_ATTRIBUTES);
     event.setAttribute("baseUrl", calculateBaseUrl());
     reactor.notify(UGCEvent.DELETE_ATTRIBUTES.getName(), Event.wrap(event));
   } catch (MongoDataException ex) {
     log.debug("logging.ugc.unableToDelAttrs", ex, attributesName, ugcId);
     throw new UGCException("Unable to delete attribute for ugc", ex);
   }
 }
예제 #15
0
  protected AbstractNetPeer(
      @Nonnull Environment env,
      @Nonnull Reactor reactor,
      @Nullable Codec<Buffer, IN, OUT> codec,
      @Nonnull Collection<Consumer<NetChannel<IN, OUT>>> consumers) {
    this.env = env;
    this.reactor = reactor;
    this.codec = codec;
    this.consumers = consumers;

    for (final Consumer<NetChannel<IN, OUT>> consumer : consumers) {
      reactor.on(
          open,
          new Consumer<Event<NetChannel<IN, OUT>>>() {
            @Override
            public void accept(Event<NetChannel<IN, OUT>> ev) {
              consumer.accept(ev.getData());
            }
          });
    }
  }
예제 #16
0
  private void multipleRingBufferDispatchers() {
    Boundary b = new Boundary();

    Reactor r1 = Reactors.reactor().env(env).dispatcher(Environment.RING_BUFFER).get();
    Reactor r2 = Reactors.reactor().env(env).dispatcher(Environment.RING_BUFFER).get();

    // Bind to a Selector using an anonymous object
    Tuple2<Selector, Object> anon = $();

    r1.on(anon.getT1(), b.bind(consumer, 3));
    r2.on(anon.getT1(), b.bind(consumer, 2));

    r1.notify(anon.getT2(), Event.wrap(r1));
    r1.notify(anon.getT2(), Event.wrap(r1));
    r1.notify(anon.getT2(), Event.wrap(r1));

    r2.notify(anon.getT2(), Event.wrap(r2));
    r2.notify(anon.getT2(), Event.wrap(r2));

    b.await();
  }
예제 #17
0
 public Promise<Boolean> close() {
   Deferred<Boolean, Promise<Boolean>> d = Promises.defer(env, reactor.getDispatcher());
   close(d);
   return d.compose();
 }
예제 #18
0
 /**
  * Notify this peer's consumers that the given channel has been closed.
  *
  * @param channel The channel that was closed.
  */
 protected void notifyClose(@Nonnull NetChannel<IN, OUT> channel) {
   reactor.notify(close.getObject(), Event.wrap(channel));
 }
예제 #19
0
 /**
  * Notify this client's consumers than a global error has occurred.
  *
  * @param error The error to notify.
  */
 protected void notifyError(@Nonnull Throwable error) {
   Assert.notNull(error, "Error cannot be null.");
   reactor.notify(error.getClass(), Event.wrap(error));
 }
예제 #20
0
/**
 * Abstract base class for {@code Codec Codecs} that perform serialization of objects. Optionally
 * handles writing class names so that an object that is serialized can be properly instantiated
 * with full type information on the other end.
 *
 * @author Jon Brisbin
 * @author Stephane Maldini
 */
public abstract class SerializationCodec<E, IN, OUT> extends BufferCodec<IN, OUT> {

  private final Logger log = Reactor.getLogger(getClass());
  private final Map<String, Class<IN>> types = new ConcurrentHashMap<String, Class<IN>>();
  private final E engine;
  private final boolean lengthFieldFraming;
  private final Codec<Buffer, IN, OUT> encoder;

  /**
   * Create a {@code SerializationCodec} using the given engine and specifying whether or not to
   * prepend a length field to frame the message.
   *
   * @param engine the engine which will perform the serialization
   * @param lengthFieldFraming {@code true} to prepend a length field, or {@code false} to skip
   */
  protected SerializationCodec(E engine, boolean lengthFieldFraming) {
    this.engine = engine;
    this.lengthFieldFraming = lengthFieldFraming;
    if (lengthFieldFraming) {
      this.encoder = new LengthFieldCodec<IN, OUT>(new DelegateCodec());
    } else {
      this.encoder = new DelegateCodec();
    }
  }

  @Override
  protected int canDecodeNext(Buffer buffer, Object context) {
    return buffer.remaining() > 0 ? buffer.limit() : -1;
  }

  @Override
  public Function<Buffer, IN> decoder(Consumer<IN> next) {
    if (lengthFieldFraming) {
      return new LengthFieldCodec<IN, OUT>(new DelegateCodec()).decoder(next);
    } else {
      return new DelegateCodec().decoder(next);
    }
  }

  @Override
  protected IN decodeNext(Buffer buffer, Object context) {
    try {
      Class<IN> clazz = readType(buffer);
      byte[] bytes = buffer.asBytes();
      buffer.position(buffer.limit());

      return deserializer(engine, clazz).apply(bytes);
    } catch (RuntimeException e) {
      if (log.isErrorEnabled()) {
        log.error("Could not decode " + buffer, e);
      }
      throw e;
    }
  }

  @Override
  public Buffer apply(OUT out) {
    return encoder.apply(out);
  }

  protected E getEngine() {
    return engine;
  }

  protected abstract Function<byte[], IN> deserializer(E engine, Class<IN> type);

  protected abstract Function<OUT, byte[]> serializer(E engine);

  private String readTypeName(Buffer buffer) {
    int len = buffer.readInt();
    if (buffer.remaining() <= len) {
      throw new IllegalArgumentException(
          "Incomplete buffer. Must contain "
              + len
              + " bytes, "
              + "but only "
              + buffer.remaining()
              + " were found.");
    }
    byte[] bytes = new byte[len];
    buffer.read(bytes);
    return new String(bytes);
  }

  private Buffer writeTypeName(Class<?> type, byte[] bytes) {
    String typeName = type.getName();
    int len = typeName.length();
    Buffer buffer = new Buffer(4 + len + bytes.length, true);
    return buffer.append(len).append(typeName).append(bytes).flip();
  }

  public Class<IN> readType(Buffer buffer) {
    String typeName = readTypeName(buffer);
    return getType(typeName);
  }

  @SuppressWarnings("unchecked")
  private Class<IN> getType(String name) {
    Class<IN> type = types.get(name);
    if (null == type) {
      try {
        type = (Class<IN>) Class.forName(name);
      } catch (ClassNotFoundException e) {
        throw new IllegalArgumentException(e.getMessage(), e);
      }
      types.put(name, type);
    }
    return type;
  }

  private class DelegateCodec extends Codec<Buffer, IN, OUT> {
    final Function<OUT, byte[]> fn = serializer(engine);

    @Override
    protected IN decodeNext(Buffer buffer, Object context) {
      return SerializationCodec.this.decodeNext(buffer, context);
    }

    @Override
    public Buffer apply(OUT o) {
      try {
        return writeTypeName(o.getClass(), fn.apply(o));
      } catch (RuntimeException e) {
        if (log.isErrorEnabled()) {
          log.error("Could not encode " + o, e);
        }
        throw e;
      }
    }
  }
}
  public static void main(String[] args) throws Exception {
    Environment env = new Environment();
    final TradeServer server = new TradeServer();

    // Use a Reactor to dispatch events using the high-speed Dispatcher
    final Reactor serverReactor = Reactors.reactor(env);

    // Create a single key and Selector for efficiency
    final Selector tradeExecute = Selectors.object("trade.execute");

    // For each Trade event, execute that on the server and notify connected clients
    // because each client that connects links to the serverReactor
    serverReactor.on(
        tradeExecute,
        (Event<Trade> ev) -> {
          server.execute(ev.getData());

          // Since we're async, for this test, use a latch to tell when we're done
          latch.countDown();
        });

    @SuppressWarnings("serial")
    WebSocketServlet wss =
        new WebSocketServlet() {
          @Override
          public void configure(WebSocketServletFactory factory) {
            factory.setCreator(
                (req, resp) ->
                    new WebSocketListener() {
                      AtomicLong counter = new AtomicLong();

                      @Override
                      public void onWebSocketBinary(byte[] payload, int offset, int len) {}

                      @Override
                      public void onWebSocketClose(int statusCode, String reason) {}

                      @Override
                      public void onWebSocketConnect(final Session session) {
                        LOG.info("Connected a websocket client: {}", session);

                        // Keep track of a rolling average
                        final AtomicReference<Float> avg = new AtomicReference<>(0f);

                        serverReactor.on(
                            tradeExecute,
                            (Event<Trade> ev) -> {
                              Trade t = ev.getData();
                              avg.set((avg.get() + t.getPrice()) / 2);

                              // Send a message every 1000th trade.
                              // Otherwise, we completely overwhelm the browser and network.
                              if (counter.incrementAndGet() % 1000 == 0) {
                                try {
                                  session
                                      .getRemote()
                                      .sendString(String.format("avg: %s", avg.get()));
                                } catch (IOException e) {
                                  if (!"Failed to write bytes".equals(e.getMessage())) {
                                    e.printStackTrace();
                                  }
                                }
                              }
                            });
                      }

                      @Override
                      public void onWebSocketError(Throwable cause) {}

                      @Override
                      public void onWebSocketText(String message) {}
                    });
          }
        };
    serve(wss);

    LOG.info(
        "Connect websocket clients now (waiting for 10 seconds).\n"
            + "Open websocket/src/main/webapp/ws.html in a browser...");
    Thread.sleep(10000);

    // Start a throughput timer
    startTimer();

    // Publish one event per trade
    for (int i = 0; i < totalTrades; i++) {
      // Pull next randomly-generated Trade from server
      Trade t = server.nextTrade();

      // Notify the Reactor the event is ready to be handled
      serverReactor.notify(tradeExecute.getObject(), Event.wrap(t));
    }

    // Stop throughput timer and output metrics
    endTimer();

    server.stop();
  }
/** @author Anatoly Kadyshev */
public final class EmbeddedMediaDriverManager {

  private static final Logger logger = Reactor.getLogger(EmbeddedMediaDriverManager.class);

  private static final EmbeddedMediaDriverManager INSTANCE = new EmbeddedMediaDriverManager();

  private final List<String> aeronDirNames = new ArrayList<>();

  private Thread shutdownHook;

  enum State {
    NOT_STARTED,

    STARTED,

    SHUTTING_DOWN
  }

  public static final long DEFAULT_RETRY_SHUTDOWN_MILLIS = 250;

  public static final long DEFAULT_SHUTDOWN_TIMEOUT_NS = TimeUnit.SECONDS.toNanos(10);

  private long retryShutdownMillis = DEFAULT_RETRY_SHUTDOWN_MILLIS;

  /** Timeout in nanos to wait until forcefully shutting down the embedded Media driver */
  private long shutdownTimeoutNs = DEFAULT_SHUTDOWN_TIMEOUT_NS;

  private MediaDriver driver;

  private int counter = 0;

  private Aeron aeron;

  private AeronCounters aeronCounters;

  private MediaDriver.Context driverContext;

  private State state = State.NOT_STARTED;

  /**
   * If true then the Media driver is shutdown when the last functionality which started it is
   * shutdown
   */
  private boolean shouldShutdownWhenNotUsed = true;

  /** If true then deletes previously created Aeron dirs upon JVM termination */
  private boolean deleteAeronDirsOnExit = false;

  private class RetryShutdownTask implements Runnable {

    private final long startNs;

    private final TimedScheduler timer;

    public RetryShutdownTask(TimedScheduler timer) {
      this.startNs = System.nanoTime();
      this.timer = timer;
    }

    @Override
    public void run() {
      if (canShutdownMediaDriver() || System.nanoTime() - startNs > shutdownTimeoutNs) {
        doShutdown();
      } else {
        timer.schedule(this, retryShutdownMillis, TimeUnit.MILLISECONDS);
      }
    }

    private boolean canShutdownMediaDriver() {
      final boolean canShutdownDriver[] = new boolean[] {true};
      aeronCounters.forEach(
          (id, label) -> {
            if (label.startsWith(AeronUtils.LABEL_PREFIX_SENDER_POS)
                || label.startsWith(AeronUtils.LABEL_PREFIX_SUBSCRIBER_POS)) {
              canShutdownDriver[0] = false;
            }
          });
      return canShutdownDriver[0];
    }
  }

  public static EmbeddedMediaDriverManager getInstance() {
    return INSTANCE;
  }

  public synchronized MediaDriver.Context getDriverContext() {
    if (driverContext == null) {
      driverContext = new MediaDriver.Context();
    }
    return driverContext;
  }

  public synchronized void launchDriver() {
    if (state == State.SHUTTING_DOWN) {
      throw new IllegalStateException("Manager is being shutdown");
    }

    if (driver == null) {
      driver = MediaDriver.launchEmbedded(getDriverContext());
      Aeron.Context ctx = new Aeron.Context();
      String aeronDirName = driver.aeronDirectoryName();
      ctx.aeronDirectoryName(aeronDirName);
      aeron = Aeron.connect(ctx);

      aeronCounters = new AeronCounters(aeronDirName);
      state = State.STARTED;

      aeronDirNames.add(aeronDirName);

      logger.info("Embedded media driver started");
    }
    counter++;
  }

  public synchronized void shutdownDriver() {
    counter = (int) Operators.subOrZero(counter, 1);
    if (counter == 0 && shouldShutdownWhenNotUsed) {
      shutdown();
    }
  }

  public synchronized Aeron getAeron() {
    return aeron;
  }

  public synchronized int getCounter() {
    return counter;
  }

  public synchronized void shutdown() {
    if (state != State.STARTED) {
      throw new IllegalStateException("Cannot shutdown manager in state: " + state);
    }
    state = State.SHUTTING_DOWN;

    counter = 0;

    if (driver != null) {
      aeron.close();

      TimedScheduler timer = Schedulers.timer();

      timer.schedule(new RetryShutdownTask(timer), retryShutdownMillis, TimeUnit.MILLISECONDS);
    }
  }

  /** Could result into JVM crashes when there is pending Aeron activity */
  private synchronized void doShutdown() {
    aeron = null;
    try {
      aeronCounters.shutdown();
    } catch (Throwable t) {
      logger.error("Failed to shutdown Aeron counters", t);
    }
    aeronCounters = null;

    CloseHelper.quietClose(driver);

    driverContext = null;
    driver = null;

    state = State.NOT_STARTED;

    setupShutdownHook();

    logger.info("Embedded media driver shutdown");
  }

  private void setupShutdownHook() {
    if (!deleteAeronDirsOnExit || shutdownHook != null) {
      return;
    }

    shutdownHook =
        new Thread() {

          @Override
          public void run() {
            synchronized (EmbeddedMediaDriverManager.this) {
              for (String aeronDirName : aeronDirNames) {
                try {
                  File dirFile = new File(aeronDirName);
                  IoUtil.delete(dirFile, false);
                } catch (Exception e) {
                  logger.error("Failed to delete Aeron directory: {}", aeronDirName);
                }
              }
            }
          }
        };

    Runtime.getRuntime().addShutdownHook(shutdownHook);
  }

  public synchronized boolean isTerminated() {
    return state == State.NOT_STARTED;
  }

  public void setShouldShutdownWhenNotUsed(boolean shouldShutdownWhenNotUsed) {
    this.shouldShutdownWhenNotUsed = shouldShutdownWhenNotUsed;
  }

  public void setDeleteAeronDirsOnExit(boolean deleteAeronDirsOnExit) {
    this.deleteAeronDirsOnExit = deleteAeronDirsOnExit;
  }

  public void setShutdownTimeoutNs(long shutdownTimeoutNs) {
    this.shutdownTimeoutNs = shutdownTimeoutNs;
  }
}
예제 #23
0
/**
 * Netty {@link io.netty.channel.ChannelInboundHandler} implementation that passes data to a Reactor
 * {@link Channel}.
 *
 * @author Stephane Maldini
 */
public class NettyChannelHandler<C extends NettyChannel> extends ChannelDuplexHandler
    implements Producer, Publisher<Object> {

  protected static final Logger log = Reactor.getLogger(NettyChannelHandler.class);

  protected final ChannelHandler<ByteBuf, ByteBuf, NettyChannel> handler;
  protected final ChannelBridge<C> bridgeFactory;
  protected final Flux<Object> input;

  final InboundSink inboundEmitter;

  public NettyChannelHandler(
      ChannelHandler<ByteBuf, ByteBuf, NettyChannel> handler,
      ChannelBridge<C> bridgeFactory,
      io.netty.channel.Channel ch) {
    this(handler, bridgeFactory, ch, null);
  }

  @SuppressWarnings("unchecked")
  public NettyChannelHandler(
      ChannelHandler<ByteBuf, ByteBuf, NettyChannel> handler,
      ChannelBridge<C> bridgeFactory,
      io.netty.channel.Channel ch,
      NettyChannelHandler parent) {
    this.handler = handler;
    if (parent == null) {
      this.inboundEmitter = new InboundSink(ch);
      // guard requests/cancel/subscribe
      this.input = Flux.from(this).subscribeOn(Schedulers.fromExecutor(ch.eventLoop()));
    } else {

      this.inboundEmitter = parent.inboundEmitter;
      this.input = parent.input;
    }
    this.bridgeFactory = bridgeFactory;
  }

  @Override
  public FluxSink<Object> downstream() {
    return inboundEmitter;
  }

  @Override
  public void channelActive(final ChannelHandlerContext ctx) throws Exception {
    super.channelActive(ctx);
    handler
        .apply(bridgeFactory.createChannelBridge(ctx.channel(), input))
        .subscribe(new CloseSubscriber(ctx));
  }

  @Override
  public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    try {
      inboundEmitter.complete();
      super.channelInactive(ctx);
    } catch (Throwable err) {
      Exceptions.throwIfFatal(err);
      inboundEmitter.fail(err);
    }
  }

  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    doRead(msg);
  }

  @SuppressWarnings("unchecked")
  protected final void doRead(Object msg) {
    if (msg == null) {
      return;
    }
    try {
      if (msg == Unpooled.EMPTY_BUFFER || msg instanceof EmptyByteBuf) {
        return;
      }
      inboundEmitter.next(msg);
    } catch (Throwable err) {
      Exceptions.throwIfFatal(err);
      inboundEmitter.fail(err);
    }
  }

  @Override
  public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    if (inboundEmitter.requested != 0L) {
      ctx.read();
    } else {
      if (log.isDebugEnabled()) {
        log.debug("Pausing read due to lack of request");
      }
    }
    ctx.fireChannelReadComplete();
  }

  @Override
  public void write(final ChannelHandlerContext ctx, Object msg, final ChannelPromise promise)
      throws Exception {
    if (msg instanceof ChannelWriter) {
      @SuppressWarnings("unchecked")
      ChannelWriter dataWriter = (ChannelWriter) msg;
      if (dataWriter.flushMode == FlushMode.MANUAL_COMPLETE) {
        dataWriter.writeStream.subscribe(new FlushOnTerminateSubscriber(ctx, promise));
      } else {
        dataWriter.writeStream.subscribe(new FlushOnEachSubscriber(ctx, promise));
      }
    } else {
      super.write(ctx, msg, promise);
    }
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable err) throws Exception {
    Exceptions.throwIfFatal(err);
    inboundEmitter.fail(err);
  }

  protected ChannelFuture doOnWrite(Object data, ChannelHandlerContext ctx) {
    if (Unpooled.EMPTY_BUFFER != data) {
      return ctx.channel().write(data);
    }
    return null;
  }

  protected void doOnTerminate(
      ChannelHandlerContext ctx,
      ChannelFuture last,
      final ChannelPromise promise,
      final Throwable exception) {
    if (ctx.channel().isOpen()) {
      ChannelFutureListener listener =
          future -> {
            if (exception != null) {
              promise.tryFailure(exception);
            } else if (future.isSuccess()) {
              promise.trySuccess();
            } else {
              promise.tryFailure(future.cause());
            }
          };

      if (last != null) {
        ctx.flush();
        last.addListener(listener);
      } else {
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(listener);
      }
    } else {
      if (exception != null) {
        promise.tryFailure(exception);
      } else {
        promise.trySuccess();
      }
    }
  }

  /** */
  public enum FlushMode {
    AUTO_EACH,
    AUTO_LOOP,
    MANUAL_COMPLETE,
    MANUAL_BOUNDARY
  }

  /** */
  public static final class ChannelWriter {

    final Publisher<?> writeStream;
    final FlushMode flushMode;

    public ChannelWriter(Publisher<?> writeStream, FlushMode flushMode) {
      this.writeStream = writeStream;
      this.flushMode = flushMode;
    }
  }

  static final class CloseSubscriber implements Subscriber<Void> {

    private final ChannelHandlerContext ctx;

    public CloseSubscriber(ChannelHandlerContext ctx) {
      this.ctx = ctx;
    }

    @Override
    public void onSubscribe(Subscription s) {
      s.request(Long.MAX_VALUE);
    }

    @Override
    public void onNext(Void aVoid) {}

    @Override
    public void onError(Throwable t) {
      if (t instanceof IOException && t.getMessage().contains("Broken pipe")) {
        if (log.isDebugEnabled()) {
          log.debug("Connection closed remotely", t);
        }
        return;
      }

      log.error("Error processing connection. Closing the channel.", t);

      ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public void onComplete() {
      ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    }
  }

  final class FlushOnTerminateSubscriber
      implements Subscriber<Object>, ChannelFutureListener, Loopback {

    private final ChannelHandlerContext ctx;
    private final ChannelPromise promise;
    ChannelFuture lastWrite;
    Subscription subscription;

    public FlushOnTerminateSubscriber(ChannelHandlerContext ctx, ChannelPromise promise) {
      this.ctx = ctx;
      this.promise = promise;
    }

    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
      if (log.isDebugEnabled()) {
        log.debug("Cancel connection");
      }
      if (subscription != null) {
        subscription.cancel();
      }
      subscription = null;
    }

    @Override
    public Object connectedInput() {
      return NettyChannelHandler.this;
    }

    @Override
    public void onSubscribe(final Subscription s) {
      if (Operators.validate(subscription, s)) {
        this.subscription = s;

        ctx.channel().closeFuture().addListener(this);

        s.request(Long.MAX_VALUE);
      }
    }

    @Override
    public void onNext(final Object w) {
      if (w == null) {
        throw Exceptions.argumentIsNullException();
      }
      if (subscription == null) {
        throw Exceptions.failWithCancel();
      }
      try {
        ChannelFuture cf = doOnWrite(w, ctx);
        lastWrite = cf;
        if (cf != null && log.isDebugEnabled()) {
          cf.addListener(
              new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                  if (!future.isSuccess()) {
                    log.error("write error :" + w, future.cause());
                    if (ByteBuf.class.isAssignableFrom(w.getClass())) {
                      ((ByteBuf) w).resetReaderIndex();
                    }
                  }
                }
              });
        }
      } catch (Throwable t) {
        log.error("Write error for " + w, t);
        onError(t);
      }
    }

    @Override
    public void onError(Throwable t) {
      if (t == null) {
        throw Exceptions.argumentIsNullException();
      }
      if (subscription == null) {
        throw new IllegalStateException("already flushed", t);
      }
      log.error("Write error", t);
      subscription = null;
      ctx.channel().closeFuture().removeListener(this);
      doOnTerminate(ctx, lastWrite, promise, t);
    }

    @Override
    public void onComplete() {
      if (subscription == null) {
        throw new IllegalStateException("already flushed");
      }
      subscription = null;
      ctx.channel().closeFuture().removeListener(this);
      doOnTerminate(ctx, lastWrite, promise, null);
    }
  }

  final class FlushOnEachSubscriber
      implements Subscriber<Object>, ChannelFutureListener, Loopback, Trackable, Receiver {

    private final ChannelHandlerContext ctx;
    private final ChannelPromise promise;

    private Subscription subscription;

    private final ChannelFutureListener writeListener =
        new ChannelFutureListener() {

          @Override
          public void operationComplete(ChannelFuture future) throws Exception {
            if (!future.isSuccess()) {
              promise.tryFailure(future.cause());
              if (log.isDebugEnabled()) {
                log.debug("Write error", future.cause());
              }
              return;
            }
            if (subscription != null) {
              subscription.request(1L);
            }
          }
        };

    public FlushOnEachSubscriber(ChannelHandlerContext ctx, ChannelPromise promise) {
      this.ctx = ctx;
      this.promise = promise;
    }

    @Override
    public boolean isCancelled() {
      return !ctx.channel().isOpen();
    }

    @Override
    public boolean isStarted() {
      return subscription != null;
    }

    @Override
    public boolean isTerminated() {
      return !ctx.channel().isOpen();
    }

    @Override
    public Object connectedInput() {
      return NettyChannelHandler.this;
    }

    @Override
    public void onSubscribe(final Subscription s) {
      if (Operators.validate(subscription, s)) {
        subscription = s;

        ctx.channel().closeFuture().addListener(this);

        s.request(1L);
      }
    }

    @Override
    public void onNext(Object w) {
      if (w == null) {
        throw Exceptions.argumentIsNullException();
      }
      if (subscription == null) {
        throw Exceptions.failWithCancel();
      }
      try {
        ChannelFuture cf = doOnWrite(w, ctx);
        if (cf != null) {
          cf.addListener(writeListener);
        }
        ctx.flush();
      } catch (Throwable t) {
        log.error("Write error for " + w, t);
        onError(t);
        throw Exceptions.failWithCancel();
      }
    }

    @Override
    public void onError(Throwable t) {
      if (t == null) {
        throw Exceptions.argumentIsNullException();
      }
      if (subscription == null) {
        throw new IllegalStateException("already flushed", t);
      }
      log.error("Write error", t);
      subscription = null;
      ctx.channel().closeFuture().removeListener(this);
      doOnTerminate(ctx, null, promise, t);
    }

    @Override
    public void onComplete() {
      if (subscription == null) {
        throw new IllegalStateException("already flushed");
      }
      subscription = null;
      if (log.isDebugEnabled()) {
        log.debug("Flush Connection");
      }
      ctx.channel().closeFuture().removeListener(this);

      doOnTerminate(ctx, null, promise, null);
    }

    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
      if (log.isDebugEnabled()) {
        log.debug("Cancel connection");
      }
      if (subscription != null) {
        subscription.cancel();
      }
      subscription = null;
    }

    @Override
    public Object upstream() {
      return subscription;
    }
  }

  public ChannelHandler<ByteBuf, ByteBuf, NettyChannel> getHandler() {
    return handler;
  }

  @Override
  public void subscribe(Subscriber<? super Object> s) {
    if (log.isDebugEnabled()) {
      log.debug(
          "Subscribing inbound receiver [pending: "
              + ""
              + inboundEmitter.getPending()
              + ", done: "
              + inboundEmitter.done
              + "]");
    }
    if (inboundEmitter.actual == null) {
      if (inboundEmitter.done) {
        if (inboundEmitter.error != null) {
          Operators.error(s, inboundEmitter.error);
          return;
        } else if (inboundEmitter.getPending() == 0) {
          Operators.complete(s);
          return;
        }
      }

      inboundEmitter.init(s);
      s.onSubscribe(inboundEmitter);
    } else {
      Operators.error(
          s, new IllegalStateException("Only one connection receive subscriber allowed."));
    }
  }

  static final class InboundSink
      implements FluxSink<Object>, Trackable, Cancellation, Subscription, Producer {

    final io.netty.channel.Channel ch;

    /** guarded by event loop */
    Subscriber<? super Object> actual;

    boolean caughtUp;
    Queue<Object> queue;
    boolean done;
    Throwable error;
    long requested;
    int wip;

    volatile Cancellation cancel;

    @SuppressWarnings("rawtypes")
    static final AtomicReferenceFieldUpdater<InboundSink, Cancellation> CANCEL =
        AtomicReferenceFieldUpdater.newUpdater(InboundSink.class, Cancellation.class, "cancel");

    static final Cancellation CANCELLED = () -> {};

    public InboundSink(io.netty.channel.Channel channel) {
      this.ch = channel;
      CANCEL.lazySet(this, this);
    }

    void init(Subscriber<? super Object> s) {
      actual = s;
      CANCEL.lazySet(this, this);
      wip = 0;
    }

    @Override
    public void next(Object value) {
      if (value == null) {
        fail(new NullPointerException("value is null"));
        return;
      }
      if (done) {
        Exceptions.onNextDropped(value);
        return;
      }
      if (caughtUp && actual != null) {
        try {
          actual.onNext(value);
        } finally {
          ch.read();
          ReferenceCountUtil.release(value);
        }

      } else {
        Queue<Object> q = queue;
        if (q == null) {
          q = QueueSupplier.unbounded().get();
          queue = q;
        }
        q.offer(value);
        if (drain()) {
          caughtUp = true;
        }
      }
    }

    @Override
    public void fail(Throwable error) {
      if (error == null) {
        error = new NullPointerException("error is null");
      }
      if (isCancelled() || done) {
        Exceptions.onErrorDropped(error);
        return;
      }
      done = true;
      if (caughtUp && actual != null) {
        actual.onError(error);
      } else {
        this.error = error;
        done = true;
        drain();
      }
    }

    @Override
    public boolean isCancelled() {
      return cancel == CANCELLED;
    }

    @Override
    public void complete() {
      if (isCancelled() || done) {
        return;
      }
      done = true;
      if (caughtUp && actual != null) {
        actual.onComplete();
      }
      drain();
    }

    boolean drain() {
      if (wip++ != 0) {
        return false;
      }

      int missed = 1;

      for (; ; ) {
        final Queue<Object> q = queue;
        final Subscriber<? super Object> a = actual;

        if (a == null) {
          return false;
        }

        long r = requested;
        long e = 0L;

        while (e != r) {
          if (isCancelled()) {
            return false;
          }

          boolean d = done;
          Object v = q != null ? q.poll() : null;
          boolean empty = v == null;

          if (d && empty) {
            cancelResource();
            if (q != null) {
              q.clear();
            }
            Throwable ex = error;
            if (ex != null) {
              a.onError(ex);
            } else {
              a.onComplete();
            }
            return false;
          }

          if (empty) {
            ch.read();
            break;
          }

          try {
            a.onNext(v);
          } finally {
            ReferenceCountUtil.release(v);
            ch.read();
          }

          e++;
        }

        if (e == r) {
          if (isCancelled()) {
            return false;
          }

          if (done && (q == null || q.isEmpty())) {
            cancelResource();
            if (q != null) {
              q.clear();
            }
            Throwable ex = error;
            if (ex != null) {
              a.onError(ex);
            } else {
              a.onComplete();
            }
            return false;
          }
        }

        if (e != 0L) {
          if (r != Long.MAX_VALUE) {
            if ((requested -= e) > 0L) {
              ch.read();
            }
          }
        }

        missed = (wip = wip - missed);
        if (missed == 0) {
          if (r == Long.MAX_VALUE) {
            ch.config().setAutoRead(true);
            ch.read();
            return true;
          }
          return false;
        }
      }
    }

    @Override
    public void setCancellation(Cancellation c) {
      if (!CANCEL.compareAndSet(this, null, c)) {
        if (cancel != CANCELLED && c != null) {
          c.dispose();
        }
      }
    }

    @Override
    public void request(long n) {
      if (Operators.validate(n)) {
        this.requested = Operators.addCap(requested, n);
        drain();
      }
    }

    void cancelResource() {
      Cancellation c = cancel;
      if (c != CANCELLED) {
        c = CANCEL.getAndSet(this, CANCELLED);
        if (c != null && c != CANCELLED) {
          requested = 0L;
          c.dispose();
        }
      }
    }

    @Override
    public void cancel() {
      cancelResource();

      if (wip++ == 0) {
        Queue<Object> q = queue;
        if (q != null) {
          q.clear();
        }
      }
    }

    @Override
    public long requestedFromDownstream() {
      return requested;
    }

    @Override
    public long getCapacity() {
      return Long.MAX_VALUE;
    }

    @Override
    public long getPending() {
      return queue != null ? queue.size() : 0;
    }

    @Override
    public Throwable getError() {
      return error;
    }

    @Override
    public Object downstream() {
      return actual;
    }

    void dereference() {
      actual = null;
    }

    @Override
    public void dispose() {
      if (ch.eventLoop().inEventLoop()) {
        dereference();
      } else {
        ch.eventLoop().execute(this::dereference);
      }

      ch.config().setAutoRead(false);
    }
  }
}
예제 #24
0
 /**
  * Notify this peer's consumers that the channel has been opened.
  *
  * @param channel The channel that was opened.
  */
 protected void notifyOpen(@Nonnull NetChannel<IN, OUT> channel) {
   reactor.notify(open.getObject(), Event.wrap(channel));
 }