[Tynstep-svn] r225 - in trunk/step: step-build/src/main/resources/checkstyle step-build/src/main/resources/eclipse step-core step-core/src/main/java/com/tyndalehouse/step/core/data/create step-core/src/main/java/com/tyndalehouse/step/core/data/entities step-core/src/main/java/com/tyndalehouse/step/core/guice step-core/src/main/java/com/tyndalehouse/step/core/guice/providers step-core/src/main/java/com/tyndalehouse/step/core/service step-core/src/main/java/com/tyndalehouse/step/core/service/impl step-core/src/main/java/com/tyndalehouse/step/core/utils step-core/src/main/java/com/tyndalehouse/step/core/xsl/impl step-core/src/main/resources step-core/src/test/java step-core/src/test/java/com/tyndalehouse/step/core/data step-core/src/test/java/com/tyndalehouse/step/core/data/create step-core/src/test/java/com/tyndalehouse/step/core/data/entities step-core/src/test/java/com/tyndalehouse/step/core/service/impl step-core/src/test/java/com/tyndalehouse/step/core/xsl/impl step-core/src/test/resources step-parent step-web/src/main/java/com/tyndalehouse/step/models 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/js step-web/src/test step-web/src/test/java/com/tyndalehouse/step/rest/controllers

ChrisBurrell at crosswire.org ChrisBurrell at crosswire.org
Sat Mar 26 10:01:59 MST 2011


Author: ChrisBurrell
Date: 2011-03-26 10:01:58 -0700 (Sat, 26 Mar 2011)
New Revision: 225

Added:
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/History.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/FavouritesService.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/FavouritesServiceImpl.java
   trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/DataDrivenTestExtension.java
   trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/FavouritesServiceImplTest.java
   trunk/step/step-core/src/test/resources/ebean.properties
   trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/FavouritesController.java
   trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/framework/StepJsonValueAdapter.java
   trunk/step/step-web/src/test/resources/
Removed:
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/BookmarkService.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/BookmarkServiceImpl.java
   trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/AbstractDataTest.java
   trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/BookmarkServiceImplTest.java
   trunk/step/step-core/src/test/java/log4j.properties
   trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/BookmarkController.java
   trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/framework/Cached.java
Modified:
   trunk/step/step-build/src/main/resources/checkstyle/checkstyle.test.xml
   trunk/step/step-build/src/main/resources/eclipse/org.eclipse.jdt.core.prefs
   trunk/step/step-build/src/main/resources/eclipse/pmd.xml
   trunk/step/step-core/pom.xml
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/create/TimelineModuleLoader.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/Bookmark.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/HotSpot.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/ScriptureReference.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/ScriptureTarget.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/Session.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/Timeband.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/TimelineEvent.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/User.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/readme.txt
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/StepCoreModule.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/DatabaseConfigProvider.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/DefaultLexiconRefsProvider.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/DefaultVersionsProvider.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/ServerSessionProvider.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/TestData.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/UserDataService.java
   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/UserDataServiceImpl.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/utils/JSwordUtils.java
   trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/xsl/impl/InterlinearProviderImpl.java
   trunk/step/step-core/src/main/resources/ebean.properties
   trunk/step/step-core/src/main/resources/step.core.properties
   trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/create/DataTest.java
   trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/entities/UserTest.java
   trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/UserDataServiceImplTest.java
   trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/xsl/impl/InterlinearProviderImplTest.java
   trunk/step/step-core/src/test/resources/log4j.properties
   trunk/step/step-parent/pom.xml
   trunk/step/step-web/src/main/java/com/tyndalehouse/step/models/WebSessionImpl.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/FrontController.java
   trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/UserController.java
   trunk/step/step-web/src/main/webapp/js/bookmark.js
   trunk/step/step-web/src/main/webapp/js/login.js
   trunk/step/step-web/src/main/webapp/js/ui_hooks.js
   trunk/step/step-web/src/test/java/com/tyndalehouse/step/rest/controllers/FrontControllerTest.java
Log:
updating with history merging functionality

Modified: trunk/step/step-build/src/main/resources/checkstyle/checkstyle.test.xml
===================================================================
--- trunk/step/step-build/src/main/resources/checkstyle/checkstyle.test.xml	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-build/src/main/resources/checkstyle/checkstyle.test.xml	2011-03-26 17:01:58 UTC (rev 225)
@@ -169,7 +169,9 @@
     <module name="CyclomaticComplexity"/>
     <module name="ArrayTypeStyle"/>
     <module name="FinalParameters"/>
-    <module name="Indentation"/>
+<!--       this should be covered by eclipse's reformatter profile 
+ 	<module name="Indentation"/>
+ -->
     <module name="TodoComment"/>
     <module name="TrailingComment"/>
     <module name="UncommentedMain"/>

Modified: trunk/step/step-build/src/main/resources/eclipse/org.eclipse.jdt.core.prefs
===================================================================
--- trunk/step/step-build/src/main/resources/eclipse/org.eclipse.jdt.core.prefs	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-build/src/main/resources/eclipse/org.eclipse.jdt.core.prefs	2011-03-26 17:01:58 UTC (rev 225)
@@ -276,3 +276,7 @@
 org.eclipse.jdt.core.formatter.tabulation.size=4
 org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
 org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=ignore

Modified: trunk/step/step-build/src/main/resources/eclipse/pmd.xml
===================================================================
--- trunk/step/step-build/src/main/resources/eclipse/pmd.xml	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-build/src/main/resources/eclipse/pmd.xml	2011-03-26 17:01:58 UTC (rev 225)
@@ -244,10 +244,6 @@
             <ruleset>Controversial Rules</ruleset>
         </rule>
         <rule>
-            <name>AvoidUsingVolatile</name>
-            <ruleset>Controversial Rules</ruleset>
-        </rule>
-        <rule>
             <name>AvoidAccessibilityAlteration</name>
             <ruleset>Controversial Rules</ruleset>
         </rule>

Modified: trunk/step/step-core/pom.xml
===================================================================
--- trunk/step/step-core/pom.xml	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/pom.xml	2011-03-26 17:01:58 UTC (rev 225)
@@ -100,6 +100,11 @@
 			<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/data/create/TimelineModuleLoader.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/create/TimelineModuleLoader.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/create/TimelineModuleLoader.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -95,6 +95,7 @@
     private Map<String, HotSpot> loadHotSpots(final Map<String, Timeband> bands) {
         LOG.debug("Loading hot spot data");
         final Map<String, HotSpot> hotSpots = load(HOTSPOTS_CSV_DATA_FILE, new CsvDataMapper<HotSpot>() {
+            @Override
             public HotSpot mapRow(final int rowNum, final CsvData data) {
                 final HotSpot hs = new HotSpot();
                 hs.setCode(data.getData(rowNum, HOTSPOT_CODE_COLUMN));
@@ -116,6 +117,7 @@
     private Map<String, Timeband> loadTimebands() {
         LOG.debug("Loading timeband data");
         final Map<String, Timeband> timebands = load(TIMEBAND_CSV_DATA_FILE, new CsvDataMapper<Timeband>() {
+            @Override
             public Timeband mapRow(final int rowNum, final CsvData data) {
                 final Timeband timeband = new Timeband();
                 timeband.setCode(data.getData(rowNum, TIMEBAND_CODE_COLUMN));

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/Bookmark.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/Bookmark.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/Bookmark.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,5 +1,7 @@
 package com.tyndalehouse.step.core.data.entities;
 
+import java.io.Serializable;
+
 import javax.persistence.CascadeType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -14,16 +16,18 @@
  * 
  */
 @Entity
-public class Bookmark {
+public class Bookmark implements Serializable {
+    private static final long serialVersionUID = 537098392958960964L;
+
     @Id
     @GeneratedValue
     private Integer id;
 
-    @Column
+    @Column(nullable = false)
     private String bookmarkReference;
 
     @ManyToOne(cascade = CascadeType.PERSIST)
-    @Column
+    @Column(nullable = false)
     private User user;
 
     /**

Added: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/History.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/History.java	                        (rev 0)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/History.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -0,0 +1,95 @@
+package com.tyndalehouse.step.core.data.entities;
+
+import java.io.Serializable;
+import java.sql.Timestamp;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+/**
+ * A user may have multiple history items, these are always ordered by the last inserted data however
+ * 
+ * @author Chris
+ * 
+ */
+ at Entity
+public class History implements Serializable {
+    private static final long serialVersionUID = 2983314321961626288L;
+
+    @Id
+    @GeneratedValue
+    private Integer id;
+
+    @Column(nullable = false)
+    private String historyReference;
+
+    @ManyToOne(cascade = CascadeType.PERSIST)
+    @Column(nullable = false)
+    private User user;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(nullable = false)
+    private Timestamp lastUpdated;
+
+    /**
+     * @return the id
+     */
+    public Integer getId() {
+        return this.id;
+    }
+
+    /**
+     * @param id the id to set
+     */
+    public void setId(final Integer id) {
+        this.id = id;
+    }
+
+    /**
+     * @return the user
+     */
+    public User getUser() {
+        return this.user;
+    }
+
+    /**
+     * @param user the user to set
+     */
+    public void setUser(final User user) {
+        this.user = user;
+    }
+
+    /**
+     * @return the historyReference
+     */
+    public String getHistoryReference() {
+        return this.historyReference;
+    }
+
+    /**
+     * @param historyReference the historyReference to set
+     */
+    public void setHistoryReference(final String historyReference) {
+        this.historyReference = historyReference;
+    }
+
+    /**
+     * @return the lastUpdated
+     */
+    public Timestamp getLastUpdated() {
+        return this.lastUpdated;
+    }
+
+    /**
+     * @param lastUpdated the lastUpdated to set
+     */
+    public void setLastUpdated(final Timestamp lastUpdated) {
+        this.lastUpdated = lastUpdated;
+    }
+}


Property changes on: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/History.java
___________________________________________________________________
Added: svn:mime-type
   + text/plain

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/HotSpot.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/HotSpot.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/HotSpot.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,5 +1,6 @@
 package com.tyndalehouse.step.core.data.entities;
 
+import java.io.Serializable;
 import java.util.List;
 
 import javax.persistence.CascadeType;
@@ -21,7 +22,9 @@
  */
 @CacheStrategy(readOnly = true)
 @Entity
-public class HotSpot implements KeyedEntity {
+public class HotSpot implements KeyedEntity, Serializable {
+    private static final long serialVersionUID = -7904172771680747618L;
+
     @Id
     @GeneratedValue
     private Integer id;
@@ -72,6 +75,7 @@
     /**
      * @return the code
      */
+    @Override
     public String getCode() {
         return this.code;
     }

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/ScriptureReference.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/ScriptureReference.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/ScriptureReference.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,5 +1,7 @@
 package com.tyndalehouse.step.core.data.entities;
 
+import java.io.Serializable;
+
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.GeneratedValue;
@@ -14,7 +16,9 @@
  * @author Chris
  */
 @Entity
-public class ScriptureReference {
+public class ScriptureReference implements Serializable {
+    private static final long serialVersionUID = -3854523992102175988L;
+
     @Id
     @GeneratedValue
     private Integer scriptureReferenceId;

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/ScriptureTarget.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/ScriptureTarget.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/ScriptureTarget.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,5 +1,7 @@
 package com.tyndalehouse.step.core.data.entities;
 
+import java.io.Serializable;
+
 import javax.persistence.DiscriminatorColumn;
 import javax.persistence.DiscriminatorType;
 import javax.persistence.Entity;
@@ -20,7 +22,8 @@
 @Entity
 @Inheritance(strategy = InheritanceType.JOINED)
 @DiscriminatorColumn(discriminatorType = DiscriminatorType.INTEGER, name = "targetTypeId")
-public class ScriptureTarget {
+public class ScriptureTarget implements Serializable {
+    private static final long serialVersionUID = -3343458338757180529L;
     @Id
     @GeneratedValue
     private Integer id;

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/Session.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/Session.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/Session.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,5 +1,6 @@
 package com.tyndalehouse.step.core.data.entities;
 
+import java.io.Serializable;
 import java.util.Date;
 
 import javax.persistence.CascadeType;
@@ -19,7 +20,9 @@
  * 
  */
 @Entity
-public class Session {
+public class Session implements Serializable {
+    private static final long serialVersionUID = 6232919302889735151L;
+
     @Id
     @GeneratedValue
     private Integer id;

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/Timeband.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/Timeband.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/Timeband.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,5 +1,6 @@
 package com.tyndalehouse.step.core.data.entities;
 
+import java.io.Serializable;
 import java.util.List;
 
 import javax.persistence.CascadeType;
@@ -19,7 +20,9 @@
  */
 @CacheStrategy(readOnly = true)
 @Entity
-public class Timeband implements KeyedEntity {
+public class Timeband implements KeyedEntity, Serializable {
+    private static final long serialVersionUID = 8217910739779785032L;
+
     @Id
     @GeneratedValue
     private Integer id;
@@ -50,6 +53,7 @@
     /**
      * @return the code
      */
+    @Override
     public String getCode() {
         return this.code;
     }

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/TimelineEvent.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/TimelineEvent.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/TimelineEvent.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,5 +1,7 @@
 package com.tyndalehouse.step.core.data.entities;
 
+import java.io.Serializable;
+
 import javax.persistence.CascadeType;
 import javax.persistence.Column;
 import javax.persistence.DiscriminatorValue;
@@ -17,7 +19,9 @@
 @CacheStrategy(readOnly = true)
 @Entity
 @DiscriminatorValue("1")
-public class TimelineEvent extends ScriptureTarget {
+public class TimelineEvent extends ScriptureTarget implements Serializable {
+    private static final long serialVersionUID = -4642904574412249515L;
+
     @Column
     private String summary;
 
@@ -120,4 +124,26 @@
         this.hotSpot = hotSpot;
     }
 
+    /**
+     * to get rid of a findbugs bug, we override to make clear we are using the parent's equal method
+     * 
+     * @param obj the object that we are comparing
+     * @return true if objects are equals
+     */
+    @SuppressWarnings("PMD.UselessOverridingMethod")
+    @Override
+    public boolean equals(final Object obj) {
+        return super.equals(obj);
+    }
+
+    /**
+     * overriding the hashcode because we've override the equals
+     * 
+     * @return the parent's hashcode
+     */
+    @SuppressWarnings("PMD.UselessOverridingMethod")
+    @Override
+    public int hashCode() {
+        return super.hashCode();
+    }
 }

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/User.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/User.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/User.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,5 +1,7 @@
 package com.tyndalehouse.step.core.data.entities;
 
+import java.io.Serializable;
+
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.GeneratedValue;
@@ -14,7 +16,9 @@
  */
 @Entity
 @Table(name = "users")
-public class User {
+public class User implements Serializable {
+    private static final long serialVersionUID = 6221804892435479330L;
+
     @Id
     @GeneratedValue
     private Integer id;

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/readme.txt
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/readme.txt	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/data/entities/readme.txt	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,2 +1,3 @@
 Entities currently need to be added to 
-com.tyndalehouse.step.core.guice.providers.DatabaseConfigProvider.addEntities(ServerConfig)
\ No newline at end of file
+com.tyndalehouse.step.core.guice.providers.DatabaseConfigProvider
+>addEntities(ServerConfig)
\ No newline at end of file

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/StepCoreModule.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/StepCoreModule.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/StepCoreModule.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -21,13 +21,13 @@
 import com.tyndalehouse.step.core.guice.providers.ServerSessionProvider;
 import com.tyndalehouse.step.core.guice.providers.TestData;
 import com.tyndalehouse.step.core.service.BibleInformationService;
-import com.tyndalehouse.step.core.service.BookmarkService;
+import com.tyndalehouse.step.core.service.FavouritesService;
 import com.tyndalehouse.step.core.service.JSwordService;
 import com.tyndalehouse.step.core.service.ModuleService;
 import com.tyndalehouse.step.core.service.TimelineService;
 import com.tyndalehouse.step.core.service.UserDataService;
 import com.tyndalehouse.step.core.service.impl.BibleInformationServiceImpl;
-import com.tyndalehouse.step.core.service.impl.BookmarkServiceImpl;
+import com.tyndalehouse.step.core.service.impl.FavouritesServiceImpl;
 import com.tyndalehouse.step.core.service.impl.JSwordServiceImpl;
 import com.tyndalehouse.step.core.service.impl.ModuleServiceImpl;
 import com.tyndalehouse.step.core.service.impl.TimelineServiceImpl;
@@ -39,6 +39,7 @@
  * @author Chris
  * 
  */
+ at SuppressWarnings("PMD.CouplingBetweenObjects")
 public class StepCoreModule extends AbstractModule {
     private static final String CORE_GUICE_PROPERTIES = "/step.core.properties";
 
@@ -51,7 +52,7 @@
         bind(BibleInformationService.class).to(BibleInformationServiceImpl.class).asEagerSingleton();
         bind(ModuleService.class).to(ModuleServiceImpl.class).asEagerSingleton();
         bind(TimelineService.class).to(TimelineServiceImpl.class);
-        bind(BookmarkService.class).to(BookmarkServiceImpl.class);
+        bind(FavouritesService.class).to(FavouritesServiceImpl.class);
         bind(UserDataService.class).to(UserDataServiceImpl.class);
         bind(Loader.class);
 

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/DatabaseConfigProvider.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/DatabaseConfigProvider.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/DatabaseConfigProvider.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -9,6 +9,7 @@
 import com.google.inject.Provider;
 import com.google.inject.name.Named;
 import com.tyndalehouse.step.core.data.entities.Bookmark;
+import com.tyndalehouse.step.core.data.entities.History;
 import com.tyndalehouse.step.core.data.entities.HotSpot;
 import com.tyndalehouse.step.core.data.entities.ScriptureReference;
 import com.tyndalehouse.step.core.data.entities.ScriptureTarget;
@@ -119,6 +120,7 @@
         config.addClass(User.class);
         config.addClass(Session.class);
         config.addClass(Bookmark.class);
+        config.addClass(History.class);
     }
 
 }

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/DefaultLexiconRefsProvider.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/DefaultLexiconRefsProvider.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/DefaultLexiconRefsProvider.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -21,6 +21,7 @@
      * 
      */
     @Provides
+    @Override
     public Map<String, String> get() {
         final Map<String, String> moduleRefs = new HashMap<String, String>();
         moduleRefs.put("strong:H", "StrongsHebrew");

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/DefaultVersionsProvider.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/DefaultVersionsProvider.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/DefaultVersionsProvider.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -21,6 +21,7 @@
      * 
      */
     @Provides
+    @Override
     public List<String> get() {
         final List<String> versions = new ArrayList<String>();
         versions.add("ESV");

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/ServerSessionProvider.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/ServerSessionProvider.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/ServerSessionProvider.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -9,7 +9,8 @@
 import com.tyndalehouse.step.core.service.UserDataService;
 
 /**
- * A server session provider
+ * A server session provider TODO should use CACHE here since we query almost everytime there is a request for
+ * bookmarks, etc.
  * 
  * @author Chris
  * 
@@ -40,7 +41,7 @@
     @Override
     public Session get() {
         final String clientSessionId = this.clientSessionProvider.get().getSessionId();
-        final Session serverSession = this.ebean.find(Session.class).where()
+        final Session serverSession = this.ebean.find(Session.class).fetch("user", "id, name").where()
                 .eq("jSessionId", clientSessionId).findUnique();
 
         if (serverSession == null) {

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/TestData.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/TestData.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/guice/providers/TestData.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,13 +1,17 @@
 package com.tyndalehouse.step.core.guice.providers;
 
+import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.commons.codec.digest.DigestUtils;
+
 import com.avaje.ebean.EbeanServer;
 import com.avaje.ebean.Transaction;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.tyndalehouse.step.core.data.entities.Bookmark;
+import com.tyndalehouse.step.core.data.entities.History;
 import com.tyndalehouse.step.core.data.entities.User;
 
 /**
@@ -26,17 +30,30 @@
     @Inject
     public TestData(final EbeanServer ebean) {
         this.ebean = ebean;
-        createBookmarks();
+        final User u = getUser();
+        createBookmarks(u);
+        createHistory(u);
     }
 
     /**
+     * creates a history item for the test user
+     * 
+     * @param u the user
+     */
+    private void createHistory(final User u) {
+        final History h = new History();
+        h.setHistoryReference("Rev 1");
+        h.setUser(u);
+        h.setLastUpdated(new Timestamp(System.currentTimeMillis()));
+        this.ebean.save(h);
+    }
+
+    /**
      * creates the bookmarks
+     * 
+     * @param u the user
      */
-    private void createBookmarks() {
-        final User u = new User();
-        u.setEmailAddress("t at t.c");
-        u.setName("Mr Test");
-        u.setPassword("password");
+    private void createBookmarks(final User u) {
 
         final List<Bookmark> bookmarks = new ArrayList<Bookmark>();
         final Bookmark b1 = new Bookmark();
@@ -56,4 +73,17 @@
         tx.commit();
         this.ebean.endTransaction();
     }
+
+    /**
+     * creates a user
+     * 
+     * @return the user to be created
+     */
+    private User getUser() {
+        final User u = new User();
+        u.setEmailAddress("t at t.c");
+        u.setName("Mr Test");
+        u.setPassword(new String(DigestUtils.sha512("password")));
+        return u;
+    }
 }

Deleted: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/BookmarkService.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/BookmarkService.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/BookmarkService.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,36 +0,0 @@
-package com.tyndalehouse.step.core.service;
-
-import java.util.List;
-
-import com.tyndalehouse.step.core.data.entities.Bookmark;
-
-/**
- * A service to add, remove bookmarks
- * 
- * @author Chris
- * 
- */
-public interface BookmarkService {
-    /**
-     * gets a set of bookmarks associated with the current session
-     * 
-     * @return a list of bookmarks
-     */
-    List<Bookmark> getBookmarks();
-
-    /**
-     * Removes a bookmark, using the current session-ed and logged on user
-     * 
-     * @param bookmarkId the bookmark id to use.
-     */
-    void removeBookmark(int bookmarkId);
-
-    /**
-     * Adds a bookmark if not already there
-     * 
-     * @param reference the reference to add to the bookmark
-     * @return the id of the bookmark that was added
-     */
-    int addBookmark(String reference);
-
-}

Copied: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/FavouritesService.java (from rev 222, trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/BookmarkService.java)
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/FavouritesService.java	                        (rev 0)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/FavouritesService.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -0,0 +1,56 @@
+package com.tyndalehouse.step.core.service;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+import com.tyndalehouse.step.core.data.entities.Bookmark;
+import com.tyndalehouse.step.core.data.entities.History;
+
+/**
+ * A service to add, remove bookmarks, history, etc.
+ * 
+ * @author Chris
+ * 
+ */
+public interface FavouritesService {
+    /**
+     * gets a set of bookmarks associated with the current session
+     * 
+     * @return a list of bookmarks
+     */
+    List<Bookmark> getBookmarks();
+
+    /**
+     * Removes a bookmark, using the current session-ed and logged on user
+     * 
+     * @param bookmarkId the bookmark id to use.
+     */
+    void removeBookmark(int bookmarkId);
+
+    /**
+     * Adds a bookmark if not already there
+     * 
+     * @param reference the reference to add to the bookmark
+     * @return the id of the bookmark that was added
+     */
+    int addBookmark(String reference);
+
+    /**
+     * gets a set of bookmarks associated with the current session
+     * 
+     * @param clientHistory the client history that we will merge in
+     * 
+     * @return a list of bookmarks
+     */
+    List<History> getHistory(List<History> clientHistory);
+
+    /**
+     * Adds a bookmark if not already there
+     * 
+     * @param reference the reference to add to the history item
+     * @param date the date at which it was last updated
+     * @return the id of the history item that was added
+     */
+    int addHistory(String reference, Timestamp date);
+
+}


Property changes on: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/FavouritesService.java
___________________________________________________________________
Added: svn:mime-type
   + text/plain

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/UserDataService.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/UserDataService.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/UserDataService.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -29,8 +29,9 @@
      * @param name the name of the person [optional]
      * @param country his country [optional]
      * @param password the password he has chosen, which we should SHA-1 and salt
+     * @return the user that has been created
      */
-    void register(String emailAddress, String name, String country, String password);
+    User register(String emailAddress, String name, String country, String password);
 
     /**
      * TODO move this to session provider This method is called to create a session for the user. This will
@@ -53,4 +54,9 @@
      * logs the current user out
      */
     void logout();
+
+    /**
+     * @return the logged in user if the user is logged in
+     */
+    User getLoggedInUser();
 }

Deleted: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/BookmarkServiceImpl.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/BookmarkServiceImpl.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/BookmarkServiceImpl.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,86 +0,0 @@
-package com.tyndalehouse.step.core.service.impl;
-
-import static com.avaje.ebean.Expr.eq;
-
-import java.util.List;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.avaje.ebean.EbeanServer;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import com.tyndalehouse.step.core.data.entities.Bookmark;
-import com.tyndalehouse.step.core.data.entities.Session;
-import com.tyndalehouse.step.core.data.entities.User;
-import com.tyndalehouse.step.core.exceptions.RequiresLoginException;
-import com.tyndalehouse.step.core.service.BookmarkService;
-
-/**
- * An implementation of the bookmark
- * 
- * @author Chris
- * 
- */
- at Singleton
-public class BookmarkServiceImpl implements BookmarkService {
-    private static final String USER_FIELD = "user";
-    private static final Logger LOG = LoggerFactory.getLogger(BookmarkServiceImpl.class);
-    private final Provider<Session> serverSession;
-    private final EbeanServer ebean;
-
-    /**
-     * 
-     * @param ebean the ebean server for retrieving and persisting
-     * @param serverSession the server session provider
-     */
-    @Inject
-    public BookmarkServiceImpl(final EbeanServer ebean, final Provider<Session> serverSession) {
-        this.ebean = ebean;
-        this.serverSession = serverSession;
-
-    }
-
-    @Override
-    public List<Bookmark> getBookmarks() {
-        // perhaps this could be made more efficient
-        // TODO we need to add some ordering on this somewhere!
-        // TODO push to a library for reuse elsewhere as some validation utils
-        final User user = this.serverSession.get().getUser();
-        if (user == null) {
-            // the user is not logged in
-            throw new RequiresLoginException("You will need to login to access this functionality");
-        }
-
-        return this.ebean.find(Bookmark.class).select("id, bookmarkReference").where().eq(USER_FIELD, user)
-                .findList();
-    }
-
-    @Override
-    public void removeBookmark(final int bookmarkId) {
-        this.ebean.delete(Bookmark.class, Integer.valueOf(bookmarkId));
-    }
-
-    @Override
-    public int addBookmark(final String reference) {
-        // first we check that the bookmark doesn't exist, then we insert it
-        final User currentUser = this.serverSession.get().getUser();
-
-        final List<Bookmark> bookmarks = this.ebean.find(Bookmark.class).where()
-                .and(eq("bookmarkReference", reference), eq(USER_FIELD, currentUser)).findList();
-
-        // no bookmark? then create!
-        if (bookmarks.size() == 0) {
-            final Bookmark b = new Bookmark();
-            b.setUser(currentUser);
-            b.setBookmarkReference(reference);
-            this.ebean.save(b);
-            return b.getId();
-        }
-
-        // bookmark already exists, just return the bookmark id and warn
-        LOG.warn("This is already a bookmark in the list");
-        return bookmarks.get(0).getId();
-    }
-}

Copied: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/FavouritesServiceImpl.java (from rev 222, trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/BookmarkServiceImpl.java)
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/FavouritesServiceImpl.java	                        (rev 0)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/FavouritesServiceImpl.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -0,0 +1,223 @@
+package com.tyndalehouse.step.core.service.impl;
+
+import static com.avaje.ebean.Expr.eq;
+
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.avaje.ebean.BeanState;
+import com.avaje.ebean.EbeanServer;
+import com.avaje.ebean.ExpressionList;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.tyndalehouse.step.core.data.entities.Bookmark;
+import com.tyndalehouse.step.core.data.entities.History;
+import com.tyndalehouse.step.core.data.entities.Session;
+import com.tyndalehouse.step.core.data.entities.User;
+import com.tyndalehouse.step.core.exceptions.RequiresLoginException;
+import com.tyndalehouse.step.core.service.FavouritesService;
+
+/**
+ * An implementation of the bookmark
+ * 
+ * @author Chris
+ * 
+ */
+ at Singleton
+public class FavouritesServiceImpl implements FavouritesService {
+    private static final String HISTORY_ORDER_BY = "lastUpdated";
+    private static final String HISTORY_FETCH_FIELDS = "id, historyReference, lastUpdated";
+    private static final int MAX_HISTORY_ITEMS = 10;
+    private static final String USER_FIELD = "user";
+    private static final Logger LOG = LoggerFactory.getLogger(FavouritesServiceImpl.class);
+    private final Provider<Session> serverSession;
+    private final EbeanServer ebean;
+
+    /**
+     * 
+     * @param ebean the ebean server for retrieving and persisting
+     * @param serverSession the server session provider
+     */
+    @Inject
+    public FavouritesServiceImpl(final EbeanServer ebean, final Provider<Session> serverSession) {
+        this.ebean = ebean;
+        this.serverSession = serverSession;
+
+    }
+
+    @Override
+    public List<Bookmark> getBookmarks() {
+        return getFavourites("id, bookmarkReference", Bookmark.class, "id", true);
+    }
+
+    @Override
+    public List<History> getHistory(final List<History> clientHistory) {
+        final List<History> combinedHistories = combineHistories(clientHistory,
+                getFavourites(HISTORY_FETCH_FIELDS, History.class, HISTORY_ORDER_BY, false));
+
+        // now we have de-duplicated lists, we can sort by dates!
+        Collections.sort(combinedHistories, new Comparator<History>() {
+            @Override
+            public int compare(final History o1, final History o2) {
+                // we want the most recent elements at the beginning of the set
+                return o2.getLastUpdated().compareTo(o1.getLastUpdated());
+            }
+        });
+
+        if (LOG.isTraceEnabled()) {
+            LOG.trace("After sorting & de-duping:");
+            for (final History h : combinedHistories) {
+                LOG.trace("Item ref: [{}]", h.getHistoryReference());
+            }
+        }
+
+        // now we prune to a maximum number of items
+        pruneCombinedHistory(combinedHistories);
+
+        // finally, we can unfortunately not return these beans as they contain the "User" object
+        // so want to return just the history...
+        return getFavourites(HISTORY_FETCH_FIELDS, History.class, HISTORY_ORDER_BY, false);
+    }
+
+    /**
+     * Ensures the top of the list is saved to the database, and the bottom part is chopped off and removed
+     * 
+     * @param combinedHistories the history that contains too many elements
+     */
+    private void pruneCombinedHistory(final List<History> combinedHistories) {
+        History lastItem = null;
+        for (int ii = 0; ii < MAX_HISTORY_ITEMS && ii < combinedHistories.size(); ii++) {
+            lastItem = combinedHistories.get(ii);
+            // if we ve not persisted this yet, then set user and save
+            if (lastItem.getUser() == null) {
+                lastItem.setUser(this.serverSession.get().getUser());
+                this.ebean.save(lastItem);
+            }
+        }
+
+        // we remove the remaining ones at the end of the list
+        while (combinedHistories.size() > MAX_HISTORY_ITEMS) {
+            final History overflowHistoryItem = combinedHistories.get(combinedHistories.size() - 1);
+            final BeanState beanState = this.ebean.getBeanState(overflowHistoryItem);
+            if (!beanState.isNew()) {
+                this.ebean.delete(overflowHistoryItem);
+            }
+            combinedHistories.remove(combinedHistories.size() - 1);
+        }
+    }
+
+    /**
+     * combines the client and server history by de-duplication and checking the latest dates
+     * 
+     * @param clientHistory the client history
+     * @param serverHistory the server history
+     * @return the histories, combined
+     */
+    List<History> combineHistories(final List<History> clientHistory, final List<History> serverHistory) {
+        final Map<String, History> combinedHistory = new HashMap<String, History>();
+
+        // first put all the items from the server history
+        for (final History h : serverHistory) {
+            combinedHistory.put(h.getHistoryReference(), h);
+        }
+
+        // then put all the items from the client history, being careful to overwrite
+        // only if date is more recent
+        for (final History h : clientHistory) {
+            final History existingItem = combinedHistory.get(h.getHistoryReference());
+
+            // if no item exists already OR if the item ante-dates the one we have
+            if (existingItem == null || existingItem.getLastUpdated().before(h.getLastUpdated())) {
+                combinedHistory.put(h.getHistoryReference(), h);
+            }
+        }
+        return new ArrayList<History>(combinedHistory.values());
+    }
+
+    /**
+     * a simple helper method for retrieving favourite items, for a particular user
+     * 
+     * @param <T> the type of favourite (history, bookmark)
+     * @param fetchProperties the fields to select out
+     * @param favouriteClass the class that matches T
+     * @param orderByClause the order specified to retrieve the data
+     * @param ascending true to mark ascending order
+     * @return a list of Ts (bookmarks or favourites, ordered correctly, by user logged on)
+     */
+    private <T> List<T> getFavourites(final String fetchProperties, final Class<T> favouriteClass,
+            final String orderByClause, final boolean ascending) {
+        final User user = this.serverSession.get().getUser();
+        if (user == null) {
+            // the user is not logged in
+            throw new RequiresLoginException("You will need to login to access this functionality");
+        }
+
+        final ExpressionList<T> query = this.ebean.find(favouriteClass).select(fetchProperties).where()
+                .eq(USER_FIELD, user);
+        if (ascending) {
+            return query.order().asc(orderByClause).findList();
+        } else {
+            return query.order().desc(orderByClause).findList();
+        }
+    }
+
+    @Override
+    public void removeBookmark(final int bookmarkId) {
+        this.ebean.delete(Bookmark.class, Integer.valueOf(bookmarkId));
+    }
+
+    @Override
+    public int addBookmark(final String reference) {
+        // first we check that the bookmark doesn't exist, then we insert it
+        final User currentUser = this.serverSession.get().getUser();
+
+        final List<Bookmark> bookmarks = this.ebean.find(Bookmark.class).where()
+                .and(eq("bookmarkReference", reference), eq(USER_FIELD, currentUser)).findList();
+
+        // no bookmark? then create!
+        if (bookmarks.isEmpty()) {
+            LOG.debug("Creating bookmark [{}]", reference);
+            final Bookmark b = new Bookmark();
+            b.setUser(currentUser);
+            b.setBookmarkReference(reference);
+            this.ebean.save(b);
+            return b.getId();
+        }
+
+        // bookmark already exists, just return the bookmark id and warn
+        LOG.warn("This is already a bookmark in the list [{}]", reference);
+        return bookmarks.get(0).getId();
+    }
+
+    @Override
+    public int addHistory(final String reference, final Timestamp lastUpdated) {
+        // we first check if the history item is already there
+        final History persistedHistoryItem = this.ebean.find(History.class).select("*").where()
+                .eq("historyReference", reference).findUnique();
+
+        final Timestamp lastUpdatedTime = lastUpdated != null ? lastUpdated : new Timestamp(
+                System.currentTimeMillis());
+        if (persistedHistoryItem == null) {
+            final History h = new History();
+            h.setHistoryReference(reference);
+            h.setLastUpdated(lastUpdatedTime);
+            h.setUser(this.serverSession.get().getUser());
+            this.ebean.save(h);
+            return h.getId();
+        }
+
+        // else just update the timestamp
+        persistedHistoryItem.setLastUpdated(lastUpdatedTime);
+        this.ebean.save(persistedHistoryItem);
+        return persistedHistoryItem.getId();
+    }
+}


Property changes on: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/FavouritesServiceImpl.java
___________________________________________________________________
Added: svn:mime-type
   + text/plain

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	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/JSwordServiceImpl.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -66,6 +66,7 @@
      * @param bibleCategory the categories of books that should be considered
      * @return returns a list of installed modules
      */
+    @Override
     public List<Book> getInstalledModules(final BookCategory... bibleCategory) {
         if (bibleCategory == null || bibleCategory.length == 0) {
             return new ArrayList<Book>();
@@ -79,6 +80,8 @@
 
         // we set up a filter to retrieve just certain types of books
         final BookFilter bf = new BookFilter() {
+            @Override
+            @SuppressWarnings("PMD.JUnit4TestShouldUseTestAnnotation")
             public boolean test(final Book b) {
                 return categories.contains(b.getBookCategory());
             }
@@ -90,6 +93,7 @@
      * @param bibleCategory the list of books that should be considered
      * @return a list of all modules
      */
+    @Override
     public List<Book> getAllModules(final BookCategory... bibleCategory) {
         final List<Book> books = new ArrayList<Book>();
         for (final Installer installer : this.bookInstallers) {
@@ -134,6 +138,7 @@
             final SAXEventProvider osissep = bookData.getSAXEventProvider();
             TransformingSAXEventProvider htmlsep = null;
             htmlsep = (TransformingSAXEventProvider) new Converter() {
+                @Override
                 public SAXEventProvider convert(final SAXEventProvider provider) throws TransformerException {
                     try {
                         final String file = requiredTransformation.getFile();

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/UserDataServiceImpl.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/UserDataServiceImpl.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/service/impl/UserDataServiceImpl.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -50,7 +50,7 @@
     }
 
     @Override
-    public void register(final String emailAddress, final String name, final String country,
+    public User register(final String emailAddress, final String name, final String country,
             final String password) {
         // first check that are we not logged in!
         Session session = this.sessionProvider.get();
@@ -77,13 +77,11 @@
         this.ebean.save(u);
 
         // next, we just associate the current session with the user by logging in
-        this.login(emailAddress, password);
+        return this.login(emailAddress, password);
     }
 
     @Override
     public Session createSession() {
-        // TODO we can't use subclassing on Android so remove in preference to enhancements
-        // final Session session = this.ebean.createEntityBean(Session.class);
         final Session session = new Session();
         final ClientSession clientSession = this.clientSessionProvider.get();
 
@@ -106,6 +104,16 @@
     }
 
     @Override
+    public User getLoggedInUser() {
+        final Session session = this.sessionProvider.get();
+        if (session == null) {
+            return null;
+        }
+
+        return session.getUser();
+    }
+
+    @Override
     public User login(final String emailAddress, final String password) {
         // logging in basically means associating the user with the session
         final Session serverSession = this.sessionProvider.get();

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/utils/JSwordUtils.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/utils/JSwordUtils.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/utils/JSwordUtils.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -53,6 +53,7 @@
 
         // finally sort by initials
         sort(versions, new Comparator<BibleVersion>() {
+            @Override
             public int compare(final BibleVersion o1, final BibleVersion o2) {
                 return o1.getInitials().compareTo(o2.getInitials());
             }

Modified: trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/xsl/impl/InterlinearProviderImpl.java
===================================================================
--- trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/xsl/impl/InterlinearProviderImpl.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/java/com/tyndalehouse/step/core/xsl/impl/InterlinearProviderImpl.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -281,7 +281,7 @@
      * @param morph the morphology (identifies how the used is word in the sentence - i.e. grammar)
      * @param word the word to be stored
      */
-    void addTextualInfo(final String verseReference, final String strong, final String morph,
+    private void addTextualInfo(final String verseReference, final String strong, final String morph,
             final String word) {
         final String strongKey = getAnyKey(strong);
 

Modified: trunk/step/step-core/src/main/resources/ebean.properties
===================================================================
--- trunk/step/step-core/src/main/resources/ebean.properties	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/resources/ebean.properties	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,17 +1,10 @@
-# -------------------------------------------------------------  
-# Load (Dev/Test/Prod) properties external to your war/jar  
-# -------------------------------------------------------------  
-# You can use load.properties to load the properties from a  
-# file external to your war/jar.   
-#load.properties.override=${CATALINA_HOME}/conf/myapp.ebean.properties  
-  
-  
 ebean.ddl.generate=false  
 ebean.ddl.run=false  
 ebean.debug.sql=true
 #ebean.debug.lazyload=false  
+
+ebean.json.jsonValueAdapter=com.tyndalehouse.step.rest.framework.StepJsonValueAdapter
   
-  
 # -------------------------------------------------------------  
 # Transaction Logging  
 # -------------------------------------------------------------  

Modified: trunk/step/step-core/src/main/resources/step.core.properties
===================================================================
--- trunk/step/step-core/src/main/resources/step.core.properties	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/main/resources/step.core.properties	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,5 +1,5 @@
 # Front controller properties
-cache.enabled=true
+cache.enabled=false
 
 #list of installers in the format: host,package,catalog
 installer.1=www.crosswire.org,/ftpmirror/pub/sword/packages/rawzip,/ftpmirror/pub/sword/raw

Deleted: trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/AbstractDataTest.java
===================================================================
--- trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/AbstractDataTest.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/AbstractDataTest.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,72 +0,0 @@
-package com.tyndalehouse.step.core.data;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.BeforeClass;
-
-import com.avaje.ebean.EbeanServer;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.tyndalehouse.step.core.data.create.DataTestModule;
-
-/**
- * A simple data test that sets up the context and objects to be able to do persistence. TODO think about
- * whethere the ebean server needs to be a new server each time by redoing the guice injector, we redo the
- * server. could make static, but then tests interfere with each other
- * 
- * @author Chris
- * 
- */
-public abstract class AbstractDataTest {
-    private static volatile EbeanServer ebean;
-    private static volatile Injector injector;
-    private boolean runInTransaction = true;
-
-    /**
-     * sets up the tests correctly
-     */
-    @BeforeClass
-    public static synchronized void setupData() {
-        if (injector == null) {
-            injector = Guice.createInjector(new DataTestModule());
-        }
-        if (ebean == null) {
-            ebean = injector.getInstance(EbeanServer.class);
-        }
-    }
-
-    /**
-     * we ensure that tests are isolated by running them in a transaction
-     */
-    @Before
-    public void startTransaction() {
-        if (this.runInTransaction) {
-            ebean.beginTransaction();
-        }
-    }
-
-    /**
-     * each method should roll back what it does to ensure that is thread-safe and doesn't interfere with
-     * others
-     */
-    @After
-    public void rollbackTransaction() {
-        if (this.runInTransaction) {
-            ebean.endTransaction();
-        }
-    }
-
-    /**
-     * @return the ebean
-     */
-    public EbeanServer getEbean() {
-        return ebean;
-    }
-
-    /**
-     * @param runInTransaction the runInTransaction to set
-     */
-    public void setRunInTransaction(final boolean runInTransaction) {
-        this.runInTransaction = runInTransaction;
-    }
-}

Copied: trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/DataDrivenTestExtension.java (from rev 222, trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/AbstractDataTest.java)
===================================================================
--- trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/DataDrivenTestExtension.java	                        (rev 0)
+++ trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/DataDrivenTestExtension.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -0,0 +1,77 @@
+package com.tyndalehouse.step.core.data;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+import com.avaje.ebean.EbeanServer;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.tyndalehouse.step.core.data.create.DataTestModule;
+
+/**
+ * A simple data test that sets up the context and objects to be able to do persistence.
+ * 
+ * @author Chris
+ * 
+ */
+public class DataDrivenTestExtension {
+    private static volatile EbeanServer ebean;
+    private static volatile Injector injector;
+    private boolean runInTransaction = true;
+
+    /**
+     * prevent initialisation, from anything but extending classes
+     */
+    protected DataDrivenTestExtension() {
+        // do nothing
+    }
+
+    /**
+     * sets up the tests correctly
+     */
+    @BeforeClass
+    public static synchronized void setupData() {
+        if (injector == null) {
+            injector = Guice.createInjector(new DataTestModule());
+        }
+        if (ebean == null) {
+            ebean = injector.getInstance(EbeanServer.class);
+        }
+    }
+
+    /**
+     * we ensure that tests are isolated by running them in a transaction
+     */
+    @Before
+    public void startTransaction() {
+        if (this.runInTransaction) {
+            ebean.beginTransaction();
+        }
+    }
+
+    /**
+     * each method should roll back what it does to ensure that is thread-safe and doesn't interfere with
+     * others
+     */
+    @After
+    public void rollbackTransaction() {
+        if (this.runInTransaction) {
+            ebean.endTransaction();
+        }
+    }
+
+    /**
+     * @return the ebean
+     */
+    public EbeanServer getEbean() {
+        return ebean;
+    }
+
+    /**
+     * @param runInTransaction the runInTransaction to set
+     */
+    public void setRunInTransaction(final boolean runInTransaction) {
+        this.runInTransaction = runInTransaction;
+    }
+}


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

Modified: trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/create/DataTest.java
===================================================================
--- trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/create/DataTest.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/create/DataTest.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -8,7 +8,7 @@
 import org.slf4j.LoggerFactory;
 
 import com.avaje.ebean.SqlRow;
-import com.tyndalehouse.step.core.data.AbstractDataTest;
+import com.tyndalehouse.step.core.data.DataDrivenTestExtension;
 import com.tyndalehouse.step.core.data.entities.Timeband;
 
 /**
@@ -17,7 +17,7 @@
  * @author Chris
  * 
  */
-public class DataTest extends AbstractDataTest {
+public class DataTest extends DataDrivenTestExtension {
     private static final Logger LOG = LoggerFactory.getLogger(DataTest.class);
 
     /**

Modified: trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/entities/UserTest.java
===================================================================
--- trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/entities/UserTest.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/data/entities/UserTest.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -5,7 +5,7 @@
 import org.junit.Test;
 
 import com.avaje.ebean.Ebean;
-import com.tyndalehouse.step.core.data.AbstractDataTest;
+import com.tyndalehouse.step.core.data.DataDrivenTestExtension;
 
 /**
  * tests that i can create a user - probably not going to do this for all entities
@@ -13,18 +13,18 @@
  * @author Chris
  * 
  */
-public class UserTest extends AbstractDataTest {
+public class UserTest extends DataDrivenTestExtension {
     /**
      * tests we can create a user
      */
     @Test
-    public void createUser() {
+    public void testCreateUser() {
         final User u = new User();
         u.setEmailAddress("chrisburrell at test.com");
         u.setName("Chris");
         u.setPassword("password");
 
-        Ebean.save(u);
+        getEbean().save(u);
         final User r = Ebean.find(User.class, u.getId());
         assertEquals(u.getEmailAddress(), r.getEmailAddress());
     }

Deleted: trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/BookmarkServiceImplTest.java
===================================================================
--- trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/BookmarkServiceImplTest.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/BookmarkServiceImplTest.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,120 +0,0 @@
-package com.tyndalehouse.step.core.service.impl;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.when;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-
-import com.google.inject.Provider;
-import com.tyndalehouse.step.core.data.AbstractDataTest;
-import com.tyndalehouse.step.core.data.entities.Bookmark;
-import com.tyndalehouse.step.core.data.entities.Session;
-import com.tyndalehouse.step.core.data.entities.User;
-
-/**
- * tests that we can create and retrieve bookmarks
- * 
- * @author Chris
- * 
- */
- at RunWith(MockitoJUnitRunner.class)
-public class BookmarkServiceImplTest extends AbstractDataTest {
-    @Mock
-    private Provider<Session> serverSession;
-    private BookmarkServiceImpl bookmarkService;
-    private User u;
-
-    /**
-     * sets up the service under test
-     */
-    @Before
-    public void setUp() {
-        this.bookmarkService = new BookmarkServiceImpl(getEbean(), this.serverSession);
-        this.u = new User();
-        this.u.setEmailAddress("b at b.com");
-        final Session s = new Session();
-        s.setUser(this.u);
-
-        // when the server session is requested, we give the user back
-        when(this.serverSession.get()).thenReturn(s);
-    }
-
-    /**
-     * ensures that with no bookmarks, this still works
-     */
-    @Test
-    public void readNoBookmarks() {
-        assertEquals(0, this.bookmarkService.getBookmarks().size());
-    }
-
-    /**
-     * ensures that with no bookmarks, this still works
-     */
-    @Test
-    public void testReadOneBookmarks() {
-        final String testReference = "Genesis 1:1";
-        saveNewBookmarkDirectly(testReference);
-        assertEquals(this.bookmarkService.getBookmarks().get(0).getBookmarkReference(), testReference);
-    }
-
-    /**
-     * ensures that we can add bookmarks
-     */
-    @Test
-    public void testAddBookmark() {
-        final String testReference = "Genesis 1:1";
-        final int bookmarkId = this.bookmarkService.addBookmark(testReference);
-
-        final Bookmark persistedBookmark = getEbean().find(Bookmark.class).where().idEq(bookmarkId)
-                .findUnique();
-        assertEquals(testReference, persistedBookmark.getBookmarkReference());
-    }
-
-    /**
-     * This should return the original bookmark, but not create a new one
-     */
-    @Test
-    public void testAddDuplicateBookmark() {
-        final String testReference = "Genesis 1:1";
-
-        final int bookmarkId = this.bookmarkService.addBookmark(testReference);
-        final int duplicateBookmarkId = this.bookmarkService.addBookmark(testReference);
-
-        assertEquals(bookmarkId, duplicateBookmarkId);
-        assertEquals(1, getEbean().find(Bookmark.class).where().eq("bookmarkReference", testReference)
-                .findRowCount());
-    }
-
-    /**
-     * tests removing a bookmark
-     */
-    @Test
-    public void testRemoveBookmark() {
-        // create a bookmark
-        final Bookmark b = saveNewBookmarkDirectly("blah");
-
-        final Integer bookmarkId = b.getId();
-        this.bookmarkService.removeBookmark(bookmarkId);
-
-        assertEquals(0, getEbean().find(Bookmark.class).where().idEq(bookmarkId).findRowCount());
-    }
-
-    /**
-     * helpers to save a bookmark
-     * 
-     * @param testReference the pasage reference
-     * @return the bookmark that was created
-     */
-    private Bookmark saveNewBookmarkDirectly(final String testReference) {
-        final Bookmark b = new Bookmark();
-        b.setBookmarkReference(testReference);
-        b.setUser(this.u);
-        getEbean().save(b);
-        return b;
-    }
-
-}

Copied: trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/FavouritesServiceImplTest.java (from rev 222, trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/BookmarkServiceImplTest.java)
===================================================================
--- trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/FavouritesServiceImplTest.java	                        (rev 0)
+++ trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/FavouritesServiceImplTest.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -0,0 +1,258 @@
+package com.tyndalehouse.step.core.service.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Provider;
+import com.tyndalehouse.step.core.data.DataDrivenTestExtension;
+import com.tyndalehouse.step.core.data.entities.Bookmark;
+import com.tyndalehouse.step.core.data.entities.History;
+import com.tyndalehouse.step.core.data.entities.Session;
+import com.tyndalehouse.step.core.data.entities.User;
+
+/**
+ * tests that we can create and retrieve bookmarks
+ * 
+ * @author Chris
+ * 
+ */
+ at RunWith(MockitoJUnitRunner.class)
+ at SuppressWarnings("PMD.TooManyMethods")
+public class FavouritesServiceImplTest extends DataDrivenTestExtension {
+    private static final Logger LOG = LoggerFactory.getLogger(FavouritesServiceImplTest.class);
+
+    @Mock
+    private Provider<Session> serverSession;
+    private FavouritesServiceImpl favouritesService;
+    private User u;
+
+    /**
+     * sets up the service under test
+     */
+    @Before
+    public void setUp() {
+        this.favouritesService = new FavouritesServiceImpl(getEbean(), this.serverSession);
+        this.u = new User();
+        this.u.setEmailAddress("b at b.com");
+        final Session s = new Session();
+        s.setUser(this.u);
+
+        // when the server session is requested, we give the user back
+        when(this.serverSession.get()).thenReturn(s);
+    }
+
+    /**
+     * ensures that with no bookmarks, this still works
+     */
+    @Test
+    public void readNoBookmarks() {
+        assertEquals(0, this.favouritesService.getBookmarks().size());
+    }
+
+    /**
+     * ensures that with no bookmarks, this still works
+     */
+    @Test
+    public void testReadOneBookmarks() {
+        final String testReference = "Genesis 1:1";
+        saveNewBookmarkDirectly(testReference);
+        assertEquals(this.favouritesService.getBookmarks().get(0).getBookmarkReference(), testReference);
+    }
+
+    /**
+     * ensures that we can add bookmarks
+     */
+    @Test
+    public void testAddBookmark() {
+        final String testReference = "Genesis 1:1";
+        final int bookmarkId = this.favouritesService.addBookmark(testReference);
+
+        final Bookmark persistedBookmark = getEbean().find(Bookmark.class).where().idEq(bookmarkId)
+                .findUnique();
+        assertEquals(testReference, persistedBookmark.getBookmarkReference());
+    }
+
+    /**
+     * This should return the original bookmark, but not create a new one
+     */
+    @Test
+    public void testAddDuplicateBookmark() {
+        final String testReference = "Genesis 1:1";
+
+        final int bookmarkId = this.favouritesService.addBookmark(testReference);
+        final int duplicateBookmarkId = this.favouritesService.addBookmark(testReference);
+
+        assertEquals(bookmarkId, duplicateBookmarkId);
+        assertEquals(1, getEbean().find(Bookmark.class).where().eq("bookmarkReference", testReference)
+                .findRowCount());
+    }
+
+    /**
+     * tests removing a bookmark
+     */
+    @Test
+    public void testRemoveBookmark() {
+        // create a bookmark
+        final Bookmark b = saveNewBookmarkDirectly("blah");
+
+        final Integer bookmarkId = b.getId();
+        this.favouritesService.removeBookmark(bookmarkId);
+
+        assertEquals(0, getEbean().find(Bookmark.class).where().idEq(bookmarkId).findRowCount());
+    }
+
+    /**
+     * helpers to save a bookmark
+     * 
+     * @param testReference the pasage reference
+     * @return the bookmark that was created
+     */
+    private Bookmark saveNewBookmarkDirectly(final String testReference) {
+        final Bookmark b = new Bookmark();
+        b.setBookmarkReference(testReference);
+        b.setUser(this.u);
+        getEbean().save(b);
+        return b;
+    }
+
+    /**
+     * tests that merging history work alright
+     */
+    @Test
+    public void testGetHistoryMerge() {
+        final long currentDate = System.currentTimeMillis();
+        final List<History> client = new ArrayList<History>();
+
+        // add client history
+        addHistoryToList(client, "A", currentDate + 10000);
+        addHistoryToList(client, "C", currentDate + 30000);
+
+        // save server history
+        final History s1 = new History();
+        s1.setHistoryReference("B");
+        s1.setLastUpdated(new Timestamp(currentDate + 20000));
+        s1.setUser(this.serverSession.get().getUser());
+        getEbean().save(s1);
+
+        final History s2 = new History();
+        s2.setHistoryReference("D");
+        s2.setLastUpdated(new Timestamp(currentDate + 40000));
+        s2.setUser(this.serverSession.get().getUser());
+        getEbean().save(s2);
+
+        final List<History> history = this.favouritesService.getHistory(client);
+        assertEquals(4, history.size());
+        assertEquals(s2.getHistoryReference(), history.get(0).getHistoryReference());
+        assertEquals(client.get(1).getHistoryReference(), history.get(1).getHistoryReference());
+        assertEquals(s1.getHistoryReference(), history.get(2).getHistoryReference());
+        assertEquals(client.get(0).getHistoryReference(), history.get(3).getHistoryReference());
+    }
+
+    /**
+     * tests pruning
+     */
+    @Test
+    public void testPruning() {
+        final long currentDate = System.currentTimeMillis();
+        final List<History> client = new ArrayList<History>();
+
+        // create 7 items in client history - naming A,B,C ... G, latest is G
+        for (int ii = 0; ii < 7; ii++) {
+            addHistoryToList(client, new String(new char[] { (char) (65 + ii) }), currentDate + (1 + ii)
+                    * 10000);
+        }
+
+        // create server items too, 7 of them, Z ... T, latest is Z
+        for (int ii = 0; ii < 7; ii++) {
+            // save server history
+            final History s1 = new History();
+            s1.setHistoryReference(new String(new char[] { (char) (90 - ii) }));
+            s1.setLastUpdated(new Timestamp(currentDate - (1 + ii) * 10000));
+            s1.setUser(this.serverSession.get().getUser());
+            getEbean().save(s1);
+            LOG.debug("Created item [{}], [{}]", s1.getHistoryReference(), s1.getLastUpdated().getTime());
+        }
+
+        final List<History> history = this.favouritesService.getHistory(client);
+
+        for (final History h : history) {
+            LOG.debug("Item ref: [{}]", h.getHistoryReference());
+        }
+
+        assertEquals(10, history.size());
+        assertEquals("G", history.get(0).getHistoryReference());
+        assertEquals("F", history.get(1).getHistoryReference());
+        assertEquals("E", history.get(2).getHistoryReference());
+        assertEquals("D", history.get(3).getHistoryReference());
+        assertEquals("C", history.get(4).getHistoryReference());
+        assertEquals("B", history.get(5).getHistoryReference());
+        assertEquals("A", history.get(6).getHistoryReference());
+        assertEquals("Z", history.get(7).getHistoryReference());
+        assertEquals("Y", history.get(8).getHistoryReference());
+        assertEquals("X", history.get(9).getHistoryReference());
+    }
+
+    /**
+     * Test that the de-duplication works
+     */
+    @Test
+    public void testCombineHistories() {
+        final List<History> clientHistory = new ArrayList<History>();
+        final List<History> serverHistory = new ArrayList<History>();
+
+        // make some client history
+        addHistoryToList(clientHistory, "A", 1);
+        addHistoryToList(clientHistory, "B", 1);
+        addHistoryToList(clientHistory, "C", 2);
+
+        // make some server history
+        addHistoryToList(serverHistory, "B", 2);
+        addHistoryToList(serverHistory, "C", 1);
+
+        final List<History> history = this.favouritesService.combineHistories(clientHistory, serverHistory);
+        Collections.sort(history, new Comparator<History>() {
+            @Override
+            public int compare(final History o1, final History o2) {
+                return o1.getHistoryReference().compareTo(o2.getHistoryReference());
+            }
+        });
+
+        assertEquals(3, history.size());
+        assertEquals("A", history.get(0).getHistoryReference());
+        assertEquals(1, history.get(0).getLastUpdated().getTime());
+        assertEquals("B", history.get(1).getHistoryReference());
+        assertEquals(2, history.get(1).getLastUpdated().getTime());
+        assertEquals("C", history.get(2).getHistoryReference());
+        assertEquals(2, history.get(2).getLastUpdated().getTime());
+    }
+
+    /**
+     * helper method that adds a history item to a list
+     * 
+     * @param historyList the list to which to add the item
+     * @param historyReference the reference
+     * @param lastUpdated the date last updated
+     */
+    private void addHistoryToList(final List<History> historyList, final String historyReference,
+            final long lastUpdated) {
+        final History c1 = new History();
+        c1.setHistoryReference(historyReference);
+        c1.setLastUpdated(new Timestamp(lastUpdated));
+        LOG.debug("Created item [{}], [{}]", c1.getHistoryReference(), c1.getLastUpdated().getTime());
+        historyList.add(c1);
+    }
+}


Property changes on: trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/FavouritesServiceImplTest.java
___________________________________________________________________
Added: svn:mime-type
   + text/plain

Modified: trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/UserDataServiceImplTest.java
===================================================================
--- trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/UserDataServiceImplTest.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/service/impl/UserDataServiceImplTest.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -14,7 +14,7 @@
 import org.mockito.runners.MockitoJUnitRunner;
 
 import com.google.inject.Provider;
-import com.tyndalehouse.step.core.data.AbstractDataTest;
+import com.tyndalehouse.step.core.data.DataDrivenTestExtension;
 import com.tyndalehouse.step.core.data.entities.Session;
 import com.tyndalehouse.step.core.data.entities.User;
 import com.tyndalehouse.step.core.exceptions.StepInternalException;
@@ -27,7 +27,7 @@
  * 
  */
 @RunWith(MockitoJUnitRunner.class)
-public class UserDataServiceImplTest extends AbstractDataTest {
+public class UserDataServiceImplTest extends DataDrivenTestExtension {
     @Mock
     private Provider<Session> serverSessionProvider;
     @Mock

Modified: trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/xsl/impl/InterlinearProviderImplTest.java
===================================================================
--- trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/xsl/impl/InterlinearProviderImplTest.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/test/java/com/tyndalehouse/step/core/xsl/impl/InterlinearProviderImplTest.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -2,6 +2,9 @@
 
 import static org.junit.Assert.assertEquals;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
 import org.junit.Test;
 
 /**
@@ -12,16 +15,28 @@
  */
 public class InterlinearProviderImplTest {
     /**
-     * this checks that when keyed with strong, morph and verse number, we can retrieve the word. We should be able to
-     * retrieve by (strong,morph), regardless of verse number. We should also be able to retrieve by (strong,verse
-     * number)
+     * this checks that when keyed with strong, morph and verse number, we can retrieve the word. We should be
+     * able to retrieve by (strong,morph), regardless of verse number. We should also be able to retrieve by
+     * (strong,verse number)
+     * 
+     * @throws InvocationTargetException reflection exception which should fail the test
+     * @throws IllegalAccessException reflection exception which should fail the test
+     * @throws NoSuchMethodException reflect exception which should fail the test
      */
     @Test
-    public void testInterlinearStrongMorphBased() {
+    public void testInterlinearStrongMorphBased() throws IllegalAccessException, InvocationTargetException,
+            NoSuchMethodException {
         final InterlinearProviderImpl interlinear = new InterlinearProviderImpl();
 
+        // NOTE: because we don't want to expose a method called during initialisation as non-private (could
+        // break
+        // the initialisation, of the provider, we use reflection to open up its access for testing purposes!
+        final Method method = interlinear.getClass().getDeclaredMethod("addTextualInfo", String.class,
+                String.class, String.class, String.class);
+        method.setAccessible(true);
+
         // add a word based on a strong,morph
-        interlinear.addTextualInfo("v1", "strong", "morph", "word");
+        method.invoke(interlinear, "v1", "strong", "morph", "word");
 
         assertEquals(interlinear.getWord("v1", "strong", "morph"), "word");
         assertEquals(interlinear.getWord("x", "strong", "morph"), "word");

Deleted: trunk/step/step-core/src/test/java/log4j.properties
===================================================================
--- trunk/step/step-core/src/test/java/log4j.properties	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/test/java/log4j.properties	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,13 +0,0 @@
-# Set root logger level to DEBUG and its only appender to A1.
-log4j.rootLogger=WARN, A1
-
-# A1 is set to be a ConsoleAppender.
-log4j.appender.A1=org.apache.log4j.ConsoleAppender
-log4j.appender.A1.layout=org.apache.log4j.PatternLayout
-log4j.appender.A1.layout.ConversionPattern=%-15.15c(%-5p) %m%n
-
-
-# Categories
-org.crosswire.jsword.book.sword.ConfigEntry=WARN
-log4j.category.com.tyndalehouse.step=DEBUG
-

Added: trunk/step/step-core/src/test/resources/ebean.properties
===================================================================
--- trunk/step/step-core/src/test/resources/ebean.properties	                        (rev 0)
+++ trunk/step/step-core/src/test/resources/ebean.properties	2011-03-26 17:01:58 UTC (rev 225)
@@ -0,0 +1,73 @@
+ebean.ddl.generate=false  
+ebean.ddl.run=false  
+ebean.debug.sql=true
+#ebean.debug.lazyload=false  
+
+
+# ebean.json.jsonValueAdapter=com.tyndalehouse.step.rest.framework.StepJsonValueAdapter
+  
+# -------------------------------------------------------------  
+# Transaction Logging  
+# -------------------------------------------------------------  
+  
+# Use java util logging to log transaction details  
+#ebean.loggingToJavaLogger=true  
+  
+# General logging level: (none, explicit, all)  
+ebean.logging=all  
+  
+# Sharing log files: (none, explicit, all)  
+ebean.logging.logfilesharing=all  
+  
+# location of transaction logs   
+ebean.logging.directory=logs  
+#ebean.logging.directory=${catalina.base}/logs/trans  
+  
+# Specific Log levels (none, summary, binding, sql)  
+ebean.logging.iud=sql  
+ebean.logging.query=sql  
+ebean.logging.sqlquery=sql  
+  
+ebean.logging.txnCommit=none  
+  
+# -------------------------------------------------------------  
+# DataSources (If using default Ebean DataSourceFactory)  
+# -------------------------------------------------------------   
+# You can specify many DataSources (one per EbeanServer)  and   
+# one of them is defined as the default/primary DataSource  
+  
+# specify the default/primary DataSource  
+#datasource.default=h2  
+  
+#datasource.h2.username=sa  
+#datasource.h2.password=  
+#datasource.h2.databaseUrl=jdbc:h2:mem:tests;DB_CLOSE_DELAY=-1  
+#datasource.h2.databaseDriver=org.h2.Driver  
+#datasource.h2.minConnections=1  
+#datasource.h2.maxConnections=25  
+#datasource.h2.heartbeatsql=select 1  
+#datasource.h2.isolationlevel=read_committed  
+  
+#datasource.mysql.username=test  
+#datasource.mysql.password=test  
+#datasource.mysql.databaseUrl=jdbc:mysql://127.0.0.1:3306/test  
+#datasource.mysql.databaseDriver=com.mysql.jdbc.Driver  
+#datasource.mysql.minConnections=1  
+#datasource.mysql.maxConnections=25  
+#datasource.mysql.heartbeatsql=select 1  
+#datasource.mysql.isolationlevel=read_committed  
+  
+#datasource.ora.username=test  
+#datasource.ora.password=test  
+#datasource.ora.databaseUrl=jdbc:oracle:thin:@127.0.0.1:1521:XE  
+#datasource.ora.databaseDriver=oracle.jdbc.driver.OracleDriver  
+#datasource.ora.minConnections=1  
+#datasource.ora.maxConnections=25  
+#datasource.ora.heartbeatsql=select count(*) from dual  
+#datasource.ora.isolationlevel=read_committed  
+  
+#datasource.pg.username=test  
+#datasource.pg.password=test  
+#datasource.pg.databaseUrl=jdbc:postgresql://127.0.0.1:5433/test  
+#datasource.pg.databaseDriver=org.postgresql.Driver  
+#datasource.pg.heartbeatsql=select 1  


Property changes on: trunk/step/step-core/src/test/resources/ebean.properties
___________________________________________________________________
Added: svn:mime-type
   + text/plain

Modified: trunk/step/step-core/src/test/resources/log4j.properties
===================================================================
--- trunk/step/step-core/src/test/resources/log4j.properties	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-core/src/test/resources/log4j.properties	2011-03-26 17:01:58 UTC (rev 225)
@@ -8,5 +8,5 @@
 log4j.appender.A1.layout.ConversionPattern=%d %-5p %C{1}:%L - %m%n
 
 log4j.category.org.crosswire.jsword.book.sword.ConfigEntry=WARN
-log4j.category.com.tyndalehouse.step=INFO
+log4j.category.com.tyndalehouse.step=DEBUG
 

Modified: trunk/step/step-parent/pom.xml
===================================================================
--- trunk/step/step-parent/pom.xml	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-parent/pom.xml	2011-03-26 17:01:58 UTC (rev 225)
@@ -46,6 +46,7 @@
 		<commons-configuration.version>1.6</commons-configuration.version>
 		<commons-dbcp.version>1.3</commons-dbcp.version>
 		<commons-io.version>1.4</commons-io.version>
+		<commons-codec.version>1.4</commons-codec.version>
 		<commons-dbutils.version>1.3</commons-dbutils.version>
 		<commons-httpclient.version>3.1</commons-httpclient.version>
 
@@ -205,6 +206,11 @@
 				<artifactId>commons-lang</artifactId>
 				<version>${commons-lang.version}</version>
 			</dependency>
+			<dependency>
+				<groupId>commons-codec</groupId>
+				<artifactId>commons-codec</artifactId>
+				<version>${commons-codec.version}</version>
+			</dependency>
 
 			<dependency>
 				<groupId>commons-collections</groupId>

Modified: trunk/step/step-web/src/main/java/com/tyndalehouse/step/models/WebSessionImpl.java
===================================================================
--- trunk/step/step-web/src/main/java/com/tyndalehouse/step/models/WebSessionImpl.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/models/WebSessionImpl.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -24,6 +24,7 @@
     /**
      * @return the session
      */
+    @Override
     public String getSessionId() {
         return this.sessionId;
     }
@@ -38,6 +39,7 @@
     /**
      * @return the ipAddress
      */
+    @Override
     public String getIpAddress() {
         return this.ipAddress;
     }

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	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/BibleController.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -4,6 +4,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 
 import org.apache.commons.lang.Validate;
 import org.slf4j.Logger;
@@ -94,7 +95,7 @@
         final List<LookupOption> lookupOptions = new ArrayList<LookupOption>();
         if (userOptions != null) {
             for (final String o : userOptions) {
-                lookupOptions.add(LookupOption.valueOf(o.toUpperCase()));
+                lookupOptions.add(LookupOption.valueOf(o.toUpperCase(Locale.ENGLISH)));
             }
         }
 

Deleted: trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/BookmarkController.java
===================================================================
--- trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/BookmarkController.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/BookmarkController.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,60 +0,0 @@
-package com.tyndalehouse.step.rest.controllers;
-
-import java.util.List;
-
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.tyndalehouse.step.core.data.entities.Bookmark;
-import com.tyndalehouse.step.core.service.BookmarkService;
-
-/**
- * This helps manage bookmarks. This implementation simply wraps around the Bookmark Service (the project
- * step-web provides a WebSessionProvider which can be used therefore to get cookie information).
- * 
- * In this case, we just simply proxy through
- * 
- * @author Chris
- * 
- */
- at Singleton
-public class BookmarkController {
-    private final BookmarkService bookmarkService;
-
-    /**
-     * We simply inject the bookmark service and proxy requests through
-     * 
-     * @param bookmarkService the bookmark service used to get our data
-     */
-    @Inject
-    public BookmarkController(final BookmarkService bookmarkService) {
-        this.bookmarkService = bookmarkService;
-    }
-
-    /**
-     * gets a set of bookmarks associated with the current session
-     * 
-     * @return a list of bookmarks
-     */
-    public List<Bookmark> getBookmarks() {
-        return this.bookmarkService.getBookmarks();
-    }
-
-    /**
-     * Removes a bookmark, using the current session-ed and logged on user
-     * 
-     * @param bookmarkId the bookmark id to use.
-     */
-    public void removeBookmark(final int bookmarkId) {
-        this.bookmarkService.removeBookmark(bookmarkId);
-    }
-
-    /**
-     * Adds a bookmark if not already there
-     * 
-     * @param reference the reference to add to the bookmark
-     * @return the id of the bookmark that was added
-     */
-    public int addBookmark(final String reference) {
-        return this.bookmarkService.addBookmark(reference);
-    }
-}

Copied: trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/FavouritesController.java (from rev 222, trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/BookmarkController.java)
===================================================================
--- trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/FavouritesController.java	                        (rev 0)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/FavouritesController.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -0,0 +1,113 @@
+package com.tyndalehouse.step.rest.controllers;
+
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.tyndalehouse.step.core.data.entities.Bookmark;
+import com.tyndalehouse.step.core.data.entities.History;
+import com.tyndalehouse.step.core.exceptions.StepInternalException;
+import com.tyndalehouse.step.core.service.FavouritesService;
+
+/**
+ * This helps manage bookmarks and history items. This implementation simply wraps around the Favourites
+ * Service (the project step-web provides a WebSessionProvider which can be used therefore to get cookie
+ * information).
+ * 
+ * In this case, we just simply proxy through
+ * 
+ * @author Chris
+ * 
+ */
+ at Singleton
+public class FavouritesController {
+    private final FavouritesService favouritesService;
+
+    /**
+     * We simply inject the bookmark service and proxy requests through
+     * 
+     * @param bookmarkService the bookmark service used to get our data
+     */
+    @Inject
+    public FavouritesController(final FavouritesService bookmarkService) {
+        this.favouritesService = bookmarkService;
+    }
+
+    /**
+     * gets a set of bookmarks associated with the current session
+     * 
+     * @return a list of bookmarks
+     */
+    public List<Bookmark> getBookmarks() {
+        return this.favouritesService.getBookmarks();
+    }
+
+    /**
+     * Removes a bookmark, using the current session-ed and logged on user
+     * 
+     * @param bookmarkId the bookmark id to use.
+     */
+    public void removeBookmark(final int bookmarkId) {
+        this.favouritesService.removeBookmark(bookmarkId);
+    }
+
+    /**
+     * Adds a bookmark if not already there
+     * 
+     * @param reference the reference to add to the bookmark
+     * @return the id of the bookmark that was added
+     */
+    public int addBookmark(final String reference) {
+        return this.favouritesService.addBookmark(reference);
+    }
+
+    /**
+     * The encoding might be client specific, so we first decode the information sent, and then pass it
+     * through into a list of objects
+     * 
+     * @param clientHistoryString the string as passed by the UI of the current cookie contents
+     * @return all the history items to the UI, merged with the client history as appropriate
+     */
+    public List<History> getHistory(final String clientHistoryString) {
+        final List<History> clientHistory = new ArrayList<History>();
+
+        // check for no history - currently a bit of a hack
+        if (StringUtils.isNotEmpty(clientHistoryString) && !"null".equals(clientHistoryString)) {
+
+            // first we split by '#' to get individual portions of the history list
+            // then by @ sign to get the date at which they were last updated
+            // we will do a fairly gross approximation here and refactor if this causes issues
+            try {
+                final String[] tokens = StringUtils.split(clientHistoryString, "~@");
+                for (int ii = 0; ii < tokens.length; ii = ii + 2) {
+                    final History item = new History();
+                    item.setHistoryReference(tokens[ii]);
+                    item.setLastUpdated(new Timestamp(Long.parseLong(tokens[ii + 1])));
+                    clientHistory.add(item);
+                }
+            } catch (final NumberFormatException e) {
+                throw new StepInternalException("Unable to parse stored history", e);
+            }
+        }
+        return this.favouritesService.getHistory(clientHistory);
+    }
+
+    /**
+     * adds or updates the history item
+     * 
+     * @param reference the reference to be checked and added/updated
+     * @return the id of the reference
+     */
+    public int addHistory(final String reference) {
+        final String[] split = StringUtils.split(reference, '@');
+        if (split.length != 2) {
+            throw new StepInternalException("Unable to add history item to user profile.");
+        }
+
+        return this.favouritesService.addHistory(split[0], new Timestamp(Long.valueOf(split[1])));
+    }
+}


Property changes on: trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/FavouritesController.java
___________________________________________________________________
Added: svn:mime-type
   + text/plain

Modified: 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	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/FrontController.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -41,6 +41,9 @@
  */
 @Singleton
 public class FrontController extends HttpServlet {
+    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 Logger LOGGER = LoggerFactory.getLogger(FrontController.class);
     private static final char PACKAGE_SEPARATOR = '.';
@@ -52,12 +55,13 @@
     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 EbeanServer ebean;
     private final transient ClientErrorResolver errorResolver;
 
     /**
@@ -67,13 +71,15 @@
      * @param isCacheEnabled indicates whether responses should be cached for fast retrieval TODO rename all
      *            ebeans to DB
      * @param ebean the db access/persisitence object
+     * @param errorResolver the error resolver is the object that helps us translate errors for the client
      */
     @Inject
     public FrontController(final Injector guiceInjector,
             @Named("cache.enabled") final Boolean isCacheEnabled, final EbeanServer ebean,
             final ClientErrorResolver errorResolver) {
         this.guiceInjector = guiceInjector;
-        this.ebean = ebean;
+        this.ebeanJson = ebean.createJsonContext();
+
         this.errorResolver = errorResolver;
         this.isCacheEnabled = Boolean.TRUE.equals(isCacheEnabled);
     }
@@ -155,7 +161,7 @@
             return new ClientHandledIssue(cause.getMessage(), this.errorResolver.resolve(cause.getClass()));
         } else if (cause instanceof IllegalArgumentException) {
             // a validation exception occurred
-            LOGGER.trace(e.getMessage(), e);
+            LOGGER.warn(e.getMessage(), e);
             return new ClientHandledIssue(cause.getMessage());
         }
 
@@ -176,13 +182,16 @@
             // therefore we can't just use simple jackson mapper
             if (responseValue == null) {
                 return new byte[0];
-            } else if (responseValue.getClass().getPackage().getName().startsWith("com.avaje")) {
-                final JsonContext json = this.ebean.createJsonContext();
+            } else {
+                final String responsePackage = responseValue.getClass().getPackage().getName();
+                if (responsePackage.startsWith(ENTITIES_PACKAGE)
+                        || responseValue.getClass().getPackage().getName().startsWith(AVAJE_PACKAGE)) {
 
-                // convert list of beans into JSON
-                response = json.toJsonString(responseValue);
-            } else {
-                response = this.jsonMapper.writeValueAsString(responseValue);
+                    // convert list of beans into JSON
+                    response = this.ebeanJson.toJsonString(responseValue);
+                } else {
+                    response = this.jsonMapper.writeValueAsString(responseValue);
+                }
             }
 
             return response.getBytes(UTF_8_ENCODING);

Modified: trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/UserController.java
===================================================================
--- trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/UserController.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/controllers/UserController.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,5 +1,7 @@
 package com.tyndalehouse.step.rest.controllers;
 
+import org.apache.commons.codec.digest.DigestUtils;
+
 import com.google.inject.Inject;
 import com.tyndalehouse.step.core.data.entities.User;
 import com.tyndalehouse.step.core.service.UserDataService;
@@ -30,25 +32,37 @@
      * @param name the name of the person [optional]
      * @param country his country [optional]
      * @param password the password he has chosen, which we should SHA-1 and salt
+     * @return the registered user TODO salt
      */
-    public void register(final String emailAddress, final String name, final String country,
+    public User register(final String emailAddress, final String name, final String country,
             final String password) {
         // do sha1 encoding here to avoid sending unencrypted string singletons all over the place...
+        return this.userDataService.register(emailAddress, name, country,
+                new String(DigestUtils.sha512(password)));
     }
 
     /**
-     * TODO sha-1 encoding Associates the current session with the username assuming password and username
-     * authenticates
+     * Associates the current session with the username assuming password and username authenticates
      * 
      * @param emailAddress the email address is used as the login token
      * @param password the password
      * @return the user that has logged in
      */
     public User login(final String emailAddress, final String password) {
-        return this.userDataService.login(emailAddress, password);
+        return this.userDataService.login(emailAddress, new String(DigestUtils.sha512(password)));
     }
 
+    /**
+     * Logs a user out
+     */
     public void logout() {
         this.userDataService.logout();
     }
+
+    /**
+     * @return true if logged in
+     */
+    public User getLoggedInUser() {
+        return this.userDataService.getLoggedInUser();
+    }
 }

Deleted: trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/framework/Cached.java
===================================================================
--- trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/framework/Cached.java	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/framework/Cached.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,22 +0,0 @@
-package com.tyndalehouse.step.rest.framework;
-
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
- * This indicates that the REST-like call can be cached by the server
- * 
- * @author Chris
- * 
- */
- at Target(METHOD)
- at Retention(RUNTIME)
-public @interface Cached {
-    /** true to indicate that the results from the method can be cached */
-    // CHECKSTYLE:OFF
-    boolean value = false;
-    // CHECKSTYLE:ON
-}

Added: trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/framework/StepJsonValueAdapter.java
===================================================================
--- trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/framework/StepJsonValueAdapter.java	                        (rev 0)
+++ trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/framework/StepJsonValueAdapter.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -0,0 +1,22 @@
+package com.tyndalehouse.step.rest.framework;
+
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+import com.avaje.ebeaninternal.server.text.json.DefaultJsonValueAdapter;
+
+/**
+ * we override the default JSON value adapter to provide custom serialisation for dates
+ * 
+ * @author Chris
+ * 
+ */
+public class StepJsonValueAdapter extends DefaultJsonValueAdapter {
+    @Override
+    public String jsonFromTimestamp(final Timestamp date) {
+        final SimpleDateFormat sdf = new SimpleDateFormat("\"EEE, d MMM yyyy HH:mm:ss Z\"",
+                Locale.getDefault());
+        return sdf.format(date);
+    }
+}


Property changes on: trunk/step/step-web/src/main/java/com/tyndalehouse/step/rest/framework/StepJsonValueAdapter.java
___________________________________________________________________
Added: svn:mime-type
   + text/plain

Modified: trunk/step/step-web/src/main/webapp/js/bookmark.js
===================================================================
--- trunk/step/step-web/src/main/webapp/js/bookmark.js	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-web/src/main/webapp/js/bookmark.js	2011-03-26 17:01:58 UTC (rev 225)
@@ -1,7 +1,8 @@
-
 /**
  * The bookmarks components record events that are happening across the application,
  * for e.g. passage changes, but will also show related information to the passage.
+ * 
+ * This could probably now be simplified by doing all the logic server side for the history
  */
 function Bookmark(bookmarkContainer) {
 	this.historyContainer = $("#historyDisplayPane");
@@ -17,7 +18,12 @@
 	this.bookmarkContainer.hear("bookmark-addition-requested", function(selfElement, data) {
 		self.addBookmark(data.reference);
 	});
+
 	
+	this.bookmarkContainer.hear("user-logged-in", function(selfElement, data) {
+		self.mergeHistory(data);
+	});
+	
 	this.initialiseHistory();
 	
 	//add accordion handlers
@@ -43,14 +49,15 @@
 		self.loadBookmarks();
 	}).hear("user-logged-out", function(selfElement, data) {
 		//we clear the bookmarks
-		
 		self.loadedBookmarks = false;
 		self.bookmarkContainer.html("");
 	});
 }
 
+//TODO make server setting
 Bookmark.maxBookmarks = 10;
-Bookmark.historyDelimiter = '#';
+//TODO make server setting
+Bookmark.historyDelimiter = '~';
 
 //we need to ignore the first two passage changes since we have those in the history
 //the history giving us the order of things
@@ -70,9 +77,18 @@
 	//if we have the bookmark already, then we stop here
 	var history = this.getHistory();
 	
-	var indexInHistory = $.inArray(passageReference, history);
+	//check if we have the reference in the array (starts for example: '1 John@' where @ denotes the time at which it happened
+	var indexInHistory = 0;
+	for(var indexInHistory = 0; indexInHistory < history.length; indexInHistory++) {
+		if(history[indexInHistory].match("^" + passageReference + "@")) {
+			break;
+		}
+	}
+
+	//if we didn't find the item
+	var fullHistoryStorageText = passageReference + "@" + new Date().getTime();
 	
-	if(indexInHistory == -1) {
+	if(indexInHistory == history.length) {
 		if(history.length > Bookmark.maxBookmarks) {
 			//we remove the first element in the array (i.e. the last child).
 			history.pop();
@@ -81,13 +97,15 @@
 		
 		//then add
 		this.createHistoryItem(passageReference);
-		history.unshift(passageReference);
+		history.unshift(fullHistoryStorageText);
+		$.get(HISTORY_ADD + fullHistoryStorageText);
 	} else {
 		//reposition item...
 		var item = $("div.bookmarkItem", this.historyContainer).eq(indexInHistory).detach();
 		history.splice(indexInHistory, 1);
 		this.historyContainer.prepend(item);
-		history.unshift(passageReference);
+		history.unshift(fullHistoryStorageText);
+		$.get(HISTORY_ADD + fullHistoryStorageText);
 	}
 	
 	this.setHistory(history);
@@ -103,13 +121,47 @@
 
 
 Bookmark.prototype.initialiseHistory = function() {
+	//create the history from the cookie, or - logged-in event will override
+	var self = this;
+	self.createHistoryItemsFromCookies();
+}
+
+/** 
+ * we need to work out what our current history is like, and then reset it to be appropriate
+ */
+Bookmark.prototype.mergeHistory = function() {
+	//we call this when we're logged on, so we get can send the history to the server, merge and set it back
+	var self = this;
+	$.getSafe(HISTORY_GET + $.cookie("history"), function(data) {
+		//we now have the merged history
+		self.clearHistory();
+		
+		var historyText = "";
+		if(data) {
+			$.each(data, function(index, item) {
+				//for each item, it has a lastUpdated and a historyReference
+				historyText += item.historyReference + '@' + new Date(item.lastUpdated).getTime() + Bookmark.historyDelimiter;
+			});
+			
+			$.cookie("history", historyText);
+			self.createHistoryItemsFromCookies();
+		}
+	});
+};
+
+/**
+ * creates the history from the items stored in the cookie,
+ * this is called either after setting the persisted history
+ * into the cookie, or when the user is not logged in!
+ */
+Bookmark.prototype.createHistoryItemsFromCookies = function() {
 	var history = this.getHistory();
 	if(history != null) {
 		for(var ii = history.length -1; ii >= 0; ii--) {
 			this.createHistoryItem(history[ii]);
 		}
 	}
-}
+};
 
 /**
  * loads the bookmarks from the server
@@ -128,7 +180,7 @@
 			}
 		});
 	}
-}
+};
 
 /**
  * creates a history item
@@ -144,8 +196,18 @@
 	this.createItem(passageReference, this.bookmarkContainer, false);
 };
 
+/**
+ * clears the history
+ */
+Bookmark.prototype.clearHistory = function() {
+	this.historyContainer.empty();
+};
 
-Bookmark.prototype.createItem = function(passageReference, container, ascending) {
+
+Bookmark.prototype.createItem = function(passageCookieReference, container, ascending) {
+	var passageRefParts = passageCookieReference.split('@');
+	var passageReference = passageRefParts[0];
+	
 	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>";

Modified: trunk/step/step-web/src/main/webapp/js/login.js
===================================================================
--- trunk/step/step-web/src/main/webapp/js/login.js	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-web/src/main/webapp/js/login.js	2011-03-26 17:01:58 UTC (rev 225)
@@ -20,6 +20,15 @@
 	this.root.hear("show-register-popup", function(selfElement, data) {
 		self.showLoginPopup();
 	});
+	
+	//we check whether we are logged on already...
+	$.getSafe(USER_GET_LOGGED_IN_USER, function(data) {
+		//if we have data, we use it to show it
+		if(data && data.name) {
+			//set the name of the user
+			self.setLoggedInUser(data.name);
+		}
+	});
 }
 
 /**
@@ -46,23 +55,12 @@
 				$.getSafe(USER_LOGIN + email + "/" + password, function(data) {
 					//we have logged in succesfully
 					//change the name of the user
-					var loginLink = $("#loginLink");
-					loginLink.text(data.name);
-					loginLink.unbind('click');
-					loginLink.click(function() {
-						self.showLogout();
-					});
-					
-					$(popup).dialog("close");
-					
-					if(callback) {
-						callback();
-					}
+					self.performSuccessfulLogin(data, popup, callback);
 				});
 			},
 			"Create an account" : function() {
 				//show register popup
-				self.showRegisterPopup();
+				self.showRegisterPopup(callback);
 			},
 			"Cancel" : function() {
 				$(this).dialog("close");
@@ -73,13 +71,43 @@
 	});
 };
 
-Login.prototype.showRegisterPopup = function() {
+/**
+ * performs a succesful login by changing the name at the top right of the screen
+ */
+Login.prototype.performSuccessfulLogin = function(data, popup, callback) {
+	this.setLoggedInUser(data.name);
+	$(popup).dialog("close");
+	
+	if(callback) {
+		callback();
+	}
+};
+
+/**
+ * shows the register popup
+ */
+Login.prototype.showRegisterPopup = function(callback) {
 	this.registerMode = true;
 	var self = this;
 	this.root.dialog({
 		buttons : { 
 			"Register" : function() {
 				//	send information to server
+				var email = $("#emailAddress", self.root).val();
+				var name = $("#name", self.root).val();
+				var country = $("#country", self.root).val();
+				var password = $("#password", self.root).val();
+				var popup = this;
+				
+				$.getSafe(USER_REGISTER + 
+						email + "/" +
+						name + "/" +
+						country + "/" +
+						password, 
+						function(data) {
+							self.performSuccessfulLogin(data, popup, callback);
+						}
+				);
 			},
 			"Cancel" : function() {
 				self.showLoginPopup();
@@ -121,3 +149,19 @@
 		}
 	});
 };
+
+/**
+ * sets the user's name up and rebinds the click to 
+ * be a logout operation instead.
+ */
+Login.prototype.setLoggedInUser = function(name) {
+	var loginLink = $("#loginLink");
+	var self = this;
+	
+	loginLink.text(name);
+	loginLink.unbind('click');
+	$.shout("user-logged-in");
+	loginLink.click(function() {
+		self.showLogout();
+	});
+}

Modified: trunk/step/step-web/src/main/webapp/js/ui_hooks.js
===================================================================
--- trunk/step/step-web/src/main/webapp/js/ui_hooks.js	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-web/src/main/webapp/js/ui_hooks.js	2011-03-26 17:01:58 UTC (rev 225)
@@ -9,9 +9,12 @@
 // The following section defines method names and controller names
 // These are used as part of the rest-like calls
 /////////////////////////////////////////////////////////////////////////
-BOOKMARKS_GET = "rest/bookmark/getBookmarks";
-BOOKMARKS_ADD = "rest/bookmark/addBookmark/";
+BOOKMARKS_GET = "rest/favourites/getBookmarks";
+BOOKMARKS_ADD = "rest/favourites/addBookmark/";
+HISTORY_GET = "rest/favourites/getHistory/";
+HISTORY_ADD = "rest/favourites/addHistory/";
 
+
 BIBLE_GET_BIBLE_VERSIONS = "rest/bible/getBibleVersions/";
 BIBLE_GET_BIBLE_TEXT = "rest/bible/getBibleText/";
 BIBLE_GET_FEATURES = "rest/bible/getFeatures/";
@@ -31,7 +34,8 @@
 USER_LOGIN = "rest/user/login/";
 USER_LOGOUT = "rest/user/logout/";
 USER_REGISTER = "rest/user/register/"
-
+USER_GET_LOGGED_IN_USER = "rest/user/getLoggedInUser";
+	
 //////////////////////////
 // SOME DEFAULTS
 //////////////////////////

Modified: 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	2011-03-22 10:29:42 UTC (rev 224)
+++ trunk/step/step-web/src/test/java/com/tyndalehouse/step/rest/controllers/FrontControllerTest.java	2011-03-26 17:01:58 UTC (rev 225)
@@ -16,7 +16,6 @@
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.util.ArrayList;
 
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletContext;
@@ -27,6 +26,7 @@
 
 import org.junit.Test;
 
+import com.avaje.ebean.EbeanServer;
 import com.google.inject.Injector;
 import com.tyndalehouse.step.core.exceptions.StepInternalException;
 import com.tyndalehouse.step.core.service.BibleInformationService;
@@ -38,6 +38,7 @@
  * @author Chris
  * 
  */
+ at SuppressWarnings("PMD.TooManyMethods")
 public class FrontControllerTest {
 
     /**
@@ -50,7 +51,7 @@
         final HttpServletRequest request = mock(HttpServletRequest.class);
         final HttpServletResponse response = mock(HttpServletResponse.class);
 
-        final FrontController fc = spy(new FrontController(null, false, null, null));
+        final FrontController fc = spy(new FrontController(null, false, mock(EbeanServer.class), null));
         final StepRequest parsedRequest = new StepRequest("SomeController", "someMethod", new String[] {
                 "arg1", "arg2" });
         final ServletOutputStream mockOutputStream = mock(ServletOutputStream.class);
@@ -74,7 +75,7 @@
         final HttpServletResponse response = mock(HttpServletResponse.class);
         final StepInternalException testException = new StepInternalException("A test exception");
 
-        final FrontController fc = spy(new FrontController(null, false, null, null));
+        final FrontController fc = spy(new FrontController(null, false, mock(EbeanServer.class), null));
         final StepRequest parsedRequest = new StepRequest("SomeController", "someMethod", new String[] {
                 "arg1", "arg2" });
 
@@ -94,7 +95,8 @@
         // index starts at ...........0123456789-123456789-123456
         final String sampleRequest = "step-web/rest/bible/get/1K2/2K2";
 
-        final FrontController fc = new FrontController(mock(Injector.class), Boolean.FALSE, null, null);
+        final FrontController fc = new FrontController(mock(Injector.class), Boolean.FALSE,
+                mock(EbeanServer.class), null);
 
         // when
         final Object[] args = fc.getArgs(sampleRequest, 24);
@@ -113,7 +115,8 @@
         // index starts at ...........0123456789-123456789-123456
         final String sampleRequest = "step-web/rest/bible/get/1K2/2K2/";
 
-        final FrontController fc = new FrontController(mock(Injector.class), Boolean.FALSE, null, null);
+        final FrontController fc = new FrontController(mock(Injector.class), Boolean.FALSE,
+                mock(EbeanServer.class), null);
 
         // when
         final Object[] args = fc.getArgs(sampleRequest, 24);
@@ -131,7 +134,7 @@
      */
     @Test
     public void testGetPath() throws ServletException {
-        final FrontController fc = new FrontController(null, null, null, null);
+        final FrontController fc = new FrontController(null, null, mock(EbeanServer.class), null);
         final FrontController spy = spy(fc);
 
         final ServletContext mockServletContext = mock(ServletContext.class);
@@ -158,7 +161,8 @@
         final HttpServletResponse response = mock(HttpServletResponse.class);
 
         final int sampleRequestLength = 10;
-        new FrontController(null, null, null, null).setupHeaders(response, sampleRequestLength);
+        new FrontController(null, null, mock(EbeanServer.class), null).setupHeaders(response,
+                sampleRequestLength);
 
         verify(response).addDateHeader(eq("Date"), anyLong());
         verify(response).setCharacterEncoding("UTF-8");
@@ -176,7 +180,7 @@
     @Test
     public void testGetControllerMethod() throws IllegalAccessException, InvocationTargetException {
         final FrontController frontController = new FrontController(mock(Injector.class), Boolean.FALSE,
-                null, null);
+                mock(EbeanServer.class), null);
         final BibleInformationService bibleInfo = mock(BibleInformationService.class);
         final BibleController controllerInstance = new BibleController(bibleInfo);
 
@@ -196,7 +200,8 @@
     public void testGetController() {
         final String controllerName = "Bible";
         final Injector mockInjector = mock(Injector.class);
-        final FrontController frontController = new FrontController(mockInjector, Boolean.FALSE, null, null);
+        final FrontController frontController = new FrontController(mockInjector, Boolean.FALSE,
+                mock(EbeanServer.class), null);
 
         final BibleController mockController = mock(BibleController.class);
         when(mockInjector.getInstance(BibleController.class)).thenReturn(mockController);
@@ -213,12 +218,12 @@
      */
     @Test
     public void testGetClasses() {
-        final FrontController fc = new FrontController(null, Boolean.FALSE, null, null);
+        final FrontController fc = new FrontController(null, Boolean.FALSE, mock(EbeanServer.class), null);
 
         assertEquals(0, fc.getClasses(null).length);
         assertEquals(0, fc.getClasses(new Object[0]).length);
-        assertArrayEquals(new Class<?>[] { String.class, ArrayList.class },
-                fc.getClasses(new Object[] { "hello", new ArrayList<String>() }));
+        assertArrayEquals(new Class<?>[] { String.class, Integer.class },
+                fc.getClasses(new Object[] { "hello", Integer.valueOf(1) }));
 
     }
 
@@ -227,7 +232,7 @@
      */
     @Test
     public void testJsonEncoding() {
-        final byte[] encodedJsonResponse = new FrontController(null, null, null, null)
+        final byte[] encodedJsonResponse = new FrontController(null, null, mock(EbeanServer.class), null)
                 .getEncodedJsonResponse("abc");
 
         // this reprensents the string "{abc}"
@@ -243,7 +248,7 @@
      */
     @Test
     public void testDoErrorHandlesCorrectly() throws IOException {
-        final FrontController fc = new FrontController(null, null, null, null);
+        final FrontController fc = new FrontController(null, null, mock(EbeanServer.class), null);
         final HttpServletResponse response = mock(HttpServletResponse.class);
         final StepRequest stepRequest = new StepRequest("controller", "method", null);
         final ServletOutputStream outputStream = mock(ServletOutputStream.class);
@@ -278,7 +283,7 @@
                 contextName + requestSeparator + servletName + requestSeparator + controllerName
                         + requestSeparator + methodName + requestSeparator + arg1 + requestSeparator + arg2);
 
-        final FrontController frontController = new FrontController(null, null, null, null);
+        final FrontController frontController = new FrontController(null, null, mock(EbeanServer.class), null);
         frontController.init(mock(ServletConfig.class));
 
         final FrontController spy = spy(frontController);
@@ -303,7 +308,7 @@
         final StepRequest sr = new StepRequest("bible", "getAllFeatures", new String[] {});
         final BibleController testController = mock(BibleController.class);
 
-        final FrontController fc = spy(new FrontController(null, null, null, null));
+        final FrontController fc = spy(new FrontController(null, null, mock(EbeanServer.class), null));
         doReturn(testController).when(fc).getController("bible");
 
         // do test




More information about the Tynstep-svn mailing list