CaravanHttpRequestBuilder.java

/*
 * #%L
 * wcm.io
 * %%
 * Copyright (C) 2014 wcm.io
 * %%
 * 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.
 * #L%
 */
package io.wcm.caravan.io.http.request;

import static com.google.common.base.Preconditions.checkNotNull;

import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.HttpGet;
import org.osgi.annotation.versioning.ProviderType;

import com.damnhandy.uri.template.UriTemplate;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;

import io.wcm.caravan.io.http.impl.CaravanHttpHelper;

/**
 * UriTemplate using HTTP request builder.
 */
@ProviderType
public final class CaravanHttpRequestBuilder {

  private final String serviceId;
  private String method = HttpGet.METHOD_NAME;
  private StringBuilder path = new StringBuilder();
  private Set<String> queryNames = Sets.newHashSet();
  private Map<String, Object> values = Maps.newHashMap();
  private final Multimap<String, String> headers = ArrayListMultimap.create();
  private Charset charset;
  private byte[] body;
  private String bodyTemplate;

  /**
   * Default constructor.
   */
  public CaravanHttpRequestBuilder() {
    serviceId = null;
  }

  /**
   * @param serviceId Logical service ID. Can be null.
   */
  public CaravanHttpRequestBuilder(String serviceId) {
    this.serviceId = serviceId;
  }

  /**
   * @param correlationId Correlation Id. Can be null.
   * @return Builder
   */
  public CaravanHttpRequestBuilder correlationId(String correlationId) {
    if (correlationId != null) {
      header(CaravanHttpRequest.CORRELATION_ID_HEADER_NAME, ImmutableList.of(correlationId));
    }
    return this;
  }

  /**
   * @see CaravanHttpRequest#getMethod()
   * @param newMethod HTTP method
   * @return Builder
   */
  public CaravanHttpRequestBuilder method(String newMethod) {
    this.method = checkNotNull(newMethod, "method");
    return this;
  }

  /**
   * Adds a header to the HTTP request
   * @see CaravanHttpRequest#getHeaders()
   * @param name Header name
   * @param value Header value
   * @return Builder
   */
  public CaravanHttpRequestBuilder header(String name, String value) {
    headers.put(name, value);
    return this;
  }

  /**
   * Sets and replaces a header for the HTTP request
   * @see CaravanHttpRequest#getHeaders()
   * @param name Header name
   * @param headerValues Header values
   * @return Builder
   */
  public CaravanHttpRequestBuilder header(String name, Collection<String> headerValues) {
    headers.putAll(name, headerValues);
    return this;
  }

  /**
   * Appends a URL fragment.
   * @param urlFragment URL fragment
   * @return Builder
   */
  public CaravanHttpRequestBuilder append(String urlFragment) {
    this.path.append(urlFragment);
    return this;
  }

  /**
   * Adds a query template expression.
   * @param name Query parameter name
   * @return Builder
   */
  public CaravanHttpRequestBuilder query(String name) {
    queryNames.add(name);
    return this;
  }

  /**
   * Adds a parameter with value to the request query
   * @see CaravanHttpRequest#getUrl()
   * @param name Parameter name
   * @param value Parameter value
   * @return Builder
   */
  public CaravanHttpRequestBuilder query(String name, Object value) {
    return query(name).value(name, value);
  }

  /**
   * Adds a value for any UriTemplate expression in URL, header or body.
   * @param name Parameter name
   * @param value Parameter value
   * @return Builder
   */
  public CaravanHttpRequestBuilder value(String name, Object value) {
    values.put(name, value);
    return this;
  }

  /**
   * Sets a template for the HTTP body.
   * @see CaravanHttpRequest#getBody()
   * @param template Body template
   * @return Builder
   */
  public CaravanHttpRequestBuilder body(String template) {
    body = null;
    charset = Charsets.UTF_8;
    bodyTemplate = template;
    return this;
  }

  /**
   * Sets the HTTP body and charset.
   * @see CaravanHttpRequest#getBody()
   * @param newBody HTTP body
   * @param newCharset HTTP charset
   * @return Builder
   */
  public CaravanHttpRequestBuilder body(byte[] newBody, Charset newCharset) {
    body = newBody;
    charset = newCharset;
    bodyTemplate = null;
    return this;
  }

  /**
   * Creates the request object with no values for the templates.
   * @return Request
   */
  public CaravanHttpRequest build() {
    return build(Collections.emptyMap());
  }

  /**
   * Creates the request object with given values for the templates.
   * @param parameters Template values
   * @return Request
   */
  public CaravanHttpRequest build(Map<String, Object> parameters) {
    String expandedUrl = getExpandedUrl(parameters);
    Multimap<String, String> expandedHeaders = getExpandedHeaders(parameters);
    byte[] expandedBody = getExpandedBody(parameters);
    return new CaravanHttpRequest(serviceId, method, expandedUrl, expandedHeaders, expandedBody, charset);
  }

  private String getExpandedUrl(Map<String, Object> parameters) {

    Map<String, Object> mergedParams = Maps.newHashMap(parameters);
    mergedParams.putAll(values);
    List<String> sortedQueryNames = Lists.newArrayList(queryNames);
    Collections.sort(sortedQueryNames);
    String operator = path.indexOf("?") == -1 ? "?" : "&";
    String query = queryNames.isEmpty() ? "" : ('{' + operator + StringUtils.join(queryNames, ',') + '}');
    return UriTemplate.fromTemplate(path + query).expand(mergedParams);

  }

  private Multimap<String, String> getExpandedHeaders(Map<String, Object> parameters) {
    Multimap<String, String> expandedHeaders = ArrayListMultimap.create();
    headers.entries().forEach(entry -> {
      String template = entry.getValue();
      String expanded = UriTemplate.expand(template, parameters);
      if (!Strings.isNullOrEmpty(expanded)) {
        expandedHeaders.put(entry.getKey(), expanded);
      }
    });
    return expandedHeaders;
  }

  private byte[] getExpandedBody(Map<String, Object> parameters) {
    if (bodyTemplate == null) {
      return body;
    }
    return CaravanHttpHelper.urlDecode(UriTemplate.expand(bodyTemplate, parameters)).getBytes(Charsets.UTF_8);
  }

}