1. /*
  2. * @(#)RMIServerImpl.java 1.54 04/06/03
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.management.remote.rmi;
  8. import java.io.IOException;
  9. import java.lang.ref.WeakReference;
  10. import java.rmi.Remote;
  11. import java.rmi.server.RemoteObject;
  12. import java.rmi.server.RemoteServer;
  13. import java.rmi.server.ServerNotActiveException;
  14. import java.security.Principal;
  15. import java.security.AccessController;
  16. import java.security.PrivilegedAction;
  17. import java.util.ArrayList;
  18. import java.util.Collections;
  19. import java.util.Iterator;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.Set;
  23. import javax.management.MBeanServer;
  24. import javax.management.remote.JMXAuthenticator;
  25. import javax.management.remote.JMXConnectorFactory;
  26. import javax.management.remote.JMXConnectorServer;
  27. import javax.security.auth.Subject;
  28. import com.sun.jmx.remote.security.JMXPluggableAuthenticator;
  29. import com.sun.jmx.remote.util.ClassLogger;
  30. import com.sun.jmx.remote.internal.ArrayNotificationBuffer;
  31. import com.sun.jmx.remote.internal.NotificationBuffer;
  32. /**
  33. * <p>An RMI object representing a connector server. Remote clients
  34. * can make connections using the {@link #newClient(Object)} method. This
  35. * method returns an RMI object representing the connection.</p>
  36. *
  37. * <p>User code does not usually reference this class directly. RMI
  38. * connection servers are usually created with the class {@link
  39. * RMIConnectorServer}. Remote clients usually create connections
  40. * either with {@link JMXConnectorFactory} or by instantiating {@link
  41. * RMIConnector}.</p>
  42. *
  43. * <p>This is an abstract class. Concrete subclasses define the
  44. * details of the client connection objects, such as whether they use
  45. * JRMP or IIOP.</p>
  46. *
  47. * @since 1.5
  48. * @since.unbundled 1.0
  49. */
  50. public abstract class RMIServerImpl implements RMIServer {
  51. /**
  52. * <p>Constructs a new <code>RMIServerImpl</code>.</p>
  53. *
  54. * @param env the environment containing attributes for the new
  55. * <code>RMIServerImpl</code>. Can be null, which is equivalent
  56. * to an empty Map.
  57. */
  58. public RMIServerImpl(Map<String,?> env) {
  59. this.env = (env == null) ? Collections.EMPTY_MAP : env;
  60. if (logger.debugOn())
  61. logger.trace("RMIServerImpl","class="+this.getClass().getName());
  62. }
  63. void setRMIConnectorServer(RMIConnectorServer connServer)
  64. throws IOException {
  65. this.connServer = connServer;
  66. }
  67. /**
  68. * <p>Exports this RMI object.</p>
  69. *
  70. * @exception IOException if this RMI object cannot be exported.
  71. */
  72. protected abstract void export() throws IOException;
  73. /**
  74. * Returns a remotable stub for this server object.
  75. * @return a remotable stub.
  76. * @exception IOException if the stub cannot be obtained - e.g the
  77. * RMIServerImpl has not been exported yet.
  78. **/
  79. public abstract Remote toStub() throws IOException;
  80. /**
  81. * <p>Sets the default <code>ClassLoader</code> for this connector
  82. * server. New client connections will use this classloader.
  83. * Existing client connections are unaffected.</p>
  84. *
  85. * @param cl the new <code>ClassLoader</code> to be used by this
  86. * connector server.
  87. *
  88. * @see #getDefaultClassLoader
  89. */
  90. public synchronized void setDefaultClassLoader(ClassLoader cl) {
  91. this.cl = cl;
  92. }
  93. /**
  94. * <p>Gets the default <code>ClassLoader</code> used by this connector
  95. * server.</p>
  96. *
  97. * @return the default <code>ClassLoader</code> used by this
  98. * connector server.</p>
  99. *
  100. * @see #setDefaultClassLoader
  101. */
  102. public synchronized ClassLoader getDefaultClassLoader() {
  103. return cl;
  104. }
  105. /**
  106. * <p>Sets the <code>MBeanServer</code> to which this connector
  107. * server is attached. New client connections will interact
  108. * with this <code>MBeanServer</code>. Existing client connections are
  109. * unaffected.</p>
  110. *
  111. * @param mbs the new <code>MBeanServer</code>. Can be null, but
  112. * new client connections will be refused as long as it is.
  113. *
  114. * @see #getMBeanServer
  115. */
  116. public synchronized void setMBeanServer(MBeanServer mbs) {
  117. this.mbeanServer = mbs;
  118. }
  119. /**
  120. * <p>The <code>MBeanServer</code> to which this connector server
  121. * is attached. This is the last value passed to {@link
  122. * #setMBeanServer} on this object, or null if that method has
  123. * never been called.</p>
  124. *
  125. * @return the <code>MBeanServer</code> to which this connector
  126. * is attached.
  127. *
  128. * @see #setMBeanServer
  129. */
  130. public synchronized MBeanServer getMBeanServer() {
  131. return mbeanServer;
  132. }
  133. public String getVersion() {
  134. // Expected format is: "protocol-version implementation-name"
  135. return "1.0 java_runtime_" + (String)
  136. AccessController.doPrivileged(new PrivilegedAction() {
  137. public Object run() {
  138. return System.getProperty("java.runtime.version");
  139. }
  140. });
  141. }
  142. /**
  143. * <p>Creates a new client connection. This method calls {@link
  144. * #makeClient makeClient} and adds the returned client connection
  145. * object to an internal list. When this
  146. * <code>RMIServerImpl</code> is shut down via its {@link
  147. * #close()} method, the {@link RMIConnection#close() close()}
  148. * method of each object remaining in the list is called.</p>
  149. *
  150. * <p>The fact that a client connection object is in this internal
  151. * list does not prevent it from being garbage collected.</p>
  152. *
  153. * @param credentials this object specifies the user-defined
  154. * credentials to be passed in to the server in order to
  155. * authenticate the caller before creating the
  156. * <code>RMIConnection</code>. Can be null.
  157. *
  158. * @return the newly-created <code>RMIConnection</code>. This is
  159. * usually the object created by <code>makeClient</code>, though
  160. * an implementation may choose to wrap that object in another
  161. * object implementing <code>RMIConnection</code>.
  162. *
  163. * @exception IOException if the new client object cannot be
  164. * created or exported.
  165. *
  166. * @exception SecurityException if the given credentials do not allow
  167. * the server to authenticate the user successfully.
  168. *
  169. * @exception IllegalStateException if {@link #getMBeanServer()}
  170. * is null.
  171. */
  172. public RMIConnection newClient(Object credentials) throws IOException {
  173. final boolean tracing = logger.traceOn();
  174. if (tracing) logger.trace("newClient","making new client");
  175. if (getMBeanServer() == null)
  176. throw new IllegalStateException("Not attached to an MBean server");
  177. Subject subject = null;
  178. JMXAuthenticator authenticator =
  179. (JMXAuthenticator) env.get(JMXConnectorServer.AUTHENTICATOR);
  180. if (authenticator == null) {
  181. /*
  182. * Create the JAAS-based authenticator only if authentication
  183. * has been enabled
  184. */
  185. if (env.get("jmx.remote.x.password.file") != null ||
  186. env.get("jmx.remote.x.login.config") != null) {
  187. authenticator = new JMXPluggableAuthenticator(env);
  188. }
  189. }
  190. if (authenticator != null) {
  191. if (tracing) logger.trace("newClient","got authenticator: " +
  192. authenticator.getClass().getName());
  193. try {
  194. subject = authenticator.authenticate(credentials);
  195. } catch (SecurityException e) {
  196. logger.trace("newClient", "Authentication failed: " + e);
  197. throw e;
  198. }
  199. }
  200. if (tracing) {
  201. if (subject != null)
  202. logger.trace("newClient","subject is not null");
  203. else logger.trace("newClient","no subject");
  204. }
  205. final String connectionId = makeConnectionId(getProtocol(), subject);
  206. if (tracing)
  207. logger.trace("newClient","making new connection: " + connectionId);
  208. RMIConnection client = makeClient(connectionId, subject);
  209. connServer.connectionOpened(connectionId, "Connection opened", null);
  210. dropDeadReferences();
  211. WeakReference wr = new WeakReference(client);
  212. synchronized (clientList) {
  213. clientList.add(wr);
  214. }
  215. if (tracing)
  216. logger.trace("newClient","new connection done: " + connectionId );
  217. return client;
  218. }
  219. /**
  220. * <p>Creates a new client connection. This method is called by
  221. * the public method {@link #newClient(Object)}.</p>
  222. *
  223. * @param connectionId the ID of the new connection. Every
  224. * connection opened by this connector server will have a
  225. * different ID. The behavior is unspecified if this parameter is
  226. * null.
  227. *
  228. * @param subject the authenticated subject. Can be null.
  229. *
  230. * @return the newly-created <code>RMIConnection</code>.
  231. *
  232. * @exception IOException if the new client object cannot be
  233. * created or exported.
  234. */
  235. protected abstract RMIConnection makeClient(String connectionId,
  236. Subject subject)
  237. throws IOException;
  238. /**
  239. * <p>Closes a client connection made by {@link #makeClient makeClient}.
  240. *
  241. * @param client a connection previously returned by
  242. * <code>makeClient</code> on which the <code>closeClient</code>
  243. * method has not previously been called. The behavior is
  244. * unspecified if these conditions are violated, including the
  245. * case where <code>client</code> is null.
  246. *
  247. * @exception IOException if the client connection cannot be
  248. * closed.
  249. */
  250. protected abstract void closeClient(RMIConnection client)
  251. throws IOException;
  252. /**
  253. * <p>Returns the protocol string for this object. The string is
  254. * <code>rmi</code> for RMI/JRMP and <code>iiop</code> for RMI/IIOP.
  255. *
  256. * @return the protocol string for this object.
  257. */
  258. protected abstract String getProtocol();
  259. /**
  260. * <p>Method called when a client connection created by {@link
  261. * #makeClient makeClient} is closed. A subclass that defines
  262. * <code>makeClient</code> must arrange for this method to be
  263. * called when the resultant object's {@link RMIConnection#close()
  264. * close} method is called. This enables it to be removed from
  265. * the <code>RMIServerImpl</code>'s list of connections. It is
  266. * not an error for <code>client</code> not to be in that
  267. * list.</p>
  268. *
  269. * <p>After removing <code>client</code> from the list of
  270. * connections, this method calls {@link #closeClient
  271. * closeClient(client)}.</p>
  272. *
  273. * @param client the client connection that has been closed.
  274. *
  275. * @exception IOException if {@link #closeClient} throws this
  276. * exception.
  277. *
  278. * @exception NullPointerException if <code>client</code> is null.
  279. */
  280. protected void clientClosed(RMIConnection client) throws IOException {
  281. final boolean debug = logger.debugOn();
  282. if (debug) logger.trace("clientClosed","client="+client);
  283. if (client == null)
  284. throw new NullPointerException("Null client");
  285. synchronized (clientList) {
  286. dropDeadReferences();
  287. for (Iterator it = clientList.iterator(); it.hasNext(); ) {
  288. WeakReference wr = (WeakReference) it.next();
  289. if (wr.get() == client) {
  290. it.remove();
  291. break;
  292. }
  293. }
  294. /* It is not a bug for this loop not to find the client. In
  295. our close() method, we remove a client from the list before
  296. calling its close() method. */
  297. }
  298. if (debug) logger.trace("clientClosed", "closing client.");
  299. closeClient(client);
  300. if (debug) logger.trace("clientClosed", "sending notif");
  301. connServer.connectionClosed(client.getConnectionId(),
  302. "Client connection closed", null);
  303. if (debug) logger.trace("clientClosed","done");
  304. }
  305. /**
  306. * <p>Closes this connection server. This method first calls the
  307. * {@link #closeServer()} method so that no new client connections
  308. * will be accepted. Then, for each remaining {@link
  309. * RMIConnection} object returned by {@link #makeClient
  310. * makeClient}, its {@link RMIConnection#close() close} method is
  311. * called.</p>
  312. *
  313. * <p>The behaviour when this method is called more than once is
  314. * unspecified.</p>
  315. *
  316. * <p>If {@link #closeServer()} throws an
  317. * <code>IOException</code>, the individual connections are
  318. * nevertheless closed, and then the <code>IOException</code> is
  319. * thrown from this method.</p>
  320. *
  321. * <p>If {@link #closeServer()} returns normally but one or more
  322. * of the individual connections throws an
  323. * <code>IOException</code>, then, after closing all the
  324. * connections, one of those <code>IOException</code>s is thrown
  325. * from this method. If more than one connection throws an
  326. * <code>IOException</code>, it is unspecified which one is thrown
  327. * from this method.</p>
  328. *
  329. * @exception IOException if {@link #closeServer()} or one of the
  330. * {@link RMIConnection#close()} calls threw
  331. * <code>IOException</code>.
  332. */
  333. public synchronized void close() throws IOException {
  334. final boolean tracing = logger.traceOn();
  335. final boolean debug = logger.debugOn();
  336. if (tracing) logger.trace("close","closing");
  337. IOException ioException = null;
  338. try {
  339. if (debug) logger.debug("close","closing Server");
  340. closeServer();
  341. } catch (IOException e) {
  342. if (tracing) logger.trace("close","Failed to close server: " + e);
  343. if (debug) logger.debug("close",e);
  344. ioException = e;
  345. }
  346. if (debug) logger.debug("close","closing Clients");
  347. // Loop to close all clients
  348. while (true) {
  349. synchronized (clientList) {
  350. if (debug) logger.debug("close","droping dead references");
  351. dropDeadReferences();
  352. if (debug) logger.debug("close","client count: "+clientList.size());
  353. if (clientList.size() == 0)
  354. break;
  355. /* Loop until we find a non-null client. Because we called
  356. dropDeadReferences(), this will usually be the first
  357. element of the list, but a garbage collection could have
  358. happened in between. */
  359. for (Iterator it = clientList.iterator(); it.hasNext(); ) {
  360. WeakReference wr = (WeakReference) it.next();
  361. RMIConnection client = (RMIConnection) wr.get();
  362. it.remove();
  363. if (client != null) {
  364. try {
  365. client.close();
  366. } catch (IOException e) {
  367. if (tracing)
  368. logger.trace("close","Failed to close client: " + e);
  369. if (debug) logger.debug("close",e);
  370. if (ioException == null)
  371. ioException = e;
  372. }
  373. break;
  374. }
  375. }
  376. }
  377. }
  378. if(notifBuffer != null)
  379. notifBuffer.dispose();
  380. if (ioException != null) {
  381. if (tracing) logger.trace("close","close failed.");
  382. throw ioException;
  383. }
  384. if (tracing) logger.trace("close","closed.");
  385. }
  386. /**
  387. * <p>Called by {@link #close()} to close the connector server.
  388. * After returning from this method, the connector server must
  389. * not accept any new connections.</p>
  390. *
  391. * @exception IOException if the attempt to close the connector
  392. * server failed.
  393. */
  394. protected abstract void closeServer() throws IOException;
  395. private static synchronized String makeConnectionId(String protocol,
  396. Subject subject) {
  397. connectionIdNumber++;
  398. String clientHost = "";
  399. try {
  400. clientHost = RemoteServer.getClientHost();
  401. } catch (ServerNotActiveException e) {
  402. logger.trace("makeConnectionId", "getClientHost", e);
  403. }
  404. StringBuffer buf = new StringBuffer();
  405. buf.append(protocol).append(":");
  406. if (clientHost.length() > 0)
  407. buf.append("//").append(clientHost);
  408. buf.append(" ");
  409. if (subject != null) {
  410. Set principals = subject.getPrincipals();
  411. String sep = "";
  412. for (Iterator it = principals.iterator(); it.hasNext(); ) {
  413. Principal p = (Principal) it.next();
  414. String name = p.getName().replace(' ', '_').replace(';', ':');
  415. buf.append(sep).append(name);
  416. sep = ";";
  417. }
  418. }
  419. buf.append(" ").append(connectionIdNumber);
  420. if (logger.traceOn())
  421. logger.trace("newConnectionId","connectionId="+buf);
  422. return buf.toString();
  423. }
  424. private void dropDeadReferences() {
  425. synchronized (clientList) {
  426. for (Iterator it = clientList.iterator(); it.hasNext(); ) {
  427. WeakReference wr = (WeakReference) it.next();
  428. if (wr.get() == null)
  429. it.remove();
  430. }
  431. }
  432. }
  433. synchronized NotificationBuffer getNotifBuffer() {
  434. //Notification buffer is lazily created when the first client connects
  435. if(notifBuffer == null)
  436. notifBuffer =
  437. ArrayNotificationBuffer.getNotificationBuffer(mbeanServer,
  438. env);
  439. return notifBuffer;
  440. }
  441. private static final ClassLogger logger =
  442. new ClassLogger("javax.management.remote.rmi", "RMIServerImpl");
  443. /** List of WeakReference values. Each one references an
  444. RMIConnection created by this object, or null if the
  445. RMIConnection has been garbage-collected. */
  446. private final List clientList = new ArrayList();
  447. private ClassLoader cl;
  448. private MBeanServer mbeanServer;
  449. private final Map env;
  450. private RMIConnectorServer connServer;
  451. private static int connectionIdNumber;
  452. private NotificationBuffer notifBuffer;
  453. }