1 /* 2 * Copyright (c) 2002, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.management.remote.rmi; 27 28 29 import com.sun.jmx.remote.security.MBeanServerFileAccessController; 30 import com.sun.jmx.remote.util.ClassLogger; 31 import com.sun.jmx.remote.util.EnvHelp; 32 33 import java.io.ByteArrayOutputStream; 34 import java.io.IOException; 35 import java.io.ObjectInputFilter; 36 import java.io.ObjectOutputStream; 37 import java.net.MalformedURLException; 38 import java.rmi.server.RMIClientSocketFactory; 39 import java.rmi.server.RMIServerSocketFactory; 40 import java.util.Collections; 41 import java.util.HashMap; 42 import java.util.HashSet; 43 import java.util.Hashtable; 44 import java.util.Map; 45 import java.util.Set; 46 47 import javax.management.InstanceNotFoundException; 48 import javax.management.MBeanServer; 49 import javax.management.remote.JMXAuthenticator; 50 51 import javax.management.remote.JMXConnectionNotification; 52 import javax.management.remote.JMXConnector; 53 import javax.management.remote.JMXConnectorServer; 54 import javax.management.remote.JMXServiceURL; 55 import javax.management.remote.MBeanServerForwarder; 56 57 import javax.naming.InitialContext; 58 import javax.naming.NamingException; 59 60 /** 61 * <p>A JMX API connector server that creates RMI-based connections 62 * from remote clients. Usually, such connector servers are made 63 * using {@link javax.management.remote.JMXConnectorServerFactory 64 * JMXConnectorServerFactory}. However, specialized applications can 65 * use this class directly, for example with an {@link RMIServerImpl} 66 * object.</p> 67 * 68 * @since 1.5 69 */ 70 public class RMIConnectorServer extends JMXConnectorServer { 71 /** 72 * <p>Name of the attribute that specifies whether the {@link 73 * RMIServer} stub that represents an RMI connector server should 74 * override an existing stub at the same address. The value 75 * associated with this attribute, if any, should be a string that 76 * is equal, ignoring case, to <code>"true"</code> or 77 * <code>"false"</code>. The default value is false.</p> 78 */ 79 public static final String JNDI_REBIND_ATTRIBUTE = 80 "jmx.remote.jndi.rebind"; 81 82 /** 83 * <p>Name of the attribute that specifies the {@link 84 * RMIClientSocketFactory} for the RMI objects created in 85 * conjunction with this connector. The value associated with this 86 * attribute must be of type <code>RMIClientSocketFactory</code> and can 87 * only be specified in the <code>Map</code> argument supplied when 88 * creating a connector server.</p> 89 */ 90 public static final String RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE = 91 "jmx.remote.rmi.client.socket.factory"; 92 93 /** 94 * <p>Name of the attribute that specifies the {@link 95 * RMIServerSocketFactory} for the RMI objects created in 96 * conjunction with this connector. The value associated with this 97 * attribute must be of type <code>RMIServerSocketFactory</code> and can 98 * only be specified in the <code>Map</code> argument supplied when 99 * creating a connector server.</p> 100 */ 101 public static final String RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE = 102 "jmx.remote.rmi.server.socket.factory"; 103 104 /** 105 * Name of the attribute that specifies a list of class names acceptable 106 * as parameters to the {@link RMIServer#newClient(java.lang.Object) RMIServer.newClient()} 107 * remote method call. 108 * <p> 109 * This list of classes should correspond to the transitive closure of the 110 * credentials class (or classes) used by the installed {@linkplain JMXAuthenticator} 111 * associated with the {@linkplain RMIServer} implementation. 112 * <p> 113 * If the attribute is not set, or is null, then any class is 114 * deemed acceptable. 115 * 116 * @deprecated Use {@link #CREDENTIALS_FILTER_PATTERN} with a 117 * {@linkplain java.io.ObjectInputFilter.Config#createFilter 118 * filter pattern} string instead. 119 */ 120 @Deprecated(since="10", forRemoval=true) 121 public static final String CREDENTIAL_TYPES = 122 "jmx.remote.rmi.server.credential.types"; 123 124 /** 125 * Name of the attribute that specifies an 126 * {@link ObjectInputFilter} pattern string to filter classes acceptable 127 * for {@link RMIServer#newClient(java.lang.Object) RMIServer.newClient()} 128 * remote method call. 129 * <p> 130 * The filter pattern must be in same format as used in 131 * {@link java.io.ObjectInputFilter.Config#createFilter} 132 * <p> 133 * This list of classes allowed by filter should correspond to the 134 * transitive closure of the credentials class (or classes) used by the 135 * installed {@linkplain JMXAuthenticator} associated with the 136 * {@linkplain RMIServer} implementation. 137 * If the attribute is not set then any class is deemed acceptable. 138 * @see ObjectInputFilter 139 */ 140 public static final String CREDENTIALS_FILTER_PATTERN = 141 "jmx.remote.rmi.server.credentials.filter.pattern"; 142 143 /** 144 * This attribute defines a pattern from which to create a 145 * {@link java.io.ObjectInputFilter} that will be used when deserializing 146 * objects sent to the {@code JMXConnectorServer} by any client. 147 * <p> 148 * The filter will be called for any class found in the serialized 149 * stream sent to server by client, including all JMX defined classes 150 * (such as {@link javax.management.ObjectName}), all method parameters, 151 * and, if present in the stream, all classes transitively referred by 152 * the serial form of any deserialized object. 153 * The pattern must be in same format as used in 154 * {@link java.io.ObjectInputFilter.Config#createFilter}. 155 * It may define a white list of permitted classes, a black list of 156 * rejected classes, a maximum depth for the deserialized objects, 157 * etc. 158 * <p> 159 * To be functional, the filter should allow at least all the 160 * concrete types in the transitive closure of all objects that 161 * might get serialized when serializing all JMX classes referred 162 * as parameters in the {@link 163 * javax.management.remote.rmi.RMIConnection} interface, 164 * plus all classes that a {@link javax.management.remote.rmi.RMIConnector client} 165 * might need to transmit wrapped in {@linkplain java.rmi.MarshalledObject 166 * marshalled objects} in order to interoperate with the MBeans registered 167 * in the {@code MBeanServer}. That would potentially include all the 168 * concrete {@linkplain javax.management.openmbean JMX OpenTypes} and the 169 * classes they use in their serial form. 170 * <p> 171 * Care must be taken when defining such a filter, as defining 172 * a white list too restrictive or a too wide a black list may 173 * prevent legitimate clients from interoperating with the 174 * {@code JMXConnectorServer}. 175 */ 176 public static final String SERIAL_FILTER_PATTERN = 177 "jmx.remote.rmi.server.serial.filter.pattern"; 178 179 /** 180 * <p>Makes an <code>RMIConnectorServer</code>. 181 * This is equivalent to calling {@link #RMIConnectorServer( 182 * JMXServiceURL,Map,RMIServerImpl,MBeanServer) 183 * RMIConnectorServer(directoryURL,environment,null,null)}</p> 184 * 185 * @param url the URL defining how to create the connector server. 186 * Cannot be null. 187 * 188 * @param environment attributes governing the creation and 189 * storing of the RMI object. Can be null, which is equivalent to 190 * an empty Map. 191 * 192 * @exception IllegalArgumentException if <code>url</code> is null. 193 * 194 * @exception MalformedURLException if <code>url</code> does not 195 * conform to the syntax for an RMI connector, or if its protocol 196 * is not recognized by this implementation. Only "rmi" is valid when 197 * this constructor is used. 198 * 199 * @exception IOException if the connector server cannot be created 200 * for some reason or if it is inevitable that its {@link #start() 201 * start} method will fail. 202 */ 203 public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment) 204 throws IOException { 205 this(url, environment, (MBeanServer) null); 206 } 207 208 /** 209 * <p>Makes an <code>RMIConnectorServer</code> for the given MBean 210 * server. 211 * This is equivalent to calling {@link #RMIConnectorServer( 212 * JMXServiceURL,Map,RMIServerImpl,MBeanServer) 213 * RMIConnectorServer(directoryURL,environment,null,mbeanServer)}</p> 214 * 215 * @param url the URL defining how to create the connector server. 216 * Cannot be null. 217 * 218 * @param environment attributes governing the creation and 219 * storing of the RMI object. Can be null, which is equivalent to 220 * an empty Map. 221 * 222 * @param mbeanServer the MBean server to which the new connector 223 * server is attached, or null if it will be attached by being 224 * registered as an MBean in the MBean server. 225 * 226 * @exception IllegalArgumentException if <code>url</code> is null. 227 * 228 * @exception MalformedURLException if <code>url</code> does not 229 * conform to the syntax for an RMI connector, or if its protocol 230 * is not recognized by this implementation. Only "rmi" is valid 231 * when this constructor is used. 232 * 233 * @exception IOException if the connector server cannot be created 234 * for some reason or if it is inevitable that its {@link #start() 235 * start} method will fail. 236 */ 237 public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment, 238 MBeanServer mbeanServer) 239 throws IOException { 240 this(url, environment, (RMIServerImpl) null, mbeanServer); 241 } 242 243 /** 244 * <p>Makes an <code>RMIConnectorServer</code> for the given MBean 245 * server.</p> 246 * 247 * @param url the URL defining how to create the connector server. 248 * Cannot be null. 249 * 250 * @param environment attributes governing the creation and 251 * storing of the RMI object. Can be null, which is equivalent to 252 * an empty Map. 253 * 254 * @param rmiServerImpl An implementation of the RMIServer interface, 255 * consistent with the protocol type specified in <var>url</var>. 256 * If this parameter is non null, the protocol type specified by 257 * <var>url</var> is not constrained, and is assumed to be valid. 258 * Otherwise, only "rmi" will be recognized. 259 * 260 * @param mbeanServer the MBean server to which the new connector 261 * server is attached, or null if it will be attached by being 262 * registered as an MBean in the MBean server. 263 * 264 * @exception IllegalArgumentException if <code>url</code> is null. 265 * 266 * @exception MalformedURLException if <code>url</code> does not 267 * conform to the syntax for an RMI connector, or if its protocol 268 * is not recognized by this implementation. Only "rmi" is recognized 269 * when <var>rmiServerImpl</var> is null. 270 * 271 * @exception IOException if the connector server cannot be created 272 * for some reason or if it is inevitable that its {@link #start() 273 * start} method will fail. 274 * 275 * @see #start 276 */ 277 public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment, 278 RMIServerImpl rmiServerImpl, 279 MBeanServer mbeanServer) 280 throws IOException { 281 super(mbeanServer); 282 283 if (url == null) throw new 284 IllegalArgumentException("Null JMXServiceURL"); 285 if (rmiServerImpl == null) { 286 final String prt = url.getProtocol(); 287 if (prt == null || !(prt.equals("rmi"))) { 288 final String msg = "Invalid protocol type: " + prt; 289 throw new MalformedURLException(msg); 290 } 291 final String urlPath = url.getURLPath(); 292 if (!urlPath.isEmpty() 293 && !urlPath.equals("/") 294 && !urlPath.startsWith("/jndi/")) { 295 final String msg = "URL path must be empty or start with " + 296 "/jndi/"; 297 throw new MalformedURLException(msg); 298 } 299 } 300 301 if (environment == null) 302 this.attributes = Collections.emptyMap(); 303 else { 304 EnvHelp.checkAttributes(environment); 305 this.attributes = Collections.unmodifiableMap(environment); 306 } 307 308 this.address = url; 309 this.rmiServerImpl = rmiServerImpl; 310 } 311 312 /** 313 * <p>Returns a client stub for this connector server. A client 314 * stub is a serializable object whose {@link 315 * JMXConnector#connect(Map) connect} method can be used to make 316 * one new connection to this connector server.</p> 317 * 318 * @param env client connection parameters of the same sort that 319 * could be provided to {@link JMXConnector#connect(Map) 320 * JMXConnector.connect(Map)}. Can be null, which is equivalent 321 * to an empty map. 322 * 323 * @return a client stub that can be used to make a new connection 324 * to this connector server. 325 * 326 * @exception UnsupportedOperationException if this connector 327 * server does not support the generation of client stubs. 328 * 329 * @exception IllegalStateException if the JMXConnectorServer is 330 * not started (see {@link #isActive()}). 331 * 332 * @exception IOException if a communications problem means that a 333 * stub cannot be created. 334 **/ 335 public JMXConnector toJMXConnector(Map<String,?> env) throws IOException { 336 // The serialized for of rmiServerImpl is automatically 337 // a RMI server stub. 338 if (!isActive()) throw new 339 IllegalStateException("Connector is not active"); 340 341 // Merge maps 342 Map<String, Object> usemap = new HashMap<String, Object>( 343 (this.attributes==null)?Collections.<String, Object>emptyMap(): 344 this.attributes); 345 346 if (env != null) { 347 EnvHelp.checkAttributes(env); 348 usemap.putAll(env); 349 } 350 351 usemap = EnvHelp.filterAttributes(usemap); 352 353 final RMIServer stub=(RMIServer)rmiServerImpl.toStub(); 354 355 return new RMIConnector(stub, usemap); 356 } 357 358 /** 359 * <p>Activates the connector server, that is starts listening for 360 * client connections. Calling this method when the connector 361 * server is already active has no effect. Calling this method 362 * when the connector server has been stopped will generate an 363 * <code>IOException</code>.</p> 364 * 365 * <p>The behavior of this method when called for the first time 366 * depends on the parameters that were supplied at construction, 367 * as described below.</p> 368 * 369 * <p>First, an object of a subclass of {@link RMIServerImpl} is 370 * required, to export the connector server through RMI:</p> 371 * 372 * <ul> 373 * 374 * <li>If an <code>RMIServerImpl</code> was supplied to the 375 * constructor, it is used. 376 * 377 * <li>Otherwise, if the <code>JMXServiceURL</code> 378 * was null, or its protocol part was <code>rmi</code>, an object 379 * of type {@link RMIJRMPServerImpl} is created. 380 * 381 * <li>Otherwise, the implementation can create an 382 * implementation-specific {@link RMIServerImpl} or it can throw 383 * {@link MalformedURLException}. 384 * 385 * </ul> 386 * 387 * <p>If the given address includes a JNDI directory URL as 388 * specified in the package documentation for {@link 389 * javax.management.remote.rmi}, then this 390 * <code>RMIConnectorServer</code> will bootstrap by binding the 391 * <code>RMIServerImpl</code> to the given address.</p> 392 * 393 * <p>If the URL path part of the <code>JMXServiceURL</code> was 394 * empty or a single slash (<code>/</code>), then the RMI object 395 * will not be bound to a directory. Instead, a reference to it 396 * will be encoded in the URL path of the RMIConnectorServer 397 * address (returned by {@link #getAddress()}). The encodings for 398 * <code>rmi</code> are described in the package documentation for 399 * {@link javax.management.remote.rmi}.</p> 400 * 401 * <p>The behavior when the URL path is neither empty nor a JNDI 402 * directory URL, or when the protocol is not <code>rmi</code>, 403 * is implementation defined, and may include throwing 404 * {@link MalformedURLException} when the connector server is created 405 * or when it is started.</p> 406 * 407 * @exception IllegalStateException if the connector server has 408 * not been attached to an MBean server. 409 * @exception IOException if the connector server cannot be 410 * started. 411 */ 412 public synchronized void start() throws IOException { 413 final boolean tracing = logger.traceOn(); 414 415 if (state == STARTED) { 416 if (tracing) logger.trace("start", "already started"); 417 return; 418 } else if (state == STOPPED) { 419 if (tracing) logger.trace("start", "already stopped"); 420 throw new IOException("The server has been stopped."); 421 } 422 423 if (getMBeanServer() == null) 424 throw new IllegalStateException("This connector server is not " + 425 "attached to an MBean server"); 426 427 // Check the internal access file property to see 428 // if an MBeanServerForwarder is to be provided 429 // 430 if (attributes != null) { 431 // Check if access file property is specified 432 // 433 String accessFile = 434 (String) attributes.get("jmx.remote.x.access.file"); 435 if (accessFile != null) { 436 // Access file property specified, create an instance 437 // of the MBeanServerFileAccessController class 438 // 439 MBeanServerForwarder mbsf; 440 try { 441 mbsf = new MBeanServerFileAccessController(accessFile); 442 } catch (IOException e) { 443 throw EnvHelp.initCause( 444 new IllegalArgumentException(e.getMessage()), e); 445 } 446 // Set the MBeanServerForwarder 447 // 448 setMBeanServerForwarder(mbsf); 449 } 450 } 451 452 try { 453 if (tracing) logger.trace("start", "setting default class loader"); 454 defaultClassLoader = EnvHelp.resolveServerClassLoader( 455 attributes, getMBeanServer()); 456 } catch (InstanceNotFoundException infc) { 457 IllegalArgumentException x = new 458 IllegalArgumentException("ClassLoader not found: "+infc); 459 throw EnvHelp.initCause(x,infc); 460 } 461 462 if (tracing) logger.trace("start", "setting RMIServer object"); 463 final RMIServerImpl rmiServer; 464 465 if (rmiServerImpl != null) 466 rmiServer = rmiServerImpl; 467 else 468 rmiServer = newServer(); 469 470 rmiServer.setMBeanServer(getMBeanServer()); 471 rmiServer.setDefaultClassLoader(defaultClassLoader); 472 rmiServer.setRMIConnectorServer(this); 473 rmiServer.export(); 474 475 try { 476 if (tracing) logger.trace("start", "getting RMIServer object to export"); 477 final RMIServer objref = objectToBind(rmiServer, attributes); 478 479 if (address != null && address.getURLPath().startsWith("/jndi/")) { 480 final String jndiUrl = address.getURLPath().substring(6); 481 482 if (tracing) 483 logger.trace("start", "Using external directory: " + jndiUrl); 484 485 String stringBoolean = (String) attributes.get(JNDI_REBIND_ATTRIBUTE); 486 final boolean rebind = EnvHelp.computeBooleanFromString( stringBoolean ); 487 488 if (tracing) 489 logger.trace("start", JNDI_REBIND_ATTRIBUTE + "=" + rebind); 490 491 try { 492 if (tracing) logger.trace("start", "binding to " + jndiUrl); 493 494 final Hashtable<?, ?> usemap = EnvHelp.mapToHashtable(attributes); 495 496 bind(jndiUrl, usemap, objref, rebind); 497 498 boundJndiUrl = jndiUrl; 499 } catch (NamingException e) { 500 // fit e in the nested exception if we are on 1.4 501 throw newIOException("Cannot bind to URL ["+jndiUrl+"]: " 502 + e, e); 503 } 504 } else { 505 // if jndiURL is null, we must encode the stub into the URL. 506 if (tracing) logger.trace("start", "Encoding URL"); 507 508 encodeStubInAddress(objref, attributes); 509 510 if (tracing) logger.trace("start", "Encoded URL: " + this.address); 511 } 512 } catch (Exception e) { 513 try { 514 rmiServer.close(); 515 } catch (Exception x) { 516 // OK: we are already throwing another exception 517 } 518 if (e instanceof RuntimeException) 519 throw (RuntimeException) e; 520 else if (e instanceof IOException) 521 throw (IOException) e; 522 else 523 throw newIOException("Got unexpected exception while " + 524 "starting the connector server: " 525 + e, e); 526 } 527 528 rmiServerImpl = rmiServer; 529 530 synchronized(openedServers) { 531 openedServers.add(this); 532 } 533 534 state = STARTED; 535 536 if (tracing) { 537 logger.trace("start", "Connector Server Address = " + address); 538 logger.trace("start", "started."); 539 } 540 } 541 542 /** 543 * <p>Deactivates the connector server, that is, stops listening for 544 * client connections. Calling this method will also close all 545 * client connections that were made by this server. After this 546 * method returns, whether normally or with an exception, the 547 * connector server will not create any new client 548 * connections.</p> 549 * 550 * <p>Once a connector server has been stopped, it cannot be started 551 * again.</p> 552 * 553 * <p>Calling this method when the connector server has already 554 * been stopped has no effect. Calling this method when the 555 * connector server has not yet been started will disable the 556 * connector server object permanently.</p> 557 * 558 * <p>If closing a client connection produces an exception, that 559 * exception is not thrown from this method. A {@link 560 * JMXConnectionNotification} is emitted from this MBean with the 561 * connection ID of the connection that could not be closed.</p> 562 * 563 * <p>Closing a connector server is a potentially slow operation. 564 * For example, if a client machine with an open connection has 565 * crashed, the close operation might have to wait for a network 566 * protocol timeout. Callers that do not want to block in a close 567 * operation should do it in a separate thread.</p> 568 * 569 * <p>This method calls the method {@link RMIServerImpl#close() 570 * close} on the connector server's <code>RMIServerImpl</code> 571 * object.</p> 572 * 573 * <p>If the <code>RMIServerImpl</code> was bound to a JNDI 574 * directory by the {@link #start() start} method, it is unbound 575 * from the directory by this method.</p> 576 * 577 * @exception IOException if the server cannot be closed cleanly, 578 * or if the <code>RMIServerImpl</code> cannot be unbound from the 579 * directory. When this exception is thrown, the server has 580 * already attempted to close all client connections, if 581 * appropriate; to call {@link RMIServerImpl#close()}; and to 582 * unbind the <code>RMIServerImpl</code> from its directory, if 583 * appropriate. All client connections are closed except possibly 584 * those that generated exceptions when the server attempted to 585 * close them. 586 */ 587 public void stop() throws IOException { 588 final boolean tracing = logger.traceOn(); 589 590 synchronized (this) { 591 if (state == STOPPED) { 592 if (tracing) logger.trace("stop","already stopped."); 593 return; 594 } else if (state == CREATED) { 595 if (tracing) logger.trace("stop","not started yet."); 596 } 597 598 if (tracing) logger.trace("stop", "stopping."); 599 state = STOPPED; 600 } 601 602 synchronized(openedServers) { 603 openedServers.remove(this); 604 } 605 606 IOException exception = null; 607 608 // rmiServerImpl can be null if stop() called without start() 609 if (rmiServerImpl != null) { 610 try { 611 if (tracing) logger.trace("stop", "closing RMI server."); 612 rmiServerImpl.close(); 613 } catch (IOException e) { 614 if (tracing) logger.trace("stop", "failed to close RMI server: " + e); 615 if (logger.debugOn()) logger.debug("stop",e); 616 exception = e; 617 } 618 } 619 620 if (boundJndiUrl != null) { 621 try { 622 if (tracing) 623 logger.trace("stop", 624 "unbind from external directory: " + boundJndiUrl); 625 626 final Hashtable<?, ?> usemap = EnvHelp.mapToHashtable(attributes); 627 628 InitialContext ctx = 629 new InitialContext(usemap); 630 631 ctx.unbind(boundJndiUrl); 632 633 ctx.close(); 634 } catch (NamingException e) { 635 if (tracing) logger.trace("stop", "failed to unbind RMI server: "+e); 636 if (logger.debugOn()) logger.debug("stop",e); 637 // fit e in as the nested exception if we are on 1.4 638 if (exception == null) 639 exception = newIOException("Cannot bind to URL: " + e, e); 640 } 641 } 642 643 if (exception != null) throw exception; 644 645 if (tracing) logger.trace("stop", "stopped"); 646 } 647 648 public synchronized boolean isActive() { 649 return (state == STARTED); 650 } 651 652 public JMXServiceURL getAddress() { 653 if (!isActive()) 654 return null; 655 return address; 656 } 657 658 public Map<String,?> getAttributes() { 659 Map<String, ?> map = EnvHelp.filterAttributes(attributes); 660 return Collections.unmodifiableMap(map); 661 } 662 663 @Override 664 public synchronized 665 void setMBeanServerForwarder(MBeanServerForwarder mbsf) { 666 super.setMBeanServerForwarder(mbsf); 667 if (rmiServerImpl != null) 668 rmiServerImpl.setMBeanServer(getMBeanServer()); 669 } 670 671 /* We repeat the definitions of connection{Opened,Closed,Failed} 672 here so that they are accessible to other classes in this package 673 even though they have protected access. */ 674 675 @Override 676 protected void connectionOpened(String connectionId, String message, 677 Object userData) { 678 super.connectionOpened(connectionId, message, userData); 679 } 680 681 @Override 682 protected void connectionClosed(String connectionId, String message, 683 Object userData) { 684 super.connectionClosed(connectionId, message, userData); 685 } 686 687 @Override 688 protected void connectionFailed(String connectionId, String message, 689 Object userData) { 690 super.connectionFailed(connectionId, message, userData); 691 } 692 693 /** 694 * Bind a stub to a registry. 695 * @param jndiUrl URL of the stub in the registry, extracted 696 * from the <code>JMXServiceURL</code>. 697 * @param attributes A Hashtable containing environment parameters, 698 * built from the Map specified at this object creation. 699 * @param rmiServer The object to bind in the registry 700 * @param rebind true if the object must be rebound. 701 **/ 702 void bind(String jndiUrl, Hashtable<?, ?> attributes, 703 RMIServer rmiServer, boolean rebind) 704 throws NamingException, MalformedURLException { 705 // if jndiURL is not null, we nust bind the stub to a 706 // directory. 707 InitialContext ctx = 708 new InitialContext(attributes); 709 710 if (rebind) 711 ctx.rebind(jndiUrl, rmiServer); 712 else 713 ctx.bind(jndiUrl, rmiServer); 714 ctx.close(); 715 } 716 717 /** 718 * Creates a new RMIServerImpl. 719 **/ 720 RMIServerImpl newServer() throws IOException { 721 final int port; 722 if (address == null) 723 port = 0; 724 else 725 port = address.getPort(); 726 727 return newJRMPServer(attributes, port); 728 } 729 730 /** 731 * Encode a stub into the JMXServiceURL. 732 * @param rmiServer The stub object to encode in the URL 733 * @param attributes A Map containing environment parameters, 734 * built from the Map specified at this object creation. 735 **/ 736 private void encodeStubInAddress( 737 RMIServer rmiServer, Map<String, ?> attributes) 738 throws IOException { 739 740 final String protocol, host; 741 final int port; 742 743 if (address == null) { 744 protocol = "rmi"; 745 host = null; // will default to local host name 746 port = 0; 747 } else { 748 protocol = address.getProtocol(); 749 host = (address.getHost().isEmpty()) ? null : address.getHost(); 750 port = address.getPort(); 751 } 752 753 final String urlPath = encodeStub(rmiServer, attributes); 754 755 address = new JMXServiceURL(protocol, host, port, urlPath); 756 } 757 758 /** 759 * Returns the IOR of the given rmiServer. 760 **/ 761 static String encodeStub( 762 RMIServer rmiServer, Map<String, ?> env) throws IOException { 763 return "/stub/" + encodeJRMPStub(rmiServer, env); 764 } 765 766 static String encodeJRMPStub( 767 RMIServer rmiServer, Map<String, ?> env) 768 throws IOException { 769 ByteArrayOutputStream bout = new ByteArrayOutputStream(); 770 ObjectOutputStream oout = new ObjectOutputStream(bout); 771 oout.writeObject(rmiServer); 772 oout.close(); 773 byte[] bytes = bout.toByteArray(); 774 return byteArrayToBase64(bytes); 775 } 776 777 /** 778 * Object that we will bind to the registry. 779 * This object is a stub connected to our RMIServerImpl. 780 **/ 781 private static RMIServer objectToBind( 782 RMIServerImpl rmiServer, Map<String, ?> env) 783 throws IOException { 784 return (RMIServer)rmiServer.toStub(); 785 } 786 787 private static RMIServerImpl newJRMPServer(Map<String, ?> env, int port) 788 throws IOException { 789 RMIClientSocketFactory csf = (RMIClientSocketFactory) 790 env.get(RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE); 791 RMIServerSocketFactory ssf = (RMIServerSocketFactory) 792 env.get(RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE); 793 return new RMIJRMPServerImpl(port, csf, ssf, env); 794 } 795 796 private static String byteArrayToBase64(byte[] a) { 797 int aLen = a.length; 798 int numFullGroups = aLen/3; 799 int numBytesInPartialGroup = aLen - 3*numFullGroups; 800 int resultLen = 4*((aLen + 2)/3); 801 final StringBuilder result = new StringBuilder(resultLen); 802 803 // Translate all full groups from byte array elements to Base64 804 int inCursor = 0; 805 for (int i=0; i<numFullGroups; i++) { 806 int byte0 = a[inCursor++] & 0xff; 807 int byte1 = a[inCursor++] & 0xff; 808 int byte2 = a[inCursor++] & 0xff; 809 result.append(intToAlpha[byte0 >> 2]); 810 result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]); 811 result.append(intToAlpha[(byte1 << 2)&0x3f | (byte2 >> 6)]); 812 result.append(intToAlpha[byte2 & 0x3f]); 813 } 814 815 // Translate partial group if present 816 if (numBytesInPartialGroup != 0) { 817 int byte0 = a[inCursor++] & 0xff; 818 result.append(intToAlpha[byte0 >> 2]); 819 if (numBytesInPartialGroup == 1) { 820 result.append(intToAlpha[(byte0 << 4) & 0x3f]); 821 result.append("=="); 822 } else { 823 // assert numBytesInPartialGroup == 2; 824 int byte1 = a[inCursor++] & 0xff; 825 result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]); 826 result.append(intToAlpha[(byte1 << 2)&0x3f]); 827 result.append('='); 828 } 829 } 830 // assert inCursor == a.length; 831 // assert result.length() == resultLen; 832 return result.toString(); 833 } 834 835 /** 836 * This array is a lookup table that translates 6-bit positive integer 837 * index values into their "Base64 Alphabet" equivalents as specified 838 * in Table 1 of RFC 2045. 839 */ 840 private static final char intToAlpha[] = { 841 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 842 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 843 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 844 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 845 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' 846 }; 847 848 /** 849 * Construct a new IOException with a nested exception. 850 * The nested exception is set only if JDK {@literal >= 1.4} 851 */ 852 private static IOException newIOException(String message, 853 Throwable cause) { 854 final IOException x = new IOException(message); 855 return EnvHelp.initCause(x,cause); 856 } 857 858 859 // Private variables 860 // ----------------- 861 862 private static ClassLogger logger = 863 new ClassLogger("javax.management.remote.rmi", "RMIConnectorServer"); 864 865 private JMXServiceURL address; 866 private RMIServerImpl rmiServerImpl; 867 private final Map<String, ?> attributes; 868 private ClassLoader defaultClassLoader = null; 869 870 private String boundJndiUrl; 871 872 // state 873 private static final int CREATED = 0; 874 private static final int STARTED = 1; 875 private static final int STOPPED = 2; 876 877 private int state = CREATED; 878 private final static Set<RMIConnectorServer> openedServers = 879 new HashSet<RMIConnectorServer>(); 880 }