001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.naming;
023:
024: import java.io.IOException;
025: import java.io.InputStream;
026: import java.io.Serializable;
027:
028: import java.lang.reflect.Constructor;
029: import java.lang.reflect.InvocationHandler;
030: import java.lang.reflect.InvocationTargetException;
031: import java.lang.reflect.Method;
032: import java.lang.reflect.Proxy;
033:
034: import java.net.URL;
035: import java.util.ArrayList;
036: import java.util.Hashtable;
037: import java.util.Properties;
038:
039: import javax.naming.CompositeName;
040: import javax.naming.Context;
041: import javax.naming.InitialContext;
042: import javax.naming.Name;
043: import javax.naming.NamingException;
044: import javax.naming.RefAddr;
045: import javax.naming.Reference;
046: import javax.naming.Referenceable;
047: import javax.naming.ldap.Control;
048: import javax.naming.spi.ObjectFactory;
049:
050: import org.jboss.system.ServiceMBeanSupport;
051: import org.jboss.util.Classes;
052:
053: /**
054: * A MBean that binds an arbitrary InitialContext into the JBoss default
055: * InitialContext as a Reference. If RemoteAccess is enabled, the reference
056: * is a Serializable object that is capable of creating the InitialContext
057: * remotely. If RemoteAccess if false, the reference is to a nonserializable object
058: * that can only be used from within this VM.
059: *
060: * @jmx:mbean extends="org.jboss.system.ServiceMBean"
061: *
062: * @see org.jboss.naming.NonSerializableFactory
063: *
064: * @version <tt>$Revision: 60428 $</tt>
065: * @author Scott.Stark@jboss.org
066: * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
067: */
068: public class ExternalContext extends ServiceMBeanSupport implements
069: ExternalContextMBean {
070: private boolean remoteAccess;
071: private SerializableInitialContext contextInfo = new SerializableInitialContext();
072:
073: /**
074: * No-args constructor for JMX.
075: */
076: public ExternalContext() {
077: super ();
078: }
079:
080: public ExternalContext(String jndiName, String contextPropsURL)
081: throws IOException, NamingException {
082: setJndiName(jndiName);
083: setPropertiesURL(contextPropsURL);
084: }
085:
086: /**
087: * Set the jndi name under which the external context is bound.
088: *
089: * @jmx:managed-attribute
090: */
091: public String getJndiName() {
092: return contextInfo.getJndiName();
093: }
094:
095: /**
096: * Set the jndi name under which the external context is bound.
097: *
098: * @jmx:managed-attribute
099: */
100: public void setJndiName(String jndiName) throws NamingException {
101: contextInfo.setJndiName(jndiName);
102: if (super .getState() == STARTED) {
103: unbind(jndiName);
104: try {
105: rebind();
106: } catch (Exception e) {
107: NamingException ne = new NamingException(
108: "Failed to update jndiName");
109: ne.setRootCause(e);
110: throw ne;
111: }
112: }
113: }
114:
115: /**
116: * @jmx:managed-attribute
117: */
118: public boolean getRemoteAccess() {
119: return remoteAccess;
120: }
121:
122: /**
123: * @jmx:managed-attribute
124: */
125: public void setRemoteAccess(final boolean remoteAccess) {
126: this .remoteAccess = remoteAccess;
127: }
128:
129: /**
130: * @jmx:managed-attribute
131: */
132: public boolean getCacheContext() {
133: return contextInfo.getCacheContext();
134: }
135:
136: /**
137: * @jmx:managed-attribute
138: */
139: public void setCacheContext(boolean cacheContext) {
140: contextInfo.setCacheContext(cacheContext);
141: }
142:
143: /**
144: * Get the class name of the InitialContext implementation to
145: * use. Should be one of:
146: * <ul>
147: * <li>javax.naming.InitialContext
148: * <li>javax.naming.directory.InitialDirContext
149: * <li>javax.naming.ldap.InitialLdapContext
150: * </ul>
151: *
152: * @jmx:managed-attribute
153: *
154: * @return the classname of the InitialContext to use
155: */
156: public String getInitialContext() {
157: return contextInfo.getInitialContext();
158: }
159:
160: /**
161: * Set the class name of the InitialContext implementation to
162: * use. Should be one of:
163: * <ul>
164: * <li>javax.naming.InitialContext
165: * <li>javax.naming.directory.InitialDirContext
166: * <li>javax.naming.ldap.InitialLdapContext
167: * </ul>
168: *
169: * @jmx:managed-attribute
170: *
171: * @param contextClass, the classname of the InitialContext to use
172: */
173: public void setInitialContext(String className)
174: throws ClassNotFoundException {
175: contextInfo.loadClass(className);
176: }
177:
178: /**
179: * Set the InitialContex class environment properties from the given URL.
180: *
181: * @jmx:managed-attribute
182: */
183: public void setPropertiesURL(String contextPropsURL)
184: throws IOException {
185: contextInfo.loadProperties(contextPropsURL);
186: }
187:
188: /**
189: * Set the InitialContex class environment properties.
190: *
191: * @jmx:managed-attribute
192: */
193: public void setProperties(final Properties props)
194: throws IOException {
195: contextInfo.setProperties(props);
196: }
197:
198: /**
199: * Get the InitialContex class environment properties.
200: *
201: * @jmx:managed-attribute
202: */
203: public Properties getProperties() throws IOException {
204: return contextInfo.getProperties();
205: }
206:
207: /**
208: * Start the service by binding the external context into the
209: * JBoss InitialContext.
210: */
211: protected void startService() throws Exception {
212: rebind();
213: }
214:
215: /**
216: * Stop the service by unbinding the external context into the
217: * JBoss InitialContext.
218: */
219: protected void stopService() throws Exception {
220: if (contextInfo.getCacheContext())
221: unbind(contextInfo.getJndiName());
222: }
223:
224: private static Context createContext(Context rootContext, Name name)
225: throws NamingException {
226: Context subctx = rootContext;
227: for (int n = 0; n < name.size(); n++) {
228: String atom = name.get(n);
229: try {
230: Object obj = subctx.lookup(atom);
231: subctx = (Context) obj;
232: } catch (NamingException e) {
233: // No binding exists, create a subcontext
234: subctx = subctx.createSubcontext(atom);
235: }
236: }
237:
238: return subctx;
239: }
240:
241: private void rebind() throws Exception {
242: Context ctx = contextInfo.newContext();
243: Context rootCtx = (Context) new InitialContext();
244: log.debug("ctx=" + ctx + ", env=" + ctx.getEnvironment());
245:
246: // Get the parent context into which we are to bind
247: String jndiName = contextInfo.getJndiName();
248: Name fullName = rootCtx.getNameParser("").parse(jndiName);
249: log.debug("fullName=" + fullName);
250:
251: Name parentName = fullName;
252: if (fullName.size() > 1)
253: parentName = fullName.getPrefix(fullName.size() - 1);
254: else
255: parentName = new CompositeName();
256:
257: log.debug("parentName=" + parentName);
258:
259: Context parentCtx = createContext(rootCtx, parentName);
260: log.debug("parentCtx=" + parentCtx);
261:
262: Name atomName = fullName.getSuffix(fullName.size() - 1);
263: String atom = atomName.get(0);
264: boolean cacheContext = contextInfo.getCacheContext();
265:
266: if (remoteAccess == true) {
267: // Bind contextInfo as a Referenceable
268: parentCtx.rebind(atom, contextInfo);
269:
270: // Cache the context using NonSerializableFactory to avoid creating
271: // more than one context for in VM lookups
272: if (cacheContext == true) {
273: // If cacheContext is true we need to wrap the Context in a
274: // proxy that allows the user to issue close on the lookup
275: // Context without closing the inmemory Context.
276: ctx = CachedContext.createProxyContext(ctx);
277: NonSerializableFactory.rebind(jndiName, ctx);
278: }
279: } else if (cacheContext == true) {
280: // Bind a reference to the extern context using
281: // NonSerializableFactory as the ObjectFactory. The Context must
282: // be wrapped in a proxy that allows the user to issue close on the
283: // lookup Context without closing the inmemory Context.
284:
285: Context proxyCtx = CachedContext.createProxyContext(ctx);
286: NonSerializableFactory.rebind(rootCtx, jndiName, proxyCtx);
287: } else {
288: // Bind the contextInfo so that each lookup results in the creation
289: // of a new Context object. The returned Context must be closed
290: // by the user to prevent resource leaks.
291:
292: parentCtx.rebind(atom, contextInfo);
293: }
294: }
295:
296: private void unbind(String jndiName) {
297: try {
298: Context rootCtx = new InitialContext();
299: Context ctx = (Context) rootCtx.lookup(jndiName);
300: if (ctx != null)
301: ctx.close();
302: rootCtx.unbind(jndiName);
303: NonSerializableFactory.unbind(jndiName);
304: } catch (NamingException e) {
305: log.error("unbind failed", e);
306: }
307: }
308:
309: /**
310: * The external InitialContext information class. It acts as the
311: * RefAddr and ObjectFactory for the external IntialContext and can
312: * be marshalled to a remote client.
313: */
314: public static class SerializableInitialContext extends RefAddr
315: implements Referenceable, Serializable, ObjectFactory {
316: private static final long serialVersionUID = -6512260531255770463L;
317: private String jndiName;
318: private Class contextClass = javax.naming.InitialContext.class;
319: private Properties contextProps;
320: private boolean cacheContext = true;
321: private transient Context initialContext;
322:
323: public SerializableInitialContext() {
324: this ("SerializableInitialContext");
325: }
326:
327: public SerializableInitialContext(String addrType) {
328: super (addrType);
329: }
330:
331: public String getJndiName() {
332: return jndiName;
333: }
334:
335: public void setJndiName(final String jndiName) {
336: this .jndiName = jndiName;
337: }
338:
339: public boolean getCacheContext() {
340: return cacheContext;
341: }
342:
343: public void setCacheContext(final boolean cacheContext) {
344: this .cacheContext = cacheContext;
345: }
346:
347: public String getInitialContext() {
348: return contextClass.getName();
349: }
350:
351: public void loadClass(String className)
352: throws ClassNotFoundException {
353: ClassLoader loader = Thread.currentThread()
354: .getContextClassLoader();
355: contextClass = loader.loadClass(className);
356: }
357:
358: public void setProperties(final Properties props) {
359: contextProps = props;
360: }
361:
362: public Properties getProperties() {
363: return contextProps;
364: }
365:
366: public void loadProperties(String contextPropsURL)
367: throws IOException {
368: InputStream is = null;
369: contextProps = new Properties();
370:
371: // See if this is a URL we can load
372: try {
373: URL url = new URL(contextPropsURL);
374: is = url.openStream();
375: contextProps.load(is);
376: return;
377: } catch (IOException e) { // Failed, try to locate a classpath resource below
378: is = null;
379: }
380:
381: is = Thread.currentThread().getContextClassLoader()
382: .getResourceAsStream(contextPropsURL);
383: if (is == null) {
384: throw new IOException(
385: "Failed to locate context props as URL or resource:"
386: + contextPropsURL);
387: }
388: contextProps.load(is);
389: }
390:
391: Context newContext() throws Exception {
392: // First check the NonSerializableFactory cache
393: initialContext = (Context) NonSerializableFactory
394: .lookup(jndiName);
395: // Create the context from the contextClass and contextProps
396: if (initialContext == null)
397: initialContext = newContext(contextClass, contextProps);
398: return initialContext;
399: }
400:
401: static Context newContext(Class contextClass,
402: Properties contextProps) throws Exception {
403: Context ctx = null;
404: try {
405: ctx = newDefaultContext(contextClass, contextProps);
406: } catch (NoSuchMethodException e) {
407: ctx = newLdapContext(contextClass, contextProps);
408: }
409: return ctx;
410: }
411:
412: private static Context newDefaultContext(Class contextClass,
413: Properties contextProps) throws Exception {
414: Context ctx = null;
415: Class[] types = { Hashtable.class };
416: Constructor ctor = contextClass.getConstructor(types);
417: Object[] args = { contextProps };
418: ctx = (Context) ctor.newInstance(args);
419: return ctx;
420: }
421:
422: private static Context newLdapContext(Class contextClass,
423: Properties contextProps) throws Exception {
424: Context ctx = null;
425: Class[] types = { Hashtable.class, Control[].class };
426: Constructor ctor = contextClass.getConstructor(types);
427: Object[] args = { contextProps, null };
428: ctx = (Context) ctor.newInstance(args);
429: return ctx;
430: }
431:
432: public Object getObjectInstance(Object obj, Name name,
433: Context nameCtx, Hashtable environment)
434: throws Exception {
435: Reference ref = (Reference) obj;
436: SerializableInitialContext sic = (SerializableInitialContext) ref
437: .get(0);
438: return sic.newContext();
439: }
440:
441: public Reference getReference() throws NamingException {
442: Reference ref = new Reference(Context.class.getName(),
443: this , this .getClass().getName(), null);
444: return ref;
445: }
446:
447: public Object getContent() {
448: return null;
449: }
450: }
451:
452: /**
453: * A proxy implementation of Context that simply intercepts the
454: * close() method and ignores it since the underlying Context
455: * object is being maintained in memory.
456: */
457: static class CachedContext implements InvocationHandler {
458: Context externalCtx;
459:
460: CachedContext(Context externalCtx) {
461: this .externalCtx = externalCtx;
462: }
463:
464: static Context createProxyContext(Context ctx) {
465: ClassLoader loader = Thread.currentThread()
466: .getContextClassLoader();
467: ArrayList<Class> ifaces = new ArrayList<Class>();
468: Classes.getAllInterfaces(ifaces, ctx.getClass());
469: Class[] interfaces = new Class[ifaces.size()];
470: ifaces.toArray(interfaces);
471: InvocationHandler handler = new CachedContext(ctx);
472: Context proxyCtx = (Context) Proxy.newProxyInstance(loader,
473: interfaces, handler);
474: return proxyCtx;
475: }
476:
477: public Object invoke(Object proxy, Method method, Object[] args)
478: throws Throwable {
479: Object value = null;
480: if (method.getName().equals("close")) {
481: // We just ignore the close method
482: } else {
483: try {
484: value = method.invoke(externalCtx, args);
485: } catch (InvocationTargetException e) {
486: throw e.getTargetException();
487: }
488: }
489: return value;
490: }
491: }
492: }
|