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