All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jppf.management.JMXConnectionWrapper Maven / Gradle / Ivy

There is a newer version: 6.3-alpha
Show newest version
/*
 * JPPF.
 * Copyright (C) 2005-2015 JPPF Team.
 * http://www.jppf.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jppf.management;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.*;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.*;

import javax.management.*;
import javax.management.remote.*;
import javax.management.remote.generic.GenericConnector;

import org.jppf.*;
import org.jppf.management.diagnostics.DiagnosticsMBean;
import org.jppf.ssl.SSLHelper;
import org.jppf.utils.*;
import org.slf4j.*;

/**
 * Wrapper around a JMX connection, providing a thread-safe way of handling disconnections and recovery.
 * @author Laurent Cohen
 */
public class JMXConnectionWrapper extends ThreadSynchronization implements JPPFAdminMBean, AutoCloseable {
  /**
   * Logger for this class.
   */
  private static Logger log = LoggerFactory.getLogger(JMXConnectionWrapper.class);
  /**
   * Determines whether debug log statements are enabled.
   */
  private static boolean debugEnabled = LoggingUtils.isDebugEnabled(log);
  /**
   * Prefix for the name given to the connection thread.
   */
  public static String CONNECTION_NAME_PREFIX = "jmx@";
  /**
   * The timeout in millis for JMX connection attempts. A value of 0 or less means no timeout.
   */
  private static final long CONNECTION_TIMEOUT = JPPFConfiguration.getProperties().getLong("jppf.management.connection.timeout", 60_000L);
  /**
   * URL of the MBean server, in a JMX-compliant format.
   */
  protected JMXServiceURL url = null;
  /**
   * The JMX client.
   */
  protected JMXConnector jmxc = null;
  /**
   * A connection to the MBean server.
   */
  protected AtomicReference mbeanConnection = new AtomicReference<>(null);
  /**
   * The host the server is running on.
   */
  protected String host = null;
  /**
   * The RMI port used by the server.
   */
  protected int port = 0;
  /**
   * The connection thread that performs the connection to the management server.
   */
  protected AtomicReference connectionThread = new AtomicReference<>(null);
  /**
   * A string representing this connection, used for logging purposes.
   */
  protected String idString = null;
  /**
   * A string representing this connection, used for displaying in the admin conosle.
   */
  protected String displayName = null;
  /**
   * Determines whether the connection to the JMX server has been established.
   */
  protected AtomicBoolean connected = new AtomicBoolean(false);
  /**
   * Determines whether the connection to the JMX server has been established.
   */
  protected boolean local = false;
  /**
   * JMX properties used for establishing the connection.
   */
  protected Map env = new HashMap<>();
  /**
   * Determines whether the JMX connection should be secure or not.
   */
  protected boolean sslEnabled = false;
  /**
   *
   */
  private final Object connectionLock = new Object();
  /**
   * The list of listeners to this connection wrapper.
   */
  private final List listeners = new CopyOnWriteArrayList<>();
  /**
   * The time at which connection attempts started.
   */
  private long connectionStart = 0L;

  /**
   * Initialize a local connection (same JVM) to the MBean server.
   */
  public JMXConnectionWrapper() {
    local = true;
    idString = displayName = "local";
    host = "local";
  }

  /**
   * Initialize the connection to the remote MBean server.
   * @param host the host the server is running on.
   * @param port the port used by the server.
   * @param sslEnabled specifies whether the jmx connection should be secure or not.
   */
  public JMXConnectionWrapper(final String host, final int port, final boolean sslEnabled) {
    try {
      this.host = (NetworkUtils.isIPv6Address(host)) ? "[" + host + "]" : host;
      this.port = port;
      this.sslEnabled = sslEnabled;
      idString = this.host + ':' + this.port;
      this.displayName = this.idString;
      url = new JMXServiceURL("service:jmx:jmxmp://" + idString);
      if (sslEnabled) SSLHelper.configureJMXProperties(env);
      env.put(GenericConnector.OBJECT_WRAPPING, new CustomWrapping());
      env.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, "com.sun.jmx.remote.protocol");
      env.put(JMXConnectorFactory.PROTOCOL_PROVIDER_CLASS_LOADER, getClass().getClassLoader());
      env.put(JMXConnectorFactory.DEFAULT_CLASS_LOADER, getClass().getClassLoader());
      env.put("jmx.remote.x.server.max.threads", 1);
      env.put("jmx.remote.x.client.connection.check.period", 0);
      env.put("jmx.remote.x.request.timeout", JPPFConfiguration.getProperties().getLong("jppf.jmx.request.timeout", Long.MAX_VALUE));
    } catch(Exception e) {
      log.error(e.getMessage(), e);
    }
    local = false;
  }

  /**
   * Initialize the connection to the remote MBean server.
   */
  public void connect() {
    if (isConnected()) return;
    if (local) {
      mbeanConnection.set(ManagementFactory.getPlatformMBeanServer());
      connected.set(true);
      fireConnected();
    } else {
      JMXConnectionThread jct = null;
      synchronized(this) {
        if ((jct = connectionThread.get()) == null) {
          jct = new JMXConnectionThread(this);
          connectionThread.set(jct);
          Thread t = new Thread(jct, CONNECTION_NAME_PREFIX + getId());
          t.setDaemon(true);
          connectionStart = System.currentTimeMillis();
          t.start();
        }
      }
    }
  }

  /**
   * Initiate the connection and wait until the connection is established or the timeout has expired, whichever comes first.
   * @param timeout the maximum time to wait for, a value of zero means no timeout and
   * this method just waits until the connection is established.
   */
  public void connectAndWait(final long timeout) {
    if (isConnected()) return;
    long start = System.currentTimeMillis();
    long max = timeout > 0 ? timeout : Long.MAX_VALUE;
    connect();
    long elapsed;
    while (!isConnected() && ((elapsed = System.currentTimeMillis() - start) < max)) goToSleep(max - elapsed);
  }

  /**
   * Initialize the connection to the remote MBean server.
   * @throws Exception if the connection could not be established.
   */
  void performConnection() throws Exception {
    connected.set(false);
    long elapsed;
    synchronized(this) {
      elapsed = System.currentTimeMillis() - connectionStart;
    }
    if ((CONNECTION_TIMEOUT > 0L) && (elapsed >= CONNECTION_TIMEOUT)) {
      fireTimeout();
      close();
      return;
    }
    synchronized(connectionLock) {
      if (jmxc == null) jmxc = JMXConnectorFactory.newJMXConnector(url, env);
      jmxc.connect();
      connectionThread.get().close();
      connectionThread.set(null);
    }
    synchronized(this) {
      mbeanConnection.set(jmxc.getMBeanServerConnection());
      try {
        setHost(InetAddress.getByName(host).getHostName());
      } catch (UnknownHostException e) {
      }
    }
    connected.set(true);
    wakeUp();
    fireConnected();
    if (debugEnabled) log.debug(getId() + " JMX connection successfully established");
  }

  /**
   * Close the connection to the remote MBean server.
   * @throws Exception if the connection could not be closed.
   */
  @Override
  public void close() throws Exception {
    listeners.clear();
    JMXConnectionThread jct = connectionThread.get();
    if (jct != null) jct.close();
    connectionThread.set(null);
    if (jmxc != null) { 
      if (isConnected()) jmxc.close();
      jmxc = null;
    }
  }

  /**
   * Invoke a method on the specified MBean.
   * @param name the name of the MBean.
   * @param methodName the name of the method to invoke.
   * @param params the method parameter values.
   * @param signature the types of the method parameters.
   * @return an object or null.
   * @throws Exception if the invocation failed.
   */
  public Object invoke(final String name, final String methodName, final Object[] params, final String[] signature) throws Exception {
    if (!isConnected()) {
      log.warn(String.format("invoking mbean '%s' method '%s(%s)' while not connected", name, methodName, (signature == null ? "" : StringUtils.arrayToString(signature))));
      return null;
    }
    synchronized(this) {
      Object result = null;
      try {
        ObjectName mbeanName = new ObjectName(name);
        result = getMbeanConnection().invoke(mbeanName, methodName, params, signature);
      } catch(IOException e) {
        if (debugEnabled) log.debug(String.format("error invoking mbean '%s' method '%s(%s)' while not connected%n%s", name, methodName, StringUtils.arrayToString(signature), ExceptionUtils.getStackTrace(e)));
        reset();
      }
      return result;
    }
  }

  /**
   * Invoke a method on the specified MBean.
   * This is a convenience method to be used when invoking a remote MBean method with no parameters.
* This is equivalent to calling invoke(name, methodName, (Object[]) null, (String[]) null). * @param name the name of the MBean. * @param methodName the name of the method to invoke. * @return an object or null. * @throws Exception if the invocation failed. */ public Object invoke(final String name, final String methodName) throws Exception { return invoke(name, methodName, (Object[]) null, (String[]) null); } /** * Get the value of an attribute of the specified MBean. * @param name the name of the MBean. * @param attribute the name of the attribute to read. * @return an object or null. * @throws Exception if the invocation failed. */ public Object getAttribute(final String name, final String attribute) throws Exception { if (!isConnected()) { log.warn(String.format("getting mbean '%s' attribute '%s' while not connected", name, attribute)); return null; } synchronized(this) { Object result = null; try { ObjectName mbeanName = new ObjectName(name); result = getMbeanConnection().getAttribute(mbeanName, attribute); } catch(IOException e) { if (debugEnabled) log.debug(getId() + " : error while invoking the JMX connection", e); reset(); throw e; } return result; } } /** * Set the value of an attribute of the specified MBean. * @param name the name of the MBean. * @param attribute the name of the attribute to write. * @param value the value to set on the attribute. * @throws Exception if the invocation failed. */ public void setAttribute(final String name, final String attribute, final Object value) throws Exception { if (!isConnected()) { log.warn(String.format("setting mbean '%s' attribute '%s' while not connected", name, attribute)); return; } synchronized(this) { try { ObjectName mbeanName = new ObjectName(name); getMbeanConnection().setAttribute(mbeanName, new Attribute(attribute, value)); } catch(IOException e) { if (debugEnabled) log.debug(getId() + " : error while invoking the JMX connection", e); reset(); throw e; } } } /** * Reset the JMX connection and attempt to reconnect. */ private void reset() { connected.set(false); if (jmxc != null) { try { jmxc.close(); } catch(Exception e2) { if (debugEnabled) log.debug(e2.getMessage(), e2); } jmxc = null; } connect(); } /** * Get the host the server is running on. * @return the host as a string. */ public String getHost() { return host; } /** * Get the host the server is running on. * @param host the host as a string. */ public void setHost(final String host) { this.host = host; //this.idString = this.host + ':' + this.port; this.displayName = this.host + ':' + this.port; } /** * Get the JMX remote port used by the server. * @return the port as an int. */ public int getPort() { return port; } /** * Get a string describing this connection. * @return a string in the format host:port. */ public String getId() { return idString; } /** * Get the service URL of the MBean server. * @return a {@link JMXServiceURL} instance. */ public JMXServiceURL getURL() { return url; } /** * Get the connection to the MBean server. * @return a MBeanServerConnection instance. */ public MBeanServerConnection getMbeanConnection() { return mbeanConnection.get(); } /** * Determines whether the connection to the JMX server has been established. * @return true if the connection is established, false otherwise. */ public boolean isConnected() { return connected.get(); } /** * Obtain a proxy to the specified remote MBean. * @param the type of the MBean (must be an interface). * @param name the name of the mbean to retrieve. * @param inf the class of the MBean interface. * @return an instance of the specified proxy interface. * @throws Exception if any error occurs. */ public T getProxy(final String name, final Class inf) throws Exception { return getProxy(new ObjectName(name), inf); } /** * Obtain a proxy to the specified remote MBean. * @param the type of the MBean (must be an interface). * @param objectName the name of the mbean to retrieve. * @param inf the class of the MBean interface. * @return an instance of the specified proxy interface. * @throws Exception if any error occurs. */ public T getProxy(final ObjectName objectName, final Class inf) throws Exception { if (!isConnected()) connect(); if (isConnected()) { MBeanServerConnection mbsc = getMbeanConnection(); return JMX.newMBeanProxy(mbsc, objectName, inf, true); } return null; } /** * Get a proxy to the diagnostics/JVM health MBean. * @return a DiagnosticsMBean instance. * @throws Exception if any error occurs. */ public DiagnosticsMBean getDiagnosticsProxy() throws Exception { throw new JPPFUnsupportedOperationException("this method can only be invoked on a subclass of " + getClass().getName()); } /** * Adds a listener to the specified MBean. * @param mBeanName the name of the MBean on which the listener should be added. * @param listener the listener to add. * @throws Exception if any error occurs. */ public void addNotificationListener(final String mBeanName, final NotificationListener listener) throws Exception { mbeanConnection.get().addNotificationListener(new ObjectName(mBeanName), listener, null, null); } /** * Adds a listener to the specified MBean. * @param mBeanName the name of the MBean on which the listener should be added. * @param listener the listener to add. * @param filter the filter object. * @param handback the handback object to use. * @throws Exception if any error occurs. */ public void addNotificationListener(final String mBeanName, final NotificationListener listener, final NotificationFilter filter, final Object handback) throws Exception { mbeanConnection.get().addNotificationListener(new ObjectName(mBeanName), listener, filter, handback); } /** * Remove a listener from the specified MBean. * @param mBeanName the name of the MBean from which the listener should be removed. * @param listener the listener to remove. * @throws Exception if any error occurs. */ public void removeNotificationListener(final String mBeanName, final NotificationListener listener) throws Exception { mbeanConnection.get().removeNotificationListener(new ObjectName(mBeanName), listener, null, null); } /** * Remove a listener from the specified MBean. * @param mBeanName the name of the MBean from which the listener should be removed. * @param listener the listener to remove. * @param filter the filter object. * @param handback the handback object used. * @throws Exception if any error occurs. */ public void removeNotificationListener(final String mBeanName, final NotificationListener listener, final NotificationFilter filter, final Object handback) throws Exception { mbeanConnection.get().removeNotificationListener(new ObjectName(mBeanName), listener, filter, handback); } /** * Get the {@link MBeanNotificationInfo} descriptors for the specified MBean. * @param mBeanName the name of the MBean. * @return a an array of {@link MBeanNotificationInfo}, which is empty if the MBean does not implement {@link NotificationBroadcaster}. * @throws Exception if any error occurs. */ public MBeanNotificationInfo[] getNotificationInfo(final String mBeanName) throws Exception { return mbeanConnection.get().getMBeanInfo(new ObjectName(mBeanName)).getNotifications(); } @Override public JPPFSystemInformation systemInformation() throws Exception { throw new JPPFException("this method is not implemented"); } /** * Get the string representing this connection, used for displaying in the admin conosle. * @return the display name as a string. */ public String getDisplayName() { return displayName; } /** * Determine whether the JMX connection is secure or not. * @return true if this connection is secure, false otherwise. */ public boolean isSecure() { return sslEnabled; } @Override public String toString() { StringBuilder sb = new StringBuilder().append(getClass().getSimpleName()).append('['); //sb.append(", idString=").append( idString); sb.append("url=").append( url); sb.append(", connected=").append( connected); sb.append(", local=").append( local); sb.append(", secure=").append( sslEnabled); sb.append(']'); return sb.toString(); } /** * Add a listener to this connection wrapper * @param listener the listener to add. */ public void addJMXWrapperListener(final JMXWrapperListener listener) { listeners.add(listener); } /** * Remove a listener from this connection wrapper * @param listener the listener to add. */ public void removeJMXWrapperListener(final JMXWrapperListener listener) { listeners.remove(listener); } /** * Notify all listeners that the connection was successful. */ protected void fireConnected() { JMXWrapperEvent event = new JMXWrapperEvent(this); for (JMXWrapperListener listener: listeners) listener.jmxWrapperConnected(event); } /** * Notify all listeners that the connection could not be established before reaching the timeout. */ protected void fireTimeout() { JMXWrapperEvent event = new JMXWrapperEvent(this); for (JMXWrapperListener listener: listeners) listener.jmxWrapperTimeout(event); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy