Package com.genesys.wsclient

Source Code of com.genesys.wsclient.GenesysEventReceiver$Setup

package com.genesys.wsclient;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicReference;

import org.cometd.bayeux.Message;
import org.cometd.bayeux.client.ClientSessionChannel;
import org.cometd.bayeux.client.ClientSessionChannel.MessageListener;
import org.cometd.client.BayeuxClient;
import org.cometd.client.transport.LongPollingTransport;
import org.cometd.websocket.client.WebSocketTransport;
import org.eclipse.jetty.client.ContentExchange;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.websocket.WebSocketClientFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.genesys.wsclient.impl.Authentication;
import com.genesys.wsclient.impl.CookieSession;
import com.genesys.wsclient.impl.Jetty769HttpRequest;
import com.genesys.wsclient.impl.Jetty769Util;
import com.genesys.wsclient.impl.JsonUtil;

/**
* Use this class in order to receive Genesys Web Services events.
*
* <p>Subscriptions to events are re-subscribed on every reconnection to the server.
*
* <p>In order to create an instance of this class, call {@link GenesysClient#setupEventReceiver()},
* setup its parameters, and call {@link Setup#create()}.
*
* <p>This is an example of a minimal setup:
* <pre>
* GenesysClient client = ...;
* GenesysEventReceiver eventReceiver = client.setupEventReceiver()
*   .eventExecutor(eventExecutor)
*   .create();
* </pre>
*
* <p>This class is thread-safe.
*/
//TODO Upgrade to cometd-java-client 3.0.0. Currently in beta2.
//(This implies upgrading the Jetty Client, so there are more changes to do than just this class)
public class GenesysEventReceiver implements AutoCloseable {
   
  private static final Logger LOG = LoggerFactory.getLogger(GenesysEventReceiver.class);
  private static final Logger LOG_EVENT;
  private static final Logger LOG_EVENT_CONTENT_RAW;
  private static final Logger LOG_EVENT_CONTENT_PRETTY;
  private static final Logger LOG_EVENT_TRANSPORT;
  private static final Logger LOG_EVENT_TRANSPORT_HEADERS;
 
  static {
    String packageName = GenesysEventReceiver.class.getPackage().getName();
    LOG_EVENT = LoggerFactory.getLogger(packageName + ".EVENT");
    LOG_EVENT_CONTENT_RAW = LoggerFactory.getLogger(packageName + ".EVENT.CONTENT.RAW");
    LOG_EVENT_CONTENT_PRETTY = LoggerFactory.getLogger(packageName + ".EVENT.CONTENT.PRETTY");
    LOG_EVENT_TRANSPORT = LoggerFactory.getLogger(packageName + ".EVENT.TRANSPORT");
    LOG_EVENT_TRANSPORT_HEADERS = LoggerFactory.getLogger(packageName + ".EVENT.TRANSPORT.HEADERS");
  }
 
  private final BayeuxClient bayeuxClient;
  private final Executor eventExecutor;
  private final List<Subscription> subscriptions = new CopyOnWriteArrayList<>();
 
  /** Synchronize on bayeuxClient for access. */
  private boolean isConnected;

  private AtomicReference<Thread> subscriberLoopThread = new AtomicReference<>();

  protected GenesysEventReceiver(Setup builder) {
    this.bayeuxClient = createBayeuxClient(builder);
    this.eventExecutor = builder.eventExecutor;
  }
 
  private static BayeuxClient createBayeuxClient(Setup builder) {
    final Authentication authentication = builder.authentication;
   
    HashMap<String, Object> longPollingOptions = new HashMap<>();   
    LongPollingTransport longPollingTransport = new LongPollingTransport(longPollingOptions, builder.client.httpClient) {

      @Override protected void customize(ContentExchange exchange) {
        super.customize(exchange);
        HttpRequest request = new Jetty769HttpRequest(exchange);
        authentication.setupRequest(request);
        logRequest(exchange);
      }
     
      @Override protected void debug(String message, Object... args) {
        LOG_EVENT_TRANSPORT.debug(message, args);
      }
     
    };

    // Transports are set the cookieProvider *after* creating the bayeuxClient, because
    // BayeuxClient invariably sets its own cookieProvider inside the constructor.
    BayeuxClient bayeuxClient;   
    if (builder.webSocketEnabled) {
      WebSocketTransport webSocketTransport = createWebSocketTransport();
      bayeuxClient = new BayeuxClient(builder.client.serverUri + "/api/v2/notifications", webSocketTransport, longPollingTransport);
      webSocketTransport.setCookieProvider(builder.cookieSession.getCookieProvider());
    } else {
      bayeuxClient = new BayeuxClient(builder.client.serverUri + "/api/v2/notifications", longPollingTransport);
    }
    longPollingTransport.setCookieProvider(builder.cookieSession.getCookieProvider());
   
    return bayeuxClient;
  }

  private static WebSocketTransport createWebSocketTransport() {
    HashMap<String, Object> webSocketOptions = new HashMap<>()
    WebSocketClientFactory webSocketClientFactory = new WebSocketClientFactory();
    try {
      webSocketClientFactory.start();
    } catch (Exception e) {
      // Can't happen as WebSocketClientFactory.start() doesn't throw exceptions.
      throw new AssertionError(e);
    }
    ScheduledExecutorService scheduler = null;
    WebSocketTransport webSocketTransport = new WebSocketTransport(webSocketOptions, webSocketClientFactory, scheduler);
    return webSocketTransport;
  }
 
  /** Starts communication with the server. */
  public void open() {
    Thread thread = new Thread(new Runnable() {
      @Override public void run() {
        subscriberLoop();
      }     
    });
    subscriberLoopThread.set(thread);
    thread.start();
    bayeuxClient.handshake();
  }
 
  /**
   * Stops communication with the server. This method can be used
   * to dispose of this instance resources. However, {@link #open()} can
   * be used to start the communication again, and continue using the
   * same list of subscriptions.
   */
  @Override
  public void close() {
    subscriberLoopThread.get().interrupt();
    bayeuxClient.disconnect();
  }
 
  private void subscriberLoop() {
    try {
      while (!Thread.currentThread().isInterrupted()) {
        // It has been preferred to use Object.wait() instead of
        // BayeuxClient.waitFor(), because wait is interruptible.
        // This way this loop will be interrupted immediately on close.
        synchronized (bayeuxClient) {
          while (isConnected == bayeuxClient.isConnected())
            bayeuxClient.wait();
         
          isConnected = bayeuxClient.isConnected();
        }
       
        if (isConnected) {
          onConnected();
        }
      }
    } catch (InterruptedException e) {
      LOG.debug("Subscriber thread stopped");
    }
  }
 
  private void onConnected() {
    LOG.debug("Resubscribing all subscriptions");
    for (Subscription subscription : subscriptions) {
      bayeuxClient.getChannel(subscription.channel).subscribe(subscription.bayeuxListener);
    }
  }
 
  /**
   * Subscribes to a channel for receiving events.
   *
   * @return An EventSubscription instance that can be used to unsubscribe.
   */
  public EventSubscription subscribe(final String channel, final GenesysEventListener listener) {
    final MessageListener bayeuxListener = new MessageListener() {
      @Override public void onMessage(ClientSessionChannel channel, final Message message) {
        LOG_EVENT.debug("Received event on channel: " + message.getChannel());
        LOG_EVENT_CONTENT_RAW.debug("Content: " + message.getJSON());
        if (LOG_EVENT_CONTENT_PRETTY.isDebugEnabled())
          LOG_EVENT_CONTENT_PRETTY.debug("Content:\n" + JsonUtil.prettify(message.getJSON()));

        eventExecutor.execute(new Runnable() {
          @Override
          public void run() {
            try {
              listener.eventReceived(new GenesysEvent(message));
            } catch (Throwable t) {
              LOG.error("Exception handling event", t);
              throw t;
            }
          }
        });
      }
    };
   
    return new Subscription(channel, bayeuxListener);
  }
 
  /**
   * Subscribes to all channels using <code>"/**"</code>.
   *
   * @see #subscribe(String, GenesysEventListener)
   */
  public EventSubscription subscribeAll(GenesysEventListener listener) {
    return subscribe("/**", listener);
  }
 
  private class Subscription implements EventSubscription {
    private final String channel;
    private final MessageListener bayeuxListener;
   
    public Subscription(String channel, MessageListener bayeuxListener) {
      this.channel = channel;
      this.bayeuxListener = bayeuxListener;
     
      subscriptions.add(this);
     
      synchronized (bayeuxClient) {
        if (isConnected) {
          bayeuxClient.getChannel(channel).subscribe(bayeuxListener);
        }
      }
    }
   
    @Override
    public void unsubscribe() {
      subscriptions.remove(this);
     
      synchronized (bayeuxClient) {
        if (isConnected) {
          bayeuxClient.getChannel(channel).unsubscribe(bayeuxListener);
        }
      }
    }
  }
 
  private static void logRequest(HttpExchange exchange) {
    String content;
    try {
      content = new String(exchange.getRequestContent().array(), "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
   
    LOG_EVENT_TRANSPORT.debug(
      "Comet request to server: " + exchange.getAddress() +
      ", " + exchange.getMethod() + " " + exchange.getRequestURI() +
      ", content: " + content);
   
    Jetty769Util.logHeaders(LOG_EVENT_TRANSPORT_HEADERS, exchange.getRequestFields());
  }
 
  /**
   * <p>The {@link Setup} class is not thread-safe. Therefore always do the whole setup
   * and creation of a {@link GenesysEventReceiver} in the same thread, or use according
   * multi-threading techniques.
   */
  public static class Setup {
    private final GenesysClient client;
    private final CookieSession cookieSession;
    private final Authentication authentication;
    private Executor eventExecutor;
    private boolean webSocketEnabled;

    protected Setup(GenesysClient client,
        CookieSession session, Authentication authentication) {
      this.client = client;
      this.cookieSession = session;
      this.authentication = authentication;
     
      String webSocketProperty = System.getProperty("com.genesys.wsclient.websocket");
      this.webSocketEnabled = webSocketProperty == null
          ? true
          : Boolean.parseBoolean(webSocketProperty);
    }
   
    public GenesysEventReceiver create() {
      if (eventExecutor == null) {
        eventExecutor = client.asyncExecutor;
        if (eventExecutor == null) {
          throw new IllegalStateException("eventExecutor is mandatory");
        }
      }
      return new GenesysEventReceiver(this);
    }

    /**
     * (Mandatory if GenesysClient asyncExecutor not set) Executor for handling events received.
     */
    public Setup eventExecutor(Executor eventExecutor) {
      this.eventExecutor = eventExecutor;
      return this;
    }
   
    /**
     * (Optional) Disable the use of WebSocket.
     *
     * <p>By default, WebSocket is the preferred
     * transport layer for events, and HTTP long-polling is used as a fall-back.
     *
     * <p>WebSocket can also be disabled by setting the system property
     * <code>com.genesys.wsclient.websocket=false</code>.
     */
    public Setup disableWebSocket() {
      webSocketEnabled = false;
      return this;
    }
   
  }
 
}
TOP

Related Classes of com.genesys.wsclient.GenesysEventReceiver$Setup

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.