TemplateAndSQLApp.java

/***************************************************************************
   Copyright 2012 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.examples;


import java.io.File;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Map;
import javax.sql.DataSource;

import net.metanotion.functor.Block;
import net.metanotion.io.JavaFileSystem;
import net.metanotion.simpletemplate.ResourceDispatcher;
import net.metanotion.simpletemplate.ResourceFactory;
import net.metanotion.simpletemplate.TemplateResources;

import net.metanotion.util.Dictionary;
import net.metanotion.util.DictionaryDispatcher;
import net.metanotion.util.Dispatcher;
import net.metanotion.util.JDBCTransaction;
import net.metanotion.util.MapToListDispatcher;
import net.metanotion.util.Message;
import net.metanotion.util.Pair;
import net.metanotion.util.Unknown;

import net.metanotion.scripting.DictionaryServer;
import net.metanotion.scripting.ObjectServer;

import net.metanotion.sql.DbUtil;

import net.metanotion.sqlc.SQLClass;
import net.metanotion.sqlc.SQLObjectServer;

import net.metanotion.web.RequestObject;
import net.metanotion.web.SessionFactory;
import net.metanotion.web.servlets.ServerUtil;

/** This is an app server that runs template scripts using the simple templates language interpreter and uses the SQL
ORM to provide the domain objects for the templates to use. */
public final class TemplateAndSQLApp implements SessionFactory<RequestObject>, Unknown {
	/** This is the main method for this example application.
		@param args The command line arguments for the server. They are as follows:
		<ol>
			<li>Path to the templates and sql folder for the server to run.</li>
			<li>JDBC URL for the database connection.</li>
			<li>JDBC username for the database connection.</li>
			<li>JDBC password for the database connection.</li>
		</ol>
	*/
	public static void main(final String[] args) {
		try {
			/** Strictly speaking we don't *need* database connection pooling, but this initializes a DataSource as
			a connection pool using the Apache Commons Pooling library. We're assuming PostgreSQL as the JDBC
			driver and using the command line arguments as DB URL, username, and password. */
			final DataSource ds = DbUtil.startDBConnectionPool(args[1], args[2], args[3]);

			/** We put our templates in a folder called "templates" underneath the directory specified in the first command
			line argument. We put our SQL methods under the "sql" folder using the same command line argument. */
			final JavaFileSystem templates = new JavaFileSystem(args[0] + File.separator + "templates");
			final JavaFileSystem sql = new JavaFileSystem(args[0] + File.separator + "sql");

			/** We're creating an ObjectServer that loads SQL class files from the specified file system. */
			final SQLObjectServer sqlObjects = new SQLObjectServer(sql);
			/** This dispatcher uses a custom dictionary specified below to hand out dispatchers for the SQL classes
			served up by the SQLObjectServer instance above. */
			final DictionaryDispatcher sqlDispatchers = new DictionaryDispatcher(new Dict(sqlObjects));
			final DictionaryServer dictObjectServer = new DictionaryServer(sqlDispatchers, sqlObjects);

			/** Just like the StaticResources class used in our simple file server examples we're creating a
			TemplateResources instance instead, of course, we also need to give the template interpreter a dispatcher
			to use for the scripting objects. */
			final ResourceFactory resources = new TemplateResources(templates);
			/** A DefaultServer works for ANY instance of a ResourceFactory. */
			/** Start listening for connections using this class as the SessionHandler. */
			ServerUtil.launchJettyServer(8080, new ResourceDispatcher(), new TemplateAndSQLApp(resources, ds, dictObjectServer));
		} catch (final Exception e) {
			e.printStackTrace();
		}
	}

	private static final class Msg implements Message<Unknown> {
		private final SQLClass cls;
		/** The method/parameter list data to use when creating the final payload for the delegated dispatcher. */
		private final Map.Entry<String,Iterable> data;
		/** Create a message to delegate dispatcher on the data parameter with a SQL connection injected into the parameter list.
			@param data The original data to dispatch against.
		*/
		public Msg(final SQLClass cls, final Map.Entry<String,Iterable> data) {
			this.cls = cls;
			this.data = data;
		}

		@Override public Class<Unknown> receiverType() { return Unknown.class; }
		@Override public Object call(final Unknown o) {
			final Iterable list = data.getValue();
			return JDBCTransaction.doTX(o.lookupInterface(DataSource.class), new Block<Connection,Object>() {
				public Object eval(final Connection conn) throws SQLException {
					final ArrayList<Object> l = new ArrayList<>();
					l.add(conn);
					for(final Object o: list) { l.add(o); }
					final Message m = cls.dispatch(new Pair<String,Iterable<Object>>(data.getKey(), l));
					return m.call(o.lookupInterface(m.receiverType()));
				}
			});
		}
	}

	/** This class provides dispatchers based on a name for use by the DictionaryDispatcher. */
	private static final class Dict implements Dictionary<String,Dispatcher> {
		/** This is our Object Server which hands out SQLClass'es, which also implement a Dispatcher for themselves. */
		private final SQLObjectServer objects;
		public Dict(final SQLObjectServer objects) { this.objects = objects; }

		/** Look up a dispatcher for a given object instance name. */
		@Override public Dispatcher get(final String name) {
			/** We ask the object server for the class. */
			final SQLClass cls = objects.get(name);
			final Dispatcher<? extends Object, Map.Entry<String,Iterable>> listDispatcher =
				new Dispatcher<Unknown, Map.Entry<String,Iterable>>() {
					@Override public Message<Unknown> dispatch(final Map.Entry<String,Iterable> data) {
						return new Msg(cls, data);
					}
				};
			/** The MapToListDispatcher takes the method map provided by the SQLClass to convert named parameters into
			a list in the proper order. */
			final MapToListDispatcher mapDispatcher = new MapToListDispatcher(cls.getMethodMap(), listDispatcher);
			/**We need to extra a method name from the resource path of a RequestObject to make a request suitable for
			consumption by the MapToListDispatcher. If the data parameter isn't a request object, it needs to be a
			Map.Entry instance. So we just go ahead and assume it is(and let the dispatcher throw a class cast exception
			if it isn't). */
			return new Dispatcher() {
				@Override public Message dispatch(final Object data) {
					if(data instanceof RequestObject) {
						final RequestObject ro = (RequestObject) data;
						final String[] resource = ro.getResource().split("/");
						final String message = resource[resource.length - 1];
						return mapDispatcher.dispatch(new Pair<String,Dictionary<String,Object>>(message, ro));
					} else {
						return mapDispatcher.dispatch((Map.Entry) data);
					}
				}
			};
		}
	}

	/** This is our DefaultServer instance. */
	private final ResourceFactory resources;
	private final DataSource ds;
	private final ObjectServer objects;
	/** Create an instance of our application's session factory to handle HTTP requests.
		@param resources The resource factory to load templates from.
		@param ds The database connection pool for our server.
		@param objects The object server backed by our SQL query interpreter.
	*/
	public TemplateAndSQLApp(final ResourceFactory resources, final DataSource ds, final ObjectServer objects) {
		this.resources = resources;
		this.ds = ds;
		this.objects = objects;
	}

	// SessionFactory
	@Override public Unknown newSession(final RequestObject ro) { return this; }

	// Unknown
	@Override public Object lookupInterface(final Class theInterface) {
		if(theInterface == Unknown.class) { return this; }
		if(theInterface == ResourceFactory.class) { return resources; }
		if(theInterface == ObjectServer.class) { return objects; }
		if(theInterface == DataSource.class) { return ds; }
		throw new RuntimeException("Can't find " + theInterface);
	}
}