blob: ef4c49f1a5434cfae1e7fac2db3577cef7a0c834 [file] [log] [blame]
/*=========================================================================
* Copyright (c) 2010-2014 Pivotal Software, Inc. All Rights Reserved.
* This product is protected by U.S. and international copyright
* and intellectual property laws. Pivotal products are covered by
* one or more patents listed at http://www.pivotal.io/patents.
*=========================================================================
*/
package com.gemstone.gemfire.management.internal.web.controllers;
import java.io.IOException;
import java.util.Set;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import com.gemstone.gemfire.internal.GemFireVersion;
import com.gemstone.gemfire.internal.lang.ObjectUtils;
import com.gemstone.gemfire.internal.lang.StringUtils;
import com.gemstone.gemfire.internal.util.IOUtils;
import com.gemstone.gemfire.management.internal.cli.i18n.CliStrings;
import com.gemstone.gemfire.management.internal.web.domain.Link;
import com.gemstone.gemfire.management.internal.web.domain.LinkIndex;
import com.gemstone.gemfire.management.internal.web.domain.QueryParameterSource;
import com.gemstone.gemfire.management.internal.web.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* The ShellCommandsController class implements GemFire REST API calls for Gfsh Shell Commands.
*
* @author John Blum
* @see com.gemstone.gemfire.management.internal.cli.commands.ShellCommands
* @see com.gemstone.gemfire.management.internal.web.controllers.AbstractCommandsController
* @see org.springframework.stereotype.Controller
* @see org.springframework.web.bind.annotation.RequestBody
* @see org.springframework.web.bind.annotation.RequestMapping
* @see org.springframework.web.bind.annotation.RequestMethod
* @see org.springframework.web.bind.annotation.RequestParam
* @see org.springframework.web.bind.annotation.ResponseBody
* @since 8.0
*/
@Controller("shellController")
@RequestMapping(AbstractCommandsController.REST_API_VERSION)
@SuppressWarnings("unused")
public class ShellCommandsController extends AbstractCommandsController {
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";
@RequestMapping(method = RequestMethod.POST, value = "/management/commands", params = "cmd")
@ResponseBody
public String command(@RequestParam("cmd") final String command) {
return processCommand(decode(command));
}
// TODO research the use of Jolokia instead
@RequestMapping(method = RequestMethod.GET, value = "/mbean/attribute")
public ResponseEntity<?> getAttribute(@RequestParam("resourceName") final String resourceName,
@RequestParam("attributeName") final String attributeName)
{
try {
final Object attributeValue = getMBeanServer().getAttribute(ObjectName.getInstance(decode(resourceName)),
decode(attributeName));
return new ResponseEntity<byte[]>(IOUtils.serializeObject(attributeValue), HttpStatus.OK);
}
catch (AttributeNotFoundException e) {
return new ResponseEntity<String>(printStackTrace(e), HttpStatus.BAD_REQUEST);
}
catch (InstanceNotFoundException e) {
return new ResponseEntity<String>(printStackTrace(e), HttpStatus.NOT_FOUND);
}
catch (MalformedObjectNameException e) {
return new ResponseEntity<String>(printStackTrace(e), HttpStatus.BAD_REQUEST);
}
catch (Exception e) {
return new ResponseEntity<String>(printStackTrace(e), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
// TODO research the use of Jolokia instead
@RequestMapping(method = RequestMethod.POST, value = "/mbean/operation")
public ResponseEntity<?> invoke(@RequestParam("resourceName") final String resourceName,
@RequestParam("operationName") final String operationName,
@RequestParam(value = "signature", required = false) String[] signature,
@RequestParam(value = "parameters", required = false) Object[] parameters)
{
signature = (signature != null ? signature : StringUtils.EMPTY_STRING_ARRAY);
parameters = (parameters != null ? parameters : ObjectUtils.EMPTY_OBJECT_ARRAY);
try {
final Object result = getMBeanServer().invoke(ObjectName.getInstance(decode(resourceName)), decode(operationName),
parameters, signature);
return new ResponseEntity<byte[]>(IOUtils.serializeObject(result), HttpStatus.OK);
}
catch (InstanceNotFoundException e) {
return new ResponseEntity<String>(printStackTrace(e), HttpStatus.NOT_FOUND);
}
catch (MalformedObjectNameException e) {
return new ResponseEntity<String>(printStackTrace(e), HttpStatus.BAD_REQUEST);
}
catch (Exception e) {
return new ResponseEntity<String>(printStackTrace(e), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@RequestMapping(method = RequestMethod.POST, value = "/mbean/query")
public ResponseEntity<?> queryNames(@RequestBody final QueryParameterSource query) {
try {
final Set<ObjectName> objectNames = getMBeanServer().queryNames(query.getObjectName(), query.getQueryExpression());
return new ResponseEntity<byte[]>(IOUtils.serializeObject(objectNames), HttpStatus.OK);
}
catch (IOException e) {
return new ResponseEntity<String>(printStackTrace(e), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* Gets a link index for the web service endpoints and REST API calls in GemFire for management and monitoring
* using GemFire shell (Gfsh).
*
* @return a LinkIndex containing Links for all web service endpoints, REST API calls in GemFire.
* @see com.gemstone.gemfire.management.internal.cli.i18n.CliStrings
* @see com.gemstone.gemfire.management.internal.web.controllers.AbstractCommandsController#toUri(String)
* @see com.gemstone.gemfire.management.internal.web.domain.Link
* @see com.gemstone.gemfire.management.internal.web.domain.LinkIndex
* @see com.gemstone.gemfire.management.internal.web.http.HttpMethod
*/
// TODO figure out a better way to maintain this link index, such as using an automated way to introspect
// the Spring Web MVC Controller RequestMapping Annotations.
@RequestMapping(method = RequestMethod.GET, value = "/index", produces = MediaType.APPLICATION_XML_VALUE)
@ResponseBody
public LinkIndex index() {
//logger.warning(String.format("Returning Link Index for Context Path (%1$s).",
// ServletUriComponentsBuilder.fromCurrentContextPath().build().toString()));
return new LinkIndex()
// Cluster Commands
.add(new Link(CliStrings.STATUS_SHARED_CONFIG, toUri("/services/cluster-config")))
// Member Commands
.add(new Link(CliStrings.LIST_MEMBER, toUri("/members")))
.add(new Link(CliStrings.DESCRIBE_MEMBER, toUri("/members/{name}")))
// Region Commands
.add(new Link(CliStrings.LIST_REGION, toUri("/regions")))
.add(new Link(CliStrings.DESCRIBE_REGION, toUri("/regions/{name}")))
.add(new Link(CliStrings.ALTER_REGION, toUri("/regions/{name}"), HttpMethod.PUT))
.add(new Link(CliStrings.CREATE_REGION, toUri("/regions"), HttpMethod.POST))
.add(new Link(CliStrings.DESTROY_REGION, toUri("/regions/{name}"), HttpMethod.DELETE))
// Index Commands
.add(new Link(CliStrings.LIST_INDEX, toUri("/indexes")))
.add(new Link(CliStrings.CLEAR_DEFINED_INDEXES, toUri("/indexes?op=clear-defined"), HttpMethod.DELETE))
.add(new Link(CliStrings.CREATE_INDEX, toUri("/indexes"), HttpMethod.POST))
.add(new Link(CliStrings.CREATE_DEFINED_INDEXES, toUri("/indexes?op=create-defined"), HttpMethod.POST))
.add(new Link(CliStrings.DEFINE_INDEX, toUri("/indexes?op=define"), HttpMethod.POST))
.add(new Link(CliStrings.DESTROY_INDEX, toUri("/indexes"), HttpMethod.DELETE))
.add(new Link(CliStrings.DESTROY_INDEX, toUri("/indexes/{name}"), HttpMethod.DELETE))
// Data Commands
.add(new Link(CliStrings.GET, toUri("/regions/{region}/data"), HttpMethod.GET))
.add(new Link(CliStrings.PUT, toUri("/regions/{region}/data"), HttpMethod.PUT))
.add(new Link(CliStrings.REMOVE, toUri("/regions/{region}/data"), HttpMethod.DELETE))
.add(new Link(CliStrings.EXPORT_DATA, toUri("/members/{member}/regions/{region}/data"), HttpMethod.GET))
.add(new Link(CliStrings.IMPORT_DATA, toUri("/members/{member}/regions/{region}/data"), HttpMethod.POST))
.add(new Link(CliStrings.LOCATE_ENTRY, toUri("/regions/{region}/data/location"), HttpMethod.GET))
.add(new Link(CliStrings.QUERY, toUri("/regions/data/query"), HttpMethod.GET))
.add(new Link(CliStrings.REBALANCE, toUri("/regions/data?op=rebalance"), HttpMethod.POST))
// Function Commands
.add(new Link(CliStrings.LIST_FUNCTION, toUri("/functions")))
.add(new Link(CliStrings.DESTROY_FUNCTION, toUri("/functions/{id}"), HttpMethod.DELETE))
.add(new Link(CliStrings.EXECUTE_FUNCTION, toUri("/functions/{id}"), HttpMethod.POST))
// Client Commands
.add(new Link(CliStrings.LIST_CLIENTS, toUri("/clients")))
.add(new Link(CliStrings.DESCRIBE_CLIENT, toUri("/clients/{clientID}")))
// Config Commands
.add(new Link(CliStrings.ALTER_RUNTIME_CONFIG, toUri("/config"), HttpMethod.POST))
.add(new Link(CliStrings.DESCRIBE_CONFIG, toUri("/members/{member}/config")))
.add(new Link(CliStrings.EXPORT_CONFIG, toUri("/config")))
.add(new Link(CliStrings.EXPORT_SHARED_CONFIG, toUri("/config/cluster")))
.add(new Link(CliStrings.IMPORT_SHARED_CONFIG, toUri("/config/cluster"), HttpMethod.POST))
// Deploy Commands
.add(new Link(CliStrings.LIST_DEPLOYED, toUri("/deployed")))
.add(new Link(CliStrings.DEPLOY, toUri("/deployed"), HttpMethod.POST))
.add(new Link(CliStrings.UNDEPLOY, toUri("/deployed"), HttpMethod.DELETE))
// Disk Store Commands
.add(new Link(CliStrings.LIST_DISK_STORE, toUri("/diskstores")))
.add(new Link(CliStrings.BACKUP_DISK_STORE, toUri("/diskstores?op=backup"), HttpMethod.POST))
.add(new Link(CliStrings.COMPACT_DISK_STORE, toUri("/diskstores/{name}?op=compact"), HttpMethod.POST))
.add(new Link(CliStrings.CREATE_DISK_STORE, toUri("/diskstores"), HttpMethod.POST))
.add(new Link(CliStrings.DESCRIBE_DISK_STORE, toUri("/diskstores/{name}")))
.add(new Link(CliStrings.DESTROY_DISK_STORE, toUri("/diskstores/{name}"), HttpMethod.DELETE))
.add(new Link(CliStrings.REVOKE_MISSING_DISK_STORE, toUri("/diskstores/{id}?op=revoke"), HttpMethod.POST))
.add(new Link(CliStrings.SHOW_MISSING_DISK_STORE, toUri("/diskstores/missing")))
// Durable Client Commands
.add(new Link(CliStrings.LIST_DURABLE_CQS, toUri("/durable-clients/{durable-client-id}/cqs")))
.add(new Link(CliStrings.COUNT_DURABLE_CQ_EVENTS, toUri("/durable-clients/{durable-client-id}/cqs/events")))
.add(new Link(CliStrings.COUNT_DURABLE_CQ_EVENTS, toUri("/durable-clients/{durable-client-id}/cqs/{durable-cq-name}/events")))
.add(new Link(CliStrings.CLOSE_DURABLE_CLIENTS, toUri("/durable-clients/{durable-client-id}?op=close"), HttpMethod.POST))
.add(new Link(CliStrings.CLOSE_DURABLE_CQS, toUri("/durable-clients/{durable-client-id}/cqs/{durable-cq-name}?op=close"), HttpMethod.POST))
// Launcher Lifecycle Commands
.add(new Link(CliStrings.STATUS_LOCATOR, toUri("/members/{name}/locator")))
.add(new Link(CliStrings.STATUS_SERVER, toUri("/members/{name}/server")))
// Miscellaneous Commands
.add(new Link(CliStrings.CHANGE_LOGLEVEL, toUri("/groups/{groups}/loglevel"), HttpMethod.POST))
.add(new Link(CliStrings.CHANGE_LOGLEVEL, toUri("/members/{members}/loglevel"), HttpMethod.POST))
.add(new Link(CliStrings.CHANGE_LOGLEVEL, toUri("/members/{members}/groups/{groups}/loglevel"), HttpMethod.POST))
.add(new Link(CliStrings.EXPORT_LOGS, toUri("/logs")))
.add(new Link(CliStrings.EXPORT_STACKTRACE, toUri("/stacktraces")))
.add(new Link(CliStrings.GC, toUri("/gc"), HttpMethod.POST))
.add(new Link(CliStrings.GC, toUri("/members/{member}/gc"), HttpMethod.POST))
.add(new Link(CliStrings.NETSTAT, toUri("/netstat")))
.add(new Link(CliStrings.SHOW_DEADLOCK, toUri("/deadlocks")))
.add(new Link(CliStrings.SHOW_LOG, toUri("/members/{member}/log")))
.add(new Link(CliStrings.SHOW_METRICS, toUri("/metrics")))
.add(new Link(CliStrings.SHUTDOWN, toUri("/shutdown"), HttpMethod.POST)) // verb!
// Queue Commands
.add(new Link(CliStrings.CREATE_ASYNC_EVENT_QUEUE, toUri("/async-event-queues"), HttpMethod.POST))
.add(new Link(CliStrings.LIST_ASYNC_EVENT_QUEUES, toUri("/async-event-queues")))
// PDX Commands
.add(new Link(CliStrings.CONFIGURE_PDX, toUri("/pdx"), HttpMethod.POST))
//.add(new Link(CliStrings.PDX_DELETE_FIELD, toUri("/pdx/type/field"), HttpMethod.DELETE))
.add(new Link(CliStrings.PDX_RENAME, toUri("/pdx/type"), HttpMethod.POST))
// Shell Commands
.add(new Link(MBEAN_ATTRIBUTE_LINK_RELATION, toUri("/mbean/attribute")))
.add(new Link(MBEAN_OPERATION_LINK_RELATION, toUri("/mbean/operation"), HttpMethod.POST))
.add(new Link(MBEAN_QUERY_LINK_RELATION, toUri("/mbean/query"), HttpMethod.POST))
.add(new Link(PING_LINK_RELATION, toUri("/ping"), HttpMethod.GET))
.add(new Link(CliStrings.VERSION, toUri("/version")))
// WAN Gateway Commands
.add(new Link(CliStrings.LIST_GATEWAY, toUri("/gateways")))
.add(new Link(CliStrings.CREATE_GATEWAYRECEIVER, toUri("/gateways/receivers"), HttpMethod.POST))
.add(new Link(CliStrings.CREATE_GATEWAYSENDER, toUri("/gateways/senders"), HttpMethod.POST))
.add(new Link(CliStrings.LOAD_BALANCE_GATEWAYSENDER, toUri("/gateways/senders/{id}?op=load-balance"), HttpMethod.POST))
.add(new Link(CliStrings.PAUSE_GATEWAYSENDER, toUri("/gateways/senders/{id}?op=pause"), HttpMethod.POST))
.add(new Link(CliStrings.RESUME_GATEWAYSENDER, toUri("/gateways/senders/{id}?op=resume"), HttpMethod.POST))
.add(new Link(CliStrings.START_GATEWAYRECEIVER, toUri("/gateways/receivers?op=start"), HttpMethod.POST))
.add(new Link(CliStrings.START_GATEWAYSENDER, toUri("/gateways/senders?op=start"), HttpMethod.POST))
.add(new Link(CliStrings.STATUS_GATEWAYRECEIVER, toUri("/gateways/receivers")))
.add(new Link(CliStrings.STATUS_GATEWAYSENDER, toUri("/gateways/senders/{id}")))
.add(new Link(CliStrings.STOP_GATEWAYRECEIVER, toUri("/gateways/receivers?op=stop"), HttpMethod.POST))
.add(new Link(CliStrings.STOP_GATEWAYSENDER, toUri("/gateways/senders/{id}?op=stop"), HttpMethod.POST))
// HDFS Store Commands
.add(new Link(CliStrings.LIST_HDFS_STORE, toUri("/hdfsstores"), HttpMethod.GET))
.add(new Link(CliStrings.DESCRIBE_HDFS_STORE, toUri("/hdfsstores/{name}"), HttpMethod.GET))
.add(new Link(CliStrings.CREATE_HDFS_STORE, toUri("/hdfsstores"), HttpMethod.POST))
.add(new Link(CliStrings.DESTROY_HDFS_STORE, toUri("/hdfsstores/{name}"), HttpMethod.DELETE))
.add(new Link(CliStrings.ALTER_HDFS_STORE, toUri("/hdfsstores/{name}"), HttpMethod.PUT));
}
@RequestMapping(method = { RequestMethod.GET, RequestMethod.HEAD }, value = "/ping")
public ResponseEntity<String> ping() {
return new ResponseEntity<String>("<html><body><h1>Mischief Managed!</h1></body></html>", HttpStatus.OK);
}
@RequestMapping(method = RequestMethod.GET, value = "/version")
@ResponseBody
public String version() {
return GemFireVersion.getProductName().concat("/").concat(GemFireVersion.getGemFireVersion());
}
@RequestMapping(method = RequestMethod.GET, value = "/version/full")
@ResponseBody
public String versionSimple() {
return GemFireVersion.asString();
}
}