1 /*
2 * JBoss, Home of Professional Open Source.
3 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
4 * as indicated by the @author tags. See the copyright.txt file in the
5 * distribution for a full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.invocation.jrmp.server;
23
24 import java.lang.reflect.Method;
25 import java.net.InetAddress;
26 import java.net.UnknownHostException;
27 import java.io.Serializable;
28 import java.rmi.server.RemoteServer;
29 import java.rmi.server.UnicastRemoteObject;
30 import java.rmi.server.RMIServerSocketFactory;
31 import java.rmi.server.RMIClientSocketFactory;
32 import java.rmi.server.RemoteStub;
33 import java.rmi.MarshalledObject;
34 import java.security.PrivilegedAction;
35 import java.security.AccessController;
36 import java.security.PrivilegedExceptionAction;
37 import java.security.PrivilegedActionException;
38
39 import javax.management.ObjectName;
40 import javax.management.MBeanRegistration;
41 import javax.management.MBeanServer;
42 import javax.naming.Name;
43 import javax.naming.InitialContext;
44 import javax.naming.Context;
45 import javax.naming.NamingException;
46 import javax.naming.NameNotFoundException;
47 import javax.transaction.Transaction;
48
49 import org.jboss.invocation.jrmp.interfaces.JRMPInvokerProxy;
50 import org.jboss.invocation.Invocation;
51 import org.jboss.invocation.Invoker;
52 import org.jboss.invocation.MarshalledInvocation;
53 import org.jboss.invocation.MarshalledValueInputStream;
54 import org.jboss.logging.Logger;
55 import org.jboss.mx.util.JMXExceptionDecoder;
56 import org.jboss.net.sockets.DefaultSocketFactory;
57 import org.jboss.security.SecurityDomain;
58 import org.jboss.system.Registry;
59 import org.jboss.system.ServiceMBeanSupport;
60 import org.jboss.tm.TransactionPropagationContextUtil;
61
62 /**
63 * The JRMPInvoker is an RMI implementation that can generate Invocations
64 * from RMI/JRMP into the JMX base.
65 *
66 * @author <a href="mailto:marc.fleury@jboss.org>Marc Fleury</a>
67 * @author <a href="mailto:scott.stark@jboss.org>Scott Stark</a>
68 * @version $Revision: 60886 $
69 * @jmx.mbean extends="org.jboss.system.ServiceMBean"
70 */
71 public class JRMPInvoker
72 extends RemoteServer
73 implements Invoker, JRMPInvokerMBean, MBeanRegistration
74 {
75 /** @since 4.2.0 */
76 static final long serialVersionUID = 3110972460891691492L;
77
78 /**
79 * Identifer to instruct the usage of an anonymous port.
80 */
81 public static final int ANONYMOUS_PORT = 0;
82
83 /**
84 * Instance logger.
85 */
86 protected Logger log;
87
88 /**
89 * Service MBean support delegate.
90 */
91 protected ServiceMBeanSupport support;
92
93 /**
94 * The port the container will be exported on
95 */
96 protected int rmiPort = ANONYMOUS_PORT;
97
98 /**
99 * An optional custom client socket factory
100 */
101 protected RMIClientSocketFactory clientSocketFactory;
102
103 /**
104 * An optional custom server socket factory
105 */
106 protected RMIServerSocketFactory serverSocketFactory;
107
108 /**
109 * The class name of the optional custom client socket factory
110 */
111 protected String clientSocketFactoryName;
112
113 /**
114 * The class name of the optional custom server socket factory
115 */
116 protected String serverSocketFactoryName;
117
118 /**
119 * The address to bind the rmi port on
120 */
121 protected String serverAddress;
122 /**
123 * The name of the security domain to use with server sockets that support SSL
124 */
125 protected String sslDomain;
126
127 protected RemoteStub invokerStub;
128 /**
129 * The socket accept backlog
130 */
131 protected int backlog = 200;
132 /**
133 * A flag to enable caching of classes in the MarshalledValueInputStream
134 */
135 protected boolean enableClassCaching = false;
136 /**
137 * A priviledged actions for MBeanServer.invoke when running with sec mgr
138 */
139 private MBeanServerAction serverAction = new MBeanServerAction();
140
141 public JRMPInvoker()
142 {
143 final JRMPInvoker delegate = this;
144
145 // adapt the support delegate to invoke our state methods
146 support = new ServiceMBeanSupport(getClass())
147 {
148 protected void startService() throws Exception
149 {
150 delegate.startService();
151 }
152 protected void stopService() throws Exception
153 {
154 delegate.stopService();
155 }
156 protected void destroyService() throws Exception
157 {
158 delegate.destroyService();
159 }
160 };
161
162 // Setup logging from delegate
163 log = support.getLog();
164 }
165
166 /**
167 * @jmx.managed-attribute
168 */
169 public int getBacklog()
170 {
171 return backlog;
172 }
173
174 /**
175 * @jmx.managed-attribute
176 */
177 public void setBacklog(int back)
178 {
179 backlog = back;
180 }
181
182 /**
183 * @jmx.managed-attribute
184 */
185 public boolean getEnableClassCaching()
186 {
187 return enableClassCaching;
188 }
189
190 /**
191 * @jmx.managed-attribute
192 */
193 public void setEnableClassCaching(boolean flag)
194 {
195 enableClassCaching = flag;
196 MarshalledValueInputStream.useClassCache(enableClassCaching);
197 }
198
199 /**
200 * @return The localhost name or null.
201 */
202 public String getServerHostName()
203 {
204 try
205 {
206 return InetAddress.getLocalHost().getHostName();
207 }
208 catch (Exception ignored)
209 {
210 return null;
211 }
212 }
213
214 /**
215 * @jmx.managed-attribute
216 */
217 public void setRMIObjectPort(final int rmiPort)
218 {
219 this.rmiPort = rmiPort;
220 }
221
222 /**
223 * @jmx.managed-attribute
224 */
225 public int getRMIObjectPort()
226 {
227 return rmiPort;
228 }
229
230 /**
231 * @jmx.managed-attribute
232 */
233 public void setRMIClientSocketFactory(final String name)
234 {
235 clientSocketFactoryName = name;
236 }
237
238 /**
239 * @jmx.managed-attribute
240 */
241 public String getRMIClientSocketFactory()
242 {
243 return clientSocketFactoryName;
244 }
245
246 /**
247 * @jmx.managed-attribute
248 */
249 public void setRMIClientSocketFactoryBean(final RMIClientSocketFactory bean)
250 {
251 clientSocketFactory = bean;
252 }
253
254 /**
255 * @jmx.managed-attribute
256 */
257 public RMIClientSocketFactory getRMIClientSocketFactoryBean()
258 {
259 return clientSocketFactory;
260 }
261
262 /**
263 * @jmx.managed-attribute
264 */
265 public void setRMIServerSocketFactory(final String name)
266 {
267 serverSocketFactoryName = name;
268 }
269
270 /**
271 * @jmx.managed-attribute
272 */
273 public String getRMIServerSocketFactory()
274 {
275 return serverSocketFactoryName;
276 }
277
278 /**
279 * @jmx.managed-attribute
280 */
281 public void setRMIServerSocketFactoryBean(final RMIServerSocketFactory bean)
282 {
283 serverSocketFactory = bean;
284 }
285
286 /**
287 * @jmx.managed-attribute
288 */
289 public RMIServerSocketFactory getRMIServerSocketFactoryBean()
290 {
291 return serverSocketFactory;
292 }
293
294 /**
295 * @jmx.managed-attribute
296 */
297 public void setServerAddress(final String address)
298 {
299 serverAddress = address;
300 }
301
302 /**
303 * @jmx.managed-attribute
304 */
305 public String getServerAddress()
306 {
307 return serverAddress;
308 }
309
310 /**
311 * @jmx.managed-attribute
312 */
313 public void setSecurityDomain(String domainName)
314 {
315 this.sslDomain = domainName;
316 }
317
318 /**
319 * @jmx.managed-attribute
320 */
321 public String getSecurityDomain()
322 {
323 return sslDomain;
324 }
325
326 public Serializable getStub()
327 {
328 return this.invokerStub;
329 }
330
331 protected void startService() throws Exception
332 {
333 loadCustomSocketFactories();
334
335 log.debug("RMI Port='" +
336 (rmiPort == ANONYMOUS_PORT ? "Anonymous" :
337 Integer.toString(rmiPort)) + "'");
338
339 log.debug("Client SocketFactory='" +
340 (clientSocketFactory == null ? "Default" :
341 clientSocketFactory.toString()) + "'");
342
343 log.debug("Server SocketFactory='" +
344 (serverSocketFactory == null ? "Default" :
345 serverSocketFactory.toString()) + "'");
346
347 log.debug("Server SocketAddr='" +
348 (serverAddress == null ? "Default" :
349 serverAddress) + "'");
350 log.debug("SecurityDomain='" +
351 (sslDomain == null ? "Default" :
352 sslDomain) + "'");
353
354 InitialContext ctx = new InitialContext();
355
356 // Validate that there is a TransactionPropagationContextImporter
357 // bound in JNDI
358 TransactionPropagationContextUtil.getTPCImporter();
359
360 // Set the transaction manager and transaction propagation
361 // context factory of the GenericProxy class
362
363 Invoker delegateInvoker = createDelegateInvoker();
364
365 // Make the remote invoker proxy available for use by the proxy factory
366 Registry.bind(support.getServiceName(), delegateInvoker);
367
368 // Export CI
369 exportCI();
370
371 log.debug("Bound JRMP invoker for JMX node");
372
373 ctx.close();
374 }
375
376 protected void stopService() throws Exception
377 {
378 InitialContext ctx = new InitialContext();
379
380 try
381 {
382 unexportCI();
383 }
384 finally
385 {
386 ctx.close();
387 }
388 this.clientSocketFactory = null;
389 this.serverSocketFactory = null;
390 this.invokerStub = null;
391 }
392
393 protected void destroyService() throws Exception
394 {
395 // Export references to the bean
396 Registry.unbind(support.getServiceName());
397 }
398
399 /**
400 * Invoke a Remote interface method.
401 */
402 public Object invoke(Invocation invocation)
403 throws Exception
404 {
405 ClassLoader oldCl = TCLAction.UTIL.getContextClassLoader();
406 ObjectName mbean = null;
407 try
408 {
409 // Deserialize the transaction if it is there
410 MarshalledInvocation mi = (MarshalledInvocation) invocation;
411 invocation.setTransaction(importTPC(mi.getTransactionPropagationContext()));
412
413 mbean = (ObjectName) Registry.lookup(invocation.getObjectName());
414
415 // The cl on the thread should be set in another interceptor
416 Object obj = serverAction.invoke(mbean,
417 "invoke",
418 new Object[]{invocation},
419 Invocation.INVOKE_SIGNATURE);
420 return new MarshalledObject(obj);
421 }
422 catch (Exception e)
423 {
424 Throwable th = JMXExceptionDecoder.decode(e);
425 if (log.isTraceEnabled())
426 log.trace("Failed to invoke on mbean: " + mbean, th);
427
428 if (th instanceof Exception)
429 e = (Exception) th;
430
431 throw e;
432 }
433 finally
434 {
435 TCLAction.UTIL.setContextClassLoader(oldCl);
436 Thread.interrupted(); // clear interruption because this thread may be pooled.
437 }
438 }
439
440 protected Invoker createDelegateInvoker()
441 {
442 return new JRMPInvokerProxy(this);
443 }
444
445 protected void exportCI() throws Exception
446 {
447 this.invokerStub = (RemoteStub) UnicastRemoteObject.exportObject
448 (this, rmiPort, clientSocketFactory, serverSocketFactory);
449 }
450
451 protected void unexportCI() throws Exception
452 {
453 UnicastRemoteObject.unexportObject(this, true);
454 }
455
456 protected void rebind(Context ctx, String name, Object val)
457 throws NamingException
458 {
459 // Bind val to name in ctx, and make sure that all
460 // intermediate contexts exist
461
462 Name n = ctx.getNameParser("").parse(name);
463 while (n.size() > 1)
464 {
465 String ctxName = n.get(0);
466 try
467 {
468 ctx = (Context) ctx.lookup(ctxName);
469 }
470 catch (NameNotFoundException e)
471 {
472 ctx = ctx.createSubcontext(ctxName);
473 }
474 n = n.getSuffix(1);
475 }
476
477 ctx.rebind(n.get(0), val);
478 }
479
480 /**
481 * Load and instantiate the clientSocketFactory, serverSocketFactory using
482 * the TCL and set the bind address and SSL domain if the serverSocketFactory
483 * supports it.
484 */
485 protected void loadCustomSocketFactories()
486 {
487 ClassLoader loader = TCLAction.UTIL.getContextClassLoader();
488
489 if( clientSocketFactory == null )
490 {
491 try
492 {
493 if (clientSocketFactoryName != null)
494 {
495 Class csfClass = loader.loadClass(clientSocketFactoryName);
496 clientSocketFactory = (RMIClientSocketFactory) csfClass.newInstance();
497 }
498 }
499 catch (Exception e)
500 {
501 log.error("Failed to load client socket factory", e);
502 clientSocketFactory = null;
503 }
504 }
505
506 if( serverSocketFactory == null )
507 {
508 try
509 {
510 if (serverSocketFactoryName != null)
511 {
512 Class ssfClass = loader.loadClass(serverSocketFactoryName);
513 serverSocketFactory = (RMIServerSocketFactory) ssfClass.newInstance();
514 if (serverAddress != null)
515 {
516 // See if the server socket supports setBindAddress(String)
517 try
518 {
519 Class[] parameterTypes = {String.class};
520 Method m = ssfClass.getMethod("setBindAddress", parameterTypes);
521 Object[] args = {serverAddress};
522 m.invoke(serverSocketFactory, args);
523 }
524 catch (NoSuchMethodException e)
525 {
526 log.warn("Socket factory does not support setBindAddress(String)");
527 // Go with default address
528 }
529 catch (Exception e)
530 {
531 log.warn("Failed to setBindAddress=" + serverAddress + " on socket factory", e);
532 // Go with default address
533 }
534 }
535 /* See if the server socket supports setSecurityDomain(SecurityDomain)
536 if an sslDomain was specified
537 */
538 if (sslDomain != null)
539 {
540 try
541 {
542 InitialContext ctx = new InitialContext();
543 SecurityDomain domain = (SecurityDomain) ctx.lookup(sslDomain);
544 Class[] parameterTypes = {SecurityDomain.class};
545 Method m = ssfClass.getMethod("setSecurityDomain", parameterTypes);
546 Object[] args = {domain};
547 m.invoke(serverSocketFactory, args);
548 }
549 catch (NoSuchMethodException e)
550 {
551 log.error("Socket factory does not support setSecurityDomain(SecurityDomain)");
552 }
553 catch (Exception e)
554 {
555 log.error("Failed to setSecurityDomain=" + sslDomain + " on socket factory", e);
556 }
557 }
558 }
559 // If a bind address was specified create a DefaultSocketFactory
560 else if (serverAddress != null)
561 {
562 DefaultSocketFactory defaultFactory = new DefaultSocketFactory(backlog);
563 serverSocketFactory = defaultFactory;
564 try
565 {
566 defaultFactory.setBindAddress(serverAddress);
567 }
568 catch (UnknownHostException e)
569 {
570 log.error("Failed to setBindAddress=" + serverAddress + " on socket factory", e);
571 }
572 }
573 }
574 catch (Exception e)
575 {
576 log.error("operation failed", e);
577 serverSocketFactory = null;
578 }
579 }
580 }
581
582 /**
583 * Import a transaction propagation context into the local VM, and
584 * return the corresponding <code>Transaction</code>.
585 *
586 * @return A transaction or null if no tpc.
587 */
588 protected Transaction importTPC(Object tpc)
589 {
590 if (tpc != null)
591 return TransactionPropagationContextUtil.importTPC(tpc);
592 return null;
593 }
594
595 //
596 // Delegate the ServiceMBean details to our support delegate
597 //
598
599 public String getName()
600 {
601 return support.getName();
602 }
603
604 public MBeanServer getServer()
605 {
606 return support.getServer();
607 }
608
609 public int getState()
610 {
611 return support.getState();
612 }
613
614 public String getStateString()
615 {
616 return support.getStateString();
617 }
618
619 public void create() throws Exception
620 {
621 support.create();
622 }
623
624 public void start() throws Exception
625 {
626 support.start();
627 }
628
629 public void stop()
630 {
631 support.stop();
632 }
633
634 public void destroy()
635 {
636 support.destroy();
637 }
638
639 public void jbossInternalLifecycle(String method) throws Exception
640 {
641 support.jbossInternalLifecycle(method);
642 }
643
644 public ObjectName preRegister(MBeanServer server, ObjectName name)
645 throws Exception
646 {
647 return support.preRegister(server, name);
648 }
649
650 public void postRegister(Boolean registrationDone)
651 {
652 support.postRegister(registrationDone);
653 }
654
655 public void preDeregister() throws Exception
656 {
657 support.preDeregister();
658 }
659
660 public void postDeregister()
661 {
662 support.postDeregister();
663 }
664
665 interface TCLAction
666 {
667 class UTIL
668 {
669 static TCLAction getTCLAction()
670 {
671 return System.getSecurityManager() == null ? NON_PRIVILEGED : PRIVILEGED;
672 }
673
674 static ClassLoader getContextClassLoader()
675 {
676 return getTCLAction().getContextClassLoader();
677 }
678
679 static ClassLoader getContextClassLoader(Thread thread)
680 {
681 return getTCLAction().getContextClassLoader(thread);
682 }
683
684 static void setContextClassLoader(ClassLoader cl)
685 {
686 getTCLAction().setContextClassLoader(cl);
687 }
688
689 static void setContextClassLoader(Thread thread, ClassLoader cl)
690 {
691 getTCLAction().setContextClassLoader(thread, cl);
692 }
693 }
694
695 TCLAction NON_PRIVILEGED = new TCLAction()
696 {
697 public ClassLoader getContextClassLoader()
698 {
699 return Thread.currentThread().getContextClassLoader();
700 }
701
702 public ClassLoader getContextClassLoader(Thread thread)
703 {
704 return thread.getContextClassLoader();
705 }
706
707 public void setContextClassLoader(ClassLoader cl)
708 {
709 Thread.currentThread().setContextClassLoader(cl);
710 }
711
712 public void setContextClassLoader(Thread thread, ClassLoader cl)
713 {
714 thread.setContextClassLoader(cl);
715 }
716 };
717
718 TCLAction PRIVILEGED = new TCLAction()
719 {
720 private final PrivilegedAction getTCLPrivilegedAction = new PrivilegedAction()
721 {
722 public Object run()
723 {
724 return Thread.currentThread().getContextClassLoader();
725 }
726 };
727
728 public ClassLoader getContextClassLoader()
729 {
730 return (ClassLoader) AccessController.doPrivileged(getTCLPrivilegedAction);
731 }
732
733 public ClassLoader getContextClassLoader(final Thread thread)
734 {
735 return (ClassLoader) AccessController.doPrivileged(new PrivilegedAction()
736 {
737 public Object run()
738 {
739 return thread.getContextClassLoader();
740 }
741 });
742 }
743
744 public void setContextClassLoader(final ClassLoader cl)
745 {
746 AccessController.doPrivileged(new PrivilegedAction()
747 {
748 public Object run()
749 {
750 Thread.currentThread().setContextClassLoader(cl);
751 return null;
752 }
753 });
754 }
755
756 public void setContextClassLoader(final Thread thread, final ClassLoader cl)
757 {
758 AccessController.doPrivileged(new PrivilegedAction()
759 {
760 public Object run()
761 {
762 thread.setContextClassLoader(cl);
763 return null;
764 }
765 });
766 }
767 };
768
769 ClassLoader getContextClassLoader();
770
771 ClassLoader getContextClassLoader(Thread thread);
772
773 void setContextClassLoader(ClassLoader cl);
774
775 void setContextClassLoader(Thread thread, ClassLoader cl);
776 }
777
778 /**
779 * Perform the MBeanServer.invoke op in a PrivilegedExceptionAction if
780 * running with a security manager.
781 */
782 class MBeanServerAction implements PrivilegedExceptionAction
783 {
784 private ObjectName target;
785 String method;
786 Object[] args;
787 String[] sig;
788
789 MBeanServerAction()
790 {
791 }
792
793 MBeanServerAction(ObjectName target, String method, Object[] args, String[] sig)
794 {
795 this.target = target;
796 this.method = method;
797 this.args = args;
798 this.sig = sig;
799 }
800
801 public Object run() throws Exception
802 {
803 Object rtnValue = support.getServer().invoke(target, method, args, sig);
804 return rtnValue;
805 }
806
807 Object invoke(ObjectName target, String method, Object[] args, String[] sig)
808 throws Exception
809 {
810 SecurityManager sm = System.getSecurityManager();
811 Object rtnValue = null;
812 if (sm == null)
813 {
814 // Direct invocation on MBeanServer
815 rtnValue = support.getServer().invoke(target, method, args, sig);
816 }
817 else
818 {
819 try
820 {
821 // Encapsulate the invocation in a PrivilegedExceptionAction
822 MBeanServerAction action = new MBeanServerAction(target, method, args, sig);
823 rtnValue = AccessController.doPrivileged(action);
824 }
825 catch (PrivilegedActionException e)
826 {
827 Exception ex = e.getException();
828 throw ex;
829 }
830 }
831 return rtnValue;
832 }
833 }
834 }