| /* |
| * 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 java.io.File; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.nio.file.Path; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.management.InstanceNotFoundException; |
| import javax.management.JMX; |
| import javax.management.MBeanServer; |
| import javax.management.MalformedObjectNameException; |
| import javax.management.ObjectName; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.apache.logging.log4j.Logger; |
| import org.springframework.beans.propertyeditors.StringArrayPropertyEditor; |
| import org.springframework.http.HttpStatus; |
| import org.springframework.http.ResponseEntity; |
| import org.springframework.web.bind.WebDataBinder; |
| import org.springframework.web.bind.annotation.ExceptionHandler; |
| import org.springframework.web.bind.annotation.InitBinder; |
| import org.springframework.web.multipart.MultipartFile; |
| |
| import org.apache.geode.internal.cache.GemFireCacheImpl; |
| import org.apache.geode.internal.cache.InternalCache; |
| import org.apache.geode.logging.internal.log4j.api.LogService; |
| import org.apache.geode.management.DistributedSystemMXBean; |
| import org.apache.geode.management.ManagementService; |
| import org.apache.geode.management.MemberMXBean; |
| import org.apache.geode.management.internal.MBeanJMXAdapter; |
| import org.apache.geode.management.internal.ManagementAgent; |
| import org.apache.geode.management.internal.SystemManagementService; |
| import org.apache.geode.management.internal.beans.FileUploader; |
| import org.apache.geode.management.internal.cli.shell.Gfsh; |
| import org.apache.geode.management.internal.web.controllers.support.LoginHandlerInterceptor; |
| import org.apache.geode.management.internal.web.util.UriUtils; |
| import org.apache.geode.security.AuthenticationFailedException; |
| import org.apache.geode.security.NotAuthorizedException; |
| |
| /** |
| * The AbstractCommandsController class is the abstract base class encapsulating common |
| * functionality across all Management Controller classes that expose REST API web service endpoints |
| * (URLs/URIs) for GemFire shell (Gfsh) commands. |
| * |
| * @see org.apache.geode.management.internal.cli.shell.Gfsh |
| * @see org.springframework.http.ResponseEntity |
| * @see org.springframework.stereotype.Controller |
| * @see org.springframework.web.bind.annotation.ExceptionHandler |
| * @see org.springframework.web.bind.annotation.InitBinder |
| * @see org.springframework.web.bind.annotation.ResponseBody |
| * @since GemFire 8.0 |
| */ |
| @SuppressWarnings("unused") |
| public abstract class AbstractCommandsController { |
| private static final Logger logger = LogService.getLogger(); |
| |
| protected static final String DEFAULT_ENCODING = UriUtils.DEFAULT_ENCODING; |
| protected static final String REST_API_VERSION = "/v1"; |
| |
| private MemberMXBean managingMemberMXBeanProxy; |
| |
| private Class accessControlKlass; |
| |
| @ExceptionHandler(Exception.class) |
| public ResponseEntity<String> internalError(final Exception e) { |
| final String stackTrace = getPrintableStackTrace(e); |
| logger.fatal(stackTrace); |
| return new ResponseEntity<>(stackTrace, HttpStatus.INTERNAL_SERVER_ERROR); |
| } |
| |
| @ExceptionHandler(AuthenticationFailedException.class) |
| public ResponseEntity<String> unauthorized(AuthenticationFailedException e) { |
| return new ResponseEntity<>(e.getMessage(), HttpStatus.UNAUTHORIZED); |
| } |
| |
| @ExceptionHandler({NotAuthorizedException.class, java.lang.SecurityException.class}) |
| public ResponseEntity<String> forbidden(Exception e) { |
| return new ResponseEntity<>(e.getMessage(), HttpStatus.FORBIDDEN); |
| } |
| |
| @ExceptionHandler(MalformedObjectNameException.class) |
| public ResponseEntity<String> badRequest(final MalformedObjectNameException e) { |
| logger.info(e); |
| return new ResponseEntity<>(getPrintableStackTrace(e), HttpStatus.BAD_REQUEST); |
| } |
| |
| @ExceptionHandler(InstanceNotFoundException.class) |
| public ResponseEntity<String> notFound(final InstanceNotFoundException e) { |
| logger.info(e); |
| return new ResponseEntity<>(getPrintableStackTrace(e), HttpStatus.NOT_FOUND); |
| } |
| |
| |
| /** |
| * Writes the stack trace of the Throwable to a String. |
| * |
| * @param t a Throwable object who's stack trace will be written to a String. |
| * @return a String containing the stack trace of the Throwable. |
| * @see java.io.StringWriter |
| * @see java.lang.Throwable#printStackTrace(java.io.PrintWriter) |
| */ |
| private static String getPrintableStackTrace(final Throwable t) { |
| final StringWriter stackTraceWriter = new StringWriter(); |
| t.printStackTrace(new PrintWriter(stackTraceWriter)); |
| return stackTraceWriter.toString(); |
| } |
| |
| /** |
| * Initializes data bindings for various HTTP request handler method parameter Java class types. |
| * |
| * @param dataBinder the DataBinder implementation used for Web transactions. |
| * @see org.springframework.web.bind.WebDataBinder |
| * @see org.springframework.web.bind.annotation.InitBinder |
| */ |
| @InitBinder |
| public void initBinder(final WebDataBinder dataBinder) { |
| dataBinder.registerCustomEditor(String[].class, |
| new StringArrayPropertyEditor(StringArrayPropertyEditor.DEFAULT_SEPARATOR, false)); |
| } |
| |
| |
| /** |
| * Gets a reference to the platform MBeanServer running in this JVM process. The MBeanServer |
| * instance constitutes a connection to the MBeanServer. This method returns a security-wrapped |
| * MBean if integrated security is active. |
| * |
| * @return a reference to the platform MBeanServer for this JVM process. |
| * @see java.lang.management.ManagementFactory#getPlatformMBeanServer() |
| * @see javax.management.MBeanServer |
| */ |
| protected MBeanServer getMBeanServer() { |
| InternalCache cache = getInternalCache(); |
| SystemManagementService service = |
| (SystemManagementService) ManagementService.getExistingManagementService(cache); |
| ManagementAgent managementAgent = service.getManagementAgent(); |
| return managementAgent.getJmxConnectorServer().getMBeanServer(); |
| } |
| |
| @SuppressWarnings("deprecation") |
| private InternalCache getInternalCache() { |
| return GemFireCacheImpl.getInstance(); |
| } |
| |
| /** |
| * Lookup operation for the MemberMXBean representing the Manager in the GemFire cluster. This |
| * method gets an instance of the Platform MBeanServer for this JVM process and uses it to lookup |
| * the MemberMXBean for the GemFire Manager based on the ObjectName declared in the |
| * DistributedSystemMXBean.getManagerObjectName() operation. |
| * |
| * @return a proxy instance to the MemberMXBean of the GemFire Manager. |
| * @see #getMBeanServer() |
| * @see #createMemberMXBeanForManagerUsingProxy(javax.management.MBeanServer, |
| * javax.management.ObjectName) |
| */ |
| private synchronized MemberMXBean getManagingMemberMXBean() { |
| if (managingMemberMXBeanProxy == null) { |
| MBeanServer mbs = getMBeanServer(); |
| DistributedSystemMXBean distributedSystemMXBean = JMX.newMXBeanProxy(mbs, |
| MBeanJMXAdapter.getDistributedSystemName(), DistributedSystemMXBean.class); |
| managingMemberMXBeanProxy = createMemberMXBeanForManagerUsingProxy(mbs, |
| distributedSystemMXBean.getMemberObjectName()); |
| } |
| |
| return managingMemberMXBeanProxy; |
| } |
| |
| protected synchronized ObjectName getMemberObjectName() { |
| MBeanServer platformMBeanServer = getMBeanServer(); |
| DistributedSystemMXBean distributedSystemMXBean = JMX.newMXBeanProxy(platformMBeanServer, |
| MBeanJMXAdapter.getDistributedSystemName(), DistributedSystemMXBean.class); |
| return distributedSystemMXBean.getMemberObjectName(); |
| } |
| |
| /** |
| * Creates a Proxy using the Platform MBeanServer and ObjectName in order to access attributes and |
| * invoke operations on the GemFire Manager's MemberMXBean. |
| * |
| * @param server a reference to this JVM's Platform MBeanServer. |
| * @param managingMemberObjectName the ObjectName of the GemFire Manager's MemberMXBean registered |
| * in the Platform MBeanServer. |
| * @return a Proxy for accessing attributes and invoking operations on the GemFire Manager's |
| * MemberMXBean. |
| * @see javax.management.JMX#newMXBeanProxy(javax.management.MBeanServerConnection, |
| * javax.management.ObjectName, Class) |
| */ |
| private MemberMXBean createMemberMXBeanForManagerUsingProxy(final MBeanServer server, |
| final ObjectName managingMemberObjectName) { |
| return JMX.newMXBeanProxy(server, managingMemberObjectName, MemberMXBean.class); |
| } |
| |
| /** |
| * Gets the environment setup during this HTTP/command request for the current command process |
| * execution. |
| * |
| * @return a mapping of environment variables to values. |
| * @see LoginHandlerInterceptor#getEnvironment() |
| */ |
| protected Map<String, String> getEnvironment() { |
| final Map<String, String> environment = new HashMap<>(); |
| |
| environment.putAll(LoginHandlerInterceptor.getEnvironment()); |
| environment.put(Gfsh.ENV_APP_NAME, Gfsh.GFSH_APP_NAME); |
| |
| return environment; |
| } |
| |
| /** |
| * Executes the specified command as entered by the user using the GemFire Shell (Gfsh). Note, |
| * Gfsh performs validation of the command during parsing before sending the command to the |
| * Manager for processing. |
| * |
| * @param command a String value containing a valid command String as would be entered by the user |
| * in Gfsh. |
| * @param environment a Map containing any environment configuration settings to be used by the |
| * Manager during command execution. For example, when executing commands originating from |
| * Gfsh, the key/value pair (APP_NAME=gfsh) is a specified mapping in the "environment. |
| * Note, it is common for the REST API to act as a bridge, or an adapter between Gfsh and |
| * the Manager, and thus need to specify this key/value pair mapping. |
| * @param multipartFiles uploaded files |
| * @return a result of the command execution as a String, typically marshalled in JSON to be |
| * serialized back to Gfsh. |
| */ |
| protected String processCommand(final String command, final Map<String, String> environment, |
| final MultipartFile[] multipartFiles) throws IOException { |
| List<String> filePaths = null; |
| Path tempDir = null; |
| if (multipartFiles != null) { |
| tempDir = FileUploader.createSecuredTempDirectory("uploaded-"); |
| // staging the files to local |
| filePaths = new ArrayList<>(); |
| for (MultipartFile multipartFile : multipartFiles) { |
| File dest = new File(tempDir.toFile(), multipartFile.getOriginalFilename()); |
| multipartFile.transferTo(dest); |
| filePaths.add(dest.getAbsolutePath()); |
| } |
| } |
| |
| MemberMXBean manager = getManagingMemberMXBean(); |
| try { |
| return manager.processCommand(command, environment, filePaths); |
| } finally { |
| if (tempDir != null) { |
| FileUtils.deleteDirectory(tempDir.toFile()); |
| } |
| } |
| } |
| } |