FunctorSessionInitializer.java

/***************************************************************************
   Copyright 2013 Emily Estes

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
***************************************************************************/
package net.metanotion.web.concrete;


import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap;

import net.metanotion.functor.Block;
import net.metanotion.util.Unknown;
import net.metanotion.web.RequestObject;
import net.metanotion.web.SessionFactory;
import net.metanotion.web.SessionInitializer;
import net.metanotion.web.SessionStore;
import net.metanotion.web.servlets.ServletSessionStore;

/** This session initializer uses a function object("functor") to transform the RequestObject provided to it by the
{@link net.metanotion.web.SessionHandler} into a new value which is then passed to the SessionFactory instance it
encapsulates to create a session based on the transformed value. Also, multiple sessions are cached and multiplexed
(this actually is the OTHER type of "functor" implemented by this class, to my understanding this is probably vaguely
monadic in nature) based on the value produced by the request object filter.
	@param <D> The type of value this class provides to the session factory instances for the initial conditions.
	@see net.metanotion.web.concrete.PrefixedInitializerMap
*/
public final class FunctorSessionInitializer<D> implements SessionInitializer {
	/** SessionStore's expect an instance of Unknown, this class is just fulfilling the leter of the law, but it
	primarily exists to sneak a "map" into the session store instead. */
	private static final class SessionMap<E> implements Unknown {
		/** This map is what we're actually trying to keep in the session, it's a public variable because it's
		immutable and thread safe, and is used as a container for the sessions produced by the factory and keyed off
		of the transformed value of the request objects. */
		public final ConcurrentHashMap<E,Unknown> sessions = new ConcurrentHashMap<>();
		@Override public <I> I lookupInterface(Class<I> theInterface) {
			throw new RuntimeException("Can't find " + theInterface);
		}
	}

	private final SessionStore store;
	private final SessionFactory<D> app;
	private final Block<RequestObject,D> roParser;

	/** Create a session initializer wrapping the factory provided and using the transformer function provided with the
		default session store ({@link net.metanotion.web.servlets.ServletSessionStore}).
		@param app A session factory instance.
		@param roParser A block which, when evaluated against a request object, will give us a data object that can be
			used by the app to generate a session. Whatever object the block returns should be consistent(i.e. hashCode
			and equals should be properly implemented for it) such that different HTTP Requests meant for the same
			"session" will evaluate to the "same" object. For an implementation that can use resource prefixes see
			{@link net.metanotion.web.concrete.PrefixedInitializerMap}
	*/
	public FunctorSessionInitializer(SessionFactory<D> app, Block<RequestObject,D> roParser) {
		this(app, roParser, new ServletSessionStore());
	}

	/** Create a session initializer wrapping the factory provided and using the transformer function provided and
		store the sessions using the session store provided.
		@param app A session factory instance.
		@param roParser A block which, when evaluated against a request object, will give us a data object that can be
			used by the app to generate a session. Whatever object the block returns should be consistent(i.e. hashCode
			and equals should be properly implemented for it) such that different HTTP Requests meant for the same
			"session" will evaluate to the "same" object. For an implementation that can use resource prefixes see
			{@link net.metanotion.web.concrete.PrefixedInitializerMap}
		@param store The session store instance to use.
	*/
	public FunctorSessionInitializer(SessionFactory<D> app, Block<RequestObject,D> roParser, SessionStore store) {
		this.store = store;
		this.app = app;
		this.roParser = roParser;
	}

	@Override public Unknown getSession(RequestObject ro) {
		try {
			Unknown session = store.getSession(ro);
			if(session==null) {
				session = new SessionMap<D>();
				store.setSession(ro, session);
			}
			final D d = roParser.eval(ro);
			final ConcurrentMap<D,Unknown> map = ((SessionMap<D>) session).sessions;
			Unknown s = map.get(d);
			if(s==null) {
				s = app.newSession(d);
				final Unknown s2 = map.putIfAbsent(d, s);
				if(s2 != null) { s = s2; }
			}
			return s;
		} catch (Exception e) { throw new RuntimeException(e); }
	}
}