001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.broker.jmx; 018 019import java.lang.annotation.Annotation; 020import java.lang.reflect.Method; 021import java.security.AccessController; 022import java.security.Principal; 023import java.util.HashMap; 024import java.util.Locale; 025import java.util.Map; 026 027import javax.management.MBeanAttributeInfo; 028import javax.management.MBeanException; 029import javax.management.MBeanOperationInfo; 030import javax.management.MBeanParameterInfo; 031import javax.management.NotCompliantMBeanException; 032import javax.management.ObjectName; 033import javax.management.ReflectionException; 034import javax.management.StandardMBean; 035import javax.security.auth.Subject; 036 037import org.apache.activemq.broker.util.AuditLogEntry; 038import org.apache.activemq.broker.util.AuditLogService; 039import org.apache.activemq.broker.util.JMXAuditLogEntry; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043/** 044 * MBean that looks for method/parameter descriptions in the Info annotation. 045 */ 046public class AnnotatedMBean extends StandardMBean { 047 048 private static final Map<String, Class<?>> primitives = new HashMap<String, Class<?>>(); 049 050 private static final Logger LOG = LoggerFactory.getLogger("org.apache.activemq.audit"); 051 052 private static final byte OFF = 0b00; 053 private static final byte ENTRY = 0b01; 054 private static final byte EXIT = 0b10; 055 private static final byte ALL = 0b11; 056 057 private static byte audit = OFF; 058 private static AuditLogService auditLog; 059 060 static { 061 Class<?>[] p = { byte.class, short.class, int.class, long.class, float.class, double.class, char.class, boolean.class, }; 062 for (Class<?> c : p) { 063 primitives.put(c.getName(), c); 064 } 065 audit = byteFromProperty("org.apache.activemq.audit"); 066 if (audit != OFF) { 067 auditLog = AuditLogService.getAuditLog(); 068 } 069 } 070 071 private final ObjectName objectName; 072 073 private static byte byteFromProperty(String s) { 074 byte val = OFF; 075 String config = System.getProperty(s, "").toLowerCase(Locale.ENGLISH); 076 if ("true".equals(config) || "entry".equals(config)) { 077 val = ENTRY; 078 } else if ("exit".equals(config)) { 079 val = EXIT; 080 } else if ("all".equals(config)) { 081 val = ALL; 082 } 083 return val; 084 } 085 086 @SuppressWarnings({ "unchecked", "rawtypes" }) 087 public static void registerMBean(ManagementContext context, Object object, ObjectName objectName) throws Exception { 088 089 String mbeanName = object.getClass().getName() + "MBean"; 090 091 for (Class c : object.getClass().getInterfaces()) { 092 if (mbeanName.equals(c.getName())) { 093 context.registerMBean(new AnnotatedMBean(object, c, objectName), objectName); 094 return; 095 } 096 } 097 098 context.registerMBean(object, objectName); 099 } 100 101 /** Instance where the MBean interface is implemented by another object. */ 102 public <T> AnnotatedMBean(T impl, Class<T> mbeanInterface, ObjectName objectName) throws NotCompliantMBeanException { 103 super(impl, mbeanInterface); 104 this.objectName = objectName; 105 } 106 107 /** Instance where the MBean interface is implemented by this object. */ 108 protected AnnotatedMBean(Class<?> mbeanInterface, ObjectName objectName) throws NotCompliantMBeanException { 109 super(mbeanInterface); 110 this.objectName = objectName; 111 } 112 113 /** {@inheritDoc} */ 114 @Override 115 protected String getDescription(MBeanAttributeInfo info) { 116 117 String descr = info.getDescription(); 118 Method m = getMethod(getMBeanInterface(), "get" + info.getName().substring(0, 1).toUpperCase() + info.getName().substring(1)); 119 if (m == null) 120 m = getMethod(getMBeanInterface(), "is" + info.getName().substring(0, 1).toUpperCase() + info.getName().substring(1)); 121 if (m == null) 122 m = getMethod(getMBeanInterface(), "does" + info.getName().substring(0, 1).toUpperCase() + info.getName().substring(1)); 123 124 if (m != null) { 125 MBeanInfo d = m.getAnnotation(MBeanInfo.class); 126 if (d != null) 127 descr = d.value(); 128 } 129 return descr; 130 } 131 132 /** {@inheritDoc} */ 133 @Override 134 protected String getDescription(MBeanOperationInfo op) { 135 136 String descr = op.getDescription(); 137 Method m = getMethod(op); 138 if (m != null) { 139 MBeanInfo d = m.getAnnotation(MBeanInfo.class); 140 if (d != null) 141 descr = d.value(); 142 } 143 return descr; 144 } 145 146 /** {@inheritDoc} */ 147 @Override 148 protected String getParameterName(MBeanOperationInfo op, MBeanParameterInfo param, int paramNo) { 149 String name = param.getName(); 150 Method m = getMethod(op); 151 if (m != null) { 152 for (Annotation a : m.getParameterAnnotations()[paramNo]) { 153 if (MBeanInfo.class.isInstance(a)) 154 name = MBeanInfo.class.cast(a).value(); 155 } 156 } 157 return name; 158 } 159 160 /** 161 * Extracts the Method from the MBeanOperationInfo 162 * 163 * @param op 164 * @return 165 */ 166 private Method getMethod(MBeanOperationInfo op) { 167 final MBeanParameterInfo[] params = op.getSignature(); 168 final String[] paramTypes = new String[params.length]; 169 for (int i = 0; i < params.length; i++) 170 paramTypes[i] = params[i].getType(); 171 172 return getMethod(getMBeanInterface(), op.getName(), paramTypes); 173 } 174 175 /** 176 * Returns the Method with the specified name and parameter types for the 177 * given class, null if it doesn't exist. 178 * 179 * @param mbean 180 * @param method 181 * @param params 182 * @return 183 */ 184 private static Method getMethod(Class<?> mbean, String method, String... params) { 185 try { 186 final ClassLoader loader = mbean.getClassLoader(); 187 final Class<?>[] paramClasses = new Class<?>[params.length]; 188 for (int i = 0; i < params.length; i++) { 189 paramClasses[i] = primitives.get(params[i]); 190 if (paramClasses[i] == null) 191 paramClasses[i] = Class.forName(params[i], false, loader); 192 } 193 return mbean.getMethod(method, paramClasses); 194 } catch (RuntimeException e) { 195 throw e; 196 } catch (Exception e) { 197 return null; 198 } 199 } 200 201 @Override 202 public Object invoke(String s, Object[] objects, String[] strings) throws MBeanException, ReflectionException { 203 JMXAuditLogEntry entry = null; 204 if (audit != OFF) { 205 Subject subject = Subject.getSubject(AccessController.getContext()); 206 String caller = "anonymous"; 207 if (subject != null) { 208 caller = ""; 209 for (Principal principal : subject.getPrincipals()) { 210 caller += principal.getName() + " "; 211 } 212 } 213 214 entry = new JMXAuditLogEntry(); 215 entry.setUser(caller); 216 entry.setTimestamp(System.currentTimeMillis()); 217 entry.setTarget(extractTargetTypeProperty(objectName)); 218 entry.setOperation(this.getMBeanInfo().getClassName() + "." + s); 219 220 try 221 { 222 if (objects.length == strings.length) 223 { 224 Method m = getMBeanMethod(this.getImplementationClass(), s, strings); 225 entry.getParameters().put("arguments", AuditLogEntry.sanitizeArguments(objects, m)); 226 } 227 else 228 { 229 // Supplied Method Signature and Arguments do not match. Set all supplied Arguments in Log Entry. To diagnose user error. 230 entry.getParameters().put("arguments", objects); 231 } 232 } 233 catch (ReflectiveOperationException e) 234 { 235 // Method or Class not found, set all supplied arguments. Set all supplied Arguments in Log Entry. To diagnose user error. 236 entry.getParameters().put("arguments", objects); 237 } 238 239 if ((audit&ENTRY) == ENTRY) { 240 auditLog.log(entry); 241 } 242 } 243 Object result = super.invoke(s, objects, strings); 244 if ((audit&EXIT) == EXIT) { 245 entry.complete(); 246 auditLog.log(entry); 247 } 248 return result; 249 } 250 251 // keep brokerName last b/c objectNames include the brokerName 252 final static String[] targetPropertiesCandidates = new String[] {"destinationName", "networkConnectorName", "connectorName", "connectionName", "brokerName"}; 253 private String extractTargetTypeProperty(ObjectName objectName) { 254 String result = null; 255 for (String attr: targetPropertiesCandidates) { 256 try { 257 result = objectName.getKeyProperty(attr); 258 if (result != null) { 259 break; 260 } 261 } catch (NullPointerException ok) {} 262 } 263 return result; 264 } 265 266 private Method getMBeanMethod(Class clazz, String methodName, String[] signature) throws ReflectiveOperationException { 267 Class[] parameterTypes = new Class[signature.length]; 268 for (int i = 0; i < signature.length; i++) { 269 parameterTypes[i] = Class.forName(signature[i]); 270 } 271 return clazz.getMethod(methodName, parameterTypes); 272 } 273}