/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file 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.naming; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.URL; import java.util.Hashtable; import java.util.Properties; import javax.naming.CompositeName; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.Name; import javax.naming.NamingException; import javax.naming.RefAddr; import javax.naming.Reference; import javax.naming.Referenceable; import javax.naming.ldap.Control; import javax.naming.spi.ObjectFactory; import org.jboss.system.ServiceMBeanSupport; import org.jboss.util.Classes; /** * A MBean that binds an arbitrary InitialContext into the JBoss default * InitialContext as a Reference. If RemoteAccess is enabled, the reference * is a Serializable object that is capable of creating the InitialContext * remotely. If RemoteAccess if false, the reference is to a nonserializable object * that can only be used from within this VM. * * @jmx:mbean extends="org.jboss.system.ServiceMBean" * * @see org.jboss.naming.NonSerializableFactory * * @version <tt>$Revision: 81030 $</tt> * @author Scott.Stark@jboss.org * @author <a href="mailto:jason@planet57.com">Jason Dillon</a> */ public class ExternalContext extends ServiceMBeanSupport implements ExternalContextMBean { private boolean remoteAccess; private SerializableInitialContext contextInfo = new SerializableInitialContext(); /** * No-args constructor for JMX. */ public ExternalContext() { super(); } public ExternalContext(String jndiName, String contextPropsURL) throws IOException, NamingException { setJndiName(jndiName); setPropertiesURL(contextPropsURL); } /** * Set the jndi name under which the external context is bound. * * @jmx:managed-attribute */ public String getJndiName() { return contextInfo.getJndiName(); } /** * Set the jndi name under which the external context is bound. * * @jmx:managed-attribute */ public void setJndiName(String jndiName) throws NamingException { contextInfo.setJndiName(jndiName); if( super.getState() == STARTED ) { unbind(jndiName); try { rebind(); } catch(Exception e) { NamingException ne = new NamingException("Failed to update jndiName"); ne.setRootCause(e); throw ne; } } } /** * @jmx:managed-attribute */ public boolean getRemoteAccess() { return remoteAccess; } /** * @jmx:managed-attribute */ public void setRemoteAccess(final boolean remoteAccess) { this.remoteAccess = remoteAccess; } /** * @jmx:managed-attribute */ public boolean getCacheContext() { return contextInfo.getCacheContext(); } /** * @jmx:managed-attribute */ public void setCacheContext(boolean cacheContext) { contextInfo.setCacheContext(cacheContext); } /** * Get the class name of the InitialContext implementation to * use. Should be one of: * <ul> * <li>javax.naming.InitialContext * <li>javax.naming.directory.InitialDirContext * <li>javax.naming.ldap.InitialLdapContext * </ul> * * @jmx:managed-attribute * * @return the classname of the InitialContext to use */ public String getInitialContext() { return contextInfo.getInitialContext(); } /** * Set the class name of the InitialContext implementation to * use. Should be one of: * <ul> * <li>javax.naming.InitialContext * <li>javax.naming.directory.InitialDirContext * <li>javax.naming.ldap.InitialLdapContext * </ul> * * @jmx:managed-attribute * * @param contextClass, the classname of the InitialContext to use */ public void setInitialContext(String className) throws ClassNotFoundException { contextInfo.loadClass(className); } /** * Set the InitialContex class environment properties from the given URL. * * @jmx:managed-attribute */ public void setPropertiesURL(String contextPropsURL) throws IOException { contextInfo.loadProperties(contextPropsURL); } /** * Set the InitialContex class environment properties. * * @jmx:managed-attribute */ public void setProperties(final Properties props) throws IOException { contextInfo.setProperties(props); } /** * Get the InitialContex class environment properties. * * @jmx:managed-attribute */ public Properties getProperties() throws IOException { return contextInfo.getProperties(); } /** * Start the service by binding the external context into the * JBoss InitialContext. */ protected void startService() throws Exception { rebind(); } /** * Stop the service by unbinding the external context into the * JBoss InitialContext. */ protected void stopService() throws Exception { if( contextInfo.getCacheContext() ) unbind(contextInfo.getJndiName()); } private static Context createContext(Context rootContext, Name name) throws NamingException { Context subctx = rootContext; for(int n = 0; n < name.size(); n ++) { String atom = name.get(n); try { Object obj = subctx.lookup(atom); subctx = (Context) obj; } catch(NamingException e) { // No binding exists, create a subcontext subctx = subctx.createSubcontext(atom); } } return subctx; } private void rebind() throws Exception { Context ctx = contextInfo.newContext(); Context rootCtx = (Context) new InitialContext(); log.debug("ctx="+ctx+", env="+ctx.getEnvironment()); // Get the parent context into which we are to bind String jndiName = contextInfo.getJndiName(); Name fullName = rootCtx.getNameParser("").parse(jndiName); log.debug("fullName="+fullName); Name parentName = fullName; if( fullName.size() > 1 ) parentName = fullName.getPrefix(fullName.size()-1); else parentName = new CompositeName(); log.debug("parentName="+parentName); Context parentCtx = createContext(rootCtx, parentName); log.debug("parentCtx="+parentCtx); Name atomName = fullName.getSuffix(fullName.size()-1); String atom = atomName.get(0); boolean cacheContext = contextInfo.getCacheContext(); if( remoteAccess == true ) { // Bind contextInfo as a Referenceable parentCtx.rebind(atom, contextInfo); // Cache the context using NonSerializableFactory to avoid creating // more than one context for in VM lookups if( cacheContext == true ) { // If cacheContext is true we need to wrap the Context in a // proxy that allows the user to issue close on the lookup // Context without closing the inmemory Context. ctx = CachedContext.createProxyContext(ctx); NonSerializableFactory.rebind(jndiName, ctx); } } else if( cacheContext == true ) { // Bind a reference to the extern context using // NonSerializableFactory as the ObjectFactory. The Context must // be wrapped in a proxy that allows the user to issue close on the // lookup Context without closing the inmemory Context. Context proxyCtx = CachedContext.createProxyContext(ctx); NonSerializableFactory.rebind(rootCtx, jndiName, proxyCtx); } else { // Bind the contextInfo so that each lookup results in the creation // of a new Context object. The returned Context must be closed // by the user to prevent resource leaks. parentCtx.rebind(atom, contextInfo); } } private void unbind(String jndiName) { try { Context rootCtx = new InitialContext(); Context ctx = (Context) rootCtx.lookup(jndiName); if( ctx != null ) ctx.close(); rootCtx.unbind(jndiName); NonSerializableFactory.unbind(jndiName); } catch(NamingException e) { log.error("unbind failed", e); } } /** * The external InitialContext information class. It acts as the * RefAddr and ObjectFactory for the external IntialContext and can * be marshalled to a remote client. */ public static class SerializableInitialContext extends RefAddr implements Referenceable, Serializable, ObjectFactory { private static final long serialVersionUID = -6512260531255770463L; private String jndiName; private Class contextClass = javax.naming.InitialContext.class; private Properties contextProps; private boolean cacheContext = true; private transient Context initialContext; public SerializableInitialContext() { this("SerializableInitialContext"); } public SerializableInitialContext(String addrType) { super(addrType); } public String getJndiName() { return jndiName; } public void setJndiName(final String jndiName) { this.jndiName = jndiName; } public boolean getCacheContext() { return cacheContext; } public void setCacheContext(final boolean cacheContext) { this.cacheContext = cacheContext; } public String getInitialContext() { return contextClass.getName(); } public void loadClass(String className) throws ClassNotFoundException { ClassLoader loader = Thread.currentThread().getContextClassLoader(); contextClass = loader.loadClass(className); } public void setProperties(final Properties props) { contextProps = props; } public Properties getProperties() { return contextProps; } public void loadProperties(String contextPropsURL) throws IOException { InputStream is = null; contextProps = new Properties(); // See if this is a URL we can load try { URL url = new URL(contextPropsURL); is = url.openStream(); contextProps.load(is); return; } catch (IOException e) { // Failed, try to locate a classpath resource below is = null; } is = Thread.currentThread().getContextClassLoader().getResourceAsStream(contextPropsURL); if( is == null ) { throw new IOException("Failed to locate context props as URL or resource:"+contextPropsURL); } contextProps.load(is); } Context newContext() throws Exception { // First check the NonSerializableFactory cache initialContext = (Context) NonSerializableFactory.lookup(jndiName); // Create the context from the contextClass and contextProps if( initialContext == null ) initialContext = newContext(contextClass, contextProps); return initialContext; } static Context newContext(Class contextClass, Properties contextProps) throws Exception { Context ctx = null; try { ctx = newDefaultContext(contextClass, contextProps); } catch(NoSuchMethodException e) { ctx = newLdapContext(contextClass, contextProps); } return ctx; } private static Context newDefaultContext(Class contextClass, Properties contextProps) throws Exception { Context ctx = null; Class[] types = {Hashtable.class}; Constructor ctor = contextClass.getConstructor(types); Object[] args = {contextProps}; ctx = (Context) ctor.newInstance(args); return ctx; } private static Context newLdapContext(Class contextClass, Properties contextProps) throws Exception { Context ctx = null; Class[] types = {Hashtable.class, Control[].class}; Constructor ctor = contextClass.getConstructor(types); Object[] args = {contextProps, null}; ctx = (Context) ctor.newInstance(args); return ctx; } public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception { Reference ref = (Reference) obj; SerializableInitialContext sic = (SerializableInitialContext) ref.get(0); return sic.newContext(); } public Reference getReference() throws NamingException { Reference ref = new Reference(Context.class.getName(), this, this.getClass().getName(), null); return ref; } public Object getContent() { return null; } } /** * A proxy implementation of Context that simply intercepts the * close() method and ignores it since the underlying Context * object is being maintained in memory. */ static class CachedContext implements InvocationHandler { Context externalCtx; CachedContext(Context externalCtx) { this.externalCtx = externalCtx; } static Context createProxyContext(Context ctx) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class[] interfaces = Classes.getAllUniqueInterfaces(ctx.getClass()); InvocationHandler handler = new CachedContext(ctx); Context proxyCtx = (Context) Proxy.newProxyInstance(loader, interfaces, handler); return proxyCtx; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object value = null; if( method.getName().equals("close") ) { // We just ignore the close method } else { try { value = method.invoke(externalCtx, args); } catch(InvocationTargetException e) { throw e.getTargetException(); } } return value; } } }