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