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

org.apache.geode.management.internal.web.shell.AbstractHttpOperationInvoker Maven / Gradle / Ivy

Go to download

Apache Geode provides a database-like consistency model, reliable transaction processing and a shared-nothing architecture to maintain very low latency performance with high concurrency processing

There is a newer version: 1.15.1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.management.internal.web.shell;

import org.apache.geode.internal.GemFireVersion;
import org.apache.geode.internal.lang.StringUtils;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.internal.util.IOUtils;
import org.apache.geode.management.DistributedSystemMXBean;
import org.apache.geode.management.internal.MBeanJMXAdapter;
import org.apache.geode.management.internal.ManagementConstants;
import org.apache.geode.management.internal.cli.shell.Gfsh;
import org.apache.geode.management.internal.web.domain.Link;
import org.apache.geode.management.internal.web.domain.QueryParameterSource;
import org.apache.geode.management.internal.web.http.ClientHttpRequest;
import org.apache.geode.management.internal.web.http.HttpHeader;
import org.apache.geode.management.internal.web.http.HttpMethod;
import org.apache.geode.management.internal.web.http.converter.SerializableObjectHttpMessageConverter;
import org.apache.geode.management.internal.web.shell.support.HttpMBeanProxyFactory;
import org.apache.geode.management.internal.web.util.UriUtils;
import org.apache.geode.security.AuthenticationFailedException;
import org.apache.geode.security.NotAuthorizedException;
import org.apache.logging.log4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.management.ObjectName;
import javax.management.QueryExp;

/**
 * The AbstractHttpOperationInvoker class is an abstract base class encapsulating common
 * functionality for all HTTP-based OperationInvoker implementations.
 * 
 * @see org.apache.geode.management.internal.cli.shell.Gfsh
 * @see org.apache.geode.management.internal.cli.shell.OperationInvoker
 * @see org.apache.geode.management.internal.web.shell.HttpOperationInvoker
 * @see org.apache.geode.management.internal.web.shell.RestHttpOperationInvoker
 * @see org.apache.geode.management.internal.web.shell.SimpleHttpOperationInvoker
 * @see org.springframework.http.client.SimpleClientHttpRequestFactory
 * @see org.springframework.web.client.RestTemplate
 * @since GemFire 8.0
 */
@SuppressWarnings("unused")
public abstract class AbstractHttpOperationInvoker implements HttpOperationInvoker {

  protected static final long DEFAULT_INITIAL_DELAY = TimeUnit.SECONDS.toMillis(1);
  protected static final long DEFAULT_PERIOD = TimeUnit.MILLISECONDS.toMillis(500);

  protected static final String MBEAN_ATTRIBUTE_LINK_RELATION = "mbean-attribute";
  protected static final String MBEAN_OPERATION_LINK_RELATION = "mbean-operation";
  protected static final String MBEAN_QUERY_LINK_RELATION = "mbean-query";
  protected static final String PING_LINK_RELATION = "ping";
  protected static final String DEFAULT_ENCODING = UriUtils.DEFAULT_ENCODING;
  protected static final String REST_API_BASE_URL = "http://localhost:8080";
  protected static final String REST_API_VERSION = "/v1";
  protected static final String REST_API_WEB_APP_CONTEXT = "/gemfire";
  protected static final String REST_API_URL =
      REST_API_BASE_URL + REST_API_WEB_APP_CONTEXT + REST_API_VERSION;
  protected static final String USER_AGENT_HTTP_REQUEST_HEADER_VALUE =
      "GemFire-Shell/v" + GemFireVersion.getGemFireVersion();

  protected static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS;

  // the ID of the GemFire distributed system (cluster)
  private Integer clusterId = CLUSTER_ID_WHEN_NOT_CONNECTED;

  // Executor for scheduling periodic Runnable task to assess the state of the Manager's HTTP
  // service or Web Service
  // hosting the M&M REST API (interface)
  private final ScheduledExecutorService executorService;

  // a reference to the GemFire shell (Gfsh) instance using this HTTP-based OperationInvoker for
  // command execution
  // and processing
  private final Gfsh gfsh;

  // a list of acceptable content/media types supported by Gfsh
  private final List acceptableMediaTypes =
      Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN);

  // a Java Logger used to log severe, warning, informational and debug messages during the
  // operation of this invoker
  private final Logger logger = LogService.getLogger();

  // the Spring RestTemplate used to send HTTP requests and make REST API calls
  private volatile RestTemplate restTemplate;

  // the base URL of the GemFire Manager's embedded HTTP service and REST API interface
  private final String baseUrl;


  protected Map securityProperties;

  /**
   * Default, public, no-arg constructor to create an instance of the AbstractHttpOperationInvoker
   * class for testing purposes.
   */
  AbstractHttpOperationInvoker(final String baseUrl) {
    this.baseUrl = baseUrl;
    this.executorService = null;
    this.gfsh = null;
    this.restTemplate = null;
  }

  /**
   * Constructs an instance of the AbstractHttpOperationInvoker class with a reference to the
   * GemFire shell (Gfsh) instance using this HTTP-based OperationInvoker to send commands to the
   * GemFire Manager via HTTP for processing.
   * 
   * @param gfsh a reference to the instance of the GemFire shell (Gfsh) using this HTTP-based
   *        OperationInvoker for command processing.
   * @throws AssertionError if the reference to the Gfsh instance is null.
   * @see #AbstractHttpOperationInvoker(org.apache.geode.management.internal.cli.shell.Gfsh, String,
   *      Map)
   * @see org.apache.geode.management.internal.cli.shell.Gfsh
   */
  public AbstractHttpOperationInvoker(final Gfsh gfsh, Map securityProperties) {
    this(gfsh, REST_API_URL, securityProperties);
  }

  /**
   * Constructs an instance of the AbstractHttpOperationInvoker class with a reference to the
   * GemFire shell (Gfsh) instance using this HTTP-based OperationInvoker to send commands to the
   * GemFire Manager via HTTP for procsessing along with the base URL to the GemFire Manager's
   * embedded HTTP service hosting the HTTP (REST) interface.
   * 
   * @param gfsh a reference to the instance of the GemFire shell (Gfsh) using this HTTP-based
   *        OperationInvoker for command processing.
   * @param baseUrl a String specifying the base URL to the GemFire Manager's embedded HTTP service
   *        hosting the REST interface.
   * @throws AssertionError if the reference to the Gfsh instance is null.
   * @see org.apache.geode.management.internal.cli.shell.Gfsh
   */
  public AbstractHttpOperationInvoker(final Gfsh gfsh, final String baseUrl,
      Map securityProperties) {
    assertNotNull(gfsh, "The reference to the GemFire shell (Gfsh) cannot be null!");

    this.gfsh = gfsh;
    this.baseUrl = StringUtils.defaultIfBlank(baseUrl, REST_API_URL);
    this.securityProperties = securityProperties;

    // constructs an instance of a single-threaded, scheduled Executor to send periodic HTTP
    // requests to the Manager's
    // HTTP service or Web Service to assess the "alive" state
    this.executorService = Executors.newSingleThreadScheduledExecutor();

    // constructs an instance of the Spring RestTemplate for M&M REST API (interface) operations
    this.restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory());

    // add our custom HttpMessageConverter for serializing DTO Objects into the HTTP request message
    // body
    // and de-serializing HTTP response message body content back into DTO Objects
    List> converters = this.restTemplate.getMessageConverters();
    // remove the MappingJacksonHttpConverter
    for (int i = converters.size() - 1; i >= 0; i--) {
      HttpMessageConverter converter = converters.get(i);
      if (converter instanceof MappingJackson2HttpMessageConverter) {
        converters.remove(converter);
      }
    }
    converters.add(new SerializableObjectHttpMessageConverter());

    // set the ResponseErrorHandler handling any errors originating from our HTTP request
    this.restTemplate.setErrorHandler(new ResponseErrorHandler() {
      @Override
      public boolean hasError(final ClientHttpResponse response) throws IOException {
        final HttpStatus status = response.getStatusCode();

        switch (status) {
          case BAD_REQUEST: // 400 *
          case UNAUTHORIZED: // 401
          case FORBIDDEN: // 403
          case NOT_FOUND: // 404 *
          case METHOD_NOT_ALLOWED: // 405 *
          case NOT_ACCEPTABLE: // 406 *
          case REQUEST_TIMEOUT: // 408
          case CONFLICT: // 409
          case REQUEST_ENTITY_TOO_LARGE: // 413
          case REQUEST_URI_TOO_LONG: // 414
          case UNSUPPORTED_MEDIA_TYPE: // 415 *
          case TOO_MANY_REQUESTS: // 429
          case INTERNAL_SERVER_ERROR: // 500 *
          case NOT_IMPLEMENTED: // 501
          case BAD_GATEWAY: // 502 ?
          case SERVICE_UNAVAILABLE: // 503
            return true;
          default:
            return false;
        }
      }

      @Override
      public void handleError(final ClientHttpResponse response) throws IOException {
        String body = readBody(response);
        final String message = String.format("The HTTP request failed with: %1$d - %2$s.",
            response.getRawStatusCode(), body);

        if (gfsh.getDebug()) {
          gfsh.logSevere(body, null);
        }

        if (response.getRawStatusCode() == 401) {
          throw new AuthenticationFailedException(message);
        } else if (response.getRawStatusCode() == 403) {
          throw new NotAuthorizedException(message);
        } else {
          throw new RuntimeException(message);
        }
      }

      private String readBody(final ClientHttpResponse response) throws IOException {
        BufferedReader responseBodyReader = null;

        try {
          responseBodyReader = new BufferedReader(new InputStreamReader(response.getBody()));

          final StringBuilder buffer = new StringBuilder();
          String line;

          while ((line = responseBodyReader.readLine()) != null) {
            buffer.append(line).append(StringUtils.LINE_SEPARATOR);
          }

          return buffer.toString().trim();
        } finally {
          IOUtils.close(responseBodyReader);
        }
      }
    });
  }

  /**
   * Asserts the argument is valid, as determined by the caller passing the result of an evaluated
   * expression to this assertion.
   * 
   * @param validArg a boolean value indicating the evaluation of the expression validating the
   *        argument.
   * @param message a String value used as the message when constructing an
   *        IllegalArgumentException.
   * @param args Object arguments used to populate placeholder's in the message.
   * @throws IllegalArgumentException if the argument is not valid.
   * @see java.lang.String#format(String, Object...)
   */
  protected static void assertArgument(final boolean validArg, final String message,
      final Object... args) {
    if (!validArg) {
      throw new IllegalArgumentException(String.format(message, args));
    }
  }

  /**
   * Asserts the Object reference is not null!
   * 
   * @param obj the reference to the Object.
   * @param message the String value used as the message when constructing and throwing a
   *        NullPointerException.
   * @param args Object arguments used to populate placeholder's in the message.
   * @throws NullPointerException if the Object reference is null.
   * @see java.lang.String#format(String, Object...)
   */
  protected static void assertNotNull(final Object obj, final String message,
      final Object... args) {
    if (obj == null) {
      throw new NullPointerException(String.format(message, args));
    }
  }

  /**
   * Asserts whether state, based on the evaluation of a conditional expression, passed to this
   * assertion is valid.
   * 
   * @param validState a boolean value indicating the evaluation of the expression from which the
   *        conditional state is based. For example, a caller might use an expression of the form
   *        (initableObj.isInitialized()).
   * @param message a String values used as the message when constructing an IllegalStateException.
   * @param args Object arguments used to populate placeholder's in the message.
   * @throws IllegalStateException if the conditional state is not valid.
   * @see java.lang.String#format(String, Object...)
   */
  protected static void assertState(final boolean validState, final String message,
      final Object... args) {
    if (!validState) {
      throw new IllegalStateException(String.format(message, args));
    }
  }

  /**
   * Gets a list of acceptable content/media types supported by Gfsh.
   * 
   * @return a List of acceptable content/media types supported by Gfsh.
   * @see org.springframework.http.MediaType
   */
  protected List getAcceptableMediaTypes() {
    return acceptableMediaTypes;
  }

  /**
   * Returns the base URL to GemFire's REST interface hosted in the GemFire Manager's embedded HTTP
   * service (Tomcat server).
   * 
   * @return a String value specifying the base URL to the GemFire REST interface.
   */
  protected String getBaseUrl() {
    return this.baseUrl;
  }

  /**
   * Determines whether Gfsh is in debug mode (or whether the user enabled debugging in Gfsh).
   * 
   * @return a boolean value indicating if debugging has been turned on in Gfsh.
   * @see org.apache.geode.management.internal.cli.shell.Gfsh#getDebug()
   */
  protected boolean isDebugEnabled() {
    return getGfsh().getDebug();
  }

  /**
   * Gets the ExecutorService used by this HTTP OperationInvoker to scheduled periodic or delayed
   * tasks.
   * 
   * @return an instance of the ScheduledExecutorService for scheduling periodic or delayed tasks.
   * @see java.util.concurrent.ScheduledExecutorService
   */
  protected final ScheduledExecutorService getExecutorService() {
    assertState(this.executorService != null,
        "The ExecutorService for this HTTP OperationInvoker (%1$s) was not properly initialized!",
        getClass().getName());
    return this.executorService;
  }

  /**
   * Returns the reference to the GemFire shell (Gfsh) instance using this HTTP-based
   * OperationInvoker to send commands to the GemFire Manager for remote execution and processing.
   * 
   * @return a reference to the instance of the GemFire shell (Gfsh) using this HTTP-based
   *         OperationInvoker to process commands.
   * @see org.apache.geode.management.internal.cli.shell.Gfsh
   */
  protected final Gfsh getGfsh() {
    return this.gfsh;
  }

  /**
   * Returns a reference to the Spring RestTemplate used by this HTTP-based OperationInvoker to send
   * HTTP requests to GemFire's REST interface, making REST API calls.
   * 
   * @return an instance of the Spring RestTemplate used to make REST API web service calls.
   * @see org.springframework.web.client.RestTemplate
   */
  protected final RestTemplate getRestTemplate() {
    return this.restTemplate;
  }

  /**
   * Creates an instance of a client HTTP request with the specified Link targeting the resource as
   * well as the intended operation on the resource.
   * 
   * @param link a Link with the URI targeting and identifying the resource as well as the method of
   *        operation on the resource.
   * @return a client HTTP request with the details of the request.
   * @see org.apache.geode.management.internal.web.http.ClientHttpRequest
   * @see org.apache.geode.management.internal.web.domain.Link
   */
  protected ClientHttpRequest createHttpRequest(final Link link) {
    final ClientHttpRequest request = new ClientHttpRequest(link);
    request.addHeaderValues(HttpHeader.USER_AGENT.getName(), USER_AGENT_HTTP_REQUEST_HEADER_VALUE);
    request.getHeaders().setAccept(getAcceptableMediaTypes());

    if (this.securityProperties != null) {
      Iterator> it = this.securityProperties.entrySet().iterator();
      while (it.hasNext()) {
        Entry entry = it.next();
        request.addHeaderValues(entry.getKey(), entry.getValue());
      }
    }
    return request;
  }

  /**
   * Creates a Link with the specified relation and URI of the remote resource.
   * 
   * @param relation a String indicating the link relation, or relative state transition, operation.
   * @param href the URI identifying the resource and it's location.
   * @return a Link with the providing relation and URI.
   * @see org.apache.geode.management.internal.web.domain.Link
   * @see java.net.URI
   */
  protected Link createLink(final String relation, final URI href) {
    return new Link(relation, href);
  }

  /**
   * Creates a Link with the specified relation and URI of the remote resource along with the method
   * of the operation.
   * 
   * @param relation a String indicating the link relation, or relative state transition, operation.
   * @param href the URI identifying the resource and it's location.
   * @param method the HTTP method for the operation of the request.
   * @return a Link with the providing relation and URI.
   * @see org.apache.geode.management.internal.web.http.HttpMethod
   * @see org.apache.geode.management.internal.web.domain.Link
   * @see java.net.URI
   */
  protected Link createLink(final String relation, final URI href, final HttpMethod method) {
    return new Link(relation, href, method);
  }

  /**
   * Decodes the encoded String value using the default encoding UTF-8. It is assumed the String
   * value was encoded with the URLEncoder using the UTF-8 encoding. This method handles
   * UnsupportedEncodingException by just returning the encodedValue.
   * 
   * @param encodedValue the encoded String value to decode.
   * @return the decoded value of the String or encodedValue if the UTF-8 encoding is unsupported.
   * @see org.apache.geode.management.internal.web.util.UriUtils#decode(String)
   */
  protected String decode(final String encodedValue) {
    return UriUtils.decode(encodedValue);
  }

  /**
   * Decodes the encoded String value using the specified encoding (such as UTF-8). It is assumed
   * the String value was encoded with the URLEncoder using the specified encoding. This method
   * handles UnsupportedEncodingException by just returning the encodedValue.
   * 
   * @param encodedValue a String value encoded in the encoding.
   * @param encoding a String value specifying the encoding.
   * @return the decoded value of the String or encodedValue if the specified encoding is
   *         unsupported.
   * @see org.apache.geode.management.internal.web.util.UriUtils#decode(String, String)
   */
  protected String decode(final String encodedValue, String encoding) {
    return UriUtils.decode(encodedValue, encoding);
  }

  /**
   * Encode the String value using the default encoding UTF-8.
   * 
   * @param value the String value to encode.
   * @return an encoded value of the String using the default encoding UTF-8 or value if the UTF-8
   *         encoding is unsupported.
   * @see org.apache.geode.management.internal.web.util.UriUtils#encode(String)
   */
  protected String encode(final String value) {
    return UriUtils.encode(value);
  }

  /**
   * Encode the String value using the specified encoding (such as UTF-8).
   * 
   * @param value the String value to encode.
   * @param encoding a String value indicating the encoding.
   * @return an encoded value of the String using the specified encoding or value if the specified
   *         encoding is unsupported.
   * @see org.apache.geode.management.internal.web.util.UriUtils#encode(String, String)
   */
  protected String encode(final String value, final String encoding) {
    return UriUtils.encode(value, encoding);
  }

  /**
   * Finds a Link containing the HTTP request URI for the relational operation (state transition) on
   * the resource.
   * 
   * @param relation a String describing the relational operation, or state transition on the
   *        resource.
   * @return an instance of Link containing the HTTP request URI used to perform the intended
   *         operation on the resource.
   * @see org.apache.geode.management.internal.web.domain.Link
   */
  protected Link findLink(final String relation) {
    return null;
  }

  /**
   * Handles resource access errors such as ConnectExceptions when the server-side process/service
   * is not listening for client connections, or the connection to the server/service fails.
   * 
   * @param e the ResourceAccessException resulting in some sort of I/O error.
   * @return a user-friendly String message describing the problem and appropriate action/response
   *         by the user.
   * @see #stop()
   * @see org.springframework.web.client.ResourceAccessException
   */
  protected String handleResourceAccessException(final ResourceAccessException e) {
    stop();

    return String.format(
        "The connection to the GemFire Manager's HTTP service @ %1$s failed with: %2$s. "
            + "Please try reconnecting or see the GemFire Manager's log file for further details.",
        getBaseUrl(), e.getMessage());
  }

  /**
   * Displays the message inside GemFire shell at debug level.
   * 
   * @param message the String containing the message to display inside Gfsh.
   * @see #isDebugEnabled()
   * @see #printInfo(String, Object...)
   */
  protected void printDebug(final String message, final Object... args) {
    if (isDebugEnabled()) {
      printInfo(message, args);
    }
  }

  /**
   * Displays the message inside GemFire shell at info level.
   * 
   * @param message the String containing the message to display inside Gfsh.
   * @see org.apache.geode.management.internal.cli.shell.Gfsh#printAsInfo(String)
   */
  protected void printInfo(final String message, final Object... args) {
    getGfsh().printAsInfo(String.format(message, args));
  }

  /**
   * Displays the message inside GemFire shell at warning level.
   * 
   * @param message the String containing the message to display inside Gfsh.
   * @see org.apache.geode.management.internal.cli.shell.Gfsh#printAsWarning(String)
   */
  protected void printWarning(final String message, final Object... args) {
    getGfsh().printAsWarning(String.format(message, args));
  }

  /**
   * Displays the message inside GemFire shell at severe level.
   * 
   * @param message the String containing the message to display inside Gfsh.
   * @see org.apache.geode.management.internal.cli.shell.Gfsh#printAsSevere(String)
   */
  protected void printSevere(final String message, final Object... args) {
    getGfsh().printAsSevere(String.format(message, args));
  }

  /**
   * Sends the HTTP request, using Spring's RestTemplate, to the GemFire REST API web service
   * endpoint, expecting the specified response type from the server in return.
   * 
   * @param  the response type.
   * @param request the client HTTP request to send.
   * @param responseType the expected Class type of the return value in the server's response.
   * @return a ResponseEntity encapsulating the details of the server's response to the client's
   *         HTTP request.
   * @see #send(org.apache.geode.management.internal.web.http.ClientHttpRequest, Class,
   *      java.util.Map)
   * @see org.apache.geode.management.internal.web.http.ClientHttpRequest
   * @see org.springframework.http.ResponseEntity
   */
  protected  ResponseEntity send(final ClientHttpRequest request,
      final Class responseType) {
    return send(request, responseType, Collections.emptyMap());
  }

  /**
   * Sends the HTTP request, using Spring's RestTemplate, to the GemFire REST API web service
   * endpoint, expecting the specified response type from the server in return.
   * 
   * @param request the client HTTP request to send.
   * @param responseType the expected Class type of the return value in the server's response.
   * @param uriVariables a Mapping of URI template path variables to values.
   * @return a ResponseEntity encapsulating the details of the server's response to the client's
   *         HTTP request.
   * @see java.net.URI
   * @see org.apache.geode.management.internal.web.http.ClientHttpRequest
   * @see org.springframework.http.ResponseEntity
   * @see org.springframework.web.client.RestTemplate#exchange(java.net.URI,
   *      org.springframework.http.HttpMethod, org.springframework.http.HttpEntity, Class)
   */
  protected  ResponseEntity send(final ClientHttpRequest request, final Class responseType,
      final Map uriVariables) {
    final URI url = request.getURL(uriVariables);

    if (isDebugEnabled()) {
      printInfo("Link: %1$s", request.getLink().toHttpRequestLine());
      printInfo("HTTP URL: %1$s", url);
      printInfo("HTTP request headers: %1$s", request.getHeaders());
      printInfo("HTTP request parameters: %1$s", request.getParameters());
    }

    final ResponseEntity response = getRestTemplate().exchange(url, request.getMethod(),
        request.createRequestEntity(), responseType);

    if (isDebugEnabled()) {
      printInfo("------------------------------------------------------------------------");
      printInfo("HTTP response headers: %1$s", response.getHeaders());
      printInfo("HTTP response status: %1$d - %2$s", response.getStatusCode().value(),
          response.getStatusCode().getReasonPhrase());

      printInfo("HTTP response body: ", response.getBody());
    }

    return response;
  }

  /**
   * Determines whether this HTTP-based OperationInvoker is successfully connected to the remote
   * GemFire Manager's HTTP service in order to send commands for execution/processing.
   * 
   * @return a boolean value indicating the connection state of the HTTP-based OperationInvoker.
   */
  @Override
  public boolean isConnected() {
    return (getRestTemplate() != null);
  }

  /**
   * Determines whether this HTTP-based OperationInvoker is ready to send commands to the GemFire
   * Manager for remote execution/processing.
   * 
   * @return a boolean value indicating whether this HTTP-based OperationInvoker is ready for
   *         command invocations.
   * @see #isConnected()
   */
  @Override
  public boolean isReady() {
    return isConnected();
  }

  // TODO research the use of Jolokia instead

  /**
   * Read the attribute identified by name from a remote resource identified by name. The intent of
   * this method is to return the value of an attribute on an MBean located in the remote
   * MBeanServer.
   * 
   * @param resourceName name/url of the remote resource from which to fetch the attribute value.
   * @param attributeName name of the attribute who's value will be fetched.
   * @return the value of the named attribute for the named resource (typically an MBean).
   * @throws MBeanAccessException if an MBean access error occurs.
   * @throws RestApiCallForCommandNotFoundException if the REST API web service endpoint for
   *         accessing an attribute on an MBean does not exists!
   * @see #createHttpRequest(org.apache.geode.management.internal.web.domain.Link)
   * @see #findLink(String)
   * @see #send(org.apache.geode.management.internal.web.http.ClientHttpRequest, Class)
   */
  @Override
  public Object getAttribute(final String resourceName, final String attributeName) {
    final Link link = findLink(MBEAN_ATTRIBUTE_LINK_RELATION);

    if (link != null) {
      final ClientHttpRequest request = createHttpRequest(link);

      request.addParameterValues("resourceName", resourceName);
      request.addParameterValues("attributeName", attributeName);

      final ResponseEntity response = send(request, byte[].class);

      try {
        return IOUtils.deserializeObject(response.getBody());
      } catch (IOException e) {
        throw new MBeanAccessException(String.format(
            "De-serializing the result of accessing attribute (%1$s) on MBean (%2$s) failed!",
            resourceName, attributeName), e);
      } catch (ClassNotFoundException e) {
        throw new MBeanAccessException(String.format(
            "The Class type of the result when accessing attribute (%1$s) on MBean (%2$s) was not found!",
            resourceName, attributeName), e);
      }
    } else {
      printSevere(
          "Getting the value of attribute (%1$s) on MBean (%2$s) is currently an unsupported operation!",
          attributeName, resourceName);
      throw new RestApiCallForCommandNotFoundException(MBEAN_ATTRIBUTE_LINK_RELATION);
    }
  }

  /**
   * Gets the identifier of the GemFire cluster.
   * 
   * @return an integer value indicating the identifier of the GemFire cluster.
   * @see #initClusterId()
   */
  @Override
  public int getClusterId() {
    return clusterId;
  }

  protected void initClusterId() {
    if (isReady()) {
      try {
        clusterId = (Integer) getAttribute(ManagementConstants.OBJECTNAME__DISTRIBUTEDSYSTEM_MXBEAN,
            "DistributedSystemId");
        printDebug("Cluster ID (%1$s)", clusterId);
      } catch (Exception ignore) {
        printDebug("Failed to determine cluster ID: %1$s", ignore.getMessage());
      }
    }
  }

  /**
   * Gets a proxy to the remote DistributedSystem MXBean to access attributes and invoke operations
   * on the distributed system, or the GemFire cluster.
   * 
   * @return a proxy instance of the GemFire Manager's DistributedSystem MXBean.
   * @see #getMBeanProxy(javax.management.ObjectName, Class)
   * @see org.apache.geode.management.DistributedSystemMXBean
   * @see org.apache.geode.management.internal.MBeanJMXAdapter#getDistributedSystemName()
   */
  public DistributedSystemMXBean getDistributedSystemMXBean() {
    return getMBeanProxy(MBeanJMXAdapter.getDistributedSystemName(), DistributedSystemMXBean.class);
  }

  /**
   * Gets a proxy to an MXBean on a remote MBeanServer using HTTP for remoting.
   * 
   * @param  the class type of the remote MXBean.
   * @param objectName the JMX ObjectName uniquely identifying the remote MXBean.
   * @param mbeanInterface the interface of the remote MXBean to proxy for attribute/operation
   *        access.
   * @return a proxy using HTTP remoting to access the specified, remote MXBean.
   * @see javax.management.ObjectName
   * @see org.apache.geode.management.internal.web.shell.support.HttpMBeanProxyFactory
   */
  public  T getMBeanProxy(final ObjectName objectName, final Class mbeanInterface) {
    return HttpMBeanProxyFactory.createMBeanProxy(this, objectName, mbeanInterface);
  }

  /**
   * Invoke an operation identified by name on a remote resource identified by name with the given
   * arguments. The intent of this method is to invoke an arbitrary operation on an MBean located in
   * the remote MBeanServer.
   * 
   * @param resourceName name/url (object name) of the remote resource (MBea) on which operation is
   *        to be invoked.
   * @param operationName name of the operation to be invoked.
   * @param params an array of arguments for the parameters to be set when the operation is invoked.
   * @param signature an array containing the signature of the operation.
   * @return result of the operation invocation.
   * @throws MBeanAccessException if an MBean access error occurs.
   * @throws RestApiCallForCommandNotFoundException if the REST API web service endpoint for
   *         invoking an operation on an MBean does not exists!
   * @see #createHttpRequest(org.apache.geode.management.internal.web.domain.Link)
   * @see #findLink(String)
   * @see #send(org.apache.geode.management.internal.web.http.ClientHttpRequest, Class)
   */
  // TODO research the use of Jolokia instead
  @Override
  public Object invoke(final String resourceName, final String operationName, final Object[] params,
      final String[] signature) {
    final Link link = findLink(MBEAN_OPERATION_LINK_RELATION);

    if (link != null) {
      final ClientHttpRequest request = createHttpRequest(link);

      request.addParameterValues("resourceName", resourceName);
      request.addParameterValues("operationName", operationName);
      request.addParameterValues("signature", (Object[]) signature);
      request.addParameterValues("parameters", params); // TODO may need to convert method parameter
                                                        // arguments

      final ResponseEntity response = send(request, byte[].class);

      try {
        return IOUtils.deserializeObject(response.getBody());
      } catch (IOException e) {
        throw new MBeanAccessException(String.format(
            "De-serializing the result from invoking operation (%1$s) on MBean (%2$s) failed!",
            resourceName, operationName), e);
      } catch (ClassNotFoundException e) {
        throw new MBeanAccessException(String.format(
            "The Class type of the result from invoking operation (%1$s) on MBean (%2$s) was not found!",
            resourceName, operationName), e);
      }
    } else {
      printSevere(
          "Invoking operation (%1$s) on MBean (%2$s) is currently an unsupported operation!",
          operationName, resourceName);
      throw new RestApiCallForCommandNotFoundException(MBEAN_OPERATION_LINK_RELATION);
    }
  }

  /**
   * This method searches the MBean server, based on the OperationsInvoker's JMX-based or remoting
   * capable MBean server connection, for MBeans matching a specific ObjectName or matching an
   * ObjectName pattern along with satisfying criteria from the Query expression.
   * 
   * @param objectName the ObjectName or pattern for which matching MBeans in the target MBean
   *        server will be returned.
   * @param queryExpression the JMX-based query expression used to filter matching MBeans.
   * @return a set of ObjectName's matching MBeans in the MBean server matching the ObjectName and
   *         Query expression criteria.
   * @see #createHttpRequest(org.apache.geode.management.internal.web.domain.Link)
   * @see #findLink(String)
   * @see #send(org.apache.geode.management.internal.web.http.ClientHttpRequest, Class)
   * @see javax.management.ObjectName
   * @see javax.management.QueryExp
   */
  @Override
  @SuppressWarnings("unchecked")
  public Set queryNames(final ObjectName objectName, final QueryExp queryExpression) {
    final Link link = findLink(MBEAN_QUERY_LINK_RELATION);

    if (link != null) {
      final ClientHttpRequest request = createHttpRequest(link);

      request.setContent(new QueryParameterSource(objectName, queryExpression));

      final ResponseEntity response = send(request, byte[].class);

      try {
        return (Set) IOUtils.deserializeObject(response.getBody());
      } catch (Exception e) {
        throw new MBeanAccessException(String.format(
            "An error occurred while querying for MBean names using ObjectName pattern (%1$s) and Query expression (%2$s)!",
            objectName, queryExpression), e);
      }
    } else {
      printSevere(
          "Running a query to get the ObjectNames of all MBeans matching the ObjectName pattern (%1$s) and Query expression (%2$s) is currently unsupported!",
          objectName, queryExpression);
      throw new RestApiCallForCommandNotFoundException(MBEAN_QUERY_LINK_RELATION);
    }
  }

  /**
   * Stops communication with and closes all connections to the remote HTTP server (service).
   */
  @Override
  public void stop() {
    if (executorService != null) {
      executorService.shutdown();
    }

    restTemplate = null;
  }

  @Override
  public String toString() {
    return String.format("GemFire Manager HTTP service @ %1$s", getBaseUrl());
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy