1 /*
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.
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.naming;
23
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.Serializable;
27 import java.lang.reflect.Constructor;
28 import java.lang.reflect.InvocationHandler;
29 import java.lang.reflect.InvocationTargetException;
30 import java.lang.reflect.Method;
31 import java.lang.reflect.Proxy;
32 import java.net.URL;
33 import java.util.Hashtable;
34 import java.util.Properties;
35
36 import javax.naming.CompositeName;
37 import javax.naming.Context;
38 import javax.naming.InitialContext;
39 import javax.naming.Name;
40 import javax.naming.NamingException;
41 import javax.naming.RefAddr;
42 import javax.naming.Reference;
43 import javax.naming.Referenceable;
44 import javax.naming.ldap.Control;
45 import javax.naming.spi.ObjectFactory;
46
47 import org.jboss.system.ServiceMBeanSupport;
48 import org.jboss.util.Classes;
49
50 /**
51 * A MBean that binds an arbitrary InitialContext into the JBoss default
52 * InitialContext as a Reference. If RemoteAccess is enabled, the reference
53 * is a Serializable object that is capable of creating the InitialContext
54 * remotely. If RemoteAccess if false, the reference is to a nonserializable object
55 * that can only be used from within this VM.
56 *
57 * @jmx:mbean extends="org.jboss.system.ServiceMBean"
58 *
59 * @see org.jboss.naming.NonSerializableFactory
60 *
61 * @version <tt>$Revision: 70435 $</tt>
62 * @author Scott.Stark@jboss.org
63 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
64 */
65 public class ExternalContext
66 extends ServiceMBeanSupport
67 implements ExternalContextMBean
68 {
69 private boolean remoteAccess;
70 private SerializableInitialContext contextInfo = new SerializableInitialContext();
71
72 /**
73 * No-args constructor for JMX.
74 */
75 public ExternalContext()
76 {
77 super();
78 }
79
80 public ExternalContext(String jndiName, String contextPropsURL)
81 throws IOException, NamingException
82 {
83 setJndiName(jndiName);
84 setPropertiesURL(contextPropsURL);
85 }
86
87 /**
88 * Set the jndi name under which the external context is bound.
89 *
90 * @jmx:managed-attribute
91 */
92 public String getJndiName()
93 {
94 return contextInfo.getJndiName();
95 }
96
97 /**
98 * Set the jndi name under which the external context is bound.
99 *
100 * @jmx:managed-attribute
101 */
102 public void setJndiName(String jndiName) throws NamingException
103 {
104 contextInfo.setJndiName(jndiName);
105 if( super.getState() == STARTED )
106 {
107 unbind(jndiName);
108 try
109 {
110 rebind();
111 }
112 catch(Exception e)
113 {
114 NamingException ne = new NamingException("Failed to update jndiName");
115 ne.setRootCause(e);
116 throw ne;
117 }
118 }
119 }
120
121 /**
122 * @jmx:managed-attribute
123 */
124 public boolean getRemoteAccess()
125 {
126 return remoteAccess;
127 }
128
129 /**
130 * @jmx:managed-attribute
131 */
132 public void setRemoteAccess(final boolean remoteAccess)
133 {
134 this.remoteAccess = remoteAccess;
135 }
136
137 /**
138 * @jmx:managed-attribute
139 */
140 public boolean getCacheContext()
141 {
142 return contextInfo.getCacheContext();
143 }
144
145 /**
146 * @jmx:managed-attribute
147 */
148 public void setCacheContext(boolean cacheContext)
149 {
150 contextInfo.setCacheContext(cacheContext);
151 }
152
153 /**
154 * Get the class name of the InitialContext implementation to
155 * use. Should be one of:
156 * <ul>
157 * <li>javax.naming.InitialContext
158 * <li>javax.naming.directory.InitialDirContext
159 * <li>javax.naming.ldap.InitialLdapContext
160 * </ul>
161 *
162 * @jmx:managed-attribute
163 *
164 * @return the classname of the InitialContext to use
165 */
166 public String getInitialContext()
167 {
168 return contextInfo.getInitialContext();
169 }
170
171 /**
172 * Set the class name of the InitialContext implementation to
173 * use. Should be one of:
174 * <ul>
175 * <li>javax.naming.InitialContext
176 * <li>javax.naming.directory.InitialDirContext
177 * <li>javax.naming.ldap.InitialLdapContext
178 * </ul>
179 *
180 * @jmx:managed-attribute
181 *
182 * @param contextClass, the classname of the InitialContext to use
183 */
184 public void setInitialContext(String className) throws ClassNotFoundException
185 {
186 contextInfo.loadClass(className);
187 }
188
189 /**
190 * Set the InitialContex class environment properties from the given URL.
191 *
192 * @jmx:managed-attribute
193 */
194 public void setPropertiesURL(String contextPropsURL) throws IOException
195 {
196 contextInfo.loadProperties(contextPropsURL);
197 }
198
199 /**
200 * Set the InitialContex class environment properties.
201 *
202 * @jmx:managed-attribute
203 */
204 public void setProperties(final Properties props) throws IOException
205 {
206 contextInfo.setProperties(props);
207 }
208
209 /**
210 * Get the InitialContex class environment properties.
211 *
212 * @jmx:managed-attribute
213 */
214 public Properties getProperties() throws IOException
215 {
216 return contextInfo.getProperties();
217 }
218
219 /**
220 * Start the service by binding the external context into the
221 * JBoss InitialContext.
222 */
223 protected void startService() throws Exception
224 {
225 rebind();
226 }
227
228 /**
229 * Stop the service by unbinding the external context into the
230 * JBoss InitialContext.
231 */
232 protected void stopService() throws Exception
233 {
234 if( contextInfo.getCacheContext() )
235 unbind(contextInfo.getJndiName());
236 }
237
238 private static Context createContext(Context rootContext, Name name) throws NamingException
239 {
240 Context subctx = rootContext;
241 for(int n = 0; n < name.size(); n ++)
242 {
243 String atom = name.get(n);
244 try
245 {
246 Object obj = subctx.lookup(atom);
247 subctx = (Context) obj;
248 }
249 catch(NamingException e)
250 {
251 // No binding exists, create a subcontext
252 subctx = subctx.createSubcontext(atom);
253 }
254 }
255
256 return subctx;
257 }
258
259 private void rebind() throws Exception
260 {
261 Context ctx = contextInfo.newContext();
262 Context rootCtx = (Context) new InitialContext();
263
264 log.debug("ctx="+ctx+", env="+ctx.getEnvironment());
265
266 // Get the parent context into which we are to bind
267 String jndiName = contextInfo.getJndiName();
268 Name fullName = rootCtx.getNameParser("").parse(jndiName);
269
270 log.debug("fullName="+fullName);
271
272 Name parentName = fullName;
273 if( fullName.size() > 1 )
274 parentName = fullName.getPrefix(fullName.size()-1);
275 else
276 parentName = new CompositeName();
277
278 log.debug("parentName="+parentName);
279
280 Context parentCtx = createContext(rootCtx, parentName);
281
282 log.debug("parentCtx="+parentCtx);
283
284 Name atomName = fullName.getSuffix(fullName.size()-1);
285 String atom = atomName.get(0);
286 boolean cacheContext = contextInfo.getCacheContext();
287
288 if( remoteAccess == true )
289 {
290 // Bind contextInfo as a Referenceable
291 parentCtx.rebind(atom, contextInfo);
292
293 // Cache the context using NonSerializableFactory to avoid creating
294 // more than one context for in VM lookups
295 if( cacheContext == true )
296 {
297 // If cacheContext is true we need to wrap the Context in a
298 // proxy that allows the user to issue close on the lookup
299 // Context without closing the inmemory Context.
300 ctx = CachedContext.createProxyContext(ctx);
301 NonSerializableFactory.rebind(jndiName, ctx);
302 }
303 }
304 else if( cacheContext == true )
305 {
306 // Bind a reference to the extern context using
307 // NonSerializableFactory as the ObjectFactory. The Context must
308 // be wrapped in a proxy that allows the user to issue close on the
309 // lookup Context without closing the inmemory Context.
310
311 Context proxyCtx = CachedContext.createProxyContext(ctx);
312 NonSerializableFactory.rebind(rootCtx, jndiName, proxyCtx);
313 }
314 else
315 {
316 // Bind the contextInfo so that each lookup results in the creation
317 // of a new Context object. The returned Context must be closed
318 // by the user to prevent resource leaks.
319
320 parentCtx.rebind(atom, contextInfo);
321 }
322 }
323
324 private void unbind(String jndiName)
325 {
326 try
327 {
328 Context rootCtx = new InitialContext();
329 Context ctx = (Context) rootCtx.lookup(jndiName);
330 if( ctx != null )
331 ctx.close();
332 rootCtx.unbind(jndiName);
333 NonSerializableFactory.unbind(jndiName);
334 }
335 catch(NamingException e)
336 {
337 log.error("unbind failed", e);
338 }
339 }
340
341 /**
342 * The external InitialContext information class. It acts as the
343 * RefAddr and ObjectFactory for the external IntialContext and can
344 * be marshalled to a remote client.
345 */
346 public static class SerializableInitialContext
347 extends RefAddr
348 implements Referenceable, Serializable, ObjectFactory
349 {
350 private static final long serialVersionUID = -6512260531255770463L;
351 private String jndiName;
352 private Class contextClass = javax.naming.InitialContext.class;
353 private Properties contextProps;
354 private boolean cacheContext = true;
355 private transient Context initialContext;
356
357 public SerializableInitialContext()
358 {
359 this("SerializableInitialContext");
360 }
361
362 public SerializableInitialContext(String addrType)
363 {
364 super(addrType);
365 }
366
367 public String getJndiName()
368 {
369 return jndiName;
370 }
371
372 public void setJndiName(final String jndiName)
373 {
374 this.jndiName = jndiName;
375 }
376
377 public boolean getCacheContext()
378 {
379 return cacheContext;
380 }
381
382 public void setCacheContext(final boolean cacheContext)
383 {
384 this.cacheContext = cacheContext;
385 }
386
387 public String getInitialContext()
388 {
389 return contextClass.getName();
390 }
391
392 public void loadClass(String className) throws ClassNotFoundException
393 {
394 ClassLoader loader = Thread.currentThread().getContextClassLoader();
395 contextClass = loader.loadClass(className);
396 }
397
398 public void setProperties(final Properties props)
399 {
400 contextProps = props;
401 }
402
403 public Properties getProperties()
404 {
405 return contextProps;
406 }
407
408 public void loadProperties(String contextPropsURL) throws IOException
409 {
410 InputStream is = null;
411 contextProps = new Properties();
412
413 // See if this is a URL we can load
414 try
415 {
416 URL url = new URL(contextPropsURL);
417 is = url.openStream();
418 contextProps.load(is);
419 return;
420 }
421 catch (IOException e)
422 { // Failed, try to locate a classpath resource below
423 is = null;
424 }
425
426 is = Thread.currentThread().getContextClassLoader().getResourceAsStream(contextPropsURL);
427 if( is == null )
428 {
429 throw new IOException("Failed to locate context props as URL or resource:"+contextPropsURL);
430 }
431 contextProps.load(is);
432 }
433
434 Context newContext() throws Exception
435 {
436 // First check the NonSerializableFactory cache
437 initialContext = (Context) NonSerializableFactory.lookup(jndiName);
438 // Create the context from the contextClass and contextProps
439 if( initialContext == null )
440 initialContext = newContext(contextClass, contextProps);
441 return initialContext;
442 }
443
444 static Context newContext(Class contextClass, Properties contextProps)
445 throws Exception
446 {
447 Context ctx = null;
448 try
449 {
450 ctx = newDefaultContext(contextClass, contextProps);
451 }
452 catch(NoSuchMethodException e)
453 {
454 ctx = newLdapContext(contextClass, contextProps);
455 }
456 return ctx;
457 }
458
459 private static Context newDefaultContext(Class contextClass, Properties contextProps)
460 throws Exception
461 {
462 Context ctx = null;
463 Class[] types = {Hashtable.class};
464 Constructor ctor = contextClass.getConstructor(types);
465 Object[] args = {contextProps};
466 ctx = (Context) ctor.newInstance(args);
467 return ctx;
468 }
469
470 private static Context newLdapContext(Class contextClass, Properties contextProps)
471 throws Exception
472 {
473 Context ctx = null;
474 Class[] types = {Hashtable.class, Control[].class};
475 Constructor ctor = contextClass.getConstructor(types);
476 Object[] args = {contextProps, null};
477 ctx = (Context) ctor.newInstance(args);
478 return ctx;
479 }
480
481 public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment)
482 throws Exception
483 {
484 Reference ref = (Reference) obj;
485 SerializableInitialContext sic = (SerializableInitialContext) ref.get(0);
486 return sic.newContext();
487 }
488
489 public Reference getReference() throws NamingException
490 {
491 Reference ref = new Reference(Context.class.getName(), this, this.getClass().getName(), null);
492 return ref;
493 }
494
495 public Object getContent()
496 {
497 return null;
498 }
499 }
500
501 /**
502 * A proxy implementation of Context that simply intercepts the
503 * close() method and ignores it since the underlying Context
504 * object is being maintained in memory.
505 */
506 static class CachedContext implements InvocationHandler
507 {
508 Context externalCtx;
509
510 CachedContext(Context externalCtx)
511 {
512 this.externalCtx = externalCtx;
513 }
514
515 static Context createProxyContext(Context ctx)
516 {
517 ClassLoader loader = Thread.currentThread().getContextClassLoader();
518 Class[] interfaces = Classes.getAllUniqueInterfaces(ctx.getClass());
519 InvocationHandler handler = new CachedContext(ctx);
520 Context proxyCtx = (Context) Proxy.newProxyInstance(loader, interfaces, handler);
521 return proxyCtx;
522 }
523
524 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
525 {
526 Object value = null;
527 if( method.getName().equals("close") )
528 {
529 // We just ignore the close method
530 }
531 else
532 {
533 try
534 {
535 value = method.invoke(externalCtx, args);
536 }
537 catch(InvocationTargetException e)
538 {
539 throw e.getTargetException();
540 }
541 }
542 return value;
543 }
544 }
545 }