org.jboss.remoting.detection.jndi.JNDIDetector Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.remoting.detection.jndi;
import org.jboss.logging.Logger;
import org.jboss.remoting.InvokerLocator;
import org.jboss.remoting.InvokerRegistry;
import org.jboss.remoting.detection.AbstractDetector;
import org.jboss.remoting.detection.Detection;
import org.jboss.remoting.ident.Identity;
import org.jboss.remoting.transport.PortUtil;
import org.jboss.remoting.util.LocalHostUtil;
import org.jnp.interfaces.NamingContextFactory;
import org.jnp.server.Main;
import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import java.net.InetAddress;
import java.util.Map;
import java.util.Properties;
/**
* This is a remoting detector for the remoting package which uses a JNDI server to
* maintain the registeries for remote invoker servers (stored as Detection messages).
* This detector is intended to be used in conjuntion with an external JNDI server that
* is already running. This is done by passing all the information needed to connect
* to the remote JNDI server via the setter methods. This can also be done within
* the jboss-service.xml. An example of the entry is as follows:
* <mbean code="org.jboss.remoting.detection.jndi.JNDIDetector" name="jboss.remoting:service=Detector,transport=jndi">
* <attribute name="Port">5555</attribute>
* <attribute name="Host">foo.bar.com</attribute>
* <attribute name="ContextFactory">org.jnp.interfaces.NamingContextFactory</attribute>
* <attribute name="URLPackage">org.jboss.naming:org.jnp.interfaces</attribute>
* </mbean>
* Note: The above xml is for the JBoss JNP JNDI server, and has not be tested (just an example).
* Be aware that just because this detector is stopped (and the entry removed from the JNDI server)
* remote JNDIDetectors may not recognize that the invoker servers are not available. This is because
* once remote invoker servers (connectors) are detected, they will be pinged directly to determine
* if they are no longer available. However, no new JNDIDetectors will detect your server once stopped.
* Also, please note that currently the detection registries are bound at the root context and
* not a sub context (which is on the todo list, but you know how that goes).
* Important to also note that if any of the above attributes are set once the detector has
* started, they will not be used in connecting to the JNDI server until the detector is stopped
* and re-started (they do not change the JNDI server connection dynamically).
*
* @author Tom Elrod
*/
public class JNDIDetector extends AbstractDetector implements JNDIDetectorMBean
{
private int port;
private String host;
private String contextFactory = NamingContextFactory.class.getName();
;
private String urlPackage = "org.jboss.naming:org.jnp.interfaces";
;
private Identity id;
private Context context;
private int cleanDetectionCount = 0;
public static final String DETECTION_SUBCONTEXT_NAME = "detection";
private String subContextName = DETECTION_SUBCONTEXT_NAME;
/**
* Indicates the number of time will detect before doing check to see if server still alive.
*/
private int detectionNumber = 5;
protected final Logger log = Logger.getLogger(getClass());
public JNDIDetector()
{
}
public JNDIDetector(Map config)
{
super(config);
}
/**
* Gets the port used to connect to the JNDI Server.
*
* @return
*/
public int getPort()
{
return port;
}
/**
* Sets the port to use when connecting to JNDI server
*
* @param port
*/
public void setPort(int port)
{
this.port = port;
}
/**
* Gets the host to use when connecting to JNDI server
*
* @return
*/
public String getHost()
{
return host;
}
/**
* Sets the host to use when connecting to JNDI server
*
* @param host
*/
public void setHost(String host)
{
this.host = host;
}
/**
* The context factory string used when connecting to the JNDI server
*
* @return
*/
public String getContextFactory()
{
return contextFactory;
}
/**
* Sets the sub context name under which detection messages will be bound
* and looked up.
* @param subContextName
*/
public void setSubContextName(String subContextName)
{
this.subContextName = subContextName;
}
/**
* Gets the sub context name under which detection messages will be bound and
* looked up.
* @return
*/
public String getSubContextName()
{
return this.subContextName;
}
/**
* The context factory string to use when connecting to the JNDI server.
* Should be a qualified class name for JNDI client.
*
* @param contextFactory
*/
public void setContextFactory(String contextFactory)
{
this.contextFactory = contextFactory;
}
/**
* The url package string used when connecting to JNDI server
*
* @return
*/
public String getURLPackage()
{
return urlPackage;
}
/**
* The url package string to use when connecting to the JNDI server.
*
* @param urlPackage
*/
public void setURLPackage(String urlPackage)
{
this.urlPackage = urlPackage;
}
/**
* Will establish the connection to the JNDI server and start detection of other servers.
*
* @throws Exception
*/
public void start() throws Exception
{
createContext();
id = Identity.get(mbeanserver);
super.start();
}
/**
* Creates connection to JNDI server (which should have already happened in start()
* method) and will begin checking for remote servers as well as registering itself
* so will be visible by remote detectors.
*/
protected void heartbeat()
{
try
{
//Need to establish connection to server
if(context == null)
{
createContext();
}
checkRemoteDetectionMsg();
}
catch(NamingException nex)
{
log.error("Can not connect to JNDI server to register local connectors.", nex);
}
}
protected void forceHeartbeat()
{
heartbeat();
}
/**
* Gets the number of detection iterations before manually pinging remote
* server to make sure still alive.
*
* @return
*/
public int getCleanDetectionNumber()
{
return detectionNumber;
}
/**
* Sets the number of detection iterations before manually pinging remote
* server to make sure still alive. This is needed since remote server
* could crash and yet still have an entry in the JNDI server, thus
* making it appear that it is still there.
*
* @param cleanDetectionNumber
*/
public void setCleanDetectionNumber(int cleanDetectionNumber)
{
detectionNumber = cleanDetectionNumber;
}
private void checkRemoteDetectionMsg()
{
try
{
boolean localFound = false;
cleanDetectionCount++;
boolean cleanDetect = cleanDetectionCount > detectionNumber;
String bindName = "";
NamingEnumeration enumeration = context.listBindings(bindName);
while(enumeration.hasMore())
{
Binding binding = (Binding) enumeration.next();
Detection regMsg = (Detection) binding.getObject();
// No need to detect myself here
if(isRemoteDetection(regMsg))
{
log.debug("Detected id: " + regMsg.getIdentity().getInstanceId() + ", message: " + regMsg);
if(cleanDetect)
{
if(log.isTraceEnabled())
{
log.trace("Doing clean detection.");
}
// Need to actually detect if servers registered in JNDI server
// are actually there (since could die before unregistering)
ClassLoader cl = JNDIDetector.this.getClass().getClassLoader();
if(!checkInvokerServer(regMsg, cl))
{
unregisterDetection(regMsg.getIdentity().getInstanceId());
}
}
else
{
// Let parent handle detection
detect(regMsg);
}
}
else
{
//verify local detection message is correct
if(!verifyLocalDetectionMsg(regMsg))
{
addLocalDetectionMsg();
}
localFound = true;
}
}
if(cleanDetect)
{
// did clean detect, now need to reset.
cleanDetectionCount = 0;
}
if(!localFound)
{
// never found local detection message in list, so add it
addLocalDetectionMsg();
}
}
catch(NamingException e)
{
log.error("Exception getting detection messages from JNDI server.", e);
}
}
private boolean verifyLocalDetectionMsg(Detection regMsg) throws NamingException
{
boolean verified = false;
InvokerLocator[] locators = InvokerRegistry.getRegisteredServerLocators();
Detection msg = createDetection();
String sId = id.getInstanceId();
InvokerLocator[] invokers = regMsg.getLocators();
// first do sanity check to make sure even local detection msg (just in case)
if(sId.equals(regMsg.getIdentity().getInstanceId()))
{
// now see if invoker list changed
boolean changed = false;
if(locators.length != invokers.length)
{
changed = true;
}
else
{
// now need to make sure all the invokers are same now as in old detection msg
// not the most efficient (or elegant) way to do this, but list is short
boolean found = false; // flag for if current invoker in list found in old list
for(int i = 0; i < locators.length; i++)
{
found = false;
for(int x = 0; x < invokers.length; x++)
{
if(locators[i].equals(invokers[x]))
{
found = true;
break;
}
}
if(!found)
{
break;
}
}
if(!found)
{
changed = true;
}
}
if(changed)
{
registerDetectionMsg(sId, msg);
}
// are sure that local detection is correct in JNDI server now
verified = true;
}
return verified;
}
private void addLocalDetectionMsg() throws NamingException
{
Detection msg = createDetection();
String sId = id.getInstanceId();
registerDetectionMsg(sId, msg);
}
private void registerDetectionMsg(String sId, Detection msg) throws NamingException
{
if(sId != null && msg != null)
{
try
{
context.bind(sId, msg);
log.info("Added " + sId + " to registry.");
}
catch(NameAlreadyBoundException nabex)
{
if(log.isTraceEnabled())
{
log.trace(sId + " already bound to server.");
}
}
}
}
/**
* Convience method to see if given proper configuration to connect to an
* existing JNDI server. If not, will create one via JBoss JNP. Should
* really only be needed for testing.
*/
private void verifyJNDIServer()
{
if(host == null || host.length() == 0)
{
try
{
log.info("JNDI Server configuration information not present so will create a local server.");
host = LocalHostUtil.getLocalHost().getHostName();
port = PortUtil.findFreePort(host);
log.info("Remoting JNDI detector starting JNDI server instance since none where specified via configuration.");
log.info("Remoting JNDI server started on host + " + host + " and port " + port);
//If no server information provided, then start one of our own by default
Main server = new Main();
server.setPort(port);
server.setBindAddress(host);
server.start();
contextFactory = NamingContextFactory.class.getName();
urlPackage = "org.jboss.naming:org.jnp.interfaces";
}
catch(Exception e)
{
log.error("Error starting up JNDI server since none was specified via configuration.", e);
}
}
}
/**
* Will try to establish the initial context to the JNDI server based
* on the configuration properties set.
*
* @throws NamingException
*/
private void createContext() throws NamingException
{
verifyJNDIServer();
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory);
env.put(Context.PROVIDER_URL, host + ":" + port);
env.put(Context.URL_PKG_PREFIXES, urlPackage);
InitialContext initialContext = new InitialContext(env);
try
{
context = (Context) initialContext.lookup(subContextName);
}
catch(NamingException e)
{
try
{
context = initialContext.createSubcontext(subContextName);
}
catch(NameAlreadyBoundException e1)
{
log.debug("The sub context " + subContextName + " was created before we could.");
context = (Context) initialContext.lookup(subContextName);
}
}
}
public void stop() throws Exception
{
try
{
super.stop();
}
finally // Need to cleanup JNDI, even if super's stop throws exception
{
String sId = id.getInstanceId();
try
{
unregisterDetection(sId);
}
catch(NamingException e)
{
log.warn("Could not unregister " + sId + " before shutdown. " +
"Root cause is " + e.getMessage());
}
}
}
private void unregisterDetection(String sId) throws NamingException
{
if(log.isTraceEnabled())
{
log.trace("unregistering detector " + sId);
}
context.unbind(sId);
}
}