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