001/* 002 * #%L 003 * Netarchivesuite - monitor 004 * %% 005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library, 006 * the National Library of France and the Austrian National Library. 007 * %% 008 * This program is free software: you can redistribute it and/or modify 009 * it under the terms of the GNU Lesser General Public License as 010 * published by the Free Software Foundation, either version 2.1 of the 011 * License, or (at your option) any later version. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Lesser Public License for more details. 017 * 018 * You should have received a copy of the GNU General Lesser Public 019 * License along with this program. If not, see 020 * <http://www.gnu.org/licenses/lgpl-2.1.html>. 021 * #L% 022 */ 023 024package dk.netarkivet.monitor.jmx; 025 026import java.io.IOException; 027import java.util.Set; 028import java.util.concurrent.atomic.AtomicBoolean; 029 030import javax.management.MBeanServerConnection; 031import javax.management.MBeanServerInvocationHandler; 032import javax.management.MalformedObjectNameException; 033import javax.management.ObjectName; 034 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038import dk.netarkivet.common.exceptions.ArgumentNotValid; 039import dk.netarkivet.common.exceptions.IOFailure; 040import dk.netarkivet.common.utils.JMXUtils; 041import dk.netarkivet.common.utils.Settings; 042import dk.netarkivet.monitor.MonitorSettings; 043 044/** Creates RMI-based JMX connections to remote servers. */ 045public class RmiProxyConnectionFactory implements JMXProxyConnectionFactory { 046 047 /** 048 * Returns a JMXProxyFactory for a specific server, jmxport, rmiport, username, and password. Makes sure that an 049 * initial context for JNDI has been specified. Then constructs a RMI-based JMXServiceUrl using the server and port. 050 * Finally connects to the URL using the name and password. 051 * 052 * @param server the given remote server 053 * @param jmxPort the JMX port on that server 054 * @param rmiPort the RMI port on that server (dedicated to the above jmxPort) 055 * @param userName the userName for access to the MBeanserver on that server 056 * @param password the password for access to the MBeanserver on that server 057 * @return a JMXProxyFactory with the above properties. 058 */ 059 public JMXProxyConnection getConnection(String server, int jmxPort, int rmiPort, String userName, String password) { 060 ArgumentNotValid.checkNotNullOrEmpty(server, "String server"); 061 ArgumentNotValid.checkNotNegative(jmxPort, "int jmxPort"); 062 ArgumentNotValid.checkNotNegative(rmiPort, "int rmiPort"); 063 ArgumentNotValid.checkNotNullOrEmpty(userName, "String userName"); 064 ArgumentNotValid.checkNotNullOrEmpty(password, "String password"); 065 return new MBeanServerProxyConnection(server, jmxPort, rmiPort, userName, password); 066 } 067 068 /** 069 * A JMXProxyFactory that constructs proxies by forwarding method calls through an MBeanServerConnection. 070 */ 071 private static class MBeanServerProxyConnection implements JMXProxyConnection { 072 /** The connection to use for method call forwarding. */ 073 private MBeanServerConnection connection; 074 /** Whether we are currently in the process of connecting. */ 075 private final AtomicBoolean connecting = new AtomicBoolean(false); 076 /** The given remote server. */ 077 private String server; 078 /** The JMX port on the server. */ 079 private int jmxPort; 080 /** The RMI call back port on the server. */ 081 private int rmiPort; 082 /** The JMX username on the server. */ 083 private String userName; 084 /** The JMX password on the server. */ 085 private String password; 086 /** The class logger. */ 087 private static final Logger log = LoggerFactory.getLogger(MBeanServerProxyConnection.class); 088 /** How long to wait for the proxied JMX connection in milliseconds. */ 089 private static final long JMX_TIMEOUT = Settings.getLong(MonitorSettings.JMX_PROXY_TIMEOUT); 090 091 /** 092 * Proxies an MBean connection with the given parameters. 093 * 094 * @param server the given remote server 095 * @param jmxPort the JMX port on that server 096 * @param rmiPort the RMI port on that server (dedicated to the above jmxPort) 097 * @param userName the userName for access to the MBeanserver on that server 098 * @param password the password for access to the MBeanserver on that server 099 */ 100 public MBeanServerProxyConnection(final String server, final int jmxPort, final int rmiPort, 101 final String userName, final String password) { 102 this.server = server; 103 this.jmxPort = jmxPort; 104 this.rmiPort = rmiPort; 105 this.userName = userName; 106 this.password = password; 107 connect(); 108 } 109 110 /** 111 * Initialise a thread to connect to the remote server. This method does not wait for the connection to finish, 112 * so there is no guarantee that the connection is initialised at the end of this method. Ensures that we only 113 * do one connect() operation at a time. 114 */ 115 private void connect() { 116 new Thread() { 117 public void run() { 118 if (connection == null && connecting.compareAndSet(false, true)) { 119 log.debug("Trying to connect to remote JMX server '{}', port '{}', rmiPort '{}', user '{}'", server, 120 jmxPort, rmiPort, userName); 121 try { 122 connection = JMXUtils 123 .getMBeanServerConnection(server, jmxPort, rmiPort, userName, password); 124 log.info("Connected to remote JMX server '{}', port '{}', rmiPort '{}', user '{}'", server, 125 jmxPort, rmiPort, userName); 126 } catch (Exception e) { 127 log.warn("Unable to connect to remote JMX server '{}', port '{}', rmiPort '{}', user '{}'", 128 server, jmxPort, rmiPort, userName, e); 129 } finally { 130 connecting.set(false); 131 synchronized (connecting) { 132 connecting.notifyAll(); 133 } 134 } 135 } 136 } 137 }.start(); 138 } 139 140 /** 141 * Sleep until the timeout has occurred, or connection is successful. 142 */ 143 private void waitForConnection() { 144 long timeouttime = System.currentTimeMillis() + JMX_TIMEOUT; 145 while (connecting.get() && (timeouttime - System.currentTimeMillis() > 0)) { 146 try { 147 synchronized (connecting) { 148 connecting.wait(Math.max(timeouttime - System.currentTimeMillis(), 1)); 149 } 150 } catch (InterruptedException e) { 151 // Just ignore it 152 } 153 } 154 } 155 156 /** 157 * Return object names from remote location. 158 * 159 * @param query The query remote mbeans should match 160 * @return set of names of matching mbeans. 161 * @throws IOFailure on communication trouble. 162 * @throws ArgumentNotValid on null or empty query. 163 */ 164 public Set<ObjectName> query(String query) { 165 ArgumentNotValid.checkNotNullOrEmpty(query, "String query"); 166 if (connection == null) { 167 connect(); 168 waitForConnection(); 169 } 170 if (connection == null) { 171 throw new IOFailure("Could not get connection for query '" + query + "'"); 172 } 173 log.debug("Trying to retrieve MBeanNames from remote JMX server '{}', port '{}', rmiPort '{}', user '{}' matching the query {}", 174 server, jmxPort, rmiPort, userName, query); 175 try { 176 return connection.queryNames(new ObjectName(query), null); 177 } catch (IOException e) { 178 throw new IOFailure("Unable to query for remote mbeans matching '" + query + "'", e); 179 } catch (MalformedObjectNameException e) { 180 throw new IOFailure("Couldn't construct the objectName with string argument:' " + query + "'.", e); 181 } 182 } 183 184 /** 185 * Returns true if this object still can return usable proxies. 186 * 187 * @return True if we can return usable proxies. Otherwise, somebody may have to make a new instance of 188 * JMXProxyFactory to get new proxies. 189 */ 190 public boolean isLive() { 191 if (connection == null) { 192 connect(); 193 waitForConnection(); 194 if (connection == null) { 195 return false; 196 } 197 } 198 try { 199 this.connection.getMBeanCount(); 200 return true; 201 } catch (Exception e) { 202 /* 203 * Catching the exception appears to be the only way to check that the connection is dead. 204 */ 205 return false; 206 } 207 } 208 209 /** 210 * Uses Java's built-in facilities for creating proxies to remote MBeans. Does not support notifications. 211 * 212 * @param name The name of an MBean on the registered MBeanServerConnection 213 * @param intf The interface that the returned proxy should implement. 214 * @param <T> the type of class the argument intf is, and the return type. 215 * @return an object implementing T. This object forwards all method calls to the MBean registered under the 216 * given name on the MBeanServerConnection that we use. 217 */ 218 public <T> T createProxy(ObjectName name, Class<T> intf) { 219 ArgumentNotValid.checkNotNull(name, "ObjectName name"); 220 ArgumentNotValid.checkNotNull(intf, "Class<T> intf"); 221 // Set true to enable notifications 222 return MBeanServerInvocationHandler.newProxyInstance(connection, name, intf, false); 223 } 224 } 225 226}