Package ch.bind.philib.msg.vm

Source Code of ch.bind.philib.msg.vm.PubSubVM$Forwarder

/*
* Copyright (c) 2013 Philipp Meinen <philipp@bind.ch>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
* THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package ch.bind.philib.msg.vm;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.bind.philib.lang.ExceptionUtil;
import ch.bind.philib.msg.MessageHandler;
import ch.bind.philib.msg.Subscription;
import ch.bind.philib.util.CowSet;
import ch.bind.philib.validation.Validation;

public final class PubSubVM implements PubSub {

  private static final Logger LOG = LoggerFactory.getLogger(PubSubVM.class);

  private final ReadWriteLock rwlock = new ReentrantReadWriteLock();

  private final Lock rlock = rwlock.readLock();

  private final Lock wlock = rwlock.writeLock();

  private final Map<String, Channel> channels = new HashMap<String, Channel>();

  private final ExecutorService executorService;

  /**
   * Creates a {@code DefaultTinyPubSub} which publishes messages through the provided {@code ExecutorService}.
   */
  public PubSubVM(ExecutorService executorService) {
    Validation.notNull(executorService);
    this.executorService = executorService;
  }

  @Override
  public Subscription subscribe(String channelName, MessageHandler handler) {
    Validation.notNull(handler);
    wlock.lock();
    try {
      Channel chan = getChannel(channelName);
      if (chan == null) {
        chan = new Channel(channelName);
        channels.put(channelName, chan);
      }
      Sub sub = chan.subscribe(handler);
      if (sub == null) {
        throw new IllegalArgumentException("double registration for channel='" + channelName + "' and handler: " + handler);
      }
      return sub;
    } finally {
      wlock.unlock();
    }
  }

  @Override
  public Subscription forward(String fromChannelName, String toChannelName) {
    return forward(this, fromChannelName, toChannelName);
  }

  @Override
  public Subscription forward(PubSub pubsub, String fromChannelName, String toChannelName) {
    Validation.notNullOrEmpty(fromChannelName);
    Validation.notNullOrEmpty(toChannelName);
    MessageHandler handler = new Forwarder(toChannelName, pubsub);
    return subscribe(fromChannelName, handler);
  }

  private void unsubscribe(Channel channel, Sub sub) {
    if (!channel.subs.remove(sub)) {
      return;
    }
    if (channel.subs.isEmpty()) {
      wlock.lock();
      try {
        if (channel.subs.isEmpty()) {
          channels.remove(channel.name);
        }
      } finally {
        wlock.unlock();
      }
    }
  }

  @Override
  public void publishSync(String channelName, Object message) {
    Validation.notNull(message);
    Channel chan = rlockedGetChannel(channelName);
    if (chan != null) {
      chan.publishSync(message);
    }
  }

  @Override
  public void publishAsync(String channelName, Object message) {
    Validation.notNull(message);
    Channel chan = rlockedGetChannel(channelName);
    if (chan != null) {
      chan.publishAsync(message);
    }
  }

  private Channel rlockedGetChannel(String channelName) {
    rlock.lock();
    try {
      return getChannel(channelName);
    } finally {
      rlock.unlock();
    }
  }

  private Channel getChannel(String channelName) {
    // fast path without channel null-or-empty check
    Channel chan = channels.get(channelName);
    if (chan == null) {
      // channel does not exist, verify that the supplied name is valid
      Validation.notNullOrEmpty(channelName);
    }
    return chan;
  }

  @Override
  public Map<String, Integer> activeChannels() {
    Map<String, Integer> rv = null;
    for (Entry<String, Channel> e : channels.entrySet()) {
      String name = e.getKey();
      Channel c = e.getValue();
      int num = c.subs.size();
      if (num > 0) {
        if (rv == null) {
          rv = new HashMap<String, Integer>();
        }
        rv.put(name, num);
      }
    }
    return rv == null ? Collections.<String, Integer> emptyMap() : rv;
  }

  private final class Channel {

    private final String name;

    private final CowSet<Sub> subs = new CowSet<Sub>(Sub.class);

    Channel(String name) {
      this.name = name;
    }

    Sub subscribe(MessageHandler handler) {
      Sub sub = new Sub(this, handler);
      if (subs.add(sub)) {
        return sub;
      }
      return null;
    }

    void publishSync(Object message) {
      publishMessage(this, message);
    }

    void publishAsync(Object message) {
      AsyncPublisher pub = new AsyncPublisher(this, message);
      executorService.execute(pub);
    }
  }

  private static final AtomicIntegerFieldUpdater<Sub> SUB_ACTIVE_UPDATER = AtomicIntegerFieldUpdater.newUpdater(Sub.class, "active");

  private final class Sub implements Subscription {

    private final Channel channel;

    private final MessageHandler handler;

    volatile int active = 1;

    public Sub(Channel channel, MessageHandler handler) {
      this.channel = channel;
      this.handler = handler;
    }

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

    @Override
    public void cancel() {
      if (SUB_ACTIVE_UPDATER.compareAndSet(this, 1, 0)) {
        PubSubVM.this.unsubscribe(channel, this);
      }
    }

    @Override
    public boolean isActive() {
      return active == 1;
    }

    @Override
    public String toString() {
      return "Subscription[active=" + isActive() + ", channel=" + getChannelName() + "]";
    }

    @Override
    public int hashCode() {
      // forward to the MessageHandler so that the CowSet reflects that reference
      return System.identityHashCode(handler);
    }

    @Override
    public boolean equals(Object obj) {
      // forward to the MessageHandler so that the CowSet reflects that reference
      if (obj == this) {
        return true;
      }
      if (obj instanceof Sub) {
        Sub o = (Sub) obj;
        return handler == o.handler;
      }
      return false;
    }
  }

  private static void publishMessage(Channel channel, Object message) {
    final Sub[] subs = channel.subs.getView();
    final String channelName = channel.name;
    for (Sub sub : subs) {
      if (sub.isActive()) {
        try {
          sub.handler.handleMessage(channelName, message);
        } catch (Exception e) {
          LOG.error("MessageHandler failed: " + ExceptionUtil.buildMessageChain(e));
        }
      }
    }
  }

  private final class AsyncPublisher implements Runnable {

    private final Channel channel;

    private final Object message;

    public AsyncPublisher(Channel channel, Object message) {
      this.channel = channel;
      this.message = message;
    }

    @Override
    public void run() {
      PubSubVM.publishMessage(channel, message);
    }
  }

  private static final class Forwarder implements MessageHandler {

    private final String to;

    private final PubSub pubsub;

    Forwarder(String to, PubSub pubsub) {
      this.to = to;
      this.pubsub = pubsub;
    }

    @Override
    public void handleMessage(String channelName, Object message) {
      pubsub.publishSync(to, message);
    }
  }
}
TOP

Related Classes of ch.bind.philib.msg.vm.PubSubVM$Forwarder

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.