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     */
017    package org.apache.activemq.broker.jmx;
018    
019    import java.lang.annotation.Annotation;
020    import java.lang.reflect.Method;
021    import java.security.AccessController;
022    import java.security.Principal;
023    import java.util.HashMap;
024    import java.util.Map;
025    
026    import javax.management.MBeanAttributeInfo;
027    import javax.management.MBeanException;
028    import javax.management.MBeanOperationInfo;
029    import javax.management.MBeanParameterInfo;
030    import javax.management.NotCompliantMBeanException;
031    import javax.management.ObjectName;
032    import javax.management.ReflectionException;
033    import javax.management.StandardMBean;
034    import javax.security.auth.Subject;
035    
036    import org.apache.activemq.broker.util.AuditLogEntry;
037    import org.apache.activemq.broker.util.AuditLogService;
038    import org.apache.activemq.broker.util.JMXAuditLogEntry;
039    import org.slf4j.Logger;
040    import org.slf4j.LoggerFactory;
041    
042    /**
043     * MBean that looks for method/parameter descriptions in the Info annotation.
044     */
045    public 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                entry.getParameters().put("arguments", objects);
195    
196                auditLog.log(entry);
197            }
198            return super.invoke(s, objects, strings);
199        }
200    }