All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rabbitmq.http.client.Client Maven / Gradle / Ivy

There is a newer version: 5.3.0
Show newest version
/*
 * Copyright 2015-2017 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 com.rabbitmq.http.client;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLContext;

import com.rabbitmq.http.client.domain.TopicPermissions;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.protocol.HttpContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriUtils;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.rabbitmq.http.client.domain.AlivenessTestResult;
import com.rabbitmq.http.client.domain.BindingInfo;
import com.rabbitmq.http.client.domain.ChannelInfo;
import com.rabbitmq.http.client.domain.ClusterId;
import com.rabbitmq.http.client.domain.ConnectionInfo;
import com.rabbitmq.http.client.domain.CurrentUserDetails;
import com.rabbitmq.http.client.domain.Definitions;
import com.rabbitmq.http.client.domain.ExchangeInfo;
import com.rabbitmq.http.client.domain.NodeInfo;
import com.rabbitmq.http.client.domain.OverviewResponse;
import com.rabbitmq.http.client.domain.PolicyInfo;
import com.rabbitmq.http.client.domain.QueueInfo;
import com.rabbitmq.http.client.domain.ShovelInfo;
import com.rabbitmq.http.client.domain.ShovelStatus;
import com.rabbitmq.http.client.domain.UserInfo;
import com.rabbitmq.http.client.domain.UserPermissions;
import com.rabbitmq.http.client.domain.VhostInfo;

public class Client {
  private static final HttpClientBuilderConfigurator NO_OP_HTTP_CLIENT_BUILDER_CONFIGURATOR =
      builder -> builder;

  private RestTemplate rt;
  private URI rootUri;

  //
  // API
  //

  /**
   * Construct an instance with the provided url and credentials.
   * @param url the url e.g. "http://localhost:15672/api/".
   * @param username the username.
   * @param password the password
   * @throws MalformedURLException for a badly formed URL.
   * @throws URISyntaxException for a badly formed URL.
   */
  public Client(String url, String username, String password) throws MalformedURLException, URISyntaxException {
    this(new URL(url), username, password);
  }

  /**
   * Construct an instance with the provided url and credentials.
   * @param url the url e.g. "http://localhost:15672/api/".
   * @param username the username.
   * @param password the password
   * @param configurator {@link HttpClientBuilderConfigurator} to use
   * @throws MalformedURLException for a badly formed URL.
   * @throws URISyntaxException for a badly formed URL.
   */
  public Client(String url, String username, String password, HttpClientBuilderConfigurator configurator)
      throws MalformedURLException, URISyntaxException {
    this(new URL(url), username, password, configurator);
  }

  /**
   * Construct an instance with the provided url and credentials.
   * @param url the url e.g. "http://localhost:15672/api/".
   * @param username the username.
   * @param password the password
   * @throws MalformedURLException for a badly formed URL.
   * @throws URISyntaxException for a badly formed URL.
   */
  public Client(URL url, String username, String password) throws MalformedURLException, URISyntaxException {
    this(url, username, password, null, null);
  }

  /**
   * Construct an instance with the provided url and credentials.
   * @param url the url e.g. "http://localhost:15672/api/".
   * @param username the username.
   * @param password the password
   * @param configurator {@link HttpClientBuilderConfigurator} to use
   * @throws MalformedURLException for a badly formed URL.
   * @throws URISyntaxException for a badly formed URL.
   */
  public Client(URL url, String username, String password, HttpClientBuilderConfigurator configurator)
      throws MalformedURLException, URISyntaxException {
    this(url, username, password, null, null, configurator);
  }

  /**
   * Construct an instance with the provided url and credentials.
   * @param url the url e.g. "http://localhost:15672/api/".
   * @param username the username.
   * @param password the password
   * @param sslConnectionSocketFactory ssl connection factory for http client
   * @param sslContext ssl context for http client
   * @throws MalformedURLException for a badly formed URL.
   * @throws URISyntaxException for a badly formed URL.
   */
  private Client(URL url, String username, String password, SSLConnectionSocketFactory sslConnectionSocketFactory, SSLContext sslContext)
      throws MalformedURLException, URISyntaxException {
    Assert.notNull(url, "URL is required; it must not be null");
    Assert.notNull(username, "username is required; it must not be null");
    Assert.notNull(password, "password is required; it must not be null");
    if (url.toString().endsWith("/")) {
      this.rootUri = url.toURI();
    } else {
      this.rootUri = new URL(url.toString() + "/").toURI();
    }


    this.rt = new RestTemplate(getRequestFactory(url, username, password, sslConnectionSocketFactory, sslContext, NO_OP_HTTP_CLIENT_BUILDER_CONFIGURATOR));
    this.rt.setMessageConverters(getMessageConverters());
  }

  /**
   * Construct an instance with the provided url and credentials.
   * @param url the url e.g. "http://localhost:15672/api/".
   * @param username the username.
   * @param password the password
   * @param sslContext ssl context for http client
   * @throws MalformedURLException for a badly formed URL.
   * @throws URISyntaxException for a badly formed URL.
   */
  public Client(URL url, String username, String password, SSLContext sslContext) throws MalformedURLException, URISyntaxException {
    this(url, username, password, null, sslContext);
  }

  /**
   * Construct an instance with the provided url and credentials.
   * @param url the url e.g. "http://localhost:15672/api/".
   * @param username the username.
   * @param password the password
   * @param sslConnectionSocketFactory ssl connection factory for http client
   * @throws MalformedURLException for a badly formed URL.
   * @throws URISyntaxException for a badly formed URL.
   */
  private Client(URL url, String username, String password, SSLConnectionSocketFactory sslConnectionSocketFactory) throws MalformedURLException, URISyntaxException {
    this(url, username, password, sslConnectionSocketFactory, null);
  }

  /**
   * Construct an instance with the provided url and credentials.
   * @param url the url e.g. "http://guest:guest@localhost:15672/api/".
   * @throws MalformedURLException for a badly formed URL.
   * @throws URISyntaxException for a badly formed URL.
   */
  public Client(String url) throws MalformedURLException, URISyntaxException {
    this(urlWithoutCredentials(url),
         StringUtils.split(new URL(url).getUserInfo(),":")[0],
         StringUtils.split(new URL(url).getUserInfo(),":")[1]
    );
  }

  /**
   * Construct an instance with the provided url and credentials.
   * @param url the url e.g. "http://guest:guest@localhost:15672/api/".
   * @throws MalformedURLException for a badly formed URL.
   * @throws URISyntaxException for a badly formed URL.
   */
  public Client(URL url) throws MalformedURLException, URISyntaxException {
    this(url, null, null);
  }

  private Client(URL url, String username, String password, SSLConnectionSocketFactory sslConnectionSocketFactory,
                 SSLContext sslContext,
                 HttpClientBuilderConfigurator configurator) throws URISyntaxException, MalformedURLException {
    Assert.notNull(url, "URL is required; it must not be null");
    Assert.notNull(username, "username is required; it must not be null");
    Assert.notNull(password, "password is required; it must not be null");
    Assert.notNull(configurator, "configurator is required; it must not be null");
    this.rootUri = url.toURI();

    HttpComponentsClientHttpRequestFactory rf = getRequestFactory(url, username, password,
        sslConnectionSocketFactory, sslContext, configurator);
    this.rt = new RestTemplate(rf);
    this.rt.setMessageConverters(getMessageConverters());
  }

  /**
   * @return cluster state overview
   */
  public OverviewResponse getOverview() {
    return this.rt.getForObject(uriWithPath("./overview"), OverviewResponse.class);
  }

  /**
   * Performs a basic node aliveness check: declares a queue, publishes a message
   * that routes to it, consumes it, cleans up.
   *
   * @param vhost vhost to use to perform aliveness check in
   * @return true if the check succeeded
   */
  public boolean alivenessTest(String vhost) {
    final URI uri = uriWithPath("./aliveness-test/" + encodePathSegment(vhost));
    return this.rt.getForObject(uri, AlivenessTestResult.class).isSuccessful();
  }

  /**
   * @return information about the user used by this client instance
   */
  public CurrentUserDetails whoAmI() {
    final URI uri = uriWithPath("./whoami/");
    return this.rt.getForObject(uri, CurrentUserDetails.class);
  }

  /**
   * Retrieves state and metrics information for all nodes in the cluster.
   *
   * @return list of nodes in the cluster
   */
  public List getNodes() {
    final URI uri = uriWithPath("./nodes/");
    return Arrays.asList(this.rt.getForObject(uri, NodeInfo[].class));
  }

  /**
   * Retrieves state and metrics information for individual node.
   *
   * @param name node name
   * @return node information
   */
  public NodeInfo getNode(String name) {
    final URI uri = uriWithPath("./nodes/" + encodePathSegment(name));
    return this.rt.getForObject(uri, NodeInfo.class);
  }

  /**
   * Retrieves state and metrics information for all client connections across the cluster.
   *
   * @return list of connections across the cluster
   */
  public List getConnections() {
    final URI uri = uriWithPath("./connections/");
    return Arrays.asList(this.rt.getForObject(uri, ConnectionInfo[].class));
  }

  /**
   * Retrieves state and metrics information for individual client connection.
   *
   * @param name connection name
   * @return connection information
   */
  public ConnectionInfo getConnection(String name) {
    final URI uri = uriWithPath("./connections/" + encodePathSegment(name));
    return this.rt.getForObject(uri, ConnectionInfo.class);
  }

  /**
   * Forcefully closes individual connection.
   * The client will receive a connection.close method frame.
   *
   * @param name connection name
   */
  public void closeConnection(String name) {
    final URI uri = uriWithPath("./connections/" + encodePathSegment(name));
    deleteIgnoring404(uri);
  }

  /**
   * Forcefully closes individual connection with a user-provided message.
   * The client will receive a connection.close method frame.
   *
   * @param name connection name
   * @param reason the reason of closing
   */
  public void closeConnection(String name, String reason) {
    final URI uri = uriWithPath("./connections/" + encodePathSegment(name));

    MultiValueMap headers = new LinkedMultiValueMap();
    headers.put("X-Reason", Collections.singletonList(reason));

    deleteIgnoring404(uri, headers);
  }

  /**
   * Retrieves state and metrics information for all channels across the cluster.
   *
   * @return list of channels across the cluster
   */
  public List getChannels() {
    final URI uri = uriWithPath("./channels/");
    return Arrays.asList(this.rt.getForObject(uri, ChannelInfo[].class));
  }

  /**
   * Retrieves state and metrics information for all channels on individual connection.
   * @param connectionName the connection name to retrieve channels
   * @return list of channels on the connection
   */
  public List getChannels(String connectionName) {
    final URI uri = uriWithPath("./connections/" + encodePathSegment(connectionName) + "/channels/");
    return Arrays.asList(this.rt.getForObject(uri, ChannelInfo[].class));
  }

  /**
   * Retrieves state and metrics information for individual channel.
   *
   * @param name channel name
   * @return channel information
   */
  public ChannelInfo getChannel(String name) {
    final URI uri = uriWithPath("./channels/" + encodePathSegment(name));
    return this.rt.getForObject(uri, ChannelInfo.class);
  }

  public List getVhosts() {
    final URI uri = uriWithPath("./vhosts/");
    return Arrays.asList(this.rt.getForObject(uri, VhostInfo[].class));
  }

  public VhostInfo getVhost(String name) {
    final URI uri = uriWithPath("./vhosts/" + encodePathSegment(name));
    return getForObjectReturningNullOn404(uri, VhostInfo.class);
  }

  public void createVhost(String name) throws JsonProcessingException {
    final URI uri = uriWithPath("./vhosts/" + encodePathSegment(name));
    this.rt.put(uri, null);
  }

  public void deleteVhost(String name) {
    final URI uri = uriWithPath("./vhosts/" + encodePathSegment(name));
    deleteIgnoring404(uri);
  }

  public List getPermissionsIn(String vhost) {
    final URI uri = uriWithPath("./vhosts/" + encodePathSegment(vhost) + "/permissions");
    UserPermissions[] result = this.getForObjectReturningNullOn404(uri, UserPermissions[].class);
    return asListOrNull(result);
  }

  public List getPermissionsOf(String username) {
    final URI uri = uriWithPath("./users/" + encodePathSegment(username) + "/permissions");
    UserPermissions[] result = this.getForObjectReturningNullOn404(uri, UserPermissions[].class);
    return asListOrNull(result);
  }

  public List getPermissions() {
    final URI uri = uriWithPath("./permissions");
    UserPermissions[] result = this.getForObjectReturningNullOn404(uri, UserPermissions[].class);
    return asListOrNull(result);
  }

  public UserPermissions getPermissions(String vhost, String username) {
    final URI uri = uriWithPath("./permissions/" + encodePathSegment(vhost) + "/" + encodePathSegment(username));
    return this.getForObjectReturningNullOn404(uri, UserPermissions.class);
  }

  public List getTopicPermissionsIn(String vhost) {
    final URI uri = uriWithPath("./vhosts/" + encodePathSegment(vhost) + "/topic-permissions");
    TopicPermissions[] result = this.getForObjectReturningNullOn404(uri, TopicPermissions[].class);
    return asListOrNull(result);
  }

  public List getTopicPermissionsOf(String username) {
    final URI uri = uriWithPath("./users/" + encodePathSegment(username) + "/topic-permissions");
    TopicPermissions[] result = this.getForObjectReturningNullOn404(uri, TopicPermissions[].class);
    return asListOrNull(result);
  }

  public List getTopicPermissions() {
    final URI uri = uriWithPath("./topic-permissions");
    TopicPermissions[] result = this.getForObjectReturningNullOn404(uri, TopicPermissions[].class);
    return asListOrNull(result);
  }

  public List getTopicPermissions(String vhost, String username) {
    final URI uri = uriWithPath("./topic-permissions/" + encodePathSegment(vhost) + "/" + encodePathSegment(username));
    return asListOrNull(this.getForObjectReturningNullOn404(uri, TopicPermissions[].class));
  }

  public List getExchanges() {
    final URI uri = uriWithPath("./exchanges/");
    return Arrays.asList(this.rt.getForObject(uri, ExchangeInfo[].class));
  }

  public List getExchanges(String vhost) {
    final URI uri = uriWithPath("./exchanges/" + encodePathSegment(vhost));
    final ExchangeInfo[] result = this.getForObjectReturningNullOn404(uri, ExchangeInfo[].class);
    return asListOrNull(result);
  }

  public ExchangeInfo getExchange(String vhost, String name) {
    final URI uri = uriWithPath("./exchanges/" + encodePathSegment(vhost) + "/" + encodePathSegment(name));
    return this.getForObjectReturningNullOn404(uri, ExchangeInfo.class);
  }

  public void declareExchange(String vhost, String name, ExchangeInfo info) {
    final URI uri = uriWithPath("./exchanges/" + encodePathSegment(vhost) + "/" + encodePathSegment(name));
    this.rt.put(uri, info);
  }

  public void deleteExchange(String vhost, String name) {
    this.deleteIgnoring404(uriWithPath("./exchanges/" + encodePathSegment(vhost) + "/" + encodePathSegment(name)));
  }

  public List getQueues() {
    final URI uri = uriWithPath("./queues/");
    return Arrays.asList(this.rt.getForObject(uri, QueueInfo[].class));
  }

  public List getQueues(String vhost) {
    final URI uri = uriWithPath("./queues/" + encodePathSegment(vhost));
    final QueueInfo[] result = this.getForObjectReturningNullOn404(uri, QueueInfo[].class);
    return asListOrNull(result);
  }

  public QueueInfo getQueue(String vhost, String name) {
    final URI uri = uriWithPath("./queues/" + encodePathSegment(vhost) + "/" + encodePathSegment(name));
    return this.getForObjectReturningNullOn404(uri, QueueInfo.class);
  }

  public void declarePolicy(String vhost, String name, PolicyInfo info) {
    final URI uri = uriWithPath("./policies/" + encodePathSegment(vhost) + "/" + encodePathSegment(name));
    this.rt.put(uri, info);
  }

  public void declareQueue(String vhost, String name, QueueInfo info) {
    final URI uri = uriWithPath("./queues/" + encodePathSegment(vhost) + "/" + encodePathSegment(name));
    this.rt.put(uri, info);
  }

  public void purgeQueue(String vhost, String name) {
    this.deleteIgnoring404(uriWithPath("./queues/" + encodePathSegment(vhost) + "/" + encodePathSegment(name) + "/contents/"));
  }

  public void deleteQueue(String vhost, String name) {
    this.deleteIgnoring404(uriWithPath("./queues/" + encodePathSegment(vhost) + "/" + encodePathSegment(name)));
  }

  public void deletePolicy(String vhost, String name) {
    this.deleteIgnoring404(uriWithPath("./policies/" + encodePathSegment(vhost) + "/" + encodePathSegment(name)));
  }

  public List getUsers() {
    final URI uri = uriWithPath("./users/");
    return Arrays.asList(this.rt.getForObject(uri, UserInfo[].class));
  }

  public UserInfo getUser(String username) {
    final URI uri = uriWithPath("./users/" + encodePathSegment(username));
    return this.getForObjectReturningNullOn404(uri, UserInfo.class);
  }

  public void createUser(String username, char[] password, List tags) {
    if(username == null) {
      throw new IllegalArgumentException("username cannot be null");
    }
    if(password == null) {
      throw new IllegalArgumentException("password cannot be null or empty. If you need to create a user that "
            + "will only authenticate using an x509 certificate, use createUserWithPasswordHash with a blank hash.");
    }
    Map body = new HashMap();
    body.put("password", new String(password));
    body.put("tags", joinStrings(",", tags));

    final URI uri = uriWithPath("./users/" + encodePathSegment(username));
    this.rt.put(uri, body);
  }

  public void createUserWithPasswordHash(String username, char[] passwordHash, List tags) {
    if(username == null) {
      throw new IllegalArgumentException("username cannot be null");
    }
    // passwordless authentication is a thing. See
    // https://github.com/rabbitmq/hop/issues/94 and https://www.rabbitmq.com/authentication.html. MK.
    if(passwordHash == null) {
      passwordHash = "".toCharArray();
    }
    Map body = new HashMap();
    body.put("password_hash", String.valueOf(passwordHash));
    body.put("tags", joinStrings(",", tags));

    final URI uri = uriWithPath("./users/" + encodePathSegment(username));
    this.rt.put(uri, body);
  }

  public void updateUser(String username, char[] password, List tags) {
    if(username == null) {
      throw new IllegalArgumentException("username cannot be null");
    }
    Map body = new HashMap();
    // only update password if provided
    if(password != null) {
      body.put("password", new String(password));
    }
    body.put("tags", joinStrings(",", tags));

    final URI uri = uriWithPath("./users/" + encodePathSegment(username));
    this.rt.put(uri, body);
  }

  public void deleteUser(String username) {
    this.deleteIgnoring404(uriWithPath("./users/" + encodePathSegment(username)));
  }

  public void updatePermissions(String vhost, String username, UserPermissions permissions) {
    final URI uri = uriWithPath("./permissions/" + encodePathSegment(vhost) + "/" + encodePathSegment(username));
    this.rt.put(uri, permissions);
  }

  public void clearPermissions(String vhost, String username) {
    final URI uri = uriWithPath("./permissions/" + encodePathSegment(vhost) + "/" + encodePathSegment(username));
    deleteIgnoring404(uri);
  }

  public void updateTopicPermissions(String vhost, String username, TopicPermissions permissions) {
    final URI uri = uriWithPath("./topic-permissions/" + encodePathSegment(vhost) + "/" + encodePathSegment(username));
    this.rt.put(uri, permissions);
  }

  public void clearTopicPermissions(String vhost, String username) {
    final URI uri = uriWithPath("./topic-permissions/" + encodePathSegment(vhost) + "/" + encodePathSegment(username));
    deleteIgnoring404(uri);
  }

  public List getPolicies() {
    final URI uri = uriWithPath("./policies/");
    return Arrays.asList(this.rt.getForObject(uri, PolicyInfo[].class));
  }

  public List getPolicies(String vhost) {
    final URI uri = uriWithPath("./policies/" + encodePathSegment(vhost));
    final PolicyInfo[] result = this.getForObjectReturningNullOn404(uri, PolicyInfo[].class);
    return asListOrNull(result);
  }

  public List getBindings() {
    final URI uri = uriWithPath("./bindings/");
    return Arrays.asList(this.rt.getForObject(uri, BindingInfo[].class));
  }

  public List getBindings(String vhost) {
    final URI uri = uriWithPath("./bindings/" + encodePathSegment(vhost));
    return Arrays.asList(this.rt.getForObject(uri, BindingInfo[].class));
  }

  /**
   * Returns a list of bindings where provided exchange is the source (other things are
   * bound to it).
   *
   * @param vhost    vhost of the exchange
   * @param exchange source exchange name
   * @return list of bindings
   */
  public List getBindingsBySource(String vhost, String exchange) {
    final String x = exchange.equals("") ? "amq.default" : exchange;
    final URI uri = uriWithPath("./exchanges/" + encodePathSegment(vhost) +
        "/" + encodePathSegment(x) + "/bindings/source");
    return Arrays.asList(this.rt.getForObject(uri, BindingInfo[].class));
  }

  /**
   * Returns a list of bindings where provided exchange is the destination (it is
   * bound to another exchange).
   *
   * @param vhost    vhost of the exchange
   * @param exchange destination exchange name
   * @return list of bindings
   */
  public List getExchangeBindingsByDestination(String vhost, String exchange) {
    final String x = exchange.equals("") ? "amq.default" : exchange;
    final URI uri = uriWithPath("./exchanges/" + encodePathSegment(vhost) +
        "/" + encodePathSegment(x) + "/bindings/destination");
    final BindingInfo[] result = this.rt.getForObject(uri, BindingInfo[].class);
    return asListOrNull(result);
  }

  /**
   * Returns a list of bindings where provided queue is the destination.
   *
   * @param vhost    vhost of the exchange
   * @param queue    destination queue name
   * @return list of bindings
   */
  public List getQueueBindings(String vhost, String queue) {
    final URI uri = uriWithPath("./queues/" + encodePathSegment(vhost) +
        "/" + encodePathSegment(queue) + "/bindings");
    final BindingInfo[] result = this.rt.getForObject(uri, BindingInfo[].class);
    return asListOrNull(result);
  }

  public List getQueueBindingsBetween(String vhost, String exchange, String queue) {
    final URI uri = uriWithPath("./bindings/" + encodePathSegment(vhost) +
        "/e/" + encodePathSegment(exchange) + "/q/" + encodePathSegment(queue));
    final BindingInfo[] result = this.rt.getForObject(uri, BindingInfo[].class);
    return asListOrNull(result);
  }

  public List getExchangeBindingsBetween(String vhost, String source, String destination) {
    final URI uri = uriWithPath("./bindings/" + encodePathSegment(vhost) +
        "/e/" + encodePathSegment(source) + "/e/" + encodePathSegment(destination));
    final BindingInfo[] result = this.rt.getForObject(uri, BindingInfo[].class);
    return asListOrNull(result);
  }

  public void bindQueue(String vhost, String queue, String exchange, String routingKey) {
    bindQueue(vhost, queue, exchange, routingKey, new HashMap());
  }

  public void bindQueue(String vhost, String queue, String exchange, String routingKey, Map args) {
    if(vhost == null || vhost.isEmpty()) {
      throw new IllegalArgumentException("vhost cannot be null or blank");
    }
    if(queue == null || queue.isEmpty()) {
      throw new IllegalArgumentException("queue cannot be null or blank");
    }
    if(exchange == null || exchange.isEmpty()) {
      throw new IllegalArgumentException("exchange cannot be null or blank");
    }
    Map body = new HashMap();
    if(!(args == null)) {
      body.put("args", args);
    }
    body.put("routing_key", routingKey);

    final URI uri = uriWithPath("./bindings/" + encodePathSegment(vhost) +
      "/e/" + encodePathSegment(exchange) + "/q/" + encodePathSegment(queue));
    this.rt.postForLocation(uri, body);
  }

  public void unbindQueue(String vhost, String queue, String exchange, String routingKey) {
    if(vhost == null || vhost.isEmpty()) {
      throw new IllegalArgumentException("vhost cannot be null or blank");
    }
    if(queue == null || queue.isEmpty()) {
      throw new IllegalArgumentException("queue cannot be null or blank");
    }
    if(exchange == null || exchange.isEmpty()) {
      throw new IllegalArgumentException("exchange cannot be null or blank");
    }
    
    this.deleteIgnoring404(uriWithPath("./bindings/" + encodePathSegment(vhost) + "/e/" + encodePathSegment(exchange) + 
      "/q/" + encodePathSegment(queue) + '/' + encodePathSegment(routingKey)));
  }

  public void bindExchange(String vhost, String destination, String source, String routingKey) {
    bindExchange(vhost, destination, source, routingKey, new HashMap());
  }

  public void bindExchange(String vhost, String destination, String source, String routingKey, Map args) {
    if(vhost == null || vhost.isEmpty()) {
      throw new IllegalArgumentException("vhost cannot be null or blank");
    }
    if(destination == null || destination.isEmpty()) {
      throw new IllegalArgumentException("destination cannot be null or blank");
    }
    if(source == null || source.isEmpty()) {
      throw new IllegalArgumentException("source cannot be null or blank");
    }
    Map body = new HashMap();
    if(!(args == null)) {
      body.put("args", args);
    }
    body.put("routing_key", routingKey);

    final URI uri = uriWithPath("./bindings/" + encodePathSegment(vhost) +
      "/e/" + encodePathSegment(source) + "/e/" + encodePathSegment(destination));
    this.rt.postForLocation(uri, body);
  }

  public void unbindExchange(String vhost, String destination, String source, String routingKey) {
    if(vhost == null || vhost.isEmpty()) {
      throw new IllegalArgumentException("vhost cannot be null or blank");
    }
    if(destination == null || destination.isEmpty()) {
      throw new IllegalArgumentException("destination cannot be null or blank");
    }
    if(source == null || source.isEmpty()) {
      throw new IllegalArgumentException("source cannot be null or blank");
    }
	    
    this.deleteIgnoring404(uriWithPath("./bindings/" + encodePathSegment(vhost) + "/e/" + encodePathSegment(source) + 
      "/e/" + encodePathSegment(destination) + '/' + encodePathSegment(routingKey)));
  }

  public ClusterId getClusterName() {
    return this.rt.getForObject(uriWithPath("./cluster-name"), ClusterId.class);
  }

  public void setClusterName(String name) {
    if(name== null || name.isEmpty()) {
      throw new IllegalArgumentException("name cannot be null or blank");
    }
    final URI uri = uriWithPath("./cluster-name");
    Map m = new HashMap();
    m.put("name", name);
    this.rt.put(uri, m);
  }

  @SuppressWarnings({"unchecked","rawtypes"})
  public List getExtensions() {
    final URI uri = uriWithPath("./extensions/");
    return Arrays.asList(this.rt.getForObject(uri, Map[].class));
  }

  public Definitions getDefinitions() {
    final URI uri = uriWithPath("./definitions/");
    return this.rt.getForObject(uri, Definitions.class);
  }
  
  //
  // Shovel support
  //

  /**
   * Declares a shovel.
   * 
   * @param vhost virtual host where to declare the shovel
   * @param info Shovel info. 
   */
  public void declareShovel(String vhost, ShovelInfo info) {
    final URI uri = uriWithPath("./parameters/shovel/" + encodePathSegment(vhost) + "/" + encodePathSegment(info.getName()));
    this.rt.put(uri, info);
  }

  /**
   * Returns virtual host shovels.
   * 
   * @return Shovels.
   */
  public List getShovels() {
    final URI uri = uriWithPath("./parameters/shovel/");
    return Arrays.asList(this.rt.getForObject(uri, ShovelInfo[].class));
  }

  /**
   * Returns virtual host shovels.
   * 
   * @param vhost Virtual host from where search shovels.
   * @return Shovels.
   */
  public List getShovels(String vhost) {
    final URI uri = uriWithPath("./parameters/shovel/" + encodePathSegment(vhost));
    final ShovelInfo[] result = this.getForObjectReturningNullOn404(uri, ShovelInfo[].class);
    return asListOrNull(result);
  }

  /**
   * Returns virtual host shovels.
   * 
   * @return Shovels.
   */
  public List getShovelsStatus() {
    final URI uri = uriWithPath("./shovels/");
    return Arrays.asList(this.rt.getForObject(uri, ShovelStatus[].class));
  }

  /**
   * Returns virtual host shovels.
   * 
   * @param vhost Virtual host from where search shovels.
   * @return Shovels.
   */
  public List getShovelsStatus(String vhost) {
    final URI uri = uriWithPath("./shovels/" + encodePathSegment(vhost));
    final ShovelStatus[] result = this.getForObjectReturningNullOn404(uri, ShovelStatus[].class);
    return asListOrNull(result);
  }

  /**
   * Deletes the specified shovel from specified virtual host.
   * 
   * @param vhost virtual host from where to delete the shovel
   * @param shovelname Shovel to be deleted.
   */
  public void deleteShovel(String vhost, String shovelname) {
	    this.deleteIgnoring404(uriWithPath("./parameters/shovel/" + encodePathSegment(vhost) + "/" + encodePathSegment(shovelname)));
  }


  //
  // Implementation
  //

  /**
   * Produces a URI used to issue HTTP requests to avoid double-escaping of path segments
   * (e.g. vhost names) from {@link RestTemplate#execute}.
   *
   * @param path The path after /api/
   * @return resolved URI
   */
  private URI uriWithPath(final String path) {
    return this.rootUri.resolve(path);
  }

  private String encodePathSegment(final String pathSegment) {
    return UriUtils.encodePathSegment(pathSegment, "UTF-8");
  }

  private List> getMessageConverters() {
    List> xs = new ArrayList>();
    final Jackson2ObjectMapperBuilder bldr = Jackson2ObjectMapperBuilder
        .json()
        .serializationInclusion(JsonInclude.Include.NON_NULL)
        .featuresToEnable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT);
    xs.add(new MappingJackson2HttpMessageConverter(bldr.build()));
    return xs;
  }

  private HttpComponentsClientHttpRequestFactory getRequestFactory(final URL url,
                                                                   final String username, final String password,
                                                                   final SSLConnectionSocketFactory sslConnectionSocketFactory,
                                                                   final SSLContext sslContext,
                                                                   final HttpClientBuilderConfigurator configurator) throws MalformedURLException {
    String theUser = username;
    String thePassword = password;
    String userInfo = url.getUserInfo();
    if (userInfo != null && theUser == null) {
      String[] userParts = userInfo.split(":");
      if (userParts.length > 0) {
        theUser = userParts[0];
      }
      if (userParts.length > 1) {
        thePassword = userParts[1];
      }
    }

    // configure HttpClientBuilder essentials
    final HttpClientBuilder bldr = HttpClientBuilder.create().
        setDefaultCredentialsProvider(getCredentialsProvider(url, theUser, thePassword));
    if (sslConnectionSocketFactory != null) {
      bldr.setSSLSocketFactory(sslConnectionSocketFactory);
    }
    if (sslContext != null) {
      bldr.setSSLContext(sslContext);
    }

    HttpClient httpClient;
    // this lets the user perform non-essential configuration (e.g. timeouts)
    // but reduces the risk of essentials not being set. MK.
    HttpClientBuilder b = configurator.configure(bldr);
    httpClient = b.build();

    // RabbitMQ HTTP API currently does not support challenge/response for PUT methods.
    AuthCache authCache = new BasicAuthCache();
    BasicScheme basicScheme = new BasicScheme();
    authCache.put(new HttpHost(rootUri.getHost(), rootUri.getPort(), rootUri.getScheme()), basicScheme);
    final HttpClientContext ctx = HttpClientContext.create();
    ctx.setAuthCache(authCache);
    return new HttpComponentsClientHttpRequestFactory(httpClient) {
      @Override
      protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
        return ctx;
      }
    };
  }

  private CredentialsProvider getCredentialsProvider(final URL url, final String username, final String password) {
    CredentialsProvider cp = new BasicCredentialsProvider();
    cp.setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT),
        new UsernamePasswordCredentials(username, password));

    return cp;
  }

  private  T getForObjectReturningNullOn404(final URI uri, final Class klass) {
    try {
      return this.rt.getForObject(uri, klass);
    } catch (final HttpClientErrorException ce) {
      if(ce.getStatusCode() == HttpStatus.NOT_FOUND) {
        return null;
      } else {
        throw ce;
      }
    }
  }

  private void deleteIgnoring404(URI uri) {
    try {
      this.rt.delete(uri);
    } catch (final HttpClientErrorException ce) {
      if(!(ce.getStatusCode() == HttpStatus.NOT_FOUND)) {
        throw ce;
      }
    }
  }

  private void deleteIgnoring404(URI uri, MultiValueMap headers) {
    try {
      HttpEntity entity = new HttpEntity(null, headers);
      this.rt.exchange(uri, HttpMethod.DELETE, entity, Object.class);
    } catch (final HttpClientErrorException ce) {
      if(!(ce.getStatusCode() == HttpStatus.NOT_FOUND)) {
        throw ce;
      }
    }
  }

  private  List asListOrNull(T[] result) {
    if(result == null) {
      return null;
    } else {
      return Arrays.asList(result);
    }
  }

  private String joinStrings(String delimiter, List tags) {
    StringBuilder sb = new StringBuilder();
    boolean appendedFirst = false;
    for (String tag : tags) {
      if(!appendedFirst) {
        sb.append(tag);
        appendedFirst = true;
      } else {
        sb.append(delimiter);
        if(tag != null) {
          sb.append(tag);
        }
      }
    }
    return sb.toString();
  }

  private static String urlWithoutCredentials(String url) throws MalformedURLException {
    URL url1 = new URL(url);
    return StringUtils.replace(url, url1.getUserInfo() + "@", "");
  }
}