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}