blob: 9d8f3e4c6d0590ada61046cc291fc033a4c8a68f [file] [log] [blame]
/*
* 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.controllers;
import static org.apache.commons.io.IOUtils.toInputStream;
import static org.apache.geode.management.internal.web.util.UriUtils.decode;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.Set;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
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;
import org.springframework.web.multipart.MultipartFile;
import org.apache.geode.annotations.Immutable;
import org.apache.geode.cache.RegionShortcut;
import org.apache.geode.internal.GemFireVersion;
import org.apache.geode.internal.util.IOUtils;
import org.apache.geode.management.cli.Result;
import org.apache.geode.management.internal.cli.i18n.CliStrings;
import org.apache.geode.management.internal.cli.result.model.ResultModel;
import org.apache.geode.management.internal.cli.util.CommandStringBuilder;
import org.apache.geode.management.internal.web.domain.QueryParameterSource;
import org.apache.geode.util.internal.GeodeConverter;
/**
* The ShellCommandsController class implements GemFire REST API calls for Gfsh Shell Commands.
*
* @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 GemFire 8.0
*/
@Controller("shellController")
@RequestMapping(AbstractCommandsController.REST_API_VERSION)
@SuppressWarnings("unused")
public class ShellCommandsController extends AbstractCommandsController {
@Immutable
private static final MultipartFile[] DEFAULT_MULTIPART_FILE = null;
private static final String DEFAULT_INDEX_TYPE = "range";
@RequestMapping(method = {RequestMethod.GET, RequestMethod.POST}, value = "/management/commands")
public ResponseEntity<InputStreamResource> command(@RequestParam(value = "cmd") String command,
@RequestParam(value = "resources", required = false) MultipartFile[] fileResource)
throws IOException {
String result = processCommand(decode(command), getEnvironment(), fileResource);
return getResponse(result);
}
@RequestMapping(method = RequestMethod.POST, value = "/indexes")
public ResponseEntity<?> createIndex(@RequestParam(CliStrings.CREATE_INDEX__NAME) String name,
@RequestParam(CliStrings.CREATE_INDEX__EXPRESSION) String expression,
@RequestParam(CliStrings.CREATE_INDEX__REGION) String region,
@RequestParam(value = CliStrings.CREATE_INDEX__TYPE,
defaultValue = DEFAULT_INDEX_TYPE) String type)
throws IOException {
CommandStringBuilder command = new CommandStringBuilder(CliStrings.CREATE_INDEX);
command.addOption(CliStrings.CREATE_INDEX__NAME, name);
command.addOption(CliStrings.CREATE_INDEX__EXPRESSION, expression);
command.addOption(CliStrings.CREATE_INDEX__REGION, region);
command.addOption(CliStrings.CREATE_INDEX__TYPE, type);
return command(command.toString(), DEFAULT_MULTIPART_FILE);
}
@RequestMapping(method = RequestMethod.POST, value = "/regions")
public ResponseEntity<?> createRegion(
@RequestParam(CliStrings.CREATE_REGION__REGION) String namePath,
@RequestParam(value = CliStrings.CREATE_REGION__SKIPIFEXISTS,
defaultValue = "false") Boolean skipIfExists,
@RequestParam(value = CliStrings.CREATE_REGION__REGIONSHORTCUT,
defaultValue = "PARTITION") RegionShortcut type)
throws IOException {
CommandStringBuilder command = new CommandStringBuilder(CliStrings.CREATE_REGION);
command.addOption(CliStrings.CREATE_REGION__REGION, namePath);
command.addOption(CliStrings.CREATE_REGION__REGIONSHORTCUT, type.name());
Optional.ofNullable(skipIfExists)
.ifPresent(it -> command.addOption(CliStrings.CREATE_REGION__SKIPIFEXISTS, it.toString()));
return command(command.toString(), DEFAULT_MULTIPART_FILE);
}
@RequestMapping(method = RequestMethod.GET, value = "/mbean/attribute")
public ResponseEntity<?> getAttribute(@RequestParam("resourceName") final String resourceName,
@RequestParam("attributeName") final String attributeName)
throws AttributeNotFoundException, MBeanException, ReflectionException,
InstanceNotFoundException, IOException, MalformedObjectNameException {
// Exceptions are caught by the @ExceptionHandler AbstractCommandsController.handleAppException
MBeanServer mBeanServer = getMBeanServer();
ObjectName objectName = ObjectName.getInstance(decode(resourceName));
final Object attributeValue = mBeanServer.getAttribute(objectName, decode(attributeName));
byte[] serializedResult = IOUtils.serializeObject(attributeValue);
return new ResponseEntity<>(serializedResult, HttpStatus.OK);
}
@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)
throws MalformedObjectNameException, MBeanException, InstanceNotFoundException,
ReflectionException, IOException {
// Exceptions are caught by the @ExceptionHandler AbstractCommandsController.handleAppException
signature = (signature != null ? signature : ArrayUtils.EMPTY_STRING_ARRAY);
parameters = (parameters != null ? parameters : ArrayUtils.EMPTY_OBJECT_ARRAY);
MBeanServer mBeanServer = getMBeanServer();
ObjectName objectName = ObjectName.getInstance(decode(resourceName));
// GEODE-6182
for (int i = 0; i < signature.length; i++) {
parameters[i] = GeodeConverter.convertToActualType(parameters[i].toString(), signature[i]);
}
final Object result =
mBeanServer.invoke(objectName, decode(operationName), parameters, signature);
byte[] serializedResult = IOUtils.serializeObject(result);
return new ResponseEntity<>(serializedResult, HttpStatus.OK);
}
@RequestMapping(method = RequestMethod.POST, value = "/mbean/query")
public ResponseEntity<?> queryNames(@RequestBody final QueryParameterSource query)
throws IOException {
// Exceptions are caught by the @ExceptionHandler AbstractCommandsController.handleAppException
final Set<ObjectName> objectNames =
getMBeanServer().queryNames(query.getObjectName(), query.getQueryExpression());
return new ResponseEntity<>(IOUtils.serializeObject(objectNames), HttpStatus.OK);
}
@RequestMapping(method = {RequestMethod.GET, RequestMethod.HEAD}, value = "/ping")
public ResponseEntity<String> ping() {
return new ResponseEntity<>("pong", 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 fullVersion() {
return GemFireVersion.asString();
}
@RequestMapping(method = RequestMethod.GET, value = "/version/release")
@ResponseBody
public String releaseVersion() {
return GemFireVersion.getGemFireVersion();
}
private ResponseEntity<InputStreamResource> getResponse(String result) {
ResultModel commandResult = ResultModel.fromJson(result);
if (commandResult.getStatus().equals(Result.Status.OK)
&& commandResult.getFileToDownload() != null) {
return getFileDownloadResponse(commandResult);
} else {
return getJsonResponse(result);
}
}
private ResponseEntity<InputStreamResource> getJsonResponse(String result) {
HttpHeaders respHeaders = new HttpHeaders();
try {
InputStreamResource isr = new InputStreamResource(toInputStream(result, "UTF-8"));
respHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
return new ResponseEntity<>(isr, respHeaders, HttpStatus.OK);
} catch (Exception e) {
throw new RuntimeException("IO Error writing file to output stream", e);
}
}
private ResponseEntity<InputStreamResource> getFileDownloadResponse(ResultModel commandResult) {
HttpHeaders respHeaders = new HttpHeaders();
Path filePath = commandResult.getFileToDownload();
try {
InputStreamResource isr = new InputStreamResource(new FileInputStream(filePath.toFile()));
respHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
return new ResponseEntity<>(isr, respHeaders, HttpStatus.OK);
} catch (Exception e) {
throw new RuntimeException("IO Error writing file to output stream", e);
} finally {
FileUtils.deleteQuietly(filePath.toFile());
}
}
}