001/*
002 * JPPF.
003 * Copyright (C) 2005-2018 JPPF Team.
004 * http://www.jppf.org
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.jppf.management;
020
021import java.lang.reflect.*;
022import java.util.Map;
023import java.util.concurrent.locks.*;
024
025import javax.management.*;
026
027import org.jppf.*;
028import org.jppf.utils.*;
029import org.jppf.utils.collections.SoftReferenceValuesMap;
030
031/**
032 * An invocation handler used by MBean proxies created by JPPF.
033 * @author Laurent Cohen
034 */
035public class MBeanInvocationHandler implements InvocationHandler {
036  /**
037   * Invocation of an arbitrary MBean method other than an attribute setter or getter.
038   */
039  private static final int INVOKE = 1;
040  /**
041   * Invocation of a {@code set<Attribute>(...)} method.
042   */
043  private static final int SET_ATTRIBUTE = 2;
044  /**
045   * Invocation of a {@code get<Attribute>()} method.
046   */
047  private static final int GET_ATTRIBUTE = 3;
048  /**
049   * Invocation of {@code addNotificationListener(NotificationListener, NotificationFilter, Object)}.
050   */
051  private static final int ADD_NOTIFICATION_LISTENER = 4;
052  /**
053   * Invocation of {@code removeNotificationListener(NotificationListener)}.
054   */
055  private static final int REMOVE_NOTIFICATION_LISTENER = 5;
056  /**
057   * Invocation of {@code removeNotificationListener(NotificationListener, NotificationFilter, Object)}.
058   */
059  private static final int REMOVE_NOTIFICATION_LISTENER_FILTER_HANDBACK = 6;
060  /**
061   * Method {@code addNotificationListener(NotificationListener, NotificationFilter, Object)}.
062   */
063  private static final Method ADD_NOTIFICATION_LISTENER_METHOD =
064    ReflectionHelper.findMethod(NotificationBroadcaster.class, "addNotificationListener", NotificationListener.class, NotificationFilter.class, Object.class);
065  /**
066   * Method {@code removeNotificationListener(NotificationListener}.
067   */
068  private static final Method REMOVE_NOTIFICATION_LISTENER_METHOD = ReflectionHelper.findMethod(NotificationBroadcaster.class, "removeNotificationListener", NotificationListener.class);
069  /**
070   * Method {@code removeNotificationListener(NotificationListener, NotificationFilter, Object)}.
071   */
072  private static final Method REMOVE_NOTIFICATION_LISTENER_3_METHOD =
073    ReflectionHelper.findMethod(NotificationEmitter.class, "removeNotificationListener", NotificationListener.class, NotificationFilter.class, Object.class);
074  /**
075   * Constant for signature of methods with no parameters.
076   */
077  private static final String[] EMPTY_SIG = {};
078  /**
079   * Mapping of methods to their descriptor.
080   */
081  private static final Map<Method, MethodInfo> methodMap = new SoftReferenceValuesMap<>();
082  /**
083   * Used to synchronize access to the method map.
084   */
085  private static final Lock mapLock = new ReentrantLock();
086  /**
087   * The MBean server connection.
088   */
089  private final MBeanServerConnection mbsc;
090  /**
091   * The object name of the MBean to proxy.
092   */
093  private final ObjectName objectName;
094
095  /**
096   * Initialize with the specified MBean connection and MBean object name.
097   * @param mbsc the MBean server connection..
098   * @param objectName the object name of the MBean to proxy.
099   */
100  public MBeanInvocationHandler(final MBeanServerConnection mbsc, final ObjectName objectName) {
101    this.mbsc = mbsc;
102    this.objectName = objectName;
103  }
104
105  @Override
106  public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
107    MethodInfo info;
108    mapLock.lock();
109    try {
110      info = methodMap.get(method);
111      if (info == null) {
112        info = new MethodInfo(method);
113        methodMap.put(method, info);
114      }
115    } finally {
116      mapLock.unlock();
117    }
118    switch(info.type) {
119      case SET_ATTRIBUTE:
120        mbsc.setAttribute(objectName, new Attribute(info.attribute, args[0]));
121        break;
122
123      case GET_ATTRIBUTE:
124        return mbsc.getAttribute(objectName, info.attribute);
125
126      case INVOKE:
127        return mbsc.invoke(objectName, method.getName(), args, info.signature);
128
129      case ADD_NOTIFICATION_LISTENER:
130        mbsc.addNotificationListener(objectName, (NotificationListener) args[0], (NotificationFilter) args[1], args[2]);
131        break;
132
133      case REMOVE_NOTIFICATION_LISTENER:
134        mbsc.removeNotificationListener(objectName, (NotificationListener) args[0]);
135        break;
136
137      case REMOVE_NOTIFICATION_LISTENER_FILTER_HANDBACK:
138        mbsc.removeNotificationListener(objectName, (NotificationListener) args[0], (NotificationFilter) args[1], args[2]);
139        break;
140
141      default:
142        throw new JPPFUnsupportedOperationException("unsupported method type for " + method);
143    }
144    return null;
145  }
146
147  /**
148   * Create a new proxy instance for the specified MBean.
149   * @param <T> the type of the proxy interface.
150   * @param inf the class of the MBean's infterface.
151   * @param mbsc the MBean server connection.
152   * @param objectName the object name of the MBean to proxy.
153   * @return a proxy of the same type as the provided interface.
154   */
155  @SuppressWarnings("unchecked")
156  public static <T> T newMBeanProxy(final Class<T> inf, final MBeanServerConnection mbsc, final ObjectName objectName) {
157    return (T) Proxy.newProxyInstance(inf.getClassLoader(), new Class<?>[] {inf, NotificationEmitter.class}, new MBeanInvocationHandler(mbsc, objectName));
158  }
159
160  /**
161   * Instances of this class describe a method invoked by the invocation handler.
162   */
163  private static final class MethodInfo {
164    /**
165     * The method signature, if any.
166     */
167    private final String[] signature;
168    /**
169     * The attribute name, if any.
170     */
171    private final String attribute;
172    /**
173     * The type of operation performed by the method, i.e. INVOKE, SET_ATTRIBUTE or GET_ATTRIBUTE.
174     */
175    private final int type;
176
177    /**
178     * Initialize the info about the method.
179     * @param method the mthod to describe.
180     * @throws Exception if any error occurs.
181     */
182    private MethodInfo(final Method method) throws Exception {
183      final String name = method.getName();
184      if (ReflectionUtils.isSetter(method)) type = SET_ATTRIBUTE;
185      else if (ReflectionUtils.isGetter(method)) type = GET_ATTRIBUTE;
186      else if ("addNotificationListener".equals(name)) {
187        type = (ReflectionUtils.sameSignature(method, ADD_NOTIFICATION_LISTENER_METHOD)) ? ADD_NOTIFICATION_LISTENER : INVOKE;
188      } else if ("removeNotificationListener".equals(name)) {
189        type = (ReflectionUtils.sameSignature(method, REMOVE_NOTIFICATION_LISTENER_METHOD))
190          ? REMOVE_NOTIFICATION_LISTENER : (ReflectionUtils.sameSignature(method, REMOVE_NOTIFICATION_LISTENER_3_METHOD) ? REMOVE_NOTIFICATION_LISTENER_FILTER_HANDBACK : INVOKE);
191      } else type = INVOKE;
192      switch(type) {
193        case SET_ATTRIBUTE:
194        case GET_ATTRIBUTE:
195          signature = EMPTY_SIG;
196          attribute = ReflectionUtils.getMBeanAttributeName(name);
197          break;
198
199        case INVOKE:
200          attribute = null;
201          final Class<?>[] paramTypes = method.getParameterTypes();
202          if (paramTypes.length <= 0) signature = EMPTY_SIG;
203          else {
204            signature = new String[paramTypes.length];
205            for (int i=0; i<paramTypes.length; i++) signature[i] = paramTypes[i].getName();
206          }
207          break;
208
209        case ADD_NOTIFICATION_LISTENER:
210        case REMOVE_NOTIFICATION_LISTENER:
211        case REMOVE_NOTIFICATION_LISTENER_FILTER_HANDBACK:
212          attribute = null;
213          signature = EMPTY_SIG;
214          break;
215
216        default:
217          throw new JPPFUnsupportedOperationException("unsupported method type for " + method);
218      }
219    }
220  }
221}