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.Map; 025 026import javax.management.MBeanAttributeInfo; 027import javax.management.MBeanException; 028import javax.management.MBeanOperationInfo; 029import javax.management.MBeanParameterInfo; 030import javax.management.NotCompliantMBeanException; 031import javax.management.ObjectName; 032import javax.management.ReflectionException; 033import javax.management.StandardMBean; 034import javax.security.auth.Subject; 035 036import org.apache.activemq.broker.util.AuditLogEntry; 037import org.apache.activemq.broker.util.AuditLogService; 038import org.apache.activemq.broker.util.JMXAuditLogEntry; 039import org.slf4j.Logger; 040import org.slf4j.LoggerFactory; 041 042/** 043 * MBean that looks for method/parameter descriptions in the Info annotation. 044 */ 045public class AnnotatedMBean extends StandardMBean { 046 047 private static final Map<String, Class<?>> primitives = new HashMap<String, Class<?>>(); 048 049 private static final Logger LOG = LoggerFactory.getLogger("org.apache.activemq.audit"); 050 051 private static boolean audit; 052 private static AuditLogService auditLog; 053 054 static { 055 Class<?>[] p = { byte.class, short.class, int.class, long.class, float.class, double.class, char.class, boolean.class, }; 056 for (Class<?> c : p) { 057 primitives.put(c.getName(), c); 058 } 059 audit = "true".equalsIgnoreCase(System.getProperty("org.apache.activemq.audit")); 060 if (audit) { 061 auditLog = AuditLogService.getAuditLog(); 062 } 063 } 064 065 @SuppressWarnings({ "unchecked", "rawtypes" }) 066 public static void registerMBean(ManagementContext context, Object object, ObjectName objectName) throws Exception { 067 068 String mbeanName = object.getClass().getName() + "MBean"; 069 070 for (Class c : object.getClass().getInterfaces()) { 071 if (mbeanName.equals(c.getName())) { 072 context.registerMBean(new AnnotatedMBean(object, c), objectName); 073 return; 074 } 075 } 076 077 context.registerMBean(object, objectName); 078 } 079 080 /** Instance where the MBean interface is implemented by another object. */ 081 public <T> AnnotatedMBean(T impl, Class<T> mbeanInterface) throws NotCompliantMBeanException { 082 super(impl, mbeanInterface); 083 } 084 085 /** Instance where the MBean interface is implemented by this object. */ 086 protected AnnotatedMBean(Class<?> mbeanInterface) throws NotCompliantMBeanException { 087 super(mbeanInterface); 088 } 089 090 /** {@inheritDoc} */ 091 @Override 092 protected String getDescription(MBeanAttributeInfo info) { 093 094 String descr = info.getDescription(); 095 Method m = getMethod(getMBeanInterface(), "get" + info.getName().substring(0, 1).toUpperCase() + info.getName().substring(1)); 096 if (m == null) 097 m = getMethod(getMBeanInterface(), "is" + info.getName().substring(0, 1).toUpperCase() + info.getName().substring(1)); 098 if (m == null) 099 m = getMethod(getMBeanInterface(), "does" + info.getName().substring(0, 1).toUpperCase() + info.getName().substring(1)); 100 101 if (m != null) { 102 MBeanInfo d = m.getAnnotation(MBeanInfo.class); 103 if (d != null) 104 descr = d.value(); 105 } 106 return descr; 107 } 108 109 /** {@inheritDoc} */ 110 @Override 111 protected String getDescription(MBeanOperationInfo op) { 112 113 String descr = op.getDescription(); 114 Method m = getMethod(op); 115 if (m != null) { 116 MBeanInfo d = m.getAnnotation(MBeanInfo.class); 117 if (d != null) 118 descr = d.value(); 119 } 120 return descr; 121 } 122 123 /** {@inheritDoc} */ 124 @Override 125 protected String getParameterName(MBeanOperationInfo op, MBeanParameterInfo param, int paramNo) { 126 String name = param.getName(); 127 Method m = getMethod(op); 128 if (m != null) { 129 for (Annotation a : m.getParameterAnnotations()[paramNo]) { 130 if (MBeanInfo.class.isInstance(a)) 131 name = MBeanInfo.class.cast(a).value(); 132 } 133 } 134 return name; 135 } 136 137 /** 138 * Extracts the Method from the MBeanOperationInfo 139 * 140 * @param op 141 * @return 142 */ 143 private Method getMethod(MBeanOperationInfo op) { 144 final MBeanParameterInfo[] params = op.getSignature(); 145 final String[] paramTypes = new String[params.length]; 146 for (int i = 0; i < params.length; i++) 147 paramTypes[i] = params[i].getType(); 148 149 return getMethod(getMBeanInterface(), op.getName(), paramTypes); 150 } 151 152 /** 153 * Returns the Method with the specified name and parameter types for the 154 * given class, null if it doesn't exist. 155 * 156 * @param mbean 157 * @param method 158 * @param params 159 * @return 160 */ 161 private static Method getMethod(Class<?> mbean, String method, String... params) { 162 try { 163 final ClassLoader loader = mbean.getClassLoader(); 164 final Class<?>[] paramClasses = new Class<?>[params.length]; 165 for (int i = 0; i < params.length; i++) { 166 paramClasses[i] = primitives.get(params[i]); 167 if (paramClasses[i] == null) 168 paramClasses[i] = Class.forName(params[i], false, loader); 169 } 170 return mbean.getMethod(method, paramClasses); 171 } catch (RuntimeException e) { 172 throw e; 173 } catch (Exception e) { 174 return null; 175 } 176 } 177 178 @Override 179 public Object invoke(String s, Object[] objects, String[] strings) throws MBeanException, ReflectionException { 180 if (audit) { 181 Subject subject = Subject.getSubject(AccessController.getContext()); 182 String caller = "anonymous"; 183 if (subject != null) { 184 caller = ""; 185 for (Principal principal : subject.getPrincipals()) { 186 caller += principal.getName() + " "; 187 } 188 } 189 190 AuditLogEntry entry = new JMXAuditLogEntry(); 191 entry.setUser(caller); 192 entry.setTimestamp(System.currentTimeMillis()); 193 entry.setOperation(this.getMBeanInfo().getClassName() + "." + s); 194 195 try 196 { 197 if (objects.length == strings.length) 198 { 199 Method m = getMBeanMethod(this.getImplementationClass(), s, strings); 200 entry.getParameters().put("arguments", AuditLogEntry.sanitizeArguments(objects, m)); 201 } 202 else 203 { 204 // Supplied Method Signature and Arguments do not match. Set all supplied Arguments in Log Entry. To diagnose user error. 205 entry.getParameters().put("arguments", objects); 206 } 207 } 208 catch (ReflectiveOperationException e) 209 { 210 // Method or Class not found, set all supplied arguments. Set all supplied Arguments in Log Entry. To diagnose user error. 211 entry.getParameters().put("arguments", objects); 212 } 213 214 auditLog.log(entry); 215 } 216 return super.invoke(s, objects, strings); 217 } 218 219 private Method getMBeanMethod(Class clazz, String methodName, String[] signature) throws ReflectiveOperationException 220 { 221 Class[] parameterTypes = new Class[signature.length]; 222 for (int i = 0; i < signature.length; i++) 223 { 224 parameterTypes[i] = Class.forName(signature[i]); 225 } 226 return clazz.getMethod(methodName, parameterTypes); 227 } 228}