Package org.springframework.data.rest.shell.commands

Source Code of org.springframework.data.rest.shell.commands.HttpCommands

package org.springframework.data.rest.shell.commands;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.data.rest.shell.context.ResponseEvent;
import org.springframework.data.rest.shell.formatter.FormatProvider;
import org.springframework.data.rest.shell.formatter.Formatter;
import org.springframework.hateoas.Link;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import org.springframework.shell.support.util.OsUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.client.*;
import org.springframework.web.util.UriComponentsBuilder;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
* Commands that issue the HTTP requests.
*
* @author Jon Brisbin
*/
@Component
public class HttpCommands implements CommandMarker, ApplicationEventPublisherAware, InitializingBean {

  private static final Logger LOG             = LoggerFactory.getLogger(HttpCommands.class);
  private static final String LOCATION_HEADER = "Location";
  @Autowired
  private ConfigurationCommands configCmds;
  @Autowired
  private DiscoveryCommands     discoveryCmds;
  @Autowired
  private ContextCommands       contextCmds;
  @Autowired
  private SslCommands           sslCmds;
  private SslAwareClientHttpRequestFactory requestFactory = new SslAwareClientHttpRequestFactory();
  @Autowired(required = false)
  private RestTemplate                     restTemplate   = new RestTemplate(requestFactory);
  @Autowired(required = false)
  private ObjectMapper                     mapper         = new ObjectMapper();
  private ApplicationEventPublisher ctx;
  private Object                    lastResult;
  private URI                       requestUri;
  @Autowired
  private FormatProvider            formatProvider;

  private static String encode(String s) {
    try {
      return URLEncoder.encode(s, "ISO-8859-1");
    } catch (UnsupportedEncodingException e) {
      throw new IllegalStateException(e);
    }
  }

  @Override
  public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
    this.ctx = applicationEventPublisher;
  }

  @Override
  public void afterPropertiesSet() throws Exception {
    mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
    mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);

    restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
      @Override
      public void handleError(ClientHttpResponse response) throws IOException {
      }
    });
  }

  @CliAvailabilityIndicator({"timeout", "get", "post", "put", "delete"})
  public boolean isHttpCommandAvailable() {
    return true;
  }

  @CliCommand(value = "timeout", help = "Set the read timeout for requests.")
  public void timeout(@CliOption(key = "",
                                 mandatory = true,
                                 help = "The timeout (in milliseconds) to wait for a response.",
                                 unspecifiedDefaultValue = "30000") int timeout) {
    requestFactory.setReadTimeout(timeout);
  }

  /**
   * HTTP GET to retrieve a resource.
   *
   * @param path   URI to resource.
   * @param params URL query parameters to pass for paging and search.
   * @return
   */
  @CliCommand(value = "get", help = "Issue HTTP GET to a resource.")
  public String get(
      @CliOption(key = {"", "rel"},
                 mandatory = false,
                 help = "The path to the resource to GET.",
                 unspecifiedDefaultValue = "") PathOrRel path,
      @CliOption(key = "follow",
                 mandatory = false,
                 help = "If a Location header is returned, immediately follow it.",
                 unspecifiedDefaultValue = "false") final String follow,
      @CliOption(key = "params",
                 mandatory = false,
                 help = "Query parameters to add to the URL as a simplified JSON fragment '{paramName:\"paramValue\"}'.") Map params,
      @CliOption(key = "output",
                 mandatory = false,
                 help = "The path to dump the output to.") String outputPath) {

    outputPath = contextCmds.evalAsString(outputPath);

    UriComponentsBuilder ucb = createUriComponentsBuilder(path.getPath());
    if (null != params) {
      for (Object key : params.keySet()) {
        Object o = params.get(key);
        ucb.queryParam(key.toString(), encode(o.toString()));
      }
    }
    requestUri = ucb.build().toUri();

    return execute(HttpMethod.GET, null, follow, outputPath);
  }

  /**
   * HTTP POST to create a new resource.
   *
   * @param path URI to resource.
   * @param data The JSON data to send.
   * @return
   */
  @CliCommand(value = "post", help = "Issue HTTP POST to create a new resource.")
  public String post(
      @CliOption(key = {"", "rel"},
                 mandatory = false,
                 help = "The path to the resource collection.",
                 unspecifiedDefaultValue = "") PathOrRel path,
      @CliOption(key = "data",
                 mandatory = false,
                 help = "The JSON data to use as the resource.") String data,
      @CliOption(key = "from",
                 mandatory = false,
                 help = "The directory from which to read JSON files to POST to the server.") String fromDir,
      @CliOption(key = "follow",
                 mandatory = false,
                 help = "If a Location header is returned, immediately follow it.",
                 unspecifiedDefaultValue = "false") final String follow,
      @CliOption(key = "params",
                 mandatory = false,
                 help = "Query parameters to add to the URL as a simplified JSON fragment '{paramName:\"paramValue\"}'.") Map params,
      @CliOption(key = "output",
                 mandatory = false,
                 help = "The path to dump the output to.") String outputTo) throws IOException {

    fromDir = contextCmds.evalAsString(fromDir);
    final String outputPath = contextCmds.evalAsString(outputTo);

    UriComponentsBuilder ucb = createUriComponentsBuilder(path.getPath());
    if (null != params) {
      for (Object key : params.keySet()) {
        Object o = params.get(key);
        ucb.queryParam(key.toString(), encode(o.toString()));
      }
    }
    requestUri = ucb.build().toUri();

    Object obj = null;
    if (null != data) {
      if (data.contains("#{")) {
        obj = contextCmds.eval(data);
      } else if (data.startsWith("[") || data.startsWith("{")) {
        Class<?> targetType = Map.class;
        if (data.startsWith("[")) {
          targetType = List.class;
        }
        obj = mapper.readValue(data.replaceAll("\\\\", "").replaceAll("'", "\""), targetType);
      } else {
        obj = data;
      }
    }

    if (null != fromDir) {
      fromDir = contextCmds.evalAsString(fromDir);
      try {
        return readFileOrFiles(HttpMethod.POST, fromDir, follow, outputPath);
      } catch (IOException e) {
        throw new IllegalStateException(e);
      }
    }

    return execute(HttpMethod.POST, obj, follow, outputPath);
  }

  /**
   * HTTP PUT to update a resource.
   *
   * @param path URI to resource.
   * @param data The JSON data to send.
   * @return
   */
  @CliCommand(value = "put", help = "Issue HTTP PUT to update a resource.")
  public String put(
      @CliOption(key = {"", "rel"},
                 mandatory = false,
                 help = "The path to the resource.",
                 unspecifiedDefaultValue = "") PathOrRel path,
      @CliOption(key = "data",
                 mandatory = false,
                 help = "The JSON data to use as the resource.") String data,
      @CliOption(key = "from",
                 mandatory = false,
                 help = "The directory from which to read JSON files to POST to the server.") String fromDir,
      @CliOption(key = "follow",
                 mandatory = false,
                 help = "If a Location header is returned, immediately follow it.",
                 unspecifiedDefaultValue = "false") final String follow,
      @CliOption(key = "params",
                 mandatory = false,
                 help = "Query parameters to add to the URL as a simplified JSON fragment '{paramName:\"paramValue\"}'.") Map params,
      @CliOption(key = "output",
                 mandatory = false,
                 help = "The path to dump the output to.") String outputPath) throws IOException {

    fromDir = contextCmds.evalAsString(fromDir);
    outputPath = contextCmds.evalAsString(outputPath);

    UriComponentsBuilder ucb = createUriComponentsBuilder(path.getPath());
    if (null != params) {
      for (Object key : params.keySet()) {
        Object o = params.get(key);
        ucb.queryParam(key.toString(), encode(o.toString()));
      }
    }
    requestUri = ucb.build().toUri();

    Object obj;
    if (null != data) {
      if (data.contains("#{")) {
        obj = contextCmds.eval(data);
      } else if (data.startsWith("[") || data.startsWith("{")) {
        Class<?> targetType = Map.class;
        if (data.startsWith("[")) {
          targetType = List.class;
        }
        try {
          obj = mapper.readValue(data.replaceAll("\\\\", "").replaceAll("'", "\""), targetType);
        } catch (JsonParseException e) {
          LOG.error(e.getMessage(), e);
          throw new IllegalStateException(e.getMessage(), e);
        }
      } else {
        obj = data;
      }
      return execute(HttpMethod.PUT, obj, follow, outputPath);
    }

    if (null != fromDir) {
      fromDir = contextCmds.evalAsString(fromDir);
      try {
        return readFileOrFiles(HttpMethod.PUT, fromDir, "false", outputPath);
      } catch (IOException e) {
        throw new IllegalStateException(e);
      }
    }

    return null;
  }

  /**
   * HTTP DELETE to delete a resource.
   *
   * @param path URI to resource.
   * @return
   */
  @CliCommand(value = "delete", help = "Issue HTTP DELETE to delete a resource.")
  public String delete(
      @CliOption(key = {"", "rel"},
                 mandatory = false,
                 help = "Issue HTTP DELETE to delete a resource.",
                 unspecifiedDefaultValue = "") PathOrRel path,
      @CliOption(key = "follow",
                 mandatory = false,
                 help = "If a Location header is returned, immediately follow it.",
                 unspecifiedDefaultValue = "false") final String follow,
      @CliOption(key = "params",
                 mandatory = false,
                 help = "Query parameters to add to the URL as a simplified JSON fragment '{paramName:\"paramValue\"}'.") Map params,
      @CliOption(key = "output",
                 mandatory = false,
                 help = "The path to dump the output to.") String outputPath) {

    outputPath = contextCmds.evalAsString(outputPath);

    UriComponentsBuilder ucb = createUriComponentsBuilder(path.getPath());
    if (null != params) {
      for (Object key : params.keySet()) {
        Object o = params.get(key);
        ucb.queryParam(key.toString(), encode(o.toString()));
      }
    }
    requestUri = ucb.build().toUri();

    return execute(HttpMethod.DELETE, null, follow, outputPath);
  }

  public String execute(final HttpMethod method,
                        final Object data,
                        final String follow,
                        final String outputPath) {
    final StringBuilder buffer = new StringBuilder();
    MediaType contentType = configCmds.getHeaders().getContentType();
    if (contentType == null) {
      contentType = MediaType.APPLICATION_JSON;
    }

    ResponseErrorHandler origErrHandler = restTemplate.getErrorHandler();
    RequestHelper helper = (null == data ? new RequestHelper() : new RequestHelper(data, contentType));
    ResponseEntity<String> response;
    try {
      restTemplate.setErrorHandler(new ResponseErrorHandler() {
        @Override
        public boolean hasError(ClientHttpResponse response) throws IOException {
          HttpStatus status = response.getStatusCode();
          return (status == HttpStatus.BAD_GATEWAY || status == HttpStatus.GATEWAY_TIMEOUT);
        }

        @Override
        public void handleError(ClientHttpResponse response) throws IOException {
          if (LOG.isWarnEnabled()) {
            LOG.warn("Client encountered an error " + response.getRawStatusCode() + ". Retrying...");
          }
          System.out.println(execute(method, data, follow, outputPath));
        }
      });

      if (LOG.isInfoEnabled()) {
        LOG.info("Sending " + method + " to " + requestUri + " using " + data);
      }
      response = restTemplate.execute(requestUri, method, helper, helper);

    } catch (ResourceAccessException e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Client encountered an error. Retrying. (" + e.getMessage() + ")", e);
      }
      // Calling this method recursively results in hang, so just retry once.
      response = restTemplate.execute(requestUri, method, helper, helper);
    } finally {
      restTemplate.setErrorHandler(origErrHandler);
    }

    if ("true".equals(follow) && response.getHeaders().containsKey(LOCATION_HEADER)) {
      try {
        configCmds.setBaseUri(response.getHeaders().getFirst(LOCATION_HEADER));
      } catch (URISyntaxException e) {
        LOG.error("Error following Location header: " + e.getMessage(), e);
      }
    }

    outputRequest(method.name(), requestUri, buffer);
    contextCmds.variables.put("response", response);
    ctx.publishEvent(new ResponseEvent(requestUri, method, response));
    outputResponse(response, buffer);

    if (null != outputPath) {
      FileWriter writer = null;
      try {
        writer = new FileWriter(new File(outputPath));
        writer.write(buffer.toString());
        writer.flush();
      } catch (IOException e) {
        LOG.error(e.getMessage(), e);
        throw new IllegalArgumentException(e);
      } finally {
        if (null != writer) {
          try {
            writer.close();
          } catch (IOException e) {
          }
        }
      }
      return "\n>> " + outputPath + "\n";
    } else {
      switch (response.getStatusCode()) {
        case BAD_REQUEST:
        case INTERNAL_SERVER_ERROR: {
          System.err.println(buffer.toString());
          return null;
        }
        default:
          return buffer.toString();
      }
    }
  }

  private String readFileOrFiles(final HttpMethod method,
                                 final String fromPath,
                                 final String follow,
                                 final String outputPath) throws IOException {
    String output;
    File fromFile = new File(fromPath);
    if (!fromFile.exists()) {
      throw new IllegalArgumentException("Path " + fromPath + " not found.");
    }

    if (fromFile.isDirectory()) {
      final AtomicInteger numItems = new AtomicInteger(0);

      FilenameFilter jsonFilter = new FilenameFilter() {
        @Override
        public boolean accept(File file, String s) {
          return s.endsWith(".json");
        }
      };
      for (File file : fromFile.listFiles(jsonFilter)) {
        Object body = readFile(file);
        String response = execute(method,
                                  body,
                                  follow,
                                  outputPath);
        if (LOG.isDebugEnabled()) {
          LOG.debug(response);
        }
        if (null != response) {
          numItems.incrementAndGet();
        }
      }

      output = "\n" + numItems.get() + " files successfully uploaded to the server using " + method + "\n";
    } else {
      Object body = readFile(fromFile);
      String response = execute(method,
                                body,
                                follow,
                                outputPath);
      if (LOG.isDebugEnabled()) {
        LOG.debug(response);
      }

      output = response;
    }

    return output;
  }

  private Object readFile(File file) throws IOException {
    StringBuilder builder = new StringBuilder();
    FileReader reader = new FileReader(file);
    char[] buffer = new char[8 * 1024];
    int read;
    while (-1 < (read = reader.read(buffer))) {
      String s = new String(buffer, 0, read);
      builder.append(s);
    }

    String bodyAsString = builder.toString();
    Object body = "";
    if (bodyAsString.length() > 0) {
      try {
        if (bodyAsString.charAt(0) == '{') {
          body = mapper.readValue(bodyAsString, Map.class);
        } else if (bodyAsString.charAt(0) == '[') {
          body = mapper.readValue(bodyAsString, List.class);
        } else {
          body = bodyAsString;
        }
      } catch (JsonParseException e) {
        LOG.error(e.getMessage(), e);
        throw new IllegalStateException(e.getMessage(), e);
      }
    }
    return body;
  }

  private UriComponentsBuilder createUriComponentsBuilder(String path) {
    UriComponentsBuilder ucb;
    if (discoveryCmds.getResources().containsKey(path)) {
      ucb = UriComponentsBuilder.fromUriString(discoveryCmds.getResources().get(path));
    } else {
      if (path.startsWith("http")) {
        ucb = UriComponentsBuilder.fromUriString(path);
      } else {
        ucb = UriComponentsBuilder.fromUri(configCmds.getBaseUri()).pathSegment(path);
      }
    }
    return ucb;
  }

  private void outputRequest(String method, URI requestUri, StringBuilder buffer) {
    buffer.append("> ")
          .append(method)
          .append(" ")
          .append(requestUri.toString())
          .append(OsUtils.LINE_SEPARATOR);
    for (Map.Entry<String, String> entry : configCmds.getHeaders().toSingleValueMap().entrySet()) {
      buffer.append("> ")
            .append(entry.getKey())
            .append(": ")
            .append(entry.getValue())
            .append(OsUtils.LINE_SEPARATOR);
    }
    buffer.append(OsUtils.LINE_SEPARATOR);
  }

  private void outputResponse(ResponseEntity<String> response, StringBuilder buffer) {
    buffer.append("< ")
          .append(response.getStatusCode().value())
          .append(" ")
          .append(response.getStatusCode().name())
          .append(OsUtils.LINE_SEPARATOR);
    for (Map.Entry<String, List<String>> entry : response.getHeaders().entrySet()) {
      buffer.append("< ")
            .append(entry.getKey())
            .append(": ");
      boolean first = true;
      for (String s : entry.getValue()) {
        if (!first) {
          buffer.append(",");
        } else {
          first = false;
        }
        buffer.append(s);
      }
      buffer.append(OsUtils.LINE_SEPARATOR);
    }
    buffer.append("< ").append(OsUtils.LINE_SEPARATOR);
    if (null != response.getBody()) {
      final Formatter formatter = formatProvider.getFormatter(response.getHeaders().getContentType().getSubtype());
      buffer.append(formatter.format(response.getBody()));
    }
  }

  private class RequestHelper implements RequestCallback,
                                         ResponseExtractor<ResponseEntity<String>> {

    private Object    body;
    private MediaType contentType;
    private HttpMessageConverterExtractor<String> extractor =
        new HttpMessageConverterExtractor<String>(String.class,
                                                  restTemplate.getMessageConverters());
    private ObjectMapper                          mapper    = new ObjectMapper();

    {
      mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
    }

    private RequestHelper() {
    }

    private RequestHelper(Object body, MediaType contentType) {
      this.body = body;
      this.contentType = contentType;
    }

    @Override
    public void doWithRequest(ClientHttpRequest request) throws IOException {
      request.getHeaders().setAll(configCmds.getHeaders().toSingleValueMap());
      if (null != contentType) {
        request.getHeaders().setContentType(contentType);
      }
      if (null != body) {
        if (body instanceof String) {
          request.getBody().write(((String) body).getBytes());
        } else if (body instanceof byte[]) {
          request.getBody().write((byte[]) body);
        } else {
          try {
            mapper.writeValue(request.getBody(), body);
          } catch (JsonParseException e) {
            LOG.error(e.getMessage(), e);
            throw new IllegalStateException(e.getMessage(), e);
          }
        }
      }
      //contextCmds.variables.put("request", request);
    }

    @SuppressWarnings({"unchecked"})
    @Override
    public ResponseEntity<String> extractData(ClientHttpResponse response) throws IOException {
      String body = extractor.extractData(response);
      contextCmds.variables.put("requestUrl", requestUri.toString());
      contextCmds.variables.put("responseHeaders", response.getHeaders());
      contextCmds.variables.put("responseBody", null);

      MediaType ct = response.getHeaders().getContentType();
      if (null != body && null != ct && ct.getSubtype().endsWith("json")) {
        // Pretty-print the JSON
        try {
          if (body.startsWith("{")) {
            lastResult = mapper.readValue(body.getBytes(), Map.class);
          } else if (body.startsWith("[")) {
            lastResult = mapper.readValue(body.getBytes(), List.class);
          } else {
            lastResult = new String(body.getBytes());
          }
        } catch (JsonParseException e) {
          LOG.error(e.getMessage(), e);
          throw new IllegalStateException(e.getMessage(), e);
        }

        contextCmds.variables.put("responseBody", lastResult);

        if (lastResult instanceof Map && ((Map) lastResult).containsKey("links")) {
          Links linksobj;
          if (contextCmds.variables.containsKey("links")) {
            linksobj = (Links) contextCmds.variables.get("links");
          } else {
            linksobj = new Links();
            contextCmds.evalCtx.addPropertyAccessor(linksobj.getPropertyAccessor());
          }
          linksobj.getLinks().clear();
          for (Map<String, String> linkmap : (List<Map<String, String>>) ((Map) lastResult).get("links")) {
            linksobj.addLink(new Link(linkmap.get("href"), linkmap.get("rel")));
          }
          contextCmds.variables.put("links", linksobj);
        }

        StringWriter sw = new StringWriter();
        try {
          mapper.writeValue(sw, lastResult);
        } catch (JsonParseException e) {
          LOG.error(e.getMessage(), e);
          throw new IllegalStateException(e.getMessage(), e);
        }
        body = sw.toString();
      }

      return new ResponseEntity<String>(body, response.getHeaders(), response.getStatusCode());
    }
  }

  private class SslAwareClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
    @Override
    protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
      if (connection instanceof HttpsURLConnection) {
        HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
        if (!sslCmds.getValidate()) {
          httpsConnection.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String s, SSLSession sslSession) {
              return true;
            }
          });
          httpsConnection.setSSLSocketFactory(sslCmds.getCustomContext().getSocketFactory());
        }
      }
      super.prepareConnection(connection, httpMethod);
    }
  }
}
TOP

Related Classes of org.springframework.data.rest.shell.commands.HttpCommands

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.