public synchronized InternalConnection getConnection(
     final String apiKey, final PusherOptions options) {
   if (connection == null) {
     try {
       connection =
           new WebSocketConnection(
               options.buildUrl(apiKey),
               options.getActivityTimeout(),
               options.getPongTimeout(),
               options.getProxy(),
               this);
     } catch (final URISyntaxException e) {
       throw new IllegalArgumentException("Failed to initialise connection", e);
     }
   }
   return connection;
 }
  /**
   * Constructs a new PusherClientEndpoint based on the given URI and parameters. The URI should be
   * of the form
   *
   * <pre>
   *     pusher-client://{app-key}/{channel_name}?events=event1,event2...
   * </pre>
   *
   * To create an endpoint for a private or presence channel (i.e. one where the channel name being
   * with 'private-' or 'presence-'), there must be an {@link com.pusher.client.Authorizer} instance
   * (e.g. {@link com.pusher.client.util.HttpAuthorizer}) registered in the {@link
   * org.apache.camel.spi.Registry} of the current {@link org.apache.camel.CamelContext}. If more
   * than one Authorizer is registered, the one whose name includes the app-key present in the URI
   * will be chosen.
   *
   * <p>A presence channel endpoint will automatically receive events when a member is added or
   * removed; no need to register for these events explicitly.
   *
   * @param uri the URI of the endpoint
   * @param remaining the un-matched portion of the URI
   * @param parameters a map of any query parameters on the endpoint URI
   * @return a PusherClientEndpoint constructed from the given URI
   * @throws Exception
   */
  protected PusherClientEndpoint createEndpoint(
      String uri, String remaining, Map<String, Object> parameters) throws Exception {
    // Extract app key and channel name from endpoint URI
    String[] path = remaining.split("/");
    if (path.length != 2) {
      throw new IllegalArgumentException(
          "Pusher uri path must contain app key and channel name: "
              + SCHEME
              + "://{app-key}/{channel_name}");
    }
    final String appKey = path[0];
    final String channelName = path[1];
    final String channelId = getChannelId(appKey, channelName);

    // Set Pusher options - first, try to find an Authorizer instance in the registry
    PusherOptions options = new PusherOptions();
    Map<String, Authorizer> authorizers =
        getCamelContext().getRegistry().findByTypeWithName(Authorizer.class);
    if (authorizers.size() == 1) {
      LOG.debug("Found 1 authorizer: {}", authorizers.keySet().iterator().next());
      options.setAuthorizer(authorizers.values().iterator().next());
    } else if (authorizers.size() > 1) {
      LOG.debug("Found {} authorizers", authorizers.size());
      for (String name : authorizers.keySet()) {
        if (name.contains(appKey)) {
          LOG.debug("Setting authorizer: " + name);
          options.setAuthorizer(authorizers.get(name));
          break;
        }
      }
      options.setAuthorizer(authorizers.values().iterator().next());
    }
    // set options from the endpoint parameters
    setProperties(options, parameters);

    // Create a new pusher from these options, and add to our collection of apps if not
    // already there
    Pusher pusher = apps.putIfAbsent(appKey, new Pusher(appKey, options));

    if (pusher == null) {
      LOG.debug("Creating new Pusher instance for appKey: {}", appKey);
      pusher = apps.get(appKey);

      // Connect to the app
      pusher.connect(
          new ConnectionEventListener() {
            @Override
            public void onConnectionStateChange(ConnectionStateChange change) {
              LOG.info(
                  "Pusher app {} connection state change from {} to {}",
                  new Object[] {appKey, change.getPreviousState(), change.getCurrentState()});
              if (change.getCurrentState() == ConnectionState.CONNECTED) {
                finished(change.getCurrentState());
              }
            }

            @Override
            public void onError(String message, String code, Exception e) {
              LOG.error(
                  "Pusher could not connect to app {}: {} ({})",
                  new Object[] {appKey, message, code});
              finished(e);
            }
          },
          ConnectionState.ALL);

      join(); // Wait for connection
      LOG.info("Pusher connected to app {}", appKey);

      // Listen for disconnections, and re-connect if they occur.
      final Pusher finalPusher = pusher;
      pusher
          .getConnection()
          .bind(
              ConnectionState.DISCONNECTED,
              new ConnectionEventListener() {
                @Override
                public void onConnectionStateChange(ConnectionStateChange change) {
                  if (change.getCurrentState() == ConnectionState.DISCONNECTED
                      && !isStoppingOrStopped()) {
                    LOG.info("App {} disconnected. Reconnecting...");
                    finalPusher.connect();
                  }
                }

                @Override
                public void onError(String message, String code, Exception e) {
                  LOG.error("Message: {}, code: {}", message, code);
                }
              });
    }

    Channel channel = channels.get(channelId);
    if (subscriptions.add(channelId)) {

      // Subscribe to the channel
      LOG.debug("Subscribing to channel {}", channelId);

      // Set up callbacks appropriate to the type of channel - public, private or presence
      PresenceChannelEventListener presenceListener = subscriptionListener(channelId);
      if (channelName.startsWith("private-")) {
        channel = pusher.subscribePrivate(channelName, presenceListener);
      } else if (channelName.startsWith("presence-")) {
        channel = pusher.subscribePresence(channelName, presenceListener);
      } else {
        channel = pusher.subscribe(channelName, presenceListener);
      }

      join(); // Wait for channel subscription
      LOG.info("Subscribed to channel {}", channelId);

      channels.put(channelId, channel);
    }

    // Create a new endpoint for this URI, and set properties on it
    final PusherClientEndpoint endpoint = new PusherClientEndpoint(uri, this, appKey, channel);
    setProperties(endpoint, parameters);

    return endpoint;
  }