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 static byte byteFromProperty(String s) { 072 byte val = OFF; 073 String config = System.getProperty(s, "").toLowerCase(Locale.ENGLISH); 074 if ("true".equals(config) || "entry".equals(config)) { 075 val = ENTRY; 076 } else if ("exit".equals(config)) { 077 val = EXIT; 078 } else if ("all".equals(config)) { 079 val = ALL; 080 } 081 return val; 082 } 083 084 @SuppressWarnings({ "unchecked", "rawtypes" }) 085 public static void registerMBean(ManagementContext context, Object object, ObjectName objectName) throws Exception { 086 087 String mbeanName = object.getClass().getName() + "MBean"; 088 089 for (Class c : object.getClass().getInterfaces()) { 090 if (mbeanName.equals(c.getName())) { 091 context.registerMBean(new AnnotatedMBean(object, c), objectName); 092 return; 093 } 094 } 095 096 context.registerMBean(object, objectName); 097 } 098 099 /** Instance where the MBean interface is implemented by another object. */ 100 public <T> AnnotatedMBean(T impl, Class<T> mbeanInterface) throws NotCompliantMBeanException { 101 super(impl, mbeanInterface); 102 } 103 104 /** Instance where the MBean interface is implemented by this object. */ 105 protected AnnotatedMBean(Class<?> mbeanInterface) throws NotCompliantMBeanException { 106 super(mbeanInterface); 107 } 108 109 /** {@inheritDoc} */ 110 @Override 111 protected String getDescription(MBeanAttributeInfo info) { 112 113 String descr = info.getDescription(); 114 Method m = getMethod(getMBeanInterface(), "get" + info.getName().substring(0, 1).toUpperCase() + info.getName().substring(1)); 115 if (m == null) 116 m = getMethod(getMBeanInterface(), "is" + info.getName().substring(0, 1).toUpperCase() + info.getName().substring(1)); 117 if (m == null) 118 m = getMethod(getMBeanInterface(), "does" + info.getName().substring(0, 1).toUpperCase() + info.getName().substring(1)); 119 120 if (m != null) { 121 MBeanInfo d = m.getAnnotation(MBeanInfo.class); 122 if (d != null) 123 descr = d.value(); 124 } 125 return descr; 126 } 127 128 /** {@inheritDoc} */ 129 @Override 130 protected String getDescription(MBeanOperationInfo op) { 131 132 String descr = op.getDescription(); 133 Method m = getMethod(op); 134 if (m != null) { 135 MBeanInfo d = m.getAnnotation(MBeanInfo.class); 136 if (d != null) 137 descr = d.value(); 138 } 139 return descr; 140 } 141 142 /** {@inheritDoc} */ 143 @Override 144 protected String getParameterName(MBeanOperationInfo op, MBeanParameterInfo param, int paramNo) { 145 String name = param.getName(); 146 Method m = getMethod(op); 147 if (m != null) { 148 for (Annotation a : m.getParameterAnnotations()[paramNo]) { 149 if (MBeanInfo.class.isInstance(a)) 150 name = MBeanInfo.class.cast(a).value(); 151 } 152 } 153 return name; 154 } 155 156 /** 157 * Extracts the Method from the MBeanOperationInfo 158 * 159 * @param op 160 * 161 * @return a Method 162 */ 163 private Method getMethod(MBeanOperationInfo op) { 164 final MBeanParameterInfo[] params = op.getSignature(); 165 final String[] paramTypes = new String[params.length]; 166 for (int i = 0; i < params.length; i++) 167 paramTypes[i] = params[i].getType(); 168 169 return getMethod(getMBeanInterface(), op.getName(), paramTypes); 170 } 171 172 /** 173 * Returns the Method with the specified name and parameter types for the 174 * given class, null if it doesn't exist. 175 * 176 * @param mbean 177 * @param method 178 * @param params 179 * 180 * @return a Method 181 */ 182 private static Method getMethod(Class<?> mbean, String method, String... params) { 183 try { 184 final ClassLoader loader = mbean.getClassLoader(); 185 final Class<?>[] paramClasses = new Class<?>[params.length]; 186 for (int i = 0; i < params.length; i++) { 187 paramClasses[i] = primitives.get(params[i]); 188 if (paramClasses[i] == null) 189 paramClasses[i] = Class.forName(params[i], false, loader); 190 } 191 return mbean.getMethod(method, paramClasses); 192 } catch (RuntimeException e) { 193 throw e; 194 } catch (Exception e) { 195 return null; 196 } 197 } 198 199 @Override 200 public Object invoke(String s, Object[] objects, String[] strings) throws MBeanException, ReflectionException { 201 JMXAuditLogEntry entry = null; 202 if (audit != OFF) { 203 Subject subject = Subject.getSubject(AccessController.getContext()); 204 String caller = "anonymous"; 205 if (subject != null) { 206 caller = ""; 207 for (Principal principal : subject.getPrincipals()) { 208 caller += principal.getName() + " "; 209 } 210 } 211 212 entry = new JMXAuditLogEntry(); 213 entry.setUser(caller); 214 entry.setTimestamp(System.currentTimeMillis()); 215 entry.setOperation(this.getMBeanInfo().getClassName() + "." + s); 216 217 try 218 { 219 if (objects.length == strings.length) 220 { 221 Method m = getMBeanMethod(this.getImplementationClass(), s, strings); 222 entry.getParameters().put("arguments", AuditLogEntry.sanitizeArguments(objects, m)); 223 } 224 else 225 { 226 // Supplied Method Signature and Arguments do not match. Set all supplied Arguments in Log Entry. To diagnose user error. 227 entry.getParameters().put("arguments", objects); 228 } 229 } 230 catch (ReflectiveOperationException e) 231 { 232 // Method or Class not found, set all supplied arguments. Set all supplied Arguments in Log Entry. To diagnose user error. 233 entry.getParameters().put("arguments", objects); 234 } 235 236 if ((audit&ENTRY) == ENTRY) { 237 auditLog.log(entry); 238 } 239 } 240 Object result = super.invoke(s, objects, strings); 241 if ((audit&EXIT) == EXIT) { 242 entry.complete(); 243 auditLog.log(entry); 244 } 245 return result; 246 } 247 248 private Method getMBeanMethod(Class clazz, String methodName, String[] signature) throws ReflectiveOperationException { 249 Class[] parameterTypes = new Class[signature.length]; 250 for (int i = 0; i < signature.length; i++) { 251 parameterTypes[i] = Class.forName(signature[i]); 252 } 253 return clazz.getMethod(methodName, parameterTypes); 254 } 255}