[Tynstep-svn] r253 - in trunk/step: step-build/src/main/resources step-core step-core/src/main/java/com/tyndalehouse/step/core/models step-core/src/main/java/com/tyndalehouse/step/core/service/impl step-core/src/main/resources/com/tyndalehouse/step/core/data/create step-core/src/main/resources/com/tyndalehouse/step/core/service/impl step-core/src/test/java/com/tyndalehouse/step/core/guice/providers step-core/src/test/java/com/tyndalehouse/step/core/service/impl step-parent step-web/src/main/java/com/tyndalehouse/step/guice step-web/src/main/java/com/tyndalehouse/step/rest/controllers step-web/src/main/java/com/tyndalehouse/step/rest/framework step-web/src/main/webapp step-web/src/main/webapp/css step-web/src/main/webapp/js step-web/src/test/java/com/tyndalehouse/step/rest/controllers step-web/src/test/java/com/tyndalehouse/step/rest/framework

ChrisBurrell at crosswire.org ChrisBurrell at crosswire.org
Mon Apr 23 04:52:53 MST 2012


Author: ChrisBurrell
Date: 2012-04-23 04:52:53 -0700 (Mon, 23 Apr 2012)
New Revision: 253

Added:
   trunk/step/step-build/src/main/resources/js/
   trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/guice/providers/DefaultInstallersProviderTest.java
   trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/framework/FrontController.java
   trunk/step/step-web/src/test/java/com/tyndalehouse/step/rest/framework/FrontControllerTest.java
Removed:
   trunk/step/step-core/src/main/resources/com/tyndalehouse/step/core/data/create/timeband/
   trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/FrontController.java
   trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/PeopleController.java
   trunk/step/step-web/src/test/java/com/tyndalehouse/step/rest/controllers/FrontControllerTest.java
Modified:
   trunk/step/step-core/pom.xml
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/models/LookupOption.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/JSwordServiceImpl.java
   trunk/step/step-core/src/main/resources/com/tyndalehouse/step/core/service/impl/default.xsl
   trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/JSwordServiceImplTest.java
   trunk/step/step-parent/pom.xml
   trunk/step/step-web/src/main/java/com/tyndalehouse/step/guice/StepServletConfig.java
   trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/BibleController.java
   trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/GeographyController.java
   trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/ModuleController.java
   trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/SetupController.java
   trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/TimelineController.java
   trunk/step/step-web/src/main/webapp/css/passage.css
   trunk/step/step-web/src/main/webapp/index.html
   trunk/step/step-web/src/main/webapp/js/bookmark.js
   trunk/step/step-web/src/main/webapp/js/passage.js
   trunk/step/step-web/src/main/webapp/js/timeline.js
   trunk/step/step-web/src/main/webapp/js/toolbar_menu.js
   trunk/step/step-web/src/main/webapp/js/ui_hooks.js
   trunk/step/step-web/src/main/webapp/panemenu.html
Log:


Modified: trunk/step/step-core/pom.xml
===================================================================
--- trunk/step/step-core/pom.xml	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-core/pom.xml	2012-04-23 11:52:53 UTC (rev 253)
@@ -61,6 +61,22 @@
 		        	</execution>
 		        </executions>
 			</plugin>
+			
+<!-- 			 <plugin> -->
+<!-- 		        <groupId>org.codehaus.mojo</groupId> -->
+<!-- 		        <artifactId>cobertura-maven-plugin</artifactId> -->
+<!-- 		        <configuration> -->
+<!-- 		          <instrumentation> -->
+<!-- 		            <excludes> -->
+<!-- 		             Controllers are generally empty and provide validation only -->
+<!-- 		              <exclude>com/tyndalehouse/step/rest/controllers/*.class</exclude> -->
+<!-- 		              <exclude>com/tyndalehouse/step/core/data/entities/*.class</exclude> -->
+<!-- 		              <exclude>com/tyndalehouse/step/core/models/*.class</exclude> -->
+<!-- 		            </excludes> -->
+<!-- 		          </instrumentation> -->
+<!-- 		        </configuration> -->
+<!-- 		      </plugin> -->
+		      
 		</plugins>
 	</build>
 
@@ -100,11 +116,6 @@
 			<artifactId>commons-collections</artifactId>
 		</dependency>
 
-<!--		<dependency>-->
-<!--			<groupId>commons-codec</groupId>-->
-<!--			<artifactId>commons-codec</artifactId>-->
-<!--		</dependency>-->
-		
 		<!--  we don't always need this - depends on what version -->
 		<dependency>
 			<groupId>commons-dbcp</groupId>

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/models/LookupOption.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/models/LookupOption.java	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/models/LookupOption.java	2012-04-23 11:52:53 UTC (rev 253)
@@ -15,22 +15,26 @@
     /**
      * Showing headings
      */
-    HEADINGS("Headings", "Headings", XslConversionType.DEFAULT, true),
+    HEADINGS("Headings", XslConversionType.DEFAULT, true),
     // CHECKSTYLE:ON
     /**
-     * Showing headings
+     * Showing verse numbers
      */
-    VERSE_NUMBERS("VNum", "Verse Nums.", XslConversionType.DEFAULT, true),
+    VERSE_NUMBERS("VNum", XslConversionType.DEFAULT, true),
+    /**
+     * Showing cross references
+     */
+    NOTES("Notes", XslConversionType.DEFAULT, true),
     /** Strong numbers */
-    STRONG_NUMBERS("StrongsNumbers", "Strongs", XslConversionType.INTERLINEAR),
+    STRONG_NUMBERS("StrongsNumbers", XslConversionType.INTERLINEAR),
 
     /** Morphology */
-    MORPHOLOGY("Morph", "Morphology", XslConversionType.INTERLINEAR),
+    MORPHOLOGY("Morph", XslConversionType.INTERLINEAR),
     // CHECKSTYLE:OFF
     /**
      * Interlinears are available when Strongs are available.
      */
-    INTERLINEAR("Interlinear", "Interlinear", XslConversionType.INTERLINEAR),
+    INTERLINEAR("Interlinear", XslConversionType.INTERLINEAR),
     // CHECKSTYLE:ON
     /**
      * Showing headings
@@ -38,7 +42,6 @@
     TINY_VERSE_NUMBERS("TinyVNum", XslConversionType.DEFAULT);
 
     private final String xsltParameterName;
-    // private final String displayName;
     private final XslConversionType stylesheet;
     private final boolean enabledByDefault;
 
@@ -48,7 +51,7 @@
      * @param xsltParameterName the corresponding parameter name in the XSLT stylesheet
      */
     private LookupOption(final String xsltParameterName) {
-        this(xsltParameterName, xsltParameterName, DEFAULT);
+        this(xsltParameterName, DEFAULT, false);
     }
 
     /**
@@ -56,29 +59,17 @@
      * @param stylesheet the stylesheet to use
      */
     private LookupOption(final String xsltParameterName, final XslConversionType stylesheet) {
-        this(xsltParameterName, null, stylesheet);
+        this(xsltParameterName, stylesheet, false);
     }
 
     /**
      * @param xsltParameterName the name of the parameter in the stylesheet
      * @param stylesheet the stylesheet to use
-     * @param displayName the name to display on the user interface
-     */
-    private LookupOption(final String xsltParameterName, final String displayName,
-            final XslConversionType stylesheet) {
-        this(xsltParameterName, displayName, stylesheet, false);
-    }
-
-    /**
-     * @param xsltParameterName the name of the parameter in the stylesheet
-     * @param stylesheet the stylesheet to use
-     * @param displayName the name to display on the user interface
      * @param enabledByDefault true to have the UI display the option by default
      */
-    private LookupOption(final String xsltParameterName, final String displayName,
-            final XslConversionType stylesheet, final boolean enabledByDefault) {
+    private LookupOption(final String xsltParameterName, final XslConversionType stylesheet,
+            final boolean enabledByDefault) {
         this.xsltParameterName = xsltParameterName;
-        // this.displayName = displayName;
         this.stylesheet = stylesheet;
         this.enabledByDefault = enabledByDefault;
     }
@@ -90,13 +81,6 @@
         return this.xsltParameterName;
     }
 
-    // /**
-    // * @return the display name of the lookup option
-    // */
-    // public String getUiKey() {
-    // return this.displayName;
-    // }
-
     /**
      * @return the stylesheet that should be used
      */

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/JSwordServiceImpl.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/JSwordServiceImpl.java	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/JSwordServiceImpl.java	2012-04-23 11:52:53 UTC (rev 253)
@@ -216,6 +216,11 @@
             options.add(LookupOption.VERSE_NUMBERS);
         }
 
+        if (book.getBookMetaData().hasFeature(FeatureType.FOOTNOTES)
+                || book.getBookMetaData().hasFeature(FeatureType.SCRIPTURE_REFERENCES)) {
+            options.add(LookupOption.NOTES);
+        }
+
         // cycle through each option
         for (final LookupOption lo : LookupOption.values()) {
             final FeatureType ft = FeatureType.fromString(lo.getXsltParameterName());

Modified: trunk/step/step-core/src/main/resources/com/tyndalehouse/step/core/service/impl/default.xsl
===================================================================
--- trunk/step/step-core/src/main/resources/com/tyndalehouse/step/core/service/impl/default.xsl	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-core/src/main/resources/com/tyndalehouse/step/core/service/impl/default.xsl	2012-04-23 11:52:53 UTC (rev 253)
@@ -668,25 +668,17 @@
 
   <!--=======================================================================-->
   <xsl:template match="reference">
-    <xsl:choose>
-      <xsl:when test="$XRef = 'true'">
-        <a href="bible://{@osisRef}"><xsl:apply-templates/></a>
-      </xsl:when>
-      <xsl:otherwise>
-        <xsl:apply-templates/>
-      </xsl:otherwise>
-    </xsl:choose>
+        <xsl:variable name="versification" select="jsword:getVersification($v11nf, $v11n)"/>
+        <xsl:variable name="passage" select="jsword:getValidKey($keyf, $versification, @osisRef)"/>
+        <xsl:variable name="passageKey" select="jsword:getName($passage)"/>
+        <a href="#" class="linkRef" onclick="javascript:changePassage(this, &quot;{$passageKey}&quot;);"><xsl:apply-templates/></a>
   </xsl:template>
   
   <xsl:template match="reference" mode="jesus">
-    <xsl:choose>
-      <xsl:when test="$XRef = 'true'">
-        <a href="bible://{@osisRef}"><xsl:apply-templates mode="jesus"/></a>
-      </xsl:when>
-      <xsl:otherwise>
-        <xsl:apply-templates mode="jesus"/>
-      </xsl:otherwise>
-    </xsl:choose>
+        <xsl:variable name="versification" select="jsword:getVersification($v11nf, $v11n)"/>
+        <xsl:variable name="passage" select="jsword:getValidKey($keyf, $versification, @osisRef)"/>
+        <xsl:variable name="passageKey" select="jsword:getName($passage)"/>
+        <a href="#" onclick="javascript:changePassage(this, &quot;{$passageKey}&quot;);"><xsl:apply-templates/></a>
   </xsl:template>
   
   <!--=======================================================================-->

Added: trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/guice/providers/DefaultInstallersProviderTest.java
===================================================================
--- trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/guice/providers/DefaultInstallersProviderTest.java	                        (rev 0)
+++ trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/guice/providers/DefaultInstallersProviderTest.java	2012-04-23 11:52:53 UTC (rev 253)
@@ -0,0 +1,34 @@
+package com.tyndalehouse.step.core.guice.providers;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Properties;
+
+import org.junit.Test;
+
+/**
+ * Test properties are loaded properly and construct a installer capable of downloading books off the internet
+ * 
+ * @author Chris
+ * 
+ */
+public class DefaultInstallersProviderTest {
+    /**
+     * tests the main flows
+     */
+    @Test
+    public void testDefaultInstaller() {
+        // DefaultInstallersProvider p = new DefaultInstallersProvider(
+        final String i = "www.crosswire.org,/ftpmirror/pub/sword/packages/rawzip,/ftpmirror/pub/sword/raw";
+        final Properties p = new Properties();
+        p.put("installer.1", i);
+
+        final DefaultInstallersProvider defaultInstallersProvider = new DefaultInstallersProvider(p,
+                "localhost", "8080");
+
+        final String def = defaultInstallersProvider.get().get(0).getInstallerDefinition();
+
+        assertEquals("www.crosswire.org,/ftpmirror/pub/sword/packages/rawzip,"
+                + "/ftpmirror/pub/sword/raw,,localhost,8080", def);
+    }
+}


Property changes on: trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/guice/providers/DefaultInstallersProviderTest.java
___________________________________________________________________
Added: svn:mime-type
   + text/plain

Modified: trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/JSwordServiceImplTest.java
===================================================================
--- trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/JSwordServiceImplTest.java	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/JSwordServiceImplTest.java	2012-04-23 11:52:53 UTC (rev 253)
@@ -266,4 +266,14 @@
         // t2.join();
         // }
     }
+
+    @Test
+    public void testPrettyXml() throws BookException, NoSuchKeyException, JDOMException, IOException {
+        final Book currentBook = Books.installed().getBook("ESV");
+        final BookData bookData = new BookData(currentBook, currentBook.getKey("Exodus 3:14"));
+        final Element osisFragment = bookData.getOsisFragment();
+
+        final XMLOutputter xmlOutputter = new XMLOutputter(Format.getPrettyFormat());
+        LOGGER.debug(xmlOutputter.outputString(osisFragment));
+    }
 }

Modified: trunk/step/step-parent/pom.xml
===================================================================
--- trunk/step/step-parent/pom.xml	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-parent/pom.xml	2012-04-23 11:52:53 UTC (rev 253)
@@ -333,7 +333,6 @@
 		<url>http://http://www.tyndale.cam.ac.uk/</url>
 	</organization>
 
-
 	<build>
 		<pluginManagement>
 			<plugins>
@@ -454,19 +453,31 @@
 					<version>2.4</version>
 					<configuration>
 						<check>
-							<branchRate>70</branchRate>
-							<lineRate>70</lineRate>
-							<haltOnFailure>false</haltOnFailure>
-							<totalBranchRate>60</totalBranchRate>
-							<totalLineRate>60</totalLineRate>
-							<packageLineRate>60</packageLineRate>
-							<packageBranchRate>60</packageBranchRate>
+							<branchRate>20</branchRate>
+							<lineRate>20</lineRate>
+							<haltOnFailure>true</haltOnFailure>
+							<totalBranchRate>20</totalBranchRate>
+							<totalLineRate>20</totalLineRate>
 						</check>
 					</configuration>
 				</plugin>
 			</plugins>
 		</pluginManagement>
 		
+		<plugins>
+<!-- 		 <plugin> -->
+<!-- 	        <groupId>org.codehaus.mojo</groupId> -->
+<!-- 	        <artifactId>cobertura-maven-plugin</artifactId> -->
+<!-- 	        <executions> -->
+<!-- 	          <execution> -->
+<!-- 	            <goals> -->
+<!-- 	              <goal>clean</goal> -->
+<!-- 	              <goal>check</goal> -->
+<!-- 	            </goals> -->
+<!-- 	          </execution> -->
+<!-- 	        </executions> -->
+<!-- 	      </plugin> -->
+		</plugins>
 
 		
 	</build>

Modified: trunk/step/step-web/src/main/java/com/tyndalehouse/step/guice/StepServletConfig.java
===================================================================
--- trunk/step/step-web/src/main/java/com/tyndalehouse/step/guice/StepServletConfig.java	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/guice/StepServletConfig.java	2012-04-23 11:52:53 UTC (rev 253)
@@ -5,7 +5,7 @@
 import com.google.inject.servlet.GuiceServletContextListener;
 import com.google.inject.servlet.ServletModule;
 import com.tyndalehouse.step.core.guice.StepCoreModule;
-import com.tyndalehouse.step.rest.controllers.FrontController;
+import com.tyndalehouse.step.rest.framework.FrontController;
 
 /**
  * Configures the listener for the web app to return the injector used to configure the whole of the

Modified: trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/BibleController.java
===================================================================
--- trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/BibleController.java	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/BibleController.java	2012-04-23 11:52:53 UTC (rev 253)
@@ -1,12 +1,12 @@
 package com.tyndalehouse.step.rest.controllers;
 
 import static org.apache.commons.lang.StringUtils.isNotBlank;
+import static org.apache.commons.lang.Validate.notEmpty;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 
-import org.apache.commons.lang.Validate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -38,6 +38,7 @@
      */
     @Inject
     public BibleController(final BibleInformationService bibleInformation) {
+
         this.bibleInformation = bibleInformation;
         LOGGER.debug("Created Bible Controller");
     }
@@ -89,8 +90,8 @@
     @Cacheable(true)
     public HtmlWrapper getBibleText(final String version, final String reference, final String options,
             final String interlinearVersion) {
-        Validate.notEmpty(version, "You need to provide a version");
-        Validate.notEmpty(reference, "You need to provide a reference");
+        notEmpty(version, "You need to provide a version");
+        notEmpty(reference, "You need to provide a reference");
 
         String[] userOptions = null;
         if (isNotBlank(options)) {

Deleted: trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/FrontController.java
===================================================================
--- trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/FrontController.java	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/FrontController.java	2012-04-23 11:52:53 UTC (rev 253)
@@ -1,355 +0,0 @@
-package com.tyndalehouse.step.rest.controllers;
-
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.codehaus.jackson.JsonGenerationException;
-import org.codehaus.jackson.map.JsonMappingException;
-import org.codehaus.jackson.map.ObjectMapper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.avaje.ebean.EbeanServer;
-import com.avaje.ebean.text.json.JsonContext;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.google.inject.Singleton;
-import com.google.inject.name.Named;
-import com.tyndalehouse.step.core.exceptions.StepInternalException;
-import com.tyndalehouse.step.rest.framework.Cacheable;
-import com.tyndalehouse.step.rest.framework.ClientErrorResolver;
-import com.tyndalehouse.step.rest.framework.ClientHandledIssue;
-import com.tyndalehouse.step.rest.framework.ResponseCache;
-import com.tyndalehouse.step.rest.framework.StepRequest;
-
-/**
- * The FrontController acts like a minimal REST server. The paths are resolved as follows:
- * 
- * /step-web/rest/controllerName/methodName/arg1/arg2/arg3
- * 
- * @author Chris
- * 
- */
- at Singleton
-public class FrontController extends HttpServlet {
-    private static final Logger LOGGER = LoggerFactory.getLogger(FrontController.class);
-    private static final String ENTITIES_PACKAGE = "com.tyndalehouse.step.core.data.entities";
-    private static final String AVAJE_PACKAGE = "com.avaje";
-    private static final String UTF_8_ENCODING = "UTF-8";
-    private static final char PACKAGE_SEPARATOR = '.';
-    private static final long serialVersionUID = 7898656504631346047L;
-    private static final String CONTROLLER_SUFFIX = "Controller";
-    private final transient Injector guiceInjector;
-    // TODO: but also check thread safety and whether we should share this object
-    private final transient ObjectMapper jsonMapper = new ObjectMapper();
-    // TODO: check if this is thread safe, and if so, then make private field
-    private final transient JsonContext ebeanJson;
-
-    // TODO: investigate EH cache here
-    private final Map<String, Method> methodNames = new HashMap<String, Method>();
-    private final Map<String, Object> controllers = new HashMap<String, Object>();
-    private final boolean isCacheEnabled;
-    private final transient ClientErrorResolver errorResolver;
-    private final transient ResponseCache responseCache;
-
-    /**
-     * creates the front controller which will dispatch all the requests
-     * <p />
-     * 
-     * @param guiceInjector the injector used to call the relevant controllers
-     * @param isCacheEnabled indicates whether responses should be cached for fast retrieval
-     * @param ebean the db access/persisitence object
-     * @param errorResolver the error resolver is the object that helps us translate errors for the client
-     * @param responseCache cache in which are put any number of responses to speed up processing
-     */
-    @Inject
-    public FrontController(final Injector guiceInjector,
-            @Named("frontcontroller.cache.enabled") final Boolean isCacheEnabled, final EbeanServer ebean,
-            final ClientErrorResolver errorResolver, final ResponseCache responseCache) {
-        this.guiceInjector = guiceInjector;
-        this.responseCache = responseCache;
-        this.ebeanJson = ebean.createJsonContext();
-
-        this.errorResolver = errorResolver;
-        this.isCacheEnabled = Boolean.TRUE.equals(isCacheEnabled);
-    }
-
-    @Override
-    protected void doGet(final HttpServletRequest request, final HttpServletResponse response) {
-        // first of all check cache against URI: (only cached responses go here)
-        byte[] jsonEncoded = this.responseCache.get(request.getRequestURI());
-
-        StepRequest sr = null;
-        try {
-            // cache miss?
-            if (jsonEncoded == null || jsonEncoded.length == 0) {
-                sr = new StepRequest(request, UTF_8_ENCODING);
-                if (jsonEncoded == null) {
-                    LOGGER.debug("The cache was missed so invoking method now...");
-                    jsonEncoded = invokeMethod(sr);
-                }
-            } else {
-                LOGGER.debug("Returning answer from cache [{}]", request.getRequestURI());
-            }
-
-            setupHeaders(response, jsonEncoded.length);
-            response.getOutputStream().write(jsonEncoded);
-            // CHECKSTYLE:OFF We allow catching errors here, since we are at the top of the structure
-        } catch (final Exception e) {
-            // CHECKSTYLE:ON
-            handleError(response, e, sr);
-        }
-    }
-
-    /**
-     * Invokes the method on the controller instance and returns JSON-ed results
-     * 
-     * @param sr the STEP Request containing all pertinent information
-     * @return byte array representation of the return value
-     */
-    byte[] invokeMethod(final StepRequest sr) {
-
-        // controller instance on which to call a method
-        final Object controllerInstance = getController(sr.getControllerName());
-
-        // resolve method
-        final Method controllerMethod = getControllerMethod(sr.getMethodName(), controllerInstance,
-                sr.getArgs(), sr.getCacheKey().getMethodKey());
-
-        // invoke the three together
-        Object returnVal;
-        try {
-            returnVal = controllerMethod.invoke(controllerInstance, (Object[]) sr.getArgs());
-
-            // CHECKSTYLE:OFF
-        } catch (final Exception e) {
-            returnVal = convertExceptionToJson(e);
-        }
-        final byte[] encodedJsonResponse = getEncodedJsonResponse(returnVal);
-        cache(encodedJsonResponse, sr, controllerMethod);
-        return encodedJsonResponse;
-        // CHECKSTYLE:ON
-    }
-
-    /**
-     * We attempt here to rethrow the exception that caused the invocation target exception, so that we can
-     * handle it nicely for the user
-     * 
-     * @param e the wrapped exception that happened during the reflective call
-     * @return a client handled issue which wraps the exception that was raised
-     */
-    private ClientHandledIssue convertExceptionToJson(final Exception e) {
-        // first we check to see if it's a step exception, or an illegal argument exception
-
-        final Throwable cause = e.getCause();
-        if (cause instanceof StepInternalException) {
-            LOGGER.trace(e.getMessage(), e);
-            return new ClientHandledIssue(cause.getMessage(), this.errorResolver.resolve(cause.getClass()));
-        } else if (cause instanceof IllegalArgumentException) {
-            // a validation exception occurred
-            LOGGER.warn(e.getMessage(), e);
-            return new ClientHandledIssue(cause.getMessage());
-        }
-
-        LOGGER.error(e.getMessage(), e);
-        return new ClientHandledIssue("An internal error has occurred");
-    }
-
-    /**
-     * Returns a json response that is encoded
-     * 
-     * @param responseValue the value that should be encoded
-     * @return the encoded form of the JSON response
-     */
-    byte[] getEncodedJsonResponse(final Object responseValue) {
-        LOGGER.debug("Encoding the following response [{}]", responseValue);
-
-        try {
-            String response;
-            // we have normal objects and avaje ebean objects which have been intercepted
-            // therefore we can't just use simple jackson mapper
-            if (responseValue == null) {
-                return new byte[0];
-            } else {
-                if (isPojo(responseValue)) {
-                    response = this.jsonMapper.writeValueAsString(responseValue);
-                } else {
-                    response = this.ebeanJson.toJsonString(responseValue);
-                }
-            }
-
-            return response.getBytes(UTF_8_ENCODING);
-        } catch (final JsonGenerationException e) {
-            throw new StepInternalException(e.getMessage(), e);
-        } catch (final JsonMappingException e) {
-            throw new StepInternalException(e.getMessage(), e);
-        } catch (final IOException e) {
-            throw new StepInternalException(e.getMessage(), e);
-        }
-    }
-
-    /**
-     * inspects the response value to determine the correct serialiser
-     * 
-     * @param responseValue the response value
-     * @return true if normal serialisation should be used
-     */
-    private boolean isPojo(final Object responseValue) {
-        if (responseValue instanceof java.util.Collection<?>) {
-            // inspect what the collection contains...
-            final Collection<?> c = (Collection<?>) responseValue;
-            if (((java.util.Collection<?>) responseValue).size() != 0) {
-                final Object o = c.iterator().next();
-                return isPojo(o);
-            }
-        }
-
-        final String responsePackage = responseValue.getClass().getPackage().getName();
-        return !responsePackage.startsWith(ENTITIES_PACKAGE) && !responsePackage.startsWith(AVAJE_PACKAGE);
-
-    }
-
-    /**
-     * caches the results for future use
-     * 
-     * @param jsonEncoded json encoding of the response
-     * @param sr the processed request URI containg the the cache key
-     * @param controllerMethod the method so that we can inspect whether an annotation is present
-     */
-    void cache(final byte[] jsonEncoded, final StepRequest sr, final Method controllerMethod) {
-        if (this.isCacheEnabled && controllerMethod.isAnnotationPresent(Cacheable.class)) {
-            this.responseCache.put(sr.getCacheKey().getResultsKey(), jsonEncoded);
-        }
-    }
-
-    /**
-     * sets up the headers and the length of the message
-     * 
-     * @param response the response
-     * @param length the length of the message
-     */
-    void setupHeaders(final HttpServletResponse response, final int length) {
-        // we ensure that headers are set up appropriately
-        response.addDateHeader("Date", System.currentTimeMillis());
-        response.setCharacterEncoding(UTF_8_ENCODING);
-        response.setContentType("application/json");
-        response.setContentLength(length);
-    }
-
-    /**
-     * deals with an error whilst executing the request
-     * 
-     * @param response the response
-     * 
-     * @param e the exception
-     * @param sr the step request
-     */
-    void handleError(final HttpServletResponse response, final Throwable e, final StepRequest sr) {
-        String requestId = null;
-        LOGGER.debug("Handling error...");
-        try {
-            requestId = sr == null ? "Failed to parse request?" : sr.getCacheKey().getResultsKey();
-            if (e != null) {
-                final byte[] errorMessage = this.getEncodedJsonResponse(e);
-                response.getOutputStream().write(errorMessage);
-                setupHeaders(response, errorMessage.length);
-
-                LOGGER.error("An internal error has occurred for [{}]", requestId, e);
-            }
-            // CHECKSTYLE:OFF We allow catching errors here, since we are at the top of the structure
-        } catch (final Exception unableToSendError) {
-            // CHECKSTYLE:ON
-            LOGGER.error("Unable to output error for request" + requestId, unableToSendError);
-            LOGGER.error("Due to original Throwable", e);
-        }
-    }
-
-    /**
-     * Retrieves a controller, either from the cache, or from Guice
-     * 
-     * @param controllerName the name of the controller (used as the key for the cache)
-     * @return the controller object
-     */
-    Object getController(final String controllerName) {
-        Object controllerInstance = this.controllers.get(controllerName);
-
-        // if retrieving yields null, get controller from Guice, and put in cache
-        if (controllerInstance == null) {
-            // make up the full class name
-            final String packageName = getClass().getPackage().getName();
-            final StringBuilder className = new StringBuilder(packageName.length() + controllerName.length()
-                    + CONTROLLER_SUFFIX.length() + 1);
-
-            className.append(packageName);
-            className.append(PACKAGE_SEPARATOR);
-            className.append(Character.toUpperCase(controllerName.charAt(0)));
-            className.append(controllerName.substring(1));
-            className.append(CONTROLLER_SUFFIX);
-
-            try {
-                final Class<?> controllerClass = Class.forName(className.toString());
-                controllerInstance = this.guiceInjector.getInstance(controllerClass);
-
-                // we use the controller name as it came in to key the map
-                this.controllers.put(controllerName, controllerInstance);
-            } catch (final ClassNotFoundException e) {
-                throw new StepInternalException("Unable to find a controller for " + className, e);
-            }
-        }
-        return controllerInstance;
-    }
-
-    /**
-     * Returns the method to be invoked upon the controller
-     * 
-     * @param methodName the method name
-     * @param controllerInstance the instance of the controller
-     * @param args the list of arguments, required to resolve the correct method if they have arguments
-     * @param cacheKey the key to retrieve in the cache
-     * @return the method to be invoked
-     */
-    Method getControllerMethod(final String methodName, final Object controllerInstance, final Object[] args,
-            final String cacheKey) {
-        final Class<? extends Object> controllerClass = controllerInstance.getClass();
-
-        // retrieve method from cache, or put in cache if not there
-        Method controllerMethod = this.methodNames.get(cacheKey);
-        if (controllerMethod == null) {
-            // resolve method
-            try {
-                controllerMethod = controllerClass.getMethod(methodName, getClasses(args));
-
-                // put method in cache
-                this.methodNames.put(cacheKey, controllerMethod);
-            } catch (final NoSuchMethodException e) {
-                throw new StepInternalException(e.getMessage(), e);
-            }
-        }
-        return controllerMethod;
-    }
-
-    /**
-     * @param args a number of arguments
-     * @return an array of classes matching the list of arguments passed in
-     */
-    Class<?>[] getClasses(final Object[] args) {
-        if (args == null) {
-            return new Class<?>[0];
-        }
-
-        final Class<?>[] classes = new Class<?>[args.length];
-
-        for (int ii = 0; ii < classes.length; ii++) {
-            classes[ii] = args[ii].getClass();
-        }
-
-        return classes;
-    }
-}

Modified: trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/GeographyController.java
===================================================================
--- trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/GeographyController.java	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/GeographyController.java	2012-04-23 11:52:53 UTC (rev 253)
@@ -1,5 +1,8 @@
 package com.tyndalehouse.step.rest.controllers;
 
+import static org.apache.commons.lang.Validate.notEmpty;
+import static org.apache.commons.lang.Validate.notNull;
+
 import java.util.List;
 
 import com.google.inject.Inject;
@@ -25,6 +28,7 @@
      */
     @Inject
     public GeographyController(final GeographyService geoService) {
+        notNull(geoService, "Failed to initialise Geography Controller");
         this.geoService = geoService;
 
     }
@@ -36,6 +40,7 @@
      * @return the list of places (lat/long/precisions)
      */
     public List<GeoPlace> getPlaces(final String reference) {
+        notEmpty("A reference is required for looking up geography modules");
         return this.geoService.getPlaces(reference);
     }
 

Modified: trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/ModuleController.java
===================================================================
--- trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/ModuleController.java	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/ModuleController.java	2012-04-23 11:52:53 UTC (rev 253)
@@ -1,5 +1,8 @@
 package com.tyndalehouse.step.rest.controllers;
 
+import static org.apache.commons.lang.Validate.notEmpty;
+import static org.apache.commons.lang.Validate.notNull;
+
 import java.util.List;
 
 import org.slf4j.Logger;
@@ -24,6 +27,7 @@
      */
     @Inject
     public ModuleController(final ModuleService moduleDefintions) {
+        notNull(moduleDefintions, "Intialising the module administration controller failed");
         this.moduleDefintions = moduleDefintions;
     }
 
@@ -53,6 +57,7 @@
      */
     @Cacheable(true)
     public String getDefinition(final String reference) {
+        notEmpty(reference, "A reference must be provided to obtain a definition");
         LOGGER.debug("Getting definition for {}", reference);
         return this.moduleDefintions.getDefinition(reference).getExplanation();
     }

Deleted: trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/PeopleController.java
===================================================================
--- trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/PeopleController.java	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/PeopleController.java	2012-04-23 11:52:53 UTC (rev 253)
@@ -1,77 +0,0 @@
-package com.tyndalehouse.step.rest.controllers;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.tyndalehouse.step.core.data.entities.RelationalPerson;
-import com.tyndalehouse.step.core.service.RelationalPeopleService;
-import com.tyndalehouse.step.models.genealogy.JitRelationalPerson;
-
-/**
- * Getting some geographical data to display
- * 
- * @author Chris
- * 
- */
- at Singleton
-public class PeopleController {
-    private final RelationalPeopleService peopleService;
-
-    /**
-     * Constructs a simple geography service
-     * 
-     * @param peopleService the people service
-     */
-    @Inject
-    public PeopleController(final RelationalPeopleService peopleService) {
-        this.peopleService = peopleService;
-
-    }
-
-    /**
-     * returns all places that are within a passage reference
-     * 
-     * @param personName the biblical name
-     * @param degreeFromUser the number of generations to lookup
-     * @return the list of places (lat/long/precisions)
-     * 
-     */
-    public JitRelationalPerson getGenealogy(final String personName, final String degreeFromUser) {
-        final int degree = Integer.parseInt(degreeFromUser);
-        final RelationalPerson genealogy = this.peopleService.getGenealogy(personName, degree);
-        return collapseGenealogy(genealogy, degree);
-    }
-
-    /**
-     * 
-     * @param sourcePerson the person we are trying to translate
-     * @param degree the number of generations to ascend
-     * @return the genealogy with the father/mother collapsed into a list
-     */
-    private JitRelationalPerson collapseGenealogy(final RelationalPerson sourcePerson, final int degree) {
-        final JitRelationalPerson p = new JitRelationalPerson();
-        p.setData(sourcePerson.getName());
-        p.setId(sourcePerson.getCode());
-        p.setName(sourcePerson.getName());
-
-        final List<JitRelationalPerson> children = new ArrayList<JitRelationalPerson>();
-
-        if (degree > 0) {
-            final int newDegree = degree - 1;
-            if (sourcePerson.getFather() != null) {
-                children.add(collapseGenealogy(sourcePerson.getFather(), newDegree));
-            }
-
-            if (sourcePerson.getMother() != null) {
-                children.add(collapseGenealogy(sourcePerson.getMother(), newDegree));
-            }
-        }
-
-        p.setChildren(children);
-
-        return p;
-
-    }
-}

Modified: trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/SetupController.java
===================================================================
--- trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/SetupController.java	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/SetupController.java	2012-04-23 11:52:53 UTC (rev 253)
@@ -1,5 +1,7 @@
 package com.tyndalehouse.step.rest.controllers;
 
+import static org.apache.commons.lang.Validate.notNull;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -28,6 +30,9 @@
      */
     @Inject
     public SetupController(final BibleInformationService bibleInformationService, final Loader loader) {
+        notNull(bibleInformationService, "No bible information service was provided");
+        notNull(loader, "No loader module was provided");
+
         this.bibleInformation = bibleInformationService;
         this.loader = loader;
     }
@@ -59,6 +64,7 @@
      * @param reference the initials of the bible to install
      */
     public void installBible(final String reference) {
+        notNull(reference, "A reference must be provided to install a bible");
         LOGGER.debug("Installing module {}", reference);
         this.bibleInformation.installModules(reference);
     }

Modified: trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/TimelineController.java
===================================================================
--- trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/TimelineController.java	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/TimelineController.java	2012-04-23 11:52:53 UTC (rev 253)
@@ -1,6 +1,5 @@
 package com.tyndalehouse.step.rest.controllers;
 
-import java.util.Date;
 import java.util.List;
 
 import org.joda.time.LocalDateTime;
@@ -43,23 +42,6 @@
     }
 
     /**
-     * a REST method to retrieve events between two dates The arrays match in index, and go by three
-     * (timebandId, from, to), (timebandId, from, to), ...
-     * 
-     * @param timebandId the timeband ids
-     * @param from the from dates
-     * @param to the to dates
-     * @return all versions of modules that are considered to be Bibles.
-     *         <p />
-     *         TODO: work out UK date format mappings
-     */
-    @Cacheable(true)
-    public String getEvents(final String[] timebandId, final Date from, final Date to) {
-        LOGGER.debug("Retrieving events between [{}] and [{}]", from, to);
-        return timebandId[0];
-    }
-
-    /**
      * Retrieves events based on a biblical reference. This method also needs to return an origin and a scale
      * for the timeline to be displayed properly as it might well be that the UI has carried out a different
      * search

Copied: trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/framework/FrontController.java (from rev 243, trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/FrontController.java)
===================================================================
--- trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/framework/FrontController.java	                        (rev 0)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/framework/FrontController.java	2012-04-23 11:52:53 UTC (rev 253)
@@ -0,0 +1,354 @@
+package com.tyndalehouse.step.rest.framework;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.codehaus.jackson.JsonGenerationException;
+import org.codehaus.jackson.map.JsonMappingException;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.avaje.ebean.EbeanServer;
+import com.avaje.ebean.text.json.JsonContext;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+import com.tyndalehouse.step.core.exceptions.StepInternalException;
+
+/**
+ * The FrontController acts like a minimal REST server. The paths are resolved as follows:
+ * 
+ * /step-web/rest/controllerName/methodName/arg1/arg2/arg3
+ * 
+ * @author Chris
+ * 
+ */
+ at Singleton
+public class FrontController extends HttpServlet {
+    private static final Logger LOGGER = LoggerFactory.getLogger(FrontController.class);
+    private static final String CONTROLLER_PACKAGE = "com.tyndalehouse.step.rest.controllers";
+    private static final String ENTITIES_PACKAGE = "com.tyndalehouse.step.core.data.entities";
+    private static final String AVAJE_PACKAGE = "com.avaje";
+    private static final String UTF_8_ENCODING = "UTF-8";
+    private static final char PACKAGE_SEPARATOR = '.';
+    private static final long serialVersionUID = 7898656504631346047L;
+    private static final String CONTROLLER_SUFFIX = "Controller";
+    private final transient Injector guiceInjector;
+    // TODO: but also check thread safety and whether we should share this object
+    private final transient ObjectMapper jsonMapper = new ObjectMapper();
+    // TODO: check if this is thread safe, and if so, then make private field
+    private final transient JsonContext ebeanJson;
+
+    // TODO: investigate EH cache here
+    private final Map<String, Method> methodNames = new HashMap<String, Method>();
+    private final Map<String, Object> controllers = new HashMap<String, Object>();
+    private final boolean isCacheEnabled;
+    private final transient ClientErrorResolver errorResolver;
+    private final transient ResponseCache responseCache;
+
+    /**
+     * creates the front controller which will dispatch all the requests
+     * <p />
+     * 
+     * @param guiceInjector the injector used to call the relevant controllers
+     * @param isCacheEnabled indicates whether responses should be cached for fast retrieval
+     * @param ebean the db access/persisitence object
+     * @param errorResolver the error resolver is the object that helps us translate errors for the client
+     * @param responseCache cache in which are put any number of responses to speed up processing
+     */
+    @Inject
+    public FrontController(final Injector guiceInjector,
+            @Named("frontcontroller.cache.enabled") final Boolean isCacheEnabled, final EbeanServer ebean,
+            final ClientErrorResolver errorResolver, final ResponseCache responseCache) {
+        this.guiceInjector = guiceInjector;
+        this.responseCache = responseCache;
+        this.ebeanJson = ebean.createJsonContext();
+
+        this.errorResolver = errorResolver;
+        this.isCacheEnabled = Boolean.TRUE.equals(isCacheEnabled);
+    }
+
+    @Override
+    protected void doGet(final HttpServletRequest request, final HttpServletResponse response) {
+        // first of all check cache against URI: (only cached responses go here)
+        byte[] jsonEncoded = this.responseCache.get(request.getRequestURI());
+
+        StepRequest sr = null;
+        try {
+            // cache miss?
+            if (jsonEncoded == null || jsonEncoded.length == 0) {
+                sr = new StepRequest(request, UTF_8_ENCODING);
+                if (jsonEncoded == null) {
+                    LOGGER.debug("The cache was missed so invoking method now...");
+                    jsonEncoded = invokeMethod(sr);
+                }
+            } else {
+                LOGGER.debug("Returning answer from cache [{}]", request.getRequestURI());
+            }
+
+            setupHeaders(response, jsonEncoded.length);
+            response.getOutputStream().write(jsonEncoded);
+            // CHECKSTYLE:OFF We allow catching errors here, since we are at the top of the structure
+        } catch (final Exception e) {
+            // CHECKSTYLE:ON
+            handleError(response, e, sr);
+        }
+    }
+
+    /**
+     * Invokes the method on the controller instance and returns JSON-ed results
+     * 
+     * @param sr the STEP Request containing all pertinent information
+     * @return byte array representation of the return value
+     */
+    byte[] invokeMethod(final StepRequest sr) {
+
+        // controller instance on which to call a method
+        final Object controllerInstance = getController(sr.getControllerName());
+
+        // resolve method
+        final Method controllerMethod = getControllerMethod(sr.getMethodName(), controllerInstance,
+                sr.getArgs(), sr.getCacheKey().getMethodKey());
+
+        // invoke the three together
+        Object returnVal;
+        try {
+            returnVal = controllerMethod.invoke(controllerInstance, (Object[]) sr.getArgs());
+
+            // CHECKSTYLE:OFF
+        } catch (final Exception e) {
+            returnVal = convertExceptionToJson(e);
+        }
+        final byte[] encodedJsonResponse = getEncodedJsonResponse(returnVal);
+        cache(encodedJsonResponse, sr, controllerMethod);
+        return encodedJsonResponse;
+        // CHECKSTYLE:ON
+    }
+
+    /**
+     * We attempt here to rethrow the exception that caused the invocation target exception, so that we can
+     * handle it nicely for the user
+     * 
+     * @param e the wrapped exception that happened during the reflective call
+     * @return a client handled issue which wraps the exception that was raised
+     */
+    private ClientHandledIssue convertExceptionToJson(final Exception e) {
+        // first we check to see if it's a step exception, or an illegal argument exception
+
+        final Throwable cause = e.getCause();
+        if (cause instanceof StepInternalException) {
+            LOGGER.trace(e.getMessage(), e);
+            return new ClientHandledIssue(cause.getMessage(), this.errorResolver.resolve(cause.getClass()));
+        } else if (cause instanceof IllegalArgumentException) {
+            // a validation exception occurred
+            LOGGER.warn(e.getMessage(), e);
+            return new ClientHandledIssue(cause.getMessage());
+        }
+
+        LOGGER.error(e.getMessage(), e);
+        return new ClientHandledIssue("An internal error has occurred");
+    }
+
+    /**
+     * Returns a json response that is encoded
+     * 
+     * @param responseValue the value that should be encoded
+     * @return the encoded form of the JSON response
+     */
+    byte[] getEncodedJsonResponse(final Object responseValue) {
+        LOGGER.debug("Encoding the following response [{}]", responseValue);
+
+        try {
+            String response;
+            // we have normal objects and avaje ebean objects which have been intercepted
+            // therefore we can't just use simple jackson mapper
+            if (responseValue == null) {
+                return new byte[0];
+            } else {
+                if (isPojo(responseValue)) {
+                    response = this.jsonMapper.writeValueAsString(responseValue);
+                } else {
+                    response = this.ebeanJson.toJsonString(responseValue);
+                }
+            }
+
+            return response.getBytes(UTF_8_ENCODING);
+        } catch (final JsonGenerationException e) {
+            throw new StepInternalException(e.getMessage(), e);
+        } catch (final JsonMappingException e) {
+            throw new StepInternalException(e.getMessage(), e);
+        } catch (final IOException e) {
+            throw new StepInternalException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * inspects the response value to determine the correct serialiser
+     * 
+     * @param responseValue the response value
+     * @return true if normal serialisation should be used
+     */
+    private boolean isPojo(final Object responseValue) {
+        if (responseValue instanceof java.util.Collection<?>) {
+            // inspect what the collection contains...
+            final Collection<?> c = (Collection<?>) responseValue;
+            if (((java.util.Collection<?>) responseValue).size() != 0) {
+                final Object o = c.iterator().next();
+                return isPojo(o);
+            }
+        }
+
+        final String responsePackage = responseValue.getClass().getPackage().getName();
+        return !responsePackage.startsWith(ENTITIES_PACKAGE) && !responsePackage.startsWith(AVAJE_PACKAGE);
+
+    }
+
+    /**
+     * caches the results for future use
+     * 
+     * @param jsonEncoded json encoding of the response
+     * @param sr the processed request URI containg the the cache key
+     * @param controllerMethod the method so that we can inspect whether an annotation is present
+     */
+    void cache(final byte[] jsonEncoded, final StepRequest sr, final Method controllerMethod) {
+        if (this.isCacheEnabled && controllerMethod.isAnnotationPresent(Cacheable.class)) {
+            this.responseCache.put(sr.getCacheKey().getResultsKey(), jsonEncoded);
+        }
+    }
+
+    /**
+     * sets up the headers and the length of the message
+     * 
+     * @param response the response
+     * @param length the length of the message
+     */
+    void setupHeaders(final HttpServletResponse response, final int length) {
+        // we ensure that headers are set up appropriately
+        response.addDateHeader("Date", System.currentTimeMillis());
+        response.setCharacterEncoding(UTF_8_ENCODING);
+        response.setContentType("application/json");
+        response.setContentLength(length);
+    }
+
+    /**
+     * deals with an error whilst executing the request
+     * 
+     * @param response the response
+     * 
+     * @param e the exception
+     * @param sr the step request
+     */
+    void handleError(final HttpServletResponse response, final Throwable e, final StepRequest sr) {
+        String requestId = null;
+        LOGGER.debug("Handling error...");
+        try {
+            requestId = sr == null ? "Failed to parse request?" : sr.getCacheKey().getResultsKey();
+            if (e != null) {
+                final byte[] errorMessage = this.getEncodedJsonResponse(e);
+                response.getOutputStream().write(errorMessage);
+                setupHeaders(response, errorMessage.length);
+
+                LOGGER.error("An internal error has occurred for [{}]", requestId, e);
+            }
+            // CHECKSTYLE:OFF We allow catching errors here, since we are at the top of the structure
+        } catch (final Exception unableToSendError) {
+            // CHECKSTYLE:ON
+            LOGGER.error("Unable to output error for request" + requestId, unableToSendError);
+            LOGGER.error("Due to original Throwable", e);
+        }
+    }
+
+    /**
+     * Retrieves a controller, either from the cache, or from Guice
+     * 
+     * @param controllerName the name of the controller (used as the key for the cache)
+     * @return the controller object
+     */
+    Object getController(final String controllerName) {
+        Object controllerInstance = this.controllers.get(controllerName);
+
+        // if retrieving yields null, get controller from Guice, and put in cache
+        if (controllerInstance == null) {
+            // make up the full class name
+            final String packageName = CONTROLLER_PACKAGE;
+            final StringBuilder className = new StringBuilder(packageName.length() + controllerName.length()
+                    + CONTROLLER_SUFFIX.length() + 1);
+
+            className.append(packageName);
+            className.append(PACKAGE_SEPARATOR);
+            className.append(Character.toUpperCase(controllerName.charAt(0)));
+            className.append(controllerName.substring(1));
+            className.append(CONTROLLER_SUFFIX);
+
+            try {
+                final Class<?> controllerClass = Class.forName(className.toString());
+                controllerInstance = this.guiceInjector.getInstance(controllerClass);
+
+                // we use the controller name as it came in to key the map
+                this.controllers.put(controllerName, controllerInstance);
+            } catch (final ClassNotFoundException e) {
+                throw new StepInternalException("Unable to find a controller for " + className, e);
+            }
+        }
+        return controllerInstance;
+    }
+
+    /**
+     * Returns the method to be invoked upon the controller
+     * 
+     * @param methodName the method name
+     * @param controllerInstance the instance of the controller
+     * @param args the list of arguments, required to resolve the correct method if they have arguments
+     * @param cacheKey the key to retrieve in the cache
+     * @return the method to be invoked
+     */
+    Method getControllerMethod(final String methodName, final Object controllerInstance, final Object[] args,
+            final String cacheKey) {
+        final Class<? extends Object> controllerClass = controllerInstance.getClass();
+
+        // retrieve method from cache, or put in cache if not there
+        Method controllerMethod = this.methodNames.get(cacheKey);
+        if (controllerMethod == null) {
+            // resolve method
+
+            try {
+                final Class<?>[] classes = getClasses(args);
+                controllerMethod = controllerClass.getMethod(methodName, classes);
+                this.methodNames.put(cacheKey, controllerMethod);
+
+                // put method in cache
+            } catch (final NoSuchMethodException e) {
+                throw new StepInternalException(e.getMessage(), e);
+            }
+        }
+
+        return controllerMethod;
+    }
+
+    /**
+     * @param args a number of arguments
+     * @return an array of classes matching the list of arguments passed in
+     */
+    Class<?>[] getClasses(final Object[] args) {
+        if (args == null) {
+            return new Class<?>[0];
+        }
+
+        final Class<?>[] classes = new Class<?>[args.length];
+
+        for (int ii = 0; ii < classes.length; ii++) {
+            classes[ii] = args[ii].getClass();
+        }
+
+        return classes;
+    }
+}

Modified: trunk/step/step-web/src/main/webapp/css/passage.css
===================================================================
--- trunk/step/step-web/src/main/webapp/css/passage.css	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/webapp/css/passage.css	2012-04-23 11:52:53 UTC (rev 253)
@@ -74,11 +74,18 @@
 	color: gray;
 }
 
-SUP.note {
-	font-size: 75%;
-	color: green;
+SUP.note a {
+	font-size: 100%;
+	font-weight: bold;
+	color: #696;
 }
 
+SUP.note a:visited {
+	font-size: 100%;
+	font-weight: bold;
+	color: #696;
+}
+
 FONT.lex {
 	color: red;
 }
@@ -151,14 +158,13 @@
 
 
 
-H3.heading {
+H3.heading  {
 	font-size: 110%;
 	color: #666699;
 	font-weight: bold;
 	clear: both;
 }
 
-
 .editable {
 	border: 1px dashed black;
 }
@@ -206,9 +212,31 @@
 }
 
 
+.notes a {
+	color: #666699;
+	font-weight: bold;
+	front-size: xx-small;
+}
+
+
+
+a.linkRef {
+	color: #696;
+	font-weight: normal;
+	front-size: smaller;
+}
+
+a:hover.linkRef {
+	color: #696;	
+	font-weight: normal;
+	front-size: smaller;
+}
+
+
 /* the following are for dictionary entries */
 FONT.orth {
 	font-weight: bold;
+	
 }
 
 FONT.pron {

Modified: trunk/step/step-web/src/main/webapp/index.html
===================================================================
--- trunk/step/step-web/src/main/webapp/index.html	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/webapp/index.html	2012-04-23 11:52:53 UTC (rev 253)
@@ -32,6 +32,7 @@
     <!-- load the maps - eventually this has to be dynamic, so that no all loading happens -->
     <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
     
+    
     <script src="js/util.js" type="text/javascript"></script>
     <script src="js/passage.js" type="text/javascript"></script>
     <script src="js/bookmark.js" type="text/javascript"></script>

Modified: trunk/step/step-web/src/main/webapp/js/bookmark.js
===================================================================
--- trunk/step/step-web/src/main/webapp/js/bookmark.js	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/webapp/js/bookmark.js	2012-04-23 11:52:53 UTC (rev 253)
@@ -210,9 +210,9 @@
 	
 	if(passageReference && passageReference != "") {
 		var item = "<div class='bookmarkItem'>";
-		item += "<a class='ui-icon ui-icon-arrowthick-1-w bookmarkArrow leftBookmarkArrow' href='#' onclick='$.shout(\"bookmark-triggered-0\", \""+ passageReference + "\");'>&larr;</a>";
+		item += "<a class='ui-icon ui-icon-arrowthick-1-w bookmarkArrow leftBookmarkArrow' href='#' onclick='$.shout(\"new-passage-0\", \""+ passageReference + "\");'>&larr;</a>";
 		item += passageReference;
-		item += "<a class='ui-icon ui-icon-arrowthick-1-e bookmarkArrow rightBookmarkArrow' href='#' onclick='$.shout(\"bookmark-triggered-1\", \""+ passageReference + "\");'>&rarr;</a>";
+		item += "<a class='ui-icon ui-icon-arrowthick-1-e bookmarkArrow rightBookmarkArrow' href='#' onclick='$.shout(\"new-passage-1\", \""+ passageReference + "\");'>&rarr;</a>";
 		item += "</div>";
 		
 		if(ascending) {

Modified: trunk/step/step-web/src/main/webapp/js/passage.js
===================================================================
--- trunk/step/step-web/src/main/webapp/js/passage.js	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/webapp/js/passage.js	2012-04-23 11:52:53 UTC (rev 253)
@@ -35,7 +35,7 @@
 	
 
 	//register when we want to be alerted that a bookmark has changed
-	this.passage.hear("bookmark-triggered-" + this.passageId, function(selfElement, data) {
+	this.passage.hear("new-passage-" + this.passageId, function(selfElement, data) {
 		self.reference.val(data);
 		self.changePassage();
 	});

Modified: trunk/step/step-web/src/main/webapp/js/timeline.js
===================================================================
--- trunk/step/step-web/src/main/webapp/js/timeline.js	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/webapp/js/timeline.js	2012-04-23 11:52:53 UTC (rev 253)
@@ -38,6 +38,7 @@
 	
 	//set up the theme
 	this.theme = Timeline.ClassicTheme.create();
+	
     this.theme.event.bubble.width = 250;
     this.eventSource = new Timeline.DefaultEventSource();
      
@@ -64,6 +65,20 @@
 				    intervalPixels: 100,
 				    zones:          zones,
 				    eventSource:    self.eventSource,
+				    zoomIndex: 10,
+				    zoomSteps: new Array(
+		              {pixelsPerInterval: 280,  unit: Timeline.DateTime.HOUR},
+		              {pixelsPerInterval: 140,  unit: Timeline.DateTime.HOUR},
+		              {pixelsPerInterval:  70,  unit: Timeline.DateTime.HOUR},
+		              {pixelsPerInterval:  35,  unit: Timeline.DateTime.HOUR},
+		              {pixelsPerInterval: 400,  unit: Timeline.DateTime.DAY},
+		              {pixelsPerInterval: 200,  unit: Timeline.DateTime.DAY},
+		              {pixelsPerInterval: 100,  unit: Timeline.DateTime.DAY},
+		              {pixelsPerInterval:  50,  unit: Timeline.DateTime.DAY},
+		              {pixelsPerInterval: 400,  unit: Timeline.DateTime.MONTH},
+		              {pixelsPerInterval: 200,  unit: Timeline.DateTime.MONTH},
+		              {pixelsPerInterval: 100,  unit: Timeline.DateTime.MONTH} 
+				    )
 				}),
 		        
 		        Timeline.createBandInfo({
@@ -244,6 +259,21 @@
 		mainBand.scrollToCenter(mainBand.getMaxVisibleDate());
 	});
 	
+	$("#bottomModuleHeader #zoomInTimeline").click(function() {
+		var mainBand = self.tl.getBand(0);
+		mainBand.zoom(true);
+		self.tl.paint();
+		self.tl.layout();
+	});
+
+	$("#bottomModuleHeader #zoomOutTimeline").click(function() {
+		var mainBand = self.tl.getBand(0);
+		mainBand.zoom(false);
+		self.tl.paint();
+		self.tl.layout();
+	});
+	
+	
 	$("#bottomModuleHeader #linkToPassage").click(function() {
 		if($(this).text() === "Link to passage") {
 			$(this).button("option", {icons: { primary: "ui-icon-pin-s" }, label: "Unlink from passage"});
@@ -261,7 +291,8 @@
 	$("#bottomModuleHeader #scrollTimelineToDate").click(function() {
 		var datePopup = $("#goToDate");
 		$("#scrollToYear", datePopup).val(self.tl.getBand(0).getCenterVisibleDate().getFullYear());
-		datePopup.dialog({ modal: true, 
+		datePopup.dialog({ title: "Please enter a year:",
+						   modal: true, 
 						   buttons : [{ 
 						            	  text: "OK", 
 						            	  click: function() { 
@@ -330,6 +361,7 @@
 	this.fillDescription(divBody); 
 	theme.event.bubble.bodyStyler(divBody); 
 	elmt.appendChild(divBody); 
+	
 	// This is where they define the times in the bubble
 	var divTime = doc.createElement("div"); 
 	divTime.innerHTML = start + " - " + end; 

Modified: trunk/step/step-web/src/main/webapp/js/toolbar_menu.js
===================================================================
--- trunk/step/step-web/src/main/webapp/js/toolbar_menu.js	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/webapp/js/toolbar_menu.js	2012-04-23 11:52:53 UTC (rev 253)
@@ -126,6 +126,7 @@
 ToolbarMenu.prototype.setDefaultOptions = function() {
 	this.toggleMenuItem("HEADINGS");
 	this.toggleMenuItem("VERSE_NUMBERS");
+	this.toggleMenuItem("NOTES");
 }
 
 /**

Modified: trunk/step/step-web/src/main/webapp/js/ui_hooks.js
===================================================================
--- trunk/step/step-web/src/main/webapp/js/ui_hooks.js	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/webapp/js/ui_hooks.js	2012-04-23 11:52:53 UTC (rev 253)
@@ -63,6 +63,10 @@
 	$.shout("pane-menu-toggle-item-" + getPassageId(menuItem), menuItem.name);
 };
 
+function changePassage(element, passageReference) {
+	$.shout("new-passage-" + getPassageId(element), passageReference);
+}
+
 /**
  * shows the login popup
  */

Modified: trunk/step/step-web/src/main/webapp/panemenu.html
===================================================================
--- trunk/step/step-web/src/main/webapp/panemenu.html	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/main/webapp/panemenu.html	2012-04-23 11:52:53 UTC (rev 253)
@@ -3,6 +3,7 @@
 	<ul>
 		<li><a href="#"  name="HEADINGS" onclick="toggleMenuItem(this);">Headings</a></li>
 		<li><a href="#" name="VERSE_NUMBERS" onclick="toggleMenuItem(this);">Verse Numbers</a></li>
+		<li><a href="#" name="NOTES" onclick="toggleMenuItem(this);">Notes and References</a></li>
 		<li><a href="#" name="STRONG_NUMBERS" onclick="toggleMenuItem(this);">Strong Numbers</a></li>
 		<li><a href="#" name="MORPHOLOGY" onclick="toggleMenuItem(this);">Morphology</a></li>
 		<li><a href="#" name="INTERLINEAR" onclick="showInterlinearChoices(this);" >Interlinear...</a></li>

Deleted: trunk/step/step-web/src/test/java/com/tyndalehouse/step/rest/controllers/FrontControllerTest.java
===================================================================
--- trunk/step/step-web/src/test/java/com/tyndalehouse/step/rest/controllers/FrontControllerTest.java	2012-04-20 19:43:48 UTC (rev 252)
+++ trunk/step/step-web/src/test/java/com/tyndalehouse/step/rest/controllers/FrontControllerTest.java	2012-04-23 11:52:53 UTC (rev 253)
@@ -1,237 +0,0 @@
-package com.tyndalehouse.step.rest.controllers;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-
-import com.avaje.ebean.EbeanServer;
-import com.google.inject.Injector;
-import com.tyndalehouse.step.core.exceptions.StepInternalException;
-import com.tyndalehouse.step.core.service.BibleInformationService;
-import com.tyndalehouse.step.rest.framework.ClientErrorResolver;
-import com.tyndalehouse.step.rest.framework.ControllerCacheKey;
-import com.tyndalehouse.step.rest.framework.ResponseCache;
-import com.tyndalehouse.step.rest.framework.StepRequest;
-
-/**
- * tests the front controller parsing process
- * 
- * @author Chris
- * 
- */
- at SuppressWarnings("PMD.TooManyMethods")
- at RunWith(MockitoJUnitRunner.class)
-public class FrontControllerTest {
-    private FrontController fcUnderTest;
-    @Mock
-    private Injector guiceInjector;
-
-    private final Boolean isCacheEnabled = Boolean.FALSE;
-
-    @Mock
-    private EbeanServer ebean;
-    @Mock
-    private ClientErrorResolver errorResolver;
-    @Mock
-    private ResponseCache responseCache;
-    @Mock
-    private StepRequest stepRequest;
-
-    /**
-     * Simply setting up the FrontController under test
-     */
-    @Before
-    public void setUp() {
-        this.fcUnderTest = new FrontController(this.guiceInjector, this.isCacheEnabled, this.ebean,
-                this.errorResolver, this.responseCache);
-    }
-
-    /**
-     * Tests normal operation of a GET method
-     * 
-     * @throws IOException uncaught exception
-     */
-    @Test
-    public void testDoGet() throws IOException {
-        final HttpServletRequest req = mock(HttpServletRequest.class);
-        final HttpServletResponse response = mock(HttpServletResponse.class);
-        final String sampleRequest = "step-web/rest/bible/get/1K2/2K2/";
-
-        when(req.getRequestURI()).thenReturn(sampleRequest);
-        when(req.getServletPath()).thenReturn("step-web/");
-        when(req.getContextPath()).thenReturn("rest/");
-
-        final FrontController fc = spy(this.fcUnderTest);
-
-        final ServletOutputStream mockOutputStream = mock(ServletOutputStream.class);
-
-        doReturn(mockOutputStream).when(response).getOutputStream();
-        final byte[] sampleResponse = new byte[] { 1, 2, 3 };
-        doReturn(sampleResponse).when(fc).invokeMethod(any(StepRequest.class));
-
-        // do the test
-        fc.doGet(req, response);
-        verify(mockOutputStream).write(sampleResponse);
-    }
-
-    /**
-     * tests what happens when doGet catches an exception
-     */
-    @Test
-    public void testDoGetHasException() {
-        final HttpServletRequest request = mock(HttpServletRequest.class);
-        final HttpServletResponse response = mock(HttpServletResponse.class);
-        final StepInternalException testException = new StepInternalException("A test exception");
-
-        final FrontController fc = spy(this.fcUnderTest);
-        final StepRequest parsedRequest = new StepRequest("blah", "SomeController", "someMethod",
-                new String[] { "arg1", "arg2" });
-
-        // TODO remove this/
-        // doThrow(testException).when(fc).parseRequest(request);
-        doNothing().when(fc).handleError(response, testException, parsedRequest);
-
-        // do the test
-        fc.doGet(request, response);
-
-    }
-
-    /**
-     * tests that the headers are setup correctly
-     */
-    @Test
-    public void testHeadersSetupCorrectly() {
-        final HttpServletResponse response = mock(HttpServletResponse.class);
-
-        final int sampleRequestLength = 10;
-        this.fcUnderTest.setupHeaders(response, sampleRequestLength);
-
-        verify(response).addDateHeader(eq("Date"), anyLong());
-        verify(response).setCharacterEncoding("UTF-8");
-        verify(response).setContentType("application/json");
-        verify(response).setContentLength(sampleRequestLength);
-
-    }
-
-    /**
-     * tests that resolving method works
-     * 
-     * @throws IllegalAccessException uncaught exception
-     * @throws InvocationTargetException uncaught exception
-     */
-    @Test
-    public void testGetControllerMethod() throws IllegalAccessException, InvocationTargetException {
-        final BibleInformationService bibleInfo = mock(BibleInformationService.class);
-        final BibleController controllerInstance = new BibleController(bibleInfo);
-
-        // when
-        final Method controllerMethod = this.fcUnderTest.getControllerMethod("getBibleVersions",
-                controllerInstance, null, null);
-
-        // then
-        controllerMethod.invoke(controllerInstance);
-        verify(bibleInfo).getAvailableBibleVersions();
-    }
-
-    /**
-     * tests the get controller method
-     */
-    @Test
-    public void testGetController() {
-        final String controllerName = "Bible";
-        final BibleController mockController = mock(BibleController.class);
-        when(this.guiceInjector.getInstance(BibleController.class)).thenReturn(mockController);
-
-        // when
-        final Object controller = this.fcUnderTest.getController(controllerName);
-
-        // then
-        assertEquals(controller.getClass(), mockController.getClass());
-    }
-
-    /**
-     * tests various combinations for getClasses
-     */
-    @Test
-    public void testGetClasses() {
-        assertEquals(0, this.fcUnderTest.getClasses(null).length);
-        assertEquals(0, this.fcUnderTest.getClasses(new Object[0]).length);
-        assertArrayEquals(new Class<?>[] { String.class, Integer.class },
-                this.fcUnderTest.getClasses(new Object[] { "hello", Integer.valueOf(1) }));
-
-    }
-
-    /**
-     * tests that we encode using json mapper and set to UTF 8
-     */
-    @Test
-    public void testJsonEncoding() {
-        final byte[] encodedJsonResponse = this.fcUnderTest.getEncodedJsonResponse("abc");
-
-        // this reprensents the string "{abc}"
-        final byte[] expectedValues = new byte[] { 34, 97, 98, 99, 34 };
-
-        assertArrayEquals(expectedValues, encodedJsonResponse);
-    }
-
-    /**
-     * If an error was thrown, we should map it and output
-     * 
-     * @throws IOException uncaught exception
-     */
-    @Test
-    public void testDoErrorHandlesCorrectly() throws IOException {
-        final HttpServletResponse response = mock(HttpServletResponse.class);
-        // final StepRequest stepRequest = new StepRequest("blah", "controller", "method", null);
-        final ServletOutputStream outputStream = mock(ServletOutputStream.class);
-        final Throwable exception = new Exception();
-        when(response.getOutputStream()).thenReturn(outputStream);
-        when(this.stepRequest.getCacheKey()).thenReturn(new ControllerCacheKey("method", "results"));
-
-        // do test
-        this.fcUnderTest.handleError(response, exception, this.stepRequest);
-
-        // check
-        verify(outputStream).write(any(byte[].class));
-    }
-
-    /**
-     * We check that invoke method calls the correct controller and method with the right arguments
-     */
-    @Test
-    public void testInvokeMethod() {
-        final StepRequest sr = new StepRequest("blah", "bible", "getAllFeatures", new String[] {});
-        final BibleController testController = mock(BibleController.class);
-
-        final FrontController fc = spy(this.fcUnderTest);
-        doReturn(testController).when(fc).getController("bible");
-
-        // do test
-        fc.invokeMethod(sr);
-
-        // verify
-        verify(testController).getAllFeatures();
-    }
-}

Copied: trunk/step/step-web/src/test/java/com/tyndalehouse/step/rest/framework/FrontControllerTest.java (from rev 243, trunk/step/step-web/src/test/java/com/tyndalehouse/step/rest/controllers/FrontControllerTest.java)
===================================================================
--- trunk/step/step-web/src/test/java/com/tyndalehouse/step/rest/framework/FrontControllerTest.java	                        (rev 0)
+++ trunk/step/step-web/src/test/java/com/tyndalehouse/step/rest/framework/FrontControllerTest.java	2012-04-23 11:52:53 UTC (rev 253)
@@ -0,0 +1,239 @@
+package com.tyndalehouse.step.rest.framework;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import com.avaje.ebean.EbeanServer;
+import com.google.inject.Injector;
+import com.tyndalehouse.step.core.exceptions.StepInternalException;
+import com.tyndalehouse.step.core.service.BibleInformationService;
+import com.tyndalehouse.step.rest.controllers.BibleController;
+import com.tyndalehouse.step.rest.framework.ClientErrorResolver;
+import com.tyndalehouse.step.rest.framework.ControllerCacheKey;
+import com.tyndalehouse.step.rest.framework.FrontController;
+import com.tyndalehouse.step.rest.framework.ResponseCache;
+import com.tyndalehouse.step.rest.framework.StepRequest;
+
+/**
+ * tests the front controller parsing process
+ * 
+ * @author Chris
+ * 
+ */
+ at SuppressWarnings("PMD.TooManyMethods")
+ at RunWith(MockitoJUnitRunner.class)
+public class FrontControllerTest {
+    private FrontController fcUnderTest;
+    @Mock
+    private Injector guiceInjector;
+
+    private final Boolean isCacheEnabled = Boolean.FALSE;
+
+    @Mock
+    private EbeanServer ebean;
+    @Mock
+    private ClientErrorResolver errorResolver;
+    @Mock
+    private ResponseCache responseCache;
+    @Mock
+    private StepRequest stepRequest;
+
+    /**
+     * Simply setting up the FrontController under test
+     */
+    @Before
+    public void setUp() {
+        this.fcUnderTest = new FrontController(this.guiceInjector, this.isCacheEnabled, this.ebean,
+                this.errorResolver, this.responseCache);
+    }
+
+    /**
+     * Tests normal operation of a GET method
+     * 
+     * @throws IOException uncaught exception
+     */
+    @Test
+    public void testDoGet() throws IOException {
+        final HttpServletRequest req = mock(HttpServletRequest.class);
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+        final String sampleRequest = "step-web/rest/bible/get/1K2/2K2/";
+
+        when(req.getRequestURI()).thenReturn(sampleRequest);
+        when(req.getServletPath()).thenReturn("step-web/");
+        when(req.getContextPath()).thenReturn("rest/");
+
+        final FrontController fc = spy(this.fcUnderTest);
+
+        final ServletOutputStream mockOutputStream = mock(ServletOutputStream.class);
+
+        doReturn(mockOutputStream).when(response).getOutputStream();
+        final byte[] sampleResponse = new byte[] { 1, 2, 3 };
+        doReturn(sampleResponse).when(fc).invokeMethod(any(StepRequest.class));
+
+        // do the test
+        fc.doGet(req, response);
+        verify(mockOutputStream).write(sampleResponse);
+    }
+
+    /**
+     * tests what happens when doGet catches an exception
+     */
+    @Test
+    public void testDoGetHasException() {
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+        final StepInternalException testException = new StepInternalException("A test exception");
+
+        final FrontController fc = spy(this.fcUnderTest);
+        final StepRequest parsedRequest = new StepRequest("blah", "SomeController", "someMethod",
+                new String[] { "arg1", "arg2" });
+
+        // TODO remove this/
+        // doThrow(testException).when(fc).parseRequest(request);
+        doNothing().when(fc).handleError(response, testException, parsedRequest);
+
+        // do the test
+        fc.doGet(request, response);
+
+    }
+
+    /**
+     * tests that the headers are setup correctly
+     */
+    @Test
+    public void testHeadersSetupCorrectly() {
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+
+        final int sampleRequestLength = 10;
+        this.fcUnderTest.setupHeaders(response, sampleRequestLength);
+
+        verify(response).addDateHeader(eq("Date"), anyLong());
+        verify(response).setCharacterEncoding("UTF-8");
+        verify(response).setContentType("application/json");
+        verify(response).setContentLength(sampleRequestLength);
+
+    }
+
+    /**
+     * tests that resolving method works
+     * 
+     * @throws IllegalAccessException uncaught exception
+     * @throws InvocationTargetException uncaught exception
+     */
+    @Test
+    public void testGetControllerMethod() throws IllegalAccessException, InvocationTargetException {
+        final BibleInformationService bibleInfo = mock(BibleInformationService.class);
+        final BibleController controllerInstance = new BibleController(bibleInfo);
+
+        // when
+        final Method controllerMethod = this.fcUnderTest.getControllerMethod("getBibleVersions",
+                controllerInstance, null, null);
+
+        // then
+        controllerMethod.invoke(controllerInstance);
+        verify(bibleInfo).getAvailableBibleVersions();
+    }
+
+    /**
+     * tests the get controller method
+     */
+    @Test
+    public void testGetController() {
+        final String controllerName = "Bible";
+        final BibleController mockController = mock(BibleController.class);
+        when(this.guiceInjector.getInstance(BibleController.class)).thenReturn(mockController);
+
+        // when
+        final Object controller = this.fcUnderTest.getController(controllerName);
+
+        // then
+        assertEquals(controller.getClass(), mockController.getClass());
+    }
+
+    /**
+     * tests various combinations for getClasses
+     */
+    @Test
+    public void testGetClasses() {
+        assertEquals(0, this.fcUnderTest.getClasses(null).length);
+        assertEquals(0, this.fcUnderTest.getClasses(new Object[0]).length);
+        assertArrayEquals(new Class<?>[] { String.class, Integer.class },
+                this.fcUnderTest.getClasses(new Object[] { "hello", Integer.valueOf(1) }));
+
+    }
+
+    /**
+     * tests that we encode using json mapper and set to UTF 8
+     */
+    @Test
+    public void testJsonEncoding() {
+        final byte[] encodedJsonResponse = this.fcUnderTest.getEncodedJsonResponse("abc");
+
+        // this reprensents the string "{abc}"
+        final byte[] expectedValues = new byte[] { 34, 97, 98, 99, 34 };
+
+        assertArrayEquals(expectedValues, encodedJsonResponse);
+    }
+
+    /**
+     * If an error was thrown, we should map it and output
+     * 
+     * @throws IOException uncaught exception
+     */
+    @Test
+    public void testDoErrorHandlesCorrectly() throws IOException {
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+        // final StepRequest stepRequest = new StepRequest("blah", "controller", "method", null);
+        final ServletOutputStream outputStream = mock(ServletOutputStream.class);
+        final Throwable exception = new Exception();
+        when(response.getOutputStream()).thenReturn(outputStream);
+        when(this.stepRequest.getCacheKey()).thenReturn(new ControllerCacheKey("method", "results"));
+
+        // do test
+        this.fcUnderTest.handleError(response, exception, this.stepRequest);
+
+        // check
+        verify(outputStream).write(any(byte[].class));
+    }
+
+    /**
+     * We check that invoke method calls the correct controller and method with the right arguments
+     */
+    @Test
+    public void testInvokeMethod() {
+        final StepRequest sr = new StepRequest("blah", "bible", "getAllFeatures", new String[] {});
+        final BibleController testController = mock(BibleController.class);
+
+        final FrontController fc = spy(this.fcUnderTest);
+        doReturn(testController).when(fc).getController("bible");
+
+        // do test
+        fc.invokeMethod(sr);
+
+        // verify
+        verify(testController).getAllFeatures();
+    }
+}




More information about the Tynstep-svn mailing list