diff --git a/apps/dashboard-app/src/main/js/src/app/diagnostic/sidebar/sidebar.tpl.html b/apps/dashboard-app/src/main/js/src/app/diagnostic/sidebar/sidebar.tpl.html
index 365a6106b..ba9b32115 100755
--- a/apps/dashboard-app/src/main/js/src/app/diagnostic/sidebar/sidebar.tpl.html
+++ b/apps/dashboard-app/src/main/js/src/app/diagnostic/sidebar/sidebar.tpl.html
@@ -1,62 +1,62 @@
 <div class="navbar-default sidebar" role="navigation" ng-show="vm.options">
     <div>
         <div class="list-group panel" style="    max-height: 600px; overflow: auto;">
 
             <span ui-sref-active="shrine-on">
                 <a href="#shrine-diagnostic1"
                    class="list-group-item shrine-off"
                    ui-sref="diagnostic.summary" data-toggle="collapse" >Summary</a>
             </span>
             <span ui-sref-active="shrine-on">
                 <a href="#shrine-diagnostic2" class="list-group-item shrine-off"
                    ui-sref="diagnostic.i2b2-connections" data-toggle="collapse">i2b2
                     Connections</a>
             </span>
             <span ui-sref-active="shrine-on">
                 <a href="#shrine-diagnostic3" class="list-group-item shrine-off"
                    ui-sref="diagnostic.keystore" data-toggle="collapse">Keystore</a>
             </span>
             <span ui-sref-active="shrine-on" ng-if="vm.options.isHub">
                 <a href="#shrine-diagnostic4" class="list-group-item shrine-off"
                    ui-sref="diagnostic.hub" data-toggle="collapse">Hub</a>
             </span>
             <span ui-sref-active="shrine-on">
                 <a href="#shrine-diagnostic5" class="list-group-item shrine-off"
                    ui-sref="diagnostic.adapter" data-toggle="collapse">Adapter</a>
             </span>
             <span ui-sref-active="shrine-on">
                 <a href="#shrine-diagnostic6" class="list-group-item shrine-off"
                    ui-sref="diagnostic.qep" data-toggle="collapse">QEP</a>
             </span>
             <span ui-sref-active="shrine-on">
                 <a href="#shrine-diagnostic7" class="list-group-item shrine-off"
-                   ui-sref="diagnostic.config" data-toggle="collapse">Config File</a>
+                   ui-sref="diagnostic.config" data-toggle="collapse">Config</a>
             </span>
             <span ui-sref-active="shrine-on" ng-if="vm.options.isHub">
                 <a href="#shrine-diagnostic8" class="list-group-item shrine-off"
                    ui-sref="diagnostic.dashboard" data-toggle="collapse">Remote Dashboards</a>
             </span>
             <span ui-sref-active="shrine-on">
                 <a href="#shrine-diagnostic9" class="list-group-item shrine-off"
                    ui-sref="diagnostic.problems" data-toggle="collapse">Problem Log</a>
             </span>
             <!-- if hub and there are downstream nodes -->
             <!--span ui-sref-active="shrine-on">
                 <span ui-sref="diagnostic.downstream-nodes">
                      <a href="#shrine-diagnostic8" class="list-group-item shrine-off"
                         data-toggle="collapse">Downstream Nodes<i class="fa fa-caret-down"></i></a>
                 </span>
             </span>
             <div class="collapse" id="shrine-diagnostic8">
                 <span ng-repeat="node in sbVM.options.downstreamNodes">
                     <downstream-node node-index="{{$index}}" node-data="node">
                     </downstream-node>
                 </span>
             </div-->
         </div>
     </div>
 </div>
 
 
 
 
diff --git a/apps/dashboard-app/src/main/js/src/app/diagnostic/views/hub.tpl.html b/apps/dashboard-app/src/main/js/src/app/diagnostic/views/hub.tpl.html
index 548a5d6e8..865ddf181 100644
--- a/apps/dashboard-app/src/main/js/src/app/diagnostic/views/hub.tpl.html
+++ b/apps/dashboard-app/src/main/js/src/app/diagnostic/views/hub.tpl.html
@@ -1,38 +1,38 @@
 <div>
     <error-handler error="vm.hubError" error-name="'hub'"></error-handler>
     <div ng-hide="vm.hubError">
         <table class="table table-striped">
             <thead>
             <tr>
                 <td colspan="2">
-                    These are the sites that this SHRINE hub will
+                    Downstream Nodes
                 </td>
             </tr>
             <tr>
                 <td colspan="2">
-                    Downstream Nodes
+                    These are the sites that this SHRINE hub will broadcast queries to
                 </td>
             </tr>
             <tr>
                 <td>
                     Site
                 </td>
                 <td>
                     URL
                 </td>
             </tr>
             </thead>
 
             <tbody>
             <tr ng-repeat="node in vm.downstreamNodes track by $index">
                 <td>
                     {{node.name}}
                 </td>
                 <td>
                     {{node.url}}
                 </td>
             </tr>
             </tbody>
         </table>
     </div>
 </div>
\ No newline at end of file
diff --git a/apps/dashboard-app/src/main/js/src/app/diagnostic/views/keystore.controller.js b/apps/dashboard-app/src/main/js/src/app/diagnostic/views/keystore.controller.js
index a442d6c13..cbbc7be8e 100644
--- a/apps/dashboard-app/src/main/js/src/app/diagnostic/views/keystore.controller.js
+++ b/apps/dashboard-app/src/main/js/src/app/diagnostic/views/keystore.controller.js
@@ -1,128 +1,120 @@
 (function () {
     'use strict';
 
     // -- register controller with angular -- //
     angular.module('shrine-tools')
         .controller('KeystoreController', KeystoreController);
 
 
     /**
      *
      * @type {string[]}
      */
     KeystoreController.$inject = ['$app', '$log'];
     function KeystoreController ($app, $log) {
         var vm = this;
         var map = $app.model.map;
         vm.qepError = false;
         vm.keyStoreError = false;
         init();
 
 
         /**
          *
          */
         function init() {
             $app.model.getKeystore()
                 .then(setKeystore, handleKeyStoreFailure);
 
             $app.model.getQep()
                 .then(setQep, handleQepFailure)
         }
 
         function setQep(qep) {
             vm.trustModelIsHub = qep.trustModelIsHub
         }
 
         /**
          *
          * @param keystore
          */
         function setKeystore (keystore) {
             vm.isHub = keystore.isHub;
             vm.keystore = {
                 file:       keystore.fileName,
                 password:   "REDACTED"
             };
             vm.certificate = [
                 ['Alias',             keystore.privateKeyAlias],
                 ['Owner',             keystore.owner],
                 ['Issuer',            keystore.issuer],
-                ['Expires',           keystore.expires],
+                ['Expires',           new Date(keystore.expires).toUTCString()],
                 ['MD5 Signature',     keystore.md5Signature],
                 ['SHA256 Signature',  keystore.sha256Signature]
             ];
 
             vm.caCertificate = [
                 ['Alias',             keystore.caTrustedAlias],
                 ['MD5 Signature',     keystore.caTrustedSignature]
             ];
 
 
             vm.downStreamValidation = downStreamValidation(keystore);
             vm.peerCertValidation   = peerCertValidation(keystore);
             vm.hubCertValidation    = hubCertValidation(keystore);
             vm.keyStoreContents     = keyStoreContents(keystore)
         }
 
         function keyStoreContents(keystore) {
             function handleEntry(entry) {
                 return [entry.alias, entry.cn, entry.md5]
             }
             return map(handleEntry, keystore.abbreviatedEntries)
         }
 
         function downStreamValidation(keystore) {
             if (keystore.remoteSiteStatuses.length == 0) {
                 return [];
             }
             var remoteSite = keystore.remoteSiteStatuses[0];
             if (remoteSite.timeOutError) {
                 return [['Timed Out', "Timed out while connecting to the hub"]]
             } else {
                 return [
                     ["CA Certificate Matches Hub's?", remoteSite.haveTheirs? "Yes": "No"]
                 ]
             }
         }
 
         function hubCertValidation(keystore) {
             function handleStatus(siteStatus) {
                 if (siteStatus.timeOutError) {
                     return [siteStatus.siteAlias, "Timed Out"]
                 } else {
                     return [siteStatus.siteAlias, siteStatus.theyHaveMine]
                 }
             }
 
-            if (keystore.remoteSiteStatuses.length > 0 && keystore.remoteSiteStatuses[0].length == 2) {
-                return map(handleStatus, keystore.remoteSiteStatuses)
-            } else {
-                return []
-            }
+            return map(handleStatus, keystore.remoteSiteStatuses)
         }
 
         function peerCertValidation(keystore) {
             function handleStatus(siteStatus) {
                 if (siteStatus.timeOutError) {
                     return [siteStatus.siteAlias, "Timed Out", "N/A"]
                 } else {
                     return [siteStatus.siteAlias, siteStatus.theyHaveMine, siteStatus.haveTheirs]
                 }
             }
-            if (keystore.remoteSiteStatuses.length > 0 && keystore.remoteSiteStatuses[0].length == 3) {
-                return map(handleStatus, keystore.remoteSiteStatuses)
-            } else {
-                return [];
-            }
+            return map(handleStatus, keystore.remoteSiteStatuses)
         }
 
         function handleKeyStoreFailure(failure) {
             vm.keyStoreError = failure;
         }
 
         function handleQepFailure(failure) {
             vm.qepError = failure;
         }
     }
 })();
\ No newline at end of file
diff --git a/apps/dashboard-app/src/main/js/src/app/diagnostic/views/keystore.tpl.html b/apps/dashboard-app/src/main/js/src/app/diagnostic/views/keystore.tpl.html
index c086bf7da..6aaf95165 100644
--- a/apps/dashboard-app/src/main/js/src/app/diagnostic/views/keystore.tpl.html
+++ b/apps/dashboard-app/src/main/js/src/app/diagnostic/views/keystore.tpl.html
@@ -1,199 +1,207 @@
 <div>
     <error-handler error="vm.keyStoreError" error-name="'keyStore'"></error-handler>
     <error-handler error="vm.qepError" error-name="'qep'"></error-handler>
     <div ng-hide="vm.keyStoreError || vm.qepError">
         <table class="table table-striped">
             <thead>
             <tr>
                 <td colspan="2">
                     Keystore File
                 </td>
             </tr>
             <tr>
                 <td colspan="2">
                     This is the keystore SHRINE will use to sign and trust queries.
                 </td>
             </tr>
             </thead>
 
             <tbody>
             <tr>
                 <td>
                     File
 
                 </td>
                 <td>
                     {{vm.keystore.file}}
                 </td>
             </tr>
             <tr>
                 <td>
                     Password
 
                 </td>
                 <td>
                     {{vm.keystore.password}}
                 </td>
             </tr>
             </tbody>
         </table>
 
 
         <table class="table table-striped">
             <thead>
             <tr>
                 <td colspan="2">
                     Query Signing Certificate
                 </td>
             </tr>
             <tr>
                 <td colspan="2">
                     This is the certificate that this SHRINE server will use for signing queries.
                 </td>
             </tr>
             </thead>
 
             <tbody>
             <tr ng-repeat="certField in vm.certificate track by $index">
                 <td>
                     {{certField[0]}}
                 </td>
                 <td>
                     {{certField[1]}}
                 </td>
             </tr>
             </tbody>
         </table>
 
         <table class="table table-striped" ng-show="vm.trustModelIsHub">
             <thead>
             <tr>
                 <td colspan="2">
                     CA Certificate Information
                 </td>
             </tr>
             <tr>
                 <td colspan="2">
                     This lists the certificates that this node recognizes as valid hub CA signatures.
                 </td>
             </tr>
             </thead>
             <tbody>
             <tr ng-repeat="certField in vm.caCertificate track by $index">
                 <td>
                     {{certField[0]}}
                 </td>
                 <td>
                     {{certField[1]}}
                 </td>
             </tr>
             </tbody>
         </table>
 
         <table class="table table-striped" ng-show="vm.trustModelIsHub && !vm.isHub">
             <thead>
             <tr>
                 <td colspan="2">
                     Certificate Validation with Hub
                 </td>
             </tr>
             </thead>
             <tbody>
             <tr ng-repeat="certField in vm.downStreamValidation">
                 <td>
                     {{certField[0]}}
                 </td>
                 <td>
                     {{certField[1]}}
                 </td>
             </tr>
             </tbody>
         </table>
 
         <table class="table table-striped" ng-hide="vm.trustModelIsHub">
             <thead>
             <tr>
                 <td colspan="3">
                     Certificate Validation
                 </td>
             </tr>
             <tr>
                 <td>
                     Site
                 </td>
                 <td>
                     Site contains local cert
                 </td>
                 <td>
                     This node contains site cert
                 </td>
             </tr>
             </thead>
             <tbody>
             <tr ng-repeat="certField in vm.peerCertValidation">
                 <td>
                     {{certField[0]}}
                 </td>
                 <td>
                     {{certField[1]}}
                 </td>
                 <td>
                     {{certField[2]}}
                 </td>
             </tr>
             </tbody>
         </table>
 
         <table class="table table-striped" ng-show="vm.isHub">
             <thead>
             <tr>
                 <td colspan="2">
                     Certificate Validation
                 </td>
             </tr>
             <tr>
                 <td>
                     Site
                 </td>
                 <td>
                     Site contains correct CA Cert
                 </td>
             </tr>
             </thead>
             <tbody>
             <tr ng-repeat="certField in vm.hubCertValidation">
                 <td>
                     {{certField[0]}}
                 </td>
                 <td>
                     {{certField[1]}}
                 </td>
             </tr>
             </tbody>
         </table>
 
 
         <table class="table table-striped">
             <thead>
             <tr>
-                <td colspan="2">
+                <td colspan="1">
                     Keystore Contents
                 </td>
+                <td colspan="2">
+                    This is a list of all certificates in the SHRINE keystore
+                </td>
+            </tr>
+            <tr>
+                <td>Alias</td>
+                <td>CN</td>
+                <td>MD5 Signature</td>
             </tr>
             </thead>
             <tbody>
             <tr ng-repeat="certField in vm.keyStoreContents">
                 <td>
                     {{certField[0]}}
                 </td>
                 <td>
                     {{certField[1]}}
                 </td>
                 <td>
                     {{certField[2]}}
                 </td>
             </tr>
             </tbody>
         </table>
     </div>
 </div>
\ No newline at end of file
diff --git a/apps/dashboard-app/src/main/js/test/admin/status/i2b2.json b/apps/dashboard-app/src/main/js/test/admin/status/i2b2.json
index d11d87897..4c8e9b6d3 100644
--- a/apps/dashboard-app/src/main/js/test/admin/status/i2b2.json
+++ b/apps/dashboard-app/src/main/js/test/admin/status/i2b2.json
@@ -1,2 +1,9 @@
-{"pmUrl":"http://services.i2b2.org/i2b2/rest/PMService/getServices","crcUrl":"http://services.i2b2.org/i2b2/rest/QueryToolService/","ontUrl":"","i2b2Domain":"HarvardDemo","username":"demo","crcProject":"Demo","ontProject":"SHRINE"}
-
+{
+  "pmUrl": "http://services.i2b2.org/i2b2/rest/PMService/getServices",
+  "crcUrl": "http://services.i2b2.org/i2b2/rest/QueryToolService/",
+  "ontUrl": "http://services.i2b2.org/i2b2/rest/OntologyService",
+  "i2b2Domain": "HarvardDemo",
+  "username": "demo",
+  "crcProject": "Demo",
+  "ontProject": "SHRINE"
+}
\ No newline at end of file
diff --git a/apps/dashboard-app/src/test/resources/teststatus/i2b2 b/apps/dashboard-app/src/test/resources/teststatus/i2b2
index 1ef1c5fda..4c8e9b6d3 100644
--- a/apps/dashboard-app/src/test/resources/teststatus/i2b2
+++ b/apps/dashboard-app/src/test/resources/teststatus/i2b2
@@ -1 +1,9 @@
-{"pmUrl":"http://services.i2b2.org/i2b2/rest/PMService/getServices","crcUrl":"http://services.i2b2.org/i2b2/rest/QueryToolService/","ontUrl":"","i2b2Domain":"HarvardDemo","username":"demo","crcProject":"Demo","ontProject":"SHRINE"}
+{
+  "pmUrl": "http://services.i2b2.org/i2b2/rest/PMService/getServices",
+  "crcUrl": "http://services.i2b2.org/i2b2/rest/QueryToolService/",
+  "ontUrl": "http://services.i2b2.org/i2b2/rest/OntologyService",
+  "i2b2Domain": "HarvardDemo",
+  "username": "demo",
+  "crcProject": "Demo",
+  "ontProject": "SHRINE"
+}
\ No newline at end of file
diff --git a/apps/shrine-app/src/main/scala/net/shrine/status/StatusJaxrs.scala b/apps/shrine-app/src/main/scala/net/shrine/status/StatusJaxrs.scala
index b81097c2f..eef39c150 100644
--- a/apps/shrine-app/src/main/scala/net/shrine/status/StatusJaxrs.scala
+++ b/apps/shrine-app/src/main/scala/net/shrine/status/StatusJaxrs.scala
@@ -1,607 +1,607 @@
 package net.shrine.status
 
 import java.security.Security
 import java.security.cert.X509Certificate
 import java.util.Date
 import javax.net.ssl.{KeyManager, SSLContext, X509TrustManager}
 import javax.ws.rs.core.{MediaType, Response}
 import javax.ws.rs.{GET, Path, Produces, WebApplicationException}
 
 import akka.actor.ActorSystem
 import akka.io.IO
 import akka.util.Timeout
 import com.sun.jersey.spi.container.{ContainerRequest, ContainerRequestFilter}
 import com.typesafe.config.{ConfigFactory, Config => TsConfig}
 import net.shrine.authorization.{QueryAuthorizationService, StewardQueryAuthorizationService}
 import net.shrine.broadcaster._
 import net.shrine.client.PosterOntClient
 import net.shrine.config.ConfigExtensions
 import net.shrine.crypto._
 import net.shrine.log.{Log, Loggable}
 import net.shrine.ont.data.OntClientOntologyMetadata
 import net.shrine.problem.{AbstractProblem, ProblemSources}
 import net.shrine.protocol._
 import net.shrine.protocol.query.{OccuranceLimited, QueryDefinition, Term}
 import net.shrine.serialization.NodeSeqSerializer
 import net.shrine.spray._
 import net.shrine.util.{PeerToPeerModel, SingleHubModel, Versions}
 import net.shrine.wiring.ShrineOrchestrator
 import org.json4s.native.Serialization
 import org.json4s.{DefaultFormats, Formats}
 import spray.can.Http
 import spray.can.Http.{HostConnectorInfo, HostConnectorSetup}
 import spray.client.pipelining._
 import spray.http.{ContentType, ContentTypes, FormData, HttpCharsets, HttpEntity, HttpHeaders, HttpRequest, HttpResponse, MediaTypes}
 import spray.io.{ClientSSLEngineProvider, PipelineContext, SSLContextProvider}
 
 import scala.collection.JavaConverters._
 import scala.collection.immutable.{Map, Seq, Set}
 import scala.concurrent.duration.FiniteDuration
 import scala.concurrent.{Await, Future, duration}
 import scala.util.control.NonFatal
 import scala.util.{Failure, Success, Try}
 
 /**
   * A subservice that shares internal state of the shrine servlet.
   *
   * @author david 
   * @since 12/2/15
   */
 @Path("/internalstatus")
 @Produces(Array(MediaType.APPLICATION_JSON))
 case class StatusJaxrs(shrineConfig: TsConfig) extends Loggable {
 
   implicit def json4sFormats: Formats = DefaultFormats + new NodeSeqSerializer
 
   @GET
   @Path("version")
   def version: String = {
     val version = Version("changeMe")
     val versionString = Serialization.write(version)
     versionString
   }
 
   @GET
   @Path("config")
   def config: String = {
     //todo probably better to reach out and grab the config from ManuallyWiredShrineJaxrsResources once it is a singleton
     Serialization.write(Json4sConfig(shrineConfig))
   }
 
   @GET
   @Path("summary")
   def summary: String = {
     val summary = Summary()
     Serialization.write(summary)
   }
 
   @GET
   @Path("i2b2")
   def i2b2: String = {
     val i2b2 = I2b2()
     Serialization.write(i2b2)
   }
 
   @GET
   @Path("optionalParts")
   def optionalParts: String = {
     val optionalParts = OptionalParts()
     Serialization.write(optionalParts)
   }
 
   @GET
   @Path("hub")
   def hub: String = {
     val hub = Hub()
     Serialization.write(hub)
   }
 
   @GET
   @Path("adapter")
   def adapter: String = {
     val adapter = Adapter()
     Serialization.write(adapter)
   }
 
   @GET
   @Path("qep")
   def qep: String = {
     val qep = Qep()
     Serialization.write(qep)
   }
 
   @GET
   @Path("keystore")
   def keystore: String = {
     Serialization.write(KeyStoreReport())
   }
 
 
 }
 
 /* todo fill in later when you take the time to get the right parts in place SHRINE-1529
 case class KeyStoreEntryReport(
                               alias:String,
                               commonName:String,
                               md5Signature:String
                               )
 */
 case class SiteStatus(siteAlias: String, theyHaveMine: Boolean, haveTheirs: Boolean, url: String, timeOutError: Boolean = false) extends DefaultJsonSupport
 
 case class AbbreviatedKeyStoreEntry(alias: String, cn: String, md5: String) extends DefaultJsonSupport
 
 case class KeyStoreReport(
                            fileName: String,
                            password: String = "REDACTED",
                            privateKeyAlias: Option[String],
                            owner: Option[String],
                            issuer: Option[String],
-                           expires: Date,
+                           expires: Long,
                            md5Signature: String,
                            sha256Signature: String,
                            caTrustedAlias: Option[String],
                            caTrustedSignature: Option[String],
                            remoteSiteStatuses: Seq[SiteStatus],
                            isHub: Boolean,
                            abbreviatedEntries: Seq[AbbreviatedKeyStoreEntry]
                            //                        keyStoreContents:List[KeyStoreEntryReport] //todo SHRINE-1529
                          )
 
 //todo build new API for the dashboard to use to check signatures
 
 object KeyStoreReport {
   def apply(): KeyStoreReport = {
     val keyStoreDescriptor: KeyStoreDescriptor = ShrineOrchestrator.keyStoreDescriptor
     val certCollection: BouncyKeyStoreCollection = ShrineOrchestrator.certCollection
     val maybeCaEntry: Option[KeyStoreEntry] = certCollection match {
       case DownStreamCertCollection(_, caEntry, _) => Some(caEntry)
       case HubCertCollection(_, caEntry, _)        => Some(caEntry)
       case px: PeerCertCollection                  => None
     }
     val siteStatusesPreZip = ShaVerificationService(certCollection.remoteSites.toList)
     val siteStatuses = siteStatusesPreZip.zipWithIndex
 
     def sortFormat(input: String): Option[String] = {
       if (input.isEmpty) None
       else {
         def isLong(str: String) = str.split('=').headOption.getOrElse(str).length > 2
         // Just an ugly sort for formatting purposes. I want The long key last, and otherwise just
         // Sort them lexicographically.
         Some(input.split(", ").sortBy(a => (isLong(a), a)).mkString(", "))
       }
     }
 
     lazy val blockForSiteStatuses = siteStatuses.map(fut => Try(Await.result(fut._1, new FiniteDuration(5, duration.SECONDS))) match {
       case Success(Some(status)) => status
       case Success(None)         => Log.warn("There was an issue with the verifySignature endpoint, check that we have network connectivity")
         SiteStatus(certCollection.remoteSites(fut._2).alias, false, false, "", true)
       case Failure(exc)          => Log.warn("We timed out while trying to connect to the verifySignature endpoint, please check network connectivity")
         SiteStatus(certCollection.remoteSites(fut._2).alias, false, false, "", true)
     })
 
     new KeyStoreReport(
       fileName = keyStoreDescriptor.file,
       privateKeyAlias = keyStoreDescriptor.privateKeyAlias,
       owner = sortFormat(certCollection.myEntry.cert.getSubjectDN.getName),
       issuer = sortFormat(certCollection.myEntry.cert.getIssuerDN.getName),
-      expires = certCollection.myEntry.cert.getNotAfter,
+      expires = certCollection.myEntry.cert.getNotAfter.getTime,
       md5Signature = UtilHasher.encodeCert(certCollection.myEntry.cert, "MD5"),
       sha256Signature = UtilHasher.encodeCert(certCollection.myEntry.cert, "SHA-256"),
       //todo sha1 signature if needed
       caTrustedAlias = maybeCaEntry.map(_.aliases.first),
       caTrustedSignature = maybeCaEntry.map(entry => UtilHasher.encodeCert(entry.cert, "MD5")),
       remoteSiteStatuses = blockForSiteStatuses,
       isHub = keyStoreDescriptor.trustModel == SingleHubModel(true),
       abbreviatedEntries = certCollection.allEntries.map(entry => AbbreviatedKeyStoreEntry(
         entry.aliases.first,
         entry.commonName.getOrElse("No common name"),
         UtilHasher.encodeCert(entry.cert, "MD5"))).toList
 
       //      keyStoreContents = certCollection.caCerts.zipWithIndex.map((cert: ((Principal, X509Certificate), Int)) => KeyStoreEntryReport(keyStoreDescriptor.caCertAliases(cert._2),cert._1._1.getName,toMd5(cert._1._2))).to[List]
     )
   }
 }
 
 case class I2b2(pmUrl: String,
                 crcUrl: Option[String],
                 ontUrl: String,
                 i2b2Domain: String,
                 username: String,
                 crcProject: String,
                 ontProject: String)
 
 object I2b2 {
   def apply(): I2b2 = new I2b2(
     pmUrl = ShrineOrchestrator.pmPoster.url,
     crcUrl = ShrineOrchestrator.adapterComponents.map(_.i2b2AdminService.crcUrl),
-    ontUrl = "", //todo. Grab from HiveConfigd?
+    ontUrl = ShrineOrchestrator.ontEndpoint.url.toString,
     i2b2Domain = ShrineOrchestrator.crcHiveCredentials.domain,
     username = ShrineOrchestrator.crcHiveCredentials.username,
     crcProject = ShrineOrchestrator.crcHiveCredentials.projectId,
     ontProject = ShrineOrchestrator.ontologyMetadata.client match {
       case client: PosterOntClient => client.hiveCredentials.projectId
       case _                       => ""
     }
   )
 }
 
 case class DownstreamNode(name: String, url: String)
 
 // Replaces StewardQueryAuthorizationService so that we never transmit a password
 case class Steward(stewardBaseUrl: String, qepUsername: String, password: String = "REDACTED")
 
 case class Qep(
                 maxQueryWaitTimeMillis: Long,
                 create: Boolean,
                 attachSigningCert: Boolean,
                 authorizationType: String,
                 includeAggregateResults: Boolean,
                 authenticationType: String,
                 steward: Option[Steward],
                 broadcasterUrl: Option[String],
                 trustModel: String,
                 trustModelIsHub: Boolean
               )
 
 object Qep {
   val key = "shrine.queryEntryPoint."
 
   import ShrineOrchestrator.queryEntryPointComponents
 
   def apply(): Qep = new Qep(
     maxQueryWaitTimeMillis = queryEntryPointComponents.fold(0L)(_.i2b2Service.queryTimeout.toMicros),
     create = queryEntryPointComponents.isDefined,
     attachSigningCert = queryEntryPointComponents.fold(false)(_.i2b2Service.broadcastAndAggregationService.attachSigningCert),
     authorizationType = queryEntryPointComponents.fold("")(_.i2b2Service.authorizationService.getClass.getSimpleName),
     includeAggregateResults = queryEntryPointComponents.fold(false)(_.i2b2Service.includeAggregateResult),
     authenticationType = queryEntryPointComponents.fold("")(_.i2b2Service.authenticator.getClass.getSimpleName),
     steward = queryEntryPointComponents.flatMap(qec => checkStewardAuthorization(qec.shrineService.authorizationService)),
     broadcasterUrl = queryEntryPointComponents.flatMap(qec => checkBroadcasterUrl(qec.i2b2Service.broadcastAndAggregationService)),
     trustModel = ShrineOrchestrator.keyStoreDescriptor.trustModel.description,
     trustModelIsHub = ShrineOrchestrator.keyStoreDescriptor.trustModel match {
       case sh: SingleHubModel => true
       case PeerToPeerModel    => false
     }
   )
 
   def checkStewardAuthorization(auth: QueryAuthorizationService): Option[Steward] = auth match {
     case sa: StewardQueryAuthorizationService => Some(Steward(sa.stewardBaseUrl.toString, sa.qepUserName))
     case _                                    => None
   }
 
   //TODO: Double check with Dave that this is the right url
   def checkBroadcasterUrl(broadcaster: BroadcastAndAggregationService): Option[String] = broadcaster match {
     case a: HubBroadcastAndAggregationService => a.broadcasterClient match {
       case PosterBroadcasterClient(poster, _) => Some(poster.url)
       case _                                  => None
     }
     case _                                    => None
   }
 }
 
 object DownstreamNodes {
   def get(): Seq[DownstreamNode] = {
     ShrineOrchestrator.hubComponents.fold(Seq.empty[DownstreamNode])(_.broadcastDestinations.map(DownstreamNode(_)).to[Seq])
   }
 }
 
 object DownstreamNode {
   def apply(nodeHandle: NodeHandle): DownstreamNode = new DownstreamNode(
     nodeHandle.nodeId.name,
     nodeHandle.client.url.map(_.toString).getOrElse("not applicable"))
 }
 
 case class Adapter(crcEndpointUrl: String,
                    setSizeObfuscation: Boolean,
                    adapterLockoutAttemptsThreshold: Int,
                    adapterMappingsFilename: Option[String],
                    adapterMappingsDate: Option[Long]
                   )
 
 object
 Adapter {
   def apply(): Adapter = {
     val crcEndpointUrl = ShrineOrchestrator.adapterComponents.fold("")(_.i2b2AdminService.crcUrl)
     val setSizeObfuscation = ShrineOrchestrator.adapterComponents.fold(false)(_.i2b2AdminService.obfuscate)
     val adapterLockoutAttemptsThreshold = ShrineOrchestrator.adapterComponents.fold(0)(_.i2b2AdminService.adapterLockoutAttemptsThreshold)
     val adapterMappingsFileName = mappingFileInfo.map(_._1)
     val adapterMappingsFileDate = mappingFileInfo.map(_._2)
 
     Adapter(crcEndpointUrl, setSizeObfuscation, adapterLockoutAttemptsThreshold, adapterMappingsFileName, adapterMappingsFileDate)
   }
 
   def mappingFileInfo: Option[(String, Long, String)] =
     ShrineOrchestrator.adapterComponents.map(ac => (ac.adapterMappings.source, ac.lastModified, ac.adapterMappings.version))
 }
 
 case class Hub(shouldQuerySelf: Boolean, //todo don't use this field any more. Drop it when possible
                create: Boolean,
                downstreamNodes: Seq[DownstreamNode])
 
 object Hub {
   def apply(): Hub = {
     val shouldQuerySelf = false
     val create = ShrineOrchestrator.hubComponents.isDefined
     val downstreamNodes = DownstreamNodes.get()
     Hub(shouldQuerySelf, create, downstreamNodes)
   }
 }
 
 
 case class OptionalParts(isHub: Boolean,
                          stewardEnabled: Boolean,
                          shouldQuerySelf: Boolean, //todo don't use this field any more. Drop it when possible
                          downstreamNodes: Seq[DownstreamNode])
 
 object OptionalParts {
   def apply(): OptionalParts = {
     OptionalParts(
       ShrineOrchestrator.hubComponents.isDefined,
       ShrineOrchestrator.queryEntryPointComponents.fold(false)(_.shrineService.authorizationService.isInstanceOf[StewardQueryAuthorizationService]),
       shouldQuerySelf = false,
       DownstreamNodes.get()
     )
   }
 }
 
 case class Summary(
                     isHub: Boolean,
                     shrineVersion: String,
                     shrineBuildDate: String,
                     ontologyVersion: String,
                     ontologyVersionTerm: String,
                     ontologyTerm: String,
                     queryResult: Option[SingleNodeResult],
                     adapterMappingsFileName: Option[String],
                     adapterMappingsDate: Option[Long],
                     adapterOk: Boolean,
                     keystoreOk: Boolean,
                     hubOk: Boolean,
                     qepOk: Boolean
                   )
 
 object Summary {
 
   val term = Term(ShrineOrchestrator.shrineConfig.getString("networkStatusQuery"))
 
   def runQueryRequest: BroadcastMessage = {
     val domain = "happy"
     val username = "happy"
 
     val networkAuthn = AuthenticationInfo(domain, username, Credential("", isToken = false))
 
     val queryDefinition = QueryDefinition("TestQuery", OccuranceLimited(1, term))
     import scala.concurrent.duration._
     val req = RunQueryRequest(
       "happyProject",
       3.minutes,
       networkAuthn,
       None,
       None,
       Set(ResultOutputType.PATIENT_COUNT_XML),
       queryDefinition)
 
 
     ShrineOrchestrator.signerVerifier.sign(BroadcastMessage(req.networkQueryId, networkAuthn, req), SigningCertStrategy.Attach)
   }
 
   def apply(): Summary = {
 
     val message = runQueryRequest
 
     val queryResult: Option[SingleNodeResult] = ShrineOrchestrator.adapterService.map { adapterService =>
 
       import scala.concurrent.duration._
 
       val start = System.currentTimeMillis
       val resultAttempt: Try[Result] = Try(adapterService.handleRequest(message))
       val end = System.currentTimeMillis
       val elapsed = (end - start).milliseconds
 
       resultAttempt match {
         case scala.util.Success(result)    => result
         case scala.util.Failure(throwable) => FailureResult(NodeId("Local"), throwable)
       }
     }
 
     val adapterOk = queryResult.fold(true) {
       case r: Result        => true
       case f: FailureResult => false
     }
 
     val hubOk = ShrineOrchestrator.hubComponents.fold(true) { hubComponents =>
       val maxQueryWaitTime = hubComponents.broadcasterMultiplexerService.maxQueryWaitTime
       val broadcaster: Broadcaster = hubComponents.broadcasterMultiplexerService.broadcaster
       val message = runQueryRequest
       val triedMultiplexer = Try(broadcaster.broadcast(message))
       //todo just use fold()() in scala 2.12
       triedMultiplexer.toOption.fold(false) { multiplexer =>
         val responses = Await.result(multiplexer.responses, maxQueryWaitTime).toSeq
         val failures = responses.collect { case f: FailureResult => f }
         val timeouts = responses.collect { case t: Timeout => t }
         val validResults = responses.collect { case r: Result => r }
 
         failures.isEmpty && timeouts.isEmpty && (validResults.size == broadcaster.destinations.size)
       }
     }
 
     val adapterMappingInfo = Adapter.mappingFileInfo
 
     val ontologyVersion = try {
       ShrineOrchestrator.ontologyMetadata.ontologyVersion
     }
     catch {
       case NonFatal(x) =>
         Log.info("Problem while getting ontology version", x)
         s"Unavailable due to: ${x.getMessage}"
     }
 
     Summary(
       isHub = ShrineOrchestrator.hubComponents.isDefined,
       shrineVersion = Versions.version,
       shrineBuildDate = Versions.buildDate,
       //todo in scala 2.12, do better
       ontologyVersion = ontologyVersion,
       ontologyVersionTerm = OntClientOntologyMetadata.versionContainerTerm,
       ontologyTerm = term.value,
       queryResult = queryResult,
       adapterMappingsFileName = adapterMappingInfo.map(_._1),
       adapterMappingsDate = adapterMappingInfo.map(_._2),
       adapterOk = adapterOk,
       keystoreOk = true, //todo something for this
       hubOk = hubOk,
       qepOk = true //todo something for this
     )
   }
 }
 
 case class Version(version: String)
 
 //todo SortedMap when possible
 case class Json4sConfig(keyValues: Map[String, String])
 
 object Json4sConfig {
   def isPassword(key: String): Boolean = {
     if (key.toLowerCase.contains("password")) true
     else false
   }
 
   def apply(config: TsConfig): Json4sConfig = {
     val entries: Set[(String, String)] = config.entrySet.asScala.to[Set].map(x => (x.getKey, x.getValue.render())).filterNot(x => isPassword(x._1))
     val sortedMap: Map[String, String] = entries.toMap
     Json4sConfig(sortedMap)
   }
 }
 
 class PermittedHostOnly extends ContainerRequestFilter {
 
   //todo generalize for happy, too
   //todo for tomcat 8 see https://jersey.java.net/documentation/latest/filters-and-interceptors.html for a cleaner version
   //shell code from http://stackoverflow.com/questions/17143514/how-to-add-custom-response-and-abort-request-in-jersey-1-11-filters
 
   //how to apply in http://stackoverflow.com/questions/4358213/how-does-one-intercept-a-request-during-the-jersey-lifecycle
   override def filter(requestContext: ContainerRequest): ContainerRequest = {
     val hostOfOrigin = requestContext.getBaseUri.getHost
     val shrineConfig: TsConfig = ShrineOrchestrator.config
     val permittedHostOfOrigin: String = shrineConfig.getOption("shrine.status.permittedHostOfOrigin", _.getString).getOrElse("localhost")
 
     val path = requestContext.getPath
 
     //happy and internalstatus API calls must come from the same host as tomcat is running on (hopefully the dashboard servlet).
     // todo access to the happy service permitted for SHRINE 1.21 per SHRINE-1366
     // restrict access to happy service when database work resumes as part of SHRINE-
     //       if ((path.contains("happy") || path.contains("internalstatus")) && (hostOfOrigin != permittedHostOfOrigin)) {
     if (path.contains("internalstatus") && (hostOfOrigin != permittedHostOfOrigin)) {
       val response = Response.status(Response.Status.UNAUTHORIZED).entity(s"Only available from $permittedHostOfOrigin, not $hostOfOrigin, controlled by shrine.status.permittedHostOfOrigin in shrine.conf").build()
       throw new WebApplicationException(response)
     }
     else requestContext
   }
 
 }
 
 object ShaVerificationService extends Loggable with DefaultJsonSupport {
   //todo: remove duplication with StewardQueryAuthorizationService
   import akka.pattern.ask
   import org.json4s.native.JsonMethods.parseOpt
   import system.dispatcher
 
   // execution context for futures
   implicit val system = ActorSystem("AuthorizationServiceActors", ConfigFactory.load("shrine")) //todo use shrine's config
 
   val certCollection = ShrineOrchestrator.certCollection
 
   def sendHttpRequest(httpRequest: HttpRequest): Future[HttpResponse] = {
     implicit val timeout: Timeout = Timeout.durationToTimeout(new FiniteDuration(5, duration.SECONDS)) //5 seconds
 
     implicit def json4sFormats: Formats = DefaultFormats
 
     implicit def trustfulSslContext: SSLContext = {
       object BlindFaithX509TrustManager extends X509TrustManager {
         def checkClientTrusted(chain: Array[X509Certificate], authType: String) = info(s"Client asked BlindFaithX509TrustManager to check $chain for $authType")
 
         def checkServerTrusted(chain: Array[X509Certificate], authType: String) = info(s"Server asked BlindFaithX509TrustManager to check $chain for $authType")
 
         def getAcceptedIssuers = Array[X509Certificate]()
       }
 
       val context = SSLContext.getInstance("TLS")
       context.init(Array[KeyManager](), Array(BlindFaithX509TrustManager), null)
       context
     }
 
     implicit def trustfulSslContextProvider: SSLContextProvider = {
       SSLContextProvider.forContext(trustfulSslContext)
     }
 
     class CustomClientSSLEngineProvider extends ClientSSLEngineProvider {
       def apply(pc: PipelineContext) = ClientSSLEngineProvider.default(trustfulSslContextProvider).apply(pc)
     }
 
     implicit def sslEngineProvider: ClientSSLEngineProvider = new CustomClientSSLEngineProvider
 
     val responseFuture: Future[HttpResponse] = for {
       HostConnectorInfo(hostConnector, _) <- {
         val hostConnectorSetup = new HostConnectorSetup(httpRequest.uri.authority.host.address,
           httpRequest.uri.authority.port,
           sslEncryption = httpRequest.uri.scheme == "https")(
           sslEngineProvider = sslEngineProvider)
 
         IO(Http) ask hostConnectorSetup
       }
       response <- sendReceive(hostConnector).apply(httpRequest)
       _ <- hostConnector ask Http.CloseAll
     } yield response
 
     responseFuture
   }
 
   type MaybeSiteStatus = Future[Option[SiteStatus]]
 
   def apply(sites: Seq[RemoteSite]): Seq[MaybeSiteStatus] = sites.map(curl)
 
 
   def curl(site: RemoteSite): MaybeSiteStatus = {
     val shaEntry = certCollection match {
       case HubCertCollection(_, caEntry, _) => caEntry
       case PeerCertCollection(my, _, _) => my
       case DownStreamCertCollection(_, caEntry, _) => caEntry
     }
     val sha256 = UtilHasher.encodeCert(shaEntry.cert, "SHA-256")
     implicit val formats = org.json4s.DefaultFormats
     val request = Post(s"https://${site.url}:${site.port}/shrine-dashboard/status/verifySignature")
       .withEntity( // For some reason, FormData isn't producing the correct HTTP call, so we do it manually
         HttpEntity.apply(
           ContentType(
             MediaTypes.`application/x-www-form-urlencoded`,
             HttpCharsets.`UTF-8`),
           s"sha256=$sha256"))
 
     for {response <- sendHttpRequest(request)
          rawResponse = new String(response.entity.data.toByteArray)
          status = parseOpt(rawResponse).fold(handleError(rawResponse))(_.extractOpt[ShaResponse] match {
            case Some(ShaResponse(ShaResponse.badFormat, false)) =>
              error(s"Somehow, this client is sending an incorrectly formatted SHA256 signature to the dashboard. Offending sig: $sha256")
              None
            case Some(ShaResponse(sha, true))                 => Some(SiteStatus(site.alias, theyHaveMine = true, haveTheirs = doWeHaveCert(sha), site.url))
            case Some(ShaResponse(sha, false))                => Some(SiteStatus(site.alias, theyHaveMine = false, haveTheirs = doWeHaveCert(sha), site.url))
            case None                                            =>
              InvalidVerifySignatureResponse(rawResponse)
              None
          })} yield status
   }
 
   def doWeHaveCert(sha256: String): Boolean = UtilHasher(certCollection).handleSig(sha256).found
 
   def handleError(response: String): Option[SiteStatus] = {
     InvalidVerifySignatureResponse(response)
     None
   }
 
   case class InvalidVerifySignatureResponse(response: String) extends AbstractProblem(ProblemSources.ShrineApp) {
     override def summary: String = "The client for handling certificate diagnostic across Dashboards in the Status Service received an invalid response from verifySignature"
 
     override def description: String = s"verifySig produced the invalid response `$response`"
   }
 
 }
diff --git a/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala b/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala
index fa075ef57..fc996ac96 100644
--- a/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala
+++ b/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineOrchestrator.scala
@@ -1,157 +1,159 @@
 package net.shrine.wiring
 
 import javax.sql.DataSource
 
 import com.typesafe.config.Config
 import net.shrine.adapter.AdapterComponents
 import net.shrine.adapter.dao.AdapterDao
 import net.shrine.adapter.service.{AdapterRequestHandler, AdapterResource, AdapterService, I2b2AdminResource, I2b2AdminService}
 import net.shrine.broadcaster.dao.HubDao
 import net.shrine.broadcaster.dao.squeryl.SquerylHubDao
 import net.shrine.broadcaster.service.HubComponents
 import net.shrine.client.{EndpointConfig, JerseyHttpClient, OntClient, Poster, PosterOntClient}
 import net.shrine.config.ConfigExtensions
 import net.shrine.config.mappings.AdapterMappings
 import net.shrine.crypto.{BouncyKeyStoreCollection, KeyStoreDescriptorParser, SignerVerifierAdapter, TrustParam}
 import net.shrine.dao.squeryl.{DataSourceSquerylInitializer, SquerylDbAdapterSelecter, SquerylInitializer}
 import net.shrine.happy.{HappyShrineResource, HappyShrineService}
 import net.shrine.log.Loggable
 import net.shrine.ont.data.OntClientOntologyMetadata
 import net.shrine.protocol.{HiveCredentials, NodeId, ResultOutputType, ResultOutputTypes}
 import net.shrine.qep.{I2b2BroadcastResource, QueryEntryPointComponents, ShrineResource}
 import net.shrine.slick.TestableDataSourceCreator
 import net.shrine.source.ConfigSource
 import net.shrine.status.StatusJaxrs
 import org.squeryl.internals.DatabaseAdapter
 
 /**
  * @author clint
  * @since Jan 14, 2014
  *
  * Application wiring for Shrine.
  */
 object ShrineOrchestrator extends ShrineJaxrsResources with Loggable {
 
   override def resources: Iterable[AnyRef] = {
     Seq(happyResource,statusJaxrs) ++
       shrineResource ++
       i2b2BroadcastResource ++
       adapterResource ++
       i2b2AdminResource ++
       hubComponents.map(_.broadcasterMultiplexerResource)
   }
 
   //todo another pass to put things only used in one place into that place's apply(Config)
 
   //Load config from file on the classpath called "shrine.conf"
   lazy val config: Config = ConfigSource.config
 
   val shrineConfig = config.getConfig("shrine")
 
   protected lazy val nodeId: NodeId = NodeId(shrineConfig.getString("humanReadableNodeName"))
 
   //TODO: Don't assume keystore lives on the filesystem, could come from classpath, etc
   lazy val keyStoreDescriptor = KeyStoreDescriptorParser(shrineConfig.getConfig("keystore"), shrineConfig.getConfigOrEmpty("hub"), shrineConfig.getConfigOrEmpty("queryEntryPoint"))
   lazy val certCollection: BouncyKeyStoreCollection = BouncyKeyStoreCollection.fromFileRecoverWithClassPath(keyStoreDescriptor)
   protected lazy val keystoreTrustParam: TrustParam = TrustParam.BouncyKeyStore(certCollection)
   //todo used by the adapterServide and happyShrineService, but not by the QEP. maybe each can have its own signerVerivier
   lazy val signerVerifier = SignerVerifierAdapter(certCollection)
   protected lazy val dataSource: DataSource = TestableDataSourceCreator.dataSource(shrineConfig.getConfig("squerylDataSource.database"))
   protected lazy val squerylAdapter: DatabaseAdapter = SquerylDbAdapterSelecter.determineAdapter(shrineConfig.getString("shrineDatabaseType"))
   protected lazy val squerylInitializer: SquerylInitializer = new DataSourceSquerylInitializer(dataSource, squerylAdapter)
 
   //todo it'd be better for the adapter and qep to each have its own connection to the pm cell.
   private lazy val pmEndpoint: EndpointConfig = shrineConfig.getConfigured("pmEndpoint", EndpointConfig(_))
   lazy val pmPoster: Poster = Poster(certCollection,pmEndpoint)
 
   protected lazy val breakdownTypes: Set[ResultOutputType] = shrineConfig.getOptionConfigured("breakdownResultOutputTypes", ResultOutputTypes.fromConfig).getOrElse(Set.empty)
 
   //todo why does the qep need a HubDao ?
   protected lazy val hubDao: HubDao = new SquerylHubDao(squerylInitializer, new net.shrine.broadcaster.dao.squeryl.tables.Tables)
 
 //todo really should be part of the adapter config, but is out in shrine's part of the name space
   lazy val crcHiveCredentials: HiveCredentials = shrineConfig.getConfigured("hiveCredentials", HiveCredentials(_, HiveCredentials.CRC))
 
   val adapterComponents:Option[AdapterComponents] = shrineConfig.getOptionConfiguredIf("adapter", AdapterComponents(
     _,
     certCollection,
     squerylInitializer,
     breakdownTypes,
     crcHiveCredentials,
     signerVerifier,
     pmPoster,
     nodeId
   ))
+  if (adapterComponents.isEmpty)
+    warn("Adapter Components is improperly configured, please check the adapter section in shrine.conf")
 
   //todo maybe just break demeter too use this
   lazy val adapterService: Option[AdapterService] = adapterComponents.map(_.adapterService)
   //todo maybe just break demeter too use this
   lazy val i2b2AdminService: Option[I2b2AdminService] = adapterComponents.map(_.i2b2AdminService)
   //todo this is only used by happy
   lazy val adapterDao: Option[AdapterDao] = adapterComponents.map(_.adapterDao)
   //todo this is only used by happy
   lazy val adapterMappings: Option[AdapterMappings] = adapterComponents.map(_.adapterMappings)
 
   val shouldQuerySelf = "hub.shouldQuerySelf"
   lazy val localAdapterServiceOption: Option[AdapterRequestHandler] = if(shrineConfig.getOption(shouldQuerySelf,_.getBoolean).getOrElse(false)) { //todo give this a default value (of false) in the reference.conf for the Hub, and make it part of the Hub's apply(config)
     require(adapterService.isDefined, s"Self-querying requested because shrine.$shouldQuerySelf is true, but this node is not configured to have an adapter")
     adapterService
   }
     else None //todo eventually make this just another downstream node accessed via loopback
 
   lazy val hubComponents: Option[HubComponents] = shrineConfig.getOptionConfiguredIf("hub",HubComponents(_,
     keystoreTrustParam,
     nodeId,
     localAdapterServiceOption,
     breakdownTypes,
     hubDao
   ))
 
   //todo anything that requires qepConfig should be inside QueryEntryPointComponents's apply
   protected lazy val qepConfig = shrineConfig.getConfig("queryEntryPoint")
 
   lazy val queryEntryPointComponents:Option[QueryEntryPointComponents] = shrineConfig.getOptionConfiguredIf("queryEntryPoint", QueryEntryPointComponents(_,
     certCollection,
     breakdownTypes,
     hubComponents.map(_.broadcastDestinations),
     hubDao, //todo the QEP should not need the hub dao
     squerylInitializer, //todo could really have its own
     pmPoster //todo could really have its own
   ))
 
   protected lazy val pmUrlString: String = pmEndpoint.url.toString
 
-  private lazy val ontEndpoint: EndpointConfig = shrineConfig.getConfigured("ontEndpoint", EndpointConfig(_))
+  private[shrine] lazy val ontEndpoint: EndpointConfig = shrineConfig.getConfigured("ontEndpoint", EndpointConfig(_))
   protected lazy val ontPoster: Poster = Poster(certCollection,ontEndpoint)
 
   //todo only used by happy outside of here
   lazy val ontologyMetadata: OntClientOntologyMetadata = {
     import scala.concurrent.duration._
 
     //TODO: XXX: Un-hard-code max wait time param
     val ontClient: OntClient = new PosterOntClient(shrineConfig.getConfigured("hiveCredentials", HiveCredentials(_, HiveCredentials.ONT)), 1.minute, ontPoster)
 
     new OntClientOntologyMetadata(ontClient)
   }
 
   protected lazy val happyResource: HappyShrineResource = new HappyShrineResource(HappyShrineService)
 
   protected lazy val statusJaxrs: StatusJaxrs = StatusJaxrs(config)
 
   protected lazy val shrineResource: Option[ShrineResource] = queryEntryPointComponents.map(x => ShrineResource(x.shrineService))
 
   protected lazy val i2b2BroadcastResource: Option[I2b2BroadcastResource] = queryEntryPointComponents.map(x => new I2b2BroadcastResource(x.i2b2Service,breakdownTypes))
 
   protected lazy val adapterResource: Option[AdapterResource] = adapterService.map(AdapterResource(_))
 
   protected lazy val i2b2AdminResource: Option[I2b2AdminResource] = i2b2AdminService.map(I2b2AdminResource(_, breakdownTypes))
   
 
 
   def poster(keystoreCertCollection: BouncyKeyStoreCollection)(endpoint: EndpointConfig): Poster = {
     val httpClient = JerseyHttpClient(keystoreCertCollection, endpoint)
 
     Poster(endpoint.url.toString, httpClient)
   }
 }
 
diff --git a/apps/shrine-app/src/test/resources/shrine.conf b/apps/shrine-app/src/test/resources/shrine.conf
index f43b7d88d..75e8f6248 100644
--- a/apps/shrine-app/src/test/resources/shrine.conf
+++ b/apps/shrine-app/src/test/resources/shrine.conf
@@ -1,94 +1,94 @@
 shrine {
   problem {
     problemHandler = "net.shrine.problem.NoOpProblemHandler$"
   }
 
   ontEndpoint {
-    url = "http://example.com:9090/i2b2/rest/OntologyService/"
+    url = "http://services.i2b2.org/i2b2/rest/OntologyService"
     acceptAllCerts = true
     timeout {
       seconds = 1
     }
   }
 
   keystore {
     file = "shrine.keystore"
     password = "justatestpassword"
     keyStoreType = "JKS"
     trustModelIsHub = true
     caCertAliases = [shrine-test-ca]
     isHub = false
   }
 
   queryEntryPoint {
     broadcasterServiceEndpoint {
       url = "https://shrine-dev1.catalyst:6443/shrine/testing"
     }
     audit {
       collectQepAudit = false
 
       database {
         slickProfileClassName = "slick.driver.H2Driver$"
         createTablesOnStart = true //for testing with H2 in memory, when not running unit tests. Set to false normally
 
         dataSourceFrom = "testDataSource" //Can be JNDI or testDataSource . Use testDataSource for tests, JNDI everywhere else
 
         testDataSource {
           driverClassName = "org.h2.Driver"
           url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" //H2 embedded in-memory for unit tests ;TRACE_LEVEL_SYSTEM_OUT=2 for H2's trace
         }
       }
     }
     trustModelIsHub = true
 
     authenticationType = "pm" //can be none, pm, or ecommons
 
     authorizationType = "shrine-steward" //can be none, shrine-steward, or hms-steward
     shrineSteward {
       qepUserName = "qep"
       qepPassword = "trustme"
       stewardBaseUrl = "https://localhost:6443"
     }
 
   }
 
   adapter {
     create = true
     audit {
       collectQepAudit = false
 
       database {
         slickProfileClassName = "slick.driver.H2Driver$"
         createTablesOnStart = true //for testing with H2 in memory, when not running unit tests. Set to false normally
 
         dataSourceFrom = "testDataSource" //Can be JNDI or testDataSource . Use testDataSource for tests, JNDI everywhere else
 
         testDataSource {
           driverClassName = "org.h2.Driver"
           url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" //H2 embedded in-memory for unit tests ;TRACE_LEVEL_SYSTEM_OUT=2 for H2's trace
         }
       }
     }
   }
 
   squerylDataSource {
     database {
       dataSourceFrom = "testDataSource" //Can be JNDI or testDataSource . Use testDataSource for tests, JNDI everywhere else
       testDataSource {
         driverClassName = "org.h2.Driver"
         url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" //H2 embedded in-memory for unit tests ;TRACE_LEVEL_SYSTEM_OUT=2 for H2's trace
       }
     }
   }
 //  squerylDataSource {
 //    database {
 //      dataSourceFrom = "testDataSource" //Can be JNDI or testDataSource . Use testDataSource for tests, JNDI everywhere else
 //
 //      testDataSource {
 //        driverClassName = "org.h2.Driver"
 //        url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" //H2 embedded in-memory for unit tests ;TRACE_LEVEL_SYSTEM_OUT=2 for H2's trace
 //      }
 //    }
 //  }
 
 }
\ No newline at end of file
diff --git a/apps/shrine-app/src/test/scala/net/shrine/status/StatusJaxrsTest.scala b/apps/shrine-app/src/test/scala/net/shrine/status/StatusJaxrsTest.scala
index f8fc71865..0fe325ac6 100644
--- a/apps/shrine-app/src/test/scala/net/shrine/status/StatusJaxrsTest.scala
+++ b/apps/shrine-app/src/test/scala/net/shrine/status/StatusJaxrsTest.scala
@@ -1,129 +1,129 @@
 package net.shrine.status
 
 import com.typesafe.config.ConfigFactory
 import net.shrine.util.{ShouldMatchersForJUnit, SingleHubModel}
 import org.json4s.native.Serialization
 import org.json4s.{DefaultFormats, Formats}
 import org.junit.Test
 
 import scala.collection.immutable.Map
 
 /**
   * Tests for StatusJaxrs
   *
   * @author david 
   * @since 12/2/15
   */
 class StatusJaxrsTest extends ShouldMatchersForJUnit {
 
   implicit def json4sFormats: Formats = DefaultFormats
   val expectedConfig = ConfigFactory.load("shrine") //new File("/Users/ty/shrine/apps/shrine-app/src/test/resources/shrine.conf"))
   val statusJaxrs = StatusJaxrs(expectedConfig)
 
   @Test
   def testVersion() = {
     val versionString = statusJaxrs.version
     val version = Serialization.read[Version](versionString)
 
     version should equal(Version("changeMe"))
   }
 
   @Test
   def testConfig() = {
     val expectedJson4sConfig = Json4sConfig(expectedConfig)
 
     val configString = statusJaxrs.config
     val config = Serialization.read[Json4sConfig](configString)
 
     config should equal(expectedJson4sConfig)
 
     val passwordKeys = config.keyValues.filter(x => Json4sConfig.isPassword(x._1))
 
     passwordKeys should equal(Map.empty[String,String])
   }
 
   @Test
   def testSummary() = {
 
     val summaryString = statusJaxrs.summary
     val summary = Serialization.read[Summary](summaryString)
 
     summary.isHub should be (true)
     summary.adapterMappingsFileName.isDefined should be (true)
     summary.adapterMappingsDate.isEmpty should be (false)
     summary.adapterOk should be (true)
     summary.keystoreOk should be (true)
     summary.hubOk should be (false)
     summary.qepOk should be (true)
 
   }
 
   @Test
   def testI2b2() = {
 
     val i2b2String = statusJaxrs.i2b2
-
+    println(i2b2String)
     val i2b2 = Serialization.read[I2b2](i2b2String)
 
     i2b2.crcUrl.isDefined should be (true)
   }
 
   @Test
   def testOptionalParts() = {
 
     val string = statusJaxrs.optionalParts
 
     val actual = Serialization.read[OptionalParts](string)
 
     actual.isHub should be (true)
     actual.stewardEnabled should be (true)
     actual.shouldQuerySelf should be (false)
     actual.downstreamNodes.size should be (4)
   }
 
   @Test
   def testHub() = {
 
     val string = statusJaxrs.hub
 
     val actual = Serialization.read[Hub](string)
 
     actual.create should be (true)
     actual.shouldQuerySelf should be (false)
     actual.downstreamNodes.size should be (4)
   }
 
   @Test
   def testQep() = {
 
     val string = statusJaxrs.qep
     val actual = Serialization.read[Qep](string)
 
     actual.create should be (true)
     actual.attachSigningCert should be (true)
     actual.authenticationType should be ("PmAuthenticator")
     actual.authorizationType should be ("StewardQueryAuthorizationService")
     actual.includeAggregateResults should be (false)
     actual.maxQueryWaitTimeMillis should be (300000000L)
     actual.trustModel should be (SingleHubModel(true).description)
     actual.trustModelIsHub should be (true)
   }
 
   @Test
   def testAdapter() = {
 
     val string = statusJaxrs.adapter
 
     val actual = Serialization.read[Adapter](string)
 
     actual.adapterLockoutAttemptsThreshold should be (10)
   }
 
   @Test
   def testKeyStore() = {
     val string = statusJaxrs.keystore
     println(string)
     val actual = Serialization.read[KeyStoreReport](string)
   }
 
 }
diff --git a/apps/steward-app/src/main/js/app/assets/css/shrine.css b/apps/steward-app/src/main/js/app/assets/css/shrine.css
index c0eacebb6..072517588 100644
--- a/apps/steward-app/src/main/js/app/assets/css/shrine.css
+++ b/apps/steward-app/src/main/js/app/assets/css/shrine.css
@@ -1,1145 +1,1147 @@
 @charset "UTF-8";
 /* import timeline.scss */
 @font-face {
   font-family: 'Nexa';
   src: url("../fnt/nexa/nexa-light-webfont.eot");
   src: url("../fnt/nexa/nexa-light-webfont.eot?#iefix") format("embedded-opentype"), url("../fnt/nexa/nexa-light-webfont.woff2") format("woff2"), url("../fnt/nexa/nexa-light-webfont.woff") format("woff"), url("../fnt/nexa/nexa-light-webfont.ttf") format("truetype"), url("../fnt/nexa/nexa-light-webfont.svg#nexa_lightregular") format("svg");
   font-weight: normal;
   font-style: normal; }
 
 @font-face {
   font-family: 'Nexa Bold';
   src: url("../fnt/nexa/nexa-bold-webfont.eot");
   src: url("../fnt/nexa/nexa-bold-webfont.eot?#iefix") format("embedded-opentype"), url("../fnt/nexa/nexa-bold-webfont.woff2") format("woff2"), url("../fnt/nexa/nexa-bold-webfont.woff") format("woff"), url("../fnt/nexa/nexa-bold-webfont.ttf") format("truetype"), url("../fnt/nexa/nexa-bold-webfont.svg#nexa_boldregular") format("svg");
   font-weight: normal;
   font-style: normal; }
 
 @font-face {
   font-family: 'Open Sans Regular';
   src: url("../fnt/open-sans/opensans-regular-webfont.eot");
   src: url("../fnt/open-sans/opensans-regular-webfont.eot?#iefix") format("embedded-opentype"), url("../fnt/open-sans/opensans-regular-webfont.woff2") format("woff2"), url("../fnt/open-sans/opensans-regular-webfont.woff") format("woff"), url("../fnt/open-sans/opensans-regular-webfont.ttf") format("truetype"), url("../fnt/open-sans/opensans-regular-webfont.svg#open_sansregular") format("svg");
   font-weight: normal;
   font-style: normal; }
 
 @font-face {
   font-family: 'Open Sans Semibold';
   src: url("../fnt/open-sans/opensans-semibold-webfont.eot");
   src: url("../fnt/open-sans/opensans-semibold-webfont.eot?#iefix") format("embedded-opentype"), url("../fnt/open-sans/opensans-semibold-webfont.woff2") format("woff2"), url("../fnt/open-sans/opensans-semibold-webfont.woff") format("woff"), url("../fnt/open-sans/opensans-semibold-webfont.ttf") format("truetype"), url("../fnt/open-sans/opensans-semibold-webfont.svg#open_sanssemibold") format("svg");
   font-weight: normal;
   font-style: normal; }
 
 @font-face {
   font-family: 'Roboto Bold';
   src: url("../fnt/roboto/roboto-bold-webfont.eot");
   src: url("../fnt/roboto/roboto-bold-webfont.eot?#iefix") format("embedded-opentype"), url("../fnt/roboto/roboto-bold-webfont.woff2") format("woff2"), url("../fnt/roboto/roboto-bold-webfont.woff") format("woff"), url("../fnt/roboto/roboto-bold-webfont.ttf") format("truetype"), url("../fnt/roboto/roboto-bold-webfont.svg#robotobold") format("svg");
   font-weight: normal;
   font-style: normal; }
 
 @font-face {
   font-family: 'Roboto Regular';
   src: url("../fnt/roboto/roboto-regular-webfont.eot");
   src: url("../fnt/roboto/roboto-regular-webfont.eot?#iefix") format("embedded-opentype"), url("../fnt/roboto/roboto-regular-webfont.woff2") format("woff2"), url("../fnt/roboto/roboto-regular-webfont.woff") format("woff"), url("../fnt/roboto/roboto-regular-webfont.ttf") format("truetype"), url("../fnt/roboto/roboto-regular-webfont.svg#robotoregular") format("svg");
   font-weight: normal;
   font-style: normal; }
 
 .main-app {
   background-image: url("../img/Background_Shrine.jpg");
   background-size: cover;
   background-color: transparent;
   background-attachment: fixed;
   width: 100%;
   height: 100%;
   width: calc(100vw);
   height: calc(100vh);
   min-width: 100%;
   min-height: 100%; }
 
 .shrine-navbar {
   background-color: rgba(255, 255, 255, 0.62);
   border-color: transparent;
   font-family: "Nexa";
   color: #5d5d5d;
   min-height: 60px;
   width: 100%;
   height: 4.8em; }
 
 .shrine-brand {
   float: left;
   padding: 22px 15px;
   font-size: 30px;
   line-height: 30px;
   height: 30px; }
 
 .shrine-brand strong {
   font-family: "Nexa Bold";
   color: #2c5566; }
 
 .shrine-navbar .shrine-institution-logo {
   background-image: url("/static/logo.png");
   background-size: contain;
   background-color: transparent;
   background-color: rgba(255, 255, 255, 0.1);
   background-repeat: no-repeat;
   background-position: right top;
   margin-right: 2%;
   margin-top: 5px;
   width: 4em;
   height: 4em;
   max-height: 4em;
   max-width: 4em; }
 
 .shrine-button {
   cursor: pointer !important;
   background-color: transparent;
   border: none; }
 
 .shrine-button span {
   position: relative;
   bottom: 5px; }
 
 .shrine-btn-default {
   margin-right: 6px;
   border: none; }
 
 .shrine-btn-on {
   padding: 12px 12px;
   border-radius: 0;
   font-family: "Roboto Bold" !important;
   color: #FFFFFF !important;
   background: linear-gradient(rgba(4, 141, 190, 0.8), rgba(2, 89, 120, 0.8)), url("../img/bckg_diagonal_lines_no_border.png") !important; }
 
 .shrine-btn-off {
   padding: 6px 12px !important;
   border-radius: 4px !important;
   font-family: "Open Sans Semibold" !important;
   background-color: #8896A4 !important;
   color: #FFFFFF !important; }
 
 .shrine-on, .shrine-on a {
   font-family: "Roboto Bold" !important;
   color: #FFFFFF !important;
   background: linear-gradient(#048DBE, #025978) !important; }
 
 .shrine-off {
   font-family: "Roboto Regular" !important;
   color: #2C5566 !important;
   background-color: #ECEEEE !important; }
 
 .shrine-button.disabled, .shrine-button[disabled] {
   cursor: default !important;
   opacity: 0.2 !important; }
 
 .shrine-copy-bold {
   font-family: "Nexa Bold";
   color: #64818e; }
 
 .shrine-copy {
   font-family: "Nexa";
   color: #000000; }
 
 .row {
   margin-right: 0;
   margin-left: 0; }
 
 td.error, span.error {
   color: red; }
 
 td.error a, td.error a:hover, span.error a, span.error a:hover {
   color: inherit;
   text-decoration: underline !important;
   cursor: pointer; }
 
 td.ok, span.ok {
   color: green; }
 
 .form-group span {
   font-family: "Open Sans Semibold";
   color: #2c5566; }
 
 fieldset button {
   color: #2E5366; }
 
 fieldset button:hover, form a:hover {
   color: #008CBA;
   text-decoration: none;
   cursor: pointer; }
 
 form a {
   font-family: "Open Sans Regular";
   color: #647d8d;
   text-decoration: none; }
 
 footer img {
   margin-left: 10px;
   margin-top: 2px; }
 
 footer {
   background-color: rgba(50, 62, 74, 0.48);
   position: fixed;
   bottom: 0;
   left: 0;
   width: 100%;
   height: 83px;
   min-height: 83px;
   max-width: 100%; }
 
 table {
   background-image: url("../img/bckg_diagonal_lines_no_border.png");
   border: 1px solid #CCD8DF; }
 
 .table tr > td:first-child {
   width: 20%;
   min-width: 140px; }
 
 .table tr > td.thin-col {
   width: 6%;
   min-width: 35px; }
 
 .table-striped > tbody > tr:nth-of-type(odd) {
   background-color: #EFF6F9; }
 
 .table-striped > tbody > tr:nth-of-type(even) {
   background-color: #FFFFFF; }
 
 thead tr {
   border: 1px solid #CCD8DF; }
 
 td {
   border-right: 1px solid #CCD8DF;
   overflow: hidden;
   max-width: 450px;
   word-break: break-all; }
 
 thead tr td, thead tr td label, tfoot tr td span {
   font-family: "Open Sans Semibold";
   color: #003153; }
 
 td a, td a:hover {
   text-decoration: none !important;
   cursor: pointer;
   font-family: "Open Sans Semibold";
   color: #003153; }
 
 .shrine-panel {
   background-image: url("../img/bckg_diagonal_lines.png");
   background-size: 100% 100%;
   padding-right: 20px;
   padding-left: 20px;
   padding-top: 30px;
   padding-bottom: 30px; }
 
 /*!
  * Start Bootstrap - SB Admin 2 Bootstrap Admin Theme (http://startbootstrap.com)
  * Code licensed under the Apache License v2.0.
  * For details, see http://www.apache.org/licenses/LICENSE-2.0.
  */
 body {
   background-image: url("../img/Background_Shrine.jpg");
   background-repeat: no-repeat;
   background-size: 100% 100%; }
 
 #wrapper {
   width: 100%;
   padding-bottom: 83px;
   /* clearance space for footer at bottom of long page */ }
 
 .login-wrapper {
   margin-top: 51px;
   margin-right: 20px; }
 
 #page-wrapper {
   background-color: transparent;
   margin-top: 54px;
   margin-right: 20px; }
 
 .navbar-top-links li {
   display: inline-block; }
 
 .navbar-top-links li:last-child {
   margin-right: 15px; }
 
 .navbar-top-links li a {
   padding: 15px;
   min-height: 50px;
   font-family: "Open Sans Semibold";
   color: #2c5566; }
 
 .navbar-top-links .dropdown-menu li {
   font-family: "Open Sans Semibold";
   color: #2c5566;
   display: block; }
 
 .navbar-top-links .dropdown-menu li:last-child {
   margin-right: 0; }
 
 .navbar-top-links .dropdown-menu li a {
   padding: 3px 20px;
   min-height: 0; }
 
 .navbar-top-links .dropdown-menu li a div {
   white-space: normal; }
 
 .navbar-top-links .dropdown-messages,
 .navbar-top-links .dropdown-tasks,
 .navbar-top-links .dropdown-alerts {
   width: 310px;
   min-width: 0; }
 
 .navbar-top-links .dropdown-messages {
   margin-left: 5px; }
 
 .navbar-top-links .dropdown-tasks {
   margin-left: -59px; }
 
 .navbar-top-links .dropdown-alerts {
   margin-left: -123px; }
 
 .navbar-top-links {
   right: 0;
   left: auto; }
 
 .sidebar .sidebar-nav.navbar-collapse {
   padding-right: 0;
   padding-left: 0; }
 
 .sidebar .sidebar-search {
   padding: 15px; }
 
 .sidebar ul li {
   border-bottom: 1px solid #e7e7e7; }
 
 .sidebar ul li a.active {
   background-color: #eee; }
 
 .sidebar .arrow {
   float: right; }
 
 .sidebar .fa.arrow:before {
   content: "\f104"; }
 
 .sidebar .active > a > .fa.arrow:before {
   content: "\f107"; }
 
 .sidebar .nav-second-level li,
 .sidebar .nav-third-level li {
   border-bottom: 0 !important; }
 
 .sidebar .nav-second-level li a {
   padding-left: 37px; }
 
 .sidebar .nav-third-level li a {
   padding-left: 52px; }
 
 @media (min-width: 768px) {
   .sidebar {
     z-index: 1;
     margin-top: 51px; }
   .navbar-top-links .dropdown-messages,
   .navbar-top-links .dropdown-tasks,
   .navbar-top-links .dropdown-alerts {
     margin-left: auto; } }
 
 .btn-outline {
   color: inherit;
   background-color: transparent;
   transition: all .5s; }
 
 .btn-primary.btn-outline {
   color: #428bca; }
 
 .btn-success.btn-outline {
   color: #5cb85c; }
 
 .btn-info.btn-outline {
   color: #5bc0de; }
 
 .btn-warning.btn-outline {
   color: #f0ad4e; }
 
 .btn-danger.btn-outline {
   color: #d9534f; }
 
 .btn-primary.btn-outline:hover,
 .btn-success.btn-outline:hover,
 .btn-info.btn-outline:hover,
 .btn-warning.btn-outline:hover,
 .btn-danger.btn-outline:hover {
   color: #fff; }
 
 .chat {
   margin: 0;
   padding: 0;
   list-style: none; }
 
 .chat li {
   margin-bottom: 10px;
   padding-bottom: 5px;
   border-bottom: 1px dotted #999; }
 
 .chat li.left .chat-body {
   margin-left: 60px; }
 
 .chat li.right .chat-body {
   margin-right: 60px; }
 
 .chat li .chat-body p {
   margin: 0; }
 
 .panel .slidedown .glyphicon,
 .chat .glyphicon {
   margin-right: 5px; }
 
 .chat-panel .panel-body {
   height: 350px;
   overflow-y: scroll; }
 
 .login-panel {
   margin-top: 25%; }
 
 .flot-chart {
   display: block;
   height: 400px; }
 
 .flot-chart-content {
   width: 100%;
   height: 100%; }
 
 .dataTables_wrapper {
   position: relative;
   clear: both; }
 
 table.dataTable thead .sorting,
 table.dataTable thead .sorting_asc,
 table.dataTable thead .sorting_desc,
 table.dataTable thead .sorting_asc_disabled,
 table.dataTable thead .sorting_desc_disabled {
   background: 0 0; }
 
 table.dataTable thead .sorting_asc:after {
   content: "\f0de";
   float: right;
   font-family: fontawesome; }
 
 table.dataTable thead .sorting_desc:after {
   content: "\f0dd";
   float: right;
   font-family: fontawesome; }
 
 table.dataTable thead .sorting:after {
   content: "\f0dc";
   float: right;
   font-family: fontawesome;
   color: rgba(50, 50, 50, 0.5); }
 
 .btn-circle {
   width: 30px;
   height: 30px;
   padding: 6px 0;
   border-radius: 15px;
   text-align: center;
   font-size: 12px;
   line-height: 1.428571429; }
 
 .btn-circle.btn-lg {
   width: 50px;
   height: 50px;
   padding: 10px 16px;
   border-radius: 25px;
   font-size: 18px;
   line-height: 1.33; }
 
 .btn-circle.btn-xl {
   width: 70px;
   height: 70px;
   padding: 10px 16px;
   border-radius: 35px;
   font-size: 24px;
   line-height: 1.33; }
 
 .show-grid [class^=col-] {
   padding-top: 10px;
   padding-bottom: 10px;
   border: 1px solid #ddd;
   background-color: #eee !important; }
 
 .show-grid {
   margin: 15px 0; }
 
 .huge {
   font-size: 40px; }
 
 .panel-green {
   border-color: #5cb85c; }
 
 .panel-green .panel-heading {
   border-color: #5cb85c;
   color: #fff;
   background-color: #5cb85c; }
 
 .panel-green a {
   color: #5cb85c; }
 
 .panel-green a:hover {
   color: #3d8b3d; }
 
 .panel-red {
   border-color: #d9534f; }
 
 .panel-red .panel-heading {
   border-color: #d9534f;
   color: #fff;
   background-color: #d9534f; }
 
 .panel-red a {
   color: #d9534f; }
 
 .panel-red a:hover {
   color: #b52b27; }
 
 .panel-yellow {
   border-color: #f0ad4e; }
 
 .panel-yellow .panel-heading {
   border-color: #f0ad4e;
   color: #fff;
   background-color: #f0ad4e; }
 
 .panel-yellow a {
   color: #f0ad4e; }
 
 .panel-yellow a:hover {
   color: #df8a13; }
 
 .modal-content {
   border: none; }
 
 .shrine-modal {
   background-color: white;
   border: 1px solid #2c5566;
   font-family: "Open Sans Semibold";
   color: #2e5366;
   padding: 15px; }
 
 .shrine-modal form div.col-sm-12 {
   border: 1px solid rgba(2, 89, 120, 0.8); }
 
 .shrine-modal input, .shrine-modal textarea {
   border-radius: 0px;
   border: 1px solid #2c5566; }
 
 .shrine-modal span {
   font-family: "Nexa Bold";
   color: #2e5366; }
 
 .shrine-modal span:hover {
   font-family: "Nexa Bold";
   color: #008CBA; }
 
 .shrine-modal button {
   background-color: white;
   border: none;
   font-family: "Nexa Bold";
   color: #2e5366; }
 
 .shrine-modal button span {
   position: relative;
   bottom: 6px; }
 
 .shrine-modal button:hover, .btn-success {
   font-family: "Nexa Bold";
   color: #008CBA;
   background-color: transparent;
   border: none; }
 
 .shrine-login {
   margin-top: 8%;
   margin-left: 1%; }
 
 .shrine-content {
   overflow: auto; }
 
 /*Fix for resizeable text area.*/
 textarea {
   resize: none; }
 
 @media (min-width: 768px) {
   .shrine-content {
     padding: 0; } }
 
 .shrine-calendar-input {
   margin-right: 1px;
   max-width: 50%; }
 
 i.shrine-close {
   float: right;
   margin-top: -40px;
   margin-right: -40px;
   cursor: pointer;
   color: #fff;
   border: 2px solid #C8CED1;
   border-radius: 30px;
   background: #8896a4;
   font-size: 31px;
   font-weight: normal;
   display: inline-block;
   line-height: 0px;
   padding: 11px 3px;
   font-style: normal; }
 
 i.shrine-close:hover {
   background: #008cba; }
 
 .shrine-close:before {
   content: "×"; }
 
 .timeline {
   position: relative;
   padding: 20px 0 20px;
   list-style: none; }
 
 .timeline:before {
   content: " ";
   position: absolute;
   top: 0;
   bottom: 0;
   left: 50%;
   width: 3px;
   margin-left: -1.5px;
   background-color: #eeeeee; }
 
 .timeline > li {
   position: relative;
   margin-bottom: 20px; }
 
 .timeline > li:before,
 .timeline > li:after {
   content: " ";
   display: table; }
 
 .timeline > li:after {
   clear: both; }
 
 .timeline > li:before,
 .timeline > li:after {
   content: " ";
   display: table; }
 
 .timeline > li:after {
   clear: both; }
 
 .timeline > li > .timeline-panel {
   float: left;
   position: relative;
   width: 46%;
   padding: 20px;
   border: 1px solid #d4d4d4;
   border-radius: 2px;
   -webkit-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.175);
   box-shadow: 0 1px 6px rgba(0, 0, 0, 0.175); }
 
 .timeline > li > .timeline-panel:before {
   content: " ";
   display: inline-block;
   position: absolute;
   top: 26px;
   right: -15px;
   border-top: 15px solid transparent;
   border-right: 0 solid #ccc;
   border-bottom: 15px solid transparent;
   border-left: 15px solid #ccc; }
 
 .timeline > li > .timeline-panel:after {
   content: " ";
   display: inline-block;
   position: absolute;
   top: 27px;
   right: -14px;
   border-top: 14px solid transparent;
   border-right: 0 solid #fff;
   border-bottom: 14px solid transparent;
   border-left: 14px solid #fff; }
 
 .timeline > li > .timeline-badge {
   z-index: 100;
   position: absolute;
   top: 16px;
   left: 50%;
   width: 50px;
   height: 50px;
   margin-left: -25px;
   border-radius: 50% 50% 50% 50%;
   text-align: center;
   font-size: 1.4em;
   line-height: 50px;
   color: #fff;
   background-color: #999999; }
 
 .timeline > li.timeline-inverted > .timeline-panel {
   float: right; }
 
 .timeline > li.timeline-inverted > .timeline-panel:before {
   right: auto;
   left: -15px;
   border-right-width: 15px;
   border-left-width: 0; }
 
 .timeline > li.timeline-inverted > .timeline-panel:after {
   right: auto;
   left: -14px;
   border-right-width: 14px;
   border-left-width: 0; }
 
 .timeline-badge.primary {
   background-color: #2e6da4 !important; }
 
 .timeline-badge.success {
   background-color: #3f903f !important; }
 
 .timeline-badge.warning {
   background-color: #f0ad4e !important; }
 
 .timeline-badge.danger {
   background-color: #d9534f !important; }
 
 .timeline-badge.info {
   background-color: #5bc0de !important; }
 
 .timeline-title {
   margin-top: 0;
   color: inherit; }
 
 .timeline-body > p,
 .timeline-body > ul {
   margin-bottom: 0; }
 
 .timeline-body > p + p {
   margin-top: 5px; }
 
 @media (max-width: 767px) {
   ul.timeline:before {
     left: 40px; }
   ul.timeline > li > .timeline-panel {
     width: calc(100% - 90px);
     width: -moz-calc(100% - 90px);
     width: -webkit-calc(100% - 90px); }
   ul.timeline > li > .timeline-badge {
     top: 16px;
     left: 15px;
     margin-left: 0; }
   ul.timeline > li > .timeline-panel {
     float: right; }
   ul.timeline > li > .timeline-panel:before {
     right: auto;
     left: -15px;
     border-right-width: 15px;
     border-left-width: 0; }
   ul.timeline > li > .timeline-panel:after {
     right: auto;
     left: -14px;
     border-right-width: 14px;
     border-left-width: 0; } }
 
 /* -- stats-graph styles -- */
 .stats-minimized, .stats-graph-minimized, .stats-ontology-minimized {
   cursor: pointer;
   opacity: .7; }
   .stats-minimized:hover, .stats-graph-minimized:hover, .stats-ontology-minimized:hover {
     border: 1px solid white;
     opacity: 1; }
 
 .stats-graph-minimized {
   transform: translate(60%, -20%) scale(0.1, 0.25); }
 
 .stats-ontology-minimized {
   transform: translate(60%, 0%) scale(0.1, 0.25); }
 
 .stats-stage-base, .stats-graph-container, .stats-ontology-container {
   padding-bottom: 0px;
   padding-top: 0px;
   color: white;
   margin: 0 auto;
   width: 100%; }
 
 .stats-graph-container {
   border-bottom: none;
   /*border-left: 1px solid #125c8a;;*/
   overflow: auto;
   padding-top: .6em;
   /*background: linear-gradient(#048DBE, #025978) !important;*/ }
 
 .stats-ontology-container {
   overflow: auto;
   height: 100vh; }
 
 .stats-graph-container .bar-bg {
   background-color: white;
   height: 30px;
   margin: 0 auto 10px auto;
   line-height: 30px;
   font-size: 16px;
   font-family: Roboto, sans-serif;
   font-weight: 100;
   color: white;
   padding: 0 0 0 10px;
   position: relative; }
   .stats-graph-container .bar-bg .user {
     position: absolute;
     z-index: 4;
     left: .2em; }
   .stats-graph-container .bar-bg .bar {
     opacity: .8;
     z-index: 1;
     background-color: #125c8a;
     border: 1px solid #1c8ed6;
     height: 30px;
     transition: 0.7s;
     width: 100%;
     display: block;
     animation: stats-bar-before 1 1.8s;
     position: absolute;
     top: 0;
     left: 0;
     z-index: 2;
     padding-right: 8px;
     text-align: right; }
     .stats-graph-container .bar-bg .bar:hover {
       background: rgba(0, 0, 0, 0.5);
       cursor: pointer; }
 
 @keyframes stats-bar-before {
   0% {
     width: 0px; }
   100% {
     width: 100%; } }
 
 .digest ul {
   top: 15rem;
   left: 5rem;
   float: left;
   clear: left;
   margin: .25em;
   padding: 0; }
   .digest ul::before {
     display: none; }
   .digest ul li {
     display: block;
     position: relative;
     float: left;
     clear: both;
     right: auto;
     padding-left: 1em;
     width: auto;
     text-align: center;
     color: white;
     animation: showterm .25s; }
     .digest ul li a {
       display: block;
       position: relative;
       float: left;
       z-index: 4;
       margin: .25em;
       padding: .25em;
       background: white;
       color: #C0C0C0;
       border: 2px solid #94a0b4;
       border-radius: .5rem;
       transition: all .75s;
       font-weight: 300;
       font-family: sans-serif; }
 
 .digest .expandable {
   position: absolute;
   z-index: 4;
   top: 0;
   left: -.5em;
   margin: .65em;
   padding: 0;
   width: .8em;
   height: .8em;
   text-align: center;
   line-height: .6em;
   font-size: 1em;
   background-color: white;
   color: #C0C0C0;
   border: 1px solid #94a0b4;
   border-radius: .25rem; }
 
 .digest > ul {
   position: relative;
   font-family: "Georgia"; }
   .digest > ul:before {
     left: .5em; }
   .digest > ul:after {
     display: none; }
 
 @keyframes showterm {
   0% {
     top: -.5rem;
     opacity: 0; }
   100% {
     top: 0rem;
     opacity: 1; } }
 
 ul:after {
   color: blue;
   border: solid gray 1px;
   border-radius: .1em; }
 
 ul > li > a {
   color: blue;
   background: white; }
 
 .digest li span.expandable:hover,
 .digest li span.expandable:hover + a,
 .digest li span.expandable:hover + a + ul li a,
 .digest li a:hover,
 .digest li a:hover + ul li a {
   background: rgba(50, 130, 136, 0) !important;
   color: #fff;
   cursor: pointer;
   z-index: 1000;
   text-decoration: none; }
 
 .digest li a:hover + ul li::after,
 .digest li a:hover + ul li::before,
 .digest li a:hover + ul::before,
 .digest li a:hover + ul ul::before {
   border-color: #99C794 !important; }
 
 /* color settings */
 a {
   font-family: "Roboto", sans-serif;
   font-size: 14px;
   font-weight: 400;
   line-height: 1.5em;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
   color: #000000; }
 
 .red {
   background: rgba(158, 56, 68, 0.5) !important;
   border: 1px solid #9e3844 !important; }
 
 .dark-red {
   background: rgba(236, 95, 103, 0.5) !important;
   border: 1px solid #EC5F67 !important; }
 
 .dark-blue {
   background: rgba(53, 105, 151, 0.75) !important;
   border: 1px solid #6699CC !important; }
 
 .blue {
   background: rgba(102, 153, 204, 0.25) !important;
   border: 1px solid #6699CC !important; }
 
 .purple {
   background: rgba(197, 148, 197, 0.5) !important;
   border: 1px solid #C594C0 !important; }
 
 .ont-hidden {
   margin: auto;
   position: fixed;
   top: 100px;
   left: 0;
   right: 0;
   width: 50%;
   max-width: 630px;
   min-width: 320px;
   opacity: 0;
   height: auto;
   z-index: 2000;
   visibility: hidden;
   -webkit-backface-visibility: hidden;
   -moz-backface-visibility: hidden;
   backface-visibility: hidden; }
 
 .ont-overlay {
   position: fixed;
   width: 100%;
   height: 100%;
   visibility: visible;
   top: 0;
   left: 0;
   z-index: 2000;
   opacity: 1;
   background: rgba(0, 0, 0, 0.9);
   -webkit-transition: all 0.3s;
   -moz-transition: all 0.3s;
   transition: all 0.1s; }
 
 button.ont-close {
   display: block;
   background: transparent;
   color: #c0c0c0;
   right: 2%;
   top: 2%;
   position: absolute;
   border-radius: 9px;
   font-size: 20px;
   cursor: pointer;
   border: none; }
   button.ont-close:hover {
     color: #fff; }
 
 .tooltip-toggle {
   cursor: pointer;
   position: relative; }
   .tooltip-toggle::before {
     border: 1px solid rgba(255, 255, 255, 0.8);
     position: absolute;
     top: -5rem;
     left: -3rem;
     background-color: rgba(0, 0, 0, 0.8);
     border-radius: 5px;
     color: #fff;
     content: attr(aria-label);
     padding: 1rem;
     text-transform: none;
     -webkit-transition: all 2s;
     transition: all 2s;
     width: 160px; }
   .tooltip-toggle::after {
     position: absolute;
     top: -.8rem;
     left: 5rem;
     border-left: 5px solid transparent;
     border-right: 5px solid transparent;
     border-top: 5px solid white;
     content: " ";
     font-size: 0;
     line-height: 0;
     margin-left: -5px;
     width: 0; }
 
 .tooltip-toggle::before, .tooltip-toggle::after {
   color: #efefef;
   font-family: monospace;
   font-size: 16px;
   opacity: 0;
   pointer-events: none;
   text-align: center; }
 
 .tooltip-toggle:focus::before, .tooltip-toggle:focus::after, .tooltip-toggle:hover::before, .tooltip-toggle:hover::after {
   opacity: 1;
   -webkit-transition: all 2s;
   transition: all 2s; }
 
 .topic-dropdown {
   z-index: 20000;
   margin: 4rem;
   left: 0%;
   color: rgba(0, 0, 0, 0.87);
   font-family: "Roboto", sans-serif;
   font-size: 14px;
   font-weight: 300;
   line-height: 1.5em;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
   position: fixed; }
   .topic-dropdown div.title {
     text-transform: uppercase;
     font-weight: 500;
     font-size: 1.3em;
     color: #63BCF8; }
   .topic-dropdown .tdd-btn {
     max-width: 1000px;
     outline: 0;
     /* takes care of blue outline */
     display: -webkit-inline-box;
     display: -webkit-inline-flex;
     display: -ms-inline-flexbox;
     display: inline-flex;
     -webkit-box-align: center;
     -webkit-align-items: center;
     -ms-flex-align: center;
     align-items: center;
     -webkit-box-pack: justify;
     -webkit-justify-content: space-between;
     -ms-flex-pack: justify;
     justify-content: space-between;
     background: rgba(0, 0, 0, 0.75);
     border: 1px solid #63BCF8 !important;
     min-width: 400px;
     border: 0;
     border-radius: 4px;
     box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
     box-sizing: border-box;
     padding: 16px 20px;
     color: #FFFFFF;
     font-size: 12px;
     font-weight: 300;
     letter-spacing: 1.2px;
     text-transform: uppercase;
     overflow: hidden;
     cursor: pointer; }
     .topic-dropdown .tdd-btn:hover {
       cursor: pointer; }
     .topic-dropdown .tdd-btn .tdd-list {
       position: absolute;
       top: 100%;
       left: 0px;
       background: rgba(0, 0, 0, 0.75);
       border: 1px solid #63BCF8 !important;
       width: 100%;
       border-radius: 4px;
       box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
       text-align: left;
       opacity: 0;
       visibility: hidden;
       -webkit-transition: 0.3s ease;
-      transition: 0.3s ease; }
+      transition: 0.3s ease;
+      max-height: 600px;
+      overflow: auto; }
       .topic-dropdown .tdd-btn .tdd-list a {
         display: block;
         border-bottom: 1px solid rgba(0, 0, 0, 0.05);
         padding: 16px 0;
         color: inherit;
         text-decoration: none;
         background: none !important; }
       .topic-dropdown .tdd-btn .tdd-list:before {
         content: '';
         position: absolute;
         top: -6px;
         left: 20px;
         width: 0;
         height: 0;
         box-shadow: 2px -2px 6px rgba(0, 0, 0, 0.05);
         border-top: 6px solid #63BCF8;
         border-right: 6px solid #63BCF8;
         border-bottom: 6px solid transparent;
         border-left: 6px solid transparent;
         -webkit-transform: rotate(-45deg);
         transform: rotate(-45deg);
         mix-blend-mode: multiple; }
       .topic-dropdown .tdd-btn .tdd-list li {
         z-index: 100;
         position: relative;
         padding: 0 20px; }
         .topic-dropdown .tdd-btn .tdd-list li.active {
           color: #5380F7; }
         .topic-dropdown .tdd-btn .tdd-list li:first-child {
           border-radius: 4px 4px 0 0; }
         .topic-dropdown .tdd-btn .tdd-list li:last-child {
           border-radius: 0 0 4px 4px; }
         .topic-dropdown .tdd-btn .tdd-list li:last-child a {
           border-bottom: 0; }
     .topic-dropdown .tdd-btn:focus .tdd-list, .topic-dropdown .tdd-btn:active .tdd-list {
       -webkit-transform: translate(0, 20px);
       transform: translate(0, 20px);
       opacity: 1;
       visibility: visible; }
 
 .dropdown-arrow {
   content: '';
   opacity: .8;
   position: absolute;
   right: 10px;
   top: 43px;
   width: 0;
   height: 0;
   box-shadow: 2px -2px 6px rgba(0, 0, 0, 0.05);
   border-top: 4px solid #63BCF8;
   border-right: 4px solid #63BCF8;
   border-bottom: 4px solid transparent;
   border-left: 4px solid transparent;
   -webkit-transform: rotate(-45deg);
   transform: rotate(135deg); }
diff --git a/apps/steward-app/src/main/js/app/assets/css/topic-dropdown.css b/apps/steward-app/src/main/js/app/assets/css/topic-dropdown.css
index 9d047e8c3..72007a386 100644
--- a/apps/steward-app/src/main/js/app/assets/css/topic-dropdown.css
+++ b/apps/steward-app/src/main/js/app/assets/css/topic-dropdown.css
@@ -1,103 +1,105 @@
 .topic-dropdown {
   z-index: 20000;
   margin: 4rem;
   left: 0%;
   color: rgba(0, 0, 0, 0.87);
   font-family: "Roboto", sans-serif;
   font-size: 14px;
   font-weight: 300;
   line-height: 1.5em;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
   position: fixed; }
   .topic-dropdown div.title {
     text-transform: uppercase;
     font-weight: 500;
     font-size: 1.3em;
     color: #63BCF8; }
   .topic-dropdown .tdd-btn {
     max-width: 1000px;
     outline: 0;
     /* takes care of blue outline */
     display: -webkit-inline-box;
     display: -webkit-inline-flex;
     display: -ms-inline-flexbox;
     display: inline-flex;
     -webkit-box-align: center;
     -webkit-align-items: center;
     -ms-flex-align: center;
     align-items: center;
     -webkit-box-pack: justify;
     -webkit-justify-content: space-between;
     -ms-flex-pack: justify;
     justify-content: space-between;
     background: rgba(0, 0, 0, 0.75);
     border: 1px solid #63BCF8 !important;
     min-width: 400px;
     border: 0;
     border-radius: 4px;
     box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
     box-sizing: border-box;
     padding: 16px 20px;
     color: #FFFFFF;
     font-size: 12px;
     font-weight: 300;
     letter-spacing: 1.2px;
     text-transform: uppercase;
     overflow: hidden;
     cursor: pointer; }
     .topic-dropdown .tdd-btn:hover {
       cursor: pointer; }
     .topic-dropdown .tdd-btn .tdd-list {
       position: absolute;
       top: 100%;
       left: 0px;
       background: rgba(0, 0, 0, 0.75);
       border: 1px solid #63BCF8 !important;
       width: 100%;
       border-radius: 4px;
       box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
       text-align: left;
       opacity: 0;
       visibility: hidden;
       -webkit-transition: 0.3s ease;
-      transition: 0.3s ease; }
+      transition: 0.3s ease;
+      max-height: 600px;
+      overflow: auto; }
       .topic-dropdown .tdd-btn .tdd-list a {
         display: block;
         border-bottom: 1px solid rgba(0, 0, 0, 0.05);
         padding: 16px 0;
         color: inherit;
         text-decoration: none;
         background: none !important; }
       .topic-dropdown .tdd-btn .tdd-list:before {
         content: '';
         position: absolute;
         top: -6px;
         left: 20px;
         width: 0;
         height: 0;
         box-shadow: 2px -2px 6px rgba(0, 0, 0, 0.05);
         border-top: 6px solid #63BCF8;
         border-right: 6px solid #63BCF8;
         border-bottom: 6px solid transparent;
         border-left: 6px solid transparent;
         -webkit-transform: rotate(-45deg);
         transform: rotate(-45deg);
         mix-blend-mode: multiple; }
       .topic-dropdown .tdd-btn .tdd-list li {
         z-index: 100;
         position: relative;
         padding: 0 20px; }
         .topic-dropdown .tdd-btn .tdd-list li.active {
           color: #5380F7; }
         .topic-dropdown .tdd-btn .tdd-list li:first-child {
           border-radius: 4px 4px 0 0; }
         .topic-dropdown .tdd-btn .tdd-list li:last-child {
           border-radius: 0 0 4px 4px; }
         .topic-dropdown .tdd-btn .tdd-list li:last-child a {
           border-bottom: 0; }
     .topic-dropdown .tdd-btn:focus .tdd-list, .topic-dropdown .tdd-btn:active .tdd-list {
       -webkit-transform: translate(0, 20px);
       transform: translate(0, 20px);
       opacity: 1;
       visibility: visible; }
diff --git a/apps/steward-app/src/main/js/app/client/statistics/query-digest/topic-dropdown.tpl.html b/apps/steward-app/src/main/js/app/client/statistics/query-digest/topic-dropdown.tpl.html
index a578603a8..cbdef6191 100644
--- a/apps/steward-app/src/main/js/app/client/statistics/query-digest/topic-dropdown.tpl.html
+++ b/apps/steward-app/src/main/js/app/client/statistics/query-digest/topic-dropdown.tpl.html
@@ -1,16 +1,16 @@
 <div class="topic-dropdown">
   <div class="title">
   Filter By Topic:
   <div> 
 	<button class="tdd-btn">
-    <span ng-init="dropdown.selected={title: 'All'}">{{dropdown.selected.title}}</span>&nbsp;&nbsp;<span class="dropdown-arrow"></span>
+    <span ng-init="dropdown.selected={title: 'All'}" style="overflow: hidden;">{{dropdown.selected.title}}</span>&nbsp;&nbsp;<span class="dropdown-arrow"></span>
     <ul class="tdd-list">
       <li ng-class="{'active': dropdown.selected.id === undefined}" ng-click="dropdown.selected={title: 'All'}; topicSelected()">
         <a href="" >All</a>
       </li>
       <li ng-repeat="topic in dropdown.topics" ng-class="{'active': dropdown.selected.id === topic.id}" ng-click="dropdown.selected={title: topic.name, id: topic.id}; topicSelected({topicId: topic.id})">
         <a href="" >{{topic.name}}</a>
       </li>
      </ul>      
     </button>
 </div>
\ No newline at end of file
diff --git a/apps/steward-app/src/main/js/app/sass/topic-dropdown.scss b/apps/steward-app/src/main/js/app/sass/topic-dropdown.scss
index e8f6edab6..ce4be178b 100644
--- a/apps/steward-app/src/main/js/app/sass/topic-dropdown.scss
+++ b/apps/steward-app/src/main/js/app/sass/topic-dropdown.scss
@@ -1,126 +1,128 @@
 .topic-dropdown {
   div.title{
     text-transform: uppercase;
     font-weight: 500;
     font-size: 1.3em;
     color: #63BCF8;
   }
   z-index: 20000;
   margin: 4rem;
   left: 0%;
   color: rgba(0, 0, 0, 0.87);
   font-family: "Roboto", sans-serif;
   font-size: 14px;
   font-weight: 300;
   line-height: 1.5em;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
   position: fixed;
     .tdd-btn {
       &:hover{
         cursor: pointer;
       }
       max-width: 1000px;
       outline: 0; /* takes care of blue outline */
       display: -webkit-inline-box;
       display: -webkit-inline-flex;
       display: -ms-inline-flexbox;
       display: inline-flex;
       -webkit-box-align: center;
       -webkit-align-items: center;
       -ms-flex-align: center;
       align-items: center;
       -webkit-box-pack: justify;
       -webkit-justify-content: space-between;
       -ms-flex-pack: justify;
       justify-content: space-between;
       background: rgba(0,0,0, .75);
       border: 1px solid #63BCF8 !important;
       min-width: 400px;
       border: 0;
       border-radius: 4px;
       box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
       box-sizing: border-box;
       padding: 16px 20px;
       color: #FFFFFF;
       font-size: 12px;
       font-weight: 300;
       letter-spacing: 1.2px;
       text-transform: uppercase;
       overflow: hidden;
       cursor: pointer;
 
       .tdd-list {
         position: absolute;
         top: 100%;
         left: 0px;
         background: rgba(0,0,0, .75);
         border: 1px solid #63BCF8 !important;
         width: 100%;
         border-radius: 4px;
         box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
         text-align: left;
         opacity: 0;
         visibility: hidden;
         -webkit-transition: 0.3s ease;
         transition: 0.3s ease;
+        max-height: 600px;
+        overflow: auto;
 
         a {
           display: block;
           border-bottom: 1px solid rgba(0, 0, 0, 0.05);
           padding: 16px 0;
           color: inherit;
           text-decoration: none;
           background: none !important;
         };
 
         &:before {
           content: '';
           position: absolute;
           top: -6px;
           left: 20px;
           width: 0;
           height: 0;
           box-shadow: 2px -2px 6px rgba(0, 0, 0, 0.05);
           border-top: 6px solid #63BCF8;;
           border-right: 6px solid #63BCF8;;
           border-bottom: 6px solid transparent;
           border-left: 6px solid transparent;
           -webkit-transform: rotate(-45deg);
           transform: rotate(-45deg);
           mix-blend-mode: multiple;
         };
 
         li {
           z-index: 100;
           position: relative;
           padding: 0 20px;
 
           &.active {
             color: #5380F7;
           }
           &:first-child {
             border-radius: 4px 4px 0 0;
           }
           &:last-child {
             border-radius: 0 0 4px 4px;
           }
           &:last-child a {
             border-bottom: 0;
           }
         };
       };
 
       &:focus .tdd-list, &:active .tdd-list {
         -webkit-transform: translate(0, 20px);
         transform: translate(0, 20px);
         opacity: 1;
         visibility: visible;
       };
   };
 }
 
 
 
 
 
diff --git a/apps/steward-app/src/main/scala/net/shrine/steward/StewardService.scala b/apps/steward-app/src/main/scala/net/shrine/steward/StewardService.scala
index 5695c2a4d..2ddc70292 100644
--- a/apps/steward-app/src/main/scala/net/shrine/steward/StewardService.scala
+++ b/apps/steward-app/src/main/scala/net/shrine/steward/StewardService.scala
@@ -1,372 +1,375 @@
 package net.shrine.steward
 
 import akka.actor.Actor
 import akka.event.Logging
 import net.shrine.authentication.UserAuthenticator
 import net.shrine.authorization.steward._
 import net.shrine.i2b2.protocol.pm.User
 import net.shrine.serialization.NodeSeqSerializer
 import net.shrine.source.ConfigSource
 import net.shrine.steward.db._
 import net.shrine.steward.pmauth.Authorizer
 import org.json4s.native.Serialization
 import shapeless.HNil
 import spray.http.{HttpRequest, HttpResponse, StatusCodes}
 import spray.httpx.Json4sSupport
 import spray.routing.directives.LogEntry
 import spray.routing._
 import org.json4s.{DefaultFormats, DefaultJsonFormats, Formats, Serialization}
 
 import scala.concurrent.ExecutionContext.Implicits.global
 import scala.util.{Failure, Success, Try}
 
 // we don't implement our route structure directly in the service actor because
 // we want to be able to test it independently, without having to spin up an actor
 
 class StewardServiceActor extends Actor with StewardService {
 
   // the HttpService trait defines only one abstract member, which
   // connects the services environment to the enclosing actor or test
   def actorRefFactory = context
 
   // this actor only runs our route, but you could add
   // other things here, like request stream processing
   // or timeout handling
   def receive = runRoute(route)
 }
 
 
 // this trait defines our service behavior independently from the service actor
 trait StewardService extends HttpService with Json4sSupport {
   implicit def json4sFormats: Formats = DefaultFormats + new NodeSeqSerializer
 
   val userAuthenticator = UserAuthenticator(ConfigSource.config)
 
   //don't need to do anything special for unauthorized users, but they do need access to a static form.
   lazy val route:Route = gruntWatchCorsSupport{
       requestLogRoute ~ fullLogRoute
   }
 
   lazy val requestLogRoute = logRequestResponse(logEntryForRequest _) {
     redirectToIndex ~ staticResources ~ makeTrouble ~ about
   }
 
   lazy val fullLogRoute = logRequestResponse(logEntryForRequestResponse _) {
     qepRoute ~ authenticatedInBrowser
   }
 
   // logs just the request method, uri and response at info level
   //logging is controlled by Akka's config, slf4j, and log4j config
    def logEntryForRequestResponse(req: HttpRequest): Any => Option[LogEntry] = {
     case res: HttpResponse => {
       Some(LogEntry(s"\n  Request: $req \n  Response: $res", Logging.InfoLevel))
     }
     case _ => None // other kind of responses
   }
 
   // logs just the request method, uri and response status at info level
   def logEntryForRequest(req: HttpRequest): Any => Option[LogEntry] = {
     case res: HttpResponse => {
       Some(LogEntry(s"\n  Request: $req \n  Response status: ${res.status}", Logging.InfoLevel))
     }
     case _ => None // other kind of responses
   }
 
   //pathPrefixTest shields the QEP code from the redirect.
   def authenticatedInBrowser: Route = pathPrefixTest("user"|"steward"|"researcher") {
     reportIfFailedToAuthenticate {
         authenticate(userAuthenticator.basicUserAuthenticator) { user =>
 
           StewardDatabase.db.upsertUser(user)
 
           pathPrefix("user") {userRoute(user)} ~
           pathPrefix("steward") {stewardRoute(user)} ~
           pathPrefix("researcher") {researcherRoute(user)}
       }
     }
   }
 
   val reportIfFailedToAuthenticate = routeRouteResponse {
     case Rejected(List(AuthenticationFailedRejection(_,_))) =>
       complete("AuthenticationFailed")
   }
 
   def makeTrouble = pathPrefix("makeTrouble") {
     complete(throw new IllegalStateException("fake trouble"))
   }
 
   lazy val redirectToIndex = pathEnd {
     redirect("steward/client/index.html", StatusCodes.PermanentRedirect) //todo pick up "steward" programatically
   } ~
   ( path("index.html") | pathSingleSlash) {
       redirect("client/index.html", StatusCodes.PermanentRedirect)
   }
 
   lazy val staticResources = pathPrefix("client") {
     pathEnd {
       redirect("client/index.html", StatusCodes.PermanentRedirect)
     } ~
     pathSingleSlash {
       redirect("index.html", StatusCodes.PermanentRedirect)
     } ~ {
       getFromResourceDirectory("client")
     }
   }
 
   lazy val about = pathPrefix("about") {
     path("createTopicsMode") {
       get {
         complete(CreateTopicsMode.createTopicsInState.name)
       }
     }
   }
 
   def userRoute(user:User):Route = get {
     pathPrefix("whoami") {
       complete(OutboundUser.createFromUser(user))
     }
   }
 
   def qepRoute:Route = pathPrefix("qep") {
     authenticate(userAuthenticator.basicUserAuthenticator) { user =>
 
       StewardDatabase.db.upsertUser(user)
 
       authorize(Authorizer.authorizeQep(user)) {
         pathPrefix("requestQueryAccess") ( requestQueryAccess ) ~
         pathPrefix("approvedTopics") ( getApprovedTopicsForUser )
       }
     }
   }
 
   def requestQueryAccess:Route = post {
     requestQueryAccessWithTopic ~ requestQueryAccessWithoutTopic
   }
 
   def requestQueryAccessWithTopic:Route = path("user" /Segment/ "topic" / IntNumber) { (userId,topicId) =>
     entity(as[InboundShrineQuery]) { shrineQuery:InboundShrineQuery =>
       //todo really pull the user out of the shrine query and check vs the PM. If they aren't there, reject them for this new reason
       val result: (TopicState, Option[TopicIdAndName]) = StewardDatabase.db.logAndCheckQuery(userId,Some(topicId),shrineQuery)
 
       respondWithStatus(result._1.statusCode) {
         if(result._1.statusCode == StatusCodes.OK) complete (result._2.getOrElse(""))
         else complete(result._1.message)
       }
     }
   }
 
   def requestQueryAccessWithoutTopic:Route = path("user" /Segment) { userId =>
     entity(as[InboundShrineQuery]) { shrineQuery:InboundShrineQuery =>
       //todo really pull the user out of the shrine query and check vs the PM. If they aren't there, reject them for this new reason
       val result = StewardDatabase.db.logAndCheckQuery(userId,None,shrineQuery)
       respondWithStatus(result._1.statusCode) {
         if(result._1.statusCode == StatusCodes.OK) complete (result._2)
         else complete(result._1.message)
       }
     }
   }
 
   lazy val getApprovedTopicsForUser:Route = get {
     //todo change to "researcher"
     path("user" /Segment) { userId =>
       //todo really pull the user out of the shrine query and check vs the PM. If they aren't there, reject them for this new reason
       val queryParameters = QueryParameters(researcherIdOption = Some(userId),stateOption = Some(TopicState.approved))
       val researchersTopics = StewardDatabase.db.selectTopicsForResearcher(queryParameters)
 
       complete(researchersTopics)
     }
   }
 
   def researcherRoute(user:User):Route = authorize(Authorizer.authorizeResearcher(user)) {
     pathPrefix("topics") { getUserTopics(user.username) } ~
       pathPrefix("queryHistory") { getUserQueryHistory(Some(user.username)) } ~
       pathPrefix("requestTopicAccess") { requestTopicAccess(user) } ~
       pathPrefix("editTopicRequest") { editTopicRequest(user) }
   }
 
   def getUserTopics(userId:UserName):Route = get {
     //lookup topics for this user in the db
    matchQueryParameters(Some(userId)){queryParameters:QueryParameters =>
       val researchersTopics = StewardDatabase.db.selectTopicsForResearcher(queryParameters)
       complete(researchersTopics)
     }
   }
 
   def matchQueryParameters(userName: Option[UserName])(parameterRoute:QueryParameters => Route): Route =  {
 
     parameters('state.?,'skip.as[Int].?,'limit.as[Int].?,'sortBy.as[String].?,'sortDirection.as[String].?,'minDate.as[Date].?,'maxDate.as[Date].?) { (stateStringOption,skipOption,limitOption,sortByOption,sortOption,minDate,maxDate) =>
 
       val stateTry = TopicState.stateForStringOption(stateStringOption)
       stateTry match {
         case Success(stateOption) =>
           val qp = QueryParameters(userName,
             stateOption,
             skipOption,
             limitOption,
             sortByOption,
             SortOrder.sortOrderForStringOption(sortOption),
             minDate,
             maxDate
           )
 
           parameterRoute(qp)
 
         case Failure(ex) => badStateRoute(stateStringOption)
       }
     }
   }
 
   def badStateRoute(stateStringOption:Option[String]):Route = {
     respondWithStatus(StatusCodes.UnprocessableEntity) {
       complete(s"Topic state ${stateStringOption.getOrElse(s"$stateStringOption (stateStringOption should never be None at this point)")} unknown. Please specify one of ${TopicState.namesToStates.keySet}")
     }
   }
 
   def getUserQueryHistory(userIdOption:Option[UserName]):Route = get {
     parameter('asJson.as[Boolean].?) { asJson =>
       path("topic" / IntNumber) { topicId: TopicId =>
         getQueryHistoryForUserByTopic(userIdOption, Some(topicId), asJson)
       } ~
         getQueryHistoryForUserByTopic(userIdOption, None, asJson)
     }
   }
 
-  def getQueryHistoryForUserByTopic(userIdOption:Option[UserName],topicIdOption:Option[TopicId], asJson: Option[Boolean]) = get {
-    matchQueryParameters(userIdOption) { queryParameters:QueryParameters =>
-      val queryHistory = StewardDatabase.db.selectQueryHistory(queryParameters, topicIdOption)
-
-      if (asJson.getOrElse(false))
-        complete(queryHistory.convertToJson)
-      else
-        complete(queryHistory)
+  def getQueryHistoryForUserByTopic(userIdOption: Option[UserName],
+                                    topicIdOption: Option[TopicId],
+                                    asJson: Option[Boolean]) =
+    get {
+      matchQueryParameters(userIdOption) { queryParameters: QueryParameters =>
+        val queryHistory = StewardDatabase.db.selectQueryHistory(queryParameters, topicIdOption)
+
+        if (asJson.getOrElse(false))
+          complete(queryHistory.convertToJson)
+        else
+          complete(queryHistory)
+      }
     }
-  }
 
   def requestTopicAccess(user:User):Route = post {
     entity(as[InboundTopicRequest]) { topicRequest: InboundTopicRequest =>
       //todo notify the data stewards
       StewardDatabase.db.createRequestForTopicAccess(user,topicRequest)
 
       complete(StatusCodes.Accepted)
     }
   }
 
   def editTopicRequest(user:User):Route = post {
     path(IntNumber) { topicId => 
       entity(as[InboundTopicRequest]) { topicRequest: InboundTopicRequest =>
         //todo notify the data stewards
         val updatedTopicTry:Try[OutboundTopic] = StewardDatabase.db.updateRequestForTopicAccess(user, topicId, topicRequest)
 
         updatedTopicTry match {
           case Success(updatedTopic) =>
             respondWithStatus(StatusCodes.Accepted) {
               complete(updatedTopic)
             }
 
           case Failure(x) => x match {
             case x:TopicDoesNotExist => respondWithStatus(StatusCodes.NotFound) {
               complete(x.getMessage)
             }
             case x:ApprovedTopicCanNotBeChanged => respondWithStatus(StatusCodes.Forbidden) {
               complete(x.getMessage)
             }
             case x:DetectedAttemptByWrongUserToChangeTopic => respondWithStatus(StatusCodes.Forbidden) {
               complete(x.getMessage)
             }
             case _ => throw x
           }
         }
       }
     }
   }
 
   def stewardRoute(user:User):Route = authorize(Authorizer.authorizeSteward(user)) {
     pathPrefix("queryHistory" / "user") {getUserQueryHistory } ~
       pathPrefix("queryHistory") {getQueryHistory} ~
       pathPrefix("topics" / "user")(getUserTopicsForSteward) ~
       path("topics"){getTopicsForSteward} ~
       pathPrefix("approveTopic")(approveTopicForUser(user)) ~
       pathPrefix("rejectTopic")(rejectTopicForUser(user)) ~
       pathPrefix("statistics"){getStatistics}
   }
 
   lazy val getUserQueryHistory:Route = pathPrefix(Segment) { userId =>
     getUserQueryHistory(Some(userId))
   }
 
   lazy val getQueryHistory:Route = getUserQueryHistory(None)
 
   lazy val getTopicsForSteward:Route = getTopicsForSteward(None)
 
   lazy val getUserTopicsForSteward:Route = path(Segment) { userId =>
     getTopicsForSteward(Some(userId))
   }
 
   def getTopicsForSteward(userIdOption:Option[UserName]):Route = get {
     //lookup topics for this user in the db
     matchQueryParameters(userIdOption) { queryParameters: QueryParameters =>
       val stewardsTopics:StewardsTopics = StewardDatabase.db.selectTopicsForSteward(queryParameters)
 
       complete(stewardsTopics)
     }
   }
 
   def approveTopicForUser(user:User):Route = changeStateForTopic(TopicState.approved,user)
 
   def rejectTopicForUser(user:User):Route = changeStateForTopic(TopicState.rejected,user)
 
   def changeStateForTopic(state:TopicState,user:User):Route = post {
     path("topic" / IntNumber) { topicId =>
       StewardDatabase.db.changeTopicState(topicId, state, user.username).fold(respondWithStatus(StatusCodes.UnprocessableEntity){
         complete(s"No topic found for $topicId")
       })(topic => complete(StatusCodes.OK))
     }
   }
 
   def getStatistics:Route = pathPrefix("queriesPerUser"){getQueriesPerUser} ~
                               pathPrefix("topicsPerState"){getTopicsPerState}
 
   def getQueriesPerUser:Route = get{
     matchQueryParameters(None) { queryParameters: QueryParameters =>
       val result = StewardDatabase.db.selectShrineQueryCountsPerUser(queryParameters)
 
       complete(result)
     }
   }
 
   def getTopicsPerState:Route = get{
     matchQueryParameters(None) { queryParameters: QueryParameters =>
       val result = StewardDatabase.db.selectTopicCountsPerState(queryParameters)
       complete(result)
     }
   }
 }
 
 //adapted from https://gist.github.com/joseraya/176821d856b43b1cfe19
 object gruntWatchCorsSupport extends Directive0 with RouteConcatenation {
 
   import spray.http.HttpHeaders.{`Access-Control-Allow-Methods`, `Access-Control-Max-Age`, `Access-Control-Allow-Headers`,`Access-Control-Allow-Origin`}
   import spray.routing.directives.RespondWithDirectives.respondWithHeaders
   import spray.routing.directives.MethodDirectives.options
   import spray.routing.directives.RouteDirectives.complete
   import spray.http.HttpMethods.{OPTIONS,GET,POST}
   import spray.http.AllOrigins
 
   private val allowOriginHeader = `Access-Control-Allow-Origin`(AllOrigins)
   private val optionsCorsHeaders = List(
     `Access-Control-Allow-Headers`("Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding, Accept-Language, Host, Referer, User-Agent, Authorization"),
     `Access-Control-Max-Age`(1728000)) //20 days
 
   val gruntWatch:Boolean = ConfigSource.config.getBoolean("shrine.steward.gruntWatch")
 
   override def happly(f: (HNil) => Route): Route = {
     if(gruntWatch) {
       options {
         respondWithHeaders(`Access-Control-Allow-Methods`(OPTIONS, GET, POST) ::  allowOriginHeader :: optionsCorsHeaders){
           complete(StatusCodes.OK)
         }
       } ~ f(HNil)
     }
     else f(HNil)
   }
 }
diff --git a/apps/steward-app/src/main/scala/net/shrine/steward/db/StewardDatabase.scala b/apps/steward-app/src/main/scala/net/shrine/steward/db/StewardDatabase.scala
index 5e57550a0..a94f3c75c 100644
--- a/apps/steward-app/src/main/scala/net/shrine/steward/db/StewardDatabase.scala
+++ b/apps/steward-app/src/main/scala/net/shrine/steward/db/StewardDatabase.scala
@@ -1,738 +1,740 @@
 package net.shrine.steward.db
 
 import java.sql.SQLException
 import java.util.concurrent.atomic.AtomicInteger
 import javax.sql.DataSource
 
 import com.typesafe.config.Config
 import net.shrine.authorization.steward.{Date, ExternalQueryId, InboundShrineQuery, InboundTopicRequest, OutboundShrineQuery, OutboundTopic, OutboundUser, QueriesPerUser, QueryContents, QueryHistory, ResearcherToAudit, ResearchersTopics, StewardQueryId, StewardsTopics, TopicId, TopicIdAndName, TopicState, TopicStateName, TopicsPerState, UserName, researcherRole, stewardRole}
 import net.shrine.i2b2.protocol.pm.User
 import net.shrine.log.Loggable
 import net.shrine.slick.{NeedsWarmUp, TestableDataSourceCreator}
 import net.shrine.source.ConfigSource
 import net.shrine.steward.CreateTopicsMode
 import slick.dbio.Effect.Read
 import slick.driver.JdbcProfile
 
 import scala.concurrent.ExecutionContext.Implicits.global
 import scala.concurrent.duration.{Duration, DurationInt}
 import scala.concurrent.{Await, Future, blocking}
 import scala.language.postfixOps
 import scala.util.Try
 /**
  * Database access code for the data steward service.
  *
  * I'm not letting Slick handle foreign key resolution for now. I want to keep that logic separate to handle dirty data with some grace.
  *
  * @author dwalend
  * @since 1.19
  */
 case class StewardDatabase(schemaDef:StewardSchema,dataSource: DataSource) extends Loggable {
   import schemaDef._
   import jdbcProfile.api._
 
   val database = Database.forDataSource(dataSource)
 
   def createTables() = schemaDef.createTables(database)
 
   def dropTables() = schemaDef.dropTables(database)
 
   def dbRun[R](action: DBIOAction[R, NoStream, Nothing]):R = {
     val future: Future[R] = database.run(action)
     blocking {
       Await.result(future, 10 seconds)
     }
   }
 
   def warmUp = {
     dbRun(allUserQuery.size.result)
   }
 
   def selectUsers:Seq[UserRecord] = {
     dbRun(allUserQuery.result)
   }
 
   // todo use whenever a shrine query is logged
   def upsertUser(user:User):Unit = {
 
     val userRecord = UserRecord(user)
     dbRun(allUserQuery.insertOrUpdate(userRecord))
   }
 
   def createRequestForTopicAccess(user:User,topicRequest:InboundTopicRequest):TopicRecord = {
     val createInState = CreateTopicsMode.createTopicsInState
     val now = System.currentTimeMillis()
     val topicRecord = TopicRecord(Some(nextTopicId.getAndIncrement),topicRequest.name,topicRequest.description,user.username,now,createInState.topicState)
     val userTopicRecord = UserTopicRecord(user.username,topicRecord.id.get,TopicState.approved,user.username,now)
 
     dbRun(for{
       _ <- allTopicQuery += topicRecord
       _ <- allUserTopicQuery += userTopicRecord
     } yield topicRecord)
   }
 
   def updateRequestForTopicAccess(user:User,topicId:TopicId,topicRequest:InboundTopicRequest):Try[OutboundTopic] = Try {
 
     dbRun(mostRecentTopicQuery.filter(_.id === topicId).result.headOption.flatMap{ option =>
         val oldTopicRecord = option.getOrElse(throw TopicDoesNotExist(topicId = topicId))
         if(user.username != oldTopicRecord.createdBy) throw DetectedAttemptByWrongUserToChangeTopic(topicId,user.username,oldTopicRecord.createdBy)
         if(oldTopicRecord.state == TopicState.approved) throw ApprovedTopicCanNotBeChanged(topicId)
 
         val updatedTopic = oldTopicRecord.copy(name = topicRequest.name,
                                                 description = topicRequest.description,
                                                 changedBy = user.username,
                                                 changeDate = System.currentTimeMillis())
         (allTopicQuery += updatedTopic).flatMap{_ =>
           outboundUsersForNamesAction(Set(updatedTopic.createdBy,updatedTopic.changedBy)).map(updatedTopic.toOutboundTopic)
         }
       }
     )
   }
 
   def selectTopicsForResearcher(parameters:QueryParameters):ResearchersTopics = {
     require(parameters.researcherIdOption.isDefined,"A researcher's parameters must supply a user id")
 
     val (count,topics,userNamesToOutboundUsers) = dbRun(
       for{
         count <- topicCountQuery(parameters).length.result
         topics <- topicSelectQuery(parameters).result
         userNamesToOutboundUsers <- outboundUsersForNamesAction((topics.map(_.createdBy) ++ topics.map(_.changedBy)).to[Set])
       } yield (count, topics,userNamesToOutboundUsers))
 
     ResearchersTopics(parameters.researcherIdOption.get,
       count,
       parameters.skipOption.getOrElse(0),
       topics.map(_.toOutboundTopic(userNamesToOutboundUsers)))
   }
 
   //treat as private (currently used in test)
   def selectTopics(queryParameters: QueryParameters):Seq[TopicRecord] = {
     dbRun(topicSelectQuery(queryParameters).result)
   }
 
   def selectTopicsForSteward(queryParameters: QueryParameters):StewardsTopics = {
 
     val (count,topics,userNamesToOutboundUsers) = dbRun{
       for{
         count <- topicCountQuery(queryParameters).length.result
         topics <- topicSelectQuery(queryParameters).result
         userNamesToOutboundUsers <- outboundUsersForNamesAction((topics.map(_.createdBy) ++ topics.map(_.changedBy)).to[Set])
       } yield (count,topics,userNamesToOutboundUsers)
     }
 
     StewardsTopics(count,
                     queryParameters.skipOption.getOrElse(0),
                     topics.map(_.toOutboundTopic(userNamesToOutboundUsers)))
   }
 
   private def topicSelectQuery(queryParameters: QueryParameters):Query[TopicTable, TopicTable#TableElementType, Seq] = {
     val countFilter = topicCountQuery(queryParameters)
 
     //todo is there some way to do something with a map from column names to columns that I don't have to update? I couldn't find one.
     //    val orderByQuery = queryParameters.sortByOption.fold(countFilter)(
     //      columnName => limitFilter.sortBy(x => queryParameters.sortOrder.orderForColumn(countFilter.columnForName(columnName))))
     val orderByQuery = queryParameters.sortByOption.fold(countFilter)(
       columnName => countFilter.sortBy(x => queryParameters.sortOrder.orderForColumn(columnName match {
         case "id" => x.id
         case "name" => x.name
         case "description" => x.description
         case "createdBy" => x.createdBy
         case "createDate" => x.createDate
         case "state" => x.state
         case "changedBy" => x.changedBy
         case "changeDate" => x.changeDate
       })))
 
     val skipFilter = queryParameters.skipOption.fold(orderByQuery)(skip => orderByQuery.drop(skip))
     val limitFilter = queryParameters.limitOption.fold(skipFilter)(limit => skipFilter.take(limit))
 
     limitFilter
   }
 
   private def topicCountQuery(queryParameters: QueryParameters):Query[TopicTable, TopicTable#TableElementType, Seq] = {
     val allTopics:Query[TopicTable, TopicTable#TableElementType, Seq] = mostRecentTopicQuery
     val researcherFilter = queryParameters.researcherIdOption.fold(allTopics)(userId => allTopics.filter(_.createdBy === userId))
     val stateFilter = queryParameters.stateOption.fold(researcherFilter)(state => researcherFilter.filter(_.state === state.name))
     val minDateFilter = queryParameters.minDate.fold(stateFilter)(minDate => stateFilter.filter(_.changeDate >= minDate))
     val maxDateFilter = queryParameters.maxDate.fold(minDateFilter)(maxDate => minDateFilter.filter(_.changeDate <= maxDate))
 
     maxDateFilter
   }
 
   def changeTopicState(topicId:TopicId,state:TopicState,userId:UserName):Option[TopicRecord] = {
 
     val noTopicRecord:Option[TopicRecord] = None
     val noOpDBIO:DBIOAction[Option[TopicRecord], NoStream, Effect.Write] = DBIO.successful(noTopicRecord)
 
     dbRun(mostRecentTopicQuery.filter(_.id === topicId).result.headOption.flatMap(
       _.fold(noOpDBIO){ originalTopic =>
         val updatedTopic = originalTopic.copy(state = state, changedBy = userId, changeDate = System.currentTimeMillis())
         (allTopicQuery += updatedTopic).map(_ => Option(updatedTopic))
       }
     ))
   }
 
   def selectTopicCountsPerState(queryParameters: QueryParameters):TopicsPerState = {
     dbRun(for{
       totalTopics <- topicCountQuery(queryParameters).length.result
       topicsPerStateName <- topicCountsPerState(queryParameters).result
     } yield TopicsPerState(totalTopics,topicsPerStateName))
   }
 
   private def topicCountsPerState(queryParameters: QueryParameters): Query[(Rep[TopicStateName], Rep[Int]), (TopicStateName, Int), Seq] = {
     val groupedByState = topicCountQuery(queryParameters).groupBy(topicRecord => topicRecord.state)
     groupedByState.map{case (state,result) => (state,result.length)}
   }
 
   def logAndCheckQuery(userId:UserName,topicId:Option[TopicId],shrineQuery:InboundShrineQuery):(TopicState,Option[TopicIdAndName]) = {
 
     //todo upsertUser(user) when the info is available from the PM
     val noOpDBIOForState: DBIOAction[TopicState, NoStream, Effect.Read] = DBIO.successful {
       if (CreateTopicsMode.createTopicsInState == CreateTopicsMode.TopicsIgnoredJustLog) TopicState.approved
       else TopicState.createTopicsModeRequiresTopic
     }
 
     val noOpDBIOForTopicName: DBIOAction[Option[String], NoStream, Read] = DBIO.successful{None}
 
     val (state,topicName) = dbRun(for{
 
       state <- topicId.fold(noOpDBIOForState)( someTopicId =>
           mostRecentTopicQuery.filter(_.id === someTopicId).filter(_.createdBy === userId).map(_.state).result.headOption.map(
             _.fold(TopicState.unknownForUser)(state => TopicState.namesToStates(state)))
         )
       topicName <- topicId.fold(noOpDBIOForTopicName)( someTopicId =>
        mostRecentTopicQuery.filter(_.id === someTopicId).filter(_.createdBy === userId).map(_.name).result.headOption
       )
       _ <- allQueryTable += ShrineQueryRecord(userId,topicId,shrineQuery,state)
     } yield (state,topicName))
 
     val topicIdAndName:Option[TopicIdAndName] = (topicId,topicName) match {
       case (Some(id),Some(name)) => Option(TopicIdAndName(id.toString,name))
       case (None,None) => None
       case (Some(id),None) =>
         if(state == TopicState.unknownForUser) None
         else throw new IllegalStateException(s"How did you get here for $userId with $id and $state for $shrineQuery")
       case (None,Some(name)) =>
         if(state == TopicState.unknownForUser) None
         else throw new IllegalStateException(s"How did you get here for $userId with no topic id but a topic name of $name and $state for $shrineQuery")
     }
     (state,topicIdAndName)
   }
 
-  def selectQueryHistory(queryParameters: QueryParameters,topicParameter:Option[TopicId]):QueryHistory = {
+  def selectQueryHistory(queryParameters: QueryParameters, topicParameter:Option[TopicId]):
+  QueryHistory = {
 
-    val (count,shrineQueries,topics,userNamesToOutboundUsers) = dbRun(for {
-      count <- shrineQueryCountQuery(queryParameters,topicParameter).length.result
+    val topicQuery = for {
+      count <- shrineQueryCountQuery(queryParameters, topicParameter).length.result
       shrineQueries <- shrineQuerySelectQuery(queryParameters, topicParameter).result
       topics <- mostRecentTopicQuery.filter(_.id.inSet(shrineQueries.map(_.topicId).to[Set].flatten)).result
       userNamesToOutboundUsers <- outboundUsersForNamesAction(shrineQueries.map(_.userId).to[Set] ++ (topics.map(_.createdBy) ++ topics.map(_.changedBy)).to[Set])
 
-    } yield (count,shrineQueries,topics,userNamesToOutboundUsers))
+    } yield (count, shrineQueries, topics, userNamesToOutboundUsers)
+
+    val (count, shrineQueries, topics, userNamesToOutboundUsers) = dbRun(topicQuery)
 
     val topicIdsToTopics: Map[Option[TopicId], TopicRecord] = topics.map(x => (x.id, x)).toMap
 
     def toOutboundShrineQuery(queryRecord: ShrineQueryRecord): OutboundShrineQuery = {
       val topic = topicIdsToTopics.get(queryRecord.topicId)
       val outboundTopic: Option[OutboundTopic] = topic.map(_.toOutboundTopic(userNamesToOutboundUsers))
 
       val outboundUserOption = userNamesToOutboundUsers.get(queryRecord.userId)
       //todo if a user is unknown and the system is in a mode that requires everyone to log into the data steward notify the data steward
       val outboundUser: OutboundUser = outboundUserOption.getOrElse(OutboundUser.createUnknownUser(queryRecord.userId))
 
       queryRecord.createOutboundShrineQuery(outboundTopic, outboundUser)
     }
 
-    val result = QueryHistory(count,queryParameters.skipOption.getOrElse(0),shrineQueries.map(toOutboundShrineQuery))
-    result
+    QueryHistory(count, queryParameters.skipOption.getOrElse(0), shrineQueries.map(toOutboundShrineQuery))
   }
 
   private def outboundUsersForNamesAction(userNames:Set[UserName]):DBIOAction[Map[UserName, OutboundUser], NoStream, Read] = {
     allUserQuery.filter(_.userName.inSet(userNames)).result.map(_.map(x => (x.userName,x.asOutboundUser)).toMap)
   }
 
   private def shrineQuerySelectQuery(queryParameters: QueryParameters,topicParameter:Option[TopicId]):Query[QueryTable, QueryTable#TableElementType, Seq] = {
     val countQuery = shrineQueryCountQuery(queryParameters,topicParameter)
 
     //todo is there some way to do something with a map from column names to columns that I don't have to update? I couldn't find one.
     //    val orderByQuery = queryParameters.sortByOption.fold(limitFilter)(
     //      columnName => limitFilter.sortBy(x => queryParameters.sortOrder.orderForColumn(allQueryTable.columnForName(columnName))))
     val orderByQuery = queryParameters.sortByOption.fold(countQuery) {
       case "topicName" =>
         val joined = countQuery.join(mostRecentTopicQuery).on(_.topicId === _.id)
         joined.sortBy(x => queryParameters.sortOrder.orderForColumn(x._2.name)).map(x => x._1)
       case columnName => countQuery.sortBy(x => queryParameters.sortOrder.orderForColumn(columnName match {
         case "stewardId" => x.stewardId
         case "externalId" => x.externalId
         case "researcherId" => x.researcherId
         case "name" => x.name
         case "topic" => x.topicId
         case "queryContents" => x.queryContents
         case "stewardResponse" => x.stewardResponse
         case "date" => x.date
       }))
     }
 
     val skipFilter = queryParameters.skipOption.fold(orderByQuery)(skip => orderByQuery.drop(skip))
     val limitFilter = queryParameters.limitOption.fold(skipFilter)(limit => skipFilter.take(limit))
 
     limitFilter
   }
 
   private def shrineQueryCountQuery(queryParameters: QueryParameters,topicParameter:Option[TopicId]):Query[QueryTable, QueryTable#TableElementType, Seq] = {
 
     val allShrineQueries:Query[QueryTable, QueryTable#TableElementType, Seq] = allQueryTable
 
     val topicFilter:Query[QueryTable, QueryTable#TableElementType, Seq] = topicParameter.fold(allShrineQueries)(topicId => allShrineQueries.filter(_.topicId === topicId))
 
     val researcherFilter:Query[QueryTable, QueryTable#TableElementType, Seq] = queryParameters.researcherIdOption.fold(topicFilter)(researcherId => topicFilter.filter(_.researcherId === researcherId))
     //todo this is probably a binary Approved/Not approved
     val stateFilter:Query[QueryTable, QueryTable#TableElementType, Seq] = queryParameters.stateOption.fold(researcherFilter)(stewardResponse => researcherFilter.filter(_.stewardResponse === stewardResponse.name))
 
     val minDateFilter = queryParameters.minDate.fold(stateFilter)(minDate => stateFilter.filter(_.date >= minDate))
     val maxDateFilter = queryParameters.maxDate.fold(minDateFilter)(maxDate => minDateFilter.filter(_.date <= maxDate))
 
     maxDateFilter
   }
 
   def selectShrineQueryCountsPerUser(queryParameters: QueryParameters):QueriesPerUser = {
 
     val (totalQueries,queriesPerUser,userNamesToOutboundUsers) = dbRun(for {
       totalQueries <- shrineQueryCountQuery(queryParameters,None).length.result
       queriesPerUser <- shrineQueryCountsPerResearcher(queryParameters).result
       userNamesToOutboundUsers <- outboundUsersForNamesAction(queriesPerUser.map(x => x._1).to[Set])
     } yield (totalQueries,queriesPerUser,userNamesToOutboundUsers))
 
     val queriesPerOutboundUser:Seq[(OutboundUser,Int)] = queriesPerUser.map(x => (userNamesToOutboundUsers(x._1),x._2))
 
     QueriesPerUser(totalQueries,queriesPerOutboundUser)
   }
 
   private def shrineQueryCountsPerResearcher(queryParameters: QueryParameters): Query[(Rep[UserName],Rep[Int]),(UserName,Int),Seq] = {
     val filteredShrineQueries:Query[QueryTable, QueryTable#TableElementType, Seq] = shrineQueryCountQuery(queryParameters,None)
     val groupedByResearcher = filteredShrineQueries.groupBy(shrineQuery => shrineQuery.researcherId)
     groupedByResearcher.map{case (researcher,result) => (researcher,result.length)}
   }
 
   lazy val nextTopicId:AtomicInteger = new AtomicInteger({
     dbRun(allTopicQuery.map(_.id).max.result).getOrElse(0) + 1
   })
 
   def selectAllAuditRequests: Seq[UserAuditRecord] = {
     dbRun(allUserAudits.result)
   }
 
   def selectMostRecentAuditRequests: Seq[UserAuditRecord] = {
     dbRun(mostRecentUserAudits.result)
   }
 
   def selectResearchersToAudit(maxQueryCountBetweenAudits:Int,minTimeBetweenAudits:Duration,now:Date):Seq[ResearcherToAudit] = {
 
     //todo one round with the db instead of O(researchers)
 
     //for each researcher
     //horizon = if the researcher has had an audit
     //                date of last audit
     //             else if no audit yet
     //                date of first query
     val researchersToHorizons: Map[UserName, Date] = dbRun(for{
       dateOfFirstQuery: Seq[(UserName, Date)] <- leastRecentUserQuery.map(record => record.researcherId -> record.date).result
       mostRecentAudit: Seq[(UserName, Date)] <- mostRecentUserAudits.map(record => record.researcher -> record.changeDate).result
     } yield {
       dateOfFirstQuery.toMap ++ mostRecentAudit.toMap
     })
 
     val researchersToHorizonsAndCounts = researchersToHorizons.map{ researcherDate =>
 
       val queryParameters = QueryParameters(researcherIdOption = Some(researcherDate._1),
                                             minDate = Some(researcherDate._2))
 
       val count:Int = dbRun(shrineQueryCountQuery(queryParameters,None).length.result)
       (researcherDate._1,(researcherDate._2,count))
     }
 
     //audit if oldest query within the horizon is >= minTimeBetweenAudits in the past and the researcher has run at least one query since.
     val oldestAllowed = System.currentTimeMillis() - minTimeBetweenAudits.toMillis
     val timeBasedAudit = researchersToHorizonsAndCounts.filter(x => x._2._2 > 0 && x._2._1 <= oldestAllowed)
 
     //audit if the researcher has run >= maxQueryCountBetweenAudits queries since horizon?
     val queryBasedAudit = researchersToHorizonsAndCounts.filter(x => x._2._2 >= maxQueryCountBetweenAudits)
 
     val toAudit = timeBasedAudit ++ queryBasedAudit
 
     val namesToOutboundUsers: Map[UserName, OutboundUser] = dbRun(outboundUsersForNamesAction(toAudit.keySet))
 
     toAudit.map(x => ResearcherToAudit(namesToOutboundUsers(x._1),x._2._2,x._2._1,now)).to[Seq]
   }
 
   def logAuditRequests(auditRequests:Seq[ResearcherToAudit],now:Date) {
     dbRun{
       allUserAudits ++= auditRequests.map(x => UserAuditRecord(researcher = x.researcher.userName,
                                                                 queryCount = x.count,
                                                                 changeDate = now
                                                               ))
     }
   }
 
 }
 
 /**
  * Separate class to support schema generation without actually connecting to the database.
  *
  * @param jdbcProfile Database profile to use for the schema
  */
 case class StewardSchema(jdbcProfile: JdbcProfile) extends Loggable {
   import jdbcProfile.api._
 
   def ddlForAllTables = {
     allUserQuery.schema ++ allTopicQuery.schema ++ allQueryTable.schema ++ allUserTopicQuery.schema ++ allUserAudits.schema
   }
 
   //to get the schema, use the REPL
   //println(StewardSchema.schema.ddlForAllTables.createStatements.mkString(";\n"))
 
   def createTables(database:Database) = {
     try {
       val future = database.run(ddlForAllTables.create)
       Await.result(future,10 seconds)
     } catch {
       //I'd prefer to check and create schema only if absent. No way to do that with Oracle.
       case x:SQLException => info("Caught exception while creating tables. Recover by assuming the tables already exist.",x)
     }
   }
 
   def dropTables(database:Database) = {
     val future = database.run(ddlForAllTables.drop)
     //Really wait forever for the cleanup
     Await.result(future,Duration.Inf)
   }
 
   class UserTable(tag:Tag) extends Table[UserRecord](tag,"users") {
     def userName = column[UserName]("userName",O.PrimaryKey)
     def fullName = column[String]("fullName")
     def isSteward = column[Boolean]("isSteward")
 
     def * = (userName,fullName,isSteward) <> (UserRecord.tupled,UserRecord.unapply)
   }
 
   class TopicTable(tag:Tag) extends Table[TopicRecord](tag,"topics") {
     def id = column[TopicId]("id")
     def name = column[String]("name")
     def description = column[String]("description")
     def createdBy = column[UserName]("createdBy")
     def createDate = column[Date]("createDate")
     def state = column[TopicStateName]("state")
     def changedBy = column[UserName]("changedBy")
     def changeDate = column[Date]("changeDate")
 
     def idIndex = index("idIndex",id,unique = false)
     def topicNameIndex = index("topicNameIndex",name,unique = false)
     def createdByIndex = index("createdByIndex",createdBy,unique = false)
     def createDateIndex = index("createDateIndex",createDate,unique = false)
     def stateIndex = index("stateIndex",state,unique = false)
     def changedByIndex = index("changedByIndex",changedBy,unique = false)
     def changeDateIndex = index("changeDateIndex",changeDate,unique = false)
 
     def * = (id.?, name, description, createdBy, createDate, state, changedBy, changeDate) <> (fromRow, toRow) //(TopicRecord.tupled,TopicRecord.unapply)
 
     def fromRow = (fromParams _).tupled
 
     def fromParams(id:Option[TopicId] = None,
                    name:String,
                    description:String,
                    createdBy:UserName,
                    createDate:Date,
                    stateName:String,
                    changedBy:UserName,
                    changeDate:Date): TopicRecord = {
       TopicRecord(id, name, description, createdBy, createDate, TopicState.namesToStates(stateName), changedBy, changeDate)
     }
 
     def toRow(topicRecord: TopicRecord) =
       Some((topicRecord.id,
         topicRecord.name,
         topicRecord.description,
         topicRecord.createdBy,
         topicRecord.createDate,
         topicRecord.state.name,
         topicRecord.changedBy,
         topicRecord.changeDate
         ))
   }
 
   class UserTopicTable(tag:Tag) extends Table[UserTopicRecord](tag,"userTopic") {
     def researcher = column[UserName]("researcher")
     def topicId = column[TopicId]("topicId")
     def state = column[TopicStateName]("state")
     def changedBy = column[UserName]("changedBy")
     def changeDate = column[Date]("changeDate")
 
     def researcherTopicIdIndex = index("researcherTopicIdIndex",(researcher,topicId),unique = true)
 
     def * = (researcher, topicId, state, changedBy, changeDate) <> (fromRow, toRow)
 
     def fromRow = (fromParams _).tupled
 
     def fromParams(researcher:UserName,
                    topicId:TopicId,
                    stateName:String,
                    changedBy:UserName,
                    changeDate:Date): UserTopicRecord = {
       UserTopicRecord(researcher,topicId,TopicState.namesToStates(stateName), changedBy, changeDate)
     }
 
     def toRow(userTopicRecord: UserTopicRecord):Option[(UserName,TopicId,String,UserName,Date)] =
       Some((userTopicRecord.researcher,
         userTopicRecord.topicId,
         userTopicRecord.state.name,
         userTopicRecord.changedBy,
         userTopicRecord.changeDate
         ))
   }
 
   class UserAuditTable(tag:Tag) extends Table[UserAuditRecord](tag,"userAudit") {
     def researcher = column[UserName]("researcher")
     def queryCount = column[Int]("queryCount")
     def changeDate = column[Date]("changeDate")
 
     def * = (researcher, queryCount, changeDate) <> (fromRow, toRow)
 
     def fromRow = (fromParams _).tupled
 
     def fromParams(researcher:UserName,
                    queryCount:Int,
                    changeDate:Date): UserAuditRecord = {
       UserAuditRecord(researcher,queryCount, changeDate)
     }
 
     def toRow(record: UserAuditRecord):Option[(UserName,Int,Date)] =
       Some((record.researcher,
         record.queryCount,
         record.changeDate
         ))
   }
 
   class QueryTable(tag:Tag) extends Table[ShrineQueryRecord](tag,"queries") {
     def stewardId = column[StewardQueryId]("stewardId",O.PrimaryKey,O.AutoInc)
     def externalId = column[ExternalQueryId]("id")
     def name = column[String]("name")
     def researcherId = column[UserName]("researcher")
     def topicId = column[Option[TopicId]]("topic")
     def queryContents = column[QueryContents]("queryContents")
     def stewardResponse = column[String]("stewardResponse")
     def date = column[Date]("date")
 
     def externalIdIndex = index("externalIdIndex",externalId,unique = false)
     def queryNameIndex = index("queryNameIndex",name,unique = false)
     def researcherIdIndex = index("researcherIdIndex",stewardId,unique = false)
     def topicIdIndex = index("topicIdIndex",topicId,unique = false)
     def stewardResponseIndex = index("stewardResponseIndex",stewardResponse,unique = false)
     def dateIndex = index("dateIndex",date,unique = false)
 
     def * = (stewardId.?,externalId,name,researcherId,topicId,queryContents,stewardResponse,date) <> (fromRow,toRow)
 
     def fromRow = (fromParams _).tupled
 
     def fromParams(stewardId:Option[StewardQueryId],
                    externalId:ExternalQueryId,
                    name:String,
                    userId:UserName,
                    topicId:Option[TopicId],
                    queryContents: QueryContents,
                    stewardResponse:String,
                    date:Date): ShrineQueryRecord = {
       ShrineQueryRecord(stewardId,externalId, name, userId, topicId, queryContents,TopicState.namesToStates(stewardResponse),date)
     }
     def toRow(queryRecord: ShrineQueryRecord):Option[(
       Option[StewardQueryId],
         ExternalQueryId,
         String,
         UserName,
         Option[TopicId],
         QueryContents,
         String,
         Date
       )] =
       Some((queryRecord.stewardId,
         queryRecord.externalId,
         queryRecord.name,
         queryRecord.userId,
         queryRecord.topicId,
         queryRecord.queryContents,
         queryRecord.stewardResponse.name,
         queryRecord.date)
       )
   }
 
   val allUserQuery = TableQuery[UserTable]
   val allTopicQuery = TableQuery[TopicTable]
   val allQueryTable = TableQuery[QueryTable]
   val allUserTopicQuery = TableQuery[UserTopicTable]
   val allUserAudits = TableQuery[UserAuditTable]
 
   val mostRecentTopicQuery: Query[TopicTable, TopicRecord, Seq] = for(
     topic <- allTopicQuery if !allTopicQuery.filter(_.id === topic.id).filter(_.changeDate > topic.changeDate).exists
   ) yield topic
 
   val mostRecentUserAudits: Query[UserAuditTable, UserAuditRecord, Seq] = for(
     record <- allUserAudits if !allUserAudits.filter(_.researcher === record.researcher).filter(_.changeDate > record.changeDate).exists
   ) yield record
 
   val leastRecentUserQuery: Query[QueryTable, ShrineQueryRecord, Seq] = for(
     record <- allQueryTable if !allQueryTable.filter(_.researcherId === record.researcherId).filter(_.date < record.date).exists
   ) yield record
 
 }
 
 object StewardSchema {
 
   val allConfig:Config = ConfigSource.config
   val config:Config = allConfig.getConfig("shrine.steward.database")
 
   val slickProfile:JdbcProfile = ConfigSource.getObject("slickProfileClassName", config)
 
   val schema = StewardSchema(slickProfile)
 }
 
 object StewardDatabase extends NeedsWarmUp {
 
   val dataSource:DataSource = TestableDataSourceCreator.dataSource(StewardSchema.config)
 
   val db = StewardDatabase(StewardSchema.schema,dataSource)
 
   val createTablesOnStart = StewardSchema.config.getBoolean("createTablesOnStart")
   if(createTablesOnStart) StewardDatabase.db.createTables()
 
   override def warmUp() = StewardDatabase.db.warmUp
 }
 
 //API help
 
 sealed case class SortOrder(name:String){
   import slick.lifted.ColumnOrdered
 
   def orderForColumn[T](column:ColumnOrdered[T]):ColumnOrdered[T] = {
     if(this == SortOrder.ascending) column.asc
     else column.desc
   }
 }
 
 object SortOrder {
   val ascending = SortOrder("ascending")
   val descending = SortOrder("descending")
 
   val sortOrders = Seq(ascending,descending)
 
   val namesToSortOrders = sortOrders.map(x => (x.name,x)).toMap
 
   def sortOrderForStringOption(option:Option[String]) = option.fold(ascending)(namesToSortOrders(_))
 }
 
 case class QueryParameters(researcherIdOption:Option[UserName] = None,
                             stateOption:Option[TopicState] =  None,
                             skipOption:Option[Int] =  None,
                             limitOption:Option[Int] = None,
                             sortByOption:Option[String] = None,
                             sortOrder:SortOrder = SortOrder.ascending,
                             minDate:Option[Date] = None,
                             maxDate:Option[Date] = None
                           )
 
 //DAO case classes, exposed for testing only
 case class ShrineQueryRecord(stewardId: Option[StewardQueryId],
                              externalId:ExternalQueryId,
                              name:String,
                              userId:UserName,
                              topicId:Option[TopicId],
                              queryContents: QueryContents,
                              stewardResponse:TopicState,
                              date:Date) {
   def createOutboundShrineQuery(outboundTopic:Option[OutboundTopic],outboundUser:OutboundUser): OutboundShrineQuery = {
     OutboundShrineQuery(stewardId.get,externalId,name,outboundUser,outboundTopic,queryContents,stewardResponse.name,date)
   }
 }
 
 object ShrineQueryRecord extends ((Option[StewardQueryId],ExternalQueryId,String,UserName,Option[TopicId],QueryContents,TopicState,Date) => ShrineQueryRecord) {
   def apply(userId:UserName,topicId:Option[TopicId],shrineQuery: InboundShrineQuery,stewardResponse:TopicState): ShrineQueryRecord = {
     ShrineQueryRecord(
       None,
       shrineQuery.externalId,
       shrineQuery.name,
       userId,
       topicId,
       shrineQuery.queryContents,
       stewardResponse,
       System.currentTimeMillis())
   }
 }
 
 case class UserRecord(userName:UserName,fullName:String,isSteward:Boolean) {
 
   lazy val asOutboundUser:OutboundUser = OutboundUser(userName,fullName,if(isSteward) Set(stewardRole,researcherRole)
                                                                         else Set(researcherRole))
 }
 
 object UserRecord extends ((UserName,String,Boolean) => UserRecord) {
 
   def apply(user:User):UserRecord = UserRecord(user.username,user.fullName,user.params.toList.contains((stewardRole,"true")))
 
 }
 
 case class TopicRecord(id:Option[TopicId] = None,
                         name:String,
                         description:String,
                         createdBy:UserName,
                         createDate:Date,
                         state:TopicState,
                         changedBy:UserName,
                         changeDate:Date) {
 
   def toOutboundTopic(userNamesToOutboundUsers: Map[UserName, OutboundUser]): OutboundTopic = {
     OutboundTopic(id.get,
       name,
       description,
       userNamesToOutboundUsers(createdBy),
       createDate,
       state.name,
       userNamesToOutboundUsers(changedBy),
       changeDate)
   }
 }
 
 
 object TopicRecord {
   def apply(id:Option[TopicId],
             name:String,
             description:String,
             createdBy:UserName,
             createDate:Date,
             state:TopicState
              ):TopicRecord = TopicRecord(id,
                                           name,
                                           description,
                                           createdBy,
                                           createDate,
                                           state,
                                           createdBy,
                                           createDate)
 }
 
 case class UserTopicRecord(researcher:UserName,
                             topicId:TopicId,
                             state:TopicState,
                             changedBy:UserName,
                             changeDate:Date)
 
 case class UserAuditRecord(researcher:UserName,
                            queryCount:Int,
                            changeDate:Date) {
   def sameExceptForTimes(userAuditRecord: UserAuditRecord):Boolean = {
     (researcher == userAuditRecord.researcher) &&
       (queryCount == userAuditRecord.queryCount)
   }
 }
 
 case class TopicDoesNotExist(topicId:TopicId) extends IllegalArgumentException(s"No topic for id $topicId")
 
 case class ApprovedTopicCanNotBeChanged(topicId:TopicId) extends IllegalStateException(s"Topic $topicId has been ${TopicState.approved}")
 
 case class DetectedAttemptByWrongUserToChangeTopic(topicId:TopicId,userId:UserName,ownerId:UserName) extends IllegalArgumentException(s"$userId does not own $topicId; $ownerId owns it.")
\ No newline at end of file
diff --git a/commons/protocol/src/main/scala/net/shrine/i2b2/protocol/pm/HiveConfig.scala b/commons/protocol/src/main/scala/net/shrine/i2b2/protocol/pm/HiveConfig.scala
index ed5ef77eb..e4f59f054 100644
--- a/commons/protocol/src/main/scala/net/shrine/i2b2/protocol/pm/HiveConfig.scala
+++ b/commons/protocol/src/main/scala/net/shrine/i2b2/protocol/pm/HiveConfig.scala
@@ -1,43 +1,44 @@
 package net.shrine.i2b2.protocol.pm
 
 import net.shrine.util.XmlUtil
 import net.shrine.serialization.{ I2b2Unmarshaller, XmlMarshaller }
 import xml.NodeSeq
 
 /**
  * @author Bill Simons
  * @date 3/5/12
  * @link http://cbmi.med.harvard.edu
  * @link http://chip.org
  *       <p/>
  *       NOTICE: This software comes with NO guarantees whatsoever and is
  *       licensed as Lgpl Open Source
  * @link http://www.gnu.org/licenses/lgpl.html
  */
+//TODO: Do we use this anymore? If it's only in happy, it can be removed
 final case class HiveConfig(val crcUrl: String, val ontologyUrl: String) extends XmlMarshaller {
   override def toXml = XmlUtil.stripWhitespace {
     <hiveConfig>
       <crcUrl>{ crcUrl }</crcUrl>
       <ontUrl>{ ontologyUrl }</ontUrl>
     </hiveConfig>
   }
 }
 
 object HiveConfig extends I2b2Unmarshaller[HiveConfig] {
   override def fromI2b2(nodeSeq: NodeSeq) = {
     val cellDataSeq = nodeSeq \ "message_body" \ "configure" \ "cell_datas" \ "cell_data"
     
     def hasId(id: String): NodeSeq => Boolean = {
       xml => (xml \\ "@id").text == id
     }
     
     def findUrlById(id: String): String = (cellDataSeq.find(hasId(id)).toSeq \ "url").text
     
     //TODO review for error handling - if given ID isn't found, urls will be empty strings. Should we fail loudly instead?
     val crcUrl = findUrlById("CRC")
     
     val ontUrl = findUrlById("ONT")
 
     HiveConfig(crcUrl, ontUrl)
   }
 }
\ No newline at end of file
diff --git a/commons/util/src/test/resources/shrine.conf b/commons/util/src/test/resources/shrine.conf
index a6c6913d3..6ab34572d 100644
--- a/commons/util/src/test/resources/shrine.conf
+++ b/commons/util/src/test/resources/shrine.conf
@@ -1,17 +1,20 @@
 shrine{
+  problem {
+    problemHandler = "net.shrine.problem.DatabaseProblemHandler$"
+  }
   dashboard {
     database {
       dataSourceFrom = "testDataSource"
       slickProfileClassName = "slick.driver.H2Driver$"
       createTablesOnStart = true
       // For testing without JNDI
       testDataSource {
 
         //typical test settings for unit tests
         driverClassName = "org.h2.Driver"
 
         url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"
       }
     }
   }
 }
\ No newline at end of file