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
021
022import java.io.IOException;
023import java.lang.management.ManagementFactory;
024
025import javax.management.*;
026import javax.management.remote.*;
027
028import org.jppf.*;
029import org.jppf.management.diagnostics.DiagnosticsMBean;
030import org.jppf.utils.*;
031import org.jppf.utils.concurrent.*;
032import org.slf4j.*;
033
034/**
035 * Wrapper around a JMX connection, providing a thread-safe way of handling disconnections and recovery.
036 * @author Laurent Cohen
037 */
038public class JMXConnectionWrapper extends AbstractJMXConnectionWrapper {
039  /**
040   * Explicit serialVersionUID.
041   */
042  private static final long serialVersionUID = 1L;
043  /**
044   * Logger for this class.
045   */
046  private static Logger log = LoggerFactory.getLogger(JMXConnectionWrapper.class);
047  /**
048   * Determines whether debug log statements are enabled.
049   */
050  private static boolean debugEnabled = LoggingUtils.isDebugEnabled(log);
051
052  /**
053   * Initialize a local connection (same JVM) to the MBean server.
054   */
055  public JMXConnectionWrapper() {
056    super();
057  }
058
059  /**
060   * Initialize the connection to the remote MBean server.
061   * @param host the host the server is running on.
062   * @param port the port used by the server.
063   * @param sslEnabled specifies whether the jmx connection should be secure or not.
064   */
065  public JMXConnectionWrapper(final String host, final int port, final boolean sslEnabled) {
066    super(host, port, sslEnabled);
067  }
068
069  /**
070   * Initialize the connection to the remote MBean server.
071   * @param protocol the JMX remote protocol to use.
072   * @param host the host the server is running on.
073   * @param port the port used by the server.
074   * @param sslEnabled specifies whether the jmx connection should be secure or not.
075   */
076  public JMXConnectionWrapper(final String protocol, final String host, final int port, final boolean sslEnabled) {
077    super(protocol, host, port, sslEnabled);
078  }
079
080  /**
081   * Initialize the connection to the remote MBean server.
082   */
083  @Override
084  public void connect() {
085    if (isConnected()) return;
086    if (local) {
087      mbeanConnection.set(ManagementFactory.getPlatformMBeanServer());
088      connected.set(true);
089      fireConnected();
090    } else {
091      JMXConnectionThread jct = null;
092      synchronized(this) {
093        if ((jct = connectionThread.get()) == null) {
094          jct = new JMXConnectionThread(this);
095          connectionThread.set(jct);
096          connectionStart = System.nanoTime();
097          ThreadUtils.startDaemonThread(jct, CONNECTION_NAME_PREFIX + getId());
098        }
099      }
100    }
101  }
102
103  /**
104   * Initiate the connection and wait until the connection is established or the timeout has expired, whichever comes first.
105   * @param timeout the maximum time to wait for, a value of zero means no timeout and
106   * this method just waits until the connection is established.
107   * @return {@code true} if the connection was established in the specified time, {@code false} otherwise. 
108   */
109  @Override
110  public boolean connectAndWait(final long timeout) {
111    if (isConnected()) return true;
112    final long start = System.nanoTime();
113    final long max = timeout > 0 ? timeout : Long.MAX_VALUE;
114    connect();
115    long elapsed;
116    while (!isConnected() && ((elapsed = (System.nanoTime() - start) / 1_000_000L) < max)) goToSleep(Math.min(10L, max - elapsed));
117    return isConnected();
118  }
119
120  @Override
121  public void close() throws Exception {
122    if (closed.compareAndSet(false, true)) {
123      connected.compareAndSet(true, false);
124      listeners.clear();
125      final JMXConnectionThread jct = connectionThread.get();
126      if (jct != null) {
127        jct.close();
128        connectionThread.set(null);
129      }
130      if (jmxc != null) {
131        final JMXConnector connector = jmxc;
132        jmxc = null;
133        final Runnable r = new Runnable() {
134          @Override
135          public void run() {
136            try {
137              connector.close();
138            } catch (final Exception e) {
139              if (debugEnabled) log.debug(e.getMessage(), e);
140            }
141          }
142        };
143        ThreadUtils.startThread(r, getDisplayName() + " closing");
144      }
145    }
146  }
147
148  /**
149   * Invoke a method on the specified MBean.
150   * @param name the name of the MBean.
151   * @param methodName the name of the method to invoke.
152   * @param params the method parameter values.
153   * @param signature the types of the method parameters.
154   * @return an object or null.
155   * @throws Exception if the invocation failed.
156   */
157  public Object invoke(final String name, final String methodName, final Object[] params, final String[] signature) throws Exception {
158    if (!isConnected()) {
159      log.warn("invoking mbean '{}' method '{}({})' while not connected", name, methodName, (signature == null ? "" : StringUtils.arrayToString(signature)));
160      return null;
161    }
162    Object result = null;
163    try {
164      final ObjectName mbeanName = ObjectNameCache.getObjectName(name);
165      result = getMbeanConnection().invoke(mbeanName, methodName, params, signature);
166    } catch(final IOException e) {
167      final String msg = String.format("error invoking mbean '%s' method '%s(%s)' while not connected%n%s", name, methodName, StringUtils.arrayToString(signature), ExceptionUtils.getStackTrace(e));
168      if (debugEnabled) log.debug(msg);
169      reset();
170    }
171    return result;
172  }
173
174  /**
175   * Invoke a method on the specified MBean.
176   * This is a convenience method to be used when invoking a remote MBean method with no parameters.<br/>
177   * This is equivalent to calling <code>invoke(name, methodName, (Object[]) null, (String[]) null)</code>.
178   * @param name the name of the MBean.
179   * @param methodName the name of the method to invoke.
180   * @return an object or null.
181   * @throws Exception if the invocation failed.
182   */
183  public Object invoke(final String name, final String methodName) throws Exception {
184    return invoke(name, methodName, (Object[]) null, (String[]) null);
185  }
186
187  /**
188   * Get the value of an attribute of the specified MBean.
189   * @param name the name of the MBean.
190   * @param attribute the name of the attribute to read.
191   * @return an object or null.
192   * @throws Exception if the invocation failed.
193   */
194  public Object getAttribute(final String name, final String attribute) throws Exception {
195    if (!isConnected()) {
196      log.warn("getting mbean '{}' attribute '{}' while not connected", name, attribute);
197      return null;
198    }
199    Object result = null;
200    try {
201      final ObjectName mbeanName = ObjectNameCache.getObjectName(name);
202      result = getMbeanConnection().getAttribute(mbeanName, attribute);
203    } catch(final IOException e) {
204      if (debugEnabled) log.debug(getId() + " : error while invoking the JMX connection", e);
205      reset();
206      throw e;
207    }
208    return result;
209  }
210
211  /**
212   * Set the value of an attribute of the specified MBean.
213   * @param name the name of the MBean.
214   * @param attribute the name of the attribute to write.
215   * @param value the value to set on the attribute.
216   * @throws Exception if the invocation failed.
217   */
218  public void setAttribute(final String name, final String attribute, final Object value) throws Exception {
219    if (!isConnected()) {
220      log.warn("setting mbean '{}' attribute '{}' while not connected", name, attribute);
221      return;
222    }
223    try {
224      final ObjectName mbeanName = ObjectNameCache.getObjectName(name);
225      getMbeanConnection().setAttribute(mbeanName, new Attribute(attribute, value));
226    } catch(final IOException e) {
227      if (debugEnabled) log.debug(getId() + " : error while invoking the JMX connection", e);
228      reset();
229      throw e;
230    }
231  }
232
233  /**
234   * Get the JMX remote port used by the server.
235   * @return the port as an int.
236   */
237  public int getPort() {
238    return port;
239  }
240
241  /**
242   * Get the service URL of the MBean server.
243   * @return a {@link JMXServiceURL} instance.
244   */
245  public JMXServiceURL getURL() {
246    return url;
247  }
248
249  /**
250   * Get the connection to the MBean server.
251   * @return a <code>MBeanServerConnection</code> instance.
252   */
253  public MBeanServerConnection getMbeanConnection() {
254    return mbeanConnection.get();
255  }
256
257  /**
258   * Determines whether the connection to the JMX server has been established.
259   * @return true if the connection is established, false otherwise.
260   */
261  public boolean isConnected() {
262    return connected.get();
263  }
264
265  /**
266   * Obtain a proxy to the specified remote MBean.
267   * @param <T> the type of the MBean (must be an interface).
268   * @param name the name of the mbean to retrieve.
269   * @param inf the class of the MBean interface.
270   * @return an instance of the specified proxy interface.
271   * @throws Exception if any error occurs.
272   */
273  public <T> T getProxy(final String name, final Class<T> inf) throws Exception {
274    return getProxy(ObjectNameCache.getObjectName(name), inf);
275  }
276
277  /**
278   * Obtain a proxy to the specified remote MBean.
279   * @param <T> the type of the MBean (must be an interface).
280   * @param objectName the name of the mbean to retrieve.
281   * @param inf the class of the MBean interface.
282   * @return an instance of the specified proxy interface.
283   * @throws Exception if any error occurs.
284   */
285  public <T> T getProxy(final ObjectName objectName, final Class<T> inf) throws Exception {
286    if (!isConnected()) connect();
287    if (isConnected()) {
288      final MBeanServerConnection mbsc = getMbeanConnection();
289      return MBeanInvocationHandler.newMBeanProxy(inf, mbsc, objectName);
290    }
291    return null;
292  }
293
294  /**
295   * Get a proxy to the diagnostics/JVM health MBean.
296   * @return a DiagnosticsMBean instance.
297   * @throws Exception if any error occurs.
298   */
299  public DiagnosticsMBean getDiagnosticsProxy() throws Exception {
300    throw new JPPFUnsupportedOperationException("this method can only be invoked on a subclass of " + getClass().getName());
301  }
302
303  /**
304   * Adds a listener to the specified MBean.
305   * @param mBeanName the name of the MBean on which the listener should be added.
306   * @param listener the listener to add.
307   * @throws Exception if any error occurs.
308   */
309  public void addNotificationListener(final String mBeanName, final NotificationListener listener) throws Exception {
310    mbeanConnection.get().addNotificationListener(ObjectNameCache.getObjectName(mBeanName), listener, null, null);
311  }
312
313  /**
314   * Adds a listener to the specified MBean.
315   * @param mBeanName the name of the MBean on which the listener should be added.
316   * @param listener the listener to add.
317   * @param filter the filter object.
318   * @param handback the handback object to use.
319   * @throws Exception if any error occurs.
320   */
321  public void addNotificationListener(final String mBeanName, final NotificationListener listener, final NotificationFilter filter, final Object handback) throws Exception {
322    mbeanConnection.get().addNotificationListener(ObjectNameCache.getObjectName(mBeanName), listener, filter, handback);
323  }
324
325  /**
326   * Remove a listener from the specified MBean.
327   * @param mBeanName the name of the MBean from which the listener should be removed.
328   * @param listener the listener to remove.
329   * @throws Exception if any error occurs.
330   */
331  public void removeNotificationListener(final String mBeanName, final NotificationListener listener) throws Exception {
332    mbeanConnection.get().removeNotificationListener(ObjectNameCache.getObjectName(mBeanName), listener, null, null);
333  }
334
335  /**
336   * Remove a listener from the specified MBean.
337   * @param mBeanName the name of the MBean from which the listener should be removed.
338   * @param listener the listener to remove.
339   * @param filter the filter object.
340   * @param handback the handback object used.
341   * @throws Exception if any error occurs.
342   */
343  public void removeNotificationListener(final String mBeanName, final NotificationListener listener, final NotificationFilter filter, final Object handback) throws Exception {
344    mbeanConnection.get().removeNotificationListener(ObjectNameCache.getObjectName(mBeanName), listener, filter, handback);
345  }
346
347  /**
348   * Get the {@link MBeanNotificationInfo} descriptors for the specified MBean.
349   * @param mBeanName the name of the MBean.
350   * @return a an array of {@link MBeanNotificationInfo}, which is empty if the MBean does not implement {@link NotificationBroadcaster}.
351   * @throws Exception if any error occurs.
352   */
353  public MBeanNotificationInfo[] getNotificationInfo(final String mBeanName) throws Exception {
354    return mbeanConnection.get().getMBeanInfo(ObjectNameCache.getObjectName(mBeanName)).getNotifications();
355  }
356
357  @Override
358  public JPPFSystemInformation systemInformation() throws Exception {
359    throw new JPPFException("this method is not implemented");
360  }
361
362  /**
363   * Determine whether the JMX connection is secure or not.
364   * @return <code>true</code> if this connection is secure, <code>false</code> otherwise.
365   */
366  public boolean isSecure() {
367    return sslEnabled;
368  }
369
370  /**
371   * The JMX client connector.
372   * @return a {@link JMXConnector} instance.
373   */
374  public JMXConnector getJmxconnector() {
375    return jmxc;
376  }
377}