DominoRunner: Provides a temporary Notes Session for your Java code...


/*
 * DominoRunner version 1.0 - 20150915
 * 
 * By Serdar Basegmez (@sbasegmez), Developi
 * http://lotusnotus.com/en
 * 
 * This class is a temporary encapsulation for a code that needs a Session. I have inspired from a number of snippets,
 * but probably, Nathan T. Freeman is the founder of the idea itself :)
 * 
 * This code has been tested on;
 * 
 * - DOTS tasklets,
 * - XPages apps,
 * - Apache Wink servlets,
 * - OSGi-level threads (specifically for HttpService)
 * 
 * Although I've been using this for a couple of months in a production system, still, it's quite out of the mainstream
 * and it contains some bad practices. So use it at your own risk :)
 * 
 * Sometimes, developing Java for Domino applications, we need a session for a quick operation. This class will try to
 * get the Session from NotesContext (XSP Session provider) and ContextInfo (Servlet session provider) respectively. 
 * If it can't get the session, it would mean that we are out of these contexts (maybe in a standalone app, or DOTS 
 * tasklet, etc.). Then it will try to start a NotesThread and terminate it after our code. 
 * 
 * Since it uses the reflection mechanism, it does not introduce any additional dependency to the project.
 * 
 * A quick sample is included as a method below...
 * 
 */

import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;

import lotus.domino.NotesException;
import lotus.domino.NotesFactory;
import lotus.domino.NotesThread;
import lotus.domino.Session;

public class DominoRunner {

	public interface SessionRoutine<T> {
		public T doRun(Session session);
		public T fallback();
		public T onException(Throwable t);
	}
	
	/*
	 * Runs the routine with a Session.
	 * 
	 * If trusted is true: it uses a server/signer session, otherwise uses the current user session.
	 * 
	 */
	public static <T> T runWithSession(boolean trusted, SessionRoutine<T> routine) {
		// We need a session. So first, we'll try to get the session from the NotesContext.
		// If the caller eventually binded to an XPages session, we'll be able to grab a session.
		// The only problem is that; session coming from NotesContext will be the SignerSession. 
		// Hope there is only one almighty developer :)

		Session session=null;
		
		session = findNotesContextSession(trusted);
		
		if(null != session) {
			// We Got a NotesContext session!
			try {
				return routine.doRun(session);
			} catch(Throwable t) {
				return routine.onException(t);
			}
		}

		// So we couldn't grab the NotesContext session... It means that we are out of XPages. 
		// If this is a servlet, we still have a chance for getting a User Session.

		// However if a trusted session requested, ContextInfo will not provide one.
		if(!trusted) {
			session = findContextInfoSession();
			
			if(null != session) {
				// We got a User Session from the ContextInfo
				try {
					return routine.doRun(session);
				} catch(Throwable t) {
					return routine.onException(t);
				}
			}
		}
		
		// We might be on an OSGi-level thread, DOTS or a servlet.
		// Either way, we hope we are allowed to have NotesThread session.
		// As far as we know, NotesThread is forbidden in XPages threads. 
		
		try {
			NotesThread.sinitThread();
			session = NotesFactory.createTrustedSession();
			
			if(null != session) {
				// Got a session!
				try {
					return routine.doRun(session);
				} catch(Throwable t) {
					return routine.onException(t);
				}
			}

			// Session is null...
			
		} catch (NotesException e) {
			// Ooops. We can't have a Session. That means trouble.
		} finally {
			NotesThread.stermThread();

			if(session != null) {
				try {
					session.recycle(); // Not sure if it's necessary?
				} catch (NotesException e) {} 
			}
			
		}

		// No other option for now. We can't have a valid Session...

		return routine.fallback();
	}

	/**
	 * We will try to get a session from the NotesContext. We use Reflection to avoid any dependencies and 
	 * ClassNotFound errors.
	 * 
	 * This is the way XSP Engine works for now, this would be changed in the future (IBM says so in the ExtLib code). 
	 */
	private static Session findNotesContextSession(final boolean signer) {
		return AccessController.doPrivileged(new PrivilegedAction<Session>() {
			@Override
			public Session run() {
				try {
					// This classloader is not available in Wink. 
					// Wink servlets replaces classloaders during runtime. 
					ClassLoader cl = Thread.currentThread().getContextClassLoader();
					Class<?> clazz = cl.loadClass("com.ibm.domino.xsp.module.nsf.NotesContext");
					Method m1 = clazz.getDeclaredMethod("getCurrentUnchecked", new Class[0]);
					Method m2;
					if(signer) {
						m2 = clazz.getDeclaredMethod("getSessionAsSignerFullAdmin", new Class[0]);
					} else {
						m2 = clazz.getDeclaredMethod("getCurrentSession", new Class[0]);
					}
					
					Object nc = m1.invoke(null, new Object[0]);
					
					if(nc==null) {
						// NotesContext is unavailable. We are out of XSP context.
						return null;
					} else {
						return (Session) m2.invoke(nc, new Object[0]);
					}
				} catch (ClassNotFoundException e) {
					// We couldn't find the class.
					return null;
				} catch (NoClassDefFoundError e) {
					// We couldn't access the class.
					return null;
				} catch (Exception e) {
					// Unhandled exception
					return null;
				}
			}
		});
	}
	
	/**
	 * We will try to get a session from the ContextInfo. We use Reflection to avoid any dependencies and 
	 * ClassNotFound errors.
	 */
	private static Session findContextInfoSession() {
		return AccessController.doPrivileged(new PrivilegedAction<Session>() {
			@Override
			public Session run() {
				try {
					// This classloader is not available in Wink. 
					// Wink servlets replaces classloaders during runtime. 
					ClassLoader cl = Thread.currentThread().getContextClassLoader();
					Class<?> clazz = cl.loadClass("com.ibm.domino.osgi.core.context.ContextInfo");
					Method m1 = clazz.getDeclaredMethod("getUserSession", new Class[0]);
					
					return (Session) m1.invoke(null, new Object[0]);

				} catch (ClassNotFoundException e) {
					// We couldn't find the class.
				} catch (NoClassDefFoundError e) {
					// We couldn't access the class.
				} catch (Throwable t) {
					// Unhandled exception
				}
				return null;
			}
		});
	}

	public static void aQuickSample() {
		String effectiveUserName = DominoRunner.runWithSession(false, new SessionRoutine<String>() {

			@Override
			public String doRun(Session session) {
				try {
					return session.getEffectiveUserName();
				} catch (NotesException e) {
					e.printStackTrace();
					return null;
				}
			}

			@Override
			public String fallback() {
				System.out.println("Unable to get a Session... No backup plan... Doomed!");
				return null;
			}

			@Override
			public String onException(Throwable t) {
				System.out.println("Exception, while getting a Session...");
				t.printStackTrace();
				return null;
			}
		});
		
		if (effectiveUserName != null) {
			System.out.println("Effective User Name: "+effectiveUserName);
		} else {
			System.out.println("Unable to find a user name...");
		}
		
	}
	
}
All code submitted to OpenNTF XSnippets, whether submitted as a "Snippet" or in the body of a Comment, is provided under the Apache License Version 2.0. See Terms of Use for full details.
No comments yetLogin first to comment...