001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.actuate.endpoint.jmx; 018 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.Map; 022 023import javax.management.Attribute; 024import javax.management.AttributeList; 025import javax.management.AttributeNotFoundException; 026import javax.management.DynamicMBean; 027import javax.management.InvalidAttributeValueException; 028import javax.management.MBeanException; 029import javax.management.MBeanInfo; 030import javax.management.ReflectionException; 031 032import reactor.core.publisher.Mono; 033 034import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException; 035import org.springframework.boot.actuate.endpoint.InvocationContext; 036import org.springframework.boot.actuate.endpoint.SecurityContext; 037import org.springframework.util.Assert; 038import org.springframework.util.ClassUtils; 039 040/** 041 * Adapter to expose a {@link ExposableJmxEndpoint JMX endpoint} as a 042 * {@link DynamicMBean}. 043 * 044 * @author Stephane Nicoll 045 * @author Andy Wilkinson 046 * @author Phillip Webb 047 * @since 2.0.0 048 */ 049public class EndpointMBean implements DynamicMBean { 050 051 private static final boolean REACTOR_PRESENT = ClassUtils.isPresent( 052 "reactor.core.publisher.Mono", EndpointMBean.class.getClassLoader()); 053 054 private final JmxOperationResponseMapper responseMapper; 055 056 private final ClassLoader classLoader; 057 058 private final ExposableJmxEndpoint endpoint; 059 060 private final MBeanInfo info; 061 062 private final Map<String, JmxOperation> operations; 063 064 EndpointMBean(JmxOperationResponseMapper responseMapper, ClassLoader classLoader, 065 ExposableJmxEndpoint endpoint) { 066 Assert.notNull(responseMapper, "ResponseMapper must not be null"); 067 Assert.notNull(endpoint, "Endpoint must not be null"); 068 this.responseMapper = responseMapper; 069 this.classLoader = classLoader; 070 this.endpoint = endpoint; 071 this.info = new MBeanInfoFactory(responseMapper).getMBeanInfo(endpoint); 072 this.operations = getOperations(endpoint); 073 } 074 075 private Map<String, JmxOperation> getOperations(ExposableJmxEndpoint endpoint) { 076 Map<String, JmxOperation> operations = new HashMap<>(); 077 endpoint.getOperations() 078 .forEach((operation) -> operations.put(operation.getName(), operation)); 079 return Collections.unmodifiableMap(operations); 080 } 081 082 @Override 083 public MBeanInfo getMBeanInfo() { 084 return this.info; 085 } 086 087 @Override 088 public Object invoke(String actionName, Object[] params, String[] signature) 089 throws MBeanException, ReflectionException { 090 JmxOperation operation = this.operations.get(actionName); 091 if (operation == null) { 092 String message = "Endpoint with id '" + this.endpoint.getEndpointId() 093 + "' has no operation named " + actionName; 094 throw new ReflectionException(new IllegalArgumentException(message), message); 095 } 096 ClassLoader previousClassLoader = overrideThreadContextClassLoader( 097 this.classLoader); 098 try { 099 return invoke(operation, params); 100 } 101 finally { 102 overrideThreadContextClassLoader(previousClassLoader); 103 } 104 } 105 106 private ClassLoader overrideThreadContextClassLoader(ClassLoader classLoader) { 107 if (classLoader != null) { 108 try { 109 return ClassUtils.overrideThreadContextClassLoader(classLoader); 110 } 111 catch (SecurityException ex) { 112 // can't set class loader, ignore it and proceed 113 } 114 } 115 return null; 116 } 117 118 private Object invoke(JmxOperation operation, Object[] params) 119 throws MBeanException, ReflectionException { 120 try { 121 String[] parameterNames = operation.getParameters().stream() 122 .map(JmxOperationParameter::getName).toArray(String[]::new); 123 Map<String, Object> arguments = getArguments(parameterNames, params); 124 InvocationContext context = new InvocationContext(SecurityContext.NONE, 125 arguments); 126 Object result = operation.invoke(context); 127 if (REACTOR_PRESENT) { 128 result = ReactiveHandler.handle(result); 129 } 130 return this.responseMapper.mapResponse(result); 131 } 132 catch (InvalidEndpointRequestException ex) { 133 throw new ReflectionException(new IllegalArgumentException(ex.getMessage()), 134 ex.getMessage()); 135 } 136 catch (Exception ex) { 137 throw new MBeanException(translateIfNecessary(ex), ex.getMessage()); 138 } 139 } 140 141 private Exception translateIfNecessary(Exception exception) { 142 if (exception.getClass().getName().startsWith("java.")) { 143 return exception; 144 } 145 return new IllegalStateException(exception.getMessage()); 146 } 147 148 private Map<String, Object> getArguments(String[] parameterNames, Object[] params) { 149 Map<String, Object> arguments = new HashMap<>(); 150 for (int i = 0; i < params.length; i++) { 151 arguments.put(parameterNames[i], params[i]); 152 } 153 return arguments; 154 } 155 156 @Override 157 public Object getAttribute(String attribute) 158 throws AttributeNotFoundException, MBeanException, ReflectionException { 159 throw new AttributeNotFoundException("EndpointMBeans do not support attributes"); 160 } 161 162 @Override 163 public void setAttribute(Attribute attribute) throws AttributeNotFoundException, 164 InvalidAttributeValueException, MBeanException, ReflectionException { 165 throw new AttributeNotFoundException("EndpointMBeans do not support attributes"); 166 } 167 168 @Override 169 public AttributeList getAttributes(String[] attributes) { 170 return new AttributeList(); 171 } 172 173 @Override 174 public AttributeList setAttributes(AttributeList attributes) { 175 return new AttributeList(); 176 } 177 178 private static class ReactiveHandler { 179 180 public static Object handle(Object result) { 181 if (result instanceof Mono) { 182 return ((Mono<?>) result).block(); 183 } 184 return result; 185 } 186 187 } 188 189}