001: /* Copyright 2001 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.jndi;
007:
008: import java.io.Serializable;
009: import java.util.Enumeration;
010: import java.util.Hashtable;
011:
012: import javax.naming.CompositeName;
013: import javax.naming.Context;
014: import javax.naming.InitialContext;
015: import javax.naming.NameAlreadyBoundException;
016: import javax.naming.NamingEnumeration;
017: import javax.naming.NamingException;
018: import javax.servlet.http.HttpSession;
019: import javax.servlet.http.HttpSessionBindingEvent;
020: import javax.servlet.http.HttpSessionBindingListener;
021:
022: import org.jasig.portal.PortalException;
023: import org.jasig.portal.services.ExternalServices;
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026: import org.w3c.dom.Document;
027: import org.w3c.dom.Node;
028: import org.w3c.dom.NodeList;
029:
030: /**
031: * JNDIManager.
032: *
033: * uPortal's JNDI tree has the following basic structure:
034: * <tt>
035: * root context
036: * |
037: * +--services--*[service name]*...
038: * |
039: * +--users--*[userID]*
040: * | |
041: * | +--layouts--*[layoutId]*
042: * + | |
043: * sessions | +--channel-ids
044: * | | | |
045: * *[sessionId]* | | +--*[fname]*--[chanId]
046: * | |
047: * | +--sessions--*[sessionId]*
048: * |
049: * |
050: * +--sessions--*[sessionId]*
051: * |
052: * +--channel-obj--*[chanId]*...
053: * |
054: * +--[layoutId]
055: * </tt>
056: * Notation:
057: * [something] referes to a value of something
058: * *[something]* refers to a set of values
059: * ... refers to a subcontext
060: *
061: *
062: * @author Bernie Durfee, bdurfee@interactivebusiness.com
063: * @author Peter Kharchenko, pkharchenko@interactivebusiness.com
064: * @version $Revision: 36552 $
065: */
066: public class JNDIManager {
067:
068: private static final Log log = LogFactory.getLog(JNDIManager.class);
069:
070: /**
071: * Empty constructor.
072: */
073: public JNDIManager() {
074: }
075:
076: /**
077: * Initializes root context node
078: */
079: public static void initializePortalContext() throws PortalException {
080: try {
081: Context context = getContext();
082:
083: // Create a subcontext for portal-wide services, initialize services
084: // Start any portal services configured in services.xml
085: ExternalServices.startServices(context
086: .createSubcontext("services"));
087:
088: /*
089: // Note: this should be moved into a common service init
090: // Bind in the logger service
091: LogService logger = LogService.instance();
092: context.bind("/services/logger", logger);
093: */
094:
095: // Create a subcontext for user specific bindings
096: context.createSubcontext("users");
097:
098: // Create a subcontext for session listings
099: context.createSubcontext("sessions");
100:
101: log
102: .debug("JNDIManager::initializePortalContext() : initialized portal JNDI context");
103:
104: } catch (Exception e) {
105: log.error("Error initializing Portal context", e);
106: }
107: }
108:
109: /**
110: * Create and populate contexts for a new user sesions
111: * @param session
112: * @param userId
113: * @param layoutId
114: * @param userLayout
115: */
116: public static void initializeSessionContext(HttpSession session,
117: String userId, String layoutId, Document userLayout)
118: throws PortalException {
119:
120: Context topContext = null;
121:
122: // get initial context
123: try {
124: topContext = (Context) getContext();
125: } catch (NamingException ne) {
126: log
127: .error(
128: "JNDIManager.initializeSessionContext(): Unable to obtain initial context",
129: ne);
130: return;
131: }
132:
133: // bind userId to /sessions context
134: try {
135: Context tsessionContext = (Context) topContext
136: .lookup("/sessions");
137: try {
138: tsessionContext.bind(session.getId(), userId);
139: } catch (NameAlreadyBoundException nabe) {
140: tsessionContext.rebind(session.getId(), userId);
141: }
142: } catch (NamingException ne) {
143: log
144: .error(
145: "JNDIManager.initializeSessionContext(): Unable to obtain /sessions context",
146: ne);
147: }
148:
149: // bind listener
150: session.setAttribute("JNDISessionListener",
151: new JNDISessionListener());
152:
153: // Get the ID of the session
154: String sessionId = session.getId();
155: Context usersContext = null;
156: try {
157: // get /users context
158: usersContext = (Context) topContext.lookup("/users");
159: } catch (NamingException ne) {
160: log
161: .error(
162: "JNDIManager.initializeSessionContext(): Could not find /users context",
163: ne);
164: throw new PortalException(
165: "JNDIManager.initializeSessionContext(): Could not find /users context",
166: ne);
167: }
168:
169: // get or create /users/[userId] context
170: Context userIdContext = null;
171: Context sessionsContext = null;
172: Context layoutsContext = null;
173: try {
174: userIdContext = (Context) usersContext.lookup(userId);
175:
176: // lookup layouts and sessions contexts
177: try {
178: layoutsContext = (Context) userIdContext
179: .lookup("layouts");
180: } catch (NamingException ne) {
181: log
182: .error("JNDIManager.initializeSessionContext(): /users/"
183: + userId
184: + "/layouts - did not exist, even though /users/"
185: + userId + " context did!");
186: layoutsContext = userIdContext
187: .createSubcontext("layouts");
188: }
189:
190: try {
191: sessionsContext = (Context) userIdContext
192: .lookup("sessions");
193: } catch (NamingException ne) {
194: log
195: .error("JNDIManager.initializeSessionContext(): context /users/"
196: + userId
197: + "/sessions - did not exist, even though /users/"
198: + userId + " context did!");
199: sessionsContext = userIdContext
200: .createSubcontext("sessions");
201: }
202:
203: } catch (NamingException ne) {
204: // new user
205: try {
206: userIdContext = usersContext.createSubcontext(userId);
207: // create layouts and sessions context
208: layoutsContext = userIdContext
209: .createSubcontext("layouts");
210: sessionsContext = userIdContext
211: .createSubcontext("sessions");
212: if (log.isDebugEnabled())
213: log
214: .debug("JNDIManager.initializeSessionContext(): "
215: + "initialized context for a userId=\""
216: + userId + "\".");
217: } catch (NamingException ne2) {
218: log
219: .error(
220: "JNDIManager.initializeSessionContext(): exception encountered while trying to create /users/"
221: + userId
222: + " and layouts/sessions contexts !",
223: ne2);
224: throw new PortalException(
225: "JNDIManager.initializeSessionContext(): exception encountered while trying to create /users/"
226: + userId
227: + " and layouts/sessions contexts !",
228: ne2);
229: }
230: }
231:
232: // bind sessions/[sessionId] context
233: Context sessionIdContext = null;
234: try {
235: sessionIdContext = sessionsContext
236: .createSubcontext(sessionId);
237: } catch (NameAlreadyBoundException nabe) {
238: log
239: .error("JNDIManager.initializeSessionContext(): trying to initialize session twice. sessionId=\""
240: + sessionId + "\"");
241: // sessionIdContext=(Context)sessionsContext.lookup(sessionId);
242: throw new PortalException(
243: "JNDIManager.initializeSessionContext(): trying to initialize session twice. sessionId=\""
244: + sessionId + "\"", nabe);
245: } catch (Exception e) {
246: log
247: .error(
248: "JNDIManager.initializeSessionContext(): error encountered while trying to create context /users/"
249: + userId + "/sessions/" + sessionId,
250: e);
251: throw new PortalException(
252: "JNDIManager.initializeSessionContext(): error encountered while trying to create context /users/"
253: + userId + "/sessions/" + sessionId, e);
254: }
255:
256: // bind layoutId
257: try {
258: sessionIdContext.bind("layoutId", layoutId);
259: } catch (Exception e) {
260: log
261: .error(
262: "JNDIManager.initializeSessionContext(): error encountered while trying to bind /users/"
263: + userId
264: + "/sessions/"
265: + sessionId
266: + "/layoutId", e);
267: throw new PortalException(
268: "JNDIManager.initializeSessionContext(): error encountered while trying to bind /users/"
269: + userId
270: + "/sessions/"
271: + sessionId
272: + "/layoutId", e);
273: }
274:
275: // make sure channel-obj context exists
276: try {
277: sessionIdContext.createSubcontext("channel-obj");
278: } catch (NameAlreadyBoundException nabe) {
279: // ignore
280: } catch (Exception e) {
281: log.error(e, e);
282: }
283:
284: try {
285: // check if the layout id binding already exists
286: try {
287: Context layoutIdContext = (Context) layoutsContext
288: .lookup(layoutId);
289: // assume layouts/[layoutId]/ has already been populated
290:
291: // bind layouts/[layoutId]/sessions/[sessionId]
292: try {
293: Context lsessionsContext = (Context) userIdContext
294: .lookup("layouts/" + layoutId + "/sessions");
295: lsessionsContext.createSubcontext(sessionId);
296:
297: if (log.isDebugEnabled())
298: log
299: .debug("JNDIManager.initializeSessionContext(): "
300: + "created /users/"
301: + userId
302: + "/layouts/"
303: + layoutId
304: + "/sessions/" + sessionId);
305:
306: } catch (Exception e) {
307: log
308: .error(
309: "JNDIManager.initializeSessionContext(): exception occured while looking up context /users/"
310: + userId
311: + "/layouts/"
312: + layoutId
313: + "/sessions , although /users/"
314: + userId
315: + "/layouts context already existed !",
316: e);
317: throw new PortalException(
318: "JNDIManager.initializeSessionContext(): exception occured while looking up context /users/"
319: + userId
320: + "/layouts/"
321: + layoutId
322: + "/sessions , although /users/"
323: + userId
324: + "/layouts context already existed !",
325: e);
326: }
327: } catch (NamingException nne) {
328: // given layout id has not been registered yet
329: Context layoutIdContext = layoutsContext
330: .createSubcontext(layoutId);
331:
332: // bind layouts/[layoutId]/sessions/[sessionId] context
333: Context lsessionsContext = layoutIdContext
334: .createSubcontext("sessions");
335: lsessionsContext.createSubcontext(sessionId);
336:
337: if (log.isDebugEnabled())
338: log
339: .debug("JNDIManager.initializeSessionContext(): "
340: + "created context /users/"
341: + userId + "/layouts/" + layoutId);
342:
343: try {
344: Context channel_idsContext = (Context) layoutIdContext
345: .createSubcontext("channel-ids");
346: // Get the list of channels in the user's layout
347: NodeList channelNodes = userLayout
348: .getElementsByTagName("channel");
349: Node fname = null;
350: Node instanceid = null;
351: // Parse through the channels and populate the JNDI
352: for (int i = 0; i < channelNodes.getLength(); i++) {
353: // Attempt to get the fname and instance ID from the channel
354: fname = channelNodes.item(i).getAttributes()
355: .getNamedItem("fname");
356: instanceid = channelNodes.item(i)
357: .getAttributes().getNamedItem("ID");
358: if (fname != null && instanceid != null) {
359: //System.out.println("fname found -> " + fname);
360: // Create a new composite name from the fname
361: CompositeName cname = new CompositeName(
362: fname.getNodeValue());
363: // Get a list of the name components
364: Enumeration e = cname.getAll();
365: // Get the root of the context
366: Context nextContext = channel_idsContext;
367: // Add all of the subcontexts in the fname
368: String subContextName = new String();
369: while (e.hasMoreElements()) {
370: subContextName = (String) e
371: .nextElement();
372: if (e.hasMoreElements()) {
373: // Bind a new sub context if the current name component is not the leaf
374: nextContext = nextContext
375: .createSubcontext(subContextName);
376: } else {
377: //System.out.println("Binding " + instanceid.getNodeValue() + " to " + nextContext.getNameInNamespace() + "/" + subContextName);
378: if (log.isDebugEnabled())
379: log
380: .debug("JNDIManager.initializeSessionContext(): "
381: + "bound "
382: + instanceid
383: .getNodeValue()
384: + " to "
385: + nextContext
386: .getNameInNamespace()
387: + "/"
388: + subContextName);
389:
390: nextContext.rebind(subContextName,
391: instanceid.getNodeValue());
392: }
393: }
394: }
395: }
396: } catch (NamingException ne) {
397: log
398: .error(
399: "JNDIManager.initializeSessionContext(): exception occured while creating cahnnel-ids context.",
400: ne);
401: throw new PortalException(
402: "JNDIManager.initializeSessionContext(): exception occured while creating cahnnel-ids context.",
403: ne);
404: }
405: }
406: } catch (Exception e) {
407: log
408: .error(
409: "JNDIManager.initializeSessionContext(): exception occured while pupulating context /users/"
410: + userId + "/layouts/" + layoutId,
411: e);
412: throw new PortalException(
413: "JNDIManager.initializeSessionContext(): exception occured while pupulating context /users/"
414: + userId + "/layouts/" + layoutId, e);
415: }
416: }
417:
418: /**
419: * Get the uPortal JNDI context
420: * @return uPortal JNDI context
421: * @exception NamingException
422: */
423: private static Context getContext() throws NamingException {
424: Hashtable environment = new Hashtable(5);
425: // Set up the path
426: environment.put(Context.INITIAL_CONTEXT_FACTORY,
427: "org.jasig.portal.jndi.PortalInitialContextFactory");
428: Context ctx = new InitialContext(environment);
429: return (ctx);
430: }
431:
432: /**
433: * This class will be bound to the user's session when they log in. When the user's session is expired this
434: * object should be unbound and will clean up all user specific objects in JNDI. Note: It's possible that
435: * not all servlet containers properly unbind objects from the session when it expires!
436: */
437: private static class JNDISessionListener implements
438: HttpSessionBindingListener, Serializable {
439:
440: public void valueBound(HttpSessionBindingEvent bindingEvent) {
441: log.info("JNDISessionListener bound for: "
442: + bindingEvent.getSession().getId());
443: }
444:
445: /**
446: * This method is called when the JNDISessionListener is unbound from a user's session. This should
447: * only happen when the users session is either destroyed or expires. Note: This method may need synchronization!
448: * If a user logs in and out quickly there may be problems with things not happening in the correct order.
449: * @param bindingEvent
450: */
451: public void valueUnbound(HttpSessionBindingEvent bindingEvent) {
452: log.info("JNDISessionListener unbound for: "
453: + bindingEvent.getSession().getId());
454: Context context = null;
455: try {
456: // Get the portal JNDI context
457: context = getContext();
458: } catch (NamingException ne) {
459: log
460: .error(
461: "JNDISessionListener.valueUnbound(): Could not get portal context",
462: ne);
463: return;
464: }
465:
466: Context usersContext = null;
467: try {
468: // get users context
469: usersContext = (Context) context.lookup("/users");
470: } catch (NamingException ne) {
471: log
472: .error(
473: "JNDISessionListener.valueUnbound(): Could not get /users context",
474: ne);
475: return;
476: }
477: if (usersContext == null) {
478: return;
479: }
480:
481: String sessionId = bindingEvent.getSession().getId();
482:
483: // obtain /sessions context
484: Context tsessionsContext = null;
485: try {
486: tsessionsContext = (Context) context
487: .lookup("/sessions");
488: } catch (NamingException ne) {
489: log
490: .error(
491: "JNDISessionListener.valueUnbound(): Could not get /sessions context",
492: ne);
493: return;
494: }
495:
496: String userId = null;
497: // obtain userId by looking at /sessions bindings
498: try {
499: userId = (String) tsessionsContext.lookup(sessionId);
500: } catch (NamingException ne) {
501: log
502: .error(
503: "JNDISessionListener.valueUnbound(): Session "
504: + sessionId
505: + " is not registered under /sessions context !",
506: ne);
507: return;
508: }
509: if (userId == null) {
510: // could do a /users/[userId]/sessions/* traversal here instead
511: log
512: .error("JNDISessionListener.valueUnbound(): Unable to determine userId for a session "
513: + sessionId
514: + " ... giving up on JNDI cleanup.");
515: return;
516: }
517:
518: // unbind userId binding in /sessions
519: try {
520: tsessionsContext.unbind(sessionId);
521: } catch (NamingException ne) {
522: log.error(
523: "JNDISessionListener.valueUnbound(): Problems unbinding /sessions/"
524: + sessionId, ne);
525: }
526:
527: Context userIdContext = null;
528: try {
529: userIdContext = (Context) usersContext.lookup(userId);
530: } catch (NamingException ne) {
531: log
532: .error("JNDISessionListener.valueUnbound(): context /users/"
533: + userId + " doesn't exist!");
534: return;
535: }
536:
537: Context sessionsContext = null;
538: try {
539: sessionsContext = (Context) userIdContext
540: .lookup("sessions");
541: } catch (NamingException ne) {
542: log
543: .error("JNDISessionListener.valueUnbound(): context /users/"
544: + userId + "/sessions doesn't exist!");
545: return;
546: }
547:
548: Context sessionIdContext = null;
549: try {
550: sessionIdContext = (Context) sessionsContext
551: .lookup(sessionId);
552: } catch (NamingException ne) {
553: log
554: .error("JNDISessionListener.valueUnbound(): context /users/"
555: + userId
556: + "/sessions/"
557: + sessionId
558: + " doesn't exist!");
559: return;
560: }
561:
562: // determine layoutId
563: String layoutId = null;
564: try {
565: layoutId = (String) sessionIdContext.lookup("layoutId");
566: } catch (NamingException ne) {
567: log
568: .error("JNDISessionListener.valueUnbound(): binding /users/"
569: + userId
570: + "/sessions/"
571: + sessionId
572: + "/layoutId doesn't exist!");
573: }
574:
575: // destroy sessionIdContext
576: try {
577: sessionsContext.unbind(sessionId);
578: log
579: .debug("JNDISessionListener.valueUnbound(): destroyed context /users/"
580: + userId + "/sessions/" + sessionId);
581: } catch (Exception e) {
582: log
583: .error(
584: "JNDISessionListener.valueUnbound(): exception occurred while trying to destroy context /users/"
585: + userId
586: + "/sessions/"
587: + sessionId, e);
588: }
589:
590: // see if this was the only session
591: try {
592: NamingEnumeration list = userIdContext.list("sessions");
593: if (!list.hasMore()) {
594: // destroy userIdContext alltogether
595: usersContext.unbind(userId);
596: log
597: .debug("JNDISessionListener.valueUnbound(): destroyed context /users/"
598: + userId
599: + " since the last remaining session has been unbound.");
600: } else {
601: // remove sessionId from the layouts/[layoutId]/sessions
602: try {
603: Context layoutsContext = (Context) userIdContext
604: .lookup("layouts");
605: try {
606: Context layoutIdContext = (Context) layoutsContext
607: .lookup(layoutId);
608: try {
609: Context lsessionsContext = (Context) layoutIdContext
610: .lookup("sessions");
611: // unbind sessionId
612: lsessionsContext.unbind(sessionId);
613: log
614: .debug("JNDISessionListener.valueUnbound(): destroyed context /users/"
615: + userId
616: + "/layouts/"
617: + layoutId
618: + "/sessions/"
619: + sessionId);
620:
621: // see if the lsessionsContext is empty
622: NamingEnumeration slist = layoutIdContext
623: .list("sessions");
624: if (!slist.hasMore()) {
625: // destroy the layoutId context
626: try {
627: layoutsContext.unbind(layoutId);
628: log
629: .debug("JNDISessionListener.valueUnbound(): destroyed context /users/"
630: + userId
631: + "/layouts/"
632: + layoutId
633: + " since the last session using it has been unbound.");
634:
635: } catch (Exception e) {
636: log.error(
637: "JNDISessionListener.valueUnbound(): error destroying /users/"
638: + userId
639: + "/layouts/"
640: + layoutId, e);
641: }
642: }
643: } catch (Exception e) {
644: log
645: .error(
646: "JNDISessionListener.valueUnbound(): error looking up /users/"
647: + userId
648: + "/layouts/"
649: + layoutId
650: + "/sesions", e);
651: }
652: } catch (Exception e) {
653: log.error(
654: "JNDISessionListener.valueUnbound(): error looking up /users/"
655: + userId + "/layouts/"
656: + layoutId, e);
657: }
658: } catch (Exception e) {
659: log.error(
660: "JNDISessionListener.valueUnbound(): error looking up /users/"
661: + userId + "/layouts", e);
662: }
663: }
664: } catch (Exception e) {
665: log.error(
666: "JNDISessionListener.valueUnbound(): error listing /users/"
667: + userId + "/sessions/", e);
668: }
669: }
670: }
671: }
|