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 * 142 * @return a Method 143 */ 144 private Method getMethod(MBeanOperationInfo op) { 145 final MBeanParameterInfo[] params = op.getSignature(); 146 final String[] paramTypes = new String[params.length]; 147 for (int i = 0; i < params.length; i++) 148 paramTypes[i] = params[i].getType(); 149 150 return getMethod(getMBeanInterface(), op.getName(), paramTypes); 151 } 152 153 /** 154 * Returns the Method with the specified name and parameter types for the 155 * given class, null if it doesn't exist. 156 * 157 * @param mbean 158 * @param method 159 * @param params 160 * 161 * @return a Method 162 */ 163 private static Method getMethod(Class<?> mbean, String method, String... params) { 164 try { 165 final ClassLoader loader = mbean.getClassLoader(); 166 final Class<?>[] paramClasses = new Class<?>[params.length]; 167 for (int i = 0; i < params.length; i++) { 168 paramClasses[i] = primitives.get(params[i]); 169 if (paramClasses[i] == null) 170 paramClasses[i] = Class.forName(params[i], false, loader); 171 } 172 return mbean.getMethod(method, paramClasses); 173 } catch (RuntimeException e) { 174 throw e; 175 } catch (Exception e) { 176 return null; 177 } 178 } 179 180 @Override 181 public Object invoke(String s, Object[] objects, String[] strings) throws MBeanException, ReflectionException { 182 if (audit) { 183 Subject subject = Subject.getSubject(AccessController.getContext()); 184 String caller = "anonymous"; 185 if (subject != null) { 186 caller = ""; 187 for (Principal principal : subject.getPrincipals()) { 188 caller += principal.getName() + " "; 189 } 190 } 191 192 AuditLogEntry entry = new JMXAuditLogEntry(); 193 entry.setUser(caller); 194 entry.setTimestamp(System.currentTimeMillis()); 195 entry.setOperation(this.getMBeanInfo().getClassName() + "." + s); 196 197 try 198 { 199 if (objects.length == strings.length) 200 { 201 Method m = getMBeanMethod(this.getImplementationClass(), s, strings); 202 entry.getParameters().put("arguments", AuditLogEntry.sanitizeArguments(objects, m)); 203 } 204 else 205 { 206 // Supplied Method Signature and Arguments do not match. Set all supplied Arguments in Log Entry. To diagnose user error. 207 entry.getParameters().put("arguments", objects); 208 } 209 } 210 catch (ReflectiveOperationException e) 211 { 212 // Method or Class not found, set all supplied arguments. Set all supplied Arguments in Log Entry. To diagnose user error. 213 entry.getParameters().put("arguments", objects); 214 } 215 216 auditLog.log(entry); 217 } 218 return super.invoke(s, objects, strings); 219 } 220 221 private Method getMBeanMethod(Class clazz, String methodName, String[] signature) throws ReflectiveOperationException { 222 Class[] parameterTypes = new Class[signature.length]; 223 for (int i = 0; i < signature.length; i++) { 224 parameterTypes[i] = Class.forName(signature[i]); 225 } 226 return clazz.getMethod(methodName, parameterTypes); 227 } 228}