2
* JBoss, Home of Professional Open Source
3
* Copyright 2005, JBoss Inc., and individual contributors as indicated
4
* by the @authors tag. See the copyright.txt in the distribution for a
5
* full listing of individual contributors.
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.
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.
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.
22
package org.jboss.remoting.detection.jndi;
24
import org.jboss.logging.Logger;
25
import org.jboss.remoting.InvokerLocator;
26
import org.jboss.remoting.InvokerRegistry;
27
import org.jboss.remoting.detection.AbstractDetector;
28
import org.jboss.remoting.detection.Detection;
29
import org.jboss.remoting.ident.Identity;
30
import org.jboss.remoting.transport.PortUtil;
31
import org.jboss.remoting.util.SecurityUtility;
32
import org.jnp.interfaces.NamingContextFactory;
33
import org.jnp.server.Main;
35
import javax.naming.Binding;
36
import javax.naming.Context;
37
import javax.naming.InitialContext;
38
import javax.naming.NameAlreadyBoundException;
39
import javax.naming.NamingEnumeration;
40
import javax.naming.NamingException;
42
import java.io.IOException;
43
import java.lang.reflect.Method;
44
import java.net.InetAddress;
45
import java.net.UnknownHostException;
46
import java.security.AccessController;
47
import java.security.PrivilegedAction;
48
import java.security.PrivilegedActionException;
49
import java.security.PrivilegedExceptionAction;
51
import java.util.Properties;
54
* This is a remoting detector for the remoting package which uses a JNDI server to
55
* maintain the registeries for remote invoker servers (stored as Detection messages).
56
* This detector is intended to be used in conjuntion with an external JNDI server that
57
* is already running. This is done by passing all the information needed to connect
58
* to the remote JNDI server via the setter methods. This can also be done within
59
* the jboss-service.xml. An example of the entry is as follows:<p>
60
* <mbean code="org.jboss.remoting.detection.jndi.JNDIDetector" name="jboss.remoting:service=Detector,transport=jndi"><br>
61
* <attribute name="Port">5555</attribute><br>
62
* <attribute name="Host">foo.bar.com</attribute><br>
63
* <attribute name="ContextFactory">org.jnp.interfaces.NamingContextFactory</attribute><br>
64
* <attribute name="URLPackage">org.jboss.naming:org.jnp.interfaces</attribute><br>
65
* </mbean><br><p>
66
* Note: The above xml is for the JBoss JNP JNDI server, and has not be tested (just an example).<p>
67
* Be aware that just because this detector is stopped (and the entry removed from the JNDI server)
68
* remote JNDIDetectors may not recognize that the invoker servers are not available. This is because
69
* once remote invoker servers (connectors) are detected, they will be pinged directly to determine
70
* if they are no longer available. However, no new JNDIDetectors will detect your server once stopped.
71
* Also, please note that currently the detection registries are bound at the root context and
72
* not a sub context (which is on the todo list, but you know how that goes).<p>
73
* Important to also note that if any of the above attributes are set once the detector has
74
* started, they will not be used in connecting to the JNDI server until the detector is stopped
75
* and re-started (they do not change the JNDI server connection dynamically).<p>
77
* @author <a href="mailto:telrod@e2technologies.net">Tom Elrod</a>
79
public class JNDIDetector extends AbstractDetector implements JNDIDetectorMBean
83
private String contextFactory = NamingContextFactory.class.getName();
85
private String urlPackage = "org.jboss.naming:org.jnp.interfaces";
89
private Context context;
91
public static final String DETECTION_SUBCONTEXT_NAME = "detection";
93
private String subContextName = DETECTION_SUBCONTEXT_NAME;
96
* Indicates the number of time will detect before doing check to see if server still alive.
98
private int detectionNumber = 5;
99
private int cleanDetectionCount = detectionNumber;
101
protected final Logger log = Logger.getLogger(getClass());
103
public JNDIDetector()
107
public JNDIDetector(Map config)
113
* Gets the port used to connect to the JNDI Server.
123
* Sets the port to use when connecting to JNDI server
127
public void setPort(int port)
133
* Gets the host to use when connecting to JNDI server
137
public String getHost()
143
* Sets the host to use when connecting to JNDI server
147
public void setHost(String host)
153
* The context factory string used when connecting to the JNDI server
157
public String getContextFactory()
159
return contextFactory;
163
* Sets the sub context name under which detection messages will be bound
165
* @param subContextName
167
public void setSubContextName(String subContextName)
169
this.subContextName = subContextName;
173
* Gets the sub context name under which detection messages will be bound and
177
public String getSubContextName()
179
return this.subContextName;
183
* The context factory string to use when connecting to the JNDI server.
184
* Should be a qualified class name for JNDI client.
186
* @param contextFactory
188
public void setContextFactory(String contextFactory)
190
this.contextFactory = contextFactory;
194
* The url package string used when connecting to JNDI server
198
public String getURLPackage()
204
* The url package string to use when connecting to the JNDI server.
208
public void setURLPackage(String urlPackage)
210
this.urlPackage = urlPackage;
214
* Will establish the connection to the JNDI server and start detection of other servers.
218
public void start() throws Exception
221
id = Identity.get(mbeanserver);
226
* Creates connection to JNDI server (which should have already happened in start()
227
* method) and will begin checking for remote servers as well as registering itself
228
* so will be visible by remote detectors.
230
protected void heartbeat()
234
//Need to establish connection to server
239
checkRemoteDetectionMsg();
241
catch(NamingException nex)
243
log.error("Can not connect to JNDI server to register local connectors.", nex);
247
protected void forceHeartbeat()
253
* Gets the number of detection iterations before manually pinging remote
254
* server to make sure still alive.
258
public int getCleanDetectionNumber()
260
return detectionNumber;
264
* Sets the number of detection iterations before manually pinging remote
265
* server to make sure still alive. This is needed since remote server
266
* could crash and yet still have an entry in the JNDI server, thus
267
* making it appear that it is still there.
269
* @param cleanDetectionNumber
271
public void setCleanDetectionNumber(int cleanDetectionNumber)
273
detectionNumber = cleanDetectionNumber;
274
cleanDetectionCount = detectionNumber;
277
private void checkRemoteDetectionMsg()
281
boolean localFound = false;
282
cleanDetectionCount++;
283
boolean cleanDetect = cleanDetectionCount > detectionNumber;
284
String bindName = "";
285
NamingEnumeration enumeration = listBindings(context, bindName);
286
while(enumeration.hasMore())
288
Binding binding = (Binding) enumeration.next();
289
Detection regMsg = (Detection) binding.getObject();
290
// No need to detect myself here
291
if(isRemoteDetection(regMsg))
293
log.debug("Detected id: " + regMsg.getIdentity().getInstanceId() + ", message: " + regMsg);
297
if(log.isTraceEnabled())
299
log.trace("Doing clean detection.");
301
// Need to actually detect if servers registered in JNDI server
302
// are actually there (since could die before unregistering)
303
ClassLoader cl = (ClassLoader) AccessController.doPrivileged( new PrivilegedAction()
307
return JNDIDetector.class.getClassLoader();
311
if(!checkInvokerServer(regMsg, cl))
313
unregisterDetection(regMsg.getIdentity().getInstanceId());
317
// Now, let parent handle detection
323
// Now, let parent handle detection
329
//verify local detection message is correct
330
if(!verifyLocalDetectionMsg(regMsg))
332
addLocalDetectionMsg();
339
// did clean detect, now need to reset.
340
cleanDetectionCount = 0;
344
// never found local detection message in list, so add it
345
addLocalDetectionMsg();
348
catch(NamingException e)
350
log.error("Exception getting detection messages from JNDI server.", e);
354
private boolean verifyLocalDetectionMsg(Detection regMsg) throws NamingException
356
boolean verified = false;
358
InvokerLocator[] locators = InvokerRegistry.getRegisteredServerLocators();
359
Detection msg = createDetection();
360
String sId = id.getInstanceId();
361
InvokerLocator[] invokers = regMsg.getLocators();
363
// first do sanity check to make sure even local detection msg (just in case)
364
if(sId.equals(regMsg.getIdentity().getInstanceId()))
367
// now see if invoker list changed
368
boolean changed = false;
369
if(locators.length != invokers.length)
375
// now need to make sure all the invokers are same now as in old detection msg
376
// not the most efficient (or elegant) way to do this, but list is short
377
boolean found = false; // flag for if current invoker in list found in old list
378
for(int i = 0; i < locators.length; i++)
381
for(int x = 0; x < invokers.length; x++)
383
if(locators[i].equals(invokers[x]))
401
registerDetectionMsg(sId, msg);
403
// are sure that local detection is correct in JNDI server now
409
private void addLocalDetectionMsg() throws NamingException
411
Detection msg = createDetection();
412
String sId = id.getInstanceId();
413
registerDetectionMsg(sId, msg);
416
private void registerDetectionMsg(String sId, Detection msg) throws NamingException
418
if(sId != null && msg != null)
422
rebind(context, sId, msg);
423
log.info("Added " + sId + " to registry.");
425
catch(NameAlreadyBoundException nabex)
427
if(log.isTraceEnabled())
429
log.trace(sId + " already bound to server.");
436
* Convience method to see if given proper configuration to connect to an
437
* existing JNDI server. If not, will create one via JBoss JNP. Should
438
* really only be needed for testing.
440
private void verifyJNDIServer()
442
if(host == null || host.length() == 0)
446
log.info("JNDI Server configuration information not present so will create a local server.");
448
Object namingBean = null;
449
Class namingBeanImplClass = null;
452
namingBeanImplClass = Class.forName("org.jnp.server.NamingBeanImpl");
453
namingBean = namingBeanImplClass.newInstance();
454
Method startMethod = namingBeanImplClass.getMethod("start", new Class[] {});
455
setSystemProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
456
startMethod.invoke(namingBean, new Object[] {});
460
log.debug("Cannot find NamingBeanImpl: must be running jdk 1.4");
463
host = getLocalHostName();
464
port = PortUtil.findFreePort(host);
466
log.info("Remoting JNDI detector starting JNDI server instance since none where specified via configuration.");
467
log.info("Remoting JNDI server started on host + " + host + " and port " + port);
469
//If no server information provided, then start one of our own by default
470
Main server = new Main();
471
if (namingBean != null)
473
Class namingBeanClass = Class.forName("org.jnp.server.NamingBean");
474
Method setNamingInfoMethod = server.getClass().getMethod("setNamingInfo", new Class[] {namingBeanClass});
475
setNamingInfoMethod.invoke(server, new Object[] {namingBean});
477
server.setPort(port);
478
server.setBindAddress(host);
481
contextFactory = NamingContextFactory.class.getName();
482
urlPackage = "org.jboss.naming:org.jnp.interfaces";
486
log.error("Error starting up JNDI server since none was specified via configuration.", e);
492
* Will try to establish the initial context to the JNDI server based
493
* on the configuration properties set.
495
* @throws NamingException
497
private void createContext() throws NamingException
501
Properties env = new Properties();
503
env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory);
504
env.put(Context.PROVIDER_URL, host + ":" + port);
505
env.put(Context.URL_PKG_PREFIXES, urlPackage);
507
InitialContext initialContext = createContext(env);
510
context = initialContextLookup(initialContext, subContextName);
512
catch(NamingException e)
516
context = createSubcontext(initialContext, subContextName);
518
catch(NameAlreadyBoundException e1)
520
log.debug("The sub context " + subContextName + " was created before we could.");
521
context = initialContextLookup(initialContext, subContextName);
526
public void stop() throws Exception
532
finally // Need to cleanup JNDI, even if super's stop throws exception
534
String sId = id.getInstanceId();
537
unregisterDetection(sId);
539
catch(NamingException e)
541
log.warn("Could not unregister " + sId + " before shutdown. " +
542
"Root cause is " + e.getMessage());
547
private void unregisterDetection(String sId) throws NamingException
549
if(log.isTraceEnabled())
551
log.trace("unregistering detector " + sId);
553
unbind(context, sId);
556
static private void setSystemProperty(final String name, final String value)
558
if (SecurityUtility.skipAccessControl())
560
System.setProperty(name, value);
566
AccessController.doPrivileged( new PrivilegedExceptionAction()
568
public Object run() throws Exception
570
return System.setProperty(name, value);
574
catch (PrivilegedActionException e)
576
throw (RuntimeException) e.getCause();
580
static private InetAddress getLocalHost() throws UnknownHostException
582
if (SecurityUtility.skipAccessControl())
586
return InetAddress.getLocalHost();
588
catch (IOException e)
590
return InetAddress.getByName("127.0.0.1");
596
return (InetAddress) AccessController.doPrivileged( new PrivilegedExceptionAction()
598
public Object run() throws IOException
602
return InetAddress.getLocalHost();
604
catch (IOException e)
606
return InetAddress.getByName("127.0.0.1");
611
catch (PrivilegedActionException e)
613
throw (UnknownHostException) e.getCause();
617
static private String getLocalHostName() throws UnknownHostException
619
if (SecurityUtility.skipAccessControl())
621
return getLocalHost().getHostName();
626
return (String) AccessController.doPrivileged( new PrivilegedExceptionAction()
628
public Object run() throws IOException
630
InetAddress address = null;
633
address = InetAddress.getLocalHost();
635
catch (IOException e)
637
address = InetAddress.getByName("127.0.0.1");
640
return address.getHostName();
644
catch (PrivilegedActionException e)
646
throw (UnknownHostException) e.getCause();
650
static private Context createSubcontext(final InitialContext initialContext, final String subContextName)
651
throws NamingException
653
if (SecurityUtility.skipAccessControl())
655
return initialContext.createSubcontext(subContextName);
660
return (Context) AccessController.doPrivileged( new PrivilegedExceptionAction()
662
public Object run() throws NamingException
664
return initialContext.createSubcontext(subContextName);
668
catch (PrivilegedActionException e)
670
throw (NamingException) e.getCause();
674
static private Context initialContextLookup(final InitialContext initialContext, final String subContextName)
675
throws NamingException
677
if (SecurityUtility.skipAccessControl())
679
return (Context) initialContext.lookup(subContextName);
684
return (Context) AccessController.doPrivileged( new PrivilegedExceptionAction()
686
public Object run() throws NamingException
688
return initialContext.lookup(subContextName);
692
catch (PrivilegedActionException e)
694
throw (NamingException) e.getCause();
698
static private NamingEnumeration listBindings(final Context context, final String bindName)
699
throws NamingException
701
if (SecurityUtility.skipAccessControl())
703
return context.listBindings(bindName);
708
return (NamingEnumeration) AccessController.doPrivileged( new PrivilegedExceptionAction()
710
public Object run() throws NamingException
712
return context.listBindings(bindName);
716
catch (PrivilegedActionException e)
718
throw (NamingException) e.getCause();
722
static private void rebind(final Context context, final String name, final Object object)
723
throws NamingException
725
if (SecurityUtility.skipAccessControl())
727
context.rebind(name, object);
733
AccessController.doPrivileged( new PrivilegedExceptionAction()
735
public Object run() throws NamingException
737
context.rebind(name, object);
742
catch (PrivilegedActionException e)
744
throw (NamingException) e.getCause();
748
static private void unbind(final Context context, final String name)
749
throws NamingException
751
if (SecurityUtility.skipAccessControl())
753
context.unbind(name);
759
AccessController.doPrivileged( new PrivilegedExceptionAction()
761
public Object run() throws NamingException
763
context.unbind(name);
768
catch (PrivilegedActionException e)
770
throw (NamingException) e.getCause();
774
static private InitialContext createContext(final Properties env) throws NamingException
776
if (SecurityUtility.skipAccessControl())
778
return new InitialContext(env);
783
return (InitialContext) AccessController.doPrivileged( new PrivilegedExceptionAction()
785
public Object run() throws Exception
787
return new InitialContext(env);
791
catch (PrivilegedActionException e)
793
throw (RuntimeException) e.getCause();