diff --git a/adapter/adapter-service/src/main/resources/adapter-drop-1.13-tables.sql b/adapter/adapter-service/src/main/resources/adapter-drop-1.13-tables.sql
deleted file mode 100644
index 28613ed7c..000000000
--- a/adapter/adapter-service/src/main/resources/adapter-drop-1.13-tables.sql
+++ /dev/null
@@ -1,5 +0,0 @@
-drop table MASTER_QUERY;
-drop table INSTANCE_IDS;
-drop table RESULT_IDS;
-drop table REQUEST_RESPONSE_DATA;
-drop table USERS_TO_MASTER_QUERY;
diff --git a/adapter/adapter-service/src/main/resources/adapter-drop.sql b/adapter/adapter-service/src/main/resources/adapter-drop.sql
deleted file mode 100644
index 028944be4..000000000
--- a/adapter/adapter-service/src/main/resources/adapter-drop.sql
+++ /dev/null
@@ -1,7 +0,0 @@
-drop table if exists PRIVILEGED_USER;
-drop table if exists ERROR_RESULT;
-drop table if exists COUNT_RESULT;
-drop table if exists BREAKDOWN_RESULT;
-drop table if exists PATIENT_SET;
-drop table if exists QUERY_RESULT;
-drop table if exists SHRINE_QUERY;
diff --git a/adapter/adapter-service/src/main/resources/adapter-migrate-data-to-1.14-tables.sql b/adapter/adapter-service/src/main/resources/adapter-migrate-data-to-1.14-tables.sql
deleted file mode 100644
index 7d0f96c4f..000000000
--- a/adapter/adapter-service/src/main/resources/adapter-migrate-data-to-1.14-tables.sql
+++ /dev/null
@@ -1,30 +0,0 @@
-alter table REQUEST_RESPONSE_DATA add index `IDX_REQUEST_RESPONSE_DATA_BROADCAST_QUERY_MASTER_ID`(BROADCAST_QUERY_MASTER_ID);
-alter table SHRINE_QUERY add index `IDX_SHRINE_QUERY_NETWORK_ID`(network_id);
-alter table RESULT_IDS add index `IDX_RESULT_IDS_BROADCAST_RESULT_INSTANCE_ID`(BROADCAST_RESULT_INSTANCE_ID);
-
-insert into
- SHRINE_QUERY (local_id, network_id, username, domain, query_name, query_expression, date_created)
-(select
- mq.LOCAL_QUERY_MASTER_ID, mq.BROADCAST_QUERY_MASTER_ID, USERNAME, DOMAIN_NAME, MASTER_NAME, QUERY_DEFINITION, TIMESTAMP(utmq.MASTER_CREATE_DATE)
- from
- USERS_TO_MASTER_QUERY utmq inner join
- MASTER_QUERY mq on utmq.BROADCAST_QUERY_MASTER_ID = mq.BROADCAST_QUERY_MASTER_ID);
-
-insert into
- QUERY_RESULT (local_id, query_id, type, status, time_elapsed, last_updated)
-(select
- rids.LOCAL_RESULT_INSTANCE_ID, sq.id, 'PATIENT_COUNT_XML', 'FINISHED', rrd.TIME_ELAPSED, sq.date_created
- from
- REQUEST_RESPONSE_DATA rrd inner join
- RESULT_IDS rids on rrd.BROADCAST_RESULT_INSTANCE_ID = rids.BROADCAST_RESULT_INSTANCE_ID inner join
- SHRINE_QUERY sq on rrd.BROADCAST_QUERY_MASTER_ID = sq.network_id);
-
-insert into
- COUNT_RESULT (result_id, original_count, obfuscated_count, date_created)
-(select
- qr.id, rrd.RESULT_SET_SIZE, rrd.RESULT_SET_SIZE + rids.OBFUSCATION_AMOUNT, sq.date_created
- from
- SHRINE_QUERY sq inner join
- QUERY_RESULT qr on sq.id = qr.query_id inner join
- REQUEST_RESPONSE_DATA rrd on rrd.BROADCAST_QUERY_MASTER_ID = sq.network_id inner join
- RESULT_IDS rids on rrd.BROADCAST_RESULT_INSTANCE_ID = rids.BROADCAST_RESULT_INSTANCE_ID);
diff --git a/adapter/adapter-service/src/main/resources/adapter-migrate-schema-to-1.14.sql b/adapter/adapter-service/src/main/resources/adapter-migrate-schema-to-1.14.sql
deleted file mode 100644
index 526071e4f..000000000
--- a/adapter/adapter-service/src/main/resources/adapter-migrate-schema-to-1.14.sql
+++ /dev/null
@@ -1,55 +0,0 @@
--- Create everything except PRIVILEGED_USER, which will already exist.
-
-create table SHRINE_QUERY(
- id int not null auto_increment,
- local_id varchar(255) not null,
- network_id bigint not null,
- username varchar(255) not null,
- domain varchar(255) not null,
- query_name varchar(255) not null,
- query_expression text not null,
- date_created timestamp default current_timestamp,
- constraint query_id_pk primary key(id),
- index ix_SHRINE_QUERY_username_domain using (username, domain)
-) engine=innodb default charset=latin1;
-
-create table QUERY_RESULT(
- id int not null auto_increment,
- local_id varchar(255) not null,
- query_id int not null,
- type enum('PATIENTSET','PATIENT_COUNT_XML','PATIENT_AGE_COUNT_XML','PATIENT_RACE_COUNT_XML','PATIENT_VITALSTATUS_COUNT_XML','PATIENT_GENDER_COUNT_XML','ERROR') not null,
- status enum('FINISHED', 'ERROR', 'PROCESSING', 'QUEUED') not null,
- time_elapsed int null,
- last_updated timestamp default current_timestamp,
- constraint QUERY_RESULT_id_pk primary key(id),
- constraint fk_QUERY_RESULT_query_id foreign key (query_id) references SHRINE_QUERY (id) on delete cascade
-) engine=innodb default charset=latin1;
-
-create table ERROR_RESULT(
- id int not null auto_increment,
- result_id int not null,
- message varchar(255) not null,
- constraint ERROR_RESULT_id_pk primary key(id),
- constraint fk_ERROR_RESULT_QUERY_RESULT_id foreign key (result_id) references QUERY_RESULT (id) on delete cascade
-) engine=innodb default charset=latin1;
-
-create table COUNT_RESULT(
- id int not null auto_increment,
- result_id int not null,
- original_count int not null,
- obfuscated_count int not null,
- date_created timestamp default current_timestamp,
- constraint COUNT_RESULT_id_pk primary key(id),
- constraint fk_COUNT_RESULT_QUERY_RESULT_id foreign key (result_id) references QUERY_RESULT (id) on delete cascade
-) engine=innodb default charset=latin1;
-
-create table BREAKDOWN_RESULT(
- id int not null auto_increment,
- result_id int not null,
- data_key varchar(255) not null,
- original_value int not null,
- obfuscated_value int not null,
- constraint BREAKDOWN_RESULT_id_pk primary key(id),
- constraint fk_BREAKDOWN_RESULT_QUERY_RESULT_id foreign key (result_id) references QUERY_RESULT (id) on delete cascade
-) engine=innodb default charset=latin1;
-
diff --git a/adapter/adapter-service/src/main/resources/adapter-migrate-schema-to-1.16.sql b/adapter/adapter-service/src/main/resources/adapter-migrate-schema-to-1.16.sql
deleted file mode 100644
index 65d46c3af..000000000
--- a/adapter/adapter-service/src/main/resources/adapter-migrate-schema-to-1.16.sql
+++ /dev/null
@@ -1,10 +0,0 @@
-alter table SHRINE_QUERY add flagged boolean not null default 0;
-alter table SHRINE_QUERY add has_been_run boolean not null default 0;
-
-alter table SHRINE_QUERY add index ix_SHRINE_QUERY_network_id (network_id);
-alter table SHRINE_QUERY add index ix_SHRINE_QUERY_local_id (local_id);
-
--- mark all queries fom before now as having been run
-update SHRINE_QUERY set has_been_run = 1;
-
-alter table SHRINE_QUERY add flag_message varchar(255) null;
diff --git a/adapter/adapter-service/src/main/resources/adapter-migrate-schema-to-1.18.sql b/adapter/adapter-service/src/main/resources/adapter-migrate-schema-to-1.18.sql
deleted file mode 100644
index 602d44e3d..000000000
--- a/adapter/adapter-service/src/main/resources/adapter-migrate-schema-to-1.18.sql
+++ /dev/null
@@ -1,3 +0,0 @@
-alter table SHRINE_QUERY modify query_expression text null;
-alter table SHRINE_QUERY add query_xml text null;
-
diff --git a/adapter/adapter-service/src/main/resources/adapter-oracle-drop.sql b/adapter/adapter-service/src/main/resources/adapter-oracle-drop.sql
deleted file mode 100644
index c8736da83..000000000
--- a/adapter/adapter-service/src/main/resources/adapter-oracle-drop.sql
+++ /dev/null
@@ -1,47 +0,0 @@
-
-drop trigger error_result_insert_add_id;
-
-drop sequence seq_error_result_id;
-
-drop table error_result;
-
-drop trigger count_result_insert_add_id;
-
-drop sequence seq_count_result_id;
-
-drop table count_result;
-
-drop trigger breakdown_result_insert_add_id;
-
-drop sequence seq_breakdown_result_id;
-
-drop table breakdown_result;
-
-drop trigger patient_set_insert_add_id;
-
-drop sequence seq_patient_set_id;
-
-drop table patient_set;
-
-drop trigger query_result_insert_add_id;
-
-drop sequence seq_query_result_id;
-
-drop table query_result;
-
-drop trigger shrine_query_insert_add_id;
-
-drop sequence seq_shrine_query_id;
-
-drop table shrine_query;
-
-drop trigger privileged_user_insert_add_id;
-
-drop sequence seq_privileged_user_id;
-
-drop table privileged_user;
-
-
-
-
-
diff --git a/adapter/adapter-service/src/main/resources/adapter-oracle.sql b/adapter/adapter-service/src/main/resources/adapter-oracle.sql
deleted file mode 100644
index 1f84f3993..000000000
--- a/adapter/adapter-service/src/main/resources/adapter-oracle.sql
+++ /dev/null
@@ -1,151 +0,0 @@
-create table shrine_query(
- id int not null,
- local_id varchar(255) not null,
- network_id int not null,
- username varchar(255) not null,
- domain varchar(255) not null,
- query_definition CLOB not null,
- date_created timestamp default systimestamp,
- constraint query_id_pk primary key(id)
-);
-
-create sequence seq_shrine_query_id
-minvalue 1
-start with 1
-increment by 1;
-
-create table query_result(
- id int not null,
- local_id varchar(255) not null,
- query_id int not null,
- type varchar(255) not null check (type in ('PATIENTSET','PATIENT_COUNT_XML','PATIENT_AGE_COUNT_XML','PATIENT_RACE_COUNT_XML','PATIENT_VITALSTATUS_COUNT_XML','PATIENT_GENDER_COUNT_XML','ERROR')),
- status varchar(255) not null check (status in ('FINISHED', 'ERROR', 'PROCESSING', 'QUEUED')),
- time_elapsed int not null,
- last_updated timestamp default systimestamp,
- constraint query_result_id_pk primary key(id),
- constraint fk_query_result_query_id foreign key (query_id) references shrine_query (id)
-);
-
-create sequence seq_query_result_id
-minvalue 1
-start with 1
-increment by 1;
-
-create table error_result(
- id int not null,
- result_id int not null,
- message varchar(255) not null,
- constraint error_result_id_pk primary key(id),
- constraint fk_error_result_query_id foreign key (result_id) references query_result (id)
-);
-alter table ERROR_RESULT add column 'CODEC' varchar not null default "Pre-1.20 Error";
-alter table ERROR_RESULT add column 'SUMMARY' text not null default "Pre-1.20 Error";
-alter table ERROR_RESULT add column 'DESCRIPTION' text not null default "Pre-1.20 Error";
-alter table ERROR_RESULT add column 'DETAILS' text not null default "Pre-1.20 Error";
-
-
-create sequence seq_error_result_id
-minvalue 1
-start with 1
-increment by 1;
-
-create table count_result(
- id int not null,
- result_id int not null,
- original_count int not null,
- obfuscated_count int not null,
- date_created timestamp default systimestamp,
- constraint count_result_id_pk primary key(id),
- constraint fk_count_result_query_id foreign key (result_id) references query_result (id)
-);
-
-create sequence seq_count_result_id
-minvalue 1
-start with 1
-increment by 1;
-
-create table breakdown_result(
- id int not null,
- result_id int not null,
- data_key varchar(255) not null,
- original_value int not null,
- obfuscated_value int not null,
- constraint breakdown_result_id_pk primary key(id),
- constraint fk_bd_result_query_id foreign key (result_id) references query_result (id)
-);
-
-create sequence seq_breakdown_result_id
-minvalue 1
-start with 1
-increment by 1;
-
-create table patient_set(
- id int not null,
- result_id int not null,
- patient_num varchar(255) not null,
- constraint patient_set_id_pk primary key(id),
- constraint fk_ps_query_id foreign key (result_id) references query_result (id)
-);
-
-create sequence seq_patient_set_id
-minvalue 1
-start with 1
-increment by 1;
-
-create table privileged_user(
- id int not null,
- username varchar(255) not null,
- domain varchar(255) not null,
- threshold int not null,
- override_date timestamp null,
- constraint priviliged_user_pk primary key(id),
- constraint ix_pu_username_domain unique (username, domain)
-);
-
-create sequence seq_privileged_user_id
-minvalue 1
-start with 1
-increment by 1;
-
-create trigger shrine_query_insert_add_id before insert on shrine_query
-for each row
-begin
- select seq_shrine_query_id.NEXTVAL into :NEW.id from dual;
-end;
-/
-create trigger query_result_insert_add_id before insert on query_result
-for each row
-begin
- select seq_query_result_id.NEXTVAL into :NEW.id from dual;
-end;
-/
-create trigger error_result_insert_add_id before insert on error_result
-for each row
-begin
- select seq_error_result_id.NEXTVAL into :NEW.id from dual;
-end;
-/
-create trigger count_result_insert_add_id before insert on count_result
-for each row
-begin
- select seq_count_result_id.NEXTVAL into :NEW.id from dual;
-end;
-/
-create trigger breakdown_result_insert_add_id before insert on breakdown_result
-for each row
-begin
- select seq_breakdown_result_id.NEXTVAL into :NEW.id from dual;
-end;
-/
-create trigger patient_set_insert_add_id before insert on patient_set
-for each row
-begin
- select seq_patient_set_id.NEXTVAL into :NEW.id from dual;
-end;
-/
-create trigger privileged_user_insert_add_id before insert on privileged_user
-for each row
-begin
- select seq_privileged_user_id.NEXTVAL into :NEW.id from dual;
-end;
-/
diff --git a/adapter/adapter-service/src/main/scala/net/shrine/adapter/audit/AdapterAuditDb.scala b/adapter/adapter-service/src/main/scala/net/shrine/adapter/audit/AdapterAuditDb.scala
index 1c041386c..4e8da9ab9 100644
--- a/adapter/adapter-service/src/main/scala/net/shrine/adapter/audit/AdapterAuditDb.scala
+++ b/adapter/adapter-service/src/main/scala/net/shrine/adapter/audit/AdapterAuditDb.scala
@@ -1,366 +1,321 @@
package net.shrine.adapter.audit
-import java.io.PrintWriter
-import java.sql.{DriverManager, Connection, SQLException}
-import java.util.logging.Logger
-import javax.naming.InitialContext
+import java.sql.SQLException
import javax.sql.DataSource
import com.typesafe.config.Config
import net.shrine.adapter.service.AdapterConfigSource
+import net.shrine.audit.{NetworkQueryId, QueryName, QueryTopicId, QueryTopicName, ShrineNodeId, Time, UserName}
import net.shrine.crypto.KeyStoreCertCollection
import net.shrine.log.Loggable
-import net.shrine.audit.{QueryTopicName, QueryTopicId, Time, QueryName, NetworkQueryId, UserName, ShrineNodeId}
import net.shrine.protocol.{BroadcastMessage, RunQueryRequest, RunQueryResponse, ShrineResponse}
-
+import net.shrine.slick.TestableDataSourceCreator
import slick.driver.JdbcProfile
-import scala.concurrent.{Future, Await}
-import scala.concurrent.duration.{Duration,DurationInt}
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.blocking
+
+import scala.concurrent.duration.{Duration, DurationInt}
+import scala.concurrent.{Await, Future, blocking}
import scala.language.postfixOps
/**
* DB code for the Adapter audit metrics.
*
* @author david
* @since 8/25/15
*/
case class AdapterAuditDb(schemaDef:AdapterAuditSchema,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 insertQueryReceived(broadcastMessage: BroadcastMessage):Unit = {
debug(s"insertQueryReceived $broadcastMessage")
QueryReceived.fromBroadcastMessage(broadcastMessage).foreach(insertQueryReceived)
}
def insertQueryReceived(queryReceived:QueryReceived):Unit = {
dbRun(allQueriesReceived += queryReceived)
}
def selectAllQueriesReceived:Seq[QueryReceived] = {
dbRun(allQueriesReceived.result)
}
def insertExecutionStarted(runQueryRequest: RunQueryRequest):Unit = {
debug(s"insertExecutionStarted $runQueryRequest")
insertExecutionStarted(ExecutionStarted.fromRequest(runQueryRequest))
}
def insertExecutionStarted(executionStart:ExecutionStarted):Unit = {
dbRun(allExecutionsStarted += executionStart)
}
def selectAllExecutionStarts:Seq[ExecutionStarted] = {
dbRun(allExecutionsStarted.result)
}
def insertExecutionCompletedShrineResponse(request: RunQueryRequest,shrineResponse: ShrineResponse) = {
debug(s"insertExecutionCompleted $shrineResponse for $request")
ExecutionCompleted.fromRequestResponse(request,shrineResponse).foreach(insertExecutionCompleted)
}
def insertExecutionCompleted(executionCompleted:ExecutionCompleted):Unit = {
dbRun(allExecutionsCompleted += executionCompleted)
}
def selectAllExecutionCompletes:Seq[ExecutionCompleted] = {
dbRun(allExecutionsCompleted.result)
}
def insertResultSent(networkQueryId: NetworkQueryId,shrineResponse:ShrineResponse):Unit = {
debug(s"insertResultSent $shrineResponse for $networkQueryId")
ResultSent.fromResponse(networkQueryId,shrineResponse).foreach(insertResultSent)
}
def insertResultSent(resultSent: ResultSent):Unit = {
dbRun(allResultsSent += resultSent)
}
def selectAllResultsSent:Seq[ResultSent] = {
dbRun(allResultsSent.result)
}
}
/**
* Separate class to support schema generation without actually connecting to the database.
*
* @param jdbcProfile Database profile to use for the schema
*/
case class AdapterAuditSchema(jdbcProfile: JdbcProfile) extends Loggable {
import jdbcProfile.api._
def ddlForAllTables = {
allQueriesReceived.schema ++
allExecutionsStarted.schema ++
allExecutionsCompleted.schema ++
allResultsSent.schema
}
//to get the schema, use the REPL
//println(AdapterAuditSchema.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 QueriesReceivedAuditTable(tag:Tag) extends Table[QueryReceived](tag,"queriesReceived") {
def shrineNodeId = column[ShrineNodeId]("shrineNodeId")
def userName = column[UserName]("userName")
def networkQueryId = column[NetworkQueryId]("networkQueryId")
def queryName = column[QueryName]("queryName")
def queryTopicId = column[Option[QueryTopicId]]("topicId")
def queryTopicName = column[Option[QueryTopicName]]("topicName")
def timeQuerySent = column[Time]("timeQuerySent")
def timeQueryReceived = column[Time]("timeReceived")
def * = (shrineNodeId,userName,networkQueryId,queryName,queryTopicId,queryTopicName,timeQuerySent,timeQueryReceived) <> (QueryReceived.tupled,QueryReceived.unapply)
}
val allQueriesReceived = TableQuery[QueriesReceivedAuditTable]
class ExecutionsStartedTable(tag:Tag) extends Table[ExecutionStarted](tag,"executionsStarted") {
def networkQueryId = column[NetworkQueryId]("networkQueryId")
def queryName = column[QueryName]("queryName")
def timeExecutionStarts = column[Time]("timeExecutionStarted")
def * = (networkQueryId,queryName,timeExecutionStarts) <> (ExecutionStarted.tupled,ExecutionStarted.unapply)
}
val allExecutionsStarted = TableQuery[ExecutionsStartedTable]
class ExecutionsCompletedTable(tag:Tag) extends Table[ExecutionCompleted](tag,"executionsCompleted") {
def networkQueryId = column[NetworkQueryId]("networkQueryId")
def replyId = column[Long]("replyId")
def queryName = column[QueryName]("queryName")
def timeExecutionCompletes = column[Time]("timeExecutionCompleted")
def * = (networkQueryId,replyId,queryName,timeExecutionCompletes) <> (ExecutionCompleted.tupled,ExecutionCompleted.unapply)
}
val allExecutionsCompleted = TableQuery[ExecutionsCompletedTable]
class ResultsSentTable(tag:Tag) extends Table[ResultSent](tag,"resultsSent") {
def networkQueryId = column[NetworkQueryId]("networkQueryId")
def replyId = column[Long]("replyId")
def queryName = column[QueryName]("queryName")
def timeResultsSent = column[Time]("timeResultsSent")
def * = (networkQueryId,replyId,queryName,timeResultsSent) <> (ResultSent.tupled,ResultSent.unapply)
}
val allResultsSent = TableQuery[ResultsSentTable]
}
object AdapterAuditSchema {
val allConfig:Config = AdapterConfigSource.config
val config:Config = allConfig.getConfig("shrine.adapter.audit.database")
val slickProfileClassName = config.getString("slickProfileClassName")
val slickProfile:JdbcProfile = AdapterConfigSource.objectForName(slickProfileClassName)
val schema = AdapterAuditSchema(slickProfile)
}
object AdapterAuditDb {
- val dataSource:DataSource = {
-
- val dataSourceFrom = AdapterAuditSchema.config.getString("dataSourceFrom")
- if(dataSourceFrom == "JNDI") {
- val jndiDataSourceName = AdapterAuditSchema.config.getString("jndiDataSourceName")
- val initialContext:InitialContext = new InitialContext()
-
- initialContext.lookup(jndiDataSourceName).asInstanceOf[DataSource]
-
- }
- else if (dataSourceFrom == "testDataSource") {
-
- val testDataSourceConfig = AdapterAuditSchema.config.getConfig("testDataSource")
- val driverClassName = testDataSourceConfig.getString("driverClassName")
- val url = testDataSourceConfig.getString("url")
-
- //Creating an instance of the driver register it. (!) From a previous epoch, but it works.
- Class.forName(driverClassName).newInstance()
-
- object TestDataSource extends DataSource {
- override def getConnection: Connection = {
- DriverManager.getConnection(url)
- }
-
- override def getConnection(username: String, password: String): Connection = {
- DriverManager.getConnection(url, username, password)
- }
-
- //unused methods
- override def unwrap[T](iface: Class[T]): T = ???
- override def isWrapperFor(iface: Class[_]): Boolean = ???
- override def setLogWriter(out: PrintWriter): Unit = ???
- override def getLoginTimeout: Int = ???
- override def setLoginTimeout(seconds: Int): Unit = ???
- override def getParentLogger: Logger = ???
- override def getLogWriter: PrintWriter = ???
- }
-
- TestDataSource
- }
- else throw new IllegalArgumentException(s"shrine.steward.database.dataSourceFrom must be either JNDI or testDataSource, not $dataSourceFrom")
- }
+ val dataSource:DataSource = TestableDataSourceCreator.dataSource(AdapterAuditSchema.config)
val db = AdapterAuditDb(AdapterAuditSchema.schema,dataSource)
val createTablesOnStart = AdapterAuditSchema.config.getBoolean("createTablesOnStart")
if(createTablesOnStart) AdapterAuditDb.db.createTables()
}
case class QueryReceived(
shrineNodeId:ShrineNodeId,
userName:UserName,
networkQueryId:NetworkQueryId,
queryName:QueryName,
queryTopicId:Option[QueryTopicId],
queryTopicName:Option[QueryTopicName],
timeQuerySent:Time,
timeQueryReceived:Time
)
object QueryReceived extends ((
ShrineNodeId,
UserName,
NetworkQueryId,
QueryName,
Option[QueryTopicId],
Option[QueryTopicName],
Time,
Time
) => QueryReceived) with Loggable {
def fromBroadcastMessage(message:BroadcastMessage):Option[QueryReceived] = {
message.request match {
case rqr:RunQueryRequest =>
val timestampAndShrineNodeCn:(Time,ShrineNodeId) = message.signature.fold{
warn(s"No signature on message ${message.requestId}")
(-1L,"No Cert For Message")}{signature =>
val timesamp = signature.timestamp.toGregorianCalendar.getTimeInMillis
val shrineNodeId:ShrineNodeId = signature.signingCert.fold("Signing Cert Not Available")(x => KeyStoreCertCollection.extractCommonName(x.toCertificate).getOrElse("Common name not in cert"))
(timesamp,shrineNodeId)
}
Some(QueryReceived(timestampAndShrineNodeCn._2,
message.networkAuthn.username,
rqr.networkQueryId,
rqr.queryDefinition.name,
rqr.topicId,
rqr.topicName,
timestampAndShrineNodeCn._1,
System.currentTimeMillis()
))
case _ => None
}
}
}
case class ExecutionStarted(
networkQueryId:NetworkQueryId,
queryName:QueryName,
timeExecutionStarted:Time
)
object ExecutionStarted extends ((
NetworkQueryId,
QueryName,
Time
) => ExecutionStarted){
def fromRequest(rqr:RunQueryRequest) = {
ExecutionStarted(rqr.networkQueryId,
rqr.queryDefinition.name,
System.currentTimeMillis())
}
}
case class ExecutionCompleted(
networkQueryId:NetworkQueryId,
replyId:Long,
queryName:QueryName,
timeExecutionCompleted:Time
)
object ExecutionCompleted extends ((
NetworkQueryId,
Long,
QueryName,
Time
) => ExecutionCompleted){
def fromRequestResponse(request: RunQueryRequest,shrineResponse:ShrineResponse) = {
shrineResponse match {
case rqr:RunQueryResponse => Some(ExecutionCompleted(
request.networkQueryId,
rqr.queryId,
rqr.queryName,
System.currentTimeMillis()))
case _ => None
}
}
}
case class ResultSent(
networkQueryId:NetworkQueryId,
responseId:Long,
queryName:QueryName,
timeResultSent:Time
)
object ResultSent extends ((
NetworkQueryId,
Long,
QueryName,
Time
) => ResultSent){
def fromResponse(networkQueryId:NetworkQueryId,shrineResponse:ShrineResponse) = {
shrineResponse match {
case rqr:RunQueryResponse => Some(ResultSent(
networkQueryId,
rqr.queryId,
rqr.queryName,
System.currentTimeMillis()))
case _ => None
}
}
}
diff --git a/adapter/adapter-service/src/main/scala/net/shrine/adapter/dao/model/ShrineQuery.scala b/adapter/adapter-service/src/main/scala/net/shrine/adapter/dao/model/ShrineQuery.scala
index ee7c233a4..2d167dd0c 100644
--- a/adapter/adapter-service/src/main/scala/net/shrine/adapter/dao/model/ShrineQuery.scala
+++ b/adapter/adapter-service/src/main/scala/net/shrine/adapter/dao/model/ShrineQuery.scala
@@ -1,48 +1,37 @@
package net.shrine.adapter.dao.model
import javax.xml.datatype.XMLGregorianCalendar
import net.shrine.protocol.query.QueryDefinition
import net.shrine.protocol.QueryMaster
-import net.shrine.protocol.query.Expression
-import net.shrine.protocol.query.Term
-import net.shrine.util.XmlDateHelper
-import org.squeryl.annotations.Column
/**
* @author clint
* @since Oct 16, 2012
*
* NB: Can't be final, since Squeryl runs this class through cglib to make a synthetic subclass :(
*/
final case class ShrineQuery(
id: Int,
localId: String,
networkId: Long,
name: String,
username: String,
domain: String,
dateCreated: XMLGregorianCalendar,
isFlagged: Boolean,
hasBeenRun: Boolean,
flagMessage: Option[String],
queryDefinition: QueryDefinition) {
def hasNotBeenRun: Boolean = !hasBeenRun
def withName(newName: String): ShrineQuery = this.copy(name = newName)
- //TODO may not be used
- def withQueryExpr(newQueryExpr: Expression): ShrineQuery = {
- val newQueryDef = queryDefinition.copy(expr = Option(newQueryExpr))
-
- this.copy(queryDefinition = newQueryDef)
- }
-
//NB: Due to the new i2b2 admin previous queries API, we need to be able to transform
//ourselves into a QueryMaster using either the network or local id .
def toQueryMaster(idField: ShrineQuery => String = _.networkId.toString): QueryMaster = {
val held = hasNotBeenRun
QueryMaster(idField(this), networkId, name, username, domain, dateCreated, Some(held), Some(isFlagged), if(isFlagged) flagMessage else None)
}
}
diff --git a/adapter/adapter-service/src/main/resources/adapter.sql b/adapter/adapter-service/src/main/sql/adapter.sql
similarity index 100%
rename from adapter/adapter-service/src/main/resources/adapter.sql
rename to adapter/adapter-service/src/main/sql/adapter.sql
diff --git a/adapter/adapter-service/src/test/resources/adapter-h2.sql b/adapter/adapter-service/src/test/resources/adapter-h2.sql
deleted file mode 100644
index bb898ff9e..000000000
--- a/adapter/adapter-service/src/test/resources/adapter-h2.sql
+++ /dev/null
@@ -1,23 +0,0 @@
-CREATE TABLE "SHRINE_QUERY" ("ID" INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,"LOCAL_ID" VARCHAR NOT NULL,"NETWORK_ID" BIGINT NOT NULL,"QUERY_NAME" VARCHAR NOT NULL,"USERNAME" VARCHAR NOT NULL,"DOMAIN" VARCHAR NOT NULL,"QUERY_EXPRESSION" VARCHAR NOT NULL,"DATE_CREATED" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)
-
-CREATE TABLE "QUERY_RESULT" ("ID" INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,"LOCAL_ID" VARCHAR NOT NULL,"QUERY_ID" INTEGER NOT NULL,"TYPE" VARCHAR NOT NULL,"STATUS" VARCHAR NOT NULL,"TIME_ELAPSED" BIGINT NULL,"LAST_UPDATED" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)
-ALTER TABLE "QUERY_RESULT" ADD CONSTRAINT "QueryResultQueryId_FK" FOREIGN KEY("QUERY_ID") REFERENCES "SHRINE_QUERY"("ID") ON UPDATE NO ACTION ON DELETE CASCADE
-
-CREATE TABLE "COUNT_RESULT" ("ID" INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,"RESULT_ID" INTEGER NOT NULL,"ORIGINAL_COUNT" BIGINT NOT NULL,"OBFUSCATED_COUNT" BIGINT NOT NULL,"DATE_CREATED" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)
-ALTER TABLE "COUNT_RESULT" ADD CONSTRAINT "CountResultResultId_FK" FOREIGN KEY("RESULT_ID") REFERENCES "QUERY_RESULT"("ID") ON UPDATE NO ACTION ON DELETE CASCADE
-
-CREATE TABLE "BREAKDOWN_RESULT" ("ID" INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,"RESULT_ID" INTEGER NOT NULL,"DATA_KEY" VARCHAR NOT NULL,"ORIGINAL_VALUE" BIGINT NOT NULL,"OBFUSCATED_VALUE" BIGINT NOT NULL)
-ALTER TABLE "BREAKDOWN_RESULT" ADD CONSTRAINT "BreakdownResultResultId_FK" FOREIGN KEY("RESULT_ID") REFERENCES "QUERY_RESULT"("ID") ON UPDATE NO ACTION ON DELETE CASCADE
-
-CREATE TABLE "ERROR_RESULT" ("ID" INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,"RESULT_ID" INTEGER NOT NULL,"MESSAGE" VARCHAR NOT NULL)
-ALTER TABLE "ERROR_RESULT" ADD CONSTRAINT "ErrorResultResultId_FK" FOREIGN KEY("RESULT_ID") REFERENCES "QUERY_RESULT"("ID") ON UPDATE NO ACTION ON DELETE CASCADE
-ALTER TABLE "ERROR_RESULT" ADD COLUMN "CODEC" VARCHAR NOT NULL DEFAULT "Old Error"
-ALTER TABLE "ERROR_RESULT" ADD COLUMN "SUMMARY" TEXT NOT NULL DEFAULT "Old Error"
-ALTER TABLE "ERROR_RESULT" ADD COLUMN "PROBLEM_DESCRIPTION" TEXT NOT NULL DEFAULT "Old Error"
-ALTER TABLE "ERROR_RESULT" ADD COLUMN "DETAILS" TEXT NOT NULL DEFAULT "Old Error"
-
-CREATE TABLE "PATIENT_SET" ("ID" INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,"RESULT_ID" INTEGER NOT NULL,"PATIENT_NUM" VARCHAR NOT NULL)
-ALTER TABLE "PATIENT_SET" ADD CONSTRAINT "PatientSetResultId_FK" FOREIGN KEY("RESULT_ID") REFERENCES "QUERY_RESULT"("ID") ON UPDATE NO ACTION ON DELETE CASCADE
-
-CREATE TABLE "PRIVILEGED_USER" ("ID" INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,"USERNAME" VARCHAR NOT NULL,"DOMAIN" VARCHAR NOT NULL,"THRESHOLD" INTEGER NOT NULL,"OVERRIDE_DATE" TIMESTAMP)
-CREATE UNIQUE INDEX "usernameAndDomainIndex" ON "PRIVILEGED_USER"("USERNAME","DOMAIN")
diff --git a/apps/dashboard-app/src/main/resources/reference.conf b/apps/dashboard-app/src/main/resources/reference.conf
index 9cc2def76..aaaa6a6a4 100644
--- a/apps/dashboard-app/src/main/resources/reference.conf
+++ b/apps/dashboard-app/src/main/resources/reference.conf
@@ -1,53 +1,56 @@
shrine {
dashboard {
gruntWatch = false //false for production, true for mvn tomcat7:run . Allows the client javascript and html files to be loaded via gruntWatch .
happyBaseUrl = "http://localhost:6060/shrine/rest/happy"
statusBaseUrl = "http://localhost:6060/shrine/rest/status"
remoteDashboard {
protocol = "https://"
port = ":6443"
+ pathPrefix = "shrine-dashboard/fromDashboard"
}
}
pmEndpoint {
url = "http://changeme.com/i2b2/services/PMService/getServices" //"http://services.i2b2.org/i2b2/services/PMService/getServices"
acceptAllCerts = true
timeout {
seconds = 10
}
}
authenticate {
realm = "SHRINE Steward API"
usersource
{
type = "PmUserSource" //Must be ConfigUserSource (for isolated testing) or PmUserSource (for everything else)
domain = "set shrine.authenticate.usersource.domain to the PM authentication domain in dashboard.conf" //"i2b2demo"
}
}
// If the pmEndpoint acceptAllCerts = false then you need to supply a keystore
// Or if you would like dashboard-to-dashboard comms to work.
// keystore {
// file = "shrine.keystore"
// password = "chiptesting"
// privateKeyAlias = "test-cert"
// keyStoreType = "JKS"
// caCertAliases = [carra ca]
// }
}
//todo typesafe config precedence seems to do the right thing, but I haven't found the rules that say this reference.conf should override others
akka {
loglevel = INFO
// log-config-on-start = on
loggers = ["akka.event.slf4j.Slf4jLogger"]
// logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
+ // Toggles whether the threads created by this ActorSystem should be daemons or not
+ daemonic = on
}
spray.servlet {
boot-class = "net.shrine.dashboard.Boot"
request-timeout = 30s
}
diff --git a/apps/dashboard-app/src/main/scala/net/shrine/dashboard/Boot.scala b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/Boot.scala
index 00d1b8acb..476eaff97 100644
--- a/apps/dashboard-app/src/main/scala/net/shrine/dashboard/Boot.scala
+++ b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/Boot.scala
@@ -1,17 +1,17 @@
package net.shrine.dashboard
import akka.actor.{Props, ActorSystem}
import spray.servlet.WebBoot
// this class is instantiated by the servlet initializer
// it needs to have a default constructor and implement
// the spray.servlet.WebBoot trait
class Boot extends WebBoot {
// we need an ActorSystem to host our application in
- val system = ActorSystem("sprayServer")
+ val system = ActorSystem("dashboardServer",DashboardConfigSource.config)
// the service actor replies to incoming HttpRequests
val serviceActor = system.actorOf(Props[DashboardServiceActor])
}
\ No newline at end of file
diff --git a/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala
index 09dd8a1f1..8f20aa941 100644
--- a/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala
+++ b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala
@@ -1,433 +1,429 @@
package net.shrine.dashboard
import akka.actor.{ActorSystem, Actor}
import akka.event.Logging
import net.shrine.authentication.UserAuthenticator
import net.shrine.authorization.steward.OutboundUser
import net.shrine.dashboard.jwtauth.ShrineJwtAuthenticator
import net.shrine.i2b2.protocol.pm.User
import net.shrine.status.protocol.{Config => StatusProtocolConfig}
import net.shrine.dashboard.httpclient.HttpClientDirectives.{forwardUnmatchedPath,requestUriThenRoute}
import net.shrine.log.Loggable
import shapeless.HNil
import spray.http.{Uri, HttpResponse, HttpRequest, StatusCodes}
import spray.httpx.Json4sSupport
import spray.routing.directives.LogEntry
import spray.routing.{AuthenticationFailedRejection, Rejected, RouteConcatenation, Directive0, Route, HttpService}
import org.json4s.{DefaultFormats, Formats}
import org.json4s.native.JsonMethods.{parse => json4sParse}
import scala.concurrent.ExecutionContext.Implicits.global
// 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 DashboardServiceActor extends Actor with DashboardService {
// 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 DashboardService extends HttpService with Json4sSupport with Loggable {
implicit def json4sFormats: Formats = DefaultFormats
val userAuthenticator = UserAuthenticator(DashboardConfigSource.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{
redirectToIndex ~ staticResources ~ makeTrouble ~ about ~ authenticatedInBrowser ~ authenticatedDashboard
}
// logs just the request method, uri and response at info level
def logEntryForRequestResponse(req: HttpRequest): Any => Option[LogEntry] = {
- case res: HttpResponse => {
- Some(LogEntry(s"\n Request: $req \n Response: $res", Logging.InfoLevel))
- }
+ 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 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"|"admin"|"toDashboard") {
logRequestResponse(logEntryForRequestResponse _) { //logging is controlled by Akka's config, slf4j, and log4j config
reportIfFailedToAuthenticate {
authenticate(userAuthenticator.basicUserAuthenticator) { user =>
pathPrefix("user") {
userRoute(user)
} ~
pathPrefix("admin") {
adminRoute(user)
} ~
pathPrefix("toDashboard") {
toDashboardRoute(user)
}
}
}
}
}
val reportIfFailedToAuthenticate = routeRouteResponse {
case Rejected(List(AuthenticationFailedRejection(_,_))) =>
complete("AuthenticationFailed")
}
def authenticatedDashboard:Route = pathPrefix("fromDashboard") {
logRequestResponse(logEntryForRequestResponse _) { //logging is controlled by Akka's config, slf4j, and log4j config
get { //all remote dashboard calls are gets.
authenticate(ShrineJwtAuthenticator.authenticate) { user =>
adminRoute(user)
}
}
}
}
def makeTrouble = pathPrefix("makeTrouble") {
complete(throw new IllegalStateException("fake trouble"))
}
lazy val redirectToIndex = pathEnd {
redirect("shrine-dashboard/client/index.html", StatusCodes.PermanentRedirect) //todo pick up "shrine-dashboard" 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") {
complete("Nothing here yet") //todo
}
def userRoute(user:User):Route = get {
pathPrefix("whoami") {
complete(OutboundUser.createFromUser(user))
}
}
//todo is this an admin? Does it matter?
def adminRoute(user:User):Route = get {
- implicit val system = ActorSystem("sprayServer")
pathPrefix("happy") {
val happyBaseUrl: String = DashboardConfigSource.config.getString("shrine.dashboard.happyBaseUrl")
forwardUnmatchedPath(happyBaseUrl)
} ~
pathPrefix("messWithHappyVersion") {
val happyBaseUrl: String = DashboardConfigSource.config.getString("shrine.dashboard.happyBaseUrl")
def pullClasspathFromConfig(httpResponse:HttpResponse,uri:Uri):Route = {
ctx => {
val result = httpResponse.entity.asString
ctx.complete(s"Got '$result' from $uri")
}
}
requestUriThenRoute(happyBaseUrl+"/version",pullClasspathFromConfig)
} ~
pathPrefix("ping") {complete("pong")}~
pathPrefix("status"){statusRoute(user)}
}
+ //Manually test this by running a curl command
+ //curl -k -w "\n%{response_code}\n" -u dave:kablam "https://shrine-dev1.catalyst:6443/shrine-dashboard/toDashboard/shrine-dev2.catalyst/shrine-dashboard/fromDashboard/ping"
def toDashboardRoute(user:User):Route = get {
- implicit val system = ActorSystem("sprayServer")
pathPrefix(Segment) { dnsName =>
val remoteDashboardProtocol = DashboardConfigSource.config.getString("shrine.dashboard.remoteDashboard.protocol")
val remoteDashboardPort = DashboardConfigSource.config.getString("shrine.dashboard.remoteDashboard.port")
+ val remoteDashboardPathPrefix = DashboardConfigSource.config.getString("shrine.dashboard.remoteDashboard.pathPrefix")
- val baseUrl = s"$remoteDashboardProtocol$dnsName$remoteDashboardPort"
+ val baseUrl = s"$remoteDashboardProtocol$dnsName$remoteDashboardPort/$remoteDashboardPathPrefix"
- forwardUnmatchedPath(baseUrl,Some(ShrineJwtAuthenticator.createOAuthCredentials))
+ forwardUnmatchedPath(baseUrl,Some(ShrineJwtAuthenticator.createOAuthCredentials(user)))
}
}
def statusRoute(user:User):Route = get {
pathPrefix("config"){getConfig}~
pathPrefix("classpath"){getClasspath}~
pathPrefix("summary"){getSummary}
}
lazy val getConfig:Route = {
val statusBaseUrl: String = DashboardConfigSource.config.getString("shrine" +
".dashboard.statusBaseUrl")
- implicit val system = ActorSystem("sprayServer")
forwardUnmatchedPath(statusBaseUrl)
}
lazy val getClasspath:Route = {
val statusBaseUrl: String = DashboardConfigSource.config.getString("shrine" +
".dashboard" +
".statusBaseUrl")
implicit val system = ActorSystem("sprayServer")
def pullClasspathFromConfig(httpResponse:HttpResponse,uri:Uri):Route = {
ctx => {
// -- import parser -- //
import org.json4s.native.JsonMethods.parse
// -- vars -- //
val result = httpResponse.entity.asString
val config = parse(result)
.extract[net.shrine.status.protocol.Config]
.keyValues
.filterKeys(_.toLowerCase.startsWith("shrine"))
val shrineConfig = ShrineConfig(config)
val audit = Audit(config)
// -- complete route -- //
ctx.complete(shrineConfig)
}
}
// -- init request -- //
requestUriThenRoute(statusBaseUrl,pullClasspathFromConfig)
}
lazy val getSummary:Route = {
// -- vars -- //
val urlKey = "shrine.dashboard.statusBaseUrl"
val statusBaseUrl: String = DashboardConfigSource.config.getString(urlKey)
implicit val system = ActorSystem("sprayServer")
def completeSummaryRoute(httpResponse:HttpResponse,uri:Uri):Route = {
ctx => {
val config = ShrineParser.parseShrineFromConfig(httpResponse.entity.asString)
ctx.complete(
Options(config)
)
}
}
requestUriThenRoute(statusBaseUrl, completeSummaryRoute)
}
}
/**
* Centralized parsing logic for map of shrine.conf
* the class literal `T.class` in Java.
*/
object ShrineParser{
// -- @todo: need to make sure this is initialized. -- //
private var shrineMap:Map[String, String]
= Map(""->"")
private val trueVal = "true"
private val rootKey = "shrine"
private def isInit =
this.shrineMap.contains(rootKey)
// -- @todo: where should this live ? -- //
def parseShrineFromConfig(resultStr:String) = {
// -- needed to use json4s parse -- //
implicit def json4sFormats: Formats = DefaultFormats
// -- extract map of shrine subset of config file -- //
this.shrineMap = json4sParse(resultStr).extract[StatusProtocolConfig].keyValues
.filterKeys(_.toLowerCase.startsWith("shrine"))
this.shrineMap
}
// -- @todo throw exception in else? -- //
private def Parser =
this.shrineMap
// -- -- //
def IsHub =
Parser.getOrElse(rootKey + ".hub.create", "")
.toLowerCase == trueVal
// -- -- //
def StewardEnabled =
Parser.keySet
.contains(rootKey + ".queryEntryPoint.shrineSteward")
// -- -- //
def ShouldQuerySelf =
Parser.getOrElse(rootKey + ".hub.shouldQuerySelf", "")
.toLowerCase == trueVal
// -- -- //
def DownstreamNodes =
for((k,v) <- Parser.filterKeys(_.toLowerCase.startsWith
("shrine.hub.downstreamnodes"))) yield (k.split('.').last, v)
}
case class Options(isHub:Boolean, stewardEnabled:Boolean, shouldQuerySelf:Boolean,
downstreamNodes:Map[String,String])
object Options{
def apply(configMap:Map[String, String]):Options ={
val isHub = ShrineParser.IsHub
val stewardEnabled = ShrineParser.StewardEnabled
val shouldQuerySelf = ShrineParser.ShouldQuerySelf
val downstreamNodes = ShrineParser.DownstreamNodes
Options(isHub, stewardEnabled, shouldQuerySelf, downstreamNodes)
}
}
case class ShrineConfig(isHub:Boolean, hub:Hub, pmEndpoint:Endpoint,
ontEndpoint:Endpoint, hiveCredentials: HiveCredentials)
object ShrineConfig{
def apply(configMap:Map[String, String]):ShrineConfig = {
val hub = Hub(configMap)
val isHub = hub.create == true
val pmEndpoint = Endpoint(configMap, "pm")
val ontEndpoint = Endpoint(configMap, "ont")
val hiveCredentials = HiveCredentials(configMap)
ShrineConfig(isHub, hub, pmEndpoint, ontEndpoint, hiveCredentials)
}
}
case class Endpoint(acceptAllCerts:Boolean, url:String, timeoutSeconds:Int)
object Endpoint{
def apply(configMap:Map[String, String], endpointType:String):Endpoint = {
val prefix = "shrine." + endpointType.toLowerCase + "Endpoint."
val acceptAllCerts = configMap.getOrElse(prefix + "acceptAllCerts", "") == "true"
val url = configMap.getOrElse(prefix + "url", "")
val timeoutSeconds = configMap.getOrElse(prefix + "timeout.seconds", "").toInt
Endpoint(acceptAllCerts, url, timeoutSeconds)
}
}
case class HiveCredentials(domain:String, username:String, password:String,
crcProjectId:String, ontProjectId:String)
object HiveCredentials{
def apply(configMap:Map[String, String]):HiveCredentials = {
val key = "shrine.hiveCredentials."
val domain = configMap.getOrElse(key + "domain", "")
val username = configMap.getOrElse(key + "username", "")
val password = "REDACTED"
val crcProjectId = configMap.getOrElse(key + "crcProjectId", "")
val ontProjectId = configMap.getOrElse(key + "ontProjectId", "")
HiveCredentials(domain, username, password, crcProjectId, ontProjectId)
}
}
// -- hub only -- //
case class Hub(shouldQuerySelf:Boolean, create:Boolean, downstreamNodes:Map[String,String])
object Hub{
def apply(configMap:Map[String,String]):Hub = {
//@todo: use missing istead of "" for 'Else'
val shouldQuerySelf = configMap.getOrElse("shrine.hub.shouldQuerySelf", "") == "true"
val create = configMap.getOrElse("shrine.hub.create", "") == "true"
val downstreamNodes = for((k,v) <- configMap.filterKeys(_.toLowerCase.startsWith
("shrine.hub.downstreamnodes"))) yield (k.split('.').last, v)
Hub(shouldQuerySelf, create, downstreamNodes)
}
}
// -- if needed -- //
case class TimeoutInfo (timeUnit:String, description:String)
case class DatabaseInfo(createTablesOnStart:Boolean, dataSourceFrom:String,
jndiDataSourceName:String, slickProfileClassName:String)
case class Audit(database:DatabaseInfo, collectQepAudit:Boolean)
object Audit{
def apply(configMap:Map[String, String]):Audit = {
val key = "shrine.queryEntryPoint.audit."
val createTablesOnStart = configMap.getOrElse(key + "database.createTablesOnStart", "") == "true"
val dataSourceFrom = configMap.getOrElse(key + "database.dataSourceFrom", "")
val jndiDataSourceName = configMap.getOrElse(key + "database.jndiDataSourceName", "")
val slickProfileClassName = configMap.getOrElse(key + "database.slickProfileClassName", "")
val collectQepAudit = configMap.getOrElse(key + "collectQepAudit", "") == "true"
val database = DatabaseInfo(createTablesOnStart, dataSourceFrom, jndiDataSourceName, slickProfileClassName)
Audit(database, collectQepAudit)
}
}
case class QEP(maxQueryWaitTimeMinutes:Int, create:Boolean,attachSigningCert:Boolean,
authorizationType:String, includeAggregateResults:Boolean, authenticationType:String, audit:Audit)
object QEP{
def apply(configMap:Map[String, String]):QEP = {
val key = "shrine.queryEntryPoint."
val sheriffUsername = configMap.getOrElse(key + "sheriffCredentials.username", "")
val maxQueryWaitTimeMinutes = configMap.getOrElse(key + "maxQueryWaitTime.minutes", "").toInt
val create = configMap.getOrElse(key + "create", "") == "true"
val attachSigningCert = configMap.getOrElse(key + "attachSigningCert", "") == "true"
val authorizationType = configMap.getOrElse(key + "authorizationType", "")
val includeAggregateResults = configMap.getOrElse(key + "includeAggregateResults", "") == "true"
val authenticationType = configMap.getOrElse(key + "authenticationType", "")
val audit = Audit(configMap)
val sheriffServiceEndpoint = Endpoint(configMap, "queryEntryPoint.sheriff")
val broadcasterServiceEndpoint = Endpoint(configMap, "queryEntryPoint.broadcasterService")
QEP(maxQueryWaitTimeMinutes, create, attachSigningCert, authorizationType, includeAggregateResults, authenticationType, audit)
}
}
//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 = DashboardConfigSource.config.getBoolean("shrine.dashboard.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/dashboard-app/src/main/scala/net/shrine/dashboard/httpclient/HttpClientDirectives.scala b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/httpclient/HttpClientDirectives.scala
index b4184f6ca..de45ea693 100644
--- a/apps/dashboard-app/src/main/scala/net/shrine/dashboard/httpclient/HttpClientDirectives.scala
+++ b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/httpclient/HttpClientDirectives.scala
@@ -1,197 +1,200 @@
package net.shrine.dashboard.httpclient
import java.io.InputStream
import java.security.cert.X509Certificate
import javax.net.ssl.{X509TrustManager, SSLContext}
+import net.shrine.dashboard.DashboardConfigSource
import net.shrine.log.Loggable
import spray.can.Http
import akka.io.IO
import akka.actor.{ActorRef, ActorSystem}
import spray.can.Http.{HostConnectorSetup, ConnectionAttemptFailedException}
import spray.http.{HttpCredentials, HttpHeaders, HttpHeader, HttpEntity, StatusCodes, HttpRequest, HttpResponse, Uri}
import spray.io.ClientSSLEngineProvider
import spray.routing.{RequestContext, Route}
import akka.pattern.ask
import scala.concurrent.{TimeoutException, Await, Future, blocking}
import scala.concurrent.duration.DurationInt
import scala.language.postfixOps
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.control.NonFatal
/**
* From https://github.com/bthuillier/spray/commit/d31fc1b5e1415e1b908fe7d1f01f364a727e2593 with extra bits from http://www.cakesolutions.net/teamblogs/http-proxy-with-spray .
* Replace when Spray has its own version.
*
* @author david
* @since 9/14/15
*/
trait HttpClientDirectives extends Loggable {
+ implicit val system = ActorSystem("dashboardServer",DashboardConfigSource.config)
/**
* Proxy the request to the specified base uri appended with the unmatched path.
*
*/
- def forwardUnmatchedPath(baseUri: Uri,maybeCredentials:Option[HttpCredentials] = None)(implicit system: ActorSystem): Route = {
+ //todo these implicits don't buy that much . Consider ditching them.
+ def forwardUnmatchedPath(baseUri: Uri,maybeCredentials:Option[HttpCredentials] = None): Route = {
def completeWithEntityAsString(httpResponse:HttpResponse,uri:Uri):Route = {
ctx => {
ctx.complete(httpResponse.entity.asString)
}
}
requestWithUnmatchedPath(baseUri,completeWithEntityAsString,maybeCredentials)
}
/**
* Make the request to the specified base uri appended with the unmatched path, then use the returned entity (as a string) to complete the route.
*
*/
- def requestWithUnmatchedPath(baseUri:Uri, route:(HttpResponse,Uri) => Route,maybeCredentials:Option[HttpCredentials] = None)(implicit system: ActorSystem): Route = {
+ def requestWithUnmatchedPath(baseUri:Uri, route:(HttpResponse,Uri) => Route,maybeCredentials:Option[HttpCredentials] = None): Route = {
ctx => {
val resourceUri = baseUri.withPath(baseUri.path.++(ctx.unmatchedPath))
- requestUriThenRoute(resourceUri,route,maybeCredentials)(system)(ctx)
+ requestUriThenRoute(resourceUri,route,maybeCredentials)(ctx)
}
}
/**
* proxy the request to the specified uri with the unmatched path, then use the returned entity (as a string) to complete the route.
*
*/
- def requestUriThenRoute(resourceUri:Uri, route:(HttpResponse,Uri) => Route,maybeCredentials:Option[HttpCredentials] = None)(implicit system: ActorSystem): Route = {
+ def requestUriThenRoute(resourceUri:Uri, route:(HttpResponse,Uri) => Route,maybeCredentials:Option[HttpCredentials] = None): Route = {
ctx => {
- val httpResponse = httpResponseForUri(resourceUri,ctx,maybeCredentials)(system)
+ val httpResponse = httpResponseForUri(resourceUri,ctx,maybeCredentials)
info(s"Got $httpResponse for $resourceUri")
handleCommonErrorsOrRoute(route)(httpResponse,resourceUri)(ctx)
}
}
- private def httpResponseForUri(resourceUri:Uri,ctx: RequestContext,maybeCredentials:Option[HttpCredentials] = None)(implicit system: ActorSystem):HttpResponse = {
+ private def httpResponseForUri(resourceUri:Uri,ctx: RequestContext,maybeCredentials:Option[HttpCredentials] = None):HttpResponse = {
info(s"Requesting $resourceUri")
if(resourceUri.scheme == "classpath") ClasspathResourceHttpClient.loadFromResource(resourceUri.path.toString())
else {
val basicRequest = HttpRequest(ctx.request.method,resourceUri)
val request = maybeCredentials.fold(basicRequest){ (credentials: HttpCredentials) =>
val headers: List[HttpHeader] = basicRequest.headers :+ HttpHeaders.Authorization(credentials)
basicRequest.copy(headers = headers)
}
HttpClient.webApiCall(request)
}
}
def handleCommonErrorsOrRoute(route:(HttpResponse,Uri) => Route)(httpResponse: HttpResponse,uri:Uri): Route = {
ctx => {
if(httpResponse.status != StatusCodes.OK) {
//todo create and report a problem
val ctxCopy: RequestContext = ctx.withHttpResponseMapped(_.copy(status = httpResponse.status))
ctxCopy.complete(s"$uri replied with $httpResponse")
}
else route(httpResponse,uri)(ctx)
}
}
}
object HttpClientDirectives extends HttpClientDirectives
/**
* A simple HttpClient to use inside the HttpDirectives
*/
object HttpClient extends Loggable {
//todo hand back a Try, Failures with custom exceptions instead of a crappy response
def webApiCall(request:HttpRequest)(implicit system: ActorSystem): HttpResponse = {
val transport: ActorRef = IO(Http)(system)
debug(s"Requesting $request uri is ${request.uri} path is ${request.uri.path}")
blocking {
val future:Future[HttpResponse] = for {
Http.HostConnectorInfo(connector, _) <- transport.ask(createConnector(request))(10 seconds) //todo make this timeout configurable
response <- connector.ask(request)(10 seconds).mapTo[HttpResponse] //todo make this timeout configurable
} yield response
try {
Await.result(future, 10 seconds) //todo make this timeout configurable
}
catch {
case x:TimeoutException => HttpResponse(status = StatusCodes.RequestTimeout,entity = HttpEntity(s"${request.uri} timed out after 10 seconds. ${x.getMessage}"))
//todo is there a better message? What comes up in real life?
case x:ConnectionAttemptFailedException => {
//no web service is there to respond
info(s"${request.uri} failed with ${x.getMessage}",x)
HttpResponse(status = StatusCodes.NotFound,entity = HttpEntity(s"${request.uri} failed with ${x.getMessage}"))
}
case NonFatal(x) => {
info(s"${request.uri} failed with ${x.getMessage}",x)
HttpResponse(status = StatusCodes.InternalServerError,entity = HttpEntity(s"${request.uri} failed with ${x.getMessage}"))
}
}
}
}
//from https://github.com/TimothyKlim/spray-ssl-poc/blob/master/src/main/scala/Main.scala
//trust all SSL contexts. We just want encrypted comms.
implicit val trustfulSslContext: SSLContext = {
class IgnoreX509TrustManager extends X509TrustManager {
def checkClientTrusted(chain: Array[X509Certificate], authType: String) {}
def checkServerTrusted(chain: Array[X509Certificate], authType: String) {}
def getAcceptedIssuers = null
}
val context = SSLContext.getInstance("TLS")
context.init(null, Array(new IgnoreX509TrustManager), null)
info("trustfulSslContex initialized")
context
}
implicit val clientSSLEngineProvider =
//todo lookup this constructor
ClientSSLEngineProvider {
_ =>
val engine = trustfulSslContext.createSSLEngine()
engine.setUseClientMode(true)
engine
}
def createConnector(request: HttpRequest) = {
val connector = new HostConnectorSetup(host = request.uri.authority.host.toString,
port = request.uri.effectivePort,
sslEncryption = request.uri.scheme == "https",
defaultHeaders = request.headers)
connector
}
}
/**
* For testing, get an HttpResponse for a classpath resource
*/
object ClasspathResourceHttpClient extends Loggable {
def loadFromResource(resourceName:String):HttpResponse = {
blocking {
val cleanResourceName = if (resourceName.startsWith ("/") ) resourceName.drop(1)
else resourceName
val classLoader = getClass.getClassLoader
try {
val is: InputStream = classLoader.getResourceAsStream (cleanResourceName)
val string:String = scala.io.Source.fromInputStream (is).mkString
HttpResponse(entity = HttpEntity(string))
}
catch{
case NonFatal(x) => {
info(s"Could not load $resourceName",x)
HttpResponse(status = StatusCodes.NotFound,entity = HttpEntity(s"Could not load $resourceName due to ${x.getMessage}"))
}
}
}
}
}
\ No newline at end of file
diff --git a/apps/dashboard-app/src/main/scala/net/shrine/dashboard/jwtauth/ShrineJwtAuthenticator.scala b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/jwtauth/ShrineJwtAuthenticator.scala
index 1f14338cc..0f58d3b35 100644
--- a/apps/dashboard-app/src/main/scala/net/shrine/dashboard/jwtauth/ShrineJwtAuthenticator.scala
+++ b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/jwtauth/ShrineJwtAuthenticator.scala
@@ -1,178 +1,202 @@
package net.shrine.dashboard.jwtauth
import java.io.ByteArrayInputStream
-import java.security.{PublicKey, Key, PrivateKey}
+import java.security.{Principal, Key, PrivateKey}
import java.security.cert.{CertificateFactory, X509Certificate}
import java.util.Date
import io.jsonwebtoken.impl.TextCodec
-import io.jsonwebtoken.{Claims, SignatureAlgorithm, ExpiredJwtException, Jwts}
-import net.shrine.crypto.{CertCollection, KeyStoreDescriptorParser, KeyStoreCertCollection}
+import io.jsonwebtoken.{Jws, ClaimJwtException, Claims, SignatureAlgorithm, Jwts}
+import net.shrine.crypto.{KeyStoreDescriptorParser, KeyStoreCertCollection}
import net.shrine.dashboard.DashboardConfigSource
import net.shrine.i2b2.protocol.pm.User
import net.shrine.log.Loggable
import net.shrine.protocol.Credential
import spray.http.HttpHeaders.{Authorization, `WWW-Authenticate`}
-import spray.http.{OAuth2BearerToken, HttpHeader, HttpChallenge}
+import spray.http.{HttpRequest, OAuth2BearerToken, HttpHeader, HttpChallenge}
import spray.routing.AuthenticationFailedRejection.{CredentialsMissing, CredentialsRejected}
import spray.routing.AuthenticationFailedRejection
import spray.routing.authentication._
import scala.concurrent.{Future, ExecutionContext}
-import scala.util.Try
+import scala.util.{Success, Failure, Try}
/**
- * An Authenticator that uses Jwt in a ShrineJwt1 header to authenticate. See http://jwt.io/introduction/ for what this is all about,
+ * An Authenticator that uses Jwt in a Bearer header to authenticate. See http://jwt.io/introduction/ for what this is all about,
* https://tools.ietf.org/html/rfc7519 for what it might include for claims.
*
* @author david
* @since 12/21/15
*/
object ShrineJwtAuthenticator extends Loggable {
val config = DashboardConfigSource.config
val certCollection: KeyStoreCertCollection = KeyStoreCertCollection.fromFileRecoverWithClassPath(KeyStoreDescriptorParser(config.getConfig("shrine.keystore")))
//from https://groups.google.com/forum/#!topic/spray-user/5DBEZUXbjtw
def authenticate(implicit ec: ExecutionContext): ContextAuthenticator[User] = { ctx =>
Future {
- val missingCredentials: Authentication[User] = Left(AuthenticationFailedRejection(CredentialsMissing, List(challengeHeader)))
- val rejectedCredentials: Authentication[User] = Left(AuthenticationFailedRejection(CredentialsRejected, List(challengeHeader)))
-
- //noinspection ComparingUnrelatedTypes
- ctx.request.headers.find(_.name.equals(Authorization.name)).fold(missingCredentials) { (header: HttpHeader) =>
-
- //header should be "$ShrineJwtAuth0 $SignerSerialNumber,$JwtsString
- val splitHeaderValue: Array[String] = header.value.split(" ")
- if (splitHeaderValue.length == 2) {
-
- val authScheme = splitHeaderValue(0)
- val jwtsString = splitHeaderValue(1)
-
- if (authScheme == BearerAuthScheme) {
- try {
- val jwtsClaims: Claims = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverBridge()).parseClaimsJws(jwtsString).getBody
- info(s"got claims $jwtsClaims")
-
- val now = new Date()
- if (jwtsClaims.getExpiration.before(now)) {
- info(s"jwts ${jwtsClaims.getExpiration} expired before now $now")
- rejectedCredentials
- }
- else {
- val cert = KeySource.certForString(Jwts.parser().setSigningKeyResolver(new SigningKeyResolverBridge()).parseClaimsJws(jwtsString).getHeader.getKeyId)
-
- certCollection.caCerts.get(CertCollection.getIssuer(cert)).fold{
- info(s"Could not find a CA certificate with issuer DN ${cert.getIssuerDN}. Known CA cert aliases are ${certCollection.caCertAliases.mkString(",")}")
- rejectedCredentials
- } { signerCert =>
- def isSignedBy(cert: X509Certificate)(caPubKey: PublicKey): Boolean = Try { cert.verify(caPubKey); true }.getOrElse(false)
- if(!isSignedBy(cert)(signerCert.getPublicKey)) {
- info(s"cert $cert was not signed by $signerCert as claimed.")
- rejectedCredentials
- }
- else if (jwtsClaims.getSubject == null) {
- info(s"jwts from ${cert.getSubjectDN.getName} subject is null")
- rejectedCredentials
- }
- else {
- val user = User(
- fullName = cert.getSubjectDN.getName,
- username = jwtsClaims.getSubject,
- domain = "dashboard-to-dashboard",
- credential = Credential("Dashboard credential", isToken = false),
- params = Map(),
- rolesByProject = Map()
- )
- Right(user)
- }
- }
- }
- } catch {
- /*
- case x: CertificateExpiredException => {
- //todo will these even be thrown here? Get some identification here
- info(s"Cert expired.", x)
- rejectedCredentials
- }
- case x: CertificateNotYetValidException => {
- info(s"Cert not yet valid.", x)
- rejectedCredentials
- }
- */
- case x: ExpiredJwtException =>
- info(s"Jwt for todo expired.", x) //todo get some identification in here
- rejectedCredentials
- }
+
+ val attempt: Try[Authentication[User]] = for {
+ header:HttpHeader <- extractAuthorizationHeader(ctx.request)
+ jwtsString:String <- extractJwtsStringAndCheckScheme(header)
+ jwtsClaims <- extractJwtsClaims(jwtsString)
+ cert: X509Certificate <- extractAndCheckCert(jwtsClaims)
+ jwtsBody:Claims <- Try{jwtsClaims.getBody}
+ jwtsSubject <- failIfNull(jwtsBody.getSubject,MissingRequiredJwtsClaim("subject",cert.getSubjectDN))
+ jwtsIssuer <- failIfNull(jwtsBody.getSubject,MissingRequiredJwtsClaim("issuer",cert.getSubjectDN))
+ } yield {
+ val user = User(
+ fullName = cert.getSubjectDN.getName,
+ username = jwtsSubject,
+ domain = jwtsIssuer,
+ credential = Credential(jwtsIssuer, isToken = false),
+ params = Map(),
+ rolesByProject = Map()
+ )
+ Right(user)
+ }
+ //todo use a fold() in Scala 2.12
+ attempt match {
+ case Success(rightUser) => rightUser
+ case Failure(x) => x match
+ {
+ case anticipated: ShrineJwtException =>
+ info(s"Failed to authenticate due to ${anticipated.toString}",anticipated)
+ anticipated.rejection
+
+ case fromJwts: ClaimJwtException =>
+ info(s"Failed to authenticate due to ${fromJwts.toString} while authenticating ${ctx.request}",fromJwts)
+ rejectedCredentials
+
+ /*
+ case x: CertificateExpiredException => {
+ //todo will these even be thrown here? Get some identification here
+ info(s"Cert expired.", x)
+ rejectedCredentials
}
- else {
- info(s"Header did not start with $BearerAuthScheme .")
- missingCredentials
+ case x: CertificateNotYetValidException => {
+ info(s"Cert not yet valid.", x)
+ rejectedCredentials
}
- }
- else {
- info(s"Header had ${splitHeaderValue.length} space-delimited segments, not 2. ")
- missingCredentials
+*/
+ case unanticipated =>
+ warn(s"Unanticipated ${unanticipated.toString} while authenticating ${ctx.request}",unanticipated)
+ rejectedCredentials
}
}
}
}
- def createOAuthCredentials: OAuth2BearerToken = {
+ def createOAuthCredentials(user:User): OAuth2BearerToken = {
val base64Cert = new String(TextCodec.BASE64URL.encode(certCollection.myCert.get.getEncoded))
val key: PrivateKey = certCollection.myKeyPair.privateKey
val expiration: Date = new Date(System.currentTimeMillis() + 30 * 1000) //good for 30 seconds
val jwtsString = Jwts.builder().
setHeaderParam("kid", base64Cert).
- setSubject(java.net.InetAddress.getLocalHost.getHostName).
+ setSubject(s"${user.username} at ${user.domain}").
+ setIssuer(java.net.InetAddress.getLocalHost.getHostName). //todo is it OK for me to use issuer this way or should I use my own claim?
setExpiration(expiration).
signWith(SignatureAlgorithm.RS512, key).
compact()
- val token = OAuth2BearerToken(jwtsString)
- info(s"token is $token")
+ OAuth2BearerToken(jwtsString)
+ }
+
+ def extractAuthorizationHeader(request: HttpRequest):Try[HttpHeader] = Try {
+ case class NoAuthorizationHeaderException(request: HttpRequest) extends ShrineJwtException(s"No ${Authorization.name} header found in $request",missingCredentials)
+ //noinspection ComparingUnrelatedTypes
+ request.headers.find(_.name.equals(Authorization.name)).getOrElse{throw NoAuthorizationHeaderException(request)}
+ }
+
+ def extractJwtsStringAndCheckScheme(httpHeader: HttpHeader) = Try {
+ val splitHeaderValue: Array[String] = httpHeader.value.trim.split(" ")
+ if (splitHeaderValue.length != 2) {
+ case class WrongNumberOfSegmentsException(httpHeader: HttpHeader) extends ShrineJwtException(s"Header had ${splitHeaderValue.length} space-delimited segments, not 2, in $httpHeader.",missingCredentials)
+ throw new WrongNumberOfSegmentsException(httpHeader)
+ }
+ else if(splitHeaderValue(0) != BearerAuthScheme) {
+ case class NotBearerAuthException(httpHeader: HttpHeader) extends ShrineJwtException(s"Expected $BearerAuthScheme, not ${splitHeaderValue(0)} in $httpHeader.",missingCredentials)
+ throw new NotBearerAuthException(httpHeader)
+ }
+ else splitHeaderValue(1)
+ }
+
+ def extractJwtsClaims(jwtsString:String): Try[Jws[Claims]] = Try {
+ Jwts.parser().setSigningKeyResolver(new SigningKeyResolverBridge()).parseClaimsJws(jwtsString)
+ }
+
+ def extractAndCheckCert(jwtsClaims:Jws[Claims]): Try[X509Certificate] = Try {
+ val cert = KeySource.certForString(jwtsClaims.getHeader.getKeyId)
+
+ val issuingSite = jwtsClaims.getBody.getIssuer
+
+ //todo is this the right way to find a cert in the certCollection?
+
+ debug(s"certCollection.caCerts.contains(${cert.getSubjectX500Principal}) is ${certCollection.caCerts.contains(cert.getSubjectX500Principal)}")
+ certCollection.caCerts.get(cert.getSubjectX500Principal).fold{
+ //if not in the keystore, check that the issuer is available
+ val issuer: Principal = cert.getIssuerX500Principal
+ case class CertIssuerNotInCollectionException(issuingSite:String,issuer: Principal) extends ShrineJwtException(s"Could not find a CA certificate with issuer DN $issuer. Known CA cert aliases are ${certCollection.caCertAliases.mkString(",")}")
+ val signingCert = certCollection.caCerts.getOrElse(issuer,{throw CertIssuerNotInCollectionException(issuingSite,issuer)})
+
+ //verify that the cert was signed using the signingCert
+ //todo this can throw CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException
+ cert.verify(signingCert.getPublicKey)
+ //todo has cert expired?
+ info(s"${cert.getSubjectX500Principal} verified using $issuer from the KeyStore")
+ cert
+ }{ principal => //if the cert is in the certCollection then all is well
+ info(s"$principal is in the KeyStore")
+ cert
+ }
+ }
- token
+ def failIfNull[E](e:E,t:Throwable):Try[E] = Try {
+ if(e == null) throw t
+ else e
}
+ case class MissingRequiredJwtsClaim(field:String,principal: Principal) extends ShrineJwtException(s"$field is null from ${principal.getName}")
+
val BearerAuthScheme = "Bearer"
val challengeHeader: `WWW-Authenticate` = `WWW-Authenticate`(HttpChallenge(BearerAuthScheme, "dashboard-to-dashboard"))
+ val missingCredentials: Authentication[User] = Left(AuthenticationFailedRejection(CredentialsMissing, List(challengeHeader)))
+ val rejectedCredentials: Authentication[User] = Left(AuthenticationFailedRejection(CredentialsRejected, List(challengeHeader)))
+
}
class KeySource {}
object KeySource extends Loggable {
def keyForString(string: String): Key = {
val certificate =certForString(string)
//todo validate cert with something like obtainAndValidateSigningCert
- info(s"Created cert $certificate")
-
//check date on cert vs time. throws CertificateExpiredException or CertificateNotYetValidException for problems
//todo skip this until you rebuild the certs used for testing certificate.checkValidity(now)
- val key = certificate.getPublicKey
- info(s"got key $key")
- key
+ certificate.getPublicKey
}
def certForString(string: String): X509Certificate = {
val certBytes = TextCodec.BASE64URL.decode(string)
- info(s"Got cert bytes $string")
-
val inputStream = new ByteArrayInputStream(certBytes)
-
val certificate = try {
CertificateFactory.getInstance("X.509").generateCertificate(inputStream).asInstanceOf[X509Certificate]
}
finally {
inputStream.close()
}
certificate
}
-}
\ No newline at end of file
+}
+
+abstract class ShrineJwtException(message:String,
+ val rejection:Authentication[User] = ShrineJwtAuthenticator.rejectedCredentials,
+ cause:Throwable = null) extends RuntimeException(message,cause)
\ No newline at end of file
diff --git a/apps/dashboard-app/src/test/resources/teststatus/configuration b/apps/dashboard-app/src/test/resources/teststatus/configuration
index a5b990d1e..90ba96a74 100644
--- a/apps/dashboard-app/src/test/resources/teststatus/configuration
+++ b/apps/dashboard-app/src/test/resources/teststatus/configuration
@@ -1,2 +1,2 @@
"/Users/ben/.m2/repository"
-{"keyValues":{"spray.can.server.parsing.ssl-session-info-header":"\"off\"","spray.can.listener-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.client.parsing.header-cache.default":"12","java.io.tmpdir":"\"/var/folders/lt/6x60mpg92w994pbhxl8q4dyc0000gp/T/\"","akka.actor.mailbox.requirements.\"akka.dispatch.UnboundedDequeBasedMessageQueueSemantics\"":"\"akka.actor.mailbox.unbounded-deque-based\"","shrine.queryEntryPoint.audit.database.createTablesOnStart":"false","spray.can.host-connector.client.request-header-size-hint":"512","spray.can.client.parsing.header-cache.If-Modified-Since":"0","akka.actor.deployment.default.routees.paths":"[]","shrine.ontEndpoint.acceptAllCerts":"true","spray.routing.file-chunking-chunk-size":"\"128k\"","akka.io.udp.direct-buffer-pool-limit":"1000","shrine.queryEntryPoint.maxQueryWaitTime.minutes":"5","akka.actor.unstarted-push-timeout":"\"10s\"","spray.can.parsing.max-chunk-ext-length":"256","spray.can.client.parsing.max-content-length":"\"8m\"","line.separator":"\"\\n\"","spray.can.server.parsing.header-cache.User-Agent":"32","spray.can.server.parsing.max-uri-length":"\"2k\"","spray.servlet.timeout-timeout":"\"500 ms\"","akka.actor.default-mailbox.mailbox-type":"\"akka.dispatch.UnboundedMailbox\"","spray.servlet.max-content-length":"\"5 m\"","shrine.queryEntryPoint.audit.database.dataSourceFrom":"\"JNDI\"","akka.actor.serializers.java":"\"akka.serialization.JavaSerializer\"","akka.actor.dsl.default-timeout":"\"5s\"","shrine.adapter.setSizeObfuscation":"true","shrine.queryEntryPoint.audit.collectQepAudit":"true","shrine.pmEndpoint.acceptAllCerts":"true","spray.can.server.chunkhandler-registration-timeout":"\"500 ms\"","akka.actor.serialize-creators":"\"off\"","akka.actor.deployment.default.virtual-nodes-factor":"10","shrine.queryEntryPoint.broadcasterServiceEndpoint.acceptAllCerts":"true","shrine.queryEntryPoint.create":"true","path.separator":"\":\"","spray.can.host-connector.client.parsing.header-cache.If-Match":"0","spray.can.host-connector.client.max-encryption-chunk-size":"\"1m\"","spray.can.client.parsing.max-header-count":"64","spray.can.manager-dispatcher":"\"akka.actor.default-dispatcher\"","akka.io.tcp.worker-dispatcher":"\"akka.actor.default-dispatcher\"","akka.jvm-exit-on-fatal-error":"\"on\"","basedir":"\"/Users/ben/git/hms/develop/apps/shrine-app\"","akka.io.udp.worker-dispatcher":"\"akka.actor.default-dispatcher\"","spray.servlet.boot-class":"\"\"","spray.can.client.parsing.max-chunk-size":"\"1m\"","shrine.hiveCredentials.username":"\"demo\"","shrine.adapter.adapterLockoutAttemptsThreshold":"10","sun.management.compiler":"\"HotSpot 64-Bit Tiered Compilers\"","akka.actor.default-dispatcher.fork-join-executor.parallelism-min":"8","akka.io.udp.selector-association-retries":"10","akka.scheduler.tick-duration":"\"10ms\"","spray.can.host-connector.client.parsing.uri-parsing-mode":"\"strict\"","spray.can.client.parsing.max-uri-length":"\"2k\"","shrine.hub.downstreamNodes.PHS":"\"http://example.com/phs\"","sun.cpu.endian":"\"little\"","akka.actor.mailbox.requirements.\"akka.dispatch.DequeBasedMessageQueueSemantics\"":"\"akka.actor.mailbox.unbounded-deque-based\"","spray.can.parsing.max-chunk-size":"\"1m\"","spray.can.server.parsing.header-cache.If-Range":"0","spray.can.client.parsing.ssl-session-info-header":"\"off\"","akka.io.tcp.register-timeout":"\"5s\"","java.specification.version":"\"1.8\"","shrine.hub.create":"true","akka.actor.creation-timeout":"\"20s\"","spray.can.host-connector.client.parsing.header-cache.Date":"0","java.vm.specification.name":"\"Java Virtual Machine Specification\"","java.vm.specification.version":"\"1.8\"","shrine.pmEndpoint.timeout.seconds":"1","akka.actor.router.type-mapping.random-group":"\"akka.routing.RandomGroup\"","shrine.hiveCredentials.ontProjectId":"\"SHRINE\"","user.home":"\"/Users/ben\"","akka.log-dead-letters-during-shutdown":"\"on\"","file.encoding.pkg":"\"sun.io\"","spray.can.client.chunkless-streaming":"\"off\"","akka.scheduler.ticks-per-wheel":"512","akka.actor.dsl.inbox-size":"1000","spray.can.parsing.max-header-value-length":"\"8k\"","akka.actor.default-mailbox.stash-capacity":"-1","spray.can.client.ssl-tracing":"\"off\"","sun.arch.data.model":"\"64\"","akka.io.udp-connected.received-message-size-limit":"\"unlimited\"","akka.log-config-on-start":"\"off\"","akka.io.tcp.direct-buffer-size":"\"128 KiB\"","akka.actor.default-dispatcher.type":"\"Dispatcher\"","akka.actor.deployment.default.resizer.rampup-rate":"0.2","spray.servlet.remote-address-header":"\"off\"","sun.boot.library.path":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib\"","spray.can.client.parsing.header-cache.User-Agent":"32","akka.actor.deployment.default.resizer.backoff-threshold":"0.3","user.dir":"\"/Users/ben/git/hms/develop/apps/shrine-app\"","shrine.queryEntryPoint.broadcasterServiceEndpoint.timeout.seconds":"1","spray.can.host-connector.client.chunkless-streaming":"\"off\"","akka.actor.mailbox.unbounded-queue-based.mailbox-type":"\"akka.dispatch.UnboundedMailbox\"","akka.actor.deployment.default.resizer.messages-per-resize":"10","akka.actor.router.type-mapping.balancing-pool":"\"akka.routing.BalancingPool\"","akka.actor.debug.autoreceive":"\"off\"","akka.io.pinned-dispatcher.type":"\"PinnedDispatcher\"","akka.actor.debug.unhandled":"\"off\"","akka.io.udp.selector-dispatcher":"\"akka.io.pinned-dispatcher\"","akka.actor.deployment.default.router":"\"from-code\"","akka.actor.debug.fsm":"\"off\"","shrine.networkStatusQuery":"\"\\\\\\\\SHRINE\\\\SHRINE\\\\Diagnoses\\\\Mental Illness\\\\Disorders usually diagnosed in infancy, childhood, or adolescence\\\\Pervasive developmental disorders\\\\Infantile autism, current or active state\\\\\"","java.library.path":"\"/Users/ben/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.\"","spray.can.client.parsing.max-chunk-ext-length":"256","akka.actor.router.type-mapping.from-code":"\"akka.routing.NoRouter\"","sun.cpu.isalist":"\"\"","akka.actor.router.type-mapping.broadcast-pool":"\"akka.routing.BroadcastPool\"","shrine.adapter.maxSignatureAge.minutes":"5","akka.io.udp.management-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.server.raw-request-uri-header":"\"off\"","akka.actor.deployment.default.resizer.enabled":"\"off\"","spray.can.client.parsing.max-response-reason-length":"64","spray.can.host-connector.pipelining":"\"off\"","spray.can.server.timeout-handler":"\"\"","shrine.adapter.audit.collectAdapterAudit":"true","slick.verifyTypes":"false","os.arch":"\"x86_64\"","spray.can.client.parsing.max-header-value-length":"\"8k\"","java.vm.version":"\"25.40-b25\"","spray.can.client.request-timeout":"\"20 s\"","spray.can.client.user-agent-header":"\"spray-can/1.3.3\"","spray.can.host-connector.client.parsing.header-cache.If-Unmodified-Since":"0","akka.actor.deployment.default.resizer.lower-bound":"1","spray.can.server.timeout-timeout":"\"2 s\"","akka.actor.serialization-bindings.\"[B\"":"\"bytes\"","akka.io.tcp.selector-association-retries":"10","spray.servlet.servlet-request-access":"\"off\"","spray.can.server.parsing.max-header-value-length":"\"8k\"","spray.can.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","akka.actor.router.type-mapping.scatter-gather-group":"\"akka.routing.ScatterGatherFirstCompletedGroup\"","slick.dumpPaths":"false","akka.actor.router.type-mapping.consistent-hashing-group":"\"akka.routing.ConsistentHashingGroup\"","spray.can.host-connector.idle-timeout":"\"30 s\"","shrine.ontEndpoint.url":"\"http://example.com:9090/i2b2/rest/OntologyService/\"","akka.io.udp-connected.select-timeout":"\"infinite\"","akka.io.udp.nr-of-selectors":"1","spray.can.host-connector.client.parsing.illegal-header-warnings":"\"on\"","akka.io.tcp.trace-logging":"\"off\"","akka.actor.deployment.default.within":"\"5 seconds\"","spray.can.client.connecting-timeout":"\"10s\"","akka.io.tcp.file-io-dispatcher":"\"akka.actor.default-dispatcher\"","shrine.queryEntryPoint.attachSigningCert":"true","spray.can.server.parsing.illegal-header-warnings":"\"on\"","java.endorsed.dirs":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/endorsed\"","java.runtime.version":"\"1.8.0_40-b27\"","spray.can.host-connector.client.parsing.max-chunk-ext-length":"256","akka.actor.debug.lifecycle":"\"off\"","java.vm.info":"\"mixed mode\"","spray.can.host-connector.max-connections":"4","shrine.hub.downstreamNodes.\"some hospital\"":"\"http://example.com/foo\"","akka.actor.debug.router-misconfiguration":"\"off\"","spray.routing.verbose-error-messages":"\"off\"","shrine.adapter.audit.database.dataSourceFrom":"\"JNDI\"","akka.actor.default-dispatcher.thread-pool-executor.core-pool-size-min":"8","akka.io.udp.max-channels":"4096","spray.can.server.reaping-cycle":"\"250 ms\"","shrine.adapter.crcEndpoint.timeout.seconds":"1","java.ext.dirs":"\"/Users/ben/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java\"","akka.actor.default-dispatcher.throughput-deadline-time":"\"0ms\"","spray.can.client.parsing.header-cache.Date":"0","spray.can.parsing.uri-parsing-mode":"\"strict\"","shrine.hub.downstreamNodes.CHB":"\"http://example.com/chb\"","shrine.keystore.file":"\"shrine.keystore\"","akka.actor.deployment.default.dispatcher":"\"\"","akka.io.tcp.management-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.host-connector.client.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","spray.can.client.max-encryption-chunk-size":"\"1m\"","java.runtime.name":"\"Java(TM) SE Runtime Environment\"","akka.actor.default-mailbox.mailbox-push-timeout-time":"\"10s\"","akka.actor.default-dispatcher.default-executor.fallback":"\"fork-join-executor\"","akka.actor.router.type-mapping.round-robin-group":"\"akka.routing.RoundRobinGroup\"","akka.io.udp.receive-throughput":"3","spray.can.host-connector.client.parsing.ssl-session-info-header":"\"off\"","spray.servlet.illegal-header-warnings":"\"on\"","akka.stdout-loglevel":"\"WARNING\"","shrine.adapter.audit.database.jndiDataSourceName":"\"java:comp/env/jdbc/adapterAuditDB\"","shrine.queryEntryPoint.authorizationType":"\"hms-steward\"","spray.can.server.parsing.max-chunk-size":"\"1m\"","file.separator":"\"/\"","spray.can.client.idle-timeout":"\"60 s\"","spray.can.server.parsing.header-cache.Date":"0","shrine.queryEntryPoint.sheriffEndpoint.timeout.seconds":"1","spray.can.host-connector.client.parsing.header-cache.If-None-Match":"0","spray.can.parsing.ssl-session-info-header":"\"off\"","spray.can.host-connector.client.idle-timeout":"\"60 s\"","akka.actor.router.type-mapping.consistent-hashing-pool":"\"akka.routing.ConsistentHashingPool\"","akka.io.tcp.batch-accept-limit":"10","shrine.keystore.privateKeyAlias":"\"test-cert\"","spray.can.client.parsing.header-cache.If-Match":"0","shrine.adapter.crcEndpoint.acceptAllCerts":"true","spray.can.parsing.header-cache.Date":"0","akka.io.udp.received-message-size-limit":"\"unlimited\"","spray.can.client.parsing.uri-parsing-mode":"\"strict\"","akka.actor.deployment.default.nr-of-instances":"1","spray.routing.file-get-conditional":"\"on\"","akka.actor.deployment.default.tail-chopping-router.interval":"\"10 milliseconds\"","spray.can.host-connector.client.parsing.header-cache.If-Modified-Since":"0","akka.actor.router.type-mapping.broadcast-group":"\"akka.routing.BroadcastGroup\"","akka.actor.mailbox.bounded-queue-based.mailbox-type":"\"akka.dispatch.BoundedMailbox\"","spray.can.server.parsing.header-cache.If-None-Match":"0","spray.can.parsing.header-cache.Content-MD5":"0","shrine.adapter.crcEndpoint.url":"\"http://services.i2b2.org/i2b2/rest/QueryToolService/\"","spray.can.server.unbind-timeout":"\"1s\"","akka.daemonic":"\"off\"","akka.io.udp-connected.max-channels":"4096","spray.can.server.ssl-tracing":"\"off\"","java.class.version":"\"52.0\"","shrine.queryEntryPoint.sheriffEndpoint.url":"\"http://localhost:8080/shrine-hms-authorization/queryAuthorization\"","akka.io.udp-connected.selector-dispatcher":"\"akka.io.pinned-dispatcher\"","spray.can.server.back-pressure.noack-rate":"10","spray.can.server.parsing.uri-parsing-mode":"\"strict\"","java.specification.name":"\"Java Platform API Specification\"","sun.boot.class.path":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/classes\"","spray.can.server.parsing.max-header-count":"64","akka.extensions":"[]","akka.actor.typed.timeout":"\"5s\"","spray.can.server.server-header":"\"spray-can/1.3.3\"","spray.can.server.request-chunk-aggregation-limit":"\"1m\"","akka.io.udp-connected.selector-association-retries":"10","akka.actor.serialize-messages":"\"off\"","spray.can.host-connector.client.parsing.max-content-length":"\"8m\"","spray.can.server.registration-timeout":"\"1s\"","shrine.pmEndpoint.url":"\"http://services.i2b2.org/i2b2/rest/PMService/getServices\"","akka.io.udp-connected.management-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.parsing.max-content-length":"\"8m\"","spray.can.server.back-pressure.reading-low-watermark":"\"infinite\"","spray.can.server.parsing.max-chunk-ext-length":"256","akka.loglevel":"\"INFO\"","spray.can.server.parsing.header-cache.If-Unmodified-Since":"0","spray.can.server.transparent-head-requests":"\"on\"","spray.can.client.parsing.header-cache.Content-MD5":"0","akka.io.tcp.selector-dispatcher":"\"akka.io.pinned-dispatcher\"","user.timezone":"\"America/New_York\"","spray.can.server.parsing.max-header-name-length":"64","spray.can.client.proxy.http":"\"default\"","spray.can.host-connector.max-retries":"5","spray.can.settings-group-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.server.parsing.header-cache.default":"12","spray.can.client.proxy.https":"\"default\"","spray.can.server.ssl-encryption":"\"off\"","spray.can.server.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","akka.actor.default-dispatcher.fork-join-executor.parallelism-factor":"3","shrine.queryEntryPoint.sheriffEndpoint.acceptAllCerts":"true","spray.can.host-connector-dispatcher":"\"akka.actor.default-dispatcher\"","java.specification.vendor":"\"Oracle Corporation\"","spray.can.host-connector.client.response-chunk-aggregation-limit":"\"1m\"","spray.can.server.parsing.max-content-length":"\"8m\"","spray.routing.file-chunking-threshold-size":"\"128k\"","akka.io.tcp.direct-buffer-pool-limit":"1000","sun.java.launcher":"\"SUN_STANDARD\"","spray.can.server.request-timeout":"\"20 s\"","akka.actor.default-dispatcher.shutdown-timeout":"\"1s\"","spray.can.server.parsing.max-response-reason-length":"64","os.version":"\"10.10.5\"","surefire.test.class.path":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/test-classes:/Users/ben/git/hms/develop/apps/shrine-app/target/classes:/Users/ben/.m2/repository/net/shrine/shrine-test-commons/1.21.0-SNAPSHOT/shrine-test-commons-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/sf/opencsv/opencsv/2.3/opencsv-2.3.jar:/Users/ben/.m2/repository/com/typesafe/config/1.2.1/config-1.2.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-util/1.21.0-SNAPSHOT/shrine-util-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/apache/commons/commons-email/1.2/commons-email-1.2.jar:/Users/ben/.m2/repository/javax/mail/mail/1.4.1/mail-1.4.1.jar:/Users/ben/.m2/repository/javax/activation/activation/1.1/activation-1.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-hms-core/1.21.0-SNAPSHOT/shrine-hms-core-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-auth/1.21.0-SNAPSHOT/shrine-auth-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-actor_2.11/2.3.8/akka-actor_2.11-2.3.8.jar:/Users/ben/.m2/repository/org/json4s/json4s-native_2.11/3.2.11/json4s-native_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-core_2.11/3.2.11/json4s-core_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-ast_2.11/3.2.11/json4s-ast_2.11-3.2.11.jar:/Users/ben/.m2/repository/io/spray/spray-client_2.11/1.3.3/spray-client_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-can_2.11/1.3.3/spray-can_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-io_2.11/1.3.3/spray-io_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-http_2.11/1.3.3/spray-http_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-scala_2.11/1.1.7/parboiled-scala_2.11-1.1.7.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-core/1.1.7/parboiled-core-1.1.7.jar:/Users/ben/.m2/repository/io/spray/spray-httpx_2.11/1.3.3/spray-httpx_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/jvnet/mimepull/mimepull/1.9.5/mimepull-1.9.5.jar:/Users/ben/.m2/repository/io/spray/spray-routing_2.11/1.3.3/spray-routing_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/chuusai/shapeless_2.11/1.2.4/shapeless_2.11-1.2.4.jar:/Users/ben/.m2/repository/io/spray/spray-servlet_2.11/1.3.3/spray-servlet_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-util_2.11/1.3.3/spray-util_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-slf4j_2.11/2.3.8/akka-slf4j_2.11-2.3.8.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-service/1.21.0-SNAPSHOT/shrine-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/squeryl/squeryl_2.11/0.9.6-RC4/squeryl_2.11-0.9.6-RC4.jar:/Users/ben/.m2/repository/cglib/cglib-nodep/2.2/cglib-nodep-2.2.jar:/Users/ben/.m2/repository/org/scala-lang/scalap/2.11.7/scalap-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-compiler/2.11.7/scala-compiler-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-parser-combinators_2.11/1.0.4/scala-parser-combinators_2.11-1.0.4.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-server/1.19/jersey-server-1.19.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-core/1.19/jersey-core-1.19.jar:/Users/ben/.m2/repository/javax/ws/rs/jsr311-api/1.1.1/jsr311-api-1.1.1.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-client/1.19/jersey-client-1.19.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-client-api/1.21.0-SNAPSHOT/shrine-adapter-client-api-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-data-commons/1.21.0-SNAPSHOT/shrine-data-commons-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/slick/slick_2.11/3.1.0/slick_2.11-3.1.0.jar:/Users/ben/.m2/repository/org/slf4j/slf4j-api/1.7.10/slf4j-api-1.7.10.jar:/Users/ben/.m2/repository/org/reactivestreams/reactive-streams/1.0.0/reactive-streams-1.0.0.jar:/Users/ben/.m2/repository/org/suecarter/freeslick_2.11/3.0.3.1/freeslick_2.11-3.0.3.1.jar:/Users/ben/.m2/repository/mysql/mysql-connector-java/5.1.37/mysql-connector-java-5.1.37.jar:/Users/ben/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-service/1.21.0-SNAPSHOT/shrine-broadcaster-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-service/1.21.0-SNAPSHOT/shrine-adapter-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-ont-support/1.21.0-SNAPSHOT/shrine-ont-support-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol-query/1.21.0-SNAPSHOT/shrine-protocol-query-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/liftweb/lift-json_2.11/2.6.2/lift-json_2.11-2.6.2.jar:/Users/ben/.m2/repository/com/thoughtworks/paranamer/paranamer/2.4.1/paranamer-2.4.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/org/scala-lang/scala-library/2.11.7/scala-library-2.11.7.jar:/Users/ben/.m2/repository/junit/junit/4.12/junit-4.12.jar:/Users/ben/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:/Users/ben/.m2/repository/org/scalatest/scalatest_2.11/2.2.5/scalatest_2.11-2.2.5.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-xml_2.11/1.0.2/scala-xml_2.11-1.0.2.jar:/Users/ben/.m2/repository/org/scala-lang/scala-actors/2.11.7/scala-actors-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-reflect/2.11.7/scala-reflect-2.11.7.jar:\"","spray.can.server.chunkless-streaming":"\"off\"","spray.can.client.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","spray.can.client.parsing.header-cache.If-Unmodified-Since":"0","spray.can.host-connector.max-redirects":"0","spray.can.host-connector.client.parsing.header-cache.User-Agent":"32","akka.io.udp.trace-logging":"\"off\"","akka.io.udp-connected.receive-throughput":"3","spray.can.parsing.max-header-count":"64","shrine.hiveCredentials.crcProjectId":"\"Demo\"","akka.actor.reaper-interval":"\"5s\"","spray.can.parsing.illegal-header-warnings":"\"on\"","spray.can.parsing.max-response-reason-length":"64","shrine.adapter.audit.database.slickProfileClassName":"\"slick.driver.MySQLDriver$\"","sun.os.patch.level":"\"unknown\"","spray.can.parsing.header-cache.If-Range":"0","gopherProxySet":"\"false\"","akka.actor.default-dispatcher.thread-pool-executor.core-pool-size-factor":"3","spray.can.parsing.header-cache.If-Modified-Since":"0","shrine.queryEntryPoint.includeAggregateResults":"false","akka.actor.router.type-mapping.tail-chopping-pool":"\"akka.routing.TailChoppingPool\"","surefire.real.class.path":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefirebooter3319812089896967790.jar\"","shrine.ontEndpoint.timeout.seconds":"1","shrine.queryEntryPoint.authenticationType":"\"ecommons\"","akka.actor.default-dispatcher.thread-pool-executor.max-pool-size-factor":"3","shrine.adapter.create":"true","akka.actor.default-dispatcher.fork-join-executor.parallelism-max":"64","shrine.queryEntryPoint.audit.database.jndiDataSourceName":"\"java:comp/env/jdbc/qepAuditDB\"","spray.can.host-connector.client.user-agent-header":"\"spray-can/1.3.3\"","akka.actor.deployment.default.mailbox":"\"\"","java.vm.specification.vendor":"\"Oracle Corporation\"","spray.can.host-connector.client.parsing.header-cache.If-Range":"0","slick.ansiDump":"false","spray.can.client.parsing.header-cache.If-None-Match":"0","akka.home":"\"\"","akka.actor.provider":"\"akka.actor.LocalActorRefProvider\"","spray.can.client.response-chunk-aggregation-limit":"\"1m\"","akka.io.tcp.max-received-message-size":"\"unlimited\"","akka.actor.mailbox.requirements.\"akka.dispatch.BoundedMessageQueueSemantics\"":"\"akka.actor.mailbox.bounded-queue-based\"","shrine.adapter.adapterMappingsFileName":"\"AdapterMappings.xml\"","spray.can.parsing.header-cache.If-Match":"0","akka.actor.default-dispatcher.mailbox-requirement":"\"\"","akka.actor.serializers.bytes":"\"akka.serialization.ByteArraySerializer\"","akka.actor.default-dispatcher.executor":"\"default-executor\"","akka.actor.debug.receive":"\"off\"","akka.actor.default-dispatcher.thread-pool-executor.max-pool-size-max":"64","spray.can.parsing.max-uri-length":"\"2k\"","user.country":"\"US\"","shrine.hiveCredentials.domain":"\"HarvardDemo\"","shrine.keystore.keyStoreType":"\"PKCS12\"","sun.jnu.encoding":"\"UTF-8\"","shrine.hub.maxQueryWaitTime.minutes":"4.5","akka.version":"\"2.3.8\"","shrine.queryEntryPoint.broadcasterServiceEndpoint.url":"\"http://example.com/shrine/rest/broadcaster/broadcast\"","user.language":"\"en\"","spray.can.host-connector.client.connecting-timeout":"\"10s\"","akka.scheduler.shutdown-timeout":"\"5s\"","spray.can.client.parsing.illegal-header-warnings":"\"on\"","akka.actor.default-dispatcher.attempt-teamwork":"\"on\"","spray.servlet.uri-parsing-mode":"\"relaxed\"","akka.io.tcp.max-channels":"256000","shrine.keystore.caCertAliases":"[\n # shrine.conf: 117\n \"carra ca\"\n]","slick.sqlIndent":"false","akka.actor.default-dispatcher.thread-pool-executor.max-pool-size-min":"8","akka.actor.default-dispatcher.thread-pool-executor.task-queue-type":"\"linked\"","spray.servlet.timeout-handler":"\"\"","spray.can.server.verbose-error-messages":"\"off\"","shrine.humanReadableNodeName":"\"SHRINE Cell\"","akka.io.tcp.file-io-transferTo-limit":"\"512 KiB\"","akka.loggers":"[\n # reference.conf: 17\n \"akka.event.Logging$DefaultLogger\"\n]","localRepository":"\"/Users/ben/.m2/repository\"","spray.can.host-connector.client.parsing.header-cache.default":"12","spray.can.parsing.header-cache.If-None-Match":"0","spray.servlet.root-path":"\"AUTO\"","akka.io.tcp.finish-connect-retries":"5","spray.can.client.parsing.header-cache.If-Range":"0","akka.actor.mailbox.requirements.\"akka.dispatch.BoundedDequeBasedMessageQueueSemantics\"":"\"akka.actor.mailbox.bounded-deque-based\"","akka.actor.default-dispatcher.throughput":"5","akka.actor.mailbox.requirements.\"akka.dispatch.UnboundedMessageQueueSemantics\"":"\"akka.actor.mailbox.unbounded-queue-based\"","akka.scheduler.implementation":"\"akka.actor.LightArrayRevolverScheduler\"","shrine.adapter.audit.database.createTablesOnStart":"false","akka.actor.default-mailbox.mailbox-capacity":"1000","spray.can.server.parsing.header-cache.Content-MD5":"0","shrine.hub.shouldQuerySelf":"true","spray.routing.range-coalescing-threshold":"80","akka.actor.router.type-mapping.random-pool":"\"akka.routing.RandomPool\"","spray.can.host-connector.client.parsing.max-header-count":"64","spray.can.parsing.header-cache.default":"12","shrine.shrineDatabaseType":"\"mysql\"","shrine.queryEntryPoint.sheriffCredentials.username":"\"sheriffUsername\"","spray.can.server.parsing.header-cache.If-Modified-Since":"0","java.awt.printerjob":"\"sun.lwawt.macosx.CPrinterJob\"","java.awt.graphicsenv":"\"sun.awt.CGraphicsEnvironment\"","spray.can.host-connector.client.proxy.http":"\"default\"","spray.servlet.request-timeout":"\"30 s\"","spray.can.host-connector.client.parsing.max-response-reason-length":"64","akka.io.pinned-dispatcher.thread-pool-executor.allow-core-pool-timeout":"\"off\"","akka.actor.guardian-supervisor-strategy":"\"akka.actor.DefaultSupervisorStrategy\"","spray.routing.range-count-limit":"16","akka.actor.serialization-bindings.\"java.io.Serializable\"":"\"java\"","akka.actor.default-dispatcher.thread-pool-executor.keep-alive-time":"\"60s\"","awt.toolkit":"\"sun.lwawt.macosx.LWCToolkit\"","spray.can.server.parsing.header-cache.If-Match":"0","akka.io.udp.direct-buffer-size":"\"128 KiB\"","java.class.path":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/test-classes:/Users/ben/git/hms/develop/apps/shrine-app/target/classes:/Users/ben/.m2/repository/net/shrine/shrine-test-commons/1.21.0-SNAPSHOT/shrine-test-commons-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/sf/opencsv/opencsv/2.3/opencsv-2.3.jar:/Users/ben/.m2/repository/com/typesafe/config/1.2.1/config-1.2.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-util/1.21.0-SNAPSHOT/shrine-util-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/apache/commons/commons-email/1.2/commons-email-1.2.jar:/Users/ben/.m2/repository/javax/mail/mail/1.4.1/mail-1.4.1.jar:/Users/ben/.m2/repository/javax/activation/activation/1.1/activation-1.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-hms-core/1.21.0-SNAPSHOT/shrine-hms-core-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-auth/1.21.0-SNAPSHOT/shrine-auth-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-actor_2.11/2.3.8/akka-actor_2.11-2.3.8.jar:/Users/ben/.m2/repository/org/json4s/json4s-native_2.11/3.2.11/json4s-native_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-core_2.11/3.2.11/json4s-core_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-ast_2.11/3.2.11/json4s-ast_2.11-3.2.11.jar:/Users/ben/.m2/repository/io/spray/spray-client_2.11/1.3.3/spray-client_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-can_2.11/1.3.3/spray-can_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-io_2.11/1.3.3/spray-io_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-http_2.11/1.3.3/spray-http_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-scala_2.11/1.1.7/parboiled-scala_2.11-1.1.7.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-core/1.1.7/parboiled-core-1.1.7.jar:/Users/ben/.m2/repository/io/spray/spray-httpx_2.11/1.3.3/spray-httpx_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/jvnet/mimepull/mimepull/1.9.5/mimepull-1.9.5.jar:/Users/ben/.m2/repository/io/spray/spray-routing_2.11/1.3.3/spray-routing_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/chuusai/shapeless_2.11/1.2.4/shapeless_2.11-1.2.4.jar:/Users/ben/.m2/repository/io/spray/spray-servlet_2.11/1.3.3/spray-servlet_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-util_2.11/1.3.3/spray-util_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-slf4j_2.11/2.3.8/akka-slf4j_2.11-2.3.8.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-service/1.21.0-SNAPSHOT/shrine-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/squeryl/squeryl_2.11/0.9.6-RC4/squeryl_2.11-0.9.6-RC4.jar:/Users/ben/.m2/repository/cglib/cglib-nodep/2.2/cglib-nodep-2.2.jar:/Users/ben/.m2/repository/org/scala-lang/scalap/2.11.7/scalap-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-compiler/2.11.7/scala-compiler-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-parser-combinators_2.11/1.0.4/scala-parser-combinators_2.11-1.0.4.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-server/1.19/jersey-server-1.19.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-core/1.19/jersey-core-1.19.jar:/Users/ben/.m2/repository/javax/ws/rs/jsr311-api/1.1.1/jsr311-api-1.1.1.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-client/1.19/jersey-client-1.19.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-client-api/1.21.0-SNAPSHOT/shrine-adapter-client-api-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-data-commons/1.21.0-SNAPSHOT/shrine-data-commons-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/slick/slick_2.11/3.1.0/slick_2.11-3.1.0.jar:/Users/ben/.m2/repository/org/slf4j/slf4j-api/1.7.10/slf4j-api-1.7.10.jar:/Users/ben/.m2/repository/org/reactivestreams/reactive-streams/1.0.0/reactive-streams-1.0.0.jar:/Users/ben/.m2/repository/org/suecarter/freeslick_2.11/3.0.3.1/freeslick_2.11-3.0.3.1.jar:/Users/ben/.m2/repository/mysql/mysql-connector-java/5.1.37/mysql-connector-java-5.1.37.jar:/Users/ben/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-service/1.21.0-SNAPSHOT/shrine-broadcaster-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-service/1.21.0-SNAPSHOT/shrine-adapter-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-ont-support/1.21.0-SNAPSHOT/shrine-ont-support-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol-query/1.21.0-SNAPSHOT/shrine-protocol-query-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/liftweb/lift-json_2.11/2.6.2/lift-json_2.11-2.6.2.jar:/Users/ben/.m2/repository/com/thoughtworks/paranamer/paranamer/2.4.1/paranamer-2.4.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/org/scala-lang/scala-library/2.11.7/scala-library-2.11.7.jar:/Users/ben/.m2/repository/junit/junit/4.12/junit-4.12.jar:/Users/ben/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:/Users/ben/.m2/repository/org/scalatest/scalatest_2.11/2.2.5/scalatest_2.11-2.2.5.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-xml_2.11/1.0.2/scala-xml_2.11-1.0.2.jar:/Users/ben/.m2/repository/org/scala-lang/scala-actors/2.11.7/scala-actors-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-reflect/2.11.7/scala-reflect-2.11.7.jar:\"","akka.actor.default-dispatcher.thread-pool-executor.core-pool-size-max":"64","akka.io.udp.select-timeout":"\"infinite\"","spray.can.host-connector.client.proxy.https":"\"default\"","akka.actor.router.type-mapping.scatter-gather-pool":"\"akka.routing.ScatterGatherFirstCompletedPool\"","akka.actor.deployment.default.resizer.pressure-threshold":"1","spray.can.client.request-header-size-hint":"512","spray.can.server.idle-timeout":"\"60 s\"","akka.io.udp-connected.trace-logging":"\"off\"","os.name":"\"Mac OS X\"","slick.detectRebuild":"false","shrine.queryEntryPoint.audit.database.slickProfileClassName":"\"slick.driver.MySQLDriver$\"","spray.can.server.pipelining-limit":"1","spray.can.host-connector.client.request-timeout":"\"20 s\"","akka.actor.mailbox.requirements.\"akka.dispatch.MultipleConsumerSemantics\"":"\"akka.actor.mailbox.unbounded-queue-based\"","spray.can.server.max-encryption-chunk-size":"\"1m\"","spray.can.host-connector.client.parsing.max-chunk-size":"\"1m\"","spray.can.server.parsing-error-abort-timeout":"\"2s\"","java.vm.vendor":"\"Oracle Corporation\"","akka.io.udp-connected.direct-buffer-pool-limit":"1000","spray.can.server.automatic-back-pressure-handling":"\"on\"","spray.can.server.verbose-error-logging":"\"off\"","spray.can.host-connector.client.reaping-cycle":"\"250 ms\"","spray.can.host-connector.client.parsing.max-header-name-length":"64","akka.actor.router.type-mapping.round-robin-pool":"\"akka.routing.RoundRobinPool\"","spray.can.connection-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.server.stats-support":"\"on\"","slick.unicodeDump":"false","java.vendor.url.bug":"\"http://bugreport.sun.com/bugreport/\"","akka.io.udp-connected.worker-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.client.reaping-cycle":"\"250 ms\"","akka.io.udp-connected.direct-buffer-size":"\"128 KiB\"","spray.can.server.response-header-size-hint":"512","spray.can.parsing.max-header-name-length":"64","spray.can.host-connector.client.parsing.header-cache.Content-MD5":"0","akka.actor.deployment.default.resizer.upper-bound":"10","akka.actor.default-dispatcher.thread-pool-executor.allow-core-timeout":"\"on\"","user.name":"\"ben\"","akka.actor.mailbox.unbounded-deque-based.mailbox-type":"\"akka.dispatch.UnboundedDequeBasedMailbox\"","akka.actor.default-dispatcher.thread-pool-executor.task-queue-size":"-1","akka.logger-startup-timeout":"\"5s\"","akka.actor.mailbox.bounded-deque-based.mailbox-type":"\"akka.dispatch.BoundedDequeBasedMailbox\"","spray.can.client.parsing.max-header-name-length":"64","spray.can.host-connector.client.ssl-tracing":"\"off\"","spray.can.parsing.header-cache.If-Unmodified-Since":"0","spray.servlet.verbose-error-messages":"\"off\"","shrine.adapter.immediatelyRunIncomingQueries":"false","akka.io.tcp.nr-of-selectors":"1","akka.actor.router.type-mapping.smallest-mailbox-pool":"\"akka.routing.SmallestMailboxPool\"","java.vm.name":"\"Java HotSpot(TM) 64-Bit Server VM\"","akka.io.tcp.windows-connection-abort-workaround-enabled":"\"auto\"","akka.actor.router.type-mapping.tail-chopping-group":"\"akka.routing.TailChoppingGroup\"","spray.routing.render-vanity-footer":"\"yes\"","sun.java.command":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefirebooter3319812089896967790.jar /Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefire6518106528052987897tmp /Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefire_0114067395742935233tmp\"","spray.can.server.remote-address-header":"\"off\"","akka.actor.deployment.default.resizer.backoff-rate":"0.1","akka.actor.debug.event-stream":"\"off\"","spray.version":"\"1.3.3\"","spray.can.host-connector.client.parsing.max-header-value-length":"\"8k\"","java.home":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre\"","akka.io.udp-connected.nr-of-selectors":"1","spray.can.host-connector.client.parsing.max-uri-length":"\"2k\"","spray.can.parsing.header-cache.User-Agent":"32","akka.io.pinned-dispatcher.executor":"\"thread-pool-executor\"","spray.can.server.bind-timeout":"\"1s\"","akka.log-dead-letters":"10","java.version":"\"1.8.0_40\"","sun.io.unicode.encoding":"\"UnicodeBig\"","spray.can.server.default-host-header":"\"\""}}
\ No newline at end of file
+{"keyValues":{"spray.can.server.parsing.ssl-session-info-header":"\"off\"","spray.can.listener-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.client.parsing.header-cache.default":"12","java.io.tmpdir":"\"/var/folders/lt/6x60mpg92w994pbhxl8q4dyc0000gp/T/\"","akka.actor.mailbox.requirements.\"akka.dispatch.UnboundedDequeBasedMessageQueueSemantics\"":"\"akka.actor.mailbox.unbounded-deque-based\"","shrine.queryEntryPoint.audit.database.createTablesOnStart":"false","spray.can.host-connector.client.request-header-size-hint":"512","spray.can.client.parsing.header-cache.If-Modified-Since":"0","akka.actor.deployment.default.routees.paths":"[]","shrine.ontEndpoint.acceptAllCerts":"true","spray.routing.file-chunking-chunk-size":"\"128k\"","akka.io.udp.direct-buffer-pool-limit":"1000","shrine.queryEntryPoint.maxQueryWaitTime.minutes":"5","akka.actor.unstarted-push-timeout":"\"10s\"","spray.can.parsing.max-chunk-ext-length":"256","spray.can.client.parsing.max-content-length":"\"8m\"","line.separator":"\"\\n\"","spray.can.server.parsing.header-cache.User-Agent":"32","spray.can.server.parsing.max-uri-length":"\"2k\"","spray.servlet.timeout-timeout":"\"500 ms\"","akka.actor.default-mailbox.mailbox-type":"\"akka.dispatch.UnboundedMailbox\"","spray.servlet.max-content-length":"\"5 m\"","shrine.queryEntryPoint.audit.database.dataSourceFrom":"\"JNDI\"","akka.actor.serializers.java":"\"akka.serialization.JavaSerializer\"","akka.actor.dsl.default-timeout":"\"5s\"","shrine.adapter.setSizeObfuscation":"true","shrine.queryEntryPoint.audit.collectQepAudit":"true","shrine.pmEndpoint.acceptAllCerts":"true","spray.can.server.chunkhandler-registration-timeout":"\"500 ms\"","akka.actor.serialize-creators":"\"off\"","akka.actor.deployment.default.virtual-nodes-factor":"10","shrine.queryEntryPoint.broadcasterServiceEndpoint.acceptAllCerts":"true","shrine.queryEntryPoint.create":"true","path.separator":"\":\"","spray.can.host-connector.client.parsing.header-cache.If-Match":"0","spray.can.host-connector.client.max-encryption-chunk-size":"\"1m\"","spray.can.client.parsing.max-header-count":"64","spray.can.manager-dispatcher":"\"akka.actor.default-dispatcher\"","akka.io.tcp.worker-dispatcher":"\"akka.actor.default-dispatcher\"","akka.jvm-exit-on-fatal-error":"\"on\"","basedir":"\"/Users/ben/git/hms/develop/apps/shrine-app\"","akka.io.udp.worker-dispatcher":"\"akka.actor.default-dispatcher\"","spray.servlet.boot-class":"\"\"","spray.can.client.parsing.max-chunk-size":"\"1m\"","shrine.hiveCredentials.username":"\"demo\"","shrine.adapter.adapterLockoutAttemptsThreshold":"10","sun.management.compiler":"\"HotSpot 64-Bit Tiered Compilers\"","akka.actor.default-dispatcher.fork-join-executor.parallelism-min":"8","akka.io.udp.selector-association-retries":"10","akka.scheduler.tick-duration":"\"10ms\"","spray.can.host-connector.client.parsing.uri-parsing-mode":"\"strict\"","spray.can.client.parsing.max-uri-length":"\"2k\"","shrine.hub.downstreamNodes.PHS":"\"http://example.com/phs\"","sun.cpu.endian":"\"little\"","akka.actor.mailbox.requirements.\"akka.dispatch.DequeBasedMessageQueueSemantics\"":"\"akka.actor.mailbox.unbounded-deque-based\"","spray.can.parsing.max-chunk-size":"\"1m\"","spray.can.server.parsing.header-cache.If-Range":"0","spray.can.client.parsing.ssl-session-info-header":"\"off\"","akka.io.tcp.register-timeout":"\"5s\"","java.specification.version":"\"1.8\"","shrine.hub.create":"true","akka.actor.creation-timeout":"\"20s\"","spray.can.host-connector.client.parsing.header-cache.Date":"0","java.vm.specification.name":"\"Java Virtual Machine Specification\"","java.vm.specification.version":"\"1.8\"","shrine.pmEndpoint.timeout.seconds":"1","akka.actor.router.type-mapping.random-group":"\"akka.routing.RandomGroup\"","shrine.hiveCredentials.ontProjectId":"\"SHRINE\"","user.home":"\"/Users/ben\"","akka.log-dead-letters-during-shutdown":"\"on\"","file.encoding.pkg":"\"sun.io\"","spray.can.client.chunkless-streaming":"\"off\"","akka.scheduler.ticks-per-wheel":"512","akka.actor.dsl.inbox-size":"1000","spray.can.parsing.max-header-value-length":"\"8k\"","akka.actor.default-mailbox.stash-capacity":"-1","spray.can.client.ssl-tracing":"\"off\"","sun.arch.data.model":"\"64\"","akka.io.udp-connected.received-message-size-limit":"\"unlimited\"","akka.log-config-on-start":"\"off\"","akka.io.tcp.direct-buffer-size":"\"128 KiB\"","akka.actor.default-dispatcher.type":"\"Dispatcher\"","akka.actor.deployment.default.resizer.rampup-rate":"0.2","spray.servlet.remote-address-header":"\"off\"","sun.boot.library.path":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib\"","spray.can.client.parsing.header-cache.User-Agent":"32","akka.actor.deployment.default.resizer.backoff-threshold":"0.3","user.dir":"\"/Users/ben/git/hms/develop/apps/shrine-app\"","shrine.queryEntryPoint.broadcasterServiceEndpoint.timeout.seconds":"1","spray.can.host-connector.client.chunkless-streaming":"\"off\"","akka.actor.mailbox.unbounded-queue-based.mailbox-type":"\"akka.dispatch.UnboundedMailbox\"","akka.actor.deployment.default.resizer.messages-per-resize":"10","akka.actor.router.type-mapping.balancing-pool":"\"akka.routing.BalancingPool\"","akka.actor.debug.autoreceive":"\"off\"","akka.io.pinned-dispatcher.type":"\"PinnedDispatcher\"","akka.actor.debug.unhandled":"\"off\"","akka.io.udp.selector-dispatcher":"\"akka.io.pinned-dispatcher\"","akka.actor.deployment.default.router":"\"from-code\"","akka.actor.debug.fsm":"\"off\"","shrine.networkStatusQuery":"\"\\\\\\\\SHRINE\\\\SHRINE\\\\Diagnoses\\\\Mental Illness\\\\Disorders usually diagnosed in infancy, childhood, or adolescence\\\\Pervasive developmental disorders\\\\Infantile autism, current or active state\\\\\"","java.library.path":"\"/Users/ben/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.\"","spray.can.client.parsing.max-chunk-ext-length":"256","akka.actor.router.type-mapping.from-code":"\"akka.routing.NoRouter\"","sun.cpu.isalist":"\"\"","akka.actor.router.type-mapping.broadcast-pool":"\"akka.routing.BroadcastPool\"","shrine.adapter.maxSignatureAge.minutes":"5","akka.io.udp.management-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.server.raw-request-uri-header":"\"off\"","akka.actor.deployment.default.resizer.enabled":"\"off\"","spray.can.client.parsing.max-response-reason-length":"64","spray.can.host-connector.pipelining":"\"off\"","spray.can.server.timeout-handler":"\"\"","shrine.adapter.audit.collectAdapterAudit":"true","slick.verifyTypes":"false","os.arch":"\"x86_64\"","spray.can.client.parsing.max-header-value-length":"\"8k\"","java.vm.version":"\"25.40-b25\"","spray.can.client.request-timeout":"\"20 s\"","spray.can.client.user-agent-header":"\"spray-can/1.3.3\"","spray.can.host-connector.client.parsing.header-cache.If-Unmodified-Since":"0","akka.actor.deployment.default.resizer.lower-bound":"1","spray.can.server.timeout-timeout":"\"2 s\"","akka.actor.serialization-bindings.\"[B\"":"\"bytes\"","akka.io.tcp.selector-association-retries":"10","spray.servlet.servlet-request-access":"\"off\"","spray.can.server.parsing.max-header-value-length":"\"8k\"","spray.can.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","akka.actor.router.type-mapping.scatter-gather-group":"\"akka.routing.ScatterGatherFirstCompletedGroup\"","slick.dumpPaths":"false","akka.actor.router.type-mapping.consistent-hashing-group":"\"akka.routing.ConsistentHashingGroup\"","spray.can.host-connector.idle-timeout":"\"30 s\"","shrine.ontEndpoint.url":"\"http://example.com:9090/i2b2/rest/OntologyService/\"","akka.io.udp-connected.select-timeout":"\"infinite\"","akka.io.udp.nr-of-selectors":"1","spray.can.host-connector.client.parsing.illegal-header-warnings":"\"on\"","akka.io.tcp.trace-logging":"\"off\"","akka.actor.deployment.default.within":"\"5 seconds\"","spray.can.client.connecting-timeout":"\"10s\"","akka.io.tcp.file-io-dispatcher":"\"akka.actor.default-dispatcher\"","shrine.queryEntryPoint.attachSigningCert":"true","spray.can.server.parsing.illegal-header-warnings":"\"on\"","java.endorsed.dirs":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/endorsed\"","java.runtime.version":"\"1.8.0_40-b27\"","spray.can.host-connector.client.parsing.max-chunk-ext-length":"256","akka.actor.debug.lifecycle":"\"off\"","java.vm.info":"\"mixed mode\"","spray.can.host-connector.max-connections":"4","shrine.hub.downstreamNodes.\"some hospital\"":"\"http://example.com/foo\"","akka.actor.debug.router-misconfiguration":"\"off\"","spray.routing.verbose-error-messages":"\"off\"","shrine.adapter.audit.database.dataSourceFrom":"\"JNDI\"","akka.actor.default-dispatcher.thread-pool-executor.core-pool-size-min":"8","akka.io.udp.max-channels":"4096","spray.can.server.reaping-cycle":"\"250 ms\"","shrine.adapter.crcEndpoint.timeout.seconds":"1","java.ext.dirs":"\"/Users/ben/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java\"","akka.actor.default-dispatcher.throughput-deadline-time":"\"0ms\"","spray.can.client.parsing.header-cache.Date":"0","spray.can.parsing.uri-parsing-mode":"\"strict\"","shrine.hub.downstreamNodes.CHB":"\"http://example.com/chb\"","shrine.keystore.file":"\"shrine.keystore\"","akka.actor.deployment.default.dispatcher":"\"\"","akka.io.tcp.management-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.host-connector.client.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","spray.can.client.max-encryption-chunk-size":"\"1m\"","java.runtime.name":"\"Java(TM) SE Runtime Environment\"","akka.actor.default-mailbox.mailbox-push-timeout-time":"\"10s\"","akka.actor.default-dispatcher.default-executor.fallback":"\"fork-join-executor\"","akka.actor.router.type-mapping.round-robin-group":"\"akka.routing.RoundRobinGroup\"","akka.io.udp.receive-throughput":"3","spray.can.host-connector.client.parsing.ssl-session-info-header":"\"off\"","spray.servlet.illegal-header-warnings":"\"on\"","akka.stdout-loglevel":"\"WARNING\"","shrine.adapter.audit.database.jndiDataSourceName":"\"java:comp/env/jdbc/adapterAuditDB\"","shrine.queryEntryPoint.authorizationType":"\"hms-steward\"","spray.can.server.parsing.max-chunk-size":"\"1m\"","file.separator":"\"/\"","spray.can.client.idle-timeout":"\"60 s\"","spray.can.server.parsing.header-cache.Date":"0","shrine.queryEntryPoint.sheriffEndpoint.timeout.seconds":"1","spray.can.host-connector.client.parsing.header-cache.If-None-Match":"0","spray.can.parsing.ssl-session-info-header":"\"off\"","spray.can.host-connector.client.idle-timeout":"\"60 s\"","akka.actor.router.type-mapping.consistent-hashing-pool":"\"akka.routing.ConsistentHashingPool\"","akka.io.tcp.batch-accept-limit":"10","shrine.keystore.privateKeyAlias":"\"test-cert\"","spray.can.client.parsing.header-cache.If-Match":"0","shrine.adapter.crcEndpoint.acceptAllCerts":"true","spray.can.parsing.header-cache.Date":"0","akka.io.udp.received-message-size-limit":"\"unlimited\"","spray.can.client.parsing.uri-parsing-mode":"\"strict\"","akka.actor.deployment.default.nr-of-instances":"1","spray.routing.file-get-conditional":"\"on\"","akka.actor.deployment.default.tail-chopping-router.interval":"\"10 milliseconds\"","spray.can.host-connector.client.parsing.header-cache.If-Modified-Since":"0","akka.actor.router.type-mapping.broadcast-group":"\"akka.routing.BroadcastGroup\"","akka.actor.mailbox.bounded-queue-based.mailbox-type":"\"akka.dispatch.BoundedMailbox\"","spray.can.server.parsing.header-cache.If-None-Match":"0","spray.can.parsing.header-cache.Content-MD5":"0","shrine.adapter.crcEndpoint.url":"\"http://services.i2b2.org/i2b2/rest/QueryToolService/\"","spray.can.server.unbind-timeout":"\"1s\"","akka.daemonic":"\"off\"","akka.io.udp-connected.max-channels":"4096","spray.can.server.ssl-tracing":"\"off\"","java.class.version":"\"52.0\"","shrine.queryEntryPoint.sheriffEndpoint.url":"\"http://localhost:8080/shrine-hms-authorization/queryAuthorization\"","akka.io.udp-connected.selector-dispatcher":"\"akka.io.pinned-dispatcher\"","spray.can.server.back-pressure.noack-rate":"10","spray.can.server.parsing.uri-parsing-mode":"\"strict\"","java.specification.name":"\"Java Platform API Specification\"","sun.boot.class.path":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/classes\"","spray.can.server.parsing.max-header-count":"64","akka.extensions":"[]","akka.actor.typed.timeout":"\"5s\"","spray.can.server.server-header":"\"spray-can/1.3.3\"","spray.can.server.request-chunk-aggregation-limit":"\"1m\"","akka.io.udp-connected.selector-association-retries":"10","akka.actor.serialize-messages":"\"off\"","spray.can.host-connector.client.parsing.max-content-length":"\"8m\"","spray.can.server.registration-timeout":"\"1s\"","shrine.pmEndpoint.url":"\"http://services.i2b2.org/i2b2/rest/PMService/getServices\"","akka.io.udp-connected.management-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.parsing.max-content-length":"\"8m\"","spray.can.server.back-pressure.reading-low-watermark":"\"infinite\"","spray.can.server.parsing.max-chunk-ext-length":"256","akka.loglevel":"\"INFO\"","spray.can.server.parsing.header-cache.If-Unmodified-Since":"0","spray.can.server.transparent-head-requests":"\"on\"","spray.can.client.parsing.header-cache.Content-MD5":"0","akka.io.tcp.selector-dispatcher":"\"akka.io.pinned-dispatcher\"","user.timezone":"\"America/New_York\"","spray.can.server.parsing.max-header-name-length":"64","spray.can.client.proxy.http":"\"default\"","spray.can.host-connector.max-retries":"5","spray.can.settings-group-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.server.parsing.header-cache.default":"12","spray.can.client.proxy.https":"\"default\"","spray.can.server.ssl-encryption":"\"off\"","spray.can.server.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","akka.actor.default-dispatcher.fork-join-executor.parallelism-factor":"3","shrine.queryEntryPoint.sheriffEndpoint.acceptAllCerts":"true","spray.can.host-connector-dispatcher":"\"akka.actor.default-dispatcher\"","java.specification.vendor":"\"Oracle Corporation\"","spray.can.host-connector.client.response-chunk-aggregation-limit":"\"1m\"","spray.can.server.parsing.max-content-length":"\"8m\"","spray.routing.file-chunking-threshold-size":"\"128k\"","akka.io.tcp.direct-buffer-pool-limit":"1000","sun.java.launcher":"\"SUN_STANDARD\"","spray.can.server.request-timeout":"\"20 s\"","akka.actor.default-dispatcher.shutdown-timeout":"\"1s\"","spray.can.server.parsing.max-response-reason-length":"64","os.version":"\"10.10.5\"","surefire.test.class.path":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/test-classes:/Users/ben/git/hms/develop/apps/shrine-app/target/classes:/Users/ben/.m2/repository/net/shrine/shrine-test-commons/1.21.0-SNAPSHOT/shrine-test-commons-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/sf/opencsv/opencsv/2.3/opencsv-2.3.jar:/Users/ben/.m2/repository/com/typesafe/config/1.2.1/config-1.2.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-util/1.21.0-SNAPSHOT/shrine-util-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/apache/commons/commons-email/1.2/commons-email-1.2.jar:/Users/ben/.m2/repository/javax/mail/mail/1.4.1/mail-1.4.1.jar:/Users/ben/.m2/repository/javax/activation/activation/1.1/activation-1.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-hms-core/1.21.0-SNAPSHOT/shrine-hms-core-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-auth/1.21.0-SNAPSHOT/shrine-auth-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-actor_2.11/2.3.8/akka-actor_2.11-2.3.8.jar:/Users/ben/.m2/repository/org/json4s/json4s-native_2.11/3.2.11/json4s-native_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-core_2.11/3.2.11/json4s-core_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-ast_2.11/3.2.11/json4s-ast_2.11-3.2.11.jar:/Users/ben/.m2/repository/io/spray/spray-client_2.11/1.3.3/spray-client_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-can_2.11/1.3.3/spray-can_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-io_2.11/1.3.3/spray-io_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-http_2.11/1.3.3/spray-http_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-scala_2.11/1.1.7/parboiled-scala_2.11-1.1.7.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-core/1.1.7/parboiled-core-1.1.7.jar:/Users/ben/.m2/repository/io/spray/spray-httpx_2.11/1.3.3/spray-httpx_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/jvnet/mimepull/mimepull/1.9.5/mimepull-1.9.5.jar:/Users/ben/.m2/repository/io/spray/spray-routing_2.11/1.3.3/spray-routing_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/chuusai/shapeless_2.11/1.2.4/shapeless_2.11-1.2.4.jar:/Users/ben/.m2/repository/io/spray/spray-servlet_2.11/1.3.3/spray-servlet_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-util_2.11/1.3.3/spray-util_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-slf4j_2.11/2.3.8/akka-slf4j_2.11-2.3.8.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-qep/1.21.0-SNAPSHOT/shrine-qep-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/squeryl/squeryl_2.11/0.9.6-RC4/squeryl_2.11-0.9.6-RC4.jar:/Users/ben/.m2/repository/cglib/cglib-nodep/2.2/cglib-nodep-2.2.jar:/Users/ben/.m2/repository/org/scala-lang/scalap/2.11.7/scalap-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-compiler/2.11.7/scala-compiler-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-parser-combinators_2.11/1.0.4/scala-parser-combinators_2.11-1.0.4.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-server/1.19/jersey-server-1.19.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-core/1.19/jersey-core-1.19.jar:/Users/ben/.m2/repository/javax/ws/rs/jsr311-api/1.1.1/jsr311-api-1.1.1.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-client/1.19/jersey-client-1.19.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-client-api/1.21.0-SNAPSHOT/shrine-adapter-client-api-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-data-commons/1.21.0-SNAPSHOT/shrine-data-commons-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/slick/slick_2.11/3.1.0/slick_2.11-3.1.0.jar:/Users/ben/.m2/repository/org/slf4j/slf4j-api/1.7.10/slf4j-api-1.7.10.jar:/Users/ben/.m2/repository/org/reactivestreams/reactive-streams/1.0.0/reactive-streams-1.0.0.jar:/Users/ben/.m2/repository/org/suecarter/freeslick_2.11/3.0.3.1/freeslick_2.11-3.0.3.1.jar:/Users/ben/.m2/repository/mysql/mysql-connector-java/5.1.37/mysql-connector-java-5.1.37.jar:/Users/ben/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-service/1.21.0-SNAPSHOT/shrine-broadcaster-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-service/1.21.0-SNAPSHOT/shrine-adapter-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-ont-support/1.21.0-SNAPSHOT/shrine-ont-support-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol-query/1.21.0-SNAPSHOT/shrine-protocol-query-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/liftweb/lift-json_2.11/2.6.2/lift-json_2.11-2.6.2.jar:/Users/ben/.m2/repository/com/thoughtworks/paranamer/paranamer/2.4.1/paranamer-2.4.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/org/scala-lang/scala-library/2.11.7/scala-library-2.11.7.jar:/Users/ben/.m2/repository/junit/junit/4.12/junit-4.12.jar:/Users/ben/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:/Users/ben/.m2/repository/org/scalatest/scalatest_2.11/2.2.5/scalatest_2.11-2.2.5.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-xml_2.11/1.0.2/scala-xml_2.11-1.0.2.jar:/Users/ben/.m2/repository/org/scala-lang/scala-actors/2.11.7/scala-actors-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-reflect/2.11.7/scala-reflect-2.11.7.jar:\"","spray.can.server.chunkless-streaming":"\"off\"","spray.can.client.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","spray.can.client.parsing.header-cache.If-Unmodified-Since":"0","spray.can.host-connector.max-redirects":"0","spray.can.host-connector.client.parsing.header-cache.User-Agent":"32","akka.io.udp.trace-logging":"\"off\"","akka.io.udp-connected.receive-throughput":"3","spray.can.parsing.max-header-count":"64","shrine.hiveCredentials.crcProjectId":"\"Demo\"","akka.actor.reaper-interval":"\"5s\"","spray.can.parsing.illegal-header-warnings":"\"on\"","spray.can.parsing.max-response-reason-length":"64","shrine.adapter.audit.database.slickProfileClassName":"\"slick.driver.MySQLDriver$\"","sun.os.patch.level":"\"unknown\"","spray.can.parsing.header-cache.If-Range":"0","gopherProxySet":"\"false\"","akka.actor.default-dispatcher.thread-pool-executor.core-pool-size-factor":"3","spray.can.parsing.header-cache.If-Modified-Since":"0","shrine.queryEntryPoint.includeAggregateResults":"false","akka.actor.router.type-mapping.tail-chopping-pool":"\"akka.routing.TailChoppingPool\"","surefire.real.class.path":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefirebooter3319812089896967790.jar\"","shrine.ontEndpoint.timeout.seconds":"1","shrine.queryEntryPoint.authenticationType":"\"ecommons\"","akka.actor.default-dispatcher.thread-pool-executor.max-pool-size-factor":"3","shrine.adapter.create":"true","akka.actor.default-dispatcher.fork-join-executor.parallelism-max":"64","shrine.queryEntryPoint.audit.database.jndiDataSourceName":"\"java:comp/env/jdbc/qepAuditDB\"","spray.can.host-connector.client.user-agent-header":"\"spray-can/1.3.3\"","akka.actor.deployment.default.mailbox":"\"\"","java.vm.specification.vendor":"\"Oracle Corporation\"","spray.can.host-connector.client.parsing.header-cache.If-Range":"0","slick.ansiDump":"false","spray.can.client.parsing.header-cache.If-None-Match":"0","akka.home":"\"\"","akka.actor.provider":"\"akka.actor.LocalActorRefProvider\"","spray.can.client.response-chunk-aggregation-limit":"\"1m\"","akka.io.tcp.max-received-message-size":"\"unlimited\"","akka.actor.mailbox.requirements.\"akka.dispatch.BoundedMessageQueueSemantics\"":"\"akka.actor.mailbox.bounded-queue-based\"","shrine.adapter.adapterMappingsFileName":"\"AdapterMappings.xml\"","spray.can.parsing.header-cache.If-Match":"0","akka.actor.default-dispatcher.mailbox-requirement":"\"\"","akka.actor.serializers.bytes":"\"akka.serialization.ByteArraySerializer\"","akka.actor.default-dispatcher.executor":"\"default-executor\"","akka.actor.debug.receive":"\"off\"","akka.actor.default-dispatcher.thread-pool-executor.max-pool-size-max":"64","spray.can.parsing.max-uri-length":"\"2k\"","user.country":"\"US\"","shrine.hiveCredentials.domain":"\"HarvardDemo\"","shrine.keystore.keyStoreType":"\"PKCS12\"","sun.jnu.encoding":"\"UTF-8\"","shrine.hub.maxQueryWaitTime.minutes":"4.5","akka.version":"\"2.3.8\"","shrine.queryEntryPoint.broadcasterServiceEndpoint.url":"\"http://example.com/shrine/rest/broadcaster/broadcast\"","user.language":"\"en\"","spray.can.host-connector.client.connecting-timeout":"\"10s\"","akka.scheduler.shutdown-timeout":"\"5s\"","spray.can.client.parsing.illegal-header-warnings":"\"on\"","akka.actor.default-dispatcher.attempt-teamwork":"\"on\"","spray.servlet.uri-parsing-mode":"\"relaxed\"","akka.io.tcp.max-channels":"256000","shrine.keystore.caCertAliases":"[\n # shrine.conf: 117\n \"carra ca\"\n]","slick.sqlIndent":"false","akka.actor.default-dispatcher.thread-pool-executor.max-pool-size-min":"8","akka.actor.default-dispatcher.thread-pool-executor.task-queue-type":"\"linked\"","spray.servlet.timeout-handler":"\"\"","spray.can.server.verbose-error-messages":"\"off\"","shrine.humanReadableNodeName":"\"SHRINE Cell\"","akka.io.tcp.file-io-transferTo-limit":"\"512 KiB\"","akka.loggers":"[\n # reference.conf: 17\n \"akka.event.Logging$DefaultLogger\"\n]","localRepository":"\"/Users/ben/.m2/repository\"","spray.can.host-connector.client.parsing.header-cache.default":"12","spray.can.parsing.header-cache.If-None-Match":"0","spray.servlet.root-path":"\"AUTO\"","akka.io.tcp.finish-connect-retries":"5","spray.can.client.parsing.header-cache.If-Range":"0","akka.actor.mailbox.requirements.\"akka.dispatch.BoundedDequeBasedMessageQueueSemantics\"":"\"akka.actor.mailbox.bounded-deque-based\"","akka.actor.default-dispatcher.throughput":"5","akka.actor.mailbox.requirements.\"akka.dispatch.UnboundedMessageQueueSemantics\"":"\"akka.actor.mailbox.unbounded-queue-based\"","akka.scheduler.implementation":"\"akka.actor.LightArrayRevolverScheduler\"","shrine.adapter.audit.database.createTablesOnStart":"false","akka.actor.default-mailbox.mailbox-capacity":"1000","spray.can.server.parsing.header-cache.Content-MD5":"0","shrine.hub.shouldQuerySelf":"true","spray.routing.range-coalescing-threshold":"80","akka.actor.router.type-mapping.random-pool":"\"akka.routing.RandomPool\"","spray.can.host-connector.client.parsing.max-header-count":"64","spray.can.parsing.header-cache.default":"12","shrine.shrineDatabaseType":"\"mysql\"","shrine.queryEntryPoint.sheriffCredentials.username":"\"sheriffUsername\"","spray.can.server.parsing.header-cache.If-Modified-Since":"0","java.awt.printerjob":"\"sun.lwawt.macosx.CPrinterJob\"","java.awt.graphicsenv":"\"sun.awt.CGraphicsEnvironment\"","spray.can.host-connector.client.proxy.http":"\"default\"","spray.servlet.request-timeout":"\"30 s\"","spray.can.host-connector.client.parsing.max-response-reason-length":"64","akka.io.pinned-dispatcher.thread-pool-executor.allow-core-pool-timeout":"\"off\"","akka.actor.guardian-supervisor-strategy":"\"akka.actor.DefaultSupervisorStrategy\"","spray.routing.range-count-limit":"16","akka.actor.serialization-bindings.\"java.io.Serializable\"":"\"java\"","akka.actor.default-dispatcher.thread-pool-executor.keep-alive-time":"\"60s\"","awt.toolkit":"\"sun.lwawt.macosx.LWCToolkit\"","spray.can.server.parsing.header-cache.If-Match":"0","akka.io.udp.direct-buffer-size":"\"128 KiB\"","java.class.path":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/test-classes:/Users/ben/git/hms/develop/apps/shrine-app/target/classes:/Users/ben/.m2/repository/net/shrine/shrine-test-commons/1.21.0-SNAPSHOT/shrine-test-commons-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/sf/opencsv/opencsv/2.3/opencsv-2.3.jar:/Users/ben/.m2/repository/com/typesafe/config/1.2.1/config-1.2.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-util/1.21.0-SNAPSHOT/shrine-util-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/apache/commons/commons-email/1.2/commons-email-1.2.jar:/Users/ben/.m2/repository/javax/mail/mail/1.4.1/mail-1.4.1.jar:/Users/ben/.m2/repository/javax/activation/activation/1.1/activation-1.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-hms-core/1.21.0-SNAPSHOT/shrine-hms-core-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-auth/1.21.0-SNAPSHOT/shrine-auth-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-actor_2.11/2.3.8/akka-actor_2.11-2.3.8.jar:/Users/ben/.m2/repository/org/json4s/json4s-native_2.11/3.2.11/json4s-native_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-core_2.11/3.2.11/json4s-core_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-ast_2.11/3.2.11/json4s-ast_2.11-3.2.11.jar:/Users/ben/.m2/repository/io/spray/spray-client_2.11/1.3.3/spray-client_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-can_2.11/1.3.3/spray-can_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-io_2.11/1.3.3/spray-io_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-http_2.11/1.3.3/spray-http_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-scala_2.11/1.1.7/parboiled-scala_2.11-1.1.7.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-core/1.1.7/parboiled-core-1.1.7.jar:/Users/ben/.m2/repository/io/spray/spray-httpx_2.11/1.3.3/spray-httpx_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/jvnet/mimepull/mimepull/1.9.5/mimepull-1.9.5.jar:/Users/ben/.m2/repository/io/spray/spray-routing_2.11/1.3.3/spray-routing_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/chuusai/shapeless_2.11/1.2.4/shapeless_2.11-1.2.4.jar:/Users/ben/.m2/repository/io/spray/spray-servlet_2.11/1.3.3/spray-servlet_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-util_2.11/1.3.3/spray-util_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-slf4j_2.11/2.3.8/akka-slf4j_2.11-2.3.8.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-qep/1.21.0-SNAPSHOT/shrine-qep-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/squeryl/squeryl_2.11/0.9.6-RC4/squeryl_2.11-0.9.6-RC4.jar:/Users/ben/.m2/repository/cglib/cglib-nodep/2.2/cglib-nodep-2.2.jar:/Users/ben/.m2/repository/org/scala-lang/scalap/2.11.7/scalap-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-compiler/2.11.7/scala-compiler-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-parser-combinators_2.11/1.0.4/scala-parser-combinators_2.11-1.0.4.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-server/1.19/jersey-server-1.19.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-core/1.19/jersey-core-1.19.jar:/Users/ben/.m2/repository/javax/ws/rs/jsr311-api/1.1.1/jsr311-api-1.1.1.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-client/1.19/jersey-client-1.19.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-client-api/1.21.0-SNAPSHOT/shrine-adapter-client-api-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-data-commons/1.21.0-SNAPSHOT/shrine-data-commons-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/slick/slick_2.11/3.1.0/slick_2.11-3.1.0.jar:/Users/ben/.m2/repository/org/slf4j/slf4j-api/1.7.10/slf4j-api-1.7.10.jar:/Users/ben/.m2/repository/org/reactivestreams/reactive-streams/1.0.0/reactive-streams-1.0.0.jar:/Users/ben/.m2/repository/org/suecarter/freeslick_2.11/3.0.3.1/freeslick_2.11-3.0.3.1.jar:/Users/ben/.m2/repository/mysql/mysql-connector-java/5.1.37/mysql-connector-java-5.1.37.jar:/Users/ben/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-service/1.21.0-SNAPSHOT/shrine-broadcaster-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-service/1.21.0-SNAPSHOT/shrine-adapter-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-ont-support/1.21.0-SNAPSHOT/shrine-ont-support-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol-query/1.21.0-SNAPSHOT/shrine-protocol-query-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/liftweb/lift-json_2.11/2.6.2/lift-json_2.11-2.6.2.jar:/Users/ben/.m2/repository/com/thoughtworks/paranamer/paranamer/2.4.1/paranamer-2.4.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/org/scala-lang/scala-library/2.11.7/scala-library-2.11.7.jar:/Users/ben/.m2/repository/junit/junit/4.12/junit-4.12.jar:/Users/ben/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:/Users/ben/.m2/repository/org/scalatest/scalatest_2.11/2.2.5/scalatest_2.11-2.2.5.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-xml_2.11/1.0.2/scala-xml_2.11-1.0.2.jar:/Users/ben/.m2/repository/org/scala-lang/scala-actors/2.11.7/scala-actors-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-reflect/2.11.7/scala-reflect-2.11.7.jar:\"","akka.actor.default-dispatcher.thread-pool-executor.core-pool-size-max":"64","akka.io.udp.select-timeout":"\"infinite\"","spray.can.host-connector.client.proxy.https":"\"default\"","akka.actor.router.type-mapping.scatter-gather-pool":"\"akka.routing.ScatterGatherFirstCompletedPool\"","akka.actor.deployment.default.resizer.pressure-threshold":"1","spray.can.client.request-header-size-hint":"512","spray.can.server.idle-timeout":"\"60 s\"","akka.io.udp-connected.trace-logging":"\"off\"","os.name":"\"Mac OS X\"","slick.detectRebuild":"false","shrine.queryEntryPoint.audit.database.slickProfileClassName":"\"slick.driver.MySQLDriver$\"","spray.can.server.pipelining-limit":"1","spray.can.host-connector.client.request-timeout":"\"20 s\"","akka.actor.mailbox.requirements.\"akka.dispatch.MultipleConsumerSemantics\"":"\"akka.actor.mailbox.unbounded-queue-based\"","spray.can.server.max-encryption-chunk-size":"\"1m\"","spray.can.host-connector.client.parsing.max-chunk-size":"\"1m\"","spray.can.server.parsing-error-abort-timeout":"\"2s\"","java.vm.vendor":"\"Oracle Corporation\"","akka.io.udp-connected.direct-buffer-pool-limit":"1000","spray.can.server.automatic-back-pressure-handling":"\"on\"","spray.can.server.verbose-error-logging":"\"off\"","spray.can.host-connector.client.reaping-cycle":"\"250 ms\"","spray.can.host-connector.client.parsing.max-header-name-length":"64","akka.actor.router.type-mapping.round-robin-pool":"\"akka.routing.RoundRobinPool\"","spray.can.connection-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.server.stats-support":"\"on\"","slick.unicodeDump":"false","java.vendor.url.bug":"\"http://bugreport.sun.com/bugreport/\"","akka.io.udp-connected.worker-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.client.reaping-cycle":"\"250 ms\"","akka.io.udp-connected.direct-buffer-size":"\"128 KiB\"","spray.can.server.response-header-size-hint":"512","spray.can.parsing.max-header-name-length":"64","spray.can.host-connector.client.parsing.header-cache.Content-MD5":"0","akka.actor.deployment.default.resizer.upper-bound":"10","akka.actor.default-dispatcher.thread-pool-executor.allow-core-timeout":"\"on\"","user.name":"\"ben\"","akka.actor.mailbox.unbounded-deque-based.mailbox-type":"\"akka.dispatch.UnboundedDequeBasedMailbox\"","akka.actor.default-dispatcher.thread-pool-executor.task-queue-size":"-1","akka.logger-startup-timeout":"\"5s\"","akka.actor.mailbox.bounded-deque-based.mailbox-type":"\"akka.dispatch.BoundedDequeBasedMailbox\"","spray.can.client.parsing.max-header-name-length":"64","spray.can.host-connector.client.ssl-tracing":"\"off\"","spray.can.parsing.header-cache.If-Unmodified-Since":"0","spray.servlet.verbose-error-messages":"\"off\"","shrine.adapter.immediatelyRunIncomingQueries":"false","akka.io.tcp.nr-of-selectors":"1","akka.actor.router.type-mapping.smallest-mailbox-pool":"\"akka.routing.SmallestMailboxPool\"","java.vm.name":"\"Java HotSpot(TM) 64-Bit Server VM\"","akka.io.tcp.windows-connection-abort-workaround-enabled":"\"auto\"","akka.actor.router.type-mapping.tail-chopping-group":"\"akka.routing.TailChoppingGroup\"","spray.routing.render-vanity-footer":"\"yes\"","sun.java.command":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefirebooter3319812089896967790.jar /Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefire6518106528052987897tmp /Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefire_0114067395742935233tmp\"","spray.can.server.remote-address-header":"\"off\"","akka.actor.deployment.default.resizer.backoff-rate":"0.1","akka.actor.debug.event-stream":"\"off\"","spray.version":"\"1.3.3\"","spray.can.host-connector.client.parsing.max-header-value-length":"\"8k\"","java.home":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre\"","akka.io.udp-connected.nr-of-selectors":"1","spray.can.host-connector.client.parsing.max-uri-length":"\"2k\"","spray.can.parsing.header-cache.User-Agent":"32","akka.io.pinned-dispatcher.executor":"\"thread-pool-executor\"","spray.can.server.bind-timeout":"\"1s\"","akka.log-dead-letters":"10","java.version":"\"1.8.0_40\"","sun.io.unicode.encoding":"\"UnicodeBig\"","spray.can.server.default-host-header":"\"\""}}
\ No newline at end of file
diff --git a/apps/dashboard-app/src/test/resources/teststatus/status b/apps/dashboard-app/src/test/resources/teststatus/status
index 0b55bdc2e..3ba06bf47 100644
--- a/apps/dashboard-app/src/test/resources/teststatus/status
+++ b/apps/dashboard-app/src/test/resources/teststatus/status
@@ -1 +1 @@
-{"keyValues":{"spray.can.server.parsing.ssl-session-info-header":"\"off\"","spray.can.listener-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.client.parsing.header-cache.default":"12","java.io.tmpdir":"\"/var/folders/lt/6x60mpg92w994pbhxl8q4dyc0000gp/T/\"","akka.actor.mailbox.requirements.\"akka.dispatch.UnboundedDequeBasedMessageQueueSemantics\"":"\"akka.actor.mailbox.unbounded-deque-based\"","shrine.queryEntryPoint.audit.database.createTablesOnStart":"false","spray.can.host-connector.client.request-header-size-hint":"512","spray.can.client.parsing.header-cache.If-Modified-Since":"0","akka.actor.deployment.default.routees.paths":"[]","shrine.ontEndpoint.acceptAllCerts":"true","spray.routing.file-chunking-chunk-size":"\"128k\"","akka.io.udp.direct-buffer-pool-limit":"1000","shrine.queryEntryPoint.maxQueryWaitTime.minutes":"5","akka.actor.unstarted-push-timeout":"\"10s\"","spray.can.parsing.max-chunk-ext-length":"256","spray.can.client.parsing.max-content-length":"\"8m\"","line.separator":"\"\\n\"","spray.can.server.parsing.header-cache.User-Agent":"32","spray.can.server.parsing.max-uri-length":"\"2k\"","spray.servlet.timeout-timeout":"\"500 ms\"","akka.actor.default-mailbox.mailbox-type":"\"akka.dispatch.UnboundedMailbox\"","spray.servlet.max-content-length":"\"5 m\"","shrine.queryEntryPoint.audit.database.dataSourceFrom":"\"JNDI\"","akka.actor.serializers.java":"\"akka.serialization.JavaSerializer\"","akka.actor.dsl.default-timeout":"\"5s\"","shrine.adapter.setSizeObfuscation":"true","shrine.queryEntryPoint.audit.collectQepAudit":"true","shrine.pmEndpoint.acceptAllCerts":"true","spray.can.server.chunkhandler-registration-timeout":"\"500 ms\"","akka.actor.serialize-creators":"\"off\"","akka.actor.deployment.default.virtual-nodes-factor":"10","shrine.queryEntryPoint.broadcasterServiceEndpoint.acceptAllCerts":"true","shrine.queryEntryPoint.create":"true","path.separator":"\":\"","spray.can.host-connector.client.parsing.header-cache.If-Match":"0","spray.can.host-connector.client.max-encryption-chunk-size":"\"1m\"","spray.can.client.parsing.max-header-count":"64","spray.can.manager-dispatcher":"\"akka.actor.default-dispatcher\"","akka.io.tcp.worker-dispatcher":"\"akka.actor.default-dispatcher\"","akka.jvm-exit-on-fatal-error":"\"on\"","basedir":"\"/Users/ben/git/hms/develop/apps/shrine-app\"","akka.io.udp.worker-dispatcher":"\"akka.actor.default-dispatcher\"","spray.servlet.boot-class":"\"\"","spray.can.client.parsing.max-chunk-size":"\"1m\"","shrine.hiveCredentials.username":"\"demo\"","shrine.adapter.adapterLockoutAttemptsThreshold":"10","sun.management.compiler":"\"HotSpot 64-Bit Tiered Compilers\"","akka.actor.default-dispatcher.fork-join-executor.parallelism-min":"8","akka.io.udp.selector-association-retries":"10","akka.scheduler.tick-duration":"\"10ms\"","spray.can.host-connector.client.parsing.uri-parsing-mode":"\"strict\"","spray.can.client.parsing.max-uri-length":"\"2k\"","shrine.hub.downstreamNodes.PHS":"\"http://example.com/phs\"","sun.cpu.endian":"\"little\"","akka.actor.mailbox.requirements.\"akka.dispatch.DequeBasedMessageQueueSemantics\"":"\"akka.actor.mailbox.unbounded-deque-based\"","spray.can.parsing.max-chunk-size":"\"1m\"","spray.can.server.parsing.header-cache.If-Range":"0","spray.can.client.parsing.ssl-session-info-header":"\"off\"","akka.io.tcp.register-timeout":"\"5s\"","java.specification.version":"\"1.8\"","shrine.hub.create":"true","akka.actor.creation-timeout":"\"20s\"","spray.can.host-connector.client.parsing.header-cache.Date":"0","java.vm.specification.name":"\"Java Virtual Machine Specification\"","java.vm.specification.version":"\"1.8\"","shrine.pmEndpoint.timeout.seconds":"1","akka.actor.router.type-mapping.random-group":"\"akka.routing.RandomGroup\"","shrine.hiveCredentials.ontProjectId":"\"SHRINE\"","user.home":"\"/Users/ben\"","akka.log-dead-letters-during-shutdown":"\"on\"","file.encoding.pkg":"\"sun.io\"","spray.can.client.chunkless-streaming":"\"off\"","akka.scheduler.ticks-per-wheel":"512","akka.actor.dsl.inbox-size":"1000","spray.can.parsing.max-header-value-length":"\"8k\"","akka.actor.default-mailbox.stash-capacity":"-1","spray.can.client.ssl-tracing":"\"off\"","sun.arch.data.model":"\"64\"","akka.io.udp-connected.received-message-size-limit":"\"unlimited\"","akka.log-config-on-start":"\"off\"","akka.io.tcp.direct-buffer-size":"\"128 KiB\"","akka.actor.default-dispatcher.type":"\"Dispatcher\"","akka.actor.deployment.default.resizer.rampup-rate":"0.2","spray.servlet.remote-address-header":"\"off\"","sun.boot.library.path":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib\"","spray.can.client.parsing.header-cache.User-Agent":"32","akka.actor.deployment.default.resizer.backoff-threshold":"0.3","user.dir":"\"/Users/ben/git/hms/develop/apps/shrine-app\"","shrine.queryEntryPoint.broadcasterServiceEndpoint.timeout.seconds":"1","spray.can.host-connector.client.chunkless-streaming":"\"off\"","akka.actor.mailbox.unbounded-queue-based.mailbox-type":"\"akka.dispatch.UnboundedMailbox\"","akka.actor.deployment.default.resizer.messages-per-resize":"10","akka.actor.router.type-mapping.balancing-pool":"\"akka.routing.BalancingPool\"","akka.actor.debug.autoreceive":"\"off\"","akka.io.pinned-dispatcher.type":"\"PinnedDispatcher\"","akka.actor.debug.unhandled":"\"off\"","akka.io.udp.selector-dispatcher":"\"akka.io.pinned-dispatcher\"","akka.actor.deployment.default.router":"\"from-code\"","akka.actor.debug.fsm":"\"off\"","shrine.networkStatusQuery":"\"\\\\\\\\SHRINE\\\\SHRINE\\\\Diagnoses\\\\Mental Illness\\\\Disorders usually diagnosed in infancy, childhood, or adolescence\\\\Pervasive developmental disorders\\\\Infantile autism, current or active state\\\\\"","java.library.path":"\"/Users/ben/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.\"","spray.can.client.parsing.max-chunk-ext-length":"256","akka.actor.router.type-mapping.from-code":"\"akka.routing.NoRouter\"","sun.cpu.isalist":"\"\"","akka.actor.router.type-mapping.broadcast-pool":"\"akka.routing.BroadcastPool\"","shrine.adapter.maxSignatureAge.minutes":"5","akka.io.udp.management-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.server.raw-request-uri-header":"\"off\"","akka.actor.deployment.default.resizer.enabled":"\"off\"","spray.can.client.parsing.max-response-reason-length":"64","spray.can.host-connector.pipelining":"\"off\"","spray.can.server.timeout-handler":"\"\"","shrine.adapter.audit.collectAdapterAudit":"true","slick.verifyTypes":"false","os.arch":"\"x86_64\"","spray.can.client.parsing.max-header-value-length":"\"8k\"","java.vm.version":"\"25.40-b25\"","spray.can.client.request-timeout":"\"20 s\"","spray.can.client.user-agent-header":"\"spray-can/1.3.3\"","spray.can.host-connector.client.parsing.header-cache.If-Unmodified-Since":"0","akka.actor.deployment.default.resizer.lower-bound":"1","spray.can.server.timeout-timeout":"\"2 s\"","akka.actor.serialization-bindings.\"[B\"":"\"bytes\"","akka.io.tcp.selector-association-retries":"10","spray.servlet.servlet-request-access":"\"off\"","spray.can.server.parsing.max-header-value-length":"\"8k\"","spray.can.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","akka.actor.router.type-mapping.scatter-gather-group":"\"akka.routing.ScatterGatherFirstCompletedGroup\"","slick.dumpPaths":"false","akka.actor.router.type-mapping.consistent-hashing-group":"\"akka.routing.ConsistentHashingGroup\"","spray.can.host-connector.idle-timeout":"\"30 s\"","shrine.ontEndpoint.url":"\"http://example.com:9090/i2b2/rest/OntologyService/\"","akka.io.udp-connected.select-timeout":"\"infinite\"","akka.io.udp.nr-of-selectors":"1","spray.can.host-connector.client.parsing.illegal-header-warnings":"\"on\"","akka.io.tcp.trace-logging":"\"off\"","akka.actor.deployment.default.within":"\"5 seconds\"","spray.can.client.connecting-timeout":"\"10s\"","akka.io.tcp.file-io-dispatcher":"\"akka.actor.default-dispatcher\"","shrine.queryEntryPoint.attachSigningCert":"true","spray.can.server.parsing.illegal-header-warnings":"\"on\"","java.endorsed.dirs":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/endorsed\"","java.runtime.version":"\"1.8.0_40-b27\"","spray.can.host-connector.client.parsing.max-chunk-ext-length":"256","akka.actor.debug.lifecycle":"\"off\"","java.vm.info":"\"mixed mode\"","spray.can.host-connector.max-connections":"4","shrine.hub.downstreamNodes.\"some hospital\"":"\"http://example.com/foo\"","akka.actor.debug.router-misconfiguration":"\"off\"","spray.routing.verbose-error-messages":"\"off\"","shrine.adapter.audit.database.dataSourceFrom":"\"JNDI\"","akka.actor.default-dispatcher.thread-pool-executor.core-pool-size-min":"8","akka.io.udp.max-channels":"4096","spray.can.server.reaping-cycle":"\"250 ms\"","shrine.adapter.crcEndpoint.timeout.seconds":"1","java.ext.dirs":"\"/Users/ben/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java\"","akka.actor.default-dispatcher.throughput-deadline-time":"\"0ms\"","spray.can.client.parsing.header-cache.Date":"0","spray.can.parsing.uri-parsing-mode":"\"strict\"","shrine.hub.downstreamNodes.CHB":"\"http://example.com/chb\"","shrine.keystore.file":"\"shrine.keystore\"","akka.actor.deployment.default.dispatcher":"\"\"","akka.io.tcp.management-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.host-connector.client.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","spray.can.client.max-encryption-chunk-size":"\"1m\"","java.runtime.name":"\"Java(TM) SE Runtime Environment\"","akka.actor.default-mailbox.mailbox-push-timeout-time":"\"10s\"","akka.actor.default-dispatcher.default-executor.fallback":"\"fork-join-executor\"","akka.actor.router.type-mapping.round-robin-group":"\"akka.routing.RoundRobinGroup\"","akka.io.udp.receive-throughput":"3","spray.can.host-connector.client.parsing.ssl-session-info-header":"\"off\"","spray.servlet.illegal-header-warnings":"\"on\"","akka.stdout-loglevel":"\"WARNING\"","shrine.adapter.audit.database.jndiDataSourceName":"\"java:comp/env/jdbc/adapterAuditDB\"","shrine.queryEntryPoint.authorizationType":"\"hms-steward\"","spray.can.server.parsing.max-chunk-size":"\"1m\"","file.separator":"\"/\"","spray.can.client.idle-timeout":"\"60 s\"","spray.can.server.parsing.header-cache.Date":"0","shrine.queryEntryPoint.sheriffEndpoint.timeout.seconds":"1","spray.can.host-connector.client.parsing.header-cache.If-None-Match":"0","spray.can.parsing.ssl-session-info-header":"\"off\"","spray.can.host-connector.client.idle-timeout":"\"60 s\"","akka.actor.router.type-mapping.consistent-hashing-pool":"\"akka.routing.ConsistentHashingPool\"","akka.io.tcp.batch-accept-limit":"10","shrine.keystore.privateKeyAlias":"\"test-cert\"","spray.can.client.parsing.header-cache.If-Match":"0","shrine.adapter.crcEndpoint.acceptAllCerts":"true","spray.can.parsing.header-cache.Date":"0","akka.io.udp.received-message-size-limit":"\"unlimited\"","spray.can.client.parsing.uri-parsing-mode":"\"strict\"","akka.actor.deployment.default.nr-of-instances":"1","spray.routing.file-get-conditional":"\"on\"","akka.actor.deployment.default.tail-chopping-router.interval":"\"10 milliseconds\"","spray.can.host-connector.client.parsing.header-cache.If-Modified-Since":"0","akka.actor.router.type-mapping.broadcast-group":"\"akka.routing.BroadcastGroup\"","akka.actor.mailbox.bounded-queue-based.mailbox-type":"\"akka.dispatch.BoundedMailbox\"","spray.can.server.parsing.header-cache.If-None-Match":"0","spray.can.parsing.header-cache.Content-MD5":"0","shrine.adapter.crcEndpoint.url":"\"http://services.i2b2.org/i2b2/rest/QueryToolService/\"","spray.can.server.unbind-timeout":"\"1s\"","akka.daemonic":"\"off\"","akka.io.udp-connected.max-channels":"4096","spray.can.server.ssl-tracing":"\"off\"","java.class.version":"\"52.0\"","shrine.queryEntryPoint.sheriffEndpoint.url":"\"http://localhost:8080/shrine-hms-authorization/queryAuthorization\"","akka.io.udp-connected.selector-dispatcher":"\"akka.io.pinned-dispatcher\"","spray.can.server.back-pressure.noack-rate":"10","spray.can.server.parsing.uri-parsing-mode":"\"strict\"","java.specification.name":"\"Java Platform API Specification\"","sun.boot.class.path":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/classes\"","spray.can.server.parsing.max-header-count":"64","akka.extensions":"[]","akka.actor.typed.timeout":"\"5s\"","spray.can.server.server-header":"\"spray-can/1.3.3\"","spray.can.server.request-chunk-aggregation-limit":"\"1m\"","akka.io.udp-connected.selector-association-retries":"10","akka.actor.serialize-messages":"\"off\"","spray.can.host-connector.client.parsing.max-content-length":"\"8m\"","spray.can.server.registration-timeout":"\"1s\"","shrine.pmEndpoint.url":"\"http://services.i2b2.org/i2b2/rest/PMService/getServices\"","akka.io.udp-connected.management-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.parsing.max-content-length":"\"8m\"","spray.can.server.back-pressure.reading-low-watermark":"\"infinite\"","spray.can.server.parsing.max-chunk-ext-length":"256","akka.loglevel":"\"INFO\"","spray.can.server.parsing.header-cache.If-Unmodified-Since":"0","spray.can.server.transparent-head-requests":"\"on\"","spray.can.client.parsing.header-cache.Content-MD5":"0","akka.io.tcp.selector-dispatcher":"\"akka.io.pinned-dispatcher\"","user.timezone":"\"America/New_York\"","spray.can.server.parsing.max-header-name-length":"64","spray.can.client.proxy.http":"\"default\"","spray.can.host-connector.max-retries":"5","spray.can.settings-group-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.server.parsing.header-cache.default":"12","spray.can.client.proxy.https":"\"default\"","spray.can.server.ssl-encryption":"\"off\"","spray.can.server.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","akka.actor.default-dispatcher.fork-join-executor.parallelism-factor":"3","shrine.queryEntryPoint.sheriffEndpoint.acceptAllCerts":"true","spray.can.host-connector-dispatcher":"\"akka.actor.default-dispatcher\"","java.specification.vendor":"\"Oracle Corporation\"","spray.can.host-connector.client.response-chunk-aggregation-limit":"\"1m\"","spray.can.server.parsing.max-content-length":"\"8m\"","spray.routing.file-chunking-threshold-size":"\"128k\"","akka.io.tcp.direct-buffer-pool-limit":"1000","sun.java.launcher":"\"SUN_STANDARD\"","spray.can.server.request-timeout":"\"20 s\"","akka.actor.default-dispatcher.shutdown-timeout":"\"1s\"","spray.can.server.parsing.max-response-reason-length":"64","os.version":"\"10.10.5\"","surefire.test.class.path":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/test-classes:/Users/ben/git/hms/develop/apps/shrine-app/target/classes:/Users/ben/.m2/repository/net/shrine/shrine-test-commons/1.21.0-SNAPSHOT/shrine-test-commons-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/sf/opencsv/opencsv/2.3/opencsv-2.3.jar:/Users/ben/.m2/repository/com/typesafe/config/1.2.1/config-1.2.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-util/1.21.0-SNAPSHOT/shrine-util-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/apache/commons/commons-email/1.2/commons-email-1.2.jar:/Users/ben/.m2/repository/javax/mail/mail/1.4.1/mail-1.4.1.jar:/Users/ben/.m2/repository/javax/activation/activation/1.1/activation-1.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-hms-core/1.21.0-SNAPSHOT/shrine-hms-core-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-auth/1.21.0-SNAPSHOT/shrine-auth-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-actor_2.11/2.3.8/akka-actor_2.11-2.3.8.jar:/Users/ben/.m2/repository/org/json4s/json4s-native_2.11/3.2.11/json4s-native_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-core_2.11/3.2.11/json4s-core_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-ast_2.11/3.2.11/json4s-ast_2.11-3.2.11.jar:/Users/ben/.m2/repository/io/spray/spray-client_2.11/1.3.3/spray-client_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-can_2.11/1.3.3/spray-can_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-io_2.11/1.3.3/spray-io_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-http_2.11/1.3.3/spray-http_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-scala_2.11/1.1.7/parboiled-scala_2.11-1.1.7.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-core/1.1.7/parboiled-core-1.1.7.jar:/Users/ben/.m2/repository/io/spray/spray-httpx_2.11/1.3.3/spray-httpx_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/jvnet/mimepull/mimepull/1.9.5/mimepull-1.9.5.jar:/Users/ben/.m2/repository/io/spray/spray-routing_2.11/1.3.3/spray-routing_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/chuusai/shapeless_2.11/1.2.4/shapeless_2.11-1.2.4.jar:/Users/ben/.m2/repository/io/spray/spray-servlet_2.11/1.3.3/spray-servlet_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-util_2.11/1.3.3/spray-util_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-slf4j_2.11/2.3.8/akka-slf4j_2.11-2.3.8.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-service/1.21.0-SNAPSHOT/shrine-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/squeryl/squeryl_2.11/0.9.6-RC4/squeryl_2.11-0.9.6-RC4.jar:/Users/ben/.m2/repository/cglib/cglib-nodep/2.2/cglib-nodep-2.2.jar:/Users/ben/.m2/repository/org/scala-lang/scalap/2.11.7/scalap-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-compiler/2.11.7/scala-compiler-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-parser-combinators_2.11/1.0.4/scala-parser-combinators_2.11-1.0.4.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-server/1.19/jersey-server-1.19.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-core/1.19/jersey-core-1.19.jar:/Users/ben/.m2/repository/javax/ws/rs/jsr311-api/1.1.1/jsr311-api-1.1.1.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-client/1.19/jersey-client-1.19.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-client-api/1.21.0-SNAPSHOT/shrine-adapter-client-api-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-data-commons/1.21.0-SNAPSHOT/shrine-data-commons-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/slick/slick_2.11/3.1.0/slick_2.11-3.1.0.jar:/Users/ben/.m2/repository/org/slf4j/slf4j-api/1.7.10/slf4j-api-1.7.10.jar:/Users/ben/.m2/repository/org/reactivestreams/reactive-streams/1.0.0/reactive-streams-1.0.0.jar:/Users/ben/.m2/repository/org/suecarter/freeslick_2.11/3.0.3.1/freeslick_2.11-3.0.3.1.jar:/Users/ben/.m2/repository/mysql/mysql-connector-java/5.1.37/mysql-connector-java-5.1.37.jar:/Users/ben/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-service/1.21.0-SNAPSHOT/shrine-broadcaster-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-service/1.21.0-SNAPSHOT/shrine-adapter-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-ont-support/1.21.0-SNAPSHOT/shrine-ont-support-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol-query/1.21.0-SNAPSHOT/shrine-protocol-query-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/liftweb/lift-json_2.11/2.6.2/lift-json_2.11-2.6.2.jar:/Users/ben/.m2/repository/com/thoughtworks/paranamer/paranamer/2.4.1/paranamer-2.4.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/org/scala-lang/scala-library/2.11.7/scala-library-2.11.7.jar:/Users/ben/.m2/repository/junit/junit/4.12/junit-4.12.jar:/Users/ben/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:/Users/ben/.m2/repository/org/scalatest/scalatest_2.11/2.2.5/scalatest_2.11-2.2.5.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-xml_2.11/1.0.2/scala-xml_2.11-1.0.2.jar:/Users/ben/.m2/repository/org/scala-lang/scala-actors/2.11.7/scala-actors-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-reflect/2.11.7/scala-reflect-2.11.7.jar:\"","spray.can.server.chunkless-streaming":"\"off\"","spray.can.client.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","spray.can.client.parsing.header-cache.If-Unmodified-Since":"0","spray.can.host-connector.max-redirects":"0","spray.can.host-connector.client.parsing.header-cache.User-Agent":"32","akka.io.udp.trace-logging":"\"off\"","akka.io.udp-connected.receive-throughput":"3","spray.can.parsing.max-header-count":"64","shrine.hiveCredentials.crcProjectId":"\"Demo\"","akka.actor.reaper-interval":"\"5s\"","spray.can.parsing.illegal-header-warnings":"\"on\"","spray.can.parsing.max-response-reason-length":"64","shrine.adapter.audit.database.slickProfileClassName":"\"slick.driver.MySQLDriver$\"","sun.os.patch.level":"\"unknown\"","spray.can.parsing.header-cache.If-Range":"0","gopherProxySet":"\"false\"","akka.actor.default-dispatcher.thread-pool-executor.core-pool-size-factor":"3","spray.can.parsing.header-cache.If-Modified-Since":"0","shrine.queryEntryPoint.includeAggregateResults":"false","akka.actor.router.type-mapping.tail-chopping-pool":"\"akka.routing.TailChoppingPool\"","surefire.real.class.path":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefirebooter8537185626748921880.jar\"","shrine.ontEndpoint.timeout.seconds":"1","shrine.queryEntryPoint.authenticationType":"\"ecommons\"","akka.actor.default-dispatcher.thread-pool-executor.max-pool-size-factor":"3","shrine.adapter.create":"true","akka.actor.default-dispatcher.fork-join-executor.parallelism-max":"64","shrine.queryEntryPoint.audit.database.jndiDataSourceName":"\"java:comp/env/jdbc/qepAuditDB\"","spray.can.host-connector.client.user-agent-header":"\"spray-can/1.3.3\"","akka.actor.deployment.default.mailbox":"\"\"","java.vm.specification.vendor":"\"Oracle Corporation\"","spray.can.host-connector.client.parsing.header-cache.If-Range":"0","slick.ansiDump":"false","spray.can.client.parsing.header-cache.If-None-Match":"0","akka.home":"\"\"","akka.actor.provider":"\"akka.actor.LocalActorRefProvider\"","spray.can.client.response-chunk-aggregation-limit":"\"1m\"","akka.io.tcp.max-received-message-size":"\"unlimited\"","akka.actor.mailbox.requirements.\"akka.dispatch.BoundedMessageQueueSemantics\"":"\"akka.actor.mailbox.bounded-queue-based\"","shrine.adapter.adapterMappingsFileName":"\"AdapterMappings.xml\"","spray.can.parsing.header-cache.If-Match":"0","akka.actor.default-dispatcher.mailbox-requirement":"\"\"","akka.actor.serializers.bytes":"\"akka.serialization.ByteArraySerializer\"","akka.actor.default-dispatcher.executor":"\"default-executor\"","akka.actor.debug.receive":"\"off\"","akka.actor.default-dispatcher.thread-pool-executor.max-pool-size-max":"64","spray.can.parsing.max-uri-length":"\"2k\"","user.country":"\"US\"","shrine.hiveCredentials.domain":"\"HarvardDemo\"","shrine.keystore.keyStoreType":"\"PKCS12\"","sun.jnu.encoding":"\"UTF-8\"","shrine.hub.maxQueryWaitTime.minutes":"4.5","akka.version":"\"2.3.8\"","shrine.queryEntryPoint.broadcasterServiceEndpoint.url":"\"http://example.com/shrine/rest/broadcaster/broadcast\"","user.language":"\"en\"","spray.can.host-connector.client.connecting-timeout":"\"10s\"","akka.scheduler.shutdown-timeout":"\"5s\"","spray.can.client.parsing.illegal-header-warnings":"\"on\"","akka.actor.default-dispatcher.attempt-teamwork":"\"on\"","spray.servlet.uri-parsing-mode":"\"relaxed\"","akka.io.tcp.max-channels":"256000","shrine.keystore.caCertAliases":"[\n # shrine.conf: 117\n \"carra ca\"\n]","slick.sqlIndent":"false","akka.actor.default-dispatcher.thread-pool-executor.max-pool-size-min":"8","akka.actor.default-dispatcher.thread-pool-executor.task-queue-type":"\"linked\"","spray.servlet.timeout-handler":"\"\"","spray.can.server.verbose-error-messages":"\"off\"","shrine.humanReadableNodeName":"\"SHRINE Cell\"","akka.io.tcp.file-io-transferTo-limit":"\"512 KiB\"","akka.loggers":"[\n # reference.conf: 17\n \"akka.event.Logging$DefaultLogger\"\n]","localRepository":"\"/Users/ben/.m2/repository\"","spray.can.host-connector.client.parsing.header-cache.default":"12","spray.can.parsing.header-cache.If-None-Match":"0","spray.servlet.root-path":"\"AUTO\"","akka.io.tcp.finish-connect-retries":"5","spray.can.client.parsing.header-cache.If-Range":"0","akka.actor.mailbox.requirements.\"akka.dispatch.BoundedDequeBasedMessageQueueSemantics\"":"\"akka.actor.mailbox.bounded-deque-based\"","akka.actor.default-dispatcher.throughput":"5","akka.actor.mailbox.requirements.\"akka.dispatch.UnboundedMessageQueueSemantics\"":"\"akka.actor.mailbox.unbounded-queue-based\"","akka.scheduler.implementation":"\"akka.actor.LightArrayRevolverScheduler\"","shrine.adapter.audit.database.createTablesOnStart":"false","akka.actor.default-mailbox.mailbox-capacity":"1000","spray.can.server.parsing.header-cache.Content-MD5":"0","shrine.hub.shouldQuerySelf":"true","spray.routing.range-coalescing-threshold":"80","akka.actor.router.type-mapping.random-pool":"\"akka.routing.RandomPool\"","spray.can.host-connector.client.parsing.max-header-count":"64","spray.can.parsing.header-cache.default":"12","shrine.shrineDatabaseType":"\"mysql\"","shrine.queryEntryPoint.sheriffCredentials.username":"\"sheriffUsername\"","spray.can.server.parsing.header-cache.If-Modified-Since":"0","java.awt.printerjob":"\"sun.lwawt.macosx.CPrinterJob\"","java.awt.graphicsenv":"\"sun.awt.CGraphicsEnvironment\"","spray.can.host-connector.client.proxy.http":"\"default\"","spray.servlet.request-timeout":"\"30 s\"","spray.can.host-connector.client.parsing.max-response-reason-length":"64","akka.io.pinned-dispatcher.thread-pool-executor.allow-core-pool-timeout":"\"off\"","akka.actor.guardian-supervisor-strategy":"\"akka.actor.DefaultSupervisorStrategy\"","spray.routing.range-count-limit":"16","akka.actor.serialization-bindings.\"java.io.Serializable\"":"\"java\"","akka.actor.default-dispatcher.thread-pool-executor.keep-alive-time":"\"60s\"","awt.toolkit":"\"sun.lwawt.macosx.LWCToolkit\"","spray.can.server.parsing.header-cache.If-Match":"0","akka.io.udp.direct-buffer-size":"\"128 KiB\"","java.class.path":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/test-classes:/Users/ben/git/hms/develop/apps/shrine-app/target/classes:/Users/ben/.m2/repository/net/shrine/shrine-test-commons/1.21.0-SNAPSHOT/shrine-test-commons-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/sf/opencsv/opencsv/2.3/opencsv-2.3.jar:/Users/ben/.m2/repository/com/typesafe/config/1.2.1/config-1.2.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-util/1.21.0-SNAPSHOT/shrine-util-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/apache/commons/commons-email/1.2/commons-email-1.2.jar:/Users/ben/.m2/repository/javax/mail/mail/1.4.1/mail-1.4.1.jar:/Users/ben/.m2/repository/javax/activation/activation/1.1/activation-1.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-hms-core/1.21.0-SNAPSHOT/shrine-hms-core-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-auth/1.21.0-SNAPSHOT/shrine-auth-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-actor_2.11/2.3.8/akka-actor_2.11-2.3.8.jar:/Users/ben/.m2/repository/org/json4s/json4s-native_2.11/3.2.11/json4s-native_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-core_2.11/3.2.11/json4s-core_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-ast_2.11/3.2.11/json4s-ast_2.11-3.2.11.jar:/Users/ben/.m2/repository/io/spray/spray-client_2.11/1.3.3/spray-client_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-can_2.11/1.3.3/spray-can_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-io_2.11/1.3.3/spray-io_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-http_2.11/1.3.3/spray-http_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-scala_2.11/1.1.7/parboiled-scala_2.11-1.1.7.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-core/1.1.7/parboiled-core-1.1.7.jar:/Users/ben/.m2/repository/io/spray/spray-httpx_2.11/1.3.3/spray-httpx_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/jvnet/mimepull/mimepull/1.9.5/mimepull-1.9.5.jar:/Users/ben/.m2/repository/io/spray/spray-routing_2.11/1.3.3/spray-routing_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/chuusai/shapeless_2.11/1.2.4/shapeless_2.11-1.2.4.jar:/Users/ben/.m2/repository/io/spray/spray-servlet_2.11/1.3.3/spray-servlet_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-util_2.11/1.3.3/spray-util_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-slf4j_2.11/2.3.8/akka-slf4j_2.11-2.3.8.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-service/1.21.0-SNAPSHOT/shrine-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/squeryl/squeryl_2.11/0.9.6-RC4/squeryl_2.11-0.9.6-RC4.jar:/Users/ben/.m2/repository/cglib/cglib-nodep/2.2/cglib-nodep-2.2.jar:/Users/ben/.m2/repository/org/scala-lang/scalap/2.11.7/scalap-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-compiler/2.11.7/scala-compiler-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-parser-combinators_2.11/1.0.4/scala-parser-combinators_2.11-1.0.4.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-server/1.19/jersey-server-1.19.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-core/1.19/jersey-core-1.19.jar:/Users/ben/.m2/repository/javax/ws/rs/jsr311-api/1.1.1/jsr311-api-1.1.1.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-client/1.19/jersey-client-1.19.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-client-api/1.21.0-SNAPSHOT/shrine-adapter-client-api-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-data-commons/1.21.0-SNAPSHOT/shrine-data-commons-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/slick/slick_2.11/3.1.0/slick_2.11-3.1.0.jar:/Users/ben/.m2/repository/org/slf4j/slf4j-api/1.7.10/slf4j-api-1.7.10.jar:/Users/ben/.m2/repository/org/reactivestreams/reactive-streams/1.0.0/reactive-streams-1.0.0.jar:/Users/ben/.m2/repository/org/suecarter/freeslick_2.11/3.0.3.1/freeslick_2.11-3.0.3.1.jar:/Users/ben/.m2/repository/mysql/mysql-connector-java/5.1.37/mysql-connector-java-5.1.37.jar:/Users/ben/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-service/1.21.0-SNAPSHOT/shrine-broadcaster-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-service/1.21.0-SNAPSHOT/shrine-adapter-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-ont-support/1.21.0-SNAPSHOT/shrine-ont-support-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol-query/1.21.0-SNAPSHOT/shrine-protocol-query-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/liftweb/lift-json_2.11/2.6.2/lift-json_2.11-2.6.2.jar:/Users/ben/.m2/repository/com/thoughtworks/paranamer/paranamer/2.4.1/paranamer-2.4.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/org/scala-lang/scala-library/2.11.7/scala-library-2.11.7.jar:/Users/ben/.m2/repository/junit/junit/4.12/junit-4.12.jar:/Users/ben/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:/Users/ben/.m2/repository/org/scalatest/scalatest_2.11/2.2.5/scalatest_2.11-2.2.5.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-xml_2.11/1.0.2/scala-xml_2.11-1.0.2.jar:/Users/ben/.m2/repository/org/scala-lang/scala-actors/2.11.7/scala-actors-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-reflect/2.11.7/scala-reflect-2.11.7.jar:\"","akka.actor.default-dispatcher.thread-pool-executor.core-pool-size-max":"64","akka.io.udp.select-timeout":"\"infinite\"","spray.can.host-connector.client.proxy.https":"\"default\"","akka.actor.router.type-mapping.scatter-gather-pool":"\"akka.routing.ScatterGatherFirstCompletedPool\"","akka.actor.deployment.default.resizer.pressure-threshold":"1","spray.can.client.request-header-size-hint":"512","spray.can.server.idle-timeout":"\"60 s\"","akka.io.udp-connected.trace-logging":"\"off\"","os.name":"\"Mac OS X\"","slick.detectRebuild":"false","shrine.queryEntryPoint.audit.database.slickProfileClassName":"\"slick.driver.MySQLDriver$\"","spray.can.server.pipelining-limit":"1","spray.can.host-connector.client.request-timeout":"\"20 s\"","akka.actor.mailbox.requirements.\"akka.dispatch.MultipleConsumerSemantics\"":"\"akka.actor.mailbox.unbounded-queue-based\"","spray.can.server.max-encryption-chunk-size":"\"1m\"","spray.can.host-connector.client.parsing.max-chunk-size":"\"1m\"","spray.can.server.parsing-error-abort-timeout":"\"2s\"","java.vm.vendor":"\"Oracle Corporation\"","akka.io.udp-connected.direct-buffer-pool-limit":"1000","spray.can.server.automatic-back-pressure-handling":"\"on\"","spray.can.server.verbose-error-logging":"\"off\"","spray.can.host-connector.client.reaping-cycle":"\"250 ms\"","spray.can.host-connector.client.parsing.max-header-name-length":"64","akka.actor.router.type-mapping.round-robin-pool":"\"akka.routing.RoundRobinPool\"","spray.can.connection-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.server.stats-support":"\"on\"","slick.unicodeDump":"false","java.vendor.url.bug":"\"http://bugreport.sun.com/bugreport/\"","akka.io.udp-connected.worker-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.client.reaping-cycle":"\"250 ms\"","akka.io.udp-connected.direct-buffer-size":"\"128 KiB\"","spray.can.server.response-header-size-hint":"512","spray.can.parsing.max-header-name-length":"64","spray.can.host-connector.client.parsing.header-cache.Content-MD5":"0","akka.actor.deployment.default.resizer.upper-bound":"10","akka.actor.default-dispatcher.thread-pool-executor.allow-core-timeout":"\"on\"","user.name":"\"ben\"","akka.actor.mailbox.unbounded-deque-based.mailbox-type":"\"akka.dispatch.UnboundedDequeBasedMailbox\"","akka.actor.default-dispatcher.thread-pool-executor.task-queue-size":"-1","akka.logger-startup-timeout":"\"5s\"","akka.actor.mailbox.bounded-deque-based.mailbox-type":"\"akka.dispatch.BoundedDequeBasedMailbox\"","spray.can.client.parsing.max-header-name-length":"64","spray.can.host-connector.client.ssl-tracing":"\"off\"","spray.can.parsing.header-cache.If-Unmodified-Since":"0","spray.servlet.verbose-error-messages":"\"off\"","shrine.adapter.immediatelyRunIncomingQueries":"false","akka.io.tcp.nr-of-selectors":"1","akka.actor.router.type-mapping.smallest-mailbox-pool":"\"akka.routing.SmallestMailboxPool\"","java.vm.name":"\"Java HotSpot(TM) 64-Bit Server VM\"","akka.io.tcp.windows-connection-abort-workaround-enabled":"\"auto\"","akka.actor.router.type-mapping.tail-chopping-group":"\"akka.routing.TailChoppingGroup\"","spray.routing.render-vanity-footer":"\"yes\"","sun.java.command":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefirebooter8537185626748921880.jar /Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefire8096778636479228628tmp /Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefire_03134404175196944217tmp\"","spray.can.server.remote-address-header":"\"off\"","akka.actor.deployment.default.resizer.backoff-rate":"0.1","akka.actor.debug.event-stream":"\"off\"","spray.version":"\"1.3.3\"","spray.can.host-connector.client.parsing.max-header-value-length":"\"8k\"","java.home":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre\"","akka.io.udp-connected.nr-of-selectors":"1","spray.can.host-connector.client.parsing.max-uri-length":"\"2k\"","spray.can.parsing.header-cache.User-Agent":"32","akka.io.pinned-dispatcher.executor":"\"thread-pool-executor\"","spray.can.server.bind-timeout":"\"1s\"","akka.log-dead-letters":"10","java.version":"\"1.8.0_40\"","sun.io.unicode.encoding":"\"UnicodeBig\"","spray.can.server.default-host-header":"\"\""}}
\ No newline at end of file
+{"keyValues":{"spray.can.server.parsing.ssl-session-info-header":"\"off\"","spray.can.listener-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.client.parsing.header-cache.default":"12","java.io.tmpdir":"\"/var/folders/lt/6x60mpg92w994pbhxl8q4dyc0000gp/T/\"","akka.actor.mailbox.requirements.\"akka.dispatch.UnboundedDequeBasedMessageQueueSemantics\"":"\"akka.actor.mailbox.unbounded-deque-based\"","shrine.queryEntryPoint.audit.database.createTablesOnStart":"false","spray.can.host-connector.client.request-header-size-hint":"512","spray.can.client.parsing.header-cache.If-Modified-Since":"0","akka.actor.deployment.default.routees.paths":"[]","shrine.ontEndpoint.acceptAllCerts":"true","spray.routing.file-chunking-chunk-size":"\"128k\"","akka.io.udp.direct-buffer-pool-limit":"1000","shrine.queryEntryPoint.maxQueryWaitTime.minutes":"5","akka.actor.unstarted-push-timeout":"\"10s\"","spray.can.parsing.max-chunk-ext-length":"256","spray.can.client.parsing.max-content-length":"\"8m\"","line.separator":"\"\\n\"","spray.can.server.parsing.header-cache.User-Agent":"32","spray.can.server.parsing.max-uri-length":"\"2k\"","spray.servlet.timeout-timeout":"\"500 ms\"","akka.actor.default-mailbox.mailbox-type":"\"akka.dispatch.UnboundedMailbox\"","spray.servlet.max-content-length":"\"5 m\"","shrine.queryEntryPoint.audit.database.dataSourceFrom":"\"JNDI\"","akka.actor.serializers.java":"\"akka.serialization.JavaSerializer\"","akka.actor.dsl.default-timeout":"\"5s\"","shrine.adapter.setSizeObfuscation":"true","shrine.queryEntryPoint.audit.collectQepAudit":"true","shrine.pmEndpoint.acceptAllCerts":"true","spray.can.server.chunkhandler-registration-timeout":"\"500 ms\"","akka.actor.serialize-creators":"\"off\"","akka.actor.deployment.default.virtual-nodes-factor":"10","shrine.queryEntryPoint.broadcasterServiceEndpoint.acceptAllCerts":"true","shrine.queryEntryPoint.create":"true","path.separator":"\":\"","spray.can.host-connector.client.parsing.header-cache.If-Match":"0","spray.can.host-connector.client.max-encryption-chunk-size":"\"1m\"","spray.can.client.parsing.max-header-count":"64","spray.can.manager-dispatcher":"\"akka.actor.default-dispatcher\"","akka.io.tcp.worker-dispatcher":"\"akka.actor.default-dispatcher\"","akka.jvm-exit-on-fatal-error":"\"on\"","basedir":"\"/Users/ben/git/hms/develop/apps/shrine-app\"","akka.io.udp.worker-dispatcher":"\"akka.actor.default-dispatcher\"","spray.servlet.boot-class":"\"\"","spray.can.client.parsing.max-chunk-size":"\"1m\"","shrine.hiveCredentials.username":"\"demo\"","shrine.adapter.adapterLockoutAttemptsThreshold":"10","sun.management.compiler":"\"HotSpot 64-Bit Tiered Compilers\"","akka.actor.default-dispatcher.fork-join-executor.parallelism-min":"8","akka.io.udp.selector-association-retries":"10","akka.scheduler.tick-duration":"\"10ms\"","spray.can.host-connector.client.parsing.uri-parsing-mode":"\"strict\"","spray.can.client.parsing.max-uri-length":"\"2k\"","shrine.hub.downstreamNodes.PHS":"\"http://example.com/phs\"","sun.cpu.endian":"\"little\"","akka.actor.mailbox.requirements.\"akka.dispatch.DequeBasedMessageQueueSemantics\"":"\"akka.actor.mailbox.unbounded-deque-based\"","spray.can.parsing.max-chunk-size":"\"1m\"","spray.can.server.parsing.header-cache.If-Range":"0","spray.can.client.parsing.ssl-session-info-header":"\"off\"","akka.io.tcp.register-timeout":"\"5s\"","java.specification.version":"\"1.8\"","shrine.hub.create":"true","akka.actor.creation-timeout":"\"20s\"","spray.can.host-connector.client.parsing.header-cache.Date":"0","java.vm.specification.name":"\"Java Virtual Machine Specification\"","java.vm.specification.version":"\"1.8\"","shrine.pmEndpoint.timeout.seconds":"1","akka.actor.router.type-mapping.random-group":"\"akka.routing.RandomGroup\"","shrine.hiveCredentials.ontProjectId":"\"SHRINE\"","user.home":"\"/Users/ben\"","akka.log-dead-letters-during-shutdown":"\"on\"","file.encoding.pkg":"\"sun.io\"","spray.can.client.chunkless-streaming":"\"off\"","akka.scheduler.ticks-per-wheel":"512","akka.actor.dsl.inbox-size":"1000","spray.can.parsing.max-header-value-length":"\"8k\"","akka.actor.default-mailbox.stash-capacity":"-1","spray.can.client.ssl-tracing":"\"off\"","sun.arch.data.model":"\"64\"","akka.io.udp-connected.received-message-size-limit":"\"unlimited\"","akka.log-config-on-start":"\"off\"","akka.io.tcp.direct-buffer-size":"\"128 KiB\"","akka.actor.default-dispatcher.type":"\"Dispatcher\"","akka.actor.deployment.default.resizer.rampup-rate":"0.2","spray.servlet.remote-address-header":"\"off\"","sun.boot.library.path":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib\"","spray.can.client.parsing.header-cache.User-Agent":"32","akka.actor.deployment.default.resizer.backoff-threshold":"0.3","user.dir":"\"/Users/ben/git/hms/develop/apps/shrine-app\"","shrine.queryEntryPoint.broadcasterServiceEndpoint.timeout.seconds":"1","spray.can.host-connector.client.chunkless-streaming":"\"off\"","akka.actor.mailbox.unbounded-queue-based.mailbox-type":"\"akka.dispatch.UnboundedMailbox\"","akka.actor.deployment.default.resizer.messages-per-resize":"10","akka.actor.router.type-mapping.balancing-pool":"\"akka.routing.BalancingPool\"","akka.actor.debug.autoreceive":"\"off\"","akka.io.pinned-dispatcher.type":"\"PinnedDispatcher\"","akka.actor.debug.unhandled":"\"off\"","akka.io.udp.selector-dispatcher":"\"akka.io.pinned-dispatcher\"","akka.actor.deployment.default.router":"\"from-code\"","akka.actor.debug.fsm":"\"off\"","shrine.networkStatusQuery":"\"\\\\\\\\SHRINE\\\\SHRINE\\\\Diagnoses\\\\Mental Illness\\\\Disorders usually diagnosed in infancy, childhood, or adolescence\\\\Pervasive developmental disorders\\\\Infantile autism, current or active state\\\\\"","java.library.path":"\"/Users/ben/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.\"","spray.can.client.parsing.max-chunk-ext-length":"256","akka.actor.router.type-mapping.from-code":"\"akka.routing.NoRouter\"","sun.cpu.isalist":"\"\"","akka.actor.router.type-mapping.broadcast-pool":"\"akka.routing.BroadcastPool\"","shrine.adapter.maxSignatureAge.minutes":"5","akka.io.udp.management-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.server.raw-request-uri-header":"\"off\"","akka.actor.deployment.default.resizer.enabled":"\"off\"","spray.can.client.parsing.max-response-reason-length":"64","spray.can.host-connector.pipelining":"\"off\"","spray.can.server.timeout-handler":"\"\"","shrine.adapter.audit.collectAdapterAudit":"true","slick.verifyTypes":"false","os.arch":"\"x86_64\"","spray.can.client.parsing.max-header-value-length":"\"8k\"","java.vm.version":"\"25.40-b25\"","spray.can.client.request-timeout":"\"20 s\"","spray.can.client.user-agent-header":"\"spray-can/1.3.3\"","spray.can.host-connector.client.parsing.header-cache.If-Unmodified-Since":"0","akka.actor.deployment.default.resizer.lower-bound":"1","spray.can.server.timeout-timeout":"\"2 s\"","akka.actor.serialization-bindings.\"[B\"":"\"bytes\"","akka.io.tcp.selector-association-retries":"10","spray.servlet.servlet-request-access":"\"off\"","spray.can.server.parsing.max-header-value-length":"\"8k\"","spray.can.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","akka.actor.router.type-mapping.scatter-gather-group":"\"akka.routing.ScatterGatherFirstCompletedGroup\"","slick.dumpPaths":"false","akka.actor.router.type-mapping.consistent-hashing-group":"\"akka.routing.ConsistentHashingGroup\"","spray.can.host-connector.idle-timeout":"\"30 s\"","shrine.ontEndpoint.url":"\"http://example.com:9090/i2b2/rest/OntologyService/\"","akka.io.udp-connected.select-timeout":"\"infinite\"","akka.io.udp.nr-of-selectors":"1","spray.can.host-connector.client.parsing.illegal-header-warnings":"\"on\"","akka.io.tcp.trace-logging":"\"off\"","akka.actor.deployment.default.within":"\"5 seconds\"","spray.can.client.connecting-timeout":"\"10s\"","akka.io.tcp.file-io-dispatcher":"\"akka.actor.default-dispatcher\"","shrine.queryEntryPoint.attachSigningCert":"true","spray.can.server.parsing.illegal-header-warnings":"\"on\"","java.endorsed.dirs":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/endorsed\"","java.runtime.version":"\"1.8.0_40-b27\"","spray.can.host-connector.client.parsing.max-chunk-ext-length":"256","akka.actor.debug.lifecycle":"\"off\"","java.vm.info":"\"mixed mode\"","spray.can.host-connector.max-connections":"4","shrine.hub.downstreamNodes.\"some hospital\"":"\"http://example.com/foo\"","akka.actor.debug.router-misconfiguration":"\"off\"","spray.routing.verbose-error-messages":"\"off\"","shrine.adapter.audit.database.dataSourceFrom":"\"JNDI\"","akka.actor.default-dispatcher.thread-pool-executor.core-pool-size-min":"8","akka.io.udp.max-channels":"4096","spray.can.server.reaping-cycle":"\"250 ms\"","shrine.adapter.crcEndpoint.timeout.seconds":"1","java.ext.dirs":"\"/Users/ben/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java\"","akka.actor.default-dispatcher.throughput-deadline-time":"\"0ms\"","spray.can.client.parsing.header-cache.Date":"0","spray.can.parsing.uri-parsing-mode":"\"strict\"","shrine.hub.downstreamNodes.CHB":"\"http://example.com/chb\"","shrine.keystore.file":"\"shrine.keystore\"","akka.actor.deployment.default.dispatcher":"\"\"","akka.io.tcp.management-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.host-connector.client.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","spray.can.client.max-encryption-chunk-size":"\"1m\"","java.runtime.name":"\"Java(TM) SE Runtime Environment\"","akka.actor.default-mailbox.mailbox-push-timeout-time":"\"10s\"","akka.actor.default-dispatcher.default-executor.fallback":"\"fork-join-executor\"","akka.actor.router.type-mapping.round-robin-group":"\"akka.routing.RoundRobinGroup\"","akka.io.udp.receive-throughput":"3","spray.can.host-connector.client.parsing.ssl-session-info-header":"\"off\"","spray.servlet.illegal-header-warnings":"\"on\"","akka.stdout-loglevel":"\"WARNING\"","shrine.adapter.audit.database.jndiDataSourceName":"\"java:comp/env/jdbc/adapterAuditDB\"","shrine.queryEntryPoint.authorizationType":"\"hms-steward\"","spray.can.server.parsing.max-chunk-size":"\"1m\"","file.separator":"\"/\"","spray.can.client.idle-timeout":"\"60 s\"","spray.can.server.parsing.header-cache.Date":"0","shrine.queryEntryPoint.sheriffEndpoint.timeout.seconds":"1","spray.can.host-connector.client.parsing.header-cache.If-None-Match":"0","spray.can.parsing.ssl-session-info-header":"\"off\"","spray.can.host-connector.client.idle-timeout":"\"60 s\"","akka.actor.router.type-mapping.consistent-hashing-pool":"\"akka.routing.ConsistentHashingPool\"","akka.io.tcp.batch-accept-limit":"10","shrine.keystore.privateKeyAlias":"\"test-cert\"","spray.can.client.parsing.header-cache.If-Match":"0","shrine.adapter.crcEndpoint.acceptAllCerts":"true","spray.can.parsing.header-cache.Date":"0","akka.io.udp.received-message-size-limit":"\"unlimited\"","spray.can.client.parsing.uri-parsing-mode":"\"strict\"","akka.actor.deployment.default.nr-of-instances":"1","spray.routing.file-get-conditional":"\"on\"","akka.actor.deployment.default.tail-chopping-router.interval":"\"10 milliseconds\"","spray.can.host-connector.client.parsing.header-cache.If-Modified-Since":"0","akka.actor.router.type-mapping.broadcast-group":"\"akka.routing.BroadcastGroup\"","akka.actor.mailbox.bounded-queue-based.mailbox-type":"\"akka.dispatch.BoundedMailbox\"","spray.can.server.parsing.header-cache.If-None-Match":"0","spray.can.parsing.header-cache.Content-MD5":"0","shrine.adapter.crcEndpoint.url":"\"http://services.i2b2.org/i2b2/rest/QueryToolService/\"","spray.can.server.unbind-timeout":"\"1s\"","akka.daemonic":"\"off\"","akka.io.udp-connected.max-channels":"4096","spray.can.server.ssl-tracing":"\"off\"","java.class.version":"\"52.0\"","shrine.queryEntryPoint.sheriffEndpoint.url":"\"http://localhost:8080/shrine-hms-authorization/queryAuthorization\"","akka.io.udp-connected.selector-dispatcher":"\"akka.io.pinned-dispatcher\"","spray.can.server.back-pressure.noack-rate":"10","spray.can.server.parsing.uri-parsing-mode":"\"strict\"","java.specification.name":"\"Java Platform API Specification\"","sun.boot.class.path":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/classes\"","spray.can.server.parsing.max-header-count":"64","akka.extensions":"[]","akka.actor.typed.timeout":"\"5s\"","spray.can.server.server-header":"\"spray-can/1.3.3\"","spray.can.server.request-chunk-aggregation-limit":"\"1m\"","akka.io.udp-connected.selector-association-retries":"10","akka.actor.serialize-messages":"\"off\"","spray.can.host-connector.client.parsing.max-content-length":"\"8m\"","spray.can.server.registration-timeout":"\"1s\"","shrine.pmEndpoint.url":"\"http://services.i2b2.org/i2b2/rest/PMService/getServices\"","akka.io.udp-connected.management-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.parsing.max-content-length":"\"8m\"","spray.can.server.back-pressure.reading-low-watermark":"\"infinite\"","spray.can.server.parsing.max-chunk-ext-length":"256","akka.loglevel":"\"INFO\"","spray.can.server.parsing.header-cache.If-Unmodified-Since":"0","spray.can.server.transparent-head-requests":"\"on\"","spray.can.client.parsing.header-cache.Content-MD5":"0","akka.io.tcp.selector-dispatcher":"\"akka.io.pinned-dispatcher\"","user.timezone":"\"America/New_York\"","spray.can.server.parsing.max-header-name-length":"64","spray.can.client.proxy.http":"\"default\"","spray.can.host-connector.max-retries":"5","spray.can.settings-group-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.server.parsing.header-cache.default":"12","spray.can.client.proxy.https":"\"default\"","spray.can.server.ssl-encryption":"\"off\"","spray.can.server.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","akka.actor.default-dispatcher.fork-join-executor.parallelism-factor":"3","shrine.queryEntryPoint.sheriffEndpoint.acceptAllCerts":"true","spray.can.host-connector-dispatcher":"\"akka.actor.default-dispatcher\"","java.specification.vendor":"\"Oracle Corporation\"","spray.can.host-connector.client.response-chunk-aggregation-limit":"\"1m\"","spray.can.server.parsing.max-content-length":"\"8m\"","spray.routing.file-chunking-threshold-size":"\"128k\"","akka.io.tcp.direct-buffer-pool-limit":"1000","sun.java.launcher":"\"SUN_STANDARD\"","spray.can.server.request-timeout":"\"20 s\"","akka.actor.default-dispatcher.shutdown-timeout":"\"1s\"","spray.can.server.parsing.max-response-reason-length":"64","os.version":"\"10.10.5\"","surefire.test.class.path":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/test-classes:/Users/ben/git/hms/develop/apps/shrine-app/target/classes:/Users/ben/.m2/repository/net/shrine/shrine-test-commons/1.21.0-SNAPSHOT/shrine-test-commons-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/sf/opencsv/opencsv/2.3/opencsv-2.3.jar:/Users/ben/.m2/repository/com/typesafe/config/1.2.1/config-1.2.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-util/1.21.0-SNAPSHOT/shrine-util-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/apache/commons/commons-email/1.2/commons-email-1.2.jar:/Users/ben/.m2/repository/javax/mail/mail/1.4.1/mail-1.4.1.jar:/Users/ben/.m2/repository/javax/activation/activation/1.1/activation-1.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-hms-core/1.21.0-SNAPSHOT/shrine-hms-core-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-auth/1.21.0-SNAPSHOT/shrine-auth-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-actor_2.11/2.3.8/akka-actor_2.11-2.3.8.jar:/Users/ben/.m2/repository/org/json4s/json4s-native_2.11/3.2.11/json4s-native_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-core_2.11/3.2.11/json4s-core_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-ast_2.11/3.2.11/json4s-ast_2.11-3.2.11.jar:/Users/ben/.m2/repository/io/spray/spray-client_2.11/1.3.3/spray-client_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-can_2.11/1.3.3/spray-can_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-io_2.11/1.3.3/spray-io_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-http_2.11/1.3.3/spray-http_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-scala_2.11/1.1.7/parboiled-scala_2.11-1.1.7.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-core/1.1.7/parboiled-core-1.1.7.jar:/Users/ben/.m2/repository/io/spray/spray-httpx_2.11/1.3.3/spray-httpx_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/jvnet/mimepull/mimepull/1.9.5/mimepull-1.9.5.jar:/Users/ben/.m2/repository/io/spray/spray-routing_2.11/1.3.3/spray-routing_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/chuusai/shapeless_2.11/1.2.4/shapeless_2.11-1.2.4.jar:/Users/ben/.m2/repository/io/spray/spray-servlet_2.11/1.3.3/spray-servlet_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-util_2.11/1.3.3/spray-util_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-slf4j_2.11/2.3.8/akka-slf4j_2.11-2.3.8.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-qep/1.21.0-SNAPSHOT/shrine-qep-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/squeryl/squeryl_2.11/0.9.6-RC4/squeryl_2.11-0.9.6-RC4.jar:/Users/ben/.m2/repository/cglib/cglib-nodep/2.2/cglib-nodep-2.2.jar:/Users/ben/.m2/repository/org/scala-lang/scalap/2.11.7/scalap-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-compiler/2.11.7/scala-compiler-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-parser-combinators_2.11/1.0.4/scala-parser-combinators_2.11-1.0.4.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-server/1.19/jersey-server-1.19.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-core/1.19/jersey-core-1.19.jar:/Users/ben/.m2/repository/javax/ws/rs/jsr311-api/1.1.1/jsr311-api-1.1.1.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-client/1.19/jersey-client-1.19.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-client-api/1.21.0-SNAPSHOT/shrine-adapter-client-api-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-data-commons/1.21.0-SNAPSHOT/shrine-data-commons-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/slick/slick_2.11/3.1.0/slick_2.11-3.1.0.jar:/Users/ben/.m2/repository/org/slf4j/slf4j-api/1.7.10/slf4j-api-1.7.10.jar:/Users/ben/.m2/repository/org/reactivestreams/reactive-streams/1.0.0/reactive-streams-1.0.0.jar:/Users/ben/.m2/repository/org/suecarter/freeslick_2.11/3.0.3.1/freeslick_2.11-3.0.3.1.jar:/Users/ben/.m2/repository/mysql/mysql-connector-java/5.1.37/mysql-connector-java-5.1.37.jar:/Users/ben/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-service/1.21.0-SNAPSHOT/shrine-broadcaster-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-service/1.21.0-SNAPSHOT/shrine-adapter-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-ont-support/1.21.0-SNAPSHOT/shrine-ont-support-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol-query/1.21.0-SNAPSHOT/shrine-protocol-query-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/liftweb/lift-json_2.11/2.6.2/lift-json_2.11-2.6.2.jar:/Users/ben/.m2/repository/com/thoughtworks/paranamer/paranamer/2.4.1/paranamer-2.4.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/org/scala-lang/scala-library/2.11.7/scala-library-2.11.7.jar:/Users/ben/.m2/repository/junit/junit/4.12/junit-4.12.jar:/Users/ben/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:/Users/ben/.m2/repository/org/scalatest/scalatest_2.11/2.2.5/scalatest_2.11-2.2.5.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-xml_2.11/1.0.2/scala-xml_2.11-1.0.2.jar:/Users/ben/.m2/repository/org/scala-lang/scala-actors/2.11.7/scala-actors-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-reflect/2.11.7/scala-reflect-2.11.7.jar:\"","spray.can.server.chunkless-streaming":"\"off\"","spray.can.client.parsing.incoming-auto-chunking-threshold-size":"\"infinite\"","spray.can.client.parsing.header-cache.If-Unmodified-Since":"0","spray.can.host-connector.max-redirects":"0","spray.can.host-connector.client.parsing.header-cache.User-Agent":"32","akka.io.udp.trace-logging":"\"off\"","akka.io.udp-connected.receive-throughput":"3","spray.can.parsing.max-header-count":"64","shrine.hiveCredentials.crcProjectId":"\"Demo\"","akka.actor.reaper-interval":"\"5s\"","spray.can.parsing.illegal-header-warnings":"\"on\"","spray.can.parsing.max-response-reason-length":"64","shrine.adapter.audit.database.slickProfileClassName":"\"slick.driver.MySQLDriver$\"","sun.os.patch.level":"\"unknown\"","spray.can.parsing.header-cache.If-Range":"0","gopherProxySet":"\"false\"","akka.actor.default-dispatcher.thread-pool-executor.core-pool-size-factor":"3","spray.can.parsing.header-cache.If-Modified-Since":"0","shrine.queryEntryPoint.includeAggregateResults":"false","akka.actor.router.type-mapping.tail-chopping-pool":"\"akka.routing.TailChoppingPool\"","surefire.real.class.path":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefirebooter8537185626748921880.jar\"","shrine.ontEndpoint.timeout.seconds":"1","shrine.queryEntryPoint.authenticationType":"\"ecommons\"","akka.actor.default-dispatcher.thread-pool-executor.max-pool-size-factor":"3","shrine.adapter.create":"true","akka.actor.default-dispatcher.fork-join-executor.parallelism-max":"64","shrine.queryEntryPoint.audit.database.jndiDataSourceName":"\"java:comp/env/jdbc/qepAuditDB\"","spray.can.host-connector.client.user-agent-header":"\"spray-can/1.3.3\"","akka.actor.deployment.default.mailbox":"\"\"","java.vm.specification.vendor":"\"Oracle Corporation\"","spray.can.host-connector.client.parsing.header-cache.If-Range":"0","slick.ansiDump":"false","spray.can.client.parsing.header-cache.If-None-Match":"0","akka.home":"\"\"","akka.actor.provider":"\"akka.actor.LocalActorRefProvider\"","spray.can.client.response-chunk-aggregation-limit":"\"1m\"","akka.io.tcp.max-received-message-size":"\"unlimited\"","akka.actor.mailbox.requirements.\"akka.dispatch.BoundedMessageQueueSemantics\"":"\"akka.actor.mailbox.bounded-queue-based\"","shrine.adapter.adapterMappingsFileName":"\"AdapterMappings.xml\"","spray.can.parsing.header-cache.If-Match":"0","akka.actor.default-dispatcher.mailbox-requirement":"\"\"","akka.actor.serializers.bytes":"\"akka.serialization.ByteArraySerializer\"","akka.actor.default-dispatcher.executor":"\"default-executor\"","akka.actor.debug.receive":"\"off\"","akka.actor.default-dispatcher.thread-pool-executor.max-pool-size-max":"64","spray.can.parsing.max-uri-length":"\"2k\"","user.country":"\"US\"","shrine.hiveCredentials.domain":"\"HarvardDemo\"","shrine.keystore.keyStoreType":"\"PKCS12\"","sun.jnu.encoding":"\"UTF-8\"","shrine.hub.maxQueryWaitTime.minutes":"4.5","akka.version":"\"2.3.8\"","shrine.queryEntryPoint.broadcasterServiceEndpoint.url":"\"http://example.com/shrine/rest/broadcaster/broadcast\"","user.language":"\"en\"","spray.can.host-connector.client.connecting-timeout":"\"10s\"","akka.scheduler.shutdown-timeout":"\"5s\"","spray.can.client.parsing.illegal-header-warnings":"\"on\"","akka.actor.default-dispatcher.attempt-teamwork":"\"on\"","spray.servlet.uri-parsing-mode":"\"relaxed\"","akka.io.tcp.max-channels":"256000","shrine.keystore.caCertAliases":"[\n # shrine.conf: 117\n \"carra ca\"\n]","slick.sqlIndent":"false","akka.actor.default-dispatcher.thread-pool-executor.max-pool-size-min":"8","akka.actor.default-dispatcher.thread-pool-executor.task-queue-type":"\"linked\"","spray.servlet.timeout-handler":"\"\"","spray.can.server.verbose-error-messages":"\"off\"","shrine.humanReadableNodeName":"\"SHRINE Cell\"","akka.io.tcp.file-io-transferTo-limit":"\"512 KiB\"","akka.loggers":"[\n # reference.conf: 17\n \"akka.event.Logging$DefaultLogger\"\n]","localRepository":"\"/Users/ben/.m2/repository\"","spray.can.host-connector.client.parsing.header-cache.default":"12","spray.can.parsing.header-cache.If-None-Match":"0","spray.servlet.root-path":"\"AUTO\"","akka.io.tcp.finish-connect-retries":"5","spray.can.client.parsing.header-cache.If-Range":"0","akka.actor.mailbox.requirements.\"akka.dispatch.BoundedDequeBasedMessageQueueSemantics\"":"\"akka.actor.mailbox.bounded-deque-based\"","akka.actor.default-dispatcher.throughput":"5","akka.actor.mailbox.requirements.\"akka.dispatch.UnboundedMessageQueueSemantics\"":"\"akka.actor.mailbox.unbounded-queue-based\"","akka.scheduler.implementation":"\"akka.actor.LightArrayRevolverScheduler\"","shrine.adapter.audit.database.createTablesOnStart":"false","akka.actor.default-mailbox.mailbox-capacity":"1000","spray.can.server.parsing.header-cache.Content-MD5":"0","shrine.hub.shouldQuerySelf":"true","spray.routing.range-coalescing-threshold":"80","akka.actor.router.type-mapping.random-pool":"\"akka.routing.RandomPool\"","spray.can.host-connector.client.parsing.max-header-count":"64","spray.can.parsing.header-cache.default":"12","shrine.shrineDatabaseType":"\"mysql\"","shrine.queryEntryPoint.sheriffCredentials.username":"\"sheriffUsername\"","spray.can.server.parsing.header-cache.If-Modified-Since":"0","java.awt.printerjob":"\"sun.lwawt.macosx.CPrinterJob\"","java.awt.graphicsenv":"\"sun.awt.CGraphicsEnvironment\"","spray.can.host-connector.client.proxy.http":"\"default\"","spray.servlet.request-timeout":"\"30 s\"","spray.can.host-connector.client.parsing.max-response-reason-length":"64","akka.io.pinned-dispatcher.thread-pool-executor.allow-core-pool-timeout":"\"off\"","akka.actor.guardian-supervisor-strategy":"\"akka.actor.DefaultSupervisorStrategy\"","spray.routing.range-count-limit":"16","akka.actor.serialization-bindings.\"java.io.Serializable\"":"\"java\"","akka.actor.default-dispatcher.thread-pool-executor.keep-alive-time":"\"60s\"","awt.toolkit":"\"sun.lwawt.macosx.LWCToolkit\"","spray.can.server.parsing.header-cache.If-Match":"0","akka.io.udp.direct-buffer-size":"\"128 KiB\"","java.class.path":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/test-classes:/Users/ben/git/hms/develop/apps/shrine-app/target/classes:/Users/ben/.m2/repository/net/shrine/shrine-test-commons/1.21.0-SNAPSHOT/shrine-test-commons-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/sf/opencsv/opencsv/2.3/opencsv-2.3.jar:/Users/ben/.m2/repository/com/typesafe/config/1.2.1/config-1.2.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-util/1.21.0-SNAPSHOT/shrine-util-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/apache/commons/commons-email/1.2/commons-email-1.2.jar:/Users/ben/.m2/repository/javax/mail/mail/1.4.1/mail-1.4.1.jar:/Users/ben/.m2/repository/javax/activation/activation/1.1/activation-1.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-hms-core/1.21.0-SNAPSHOT/shrine-hms-core-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-auth/1.21.0-SNAPSHOT/shrine-auth-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-actor_2.11/2.3.8/akka-actor_2.11-2.3.8.jar:/Users/ben/.m2/repository/org/json4s/json4s-native_2.11/3.2.11/json4s-native_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-core_2.11/3.2.11/json4s-core_2.11-3.2.11.jar:/Users/ben/.m2/repository/org/json4s/json4s-ast_2.11/3.2.11/json4s-ast_2.11-3.2.11.jar:/Users/ben/.m2/repository/io/spray/spray-client_2.11/1.3.3/spray-client_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-can_2.11/1.3.3/spray-can_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-io_2.11/1.3.3/spray-io_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-http_2.11/1.3.3/spray-http_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-scala_2.11/1.1.7/parboiled-scala_2.11-1.1.7.jar:/Users/ben/.m2/repository/org/parboiled/parboiled-core/1.1.7/parboiled-core-1.1.7.jar:/Users/ben/.m2/repository/io/spray/spray-httpx_2.11/1.3.3/spray-httpx_2.11-1.3.3.jar:/Users/ben/.m2/repository/org/jvnet/mimepull/mimepull/1.9.5/mimepull-1.9.5.jar:/Users/ben/.m2/repository/io/spray/spray-routing_2.11/1.3.3/spray-routing_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/chuusai/shapeless_2.11/1.2.4/shapeless_2.11-1.2.4.jar:/Users/ben/.m2/repository/io/spray/spray-servlet_2.11/1.3.3/spray-servlet_2.11-1.3.3.jar:/Users/ben/.m2/repository/io/spray/spray-util_2.11/1.3.3/spray-util_2.11-1.3.3.jar:/Users/ben/.m2/repository/com/typesafe/akka/akka-slf4j_2.11/2.3.8/akka-slf4j_2.11-2.3.8.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-qep/1.21.0-SNAPSHOT/shrine-qep-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/org/squeryl/squeryl_2.11/0.9.6-RC4/squeryl_2.11-0.9.6-RC4.jar:/Users/ben/.m2/repository/cglib/cglib-nodep/2.2/cglib-nodep-2.2.jar:/Users/ben/.m2/repository/org/scala-lang/scalap/2.11.7/scalap-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-compiler/2.11.7/scala-compiler-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-parser-combinators_2.11/1.0.4/scala-parser-combinators_2.11-1.0.4.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-server/1.19/jersey-server-1.19.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-core/1.19/jersey-core-1.19.jar:/Users/ben/.m2/repository/javax/ws/rs/jsr311-api/1.1.1/jsr311-api-1.1.1.jar:/Users/ben/.m2/repository/com/sun/jersey/jersey-client/1.19/jersey-client-1.19.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-client-api/1.21.0-SNAPSHOT/shrine-adapter-client-api-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-data-commons/1.21.0-SNAPSHOT/shrine-data-commons-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/com/typesafe/slick/slick_2.11/3.1.0/slick_2.11-3.1.0.jar:/Users/ben/.m2/repository/org/slf4j/slf4j-api/1.7.10/slf4j-api-1.7.10.jar:/Users/ben/.m2/repository/org/reactivestreams/reactive-streams/1.0.0/reactive-streams-1.0.0.jar:/Users/ben/.m2/repository/org/suecarter/freeslick_2.11/3.0.3.1/freeslick_2.11-3.0.3.1.jar:/Users/ben/.m2/repository/mysql/mysql-connector-java/5.1.37/mysql-connector-java-5.1.37.jar:/Users/ben/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-service/1.21.0-SNAPSHOT/shrine-broadcaster-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-adapter-service/1.21.0-SNAPSHOT/shrine-adapter-service-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-ont-support/1.21.0-SNAPSHOT/shrine-ont-support-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/shrine/shrine-crypto/1.21.0-SNAPSHOT/shrine-crypto-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol/1.21.0-SNAPSHOT/shrine-protocol-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-protocol-query/1.21.0-SNAPSHOT/shrine-protocol-query-1.21.0-SNAPSHOT.jar:/Users/ben/.m2/repository/net/liftweb/lift-json_2.11/2.6.2/lift-json_2.11-2.6.2.jar:/Users/ben/.m2/repository/com/thoughtworks/paranamer/paranamer/2.4.1/paranamer-2.4.1.jar:/Users/ben/.m2/repository/net/shrine/shrine-config/1.21.0-SNAPSHOT/shrine-config-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-broadcaster-aggregator/1.21.0-SNAPSHOT/shrine-broadcaster-aggregator-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/net/shrine/shrine-client/1.21.0-SNAPSHOT/shrine-client-1.21.0-SNAPSHOT-tests.jar:/Users/ben/.m2/repository/org/scala-lang/scala-library/2.11.7/scala-library-2.11.7.jar:/Users/ben/.m2/repository/junit/junit/4.12/junit-4.12.jar:/Users/ben/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:/Users/ben/.m2/repository/org/scalatest/scalatest_2.11/2.2.5/scalatest_2.11-2.2.5.jar:/Users/ben/.m2/repository/org/scala-lang/modules/scala-xml_2.11/1.0.2/scala-xml_2.11-1.0.2.jar:/Users/ben/.m2/repository/org/scala-lang/scala-actors/2.11.7/scala-actors-2.11.7.jar:/Users/ben/.m2/repository/org/scala-lang/scala-reflect/2.11.7/scala-reflect-2.11.7.jar:\"","akka.actor.default-dispatcher.thread-pool-executor.core-pool-size-max":"64","akka.io.udp.select-timeout":"\"infinite\"","spray.can.host-connector.client.proxy.https":"\"default\"","akka.actor.router.type-mapping.scatter-gather-pool":"\"akka.routing.ScatterGatherFirstCompletedPool\"","akka.actor.deployment.default.resizer.pressure-threshold":"1","spray.can.client.request-header-size-hint":"512","spray.can.server.idle-timeout":"\"60 s\"","akka.io.udp-connected.trace-logging":"\"off\"","os.name":"\"Mac OS X\"","slick.detectRebuild":"false","shrine.queryEntryPoint.audit.database.slickProfileClassName":"\"slick.driver.MySQLDriver$\"","spray.can.server.pipelining-limit":"1","spray.can.host-connector.client.request-timeout":"\"20 s\"","akka.actor.mailbox.requirements.\"akka.dispatch.MultipleConsumerSemantics\"":"\"akka.actor.mailbox.unbounded-queue-based\"","spray.can.server.max-encryption-chunk-size":"\"1m\"","spray.can.host-connector.client.parsing.max-chunk-size":"\"1m\"","spray.can.server.parsing-error-abort-timeout":"\"2s\"","java.vm.vendor":"\"Oracle Corporation\"","akka.io.udp-connected.direct-buffer-pool-limit":"1000","spray.can.server.automatic-back-pressure-handling":"\"on\"","spray.can.server.verbose-error-logging":"\"off\"","spray.can.host-connector.client.reaping-cycle":"\"250 ms\"","spray.can.host-connector.client.parsing.max-header-name-length":"64","akka.actor.router.type-mapping.round-robin-pool":"\"akka.routing.RoundRobinPool\"","spray.can.connection-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.server.stats-support":"\"on\"","slick.unicodeDump":"false","java.vendor.url.bug":"\"http://bugreport.sun.com/bugreport/\"","akka.io.udp-connected.worker-dispatcher":"\"akka.actor.default-dispatcher\"","spray.can.client.reaping-cycle":"\"250 ms\"","akka.io.udp-connected.direct-buffer-size":"\"128 KiB\"","spray.can.server.response-header-size-hint":"512","spray.can.parsing.max-header-name-length":"64","spray.can.host-connector.client.parsing.header-cache.Content-MD5":"0","akka.actor.deployment.default.resizer.upper-bound":"10","akka.actor.default-dispatcher.thread-pool-executor.allow-core-timeout":"\"on\"","user.name":"\"ben\"","akka.actor.mailbox.unbounded-deque-based.mailbox-type":"\"akka.dispatch.UnboundedDequeBasedMailbox\"","akka.actor.default-dispatcher.thread-pool-executor.task-queue-size":"-1","akka.logger-startup-timeout":"\"5s\"","akka.actor.mailbox.bounded-deque-based.mailbox-type":"\"akka.dispatch.BoundedDequeBasedMailbox\"","spray.can.client.parsing.max-header-name-length":"64","spray.can.host-connector.client.ssl-tracing":"\"off\"","spray.can.parsing.header-cache.If-Unmodified-Since":"0","spray.servlet.verbose-error-messages":"\"off\"","shrine.adapter.immediatelyRunIncomingQueries":"false","akka.io.tcp.nr-of-selectors":"1","akka.actor.router.type-mapping.smallest-mailbox-pool":"\"akka.routing.SmallestMailboxPool\"","java.vm.name":"\"Java HotSpot(TM) 64-Bit Server VM\"","akka.io.tcp.windows-connection-abort-workaround-enabled":"\"auto\"","akka.actor.router.type-mapping.tail-chopping-group":"\"akka.routing.TailChoppingGroup\"","spray.routing.render-vanity-footer":"\"yes\"","sun.java.command":"\"/Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefirebooter8537185626748921880.jar /Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefire8096778636479228628tmp /Users/ben/git/hms/develop/apps/shrine-app/target/surefire/surefire_03134404175196944217tmp\"","spray.can.server.remote-address-header":"\"off\"","akka.actor.deployment.default.resizer.backoff-rate":"0.1","akka.actor.debug.event-stream":"\"off\"","spray.version":"\"1.3.3\"","spray.can.host-connector.client.parsing.max-header-value-length":"\"8k\"","java.home":"\"/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre\"","akka.io.udp-connected.nr-of-selectors":"1","spray.can.host-connector.client.parsing.max-uri-length":"\"2k\"","spray.can.parsing.header-cache.User-Agent":"32","akka.io.pinned-dispatcher.executor":"\"thread-pool-executor\"","spray.can.server.bind-timeout":"\"1s\"","akka.log-dead-letters":"10","java.version":"\"1.8.0_40\"","sun.io.unicode.encoding":"\"UnicodeBig\"","spray.can.server.default-host-header":"\"\""}}
\ No newline at end of file
diff --git a/apps/dashboard-app/src/test/scala/net/shrine/dashboard/DashboardServiceTest.scala b/apps/dashboard-app/src/test/scala/net/shrine/dashboard/DashboardServiceTest.scala
index 12d55ac50..96c12992e 100644
--- a/apps/dashboard-app/src/test/scala/net/shrine/dashboard/DashboardServiceTest.scala
+++ b/apps/dashboard-app/src/test/scala/net/shrine/dashboard/DashboardServiceTest.scala
@@ -1,283 +1,284 @@
package net.shrine.dashboard
import java.math.BigInteger
import java.security.PrivateKey
import java.util.Date
import io.jsonwebtoken.impl.TextCodec
import io.jsonwebtoken.{SignatureAlgorithm, Jwts}
import net.shrine.authorization.steward.OutboundUser
import net.shrine.crypto.{KeyStoreDescriptorParser, KeyStoreCertCollection}
import net.shrine.dashboard.jwtauth.ShrineJwtAuthenticator
import net.shrine.i2b2.protocol.pm.User
import net.shrine.protocol.Credential
import org.json4s.native.JsonMethods.parse
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.FlatSpec
import spray.http.HttpHeaders.Authorization
import spray.http.{OAuth2BearerToken, HttpHeaders, BasicHttpCredentials}
import spray.testkit.ScalatestRouteTest
import spray.http.StatusCodes.{OK,PermanentRedirect,Unauthorized,NotFound}
import scala.language.postfixOps
@RunWith(classOf[JUnitRunner])
class DashboardServiceTest extends FlatSpec with ScalatestRouteTest with DashboardService {
def actorRefFactory = system
import scala.concurrent.duration._
implicit val routeTestTimeout = RouteTestTimeout(10 seconds)
val adminUserName = "keith"
val adminFullName = adminUserName
/**
* to run these tests with I2B2
* add a user named keith, to be the admin
* add a Boolean parameter for keith, Admin, true
* add all this user to the i2b2 project
*/
val adminCredentials = BasicHttpCredentials(adminUserName,"shh!")
val brokenCredentials = BasicHttpCredentials(adminUserName,"wrong password")
val adminUser = User(
fullName = adminUserName,
username = adminFullName,
domain = "domain",
credential = new Credential("admin's password",false),
params = Map(),
rolesByProject = Map()
)
val adminOutboundUser = OutboundUser.createFromUser(adminUser)
"DashboardService" should "return an OK and a valid outbound user for a user/whoami request" in {
Get(s"/user/whoami") ~>
addCredentials(adminCredentials) ~>
route ~> check {
assertResult(OK)(status)
val userJson = new String(body.data.toByteArray)
val outboundUser = parse(userJson).extract[OutboundUser]
assertResult(adminOutboundUser)(outboundUser)
}
}
"DashboardService" should "return an OK and a valid outbound user for a user/whoami request and an '' " in {
Get(s"/user/whoami") ~>
addCredentials(brokenCredentials) ~>
route ~> check {
assertResult(OK)(status)
val response = new String(body.data.toByteArray)
assertResult(""""AuthenticationFailed"""")(response)
}
}
"DashboardService" should "redirect several urls to client/index.html" in {
Get() ~>
route ~> check {
status === PermanentRedirect
header("Location") === "client/index.html"
}
Get("/") ~>
route ~> check {
status === PermanentRedirect
header("Location") === "client/index.html"
}
Get("/index.html") ~>
route ~> check {
status === PermanentRedirect
header("Location") === "client/index.html"
}
Get("/client") ~>
route ~> check {
status === PermanentRedirect
header("Location") === "client/index.html"
}
Get("/client/") ~>
route ~> check {
status === PermanentRedirect
header("Location") === "client/index.html"
}
}
"DashboardService" should "return an OK and the right version string for an admin/happy/version test" in {
Get(s"/admin/happy/version") ~>
addCredentials(adminCredentials) ~>
route ~> check {
assertResult(OK)(status)
val versionString = new String(body.data.toByteArray)
//todo test it to see if it's right
}
}
"DashboardService" should "return an OK and mess with the right version string for an admin/messWithHappyVersion test" in {
Get(s"/admin/messWithHappyVersion") ~>
addCredentials(adminCredentials) ~>
route ~> check {
assertResult(OK)(status)
val versionString = new String(body.data.toByteArray)
//todo test it to see if it's right
}
}
"DashboardService" should "return an OK for admin/status/config" in {
Get(s"/admin/status/config") ~>
addCredentials(adminCredentials) ~>
route ~> check {
assertResult(OK)(status)
val statusString = new String(body.data.toByteArray)
//todo test it to see if it's right
}
}
"DashboardService" should "return an OK for admin/status/classpath" in {
Get(s"/admin/status/classpath") ~>
addCredentials(adminCredentials) ~>
route ~> check {
assertResult(OK)(status)
val classpathString = new String(body.data.toByteArray)
//todo test it to see if it's right
//println(classpathString)
}
}
"DashboardService" should "return an OK for admin/status/summary" in {
Get(s"/admin/status/summary") ~>
addCredentials(adminCredentials) ~>
route ~> check {
assertResult(OK)(status)
val summary = new String(body.data.toByteArray)
//todo test it to see if it's right
println("Sumary " + summary)
}
}
val dashboardCredentials = BasicHttpCredentials(adminUserName,"shh!")
"DashboardService" should "return an OK and pong for fromDashboard/ping" in {
Get(s"/fromDashboard/ping") ~>
- addCredentials(ShrineJwtAuthenticator.createOAuthCredentials) ~>
+ addCredentials(ShrineJwtAuthenticator.createOAuthCredentials(adminUser)) ~>
route ~> check {
assertResult(OK)(status)
val string = new String(body.data.toByteArray)
assertResult(""""pong"""")(string)
}
}
"DashboardService" should "reject a fromDashboard/ping with an expired jwts header" in {
val config = DashboardConfigSource.config
val shrineCertCollection: KeyStoreCertCollection = KeyStoreCertCollection.fromFileRecoverWithClassPath(KeyStoreDescriptorParser(config.getConfig("shrine.keystore")))
val base64Cert = new String(TextCodec.BASE64URL.encode(shrineCertCollection.myCert.get.getEncoded))
val key: PrivateKey = shrineCertCollection.myKeyPair.privateKey
val expiration: Date = new Date(System.currentTimeMillis() - 300 * 1000) //bad for 5 minutes
val jwtsString = Jwts.builder().
setHeaderParam("kid", base64Cert).
setSubject(java.net.InetAddress.getLocalHost.getHostName).
setExpiration(expiration).
signWith(SignatureAlgorithm.RS512, key).
compact()
Get(s"/fromDashboard/ping") ~>
addCredentials(OAuth2BearerToken(jwtsString)) ~>
sealRoute(route) ~> check {
assertResult(Unauthorized)(status)
}
}
"DashboardService" should "reject a fromDashboard/ping with no subject" in {
val config = DashboardConfigSource.config
val shrineCertCollection: KeyStoreCertCollection = KeyStoreCertCollection.fromClassPathResource(KeyStoreDescriptorParser(config.getConfig("shrine.keystore")))
val base64Cert = new String(TextCodec.BASE64URL.encode(shrineCertCollection.myCert.get.getEncoded))
val key: PrivateKey = shrineCertCollection.myKeyPair.privateKey
val expiration: Date = new Date(System.currentTimeMillis() + 30 * 1000)
val jwtsString = Jwts.builder().
setHeaderParam("kid", base64Cert).
setExpiration(expiration).
signWith(SignatureAlgorithm.RS512, key).
compact()
Get(s"/fromDashboard/ping") ~>
addCredentials(OAuth2BearerToken(jwtsString)) ~>
sealRoute(route) ~> check {
assertResult(Unauthorized)(status)
}
}
"DashboardService" should "reject a fromDashboard/ping with no Authorization header" in {
Get(s"/fromDashboard/ping") ~>
sealRoute(route) ~> check {
assertResult(Unauthorized)(status)
}
}
"DashboardService" should "reject a fromDashboard/ping with an Authorization header for the wrong authorization spec" in {
Get(s"/fromDashboard/ping") ~>
addCredentials(adminCredentials) ~>
sealRoute(route) ~> check {
assertResult(Unauthorized)(status)
}
}
"DashboardService" should "not find a bogus web service to talk to" in {
- Get(s"/toDashboard/test") ~>
+ Get(s"/toDashboard/bogus.harvard.edu/ping") ~>
addCredentials(adminCredentials) ~>
sealRoute(route) ~> check {
val string = new String(body.data.toByteArray)
assertResult(NotFound)(status)
}
}
+
}
diff --git a/apps/dashboard-war/src/main/resources/reference.conf b/apps/dashboard-war/src/main/resources/reference.conf
index 4b089eb02..ed315800d 100644
--- a/apps/dashboard-war/src/main/resources/reference.conf
+++ b/apps/dashboard-war/src/main/resources/reference.conf
@@ -1,47 +1,49 @@
shrine {
dashboard {
gruntWatch = false //false for production, true for mvn tomcat7:run . Allows the client javascript and html files to be loaded via gruntWatch .
}
pmEndpoint {
url = "http://changeme.com/i2b2/services/PMService/getServices" //"http://services.i2b2.org/i2b2/services/PMService/getServices"
acceptAllCerts = true
timeout {
seconds = 1
}
}
// If the pmEndpoint acceptAllCerts = false then you need to supply a keystore
// keystore {
// file = "shrine.keystore"
// password = "chiptesting"
// privateKeyAlias = "test-cert"
// keyStoreType = "JKS"
// caCertAliases = [carra ca]
// }
authenticate {
realm = "SHRINE API"
usersource
{
type = "PmUserSource" //Must be ConfigUserSource (for isolated testing) or PmUserSource (for everything else)
domain = "set shrine.authenticate.usersource.domain to the PM authentication domain in dashboard.conf" //"i2b2demo"
}
}
}
//todo typesafe config precedence seems to do the right thing, but I haven't found the rules that say this reference.conf should override others
akka {
loglevel = INFO
-// log-config-on-start = on
+ // log-config-on-start = on
loggers = ["akka.event.slf4j.Slf4jLogger"]
-// logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
+ // logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
+ // Toggles whether the threads created by this ActorSystem should be daemons or not
+ daemonic = on
}
spray.servlet {
boot-class = "net.shrine.dashboard.Boot"
request-timeout = 30s
}
diff --git a/apps/dashboard-war/src/test/resources/dashboard.conf b/apps/dashboard-war/src/test/resources/dashboard.conf
index 5d0008424..bd9c141c9 100644
--- a/apps/dashboard-war/src/test/resources/dashboard.conf
+++ b/apps/dashboard-war/src/test/resources/dashboard.conf
@@ -1,24 +1,31 @@
shrine {
authenticate {
usersource {
//Bogus security for testing
type = "ConfigUserSource" //Must be ConfigUserSource (for isolated testing) or PmUserSource (for everything else)
researcher {
username = "ben"
password = "kapow"
}
steward {
username = "dave"
password = "kablam"
}
qep{
username = "qep"
password = "trustme"
}
admin{
username = "keith"
password = "shh!"
}
}
}
+ keystore {
+ file = "shrine.keystore"
+ password = "chiptesting"
+ privateKeyAlias = "test-cert"
+ keyStoreType = "JKS"
+ caCertAliases = [carra ca]
+ }
}
\ No newline at end of file
diff --git a/apps/dashboard-war/src/test/resources/shrine.keystore b/apps/dashboard-war/src/test/resources/shrine.keystore
new file mode 100644
index 000000000..faa5158ea
Binary files /dev/null and b/apps/dashboard-war/src/test/resources/shrine.keystore differ
diff --git a/apps/shrine-app/pom.xml b/apps/shrine-app/pom.xml
index 31612e9c5..1e1bc8ad5 100644
--- a/apps/shrine-app/pom.xml
+++ b/apps/shrine-app/pom.xml
@@ -1,108 +1,108 @@
shrine-base
net.shrine
1.21.0-SNAPSHOT
../../pom.xml
4.0.0
shrine-app
SHRINE App
jar
src/main/scala
src/test/scala
net.alchim31.maven
scala-maven-plugin
net.shrine
shrine-test-commons
${project.version}
test-jar
test
net.shrine
shrine-config
${project.version}
net.shrine
shrine-hms-core
${project.version}
net.shrine
- shrine-service
+ shrine-qep
${project.version}
net.shrine
shrine-broadcaster-aggregator
${project.version}
net.shrine
shrine-broadcaster-service
${project.version}
net.shrine
shrine-adapter-service
${project.version}
net.shrine
shrine-crypto
${project.version}
net.shrine
shrine-ont-support
${project.version}
net.shrine
shrine-crypto
${project.version}
test-jar
test
net.shrine
shrine-protocol
${project.version}
test-jar
test
net.shrine
shrine-config
${project.version}
test-jar
test
net.shrine
shrine-broadcaster-aggregator
${project.version}
test-jar
test
net.shrine
shrine-client
${project.version}
test-jar
test
diff --git a/apps/shrine-app/src/main/scala/net/shrine/happy/HappyShrineService.scala b/apps/shrine-app/src/main/scala/net/shrine/happy/HappyShrineService.scala
index 4fe5587ea..bc5b1f826 100644
--- a/apps/shrine-app/src/main/scala/net/shrine/happy/HappyShrineService.scala
+++ b/apps/shrine-app/src/main/scala/net/shrine/happy/HappyShrineService.scala
@@ -1,317 +1,317 @@
package net.shrine.happy
import net.shrine.log.Loggable
import net.shrine.wiring.ShrineConfig
import scala.concurrent.Await
import scala.util.Try
import scala.xml.Node
import scala.xml.NodeSeq
import net.shrine.adapter.dao.AdapterDao
import net.shrine.adapter.service.AdapterRequestHandler
import net.shrine.broadcaster.{IdAndUrl, AdapterClientBroadcaster}
-import net.shrine.service.dao.AuditDao
+import net.shrine.qep.dao.AuditDao
import net.shrine.client.Poster
import net.shrine.crypto.KeyStoreCertCollection
import net.shrine.crypto.Signer
import net.shrine.i2b2.protocol.pm.GetUserConfigurationRequest
import net.shrine.i2b2.protocol.pm.HiveConfig
import net.shrine.protocol.AuthenticationInfo
import net.shrine.protocol.BroadcastMessage
import net.shrine.protocol.Credential
import net.shrine.protocol.Failure
import net.shrine.protocol.NodeId
import net.shrine.protocol.Result
import net.shrine.protocol.ResultOutputType
import net.shrine.protocol.RunQueryRequest
import net.shrine.protocol.Timeout
import net.shrine.protocol.query.OccuranceLimited
import net.shrine.protocol.query.QueryDefinition
import net.shrine.protocol.query.Term
import net.shrine.util.{StackTrace, Versions, XmlUtil}
import net.shrine.ont.data.OntologyMetadata
import net.shrine.config.mappings.AdapterMappings
import net.shrine.crypto.SigningCertStrategy
/**
* @author Bill Simons
* @since 6/20/11
* @see http://cbmi.med.harvard.edu
* @see http://chip.org
*
* NOTICE: This software comes with NO guarantees whatsoever and is
* licensed as Lgpl Open Source
* @see http://www.gnu.org/licenses/lgpl.html
*/
final class HappyShrineService(
config: ShrineConfig,
certCollection: KeyStoreCertCollection,
signer: Signer,
pmPoster: Poster,
ontologyMetadata: OntologyMetadata,
adapterMappings: Option[AdapterMappings],
auditDaoOption: Option[AuditDao],
adapterDaoOption: Option[AdapterDao],
broadcasterOption: Option[AdapterClientBroadcaster],
adapterOption: Option[AdapterRequestHandler]) extends HappyShrineRequestHandler with Loggable {
info("Happy service initialized")
private val notAnAdapter = ""
private val notAHub = ""
private val domain = "happy"
private val username = "happy"
private val networkAuthn = AuthenticationInfo(domain, username, Credential("", isToken = false))
override def keystoreReport: String = {
val myCertId = certCollection.myCertId
def unpack(name: Option[String]) = name.getOrElse("Unknown")
XmlUtil.stripWhitespace {
{ config.keystoreDescriptor.file }
{ config.keystoreDescriptor.keyStoreType }
{ config.keystoreDescriptor.privateKeyAlias.getOrElse("unspecified") }
{
myCertId.map { myId =>
{ unpack(myId.name) }
{ myId.serial }
}.getOrElse {
}
}
{
certCollection.ids.map { certId =>
{ unpack(certId.name) }
{ certId.serial }
}
}
}.toString
}
private def nodeListAsXml: Iterable[Node] = config.hubConfig match {
case None => Nil
case Some(hubConfig) => hubConfig.downstreamNodes.map {
case IdAndUrl(NodeId(nodeName), nodeUrl) => {
{ nodeName }
{ nodeUrl }
}
}
}
override def routingReport: String = XmlUtil.stripWhitespace {
{ nodeListAsXml }
}.toString
override def hiveReport: String = {
val report = for {
adapterConfig <- config.adapterConfig
} yield {
val credentials = config.pmHiveCredentials
val pmRequest = GetUserConfigurationRequest(credentials.toAuthenticationInfo)
val response = pmPoster.post(pmRequest.toI2b2String)
HiveConfig.fromI2b2(response.body).toXmlString
}
report.getOrElse(notAnAdapter)
}
private def failureToXml(failure: Failure): NodeSeq = {
{ failure.origin }
{ StackTrace.stackTraceAsString(failure.cause) }
}
private def timeoutToXml(timeout: Timeout): NodeSeq = {
{ timeout.origin }
}
override def networkReport: String = {
val report = for {
hubConfig <- config.hubConfig
broadcaster <- broadcasterOption
} yield {
val message = newBroadcastMessageWithRunQueryRequest
val multiplexer = broadcaster.broadcast(message)
import scala.concurrent.duration._
val responses = Await.result(multiplexer.responses, hubConfig.maxQueryWaitTime).toSeq
val failures = responses.collect { case f: Failure => f }
val timeouts = responses.collect { case t: Timeout => t }
val validResults = responses.collect { case r: Result => r }
val noProblems = failures.isEmpty && timeouts.isEmpty
XmlUtil.stripWhitespace {
{ hubConfig.shouldQuerySelf }
{ nodeListAsXml }
{ noProblems }
{ broadcaster.destinations.size }
{ validResults.size }
{ failures.size }
{ timeouts.size }
{
nodeListAsXml
}
{
failures.map(failureToXml)
}
{
timeouts.map(timeoutToXml)
}
}.toString
}
report.getOrElse(notAHub)
}
private def newRunQueryRequest(authn: AuthenticationInfo): RunQueryRequest = {
val queryDefinition = QueryDefinition("TestQuery", OccuranceLimited(1, Term(config.adapterStatusQuery)))
import scala.concurrent.duration._
RunQueryRequest(
"happyProject",
3.minutes,
authn,
None,
None,
Set(ResultOutputType.PATIENT_COUNT_XML),
queryDefinition)
}
private def newBroadcastMessageWithRunQueryRequest: BroadcastMessage = {
val req = newRunQueryRequest(networkAuthn)
signer.sign(BroadcastMessage(req.networkQueryId, networkAuthn, req), SigningCertStrategy.Attach)
}
override def adapterReport: String = {
val report = for {
adapterRequestHandler <- adapterOption
} yield {
val message = newBroadcastMessageWithRunQueryRequest
import scala.concurrent.duration._
val (resultAttempt: Try[Result], elapsed: Duration) = {
val start = System.currentTimeMillis
val attempt = Try(adapterRequestHandler.handleRequest(message))
val end = System.currentTimeMillis
(attempt, (end - start).milliseconds)
}
XmlUtil.stripWhitespace {
{
resultAttempt match {
case scala.util.Failure(cause) => failureToXml(Failure(NodeId("Local"), cause))
case scala.util.Success(Result(origin, elapsed, response)) => {
{ origin }
{ elapsed }
{ response.toXml }
}
}
}
}.toString
}
report.getOrElse(notAnAdapter)
}
override def auditReport: String = {
val report = for {
auditDao <- auditDaoOption
} yield {
val recentEntries = auditDao.findRecentEntries(10)
XmlUtil.stripWhitespace {
{
recentEntries map { entry =>
{ entry.id }
{ entry.username }
}
}
}.toString
}
report.getOrElse(notAHub)
}
override def queryReport: String = {
val report = for {
adapterDao <- adapterDaoOption
} yield {
val recentQueries = adapterDao.findRecentQueries(10)
XmlUtil.stripWhitespace {
{
recentQueries.map { query =>
{ query.networkId }
{ query.dateCreated }
{ query.name }
}
}
}.toString
}
report.getOrElse(notAnAdapter)
}
override def versionReport: String = XmlUtil.stripWhitespace {
{ Versions.version }
{ ontologyMetadata.ontologyVersion }
{ adapterMappings.map(_.version).getOrElse("No adapter mappings present") }
{ Versions.scmRevision }
{ Versions.scmBranch }
{ Versions.buildDate }
}.toString
override def all: String = {
s"$versionReport$keystoreReport$routingReport$hiveReport$networkReport$adapterReport$auditReport$queryReport"
}
}
diff --git a/apps/shrine-app/src/main/scala/net/shrine/wiring/AuthStrategy.scala b/apps/shrine-app/src/main/scala/net/shrine/wiring/AuthStrategy.scala
index 8732444a6..7a422ede7 100644
--- a/apps/shrine-app/src/main/scala/net/shrine/wiring/AuthStrategy.scala
+++ b/apps/shrine-app/src/main/scala/net/shrine/wiring/AuthStrategy.scala
@@ -1,77 +1,77 @@
package net.shrine.wiring
import net.shrine.authorization.steward.StewardConfig
import net.shrine.config.Keys
import net.shrine.authentication.{AuthenticationType, Authenticator, PmAuthenticator}
import net.shrine.authorization.{AuthorizationType, StewardQueryAuthorizationService, QueryAuthorizationService, AllowsAllAuthorizationService}
-import net.shrine.service.AllowsAllAuthenticator
+import net.shrine.qep.AllowsAllAuthenticator
import net.shrine.client.Poster
import net.shrine.hms.authorization.HmsDataStewardAuthorizationService
import net.shrine.hms.authentication.EcommonsPmAuthenticator
import net.shrine.hms.authorization.JerseySheriffClient
/**
* @author clint
* @since Jul 1, 2014
*/
object AuthStrategy {
import AuthenticationType._
import AuthorizationType._
def determineAuthenticator(authType: AuthenticationType, pmPoster: Poster): Authenticator = authType match {
case NoAuthentication => AllowsAllAuthenticator
case Pm => PmAuthenticator(pmPoster)
case Ecommons => EcommonsPmAuthenticator(pmPoster)
case _ => throw new Exception(s"Disallowed authentication type '$authType'")
}
def determineQueryAuthorizationService(authType: AuthorizationType, shrineConfig: ShrineConfig, authenticator: Authenticator): QueryAuthorizationService = {
authType match {
case ShrineSteward => makeShrineStewardAuthorizationService(shrineConfig)
case HmsSteward => makeHmsStewardAuthorizationService(shrineConfig, authenticator)
case NoAuthorization => AllowsAllAuthorizationService
case _ => throw new Exception(s"Disallowed authorization type '$authType'")
}
}
private def makeShrineStewardAuthorizationService(shrineConfig: ShrineConfig): QueryAuthorizationService = {
require(shrineConfig.queryEntryPointConfig.isDefined, s"${Keys.queryEntryPoint} section must be defined in shrine.conf")
val queryEntryPointConfig = shrineConfig.queryEntryPointConfig.get
require(queryEntryPointConfig.stewardConfig.isDefined, s"${Keys.queryEntryPoint}.shrineSteward section must be defined in shrine.conf")
val stewardConfig: StewardConfig = queryEntryPointConfig.stewardConfig.get
StewardQueryAuthorizationService(
qepUserName = stewardConfig.qepUserName,
qepPassword = stewardConfig.qepPassword,
stewardBaseUrl = stewardConfig.stewardBaseUrl)
}
private def makeHmsStewardAuthorizationService(shrineConfig: ShrineConfig, authenticator: => Authenticator): QueryAuthorizationService = {
//NB: Fail fast here, since on the fully-meshed HMS deployment, all nodes are expected to be
//query entry points
require(shrineConfig.queryEntryPointConfig.isDefined, s"${Keys.queryEntryPoint} section must be defined in shrine.conf")
val queryEntryPointConfig = shrineConfig.queryEntryPointConfig.get
val sheriffEndpointOption = queryEntryPointConfig.sheriffEndpoint
val sheriffCredentialsOption = queryEntryPointConfig.sheriffCredentials
//NB: Fail fast, HMS nodes need to use The Sheriff
require(sheriffEndpointOption.isDefined, "Sheriff endpoint must be defined in shrine.conf")
//NB: Fail fast, HMS nodes need to use The Sheriff
require(sheriffCredentialsOption.isDefined, "Sheriff credentials must be defined in shrine.conf")
val sheriffUrl = sheriffEndpointOption.get.url.toString
val sheriffUsername = sheriffCredentialsOption.get.username
val sheriffPassword = sheriffCredentialsOption.get.password
val sheriffClient = JerseySheriffClient(sheriffUrl, sheriffUsername, sheriffPassword)
HmsDataStewardAuthorizationService(sheriffClient, authenticator)
}
}
\ No newline at end of file
diff --git a/apps/shrine-app/src/main/scala/net/shrine/wiring/ManuallyWiredShrineJaxrsResources.scala b/apps/shrine-app/src/main/scala/net/shrine/wiring/ManuallyWiredShrineJaxrsResources.scala
index 02892b63f..4c6dc79aa 100644
--- a/apps/shrine-app/src/main/scala/net/shrine/wiring/ManuallyWiredShrineJaxrsResources.scala
+++ b/apps/shrine-app/src/main/scala/net/shrine/wiring/ManuallyWiredShrineJaxrsResources.scala
@@ -1,345 +1,345 @@
package net.shrine.wiring
import javax.sql.DataSource
import com.typesafe.config.{Config, ConfigFactory}
import net.shrine.adapter.{Adapter, AdapterMap, DeleteQueryAdapter, FlagQueryAdapter, ReadInstanceResultsAdapter, ReadPreviousQueriesAdapter, ReadQueryDefinitionAdapter, ReadQueryResultAdapter, ReadTranslatedQueryDefinitionAdapter, RenameQueryAdapter, RunQueryAdapter, UnFlagQueryAdapter}
import net.shrine.adapter.dao.{AdapterDao, I2b2AdminDao}
import net.shrine.adapter.dao.squeryl.{SquerylAdapterDao, SquerylI2b2AdminDao}
import net.shrine.adapter.dao.squeryl.tables.{Tables => AdapterTables}
import net.shrine.adapter.service.{AdapterRequestHandler, AdapterResource, AdapterService, I2b2AdminResource, I2b2AdminService}
import net.shrine.adapter.translators.{ExpressionTranslator, QueryDefinitionTranslator}
import net.shrine.authentication.Authenticator
import net.shrine.authorization.QueryAuthorizationService
import net.shrine.broadcaster.{AdapterClientBroadcaster, BroadcastAndAggregationService, BroadcasterClient, InJvmBroadcasterClient, NodeHandle, PosterBroadcasterClient, SigningBroadcastAndAggregationService}
import net.shrine.broadcaster.dao.HubDao
import net.shrine.broadcaster.dao.squeryl.SquerylHubDao
import net.shrine.broadcaster.service.{BroadcasterMultiplexerResource, BroadcasterMultiplexerService}
import net.shrine.client.{EndpointConfig, HttpClient, JerseyHttpClient, OntClient, Poster, PosterOntClient}
import net.shrine.config.mappings.{AdapterMappings, AdapterMappingsSource, ClasspathFormatDetectingAdapterMappingsSource}
import net.shrine.crypto.{DefaultSignerVerifier, KeyStoreCertCollection, 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, OntologyMetadata}
import net.shrine.protocol.{NodeId, RequestType, ResultOutputType}
-import net.shrine.service.{I2b2BroadcastResource, I2b2BroadcastService, ShrineResource, ShrineService}
-import net.shrine.service.dao.AuditDao
-import net.shrine.service.dao.squeryl.SquerylAuditDao
-import net.shrine.service.dao.squeryl.tables.{Tables => HubTables}
+import net.shrine.qep.{I2b2BroadcastResource, I2b2QepService, ShrineResource, QepService}
+import net.shrine.qep.dao.AuditDao
+import net.shrine.qep.dao.squeryl.SquerylAuditDao
+import net.shrine.qep.dao.squeryl.tables.{Tables => HubTables}
import net.shrine.status.StatusJaxrs
import org.squeryl.internals.DatabaseAdapter
/**
* @author clint
* @since Jan 14, 2014
*
* Application wiring for Shrine, in the base, non-HMS case. All vals are protecetd, so they may be accessed,
* in subclasses without ballooning this class's public API, and lazy, to work around init-order surprises when
* overriding vals declared inline. See
*
* https://stackoverflow.com/questions/15762650/scala-override-val-in-class-inheritance
*
* among other links mentioning val overrides, early initializers, etc. -Clint
*/
//todo this is Shrine. Rename it when things are clean and calm.
object ManuallyWiredShrineJaxrsResources extends ShrineJaxrsResources with Loggable {
import NodeHandleSource.makeNodeHandles
override def resources: Iterable[AnyRef] = {
Seq(happyResource,statusJaxrs) ++ shrineResource ++ i2b2BroadcastResource ++ adapterResource ++ i2b2AdminResource ++ broadcasterMultiplexerResource
}
//Load config from file on the classpath called "shrine.conf"
lazy val config: Config = ConfigFactory.load("shrine")
protected lazy val shrineConfig: ShrineConfig = ShrineConfig(config)
protected lazy val nodeId: NodeId = NodeId(shrineConfig.humanReadableNodeName)
//TODO: Don't assume keystore lives on the filesystem, could come from classpath, etc
protected lazy val shrineCertCollection: KeyStoreCertCollection = KeyStoreCertCollection.fromFile(shrineConfig.keystoreDescriptor)
protected lazy val keystoreTrustParam: TrustParam = TrustParam.SomeKeyStore(shrineCertCollection)
protected lazy val signerVerifier: DefaultSignerVerifier = new DefaultSignerVerifier(shrineCertCollection)
protected lazy val dataSource: DataSource = Jndi("java:comp/env/jdbc/shrineDB").get
protected lazy val squerylAdapter: DatabaseAdapter = SquerylDbAdapterSelecter.determineAdapter(shrineConfig.shrineDatabaseType)
protected lazy val squerylInitializer: SquerylInitializer = new DataSourceSquerylInitializer(dataSource, squerylAdapter)
private def makePoster = poster(shrineCertCollection) _
private lazy val pmEndpoint: EndpointConfig = shrineConfig.pmEndpoint
private lazy val ontEndpoint: EndpointConfig = shrineConfig.ontEndpoint
protected lazy val pmPoster: Poster = makePoster(pmEndpoint)
protected lazy val ontPoster: Poster = makePoster(ontEndpoint)
protected lazy val breakdownTypes: Set[ResultOutputType] = shrineConfig.breakdownResultOutputTypes
protected lazy val hubDao: HubDao = new SquerylHubDao(squerylInitializer, new net.shrine.broadcaster.dao.squeryl.tables.Tables)
//todo move as much of this block as possible to the adapter project
protected lazy val (adapterService, i2b2AdminService, adapterDao, adapterMappings) = adapterComponentsToTuple(shrineConfig.adapterConfig.map { adapterConfig =>
val crcEndpoint: EndpointConfig = adapterConfig.crcEndpoint
val crcPoster: Poster = makePoster(crcEndpoint)
val squerylAdapterTables: AdapterTables = new AdapterTables
val adapterDao: AdapterDao = new SquerylAdapterDao(squerylInitializer, squerylAdapterTables)(breakdownTypes)
//NB: Is i2b2HiveCredentials.projectId the right project id to use?
val i2b2AdminDao: I2b2AdminDao = new SquerylI2b2AdminDao(shrineConfig.crcHiveCredentials.projectId, squerylInitializer, squerylAdapterTables)
val adapterMappingsSource: AdapterMappingsSource = ClasspathFormatDetectingAdapterMappingsSource(adapterConfig.adapterMappingsFileName)
//NB: Fail fast
val adapterMappings: AdapterMappings = adapterMappingsSource.load.get
val expressionTranslator: ExpressionTranslator = ExpressionTranslator(adapterMappings)
val queryDefinitionTranslator: QueryDefinitionTranslator = new QueryDefinitionTranslator(expressionTranslator)
val doObfuscation = adapterConfig.setSizeObfuscation
val runQueryAdapter = new RunQueryAdapter(
crcPoster,
adapterDao,
shrineConfig.crcHiveCredentials,
queryDefinitionTranslator,
adapterConfig.adapterLockoutAttemptsThreshold,
doObfuscation,
adapterConfig.immediatelyRunIncomingQueries,
breakdownTypes,
collectAdapterAudit = adapterConfig.collectAdapterAudit
)
val readInstanceResultsAdapter: Adapter = new ReadInstanceResultsAdapter(
crcPoster,
shrineConfig.crcHiveCredentials,
adapterDao,
doObfuscation,
breakdownTypes,
collectAdapterAudit = adapterConfig.collectAdapterAudit
)
val readQueryResultAdapter: Adapter = new ReadQueryResultAdapter(
crcPoster,
shrineConfig.crcHiveCredentials,
adapterDao,
doObfuscation,
breakdownTypes,
collectAdapterAudit = adapterConfig.collectAdapterAudit
)
val readPreviousQueriesAdapter: Adapter = new ReadPreviousQueriesAdapter(adapterDao)
val deleteQueryAdapter: Adapter = new DeleteQueryAdapter(adapterDao)
val renameQueryAdapter: Adapter = new RenameQueryAdapter(adapterDao)
val readQueryDefinitionAdapter: Adapter = new ReadQueryDefinitionAdapter(adapterDao)
val readTranslatedQueryDefinitionAdapter: Adapter = new ReadTranslatedQueryDefinitionAdapter(nodeId, queryDefinitionTranslator)
val flagQueryAdapter: Adapter = new FlagQueryAdapter(adapterDao)
val unFlagQueryAdapter: Adapter = new UnFlagQueryAdapter(adapterDao)
val adapterMap = AdapterMap(Map(
RequestType.QueryDefinitionRequest -> runQueryAdapter,
RequestType.GetRequestXml -> readQueryDefinitionAdapter,
RequestType.UserRequest -> readPreviousQueriesAdapter,
RequestType.InstanceRequest -> readInstanceResultsAdapter,
RequestType.MasterDeleteRequest -> deleteQueryAdapter,
RequestType.MasterRenameRequest -> renameQueryAdapter,
RequestType.GetQueryResult -> readQueryResultAdapter,
RequestType.ReadTranslatedQueryDefinitionRequest -> readTranslatedQueryDefinitionAdapter,
RequestType.FlagQueryRequest -> flagQueryAdapter,
RequestType.UnFlagQueryRequest -> unFlagQueryAdapter))
AdapterComponents(
new AdapterService(nodeId, signerVerifier, adapterConfig.maxSignatureAge, adapterMap),
new I2b2AdminService(adapterDao, i2b2AdminDao, pmPoster, runQueryAdapter),
adapterDao,
adapterMappings)
})
private lazy val localAdapterServiceOption: Option[AdapterRequestHandler] = shrineConfig.hubConfig.flatMap(hubConfig => makeAdapterServiceOption(hubConfig.shouldQuerySelf, adapterService))
private lazy val broadcastDestinations: Option[Set[NodeHandle]] = shrineConfig.hubConfig.map(hubConfig => makeNodeHandles(keystoreTrustParam, hubConfig.maxQueryWaitTime, hubConfig.downstreamNodes, nodeId, localAdapterServiceOption, breakdownTypes))
protected lazy val (shrineService, i2b2Service, auditDao) = queryEntryPointComponentsToTuple(shrineConfig.queryEntryPointConfig.map { queryEntryPointConfig =>
val broadcasterClient: BroadcasterClient = {
if(queryEntryPointConfig.broadcasterIsLocal) {
//If broadcaster is local, we need a hub config
//TODO: Enforce this when unmarshalling configs
require(broadcastDestinations.isDefined, "Local broadcaster specified, but no downstream nodes defined")
val broadcaster: AdapterClientBroadcaster = AdapterClientBroadcaster(broadcastDestinations.get, hubDao)
InJvmBroadcasterClient(broadcaster)
} else {
//if broadcaster is remote, we need an endpoint
//TODO: Enforce this when unmarshalling configs
require(queryEntryPointConfig.broadcasterServiceEndpoint.isDefined, "Non-local broadcaster requested, but no URL for the remote broadcaster is specified")
PosterBroadcasterClient(makePoster(queryEntryPointConfig.broadcasterServiceEndpoint.get), breakdownTypes)
}
}
val commonName:String = shrineCertCollection.myCommonName.getOrElse{
val hostname = java.net.InetAddress.getLocalHost.getHostName
warn(s"No common name available from ${shrineCertCollection.descriptor}. Using $hostname instead.")
hostname
}
val broadcastService: BroadcastAndAggregationService = SigningBroadcastAndAggregationService(broadcasterClient, signerVerifier, queryEntryPointConfig.signingCertStrategy)
val auditDao: AuditDao = new SquerylAuditDao(squerylInitializer, new HubTables)
val authenticationType = queryEntryPointConfig.authenticationType
val authorizationType = queryEntryPointConfig.authorizationType
val authenticator: Authenticator = AuthStrategy.determineAuthenticator(authenticationType, pmPoster)
val authorizationService: QueryAuthorizationService = AuthStrategy.determineQueryAuthorizationService(authorizationType, shrineConfig, authenticator)
debug(s"authorizationService set to $authorizationService")
QueryEntryPointComponents(
- ShrineService(
+ QepService(
commonName,
auditDao,
authenticator,
authorizationService,
queryEntryPointConfig.includeAggregateResults,
broadcastService,
queryEntryPointConfig.maxQueryWaitTime,
breakdownTypes,
queryEntryPointConfig.collectQepAudit
),
- I2b2BroadcastService(
+ I2b2QepService(
commonName,
auditDao,
authenticator,
authorizationService,
queryEntryPointConfig.includeAggregateResults,
broadcastService,
queryEntryPointConfig.maxQueryWaitTime,
breakdownTypes,
queryEntryPointConfig.collectQepAudit
),
auditDao)
})
private lazy val broadcasterOption = unpackHubComponents {
for {
hubConfig <- shrineConfig.hubConfig
} yield {
require(broadcastDestinations.isDefined, "This node is configured to be a hub, but no downstream nodes are defined")
HubComponents(AdapterClientBroadcaster(broadcastDestinations.get, hubDao))
}
}
protected lazy val broadcasterMultiplexerService = {
for {
broadcaster <- broadcasterOption
hubConfig <- shrineConfig.hubConfig
} yield {
BroadcasterMultiplexerService(broadcaster, hubConfig.maxQueryWaitTime)
}
}
protected lazy val pmUrlString: String = shrineConfig.pmEndpoint.url.toString
protected lazy val ontologyMetadata: OntologyMetadata = {
import scala.concurrent.duration._
//TODO: XXX: Un-hard-code max wait time param
val ontClient: OntClient = new PosterOntClient(shrineConfig.ontHiveCredentials, 1.minute, ontPoster)
new OntClientOntologyMetadata(ontClient)
}
//TODO: Don't assume we're an adapter with an AdapterMappings (don't call .get)
protected lazy val happyService: HappyShrineService = {
new HappyShrineService(
shrineConfig,
shrineCertCollection,
signerVerifier,
pmPoster,
ontologyMetadata,
adapterMappings,
auditDao,
adapterDao,
broadcasterOption,
adapterService)
}
protected lazy val happyResource: HappyShrineResource = new HappyShrineResource(happyService)
protected lazy val statusJaxrs: StatusJaxrs = StatusJaxrs(config)
protected lazy val shrineResource: Option[ShrineResource] = shrineService.map(ShrineResource(_))
protected lazy val i2b2BroadcastResource: Option[I2b2BroadcastResource] = i2b2Service.map(new I2b2BroadcastResource(_, breakdownTypes))
protected lazy val adapterResource: Option[AdapterResource] = adapterService.map(AdapterResource(_))
protected lazy val i2b2AdminResource: Option[I2b2AdminResource] = i2b2AdminService.map(I2b2AdminResource(_, breakdownTypes))
protected lazy val broadcasterMultiplexerResource: Option[BroadcasterMultiplexerResource] = broadcasterMultiplexerService.map(BroadcasterMultiplexerResource(_))
def makeAdapterServiceOption(isQueryable: Boolean, adapterRequestHandler: Option[AdapterRequestHandler]): Option[AdapterRequestHandler] = {
if (isQueryable) {
require(adapterRequestHandler.isDefined, "Self-querying requested, but this node is not configured to be an adapter")
adapterRequestHandler
} else { None }
}
def makeHttpClient(keystoreCertCollection: KeyStoreCertCollection, endpoint: EndpointConfig): HttpClient = {
import TrustParam.{AcceptAllCerts, SomeKeyStore}
val trustParam = if (endpoint.acceptAllCerts) AcceptAllCerts else SomeKeyStore(keystoreCertCollection)
JerseyHttpClient(trustParam, endpoint.timeout)
}
private final case class AdapterComponents(adapterService: AdapterService, i2b2AdminService: I2b2AdminService, adapterDao: AdapterDao, adapterMappings: AdapterMappings)
- private final case class QueryEntryPointComponents(shrineService: ShrineService, i2b2Service: I2b2BroadcastService, auditDao: AuditDao)
+ private final case class QueryEntryPointComponents(shrineService: QepService, i2b2Service: I2b2QepService, auditDao: AuditDao)
private final case class HubComponents(broadcaster: AdapterClientBroadcaster)
//TODO: TEST
private def adapterComponentsToTuple(option: Option[AdapterComponents]): (Option[AdapterService], Option[I2b2AdminService], Option[AdapterDao], Option[AdapterMappings]) = option match {
case None => (None, None, None, None)
case Some(AdapterComponents(a, b, c, d)) => (Option(a), Option(b), Option(c), Option(d))
}
//TODO: TEST
- private def queryEntryPointComponentsToTuple(option: Option[QueryEntryPointComponents]): (Option[ShrineService], Option[I2b2BroadcastService], Option[AuditDao]) = option match {
+ private def queryEntryPointComponentsToTuple(option: Option[QueryEntryPointComponents]): (Option[QepService], Option[I2b2QepService], Option[AuditDao]) = option match {
case None => (None, None, None)
case Some(QueryEntryPointComponents(a, b, c)) => (Option(a), Option(b), Option(c))
}
//TODO: TEST
private def unpackHubComponents(option: Option[HubComponents]): Option[AdapterClientBroadcaster] = option.map(_.broadcaster)
def poster(keystoreCertCollection: KeyStoreCertCollection)(endpoint: EndpointConfig): Poster = {
val httpClient = makeHttpClient(keystoreCertCollection, endpoint)
Poster(endpoint.url.toString, httpClient)
}
}
diff --git a/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineConfig.scala b/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineConfig.scala
index cf5fa6f6a..4e34b18a6 100644
--- a/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineConfig.scala
+++ b/apps/shrine-app/src/main/scala/net/shrine/wiring/ShrineConfig.scala
@@ -1,63 +1,63 @@
package net.shrine.wiring
import com.typesafe.config.Config
import net.shrine.adapter.service.AdapterConfig
import net.shrine.broadcaster.HubConfig
import net.shrine.client.EndpointConfig
import net.shrine.config.{ConfigExtensions, Keys}
import net.shrine.crypto.{KeyStoreDescriptorParser, KeyStoreDescriptor}
import net.shrine.protocol.{ResultOutputTypes, HiveCredentials, ResultOutputType}
-import net.shrine.service.QepConfig
+import net.shrine.qep.QepConfig
/**
* @author clint
* @since Feb 6, 2013
*/
final case class ShrineConfig(
adapterConfig: Option[AdapterConfig],
hubConfig: Option[HubConfig],
queryEntryPointConfig: Option[QepConfig],
crcHiveCredentials: HiveCredentials,
ontHiveCredentials: HiveCredentials,
//TODO: Do we want seperate hive credentials for talking to the Ont cell?
//On the dev stack, the project id is the only thing that needs to change, but can we rely on that?
pmEndpoint: EndpointConfig,
ontEndpoint: EndpointConfig,
adapterStatusQuery: String,
humanReadableNodeName: String,
shrineDatabaseType: String,
keystoreDescriptor: KeyStoreDescriptor,
breakdownResultOutputTypes: Set[ResultOutputType]) {
//NB: Preparing for the possible case where we'd need distinct credentials for talking to the PM
def pmHiveCredentials: HiveCredentials = crcHiveCredentials
}
object ShrineConfig {
def apply(config: Config): ShrineConfig = {
val configForShrine = config.getConfig("shrine")
import Keys._
def getOptionConfiguredIf[T](key:String,constructor: Config => T):Option[T] = {
if(configForShrine.getBoolean(s"$key.create")) configForShrine.getOptionConfigured(key,constructor)
else None
}
ShrineConfig(
getOptionConfiguredIf(adapter, AdapterConfig(_)),
getOptionConfiguredIf(hub, HubConfig(_)),
getOptionConfiguredIf(queryEntryPoint, QepConfig(_)),
configForShrine.getConfigured(hiveCredentials,HiveCredentials(_,HiveCredentials.CRC)),
configForShrine.getConfigured(hiveCredentials,HiveCredentials(_,HiveCredentials.ONT)),
configForShrine.getConfigured(pmEndpoint,EndpointConfig(_)),
configForShrine.getConfigured(ontEndpoint,EndpointConfig(_)),
configForShrine.getString(networkStatusQuery),
configForShrine.getString(humanReadableNodeName),
configForShrine.getString(shrineDatabaseType),
configForShrine.getConfigured(keystore,KeyStoreDescriptorParser(_)),
configForShrine.getOptionConfigured(breakdownResultOutputTypes,ResultOutputTypes.fromConfig).getOrElse(Set.empty)
)
}
}
diff --git a/apps/shrine-app/src/test/scala/net/shrine/wiring/AuthStrategyTest.scala b/apps/shrine-app/src/test/scala/net/shrine/wiring/AuthStrategyTest.scala
index a9ca9a03f..b42456dcc 100644
--- a/apps/shrine-app/src/test/scala/net/shrine/wiring/AuthStrategyTest.scala
+++ b/apps/shrine-app/src/test/scala/net/shrine/wiring/AuthStrategyTest.scala
@@ -1,113 +1,113 @@
package net.shrine.wiring
import net.shrine.protocol.CredentialConfig
import net.shrine.util.ShouldMatchersForJUnit
import org.junit.Test
import net.shrine.client.{EndpointConfig, Poster}
import net.shrine.authentication.{AuthenticationType, PmAuthenticator, Authenticator}
import net.shrine.hms.authentication.EcommonsPmAuthenticator
-import net.shrine.service.{QepConfig, AllowsAllAuthenticator}
+import net.shrine.qep.{QepConfig, AllowsAllAuthenticator}
import net.shrine.authorization.{AuthorizationType, AllowsAllAuthorizationService}
import net.shrine.hms.authorization.HmsDataStewardAuthorizationService
import java.net.URL
import net.shrine.hms.authorization.JerseySheriffClient
import net.shrine.crypto.SigningCertStrategy
/**
* @author clint
* @since Jul 2, 2014
*/
final class AuthStrategyTest extends ShouldMatchersForJUnit {
private[this] val pmPoster = Poster("http://example.com", null)
@Test
def testDefaultDetermineAuthenticator(): Unit = {
import AuthenticationType._
intercept[Exception] {
AuthStrategy.determineAuthenticator(null, pmPoster)
}
{
val authenticator = AuthStrategy.determineAuthenticator(Pm, pmPoster).asInstanceOf[PmAuthenticator]
authenticator.pmPoster should be(pmPoster)
}
{
val authenticator = AuthStrategy.determineAuthenticator(Ecommons, pmPoster).asInstanceOf[EcommonsPmAuthenticator]
authenticator.pmPoster should be(pmPoster)
}
{
val authenticator = AuthStrategy.determineAuthenticator(NoAuthentication, pmPoster)
authenticator should be(AllowsAllAuthenticator)
}
}
@Test
def testDefaultDetermineAuthorizationService(): Unit = {
import AuthorizationType._
intercept[Exception] {
AuthStrategy.determineQueryAuthorizationService(null, null, null)
}
{
val authService = AuthStrategy.determineQueryAuthorizationService(NoAuthorization, null, null)
authService should be(AllowsAllAuthorizationService)
}
{
val authenticationType = AuthenticationType.Ecommons
val authorizationType = HmsSteward
import scala.concurrent.duration._
val sheriffUrl = "http://example.com/sheriff"
val sheriffCredentials = CredentialConfig(None, "u", "p")
val shrineConfig = ShrineConfig(
None, //adapter config
None, //hub config
Some(QepConfig(
authenticationType,
authorizationType,
Some(EndpointConfig(new URL(sheriffUrl), acceptAllCerts = false, 42.minutes)),
Some(sheriffCredentials),
None,
includeAggregateResults = false,
1.minute,
None,
SigningCertStrategy.Attach,
collectQepAudit = false)),
null, //hiveCredentials
null, //more hiveCredentials
null, //pmEndpoint
null, //ontEndpoint
null, //adapterStatusQuery
null, //humanReadableNodeName
null, //shrineDatabaseType
null, //keystoreDescriptor
Set.empty
) //breakdown types
val authenticator: Authenticator = AllowsAllAuthenticator
val authService = AuthStrategy.determineQueryAuthorizationService(HmsSteward, shrineConfig, authenticator).asInstanceOf[HmsDataStewardAuthorizationService]
authService.authenticator should be(authenticator)
//noinspection ScalaUnnecessaryParentheses
authService.sheriffClient should not be(null)
val jerseySheriffClient = authService.sheriffClient.asInstanceOf[JerseySheriffClient]
jerseySheriffClient.sheriffUrl should equal(sheriffUrl)
jerseySheriffClient.sheriffUsername should equal(sheriffCredentials.username)
jerseySheriffClient.sheriffPassword should equal(sheriffCredentials.password)
}
}
}
\ No newline at end of file
diff --git a/apps/steward-app/pom.xml b/apps/steward-app/pom.xml
index 0ae3bbe20..d5a0ee193 100644
--- a/apps/steward-app/pom.xml
+++ b/apps/steward-app/pom.xml
@@ -1,187 +1,192 @@
shrine-base
net.shrine
1.21.0-SNAPSHOT
../../pom.xml
4.0.0
steward-app
Steward App
jar
src/main/scala
src/test/scala
net.alchim31.maven
scala-maven-plugin
com.github.eirslett
frontend-maven-plugin
0.0.23
src/main/js
install node and npm
install-node-and-npm
v0.10.33
2.7.4
npm install
npm
generate-resources
install
bower install
bower
install
grunt default
grunt
--no-color
io.spray
spray-routing_2.11
${spray-version}
io.spray
spray-servlet_2.11
${spray-version}
io.spray
spray-util_2.11
${spray-version}
io.spray
spray-testkit_2.11
${spray-version}
test
com.typesafe.akka
akka-actor_2.11
${akka-version}
com.typesafe.akka
akka-slf4j_2.11
${akka-version}
com.typesafe.akka
akka-testkit_2.11
${akka-testkit-version}
test
org.json4s
json4s-native_2.11
${json4s-version}
com.typesafe.slick
slick_2.11
${slick-version}
org.suecarter
freeslick_2.11
3.0.3.1
org.slf4j
slf4j-log4j12
${slf4j-version}
com.h2database
h2
${h2-version}
test
net.shrine
shrine-protocol
${project.version}
+
+ net.shrine
+ shrine-data-commons
+ ${project.version}
+
net.shrine
shrine-client
${project.version}
net.shrine
shrine-crypto
${project.version}
test-jar
test
net.shrine
shrine-auth
${project.version}
mysql
mysql-connector-java
${mysql-version}
diff --git a/apps/steward-app/src/main/resources/reference.conf b/apps/steward-app/src/main/resources/reference.conf
index 142b7a4a4..66dd00369 100644
--- a/apps/steward-app/src/main/resources/reference.conf
+++ b/apps/steward-app/src/main/resources/reference.conf
@@ -1,79 +1,81 @@
shrine {
steward {
createTopicsMode = Pending //Can be Pending, Approved, or TopcisIgnoredJustLog
//Pending - new topics start in the Pending state; researchers must wait for the Steward to approve them
//Approved - new topics start in the Approved state; researchers can use them immediately
//TopicsIgnoredJustLog - all queries are logged and approved; researchers don't need to create topics
database {
dataSourceFrom = "JNDI" //Can be JNDI or testDataSource . Use testDataSource for tests, JNDI everywhere else
jndiDataSourceName = "java:comp/env/jdbc/stewardDB" //or leave out for tests
slickProfileClassName = "slick.driver.MySQLDriver$" // Can be
// slick.driver.H2Driver$
// slick.driver.MySQLDriver$
// slick.driver.PostgresDriver$
// slick.driver.SQLServerDriver$
// slick.driver.JdbcDriver$
// freeslick.OracleProfile$
// freeslick.MSSQLServerProfile$
//
// (Yes, with the $ on the end)
// For testing without JNDI
// testDataSource {
//typical test settings for unit tests
//driverClassName = "org.h2.Driver"
//url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" //H2 embedded in-memory for unit tests
//url = "jdbc:h2:~/stewardTest.h2" //H2 embedded on disk at ~/test
// }
createTablesOnStart = false //for testing with H2 in memory, when not running unit tests. Set to false normally
}
gruntWatch = false //false for production, true for mvn tomcat7:run . Allows the client javascript and html files to be loaded via gruntWatch .
}
pmEndpoint {
url = "http://changeme.com/i2b2/services/PMService/getServices" //"http://services.i2b2.org/i2b2/services/PMService/getServices"
acceptAllCerts = true
timeout {
seconds = 10
}
}
authenticate {
realm = "SHRINE Steward API"
usersource
{
type = "PmUserSource" //Must be ConfigUserSource (for isolated testing) or PmUserSource (for everything else)
domain = "set shrine.authenticate.usersource.domain to the PM authentication domain in steward.conf" //"i2b2demo"
}
}
// If the pmEndpoint acceptAllCerts = false then you need to supply a keystore
// keystore {
// file = "shrine.keystore"
// password = "chiptesting"
// privateKeyAlias = "test-cert"
// keyStoreType = "JKS"
// caCertAliases = [carra ca]
// }
}
//todo typesafe config precedence seems to do the right thing, but I haven't found the rules that say this reference.conf should override others
akka {
loglevel = INFO
-// log-config-on-start = on
+ // log-config-on-start = on
loggers = ["akka.event.slf4j.Slf4jLogger"]
-// logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
+ // logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
+ // Toggles whether the threads created by this ActorSystem should be daemons or not
+ daemonic = on
}
spray.servlet {
boot-class = "net.shrine.steward.Boot"
request-timeout = 30s
}
diff --git a/apps/steward-app/src/main/scala/net/shrine/steward/Boot.scala b/apps/steward-app/src/main/scala/net/shrine/steward/Boot.scala
index af7df405f..463aa0564 100644
--- a/apps/steward-app/src/main/scala/net/shrine/steward/Boot.scala
+++ b/apps/steward-app/src/main/scala/net/shrine/steward/Boot.scala
@@ -1,17 +1,17 @@
package net.shrine.steward
import akka.actor.{Props, ActorSystem}
import spray.servlet.WebBoot
// this class is instantiated by the servlet initializer
// it needs to have a default constructor and implement
// the spray.servlet.WebBoot trait
class Boot extends WebBoot {
// we need an ActorSystem to host our application in
- val system = ActorSystem("sprayServer")
+ val system = ActorSystem("sprayServer",StewardConfigSource.config)
// the service actor replies to incoming HttpRequests
val serviceActor = system.actorOf(Props[StewardServiceActor])
}
\ No newline at end of file
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 0b82b0895..980aba3c1 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,681 +1,633 @@
package net.shrine.steward.db
-import java.io.PrintWriter
-import java.sql.{DriverManager, Connection, SQLException}
+import java.sql.SQLException
import java.util.concurrent.atomic.AtomicInteger
-import java.util.logging.Logger
-import javax.naming.InitialContext
import javax.sql.DataSource
import com.typesafe.config.Config
-import net.shrine.authorization.steward.{TopicIdAndName, TopicsPerState, QueriesPerUser, OutboundUser, TopicStateName, StewardQueryId, InboundTopicRequest, InboundShrineQuery, QueryHistory, StewardsTopics, ResearchersTopics, OutboundShrineQuery, TopicState, OutboundTopic, UserName, Date, QueryContents, ExternalQueryId, TopicId, researcherRole, stewardRole}
+import net.shrine.authorization.steward.{Date, ExternalQueryId, InboundShrineQuery, InboundTopicRequest, OutboundShrineQuery, OutboundTopic, OutboundUser, QueriesPerUser, QueryContents, QueryHistory, 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.TestableDataSourceCreator
import net.shrine.steward.{CreateTopicsMode, StewardConfigSource}
import slick.dbio.Effect.Read
-
import slick.driver.JdbcProfile
-import scala.concurrent.{Future, Await}
-import scala.concurrent.duration.{Duration,DurationInt}
-import scala.language.postfixOps
-import scala.util.Try
import scala.concurrent.ExecutionContext.Implicits.global
-
-import scala.concurrent.blocking
+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 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 = StewardConfigSource.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 (StewardConfigSource.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) => {
+ 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")
- }
}
(state,topicIdAndName)
}
def selectQueryHistory(queryParameters: QueryParameters,topicParameter:Option[TopicId]):QueryHistory = {
val (count,shrineQueries,topics,userNamesToOutboundUsers) = dbRun(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))
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)
}
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" => {
+ 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
})
}
/**
* 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
}
//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 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 mostRecentTopicQuery: Query[TopicTable, TopicRecord, Seq] = for(
topic <- allTopicQuery if !allTopicQuery.filter(_.id === topic.id).filter(_.changeDate > topic.changeDate).exists
) yield topic
}
object StewardSchema {
val allConfig:Config = StewardConfigSource.config
val config:Config = allConfig.getConfig("shrine.steward.database")
val slickProfileClassName = config.getString("slickProfileClassName")
val slickProfile:JdbcProfile = StewardConfigSource.objectForName(slickProfileClassName)
val schema = StewardSchema(slickProfile)
}
object StewardDatabase {
- val dataSource:DataSource = {
-
- val dataSourceFrom = StewardSchema.config.getString("dataSourceFrom")
- if(dataSourceFrom == "JNDI") {
- val jndiDataSourceName = StewardSchema.config.getString("jndiDataSourceName")
- val initialContext:InitialContext = new InitialContext()
-
- initialContext.lookup(jndiDataSourceName).asInstanceOf[DataSource]
-
- }
- else if (dataSourceFrom == "testDataSource") {
-
- val testDataSourceConfig = StewardSchema.config.getConfig("testDataSource")
- val driverClassName = testDataSourceConfig.getString("driverClassName")
- val url = testDataSourceConfig.getString("url")
-
- //Creating an instance of the driver register it. (!) From a previous epoch, but it works.
- Class.forName(driverClassName).newInstance()
-
- object TestDataSource extends DataSource {
- override def getConnection: Connection = {
- DriverManager.getConnection(url)
- }
-
- override def getConnection(username: String, password: String): Connection = {
- DriverManager.getConnection(url, username, password)
- }
-
- //unused methods
- override def unwrap[T](iface: Class[T]): T = ???
- override def isWrapperFor(iface: Class[_]): Boolean = ???
- override def setLogWriter(out: PrintWriter): Unit = ???
- override def getLoginTimeout: Int = ???
- override def setLoginTimeout(seconds: Int): Unit = ???
- override def getParentLogger: Logger = ???
- override def getLogWriter: PrintWriter = ???
- }
-
- TestDataSource
- }
- else throw new IllegalArgumentException(s"shrine.steward.database.dataSourceFrom must be either JNDI or testDataSource, not $dataSourceFrom")
- }
+ val dataSource:DataSource = TestableDataSourceCreator.dataSource(StewardSchema.config)
val db = StewardDatabase(StewardSchema.schema,dataSource)
val createTablesOnStart = StewardSchema.config.getBoolean("createTablesOnStart")
if(createTablesOnStart) StewardDatabase.db.createTables()
}
//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
+ 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 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/apps/steward-war/src/main/resources/reference.conf b/apps/steward-war/src/main/resources/reference.conf
index da034e389..cd834d042 100644
--- a/apps/steward-war/src/main/resources/reference.conf
+++ b/apps/steward-war/src/main/resources/reference.conf
@@ -1,78 +1,81 @@
shrine {
steward {
createTopicsMode = Pending //Can be Pending, Approved, or TopcisIgnoredJustLog
//Pending - new topics start in the Pending state; researchers must wait for the Steward to approve them
//Approved - new topics start in the Approved state; researchers can use them immediately
//TopicsIgnoredJustLog - all queries are logged and approved; researchers don't need to create topics
database {
dataSourceFrom = "JNDI" //Can be JNDI or testDataSource . Use testDataSource for tests, JNDI everywhere else
jndiDataSourceName = "java:comp/env/jdbc/stewardDB" //or leave out for tests
slickProfileClassName = "slick.driver.MySQLDriver$" // Can be slick.driver.H2Driver$
// slick.driver.MySQLDriver$
// slick.driver.PostgresDriver$
// slick.driver.SQLServerDriver$
// slick.driver.JdbcDriver$
// com.typesafe.slick.driver.oracle.OracleDriver$
//
// (Yes, with the $ on the end)
// Slick 3.0 renamed these from scala.slick.driver
// For testing without JNDI
// testDataSource {
//typical test settings for unit tests
//driverClassName = "org.h2.Driver"
//url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" //H2 embedded in-memory for unit tests
//url = "jdbc:h2:~/stewardTest.h2" //H2 embedded on disk at ~/test
// }
createTablesOnStart = false //for testing with H2 in memory, when not running unit tests. Set to false normally
}
gruntWatch = false //false for production, true for mvn tomcat7:run . Allows the client javascript and html files to be loaded via gruntWatch .
}
pmEndpoint {
url = "http://changeme.com/i2b2/services/PMService/getServices" //"http://services.i2b2.org/i2b2/services/PMService/getServices"
acceptAllCerts = true
timeout {
seconds = 1
}
}
// If the pmEndpoint acceptAllCerts = false then you need to supply a keystore
// keystore {
// file = "shrine.keystore"
// password = "chiptesting"
// privateKeyAlias = "test-cert"
// keyStoreType = "JKS"
// caCertAliases = [carra ca]
// }
authenticate {
realm = "SHRINE Steward API"
usersource
{
type = "PmUserSource" //Must be ConfigUserSource (for isolated testing) or PmUserSource (for everything else)
domain = "set shrine.authenticate.usersource.domain to the PM authentication domain in steward.conf" //"i2b2demo"
}
}
}
//todo typesafe config precedence seems to do the right thing, but I haven't found the rules that say this reference.conf should override others
akka {
loglevel = INFO
-// log-config-on-start = on
+ // log-config-on-start = on
loggers = ["akka.event.slf4j.Slf4jLogger"]
-// logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
+ // logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
+ // Toggles whether the threads created by this ActorSystem should be daemons or not
+ daemonic = on
+
}
spray.servlet {
boot-class = "net.shrine.steward.Boot"
request-timeout = 30s
}
diff --git a/commons/auth/src/main/scala/net/shrine/authorization/StewardQueryAuthorizationService.scala b/commons/auth/src/main/scala/net/shrine/authorization/StewardQueryAuthorizationService.scala
index 86c0ce689..221b870ca 100644
--- a/commons/auth/src/main/scala/net/shrine/authorization/StewardQueryAuthorizationService.scala
+++ b/commons/auth/src/main/scala/net/shrine/authorization/StewardQueryAuthorizationService.scala
@@ -1,217 +1,218 @@
package net.shrine.authorization
import java.net.URL
import javax.net.ssl.{KeyManager, X509TrustManager, SSLContext}
import java.security.cert.X509Certificate
import akka.io.IO
+import com.typesafe.config.ConfigFactory
import net.shrine.authorization.AuthorizationResult.{NotAuthorized, Authorized}
import net.shrine.authorization.steward.{TopicIdAndName, ResearchersTopics, InboundShrineQuery}
import net.shrine.log.Loggable
import net.shrine.protocol.{ApprovedTopic, RunQueryRequest, ReadApprovedQueryTopicsResponse, ErrorResponse, ReadApprovedQueryTopicsRequest}
import org.json4s.native.JsonMethods.parse
import org.json4s.{DefaultFormats, Formats}
import akka.actor.ActorSystem
import akka.util.Timeout
import akka.pattern.ask
import spray.can.Http
import spray.can.Http.{HostConnectorInfo, HostConnectorSetup}
import spray.http.{HttpResponse, HttpRequest, BasicHttpCredentials}
import spray.http.StatusCodes.{Unauthorized, OK, UnavailableForLegalReasons}
import spray.httpx.TransformerPipelineSupport.WithTransformation
import spray.httpx.Json4sSupport
import spray.client.pipelining.{addCredentials,sendReceive,Post,Get}
import spray.io.{SSLContextProvider, PipelineContext, ClientSSLEngineProvider}
import scala.concurrent.duration.{Duration, FiniteDuration, DurationInt}
import scala.concurrent.{Await, Future}
import scala.language.postfixOps
/**
* A QueryAuthorizationService that talks to the standard data steward application to learn about topics (intents) and check that a
* shrine query can be run
*
* @author david
* @since 4/2/15
*/
final case class StewardQueryAuthorizationService(qepUserName:String,
qepPassword:String,
stewardBaseUrl:URL,
defaultTimeout:FiniteDuration = 10 seconds) extends QueryAuthorizationService with Loggable with Json4sSupport {
import system.dispatcher // execution context for futures
- implicit val system = ActorSystem()
+ implicit val system = ActorSystem("AuthorizationService",ConfigFactory.load("shrine")) //todo use shrine's config
implicit val timeout:Timeout = Timeout.durationToTimeout(defaultTimeout)//10 seconds
implicit def json4sFormats: Formats = DefaultFormats
val qepCredentials = BasicHttpCredentials(qepUserName,qepPassword)
def sendHttpRequest(httpRequest: HttpRequest):Future[HttpResponse] = {
// Place a special SSLContext in scope here to be used by HttpClient.
// It trusts all server certificates.
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 requestWithCredentials = httpRequest ~> addCredentials(qepCredentials)
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(requestWithCredentials)
_ <- hostConnector ask Http.CloseAll
} yield response
responseFuture
}
/* todo to recycle connections with http://spray.io/documentation/1.2.3/spray-client/ if needed
def sendHttpRequest(httpRequest: HttpRequest):Future[HttpResponse] = {
import akka.io.IO
import akka.pattern.ask
import spray.can.Http
val requestWithCredentials = httpRequest ~> addCredentials(qepCredentials)
//todo failures via onFailure callbacks
for{
sendR:SendReceive <- connectorSource
response:HttpResponse <- sendR(requestWithCredentials)
} yield response
}
val connectorSource: Future[SendReceive] = //Future[HttpRequest => Future[HttpResponse]]
for (
//keep asking for a connector until you get one
//todo correct URL
// Http.HostConnectorInfo(connector, _) <- IO(Http) ? Http.HostConnectorSetup("www.spray.io", port = 8080)
Http.HostConnectorInfo(connector, _) <- IO(Http) ? Http.HostConnectorSetup("localhost", port = 6060)
) yield sendReceive(connector)
*/
def sendAndReceive(httpRequest: HttpRequest,timeout:Duration = defaultTimeout):HttpResponse = {
info("StewardQueryAuthorizationService will request "+httpRequest.uri) //todo someday log request and response
val responseFuture = sendHttpRequest(httpRequest)
val response:HttpResponse = Await.result(responseFuture,timeout)
info("StewardQueryAuthorizationService received response with status "+response.status)
response
}
//Contact a data steward and either return an Authorized or a NotAuthorized or throw an exception
override def authorizeRunQueryRequest(runQueryRequest: RunQueryRequest): AuthorizationResult = {
debug(s"authorizeRunQueryRequest started for ${runQueryRequest.queryDefinition.name}")
val interpreted = runQueryRequest.topicId.fold(
authorizeRunQueryRequestNoTopic(runQueryRequest)
)(
authorizeRunQueryRequestForTopic(runQueryRequest,_)
)
debug(s"authorizeRunQueryRequest completed with $interpreted) for ${runQueryRequest.queryDefinition.name}")
interpreted
}
def authorizeRunQueryRequestNoTopic(runQueryRequest: RunQueryRequest): AuthorizationResult = {
val userName = runQueryRequest.authn.username
val queryId = runQueryRequest.queryDefinition.name
//xml's .text returns something that looks like xquery with backwards slashes. toString() returns xml.
val queryForJson = InboundShrineQuery(runQueryRequest.networkQueryId,queryId,runQueryRequest.queryDefinition.toXml.toString())
val request = Post(s"$stewardBaseUrl/steward/qep/requestQueryAccess/user/$userName", queryForJson)
val response:HttpResponse = sendAndReceive(request,runQueryRequest.waitTime)
interpretAuthorizeRunQueryResponse(response)
}
def authorizeRunQueryRequestForTopic(runQueryRequest: RunQueryRequest,topicIdString:String): AuthorizationResult = {
val userName = runQueryRequest.authn.username
val queryId = runQueryRequest.queryDefinition.name
//xml's .text returns something that looks like xquery with backwards slashes. toString() returns xml.
val queryForJson = InboundShrineQuery(runQueryRequest.networkQueryId,queryId,runQueryRequest.queryDefinition.toXml.toString())
val request = Post(s"$stewardBaseUrl/steward/qep/requestQueryAccess/user/$userName/topic/$topicIdString", queryForJson)
val response:HttpResponse = sendAndReceive(request,runQueryRequest.waitTime)
debug(s"authorizeRunQueryRequestForTopic response is $response")
interpretAuthorizeRunQueryResponse(response)
}
/** Interpret the response from the steward app. Primarily here for testing. */
def interpretAuthorizeRunQueryResponse(response:HttpResponse):AuthorizationResult = {
response.status match {
case OK => {
val topicJson = new String(response.entity.data.toByteArray)
debug(s"topicJson is $topicJson")
val topic:Option[TopicIdAndName] = parse(topicJson).extractOpt[TopicIdAndName]
debug(s"topic is $topic")
Authorized(topic.map(x => (x.id,x.name)))
}
case UnavailableForLegalReasons => NotAuthorized(response.entity.asString)
case Unauthorized => throw new AuthorizationException(s"steward rejected qep's login credentials. $response")
case _ => throw new AuthorizationException(s"QueryAuthorizationService detected a problem: $response")
}
}
//Either read the approved topics from a data steward or have an error response.
override def readApprovedEntries(readTopicsRequest: ReadApprovedQueryTopicsRequest): Either[ErrorResponse, ReadApprovedQueryTopicsResponse] = {
val userName = readTopicsRequest.authn.username
val request = Get(s"$stewardBaseUrl/steward/qep/approvedTopics/user/$userName")
val response:HttpResponse = sendAndReceive(request,readTopicsRequest.waitTime)
if(response.status == OK) {
val topicsJson = new String(response.entity.data.toByteArray)
val topicsFromSteward: ResearchersTopics = parse(topicsJson).extract[ResearchersTopics]
val topics: Seq[ApprovedTopic] = topicsFromSteward.topics.map(topic => ApprovedTopic(topic.id, topic.name))
Right(ReadApprovedQueryTopicsResponse(topics))
}
else Left(ErrorResponse(s"Response status is ${response.status}, not OK. Response is "+response))
}
override def toString() = {
super.toString().replaceAll(qepPassword,"REDACTED")
}
}
diff --git a/commons/data-commons/src/main/scala/net/shrine/slick/TestableDataSourceCreator.scala b/commons/data-commons/src/main/scala/net/shrine/slick/TestableDataSourceCreator.scala
new file mode 100644
index 000000000..6984997fb
--- /dev/null
+++ b/commons/data-commons/src/main/scala/net/shrine/slick/TestableDataSourceCreator.scala
@@ -0,0 +1,60 @@
+package net.shrine.slick
+
+import java.io.PrintWriter
+import java.sql.{DriverManager, Connection}
+import java.util.logging.Logger
+import javax.naming.InitialContext
+import javax.sql.DataSource
+
+import com.typesafe.config.Config
+
+/**
+ * @author david
+ * @since 1/26/16
+ */
+object TestableDataSourceCreator {
+
+ def dataSource(config:Config):DataSource = {
+
+ val dataSourceFrom = config.getString("dataSourceFrom")
+ if(dataSourceFrom == "JNDI") {
+ val jndiDataSourceName = config.getString("jndiDataSourceName")
+ val initialContext:InitialContext = new InitialContext()
+
+ initialContext.lookup(jndiDataSourceName).asInstanceOf[DataSource]
+
+ }
+ else if (dataSourceFrom == "testDataSource") {
+
+ val testDataSourceConfig = config.getConfig("testDataSource")
+ val driverClassName = testDataSourceConfig.getString("driverClassName")
+ val url = testDataSourceConfig.getString("url")
+
+ //Creating an instance of the driver register it. (!) From a previous epoch, but it works.
+ Class.forName(driverClassName).newInstance()
+
+ object TestDataSource extends DataSource {
+ override def getConnection: Connection = {
+ DriverManager.getConnection(url)
+ }
+
+ override def getConnection(username: String, password: String): Connection = {
+ DriverManager.getConnection(url, username, password)
+ }
+
+ //unused methods
+ override def unwrap[T](iface: Class[T]): T = ???
+ override def isWrapperFor(iface: Class[_]): Boolean = ???
+ override def setLogWriter(out: PrintWriter): Unit = ???
+ override def getLoginTimeout: Int = ???
+ override def setLoginTimeout(seconds: Int): Unit = ???
+ override def getParentLogger: Logger = ???
+ override def getLogWriter: PrintWriter = ???
+ }
+
+ TestDataSource
+ }
+ else throw new IllegalArgumentException(s"dataSourceFrom config value must be either JNDI or testDataSource, not $dataSourceFrom")
+ }
+
+}
diff --git a/commons/data-commons/src/main/scala/net/shrine/slick/package.scala b/commons/data-commons/src/main/scala/net/shrine/slick/package.scala
new file mode 100644
index 000000000..301d22466
--- /dev/null
+++ b/commons/data-commons/src/main/scala/net/shrine/slick/package.scala
@@ -0,0 +1,11 @@
+package net.shrine
+
+/**
+ * Reused slick code.
+ *
+ * @author david
+ * @since 1/26/16
+ */
+package object slick {
+
+}
diff --git a/commons/protocol/src/main/scala/net/shrine/protocol/FlagQueryRequest.scala b/commons/protocol/src/main/scala/net/shrine/protocol/FlagQueryRequest.scala
index fcb1a8bf9..142f0755e 100644
--- a/commons/protocol/src/main/scala/net/shrine/protocol/FlagQueryRequest.scala
+++ b/commons/protocol/src/main/scala/net/shrine/protocol/FlagQueryRequest.scala
@@ -1,98 +1,104 @@
package net.shrine.protocol
+import net.shrine.log.Loggable
+
import scala.concurrent.duration.Duration
import scala.util.Try
import scala.xml.NodeSeq
import net.shrine.serialization.I2b2Unmarshaller
import net.shrine.util.XmlUtil
import net.shrine.util.NodeSeqEnrichments
import net.shrine.util.XmlUtil
import scala.xml.NodeBuffer
import net.shrine.serialization.I2b2Unmarshaller
import net.shrine.serialization.I2b2UnmarshallingHelpers
/**
* @author Clint Gilbert
* @date March, 2014
*
* NB: Now needs to be serializable as an i2b2 blob, because even though flagging is a Shrine-only feature,
* flagging will be initiated from the legacy i2b2 webclient for the forseeable future. In that context,
* generating i2b2 XML blobs and running them through the proxy is the path of least resistance. Sigh.
* - 17 June 2014
*/
final case class FlagQueryRequest(
override val projectId: String,
override val waitTime: Duration,
override val authn: AuthenticationInfo,
networkQueryId: Long,
message: Option[String]) extends ShrineRequest(projectId, waitTime, authn) with HandleableI2b2Request with HasHeaderFields {
override val requestType = RequestType.FlagQueryRequest
override def handleI2b2(handler: I2b2RequestHandler, shouldBroadcast: Boolean): ShrineResponse = {
handler.flagQuery(this, shouldBroadcast)
}
import FlagQueryRequest.rootTagName
override def toXml = XmlUtil.stripWhitespace {
XmlUtil.renameRootTag(rootTagName) {
{ headerFragment }
{ networkQueryId }
{ message.map(m => { m }).orNull }
}
}
protected override def i2b2MessageBody = XmlUtil.stripWhitespace {
{ headerFragment }
{
XmlUtil.renameRootTag(rootTagName) {
{ networkQueryId }
{ message.map(m => { m }).orNull }
}
}
}
}
-object FlagQueryRequest extends I2b2XmlUnmarshaller[FlagQueryRequest] with ShrineXmlUnmarshaller[FlagQueryRequest] with ShrineRequestUnmarshaller with I2b2UnmarshallingHelpers with HasRootTagName {
+object FlagQueryRequest extends I2b2XmlUnmarshaller[FlagQueryRequest] with ShrineXmlUnmarshaller[FlagQueryRequest] with ShrineRequestUnmarshaller with I2b2UnmarshallingHelpers with HasRootTagName with Loggable {
override val rootTagName = "flagQuery"
override def fromXml(breakdownTypes: Set[ResultOutputType])(xml: NodeSeq): Try[FlagQueryRequest] = {
import NodeSeqEnrichments.Strictness._
for {
tagName <- Try(xml.head.label)
if tagName == rootTagName
projectId <- shrineProjectId(xml)
waitTimeMs <- shrineWaitTime(xml)
authn <- shrineAuthenticationInfo(xml)
networkQueryId <- xml.withChild("networkQueryId").map(_.text.toLong)
message = (xml \ "message").headOption.map(_.text)
} yield {
+ info(s"FlagQueryRequest fromXML $message from $xml")
+
FlagQueryRequest(projectId, waitTimeMs, authn, networkQueryId, message)
}
}
override def fromI2b2(breakdownTypes: Set[ResultOutputType])(xml: NodeSeq): Try[FlagQueryRequest] = {
import NodeSeqEnrichments.Strictness._
for {
projectId <- i2b2ProjectId(xml)
waitTime <- i2b2WaitTime(xml)
authn <- i2b2AuthenticationInfo(xml)
networkQueryId <- (xml withChild "message_body" withChild "request" withChild rootTagName withChild "networkQueryId").map(_.text.toLong)
message = (xml \ "message_body" \ "request" \ rootTagName \ "message").headOption.map(_.text)
} yield {
+ info(s"FlagQueryRequest fromI2b2 $message from $xml")
+
FlagQueryRequest(projectId, waitTime, authn, networkQueryId, message)
}
}
}
\ No newline at end of file
diff --git a/commons/protocol/src/main/scala/net/shrine/protocol/QueryMaster.scala b/commons/protocol/src/main/scala/net/shrine/protocol/QueryMaster.scala
index 195a7e8ba..af93915dc 100644
--- a/commons/protocol/src/main/scala/net/shrine/protocol/QueryMaster.scala
+++ b/commons/protocol/src/main/scala/net/shrine/protocol/QueryMaster.scala
@@ -1,23 +1,24 @@
package net.shrine.protocol
import javax.xml.datatype.XMLGregorianCalendar
/**
* @author Bill Simons
- * @date 4/2/12
- * @link http://cbmi.med.harvard.edu
- * @link http://chip.org
+ * @since 4/2/12
+ * @see http://cbmi.med.harvard.edu
+ * @see http://chip.org
*
* NOTICE: This software comes with NO guarantees whatsoever and is
* licensed as Lgpl Open Source
- * @link http://www.gnu.org/licenses/lgpl.html
+ * @see http://www.gnu.org/licenses/lgpl.html
*/
final case class QueryMaster (
- queryMasterId: String, networkQueryId: Long,
+ queryMasterId: String, //Outside of tests, this is always the networkQueryId as a string
+ networkQueryId: Long,
name: String,
userId: String,
groupId: String,
createDate: XMLGregorianCalendar,
held: Option[Boolean] = None,
flagged: Option[Boolean] = None,
flagMessage: Option[String] = None)
\ No newline at end of file
diff --git a/commons/protocol/src/main/scala/net/shrine/protocol/ReadResultOutputTypesResponse.scala b/commons/protocol/src/main/scala/net/shrine/protocol/ReadResultOutputTypesResponse.scala
index 6374e0825..e39366a3e 100644
--- a/commons/protocol/src/main/scala/net/shrine/protocol/ReadResultOutputTypesResponse.scala
+++ b/commons/protocol/src/main/scala/net/shrine/protocol/ReadResultOutputTypesResponse.scala
@@ -1,37 +1,37 @@
package net.shrine.protocol
import scala.xml.NodeSeq
import net.shrine.util.XmlUtil
/**
* @author clint
- * @date Oct 6, 2014
+ * @since Oct 6, 2014
*
* NB: Takes a Seq of ResultOutputTypes, even though this should probably be a Set to disallow dupes, for
* better determinism when testing.
*/
final case class ReadResultOutputTypesResponse(outputTypes: Seq[ResultOutputType]) extends ShrineResponse {
override protected[protocol] def i2b2MessageBody: NodeSeq = XmlUtil.stripWhitespace {
DONE
{
outputTypes.zipWithIndex.map {
case (outputType, index) =>
outputType.withId(index + 1).toI2b2
}
}
}
//NB: Not used anywhere, but perhaps useful for logging
override def toXml: NodeSeq = XmlUtil.stripWhitespace {
{
outputTypes.map(ot => { ot })
}
}
}
diff --git a/hub/broadcaster-aggregator/src/main/scala/net/shrine/aggregation/ReadPreviousQueriesAggregator.scala b/hub/broadcaster-aggregator/src/main/scala/net/shrine/aggregation/ReadPreviousQueriesAggregator.scala
deleted file mode 100644
index 734bf9599..000000000
--- a/hub/broadcaster-aggregator/src/main/scala/net/shrine/aggregation/ReadPreviousQueriesAggregator.scala
+++ /dev/null
@@ -1,43 +0,0 @@
-package net.shrine.aggregation
-
-import net.shrine.aggregation.BasicAggregator.Valid
-import net.shrine.log.Loggable
-import net.shrine.protocol.{QueryMaster, ShrineResponse, ReadPreviousQueriesResponse}
-
-/**
- * @author Bill Simons
- * @author Clint Gilbert
- * @date 6/8/11
- * @link http://cbmi.med.harvard.edu
- * @link http://chip.org
- *
- * NOTICE: This software comes with NO guarantees whatsoever and is
- * licensed as Lgpl Open Source
- * @link http://www.gnu.org/licenses/lgpl.html
- */
-final class ReadPreviousQueriesAggregator extends IgnoresErrorsAggregator[ReadPreviousQueriesResponse] with Loggable {
-
- private[aggregation] def newestToOldest(x: QueryMaster, y: QueryMaster) = x.createDate.compare(y.createDate) > 0
-
- private[aggregation] def oldestToNewest(x: QueryMaster, y: QueryMaster) = x.createDate.compare(y.createDate) < 0
-
- override def makeResponseFrom(responses: Iterable[Valid[ReadPreviousQueriesResponse]]): ShrineResponse = {
- //debug(s"Raw previous query responses: $responses")
-
- val mastersGroupedById = responses.flatMap(_.response.queryMasters).groupBy(_.queryMasterId)
-
- val sortedMastersById = mastersGroupedById.map { case (id, mastersWithThatId) => (id, mastersWithThatId.toSeq.sortWith(oldestToNewest)) }.toMap
-
- val mostRecentMastersForEachId = sortedMastersById.flatMap { case (id, mastersWithThatId) => mastersWithThatId.headOption }.toSeq
-
- val sortedMasters = mostRecentMastersForEachId.sortWith(newestToOldest)
-
- val result = ReadPreviousQueriesResponse(sortedMasters)
-
- //debug("Previous queries: ")
-
- //sortedMasters.foreach(debug(_))
-
- result
- }
-}
\ No newline at end of file
diff --git a/hub/broadcaster-aggregator/src/main/resources/hub.sql b/hub/broadcaster-aggregator/src/main/sql/hub.sql
similarity index 100%
rename from hub/broadcaster-aggregator/src/main/resources/hub.sql
rename to hub/broadcaster-aggregator/src/main/sql/hub.sql
diff --git a/hub/broadcaster-aggregator/src/test/scala/net/shrine/aggregation/ReadPreviousQueriesAggregatorTest.scala b/hub/broadcaster-aggregator/src/test/scala/net/shrine/aggregation/ReadPreviousQueriesAggregatorTest.scala
deleted file mode 100644
index bfd7c5765..000000000
--- a/hub/broadcaster-aggregator/src/test/scala/net/shrine/aggregation/ReadPreviousQueriesAggregatorTest.scala
+++ /dev/null
@@ -1,98 +0,0 @@
-package net.shrine.aggregation
-
-import scala.concurrent.duration.DurationInt
-
-import org.junit.Test
-import net.shrine.util.ShouldMatchersForJUnit
-import org.scalatest.mock.EasyMockSugar
-
-import javax.xml.datatype.XMLGregorianCalendar
-import net.shrine.protocol.NodeId
-import net.shrine.protocol.QueryMaster
-import net.shrine.protocol.ReadPreviousQueriesResponse
-import net.shrine.protocol.Result
-import net.shrine.util.XmlDateHelper
-import net.shrine.util.XmlGcEnrichments
-
-/**
- * @author Bill Simons
- * @date 6/8/11
- * @link http://cbmi.med.harvard.edu
- * @link http://chip.org
- *
- * NOTICE: This software comes with NO guarantees whatsoever and is
- * licensed as Lgpl Open Source
- * @link http://www.gnu.org/licenses/lgpl.html
- */
-final class ReadPreviousQueriesAggregatorTest extends ShouldMatchersForJUnit with EasyMockSugar {
- private def newQueryMasterType(groupId: String, userId: String, masterId: String, networkId: Long, name: String, createDate: XMLGregorianCalendar): QueryMaster = {
- QueryMaster(masterId, networkId, name, userId, groupId, createDate)
- }
-
- import scala.concurrent.duration._
- import XmlGcEnrichments._
- import org.easymock.EasyMock.{expect => invoke}
-
- @Test
- def testOldestToNewest {
- val groupId = "groupId"
- val userId = "userId"
- val aggregator = new ReadPreviousQueriesAggregator
- val date = XmlDateHelper.now
-
- val older = newQueryMasterType(groupId, userId, "1", 2L, "name1", date)
- val newer = newQueryMasterType(groupId, userId, "1", 2L, "name1", date + 1.day)
-
- aggregator.oldestToNewest(newer, older) should be(false)
- aggregator.oldestToNewest(newer, newer) should be(false)
- aggregator.oldestToNewest(older, newer) should be(true)
- }
-
- @Test
- def testNewestToOldest {
- val groupId = "groupId"
- val userId = "userId"
- val aggregator = new ReadPreviousQueriesAggregator
- val date = XmlDateHelper.now
- val older = newQueryMasterType(groupId, userId, "1", 2L, "name1", date)
- val newer = newQueryMasterType(groupId, userId, "1", 2L, "name1", date + 1.day)
-
- aggregator.newestToOldest(newer, older) should be(true)
- aggregator.newestToOldest(newer, newer) should be(false)
- aggregator.newestToOldest(older, newer) should be(false)
- }
-
- @Test
- def testAggregate {
- val userId = "userId"
- val groupId = "groupId"
- val firstDate = XmlDateHelper.now
-
- val firstQm = newQueryMasterType(groupId, userId, "1", 2L, "name1", firstDate)
- val lastQma = newQueryMasterType(groupId, userId, "2", 2L, "name2", firstDate + 2.days)
- val lastQmb = newQueryMasterType(groupId, userId, "2", 2L, "name2", firstDate + 1.day)
- val middleQm = newQueryMasterType(groupId, userId, "3", 2L, "name3", firstDate + 1.hour)
-
- val masters1 = Seq(firstQm, lastQma)
- val masters2 = Seq(lastQmb, middleQm)
-
- val response1 = ReadPreviousQueriesResponse(masters1)
- val response2 = ReadPreviousQueriesResponse(masters2)
-
- val result1 = Result(NodeId("A"), 1.second, response1)
- val result2 = Result(NodeId("B"), 2.seconds, response2)
-
- val aggregator = new ReadPreviousQueriesAggregator
-
- //TODO: test handling error responses
- val actual = aggregator.aggregate(Seq(result1, result2), Nil).asInstanceOf[ReadPreviousQueriesResponse]
-
- actual.isInstanceOf[ReadPreviousQueriesResponse] should be(true)
-
- actual.queryMasters.size should equal(3)
- actual.queryMasters(0) should equal(lastQmb)
- actual.queryMasters(0).createDate should equal(lastQmb.createDate)
- actual.queryMasters(1) should equal(middleQm)
- actual.queryMasters(2) should equal(firstQm)
- }
-}
\ No newline at end of file
diff --git a/integration/pom.xml b/integration/pom.xml
index db067da03..0ab17f31c 100644
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -1,119 +1,119 @@
4.0.0
SHRINE Integration Tests
shrine-integration-tests
net.shrine
shrine-base
1.21.0-SNAPSHOT
src/main/scala
src/test/scala
net.alchim31.maven
scala-maven-plugin
net.shrine
shrine-crypto
${project.version}
net.shrine
shrine-crypto
${project.version}
test-jar
test
net.shrine
shrine-test-commons
${project.version}
test-jar
test
net.shrine
shrine-data-commons
${project.version}
test-jar
test
net.shrine
shrine-protocol
${project.version}
test
net.shrine
shrine-adapter-service
${project.version}
test
net.shrine
shrine-adapter-service
${project.version}
test-jar
test
net.shrine
shrine-adapter-client-api
${project.version}
test
net.shrine
shrine-broadcaster-aggregator
${project.version}
test
net.shrine
- shrine-service
+ shrine-qep
${project.version}
test
net.shrine
shrine-broadcaster-service
${project.version}
test
com.h2database
h2
test
org.slf4j
slf4j-log4j12
test
org.springframework
spring-jdbc
test
com.sun.jersey.jersey-test-framework
jersey-test-framework-http
${jersey-version}
test
com.sun.jersey.contribs
jersey-simple-server
${jersey-version}
test
diff --git a/integration/src/test/resources/shrine.conf b/integration/src/test/resources/shrine.conf
new file mode 100644
index 000000000..d1f91a408
--- /dev/null
+++ b/integration/src/test/resources/shrine.conf
@@ -0,0 +1,132 @@
+shrine {
+ pmEndpoint {
+ url = "http://services.i2b2.org/i2b2/rest/PMService/getServices"
+ acceptAllCerts = true
+ timeout {
+ seconds = 1
+ }
+ }
+
+ ontEndpoint {
+ url = "http://example.com:9090/i2b2/rest/OntologyService/"
+ acceptAllCerts = true
+ timeout {
+ seconds = 1
+ }
+ }
+
+ hiveCredentials {
+ domain = "HarvardDemo"
+ username = "demo"
+ password = "demouser"
+ crcProjectId = "Demo"
+ ontProjectId = "SHRINE"
+ }
+
+ queryEntryPoint {
+ attachSigningCert = true
+
+ authenticationType = "ecommons"
+
+ authorizationType = "hms-steward" //can be none, shrine-steward, or hms-steward
+
+ //hms-steward config
+ sheriffEndpoint {
+ url = "http://localhost:8080/shrine-hms-authorization/queryAuthorization"
+ acceptAllCerts = true
+ timeout {
+ seconds = 1
+ }
+ }
+
+ sheriffCredentials {
+ username = "sheriffUsername"
+ password = "sheriffPassword"
+ }
+
+ //shrine-steward config
+// shrineSteward {
+// qepUserName = "qep"
+// qepPassword = "trustme"
+// stewardBaseUrl = "https://localhost:6443
+// }
+
+ includeAggregateResults = false
+
+ maxQueryWaitTime {
+ minutes = 5
+ }
+
+ broadcasterServiceEndpoint {
+ url = "http://example.com/shrine/rest/broadcaster/broadcast"
+ acceptAllCerts = true
+ timeout {
+ seconds = 1
+ }
+ }
+ audit {
+ 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
+ }
+ }
+ }
+ }
+
+ hub {
+ create = true
+ maxQueryWaitTime {
+ minutes = 4.5
+ }
+
+ downstreamNodes {
+ "some hospital" = "http://example.com/foo"
+ CHB = "http://example.com/chb"
+ PHS = "http://example.com/phs"
+ }
+
+ shouldQuerySelf = true
+ }
+
+ adapter {
+ crcEndpoint {
+ url = "http://services.i2b2.org/i2b2/rest/QueryToolService/"
+ acceptAllCerts = true
+ timeout {
+ seconds = 1
+ }
+ }
+
+ setSizeObfuscation = true
+
+ adapterLockoutAttemptsThreshold = 10
+
+ adapterMappingsFileName = "AdapterMappings.xml"
+
+ maxSignatureAge {
+ minutes = 5
+ }
+
+ immediatelyRunIncomingQueries = false
+ }
+
+ networkStatusQuery = "\\\\SHRINE\\SHRINE\\Diagnoses\\Mental Illness\\Disorders usually diagnosed in infancy, childhood, or adolescence\\Pervasive developmental disorders\\Infantile autism, current or active state\\"
+
+ humanReadableNodeName = "SHRINE Cell"
+
+ shrineDatabaseType = "mysql"
+
+ keystore {
+ file = "shrine.keystore"
+ password = "chiptesting"
+ privateKeyAlias = "test-cert"
+ keyStoreType = "PKCS12"
+ caCertAliases = [carra ca]
+ }
+}
diff --git a/integration/src/test/scala/net/shrine/integration/AbstractHubAndSpokesTest.scala b/integration/src/test/scala/net/shrine/integration/AbstractHubAndSpokesTest.scala
index 37047130b..5665432aa 100644
--- a/integration/src/test/scala/net/shrine/integration/AbstractHubAndSpokesTest.scala
+++ b/integration/src/test/scala/net/shrine/integration/AbstractHubAndSpokesTest.scala
@@ -1,75 +1,57 @@
package net.shrine.integration
+import net.shrine.adapter.service.{AdapterRequestHandler, AdapterResource, JerseyTestComponent}
+import net.shrine.client.{JerseyHttpClient, Poster}
+import net.shrine.crypto.{DefaultSignerVerifier, TestKeystore, TrustParam}
+import net.shrine.protocol.{AuthenticationInfo, CertId, Credential, NodeId}
+import org.junit.{After, Before}
+
import scala.concurrent.duration.DurationInt
-import net.shrine.adapter.client.RemoteAdapterClient
-import net.shrine.adapter.service.AdapterRequestHandler
-import net.shrine.adapter.service.AdapterResource
-import net.shrine.adapter.service.JerseyTestComponent
-import net.shrine.broadcaster.AdapterClientBroadcaster
-import net.shrine.broadcaster.InJvmBroadcasterClient
-import net.shrine.broadcaster.NodeHandle
-import net.shrine.client.JerseyHttpClient
-import net.shrine.client.Poster
-import net.shrine.crypto.DefaultSignerVerifier
-import net.shrine.crypto.TestKeystore
-import net.shrine.crypto.TrustParam
-import net.shrine.protocol.AuthenticationInfo
-import net.shrine.protocol.CertId
-import net.shrine.protocol.Credential
-import net.shrine.protocol.NodeId
-import net.shrine.protocol.ShrineRequestHandler
-import net.shrine.service.ShrineResource
-import net.shrine.service.ShrineService
-import org.junit.Before
-import org.junit.After
-import net.shrine.protocol.DefaultBreakdownResultOutputTypes
/**
* @author clint
* @date Mar 6, 2014
*/
trait AbstractHubAndSpokesTest {
@Before
def setUp(): Unit = {
spokes.foreach(_.JerseyTest.setUp())
}
@After
def tearDown(): Unit = {
spokes.foreach(_.JerseyTest.tearDown())
}
def posterFor(component: JerseyTestComponent[_]): Poster = Poster(component.resourceUrl, JerseyHttpClient(TrustParam.AcceptAllCerts, 30.minutes))
- import scala.concurrent.duration._
-
val networkAuthn = AuthenticationInfo("d", "u", Credential("p", false))
val certCollection = TestKeystore.certCollection
lazy val myCertId: CertId = certCollection.myCertId.get
lazy val signerVerifier = new DefaultSignerVerifier(certCollection)
import AbstractHubAndSpokesTest.SpokeComponent
lazy val Spoke0Component = SpokeComponent(9998, NodeId("Spoke 0"))
lazy val Spoke1Component = SpokeComponent(9999, NodeId("Spoke 1"))
lazy val spokes: Set[SpokeComponent] = Set(Spoke0Component, Spoke1Component)
}
object AbstractHubAndSpokesTest {
final case class SpokeComponent(override val port: Int, nodeId: NodeId) extends JerseyTestComponent[AdapterRequestHandler] {
override val basePath = "adapter"
def mockHandler = handler.asInstanceOf[MockAdapterRequestHandler]
override lazy val makeHandler: AdapterRequestHandler = new MockAdapterRequestHandler(nodeId)
override def resourceClass(handler: AdapterRequestHandler) = AdapterResource(handler)
}
}
\ No newline at end of file
diff --git a/integration/src/test/scala/net/shrine/integration/AbstractHubComponent.scala b/integration/src/test/scala/net/shrine/integration/AbstractHubComponent.scala
index da1a25d50..f6c112a6f 100644
--- a/integration/src/test/scala/net/shrine/integration/AbstractHubComponent.scala
+++ b/integration/src/test/scala/net/shrine/integration/AbstractHubComponent.scala
@@ -1,44 +1,38 @@
package net.shrine.integration
-import net.shrine.adapter.service.JerseyTestComponent
-import net.shrine.protocol.{NodeId, ShrineRequestHandler, AuthenticationInfo, DefaultBreakdownResultOutputTypes}
-import net.shrine.broadcaster.NodeHandle
import net.shrine.adapter.client.RemoteAdapterClient
-import net.shrine.broadcaster.AdapterClientBroadcaster
-import net.shrine.service.ShrineService
-import net.shrine.broadcaster.SigningBroadcastAndAggregationService
-import net.shrine.broadcaster.InJvmBroadcasterClient
+import net.shrine.adapter.service.JerseyTestComponent
+import net.shrine.broadcaster.{AdapterClientBroadcaster, BroadcasterClient, InJvmBroadcasterClient, NodeHandle, PosterBroadcasterClient, SigningBroadcastAndAggregationService}
import net.shrine.client.ShrineClient
-import net.shrine.broadcaster.BroadcasterClient
-import net.shrine.broadcaster.PosterBroadcasterClient
import net.shrine.crypto.SigningCertStrategy
+import net.shrine.protocol.{AuthenticationInfo, DefaultBreakdownResultOutputTypes, NodeId}
/**
* @author clint
* @since Jun 23, 2014
*/
abstract class AbstractHubComponent[H <: AnyRef](
enclosingTest: AbstractHubAndSpokesTest,
override val basePath: String,
override val port: Int) extends JerseyTestComponent[H] {
lazy val broadcaster: InspectableDelegatingBroadcaster = {
- import enclosingTest.{ spokes, posterFor }
+ import enclosingTest.{posterFor, spokes}
val destinations: Set[NodeHandle] = spokes.map { spoke =>
val client = RemoteAdapterClient(NodeId.Unknown,posterFor(spoke), DefaultBreakdownResultOutputTypes.toSet)
NodeHandle(spoke.nodeId, client)
}
InspectableDelegatingBroadcaster(AdapterClientBroadcaster(destinations, MockHubDao))
}
def clientFor(projectId: String, networkAuthn: AuthenticationInfo): ShrineClient
protected def inJvmBroadcasterClient: BroadcasterClient = InJvmBroadcasterClient(broadcaster)
protected def posterBroadcasterClient(hubComponent: JerseyTestComponent[_]): BroadcasterClient = PosterBroadcasterClient(enclosingTest.posterFor(hubComponent), DefaultBreakdownResultOutputTypes.toSet)
protected def signingBroadcastService(broadcasterClient: BroadcasterClient): SigningBroadcastAndAggregationService = SigningBroadcastAndAggregationService(broadcasterClient, enclosingTest.signerVerifier, SigningCertStrategy.Attach)
}
\ No newline at end of file
diff --git a/integration/src/test/scala/net/shrine/integration/Hubs.scala b/integration/src/test/scala/net/shrine/integration/Hubs.scala
index 841ab38eb..a8a789b97 100644
--- a/integration/src/test/scala/net/shrine/integration/Hubs.scala
+++ b/integration/src/test/scala/net/shrine/integration/Hubs.scala
@@ -1,77 +1,77 @@
package net.shrine.integration
-import net.shrine.service.ShrineResource
+import net.shrine.qep.ShrineResource
import net.shrine.protocol.ShrineRequestHandler
-import net.shrine.service.I2b2BroadcastResource
+import net.shrine.qep.I2b2BroadcastResource
import net.shrine.protocol.I2b2RequestHandler
-import net.shrine.service.ShrineService
-import net.shrine.service.I2b2BroadcastService
+import net.shrine.qep.QepService
+import net.shrine.qep.I2b2QepService
import net.shrine.client.ShrineClient
import net.shrine.client.JerseyShrineClient
import net.shrine.protocol.AuthenticationInfo
import net.shrine.crypto.TrustParam
import net.shrine.client.Poster
import net.shrine.client.JerseyHttpClient
import net.shrine.broadcaster.BroadcasterClient
import net.shrine.protocol.DefaultBreakdownResultOutputTypes
object Hubs {
val defaultShrineBasePath = ""
val defaultI2b2BasePath = "i2b2/request"
val defaultPort = 9997
final case class Shrine(
enclosingTest: AbstractHubAndSpokesTest,
broadcasterClient: Option[BroadcasterClient] = None,
override val basePath: String = defaultShrineBasePath,
override val port: Int = defaultPort) extends AbstractHubComponent[ShrineRequestHandler](enclosingTest, basePath, port) {
override def resourceClass(handler: ShrineRequestHandler) = ShrineResource(handler)
override lazy val makeHandler: ShrineRequestHandler = {
import scala.concurrent.duration._
- ShrineService(
+ QepService(
"example.com",
MockAuditDao,
MockAuthenticator,
MockQueryAuthorizationService,
includeAggregateResult = false,
signingBroadcastService(broadcasterClient.getOrElse(inJvmBroadcasterClient)),
1.hour,
Set.empty,
false)
}
override def clientFor(projectId: String, networkAuthn: AuthenticationInfo): ShrineClient = new JerseyShrineClient(resourceUrl, projectId, networkAuthn, DefaultBreakdownResultOutputTypes.toSet, TrustParam.AcceptAllCerts)
}
final case class I2b2(
enclosingTest: AbstractHubAndSpokesTest,
broadcasterClient: Option[BroadcasterClient] = None,
override val basePath: String = defaultI2b2BasePath,
override val port: Int = defaultPort) extends AbstractHubComponent[I2b2RequestHandler](enclosingTest, basePath, port) {
override def resourceClass(handler: I2b2RequestHandler) = I2b2BroadcastResource(handler, DefaultBreakdownResultOutputTypes.toSet)
import scala.concurrent.duration._
override lazy val makeHandler: I2b2RequestHandler = {
- I2b2BroadcastService(
+ I2b2QepService(
"example.com",
MockAuditDao,
MockAuthenticator,
MockQueryAuthorizationService,
includeAggregateResult = false,
signingBroadcastService(broadcasterClient.getOrElse(inJvmBroadcasterClient)),
1.hour,
Set.empty,
false
)
}
override def clientFor(projectId: String, networkAuthn: AuthenticationInfo): ShrineClient = {
I2b2ShrineClient(Poster(resourceUrl, JerseyHttpClient(TrustParam.AcceptAllCerts, 1.hour)), projectId, networkAuthn)
}
}
}
\ No newline at end of file
diff --git a/integration/src/test/scala/net/shrine/integration/MockAuditDao.scala b/integration/src/test/scala/net/shrine/integration/MockAuditDao.scala
index 6e858bd61..b0aa79269 100644
--- a/integration/src/test/scala/net/shrine/integration/MockAuditDao.scala
+++ b/integration/src/test/scala/net/shrine/integration/MockAuditDao.scala
@@ -1,23 +1,23 @@
package net.shrine.integration
-import net.shrine.service.dao.AuditDao
+import net.shrine.qep.dao.AuditDao
import java.util.Date
-import net.shrine.service.dao.model.AuditEntry
+import net.shrine.qep.dao.model.AuditEntry
/**
* @author clint
* @date Nov 27, 2013
*/
object MockAuditDao extends AuditDao {
override def addAuditEntry(
time: Date,
project: String,
domain: String,
username: String,
queryText: String,
queryTopic: Option[String]): Unit = ()
override def findRecentEntries(limit: Int): Seq[AuditEntry] = Nil
override def inTransaction[T](f: => T): T = f
}
\ No newline at end of file
diff --git a/integration/src/test/scala/net/shrine/integration/NetworkSimulationTest.scala b/integration/src/test/scala/net/shrine/integration/NetworkSimulationTest.scala
index 7aeb91b3d..9e066d675 100644
--- a/integration/src/test/scala/net/shrine/integration/NetworkSimulationTest.scala
+++ b/integration/src/test/scala/net/shrine/integration/NetworkSimulationTest.scala
@@ -1,337 +1,337 @@
package net.shrine.integration
import net.shrine.log.Loggable
import scala.concurrent.Future
import scala.concurrent.duration.DurationInt
import org.junit.Test
import net.shrine.util.ShouldMatchersForJUnit
import net.shrine.adapter.AdapterMap
import net.shrine.adapter.DeleteQueryAdapter
import net.shrine.adapter.client.AdapterClient
import net.shrine.adapter.dao.squeryl.AbstractSquerylAdapterTest
import net.shrine.adapter.service.AdapterRequestHandler
import net.shrine.adapter.service.AdapterService
import net.shrine.broadcaster.AdapterClientBroadcaster
import net.shrine.broadcaster.NodeHandle
import net.shrine.crypto.DefaultSignerVerifier
import net.shrine.crypto.TestKeystore
import net.shrine.protocol.{HiveCredentials, AuthenticationInfo, BroadcastMessage, Credential, DeleteQueryRequest, DeleteQueryResponse, NodeId, Result, RunQueryRequest, CertId, RequestType, FlagQueryRequest, FlagQueryResponse, RawCrcRunQueryResponse, ResultOutputType, QueryResult, RunQueryResponse, AggregatedRunQueryResponse, UnFlagQueryRequest, UnFlagQueryResponse, DefaultBreakdownResultOutputTypes}
-import net.shrine.service.ShrineService
+import net.shrine.qep.QepService
import net.shrine.broadcaster.SigningBroadcastAndAggregationService
import net.shrine.broadcaster.InJvmBroadcasterClient
import net.shrine.adapter.FlagQueryAdapter
import net.shrine.protocol.query.Term
import net.shrine.adapter.RunQueryAdapter
import net.shrine.client.Poster
import net.shrine.client.HttpClient
import net.shrine.client.HttpResponse
import net.shrine.adapter.translators.QueryDefinitionTranslator
import net.shrine.adapter.translators.ExpressionTranslator
import net.shrine.util.XmlDateHelper
import net.shrine.adapter.ReadQueryResultAdapter
import net.shrine.protocol.query.QueryDefinition
import net.shrine.adapter.UnFlagQueryAdapter
import net.shrine.crypto.SigningCertStrategy
/**
* @author clint
* @since Nov 27, 2013
*
* An in-JVM simulation of a Shrine network with one hub and 4 doanstream adapters.
*
* The hub and adapters are wired up with mock AdapterClients that do in-JVM communication via method calls
* instead of remotely.
*
* The adapters are configured to respond with valid results for DeleteQueryRequests
* only. Other requests could be handled, but that would not provide benefit to offset the effort of wiring
* up more and more-complex Adapters.
*
* The test network is queried, and the final result, as well as the state of each adapter, is inspected to
* ensure that the right messages were sent between elements of the system.
*
*/
final class NetworkSimulationTest extends AbstractSquerylAdapterTest with ShouldMatchersForJUnit {
private val certCollection = TestKeystore.certCollection
private lazy val myCertId: CertId = certCollection.myCertId.get
private lazy val signerVerifier = new DefaultSignerVerifier(certCollection)
private val domain = "test-domain"
private val username = "test-username"
private val password = "test-password"
import NetworkSimulationTest._
import scala.concurrent.duration._
private def deleteQueryAdapter: DeleteQueryAdapter = new DeleteQueryAdapter(dao)
private def flagQueryAdapter: FlagQueryAdapter = new FlagQueryAdapter(dao)
private def unFlagQueryAdapter: UnFlagQueryAdapter = new UnFlagQueryAdapter(dao)
private def mockPoster = Poster("http://example.com", new HttpClient {
override def post(input: String, url: String): HttpResponse = ???
})
private val hiveCredentials = HiveCredentials("d", "u", "pwd", "pid")
private def queuesQueriesRunQueryAdapter: RunQueryAdapter = {
val translator = new QueryDefinitionTranslator(new ExpressionTranslator(Map("n1" -> Set("l1"))))
RunQueryAdapter(
mockPoster,
dao,
hiveCredentials,
translator,
10000,
doObfuscation = false,
runQueriesImmediately = false,
DefaultBreakdownResultOutputTypes.toSet,
collectAdapterAudit = false
)
}
private def immediatelyRunsQueriesRunQueryAdapter(setSize: Long): RunQueryAdapter = {
val mockCrcPoster = Poster("http://example.com", new HttpClient {
override def post(input: String, url: String): HttpResponse = {
val req = RunQueryRequest.fromI2b2String(DefaultBreakdownResultOutputTypes.toSet)(input).get
val now = XmlDateHelper.now
val queryResult = QueryResult(1L, 42L, Some(ResultOutputType.PATIENT_COUNT_XML), setSize, Some(now), Some(now), Some("desc"), QueryResult.StatusType.Finished, Some("status"))
val mockCrcXml = RawCrcRunQueryResponse(req.networkQueryId, XmlDateHelper.now, req.authn.username, req.projectId, req.queryDefinition, 42L, Map(ResultOutputType.PATIENT_COUNT_XML -> Seq(queryResult))).toI2b2String
HttpResponse.ok(mockCrcXml)
}
})
queuesQueriesRunQueryAdapter.copy(poster = mockCrcPoster, runQueriesImmediately = true)
}
private def readQueryResultAdapter(setSize: Long): ReadQueryResultAdapter = {
new ReadQueryResultAdapter(
mockPoster,
hiveCredentials,
dao,
doObfuscation = false,
DefaultBreakdownResultOutputTypes.toSet,
collectAdapterAudit = false
)
}
private lazy val adaptersByNodeId: Seq[(NodeId, MockAdapterRequestHandler)] = {
import NodeName._
import RequestType.{ MasterDeleteRequest => MasterDeleteRequestRT, FlagQueryRequest => FlagQueryRequestRT, QueryDefinitionRequest => RunQueryRT, GetQueryResult => ReadQueryResultRT, UnFlagQueryRequest => UnFlagQueryRequestRT }
(for {
(childName, setSize) <- Seq((A, 1L), (B, 2L), (C, 3L), (D, 4L))
} yield {
val nodeId = NodeId(childName.name)
val maxSignatureAge = 1.hour
val adapterMap = AdapterMap(Map(
MasterDeleteRequestRT -> deleteQueryAdapter,
FlagQueryRequestRT -> flagQueryAdapter,
UnFlagQueryRequestRT -> unFlagQueryAdapter,
RunQueryRT -> queuesQueriesRunQueryAdapter,
ReadQueryResultRT -> readQueryResultAdapter(setSize)))
nodeId -> MockAdapterRequestHandler(new AdapterService(nodeId, signerVerifier, maxSignatureAge, adapterMap))
})
}
- private lazy val shrineService: ShrineService = {
+ private lazy val shrineService: QepService = {
val destinations: Set[NodeHandle] = {
(for {
(nodeId, adapterRequestHandler) <- adaptersByNodeId
} yield {
NodeHandle(nodeId, MockAdapterClient(nodeId, adapterRequestHandler))
}).toSet
}
- ShrineService(
+ QepService(
"example.com",
MockAuditDao,
MockAuthenticator,
MockQueryAuthorizationService,
true,
SigningBroadcastAndAggregationService(InJvmBroadcasterClient(AdapterClientBroadcaster(destinations, MockHubDao)), signerVerifier, SigningCertStrategy.Attach),
1.hour,
DefaultBreakdownResultOutputTypes.toSet,
false)
}
@Test
def testSimulatedNetwork = afterCreatingTables {
val authn = AuthenticationInfo(domain, username, Credential(password, false))
val masterId = 12345L
import scala.concurrent.duration._
val req = DeleteQueryRequest("some-project-id", 1.second, authn, masterId)
val resp = shrineService.deleteQuery(req, true)
for {
(nodeId, mockAdapter) <- adaptersByNodeId
} {
mockAdapter.lastMessage.networkAuthn.domain should equal(authn.domain)
mockAdapter.lastMessage.networkAuthn.username should equal(authn.username)
mockAdapter.lastMessage.request should equal(req)
mockAdapter.lastResult.response should equal(DeleteQueryResponse(masterId))
}
resp should equal(DeleteQueryResponse(masterId))
}
@Test
def testQueueQuery = afterCreatingTables {
val authn = AuthenticationInfo(domain, username, Credential(password, false))
val topicId = "askldjlkas"
val topicName = "Topic Name"
val queryName = "lsadj3028940"
import scala.concurrent.duration._
val runQueryReq = RunQueryRequest("some-project-id", 1.second, authn, 12345L, Some(topicId), Some(topicName), Set(ResultOutputType.PATIENT_COUNT_XML), QueryDefinition(queryName, Term("n1")))
val aggregatedRunQueryResp = shrineService.runQuery(runQueryReq, true).asInstanceOf[AggregatedRunQueryResponse]
var broadcastMessageId: Option[Long] = None
//Broadcast the original run query request; all nodes should queue the query
for {
(nodeId, mockAdapter) <- adaptersByNodeId
} {
broadcastMessageId = Option(mockAdapter.lastMessage.requestId)
mockAdapter.lastMessage.networkAuthn.domain should equal(authn.domain)
mockAdapter.lastMessage.networkAuthn.username should equal(authn.username)
val lastReq = mockAdapter.lastMessage.request.asInstanceOf[RunQueryRequest]
lastReq.authn should equal(runQueryReq.authn)
lastReq.requestType should equal(runQueryReq.requestType)
lastReq.waitTime should equal(runQueryReq.waitTime)
//todo what to do with this check? lastReq.networkQueryId should equal(mockAdapter.lastMessage.requestId)
lastReq.outputTypes should equal(runQueryReq.outputTypes)
lastReq.projectId should equal(runQueryReq.projectId)
lastReq.queryDefinition should equal(runQueryReq.queryDefinition)
lastReq.topicId should equal(runQueryReq.topicId)
val runQueryResp = mockAdapter.lastResult.response.asInstanceOf[RunQueryResponse]
runQueryResp.queryId should equal(-1L)
runQueryResp.singleNodeResult.statusType should equal(QueryResult.StatusType.Held)
runQueryResp.singleNodeResult.setSize should equal(-1L)
}
aggregatedRunQueryResp.queryId should equal(broadcastMessageId.get)
aggregatedRunQueryResp.results.map(_.setSize) should equal(Seq(-1L, -1L, -1L, -1L, -4L))
}
@Test
def testFlagQuery = afterCreatingTables {
val authn = AuthenticationInfo(domain, username, Credential(password, false))
val masterId = 12345L
import scala.concurrent.duration._
val networkQueryId = 9999L
val name = "some query"
val expr = Term("foo")
val fooQuery = QueryDefinition(name,expr)
dao.insertQuery(masterId.toString, networkQueryId, authn, fooQuery, isFlagged = false, hasBeenRun = true, flagMessage = None)
dao.findQueryByNetworkId(networkQueryId).get.isFlagged should be(false)
dao.findQueryByNetworkId(networkQueryId).get.hasBeenRun should be(true)
dao.findQueryByNetworkId(networkQueryId).get.flagMessage should be(None)
val req = FlagQueryRequest("some-project-id", 1.second, authn, networkQueryId, Some("foo"))
val resp = shrineService.flagQuery(req, true)
resp should equal(FlagQueryResponse)
dao.findQueryByNetworkId(networkQueryId).get.isFlagged should be(true)
dao.findQueryByNetworkId(networkQueryId).get.hasBeenRun should be(true)
dao.findQueryByNetworkId(networkQueryId).get.flagMessage should be(Some("foo"))
}
@Test
def testUnFlagQuery = afterCreatingTables {
val authn = AuthenticationInfo(domain, username, Credential(password, false))
val masterId = 12345L
import scala.concurrent.duration._
val networkQueryId = 9999L
val flagMsg = Some("foo")
val name = "some query"
val expr = Term("foo")
val fooQuery = QueryDefinition(name,expr)
dao.insertQuery(masterId.toString, networkQueryId, authn, fooQuery, isFlagged = true, hasBeenRun = true, flagMessage = flagMsg)
dao.findQueryByNetworkId(networkQueryId).get.isFlagged should be(true)
dao.findQueryByNetworkId(networkQueryId).get.hasBeenRun should be(true)
dao.findQueryByNetworkId(networkQueryId).get.flagMessage should be(flagMsg)
val req = UnFlagQueryRequest("some-project-id", 1.second, authn, networkQueryId)
val resp = shrineService.unFlagQuery(req, true)
resp should equal(UnFlagQueryResponse)
dao.findQueryByNetworkId(networkQueryId).get.isFlagged should be(false)
dao.findQueryByNetworkId(networkQueryId).get.hasBeenRun should be(true)
dao.findQueryByNetworkId(networkQueryId).get.flagMessage should be(None)
}
}
object NetworkSimulationTest {
private final case class MockAdapterClient(nodeId: NodeId, adapter: AdapterRequestHandler) extends AdapterClient with Loggable {
import scala.concurrent.ExecutionContext.Implicits.global
override def query(message: BroadcastMessage): Future[Result] = Future.successful {
debug(s"Invoking Adapter $nodeId with $message")
val result = adapter.handleRequest(message)
debug(s"Got result from $nodeId: $result")
result
}
}
private final case class MockAdapterRequestHandler(delegate: AdapterRequestHandler) extends AdapterRequestHandler {
@volatile var lastMessage: BroadcastMessage = _
@volatile var lastResult: Result = _
override def handleRequest(request: BroadcastMessage): Result = {
lastMessage = request
val result = delegate.handleRequest(request)
lastResult = result
result
}
}
}
diff --git a/integration/src/test/scala/net/shrine/integration/OneHubTwoSpokesJaxrsTest.scala b/integration/src/test/scala/net/shrine/integration/OneHubTwoSpokesJaxrsTest.scala
index 3c23d545f..aad2f697d 100644
--- a/integration/src/test/scala/net/shrine/integration/OneHubTwoSpokesJaxrsTest.scala
+++ b/integration/src/test/scala/net/shrine/integration/OneHubTwoSpokesJaxrsTest.scala
@@ -1,117 +1,90 @@
package net.shrine.integration
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
+import net.shrine.protocol.{DeleteQueryRequest, DeleteQueryResponse, RequestType, Result}
import net.shrine.util.ShouldMatchersForJUnit
-import net.shrine.adapter.client.RemoteAdapterClient
-import net.shrine.adapter.service.AdapterRequestHandler
-import net.shrine.adapter.service.AdapterResource
-import net.shrine.adapter.service.JerseyTestComponent
-import net.shrine.broadcaster.AdapterClientBroadcaster
-import net.shrine.broadcaster.NodeHandle
-import net.shrine.client.JerseyHttpClient
-import net.shrine.client.JerseyShrineClient
-import net.shrine.client.Poster
-import net.shrine.crypto.DefaultSignerVerifier
-import net.shrine.crypto.TestKeystore
-import net.shrine.crypto.TrustParam
-import net.shrine.protocol.AuthenticationInfo
-import net.shrine.protocol.Credential
-import net.shrine.protocol.DeleteQueryRequest
-import net.shrine.protocol.DeleteQueryResponse
-import net.shrine.protocol.NodeId
-import net.shrine.protocol.RequestType
-import net.shrine.protocol.Result
-import net.shrine.protocol.ShrineRequestHandler
-import net.shrine.service.ShrineResource
-import net.shrine.service.ShrineService
-import net.shrine.protocol.CertId
-import net.shrine.broadcaster.InJvmBroadcasterClient
+import org.junit.{After, Before, Test}
/**
* @author clint
- * @date Jan 8, 2014
+ * @since Jan 8, 2014
*
* An end-to-end JAX-RS test that fires up a Hub and two spokes, makes a query,
* and verifies that the correct requests were broadcast to the spokes, and
* that the correct responses were received and aggregated at the hub.
*
* DeleteQueryResponses are used because they have very few fields and are easy
* to construct and verify. It might be nice in the future to use
* RunQuery{Request,Response}, but that was more trouble than it was worth for
* a first pass.
*
* NB: The hub runs on port 9997, and the two spokes run on ports 9998 and 9999.
*/
final class OneHubTwoSpokesJaxrsTest extends AbstractHubAndSpokesTest with ShouldMatchersForJUnit { thisTest =>
- import scala.concurrent.duration._
-
@Test
def testBroadcastDeleteQueryShrine: Unit = {
doTestBroadcastDeleteQuery(shrineHubComponent)
}
@Test
def testBroadcastDeleteQueryI2b2: Unit = {
doTestBroadcastDeleteQuery(i2b2HubComponent)
}
lazy val shrineHubComponent = Hubs.Shrine(thisTest, port = 9997)
lazy val i2b2HubComponent = Hubs.I2b2(thisTest, port = 9996)
private def doTestBroadcastDeleteQuery[H <: AnyRef](hubComponent: AbstractHubComponent[H]): Unit = {
val masterId = 123456L
val projectId = "some-project-id"
val client = hubComponent.clientFor(projectId, networkAuthn)
//Broadcast a message
val resp = client.deleteQuery(masterId, true)
//Make sure we got the right response
resp.queryId should equal(masterId)
//Make sure all the spokes received the right message
spokes.foreach { spoke =>
val lastMessage = spoke.mockHandler.lastMessage.get
lastMessage.networkAuthn.domain should equal(networkAuthn.domain)
lastMessage.networkAuthn.username should equal(networkAuthn.username)
val req = lastMessage.request.asInstanceOf[DeleteQueryRequest]
req.queryId should equal(masterId)
req.projectId should equal(projectId)
req.requestType should equal(RequestType.MasterDeleteRequest)
req.authn should equal(networkAuthn)
}
//Make sure we got the right responses at the hub
val multiplexer = hubComponent.broadcaster.lastMultiplexer.get
val expectedResponses = spokes.map { spoke =>
Result(spoke.nodeId, spoke.mockHandler.elapsed, DeleteQueryResponse(masterId))
}.toSet
multiplexer.resultsSoFar.toSet should equal(expectedResponses)
}
@Before
override def setUp() {
super.setUp()
shrineHubComponent.JerseyTest.setUp()
i2b2HubComponent.JerseyTest.setUp()
}
@After
override def tearDown() {
shrineHubComponent.JerseyTest.tearDown()
i2b2HubComponent.JerseyTest.tearDown()
super.tearDown()
}
}
\ No newline at end of file
diff --git a/integration/src/test/scala/net/shrine/integration/OneQepOneHubTwoSpokesJaxrsTest.scala b/integration/src/test/scala/net/shrine/integration/OneQepOneHubTwoSpokesJaxrsTest.scala
index decc937eb..215b63c9b 100644
--- a/integration/src/test/scala/net/shrine/integration/OneQepOneHubTwoSpokesJaxrsTest.scala
+++ b/integration/src/test/scala/net/shrine/integration/OneQepOneHubTwoSpokesJaxrsTest.scala
@@ -1,265 +1,247 @@
package net.shrine.integration
-import net.shrine.util.ShouldMatchersForJUnit
-import net.shrine.protocol.{NodeId, DeleteQueryRequest, RequestType, Result, DeleteQueryResponse, ShrineRequestHandler, FlagQueryResponse, FlagQueryRequest, UnFlagQueryResponse, UnFlagQueryRequest, ResultOutputType, RunQueryRequest, RunQueryResponse, DefaultBreakdownResultOutputTypes}
-import org.junit.After
-import org.junit.Before
-import net.shrine.client.JerseyShrineClient
-import net.shrine.crypto.TrustParam
-import org.junit.Test
-import net.shrine.broadcaster.service.BroadcasterMultiplexerRequestHandler
-import net.shrine.adapter.service.JerseyTestComponent
-import net.shrine.broadcaster.service.BroadcasterMultiplexerService
-import net.shrine.broadcaster.service.BroadcasterMultiplexerResource
-import net.shrine.broadcaster.NodeHandle
import net.shrine.adapter.client.RemoteAdapterClient
-import net.shrine.client.Poster
-import net.shrine.client.JerseyHttpClient
-import net.shrine.broadcaster.AdapterClientBroadcaster
-import net.shrine.broadcaster.InJvmBroadcasterClient
-import net.shrine.service.ShrineService
-import net.shrine.service.ShrineResource
-import net.shrine.broadcaster.PosterBroadcasterClient
-import net.shrine.protocol.query.Term
-import net.shrine.protocol.query.Or
-import net.shrine.protocol.query.Modifiers
-import net.shrine.protocol.query.QueryDefinition
-import net.shrine.protocol.query.Constrained
-import net.shrine.protocol.query.ValueConstraint
+import net.shrine.adapter.service.JerseyTestComponent
+import net.shrine.broadcaster.{AdapterClientBroadcaster, NodeHandle, PosterBroadcasterClient}
+import net.shrine.broadcaster.service.{BroadcasterMultiplexerRequestHandler, BroadcasterMultiplexerResource, BroadcasterMultiplexerService}
+import net.shrine.protocol.query.{Constrained, Modifiers, Or, QueryDefinition, Term, ValueConstraint}
+import net.shrine.protocol.{DefaultBreakdownResultOutputTypes, DeleteQueryRequest, DeleteQueryResponse, FlagQueryRequest, FlagQueryResponse, NodeId, RequestType, Result, ResultOutputType, RunQueryRequest, RunQueryResponse, UnFlagQueryRequest, UnFlagQueryResponse}
+import net.shrine.util.ShouldMatchersForJUnit
+import org.junit.{After, Before, Test}
/**
* @author clint
* @since Mar 6, 2014
*/
final class OneQepOneHubTwoSpokesJaxrsTest extends AbstractHubAndSpokesTest with ShouldMatchersForJUnit { thisTest =>
@Test
def testBroadcastDeleteQueryShrine(): Unit = doTestBroadcastDeleteQuery(shrineQueryEntryPointComponent)
@Test
def testBroadcastDeleteQueryI2b2(): Unit = doTestBroadcastDeleteQuery(i2b2QueryEntryPointComponent)
@Test
def testBroadcastFlagQueryShrine(): Unit = doTestBroadcastFlagQuery(shrineQueryEntryPointComponent)
@Test
def testBroadcastFlagQueryI2b2(): Unit = doTestBroadcastFlagQuery(i2b2QueryEntryPointComponent)
@Test
def testBroadcastUnFlagQueryShrine(): Unit = doTestBroadcastUnFlagQuery(shrineQueryEntryPointComponent)
@Test
def testBroadcastUnFlagQueryI2b2(): Unit = doTestBroadcastUnFlagQuery(i2b2QueryEntryPointComponent)
@Test
def testBroadcastRunQueryShrine(): Unit = doTestBroadcastRunQuery(shrineQueryEntryPointComponent)
@Test
def testBroadcastRunQueryI2b2(): Unit = doTestBroadcastRunQuery(i2b2QueryEntryPointComponent)
private def doTestBroadcastDeleteQuery[H <: AnyRef](queryEntryPointComponent: AbstractHubComponent[H]): Unit = {
val masterId = 123456L
val projectId = "some-project-id"
val client = queryEntryPointComponent.clientFor(projectId, networkAuthn)
//Broadcast a message
val resp = client.deleteQuery(masterId, true)
//Make sure we got the right response
resp.queryId should equal(masterId)
//Make sure all the spokes received the right message
spokes.foreach { spoke =>
val lastMessage = spoke.mockHandler.lastMessage.get
lastMessage.networkAuthn.domain should equal(networkAuthn.domain)
lastMessage.networkAuthn.username should equal(networkAuthn.username)
val req = lastMessage.request.asInstanceOf[DeleteQueryRequest]
req.queryId should equal(masterId)
req.projectId should equal(projectId)
req.requestType should equal(RequestType.MasterDeleteRequest)
req.authn should equal(networkAuthn)
}
//Make sure we got the right responses at the hub
val multiplexer = HubComponent.broadcaster.lastMultiplexer.get
val expectedResponses = spokes.map { spoke =>
Result(spoke.nodeId, spoke.mockHandler.elapsed, DeleteQueryResponse(masterId))
}.toSet
multiplexer.resultsSoFar.toSet should equal(expectedResponses)
}
private def doTestBroadcastRunQuery[H <: AnyRef](queryEntryPointComponent: AbstractHubComponent[H]): Unit = {
val masterId = 123456L
val projectId = "some-project-id"
val client = queryEntryPointComponent.clientFor(projectId, networkAuthn)
//Include a modified term, to ensure they're parsed properly
val queryDefinition = QueryDefinition("foo", Or(Term("x"), Constrained(Term("y"), Modifiers("some-modifier", "ap", "k"), ValueConstraint("foo", Some("bar"), "baz", "nuh"))))
//Broadcast a message
val resp = client.runQuery("some-topic-id", Set(ResultOutputType.PATIENT_COUNT_XML), queryDefinition, true)
resp.results.size should equal(spokes.size)
//Make sure all the spokes received the right message
spokes.foreach { spoke =>
val lastMessage = spoke.mockHandler.lastMessage.get
lastMessage.networkAuthn.domain should equal(networkAuthn.domain)
lastMessage.networkAuthn.username should equal(networkAuthn.username)
val req = lastMessage.request.asInstanceOf[RunQueryRequest]
req.projectId should equal(projectId)
req.requestType should equal(RequestType.QueryDefinitionRequest)
req.authn should equal(networkAuthn)
req.queryDefinition should equal(queryDefinition)
}
//Make sure we got the right responses at the hub
val multiplexer = HubComponent.broadcaster.lastMultiplexer.get
multiplexer.resultsSoFar.collect { case Result(_, _, payload) => payload.getClass } should equal((1 to spokes.size).map(_ => classOf[RunQueryResponse]))
val expectedResponders = spokes.map(_.nodeId).toSet
multiplexer.resultsSoFar.map(_.origin).toSet should equal(expectedResponders)
}
private def doTestBroadcastFlagQuery[H <: AnyRef](queryEntryPointComponent: AbstractHubComponent[H]): Unit = {
val networkQueryId = 123456L
val projectId = "some-project-id"
val client = queryEntryPointComponent.clientFor(projectId, networkAuthn)
val message = "flag message"
//Broadcast a message
val resp = client.flagQuery(networkQueryId, Some(message), true)
//Make sure we got the right response
resp should be(FlagQueryResponse)
//Make sure all the spokes received the right message
spokes.foreach { spoke =>
val lastMessage = spoke.mockHandler.lastMessage.get
lastMessage.networkAuthn.domain should equal(networkAuthn.domain)
lastMessage.networkAuthn.username should equal(networkAuthn.username)
val req = lastMessage.request.asInstanceOf[FlagQueryRequest]
req.networkQueryId should equal(networkQueryId)
req.projectId should equal(projectId)
req.requestType should equal(RequestType.FlagQueryRequest)
req.authn should equal(networkAuthn)
req.message should be(Some(message))
}
//Make sure we got the right responses at the hub
val multiplexer = HubComponent.broadcaster.lastMultiplexer.get
val expectedResponses = spokes.map { spoke =>
Result(spoke.nodeId, spoke.mockHandler.elapsed, FlagQueryResponse)
}.toSet
multiplexer.resultsSoFar.toSet should equal(expectedResponses)
}
private def doTestBroadcastUnFlagQuery[H <: AnyRef](queryEntryPointComponent: AbstractHubComponent[H]): Unit = {
val networkQueryId = 123456L
val projectId = "some-project-id"
val client = queryEntryPointComponent.clientFor(projectId, networkAuthn)
//Broadcast a message
val resp = client.unFlagQuery(networkQueryId, true)
//Make sure we got the right response
resp should be(UnFlagQueryResponse)
//Make sure all the spokes received the right message
spokes.foreach { spoke =>
val lastMessage = spoke.mockHandler.lastMessage.get
lastMessage.networkAuthn.domain should equal(networkAuthn.domain)
lastMessage.networkAuthn.username should equal(networkAuthn.username)
val req = lastMessage.request.asInstanceOf[UnFlagQueryRequest]
req.networkQueryId should equal(networkQueryId)
req.projectId should equal(projectId)
req.requestType should equal(RequestType.UnFlagQueryRequest)
req.authn should equal(networkAuthn)
}
//Make sure we got the right responses at the hub
val multiplexer = HubComponent.broadcaster.lastMultiplexer.get
val expectedResponses = spokes.map { spoke =>
Result(spoke.nodeId, spoke.mockHandler.elapsed, UnFlagQueryResponse)
}.toSet
multiplexer.resultsSoFar.toSet should equal(expectedResponses)
}
import scala.concurrent.duration._
lazy val i2b2QueryEntryPointComponent = Hubs.I2b2(thisTest, port = 9995, broadcasterClient = Some(PosterBroadcasterClient(posterFor(HubComponent), DefaultBreakdownResultOutputTypes.toSet)))
lazy val shrineQueryEntryPointComponent = Hubs.Shrine(thisTest, port = 9996, broadcasterClient = Some(PosterBroadcasterClient(posterFor(HubComponent), DefaultBreakdownResultOutputTypes.toSet)))
object HubComponent extends JerseyTestComponent[BroadcasterMultiplexerRequestHandler] {
override val basePath = "broadcaster/broadcast"
override val port = 9997
override def resourceClass(handler: BroadcasterMultiplexerRequestHandler) = BroadcasterMultiplexerResource(handler)
lazy val broadcaster: InspectableDelegatingBroadcaster = {
val destinations: Set[NodeHandle] = spokes.map { spoke =>
val client = RemoteAdapterClient(NodeId.Unknown,posterFor(spoke), DefaultBreakdownResultOutputTypes.toSet)
NodeHandle(spoke.nodeId, client)
}
InspectableDelegatingBroadcaster(AdapterClientBroadcaster(destinations, MockHubDao))
}
override lazy val makeHandler: BroadcasterMultiplexerRequestHandler = {
BroadcasterMultiplexerService(broadcaster, 1.hour)
}
}
@Before
override def setUp() {
super.setUp()
HubComponent.JerseyTest.setUp()
shrineQueryEntryPointComponent.JerseyTest.setUp()
i2b2QueryEntryPointComponent.JerseyTest.setUp()
}
@After
override def tearDown() {
i2b2QueryEntryPointComponent.JerseyTest.tearDown()
shrineQueryEntryPointComponent.JerseyTest.tearDown()
HubComponent.JerseyTest.tearDown()
super.tearDown()
}
}
\ No newline at end of file
diff --git a/qep/service/pom.xml b/qep/service/pom.xml
index ec3908409..beae5aa46 100644
--- a/qep/service/pom.xml
+++ b/qep/service/pom.xml
@@ -1,208 +1,208 @@
4.0.0
SHRINE Service
- shrine-service
+ shrine-qep
jar
net.shrine
shrine-base
1.21.0-SNAPSHOT
../../pom.xml
src/main/scala
src/test/scala
net.alchim31.maven
scala-maven-plugin
false
org.squeryl
squeryl_${scala-major-version}
org.scala-lang
scalap
org.scala-lang
scalap
${scala-version}
com.sun.jersey
jersey-server
com.sun.jersey
jersey-client
net.shrine
shrine-auth
${project.version}
net.shrine
shrine-broadcaster-aggregator
${project.version}
net.shrine
shrine-crypto
${project.version}
net.shrine
shrine-client
${project.version}
net.shrine
shrine-adapter-client-api
${project.version}
net.shrine
shrine-protocol
${project.version}
net.shrine
shrine-data-commons
${project.version}
com.typesafe.slick
slick_2.11
${slick-version}
org.suecarter
freeslick_2.11
3.0.3.1
org.slf4j
slf4j-log4j12
${slf4j-version}
mysql
mysql-connector-java
log4j
log4j
com.sun.jersey.jersey-test-framework
jersey-test-framework-http
${jersey-version}
test
com.sun.jersey.contribs
jersey-simple-server
${jersey-version}
test
com.h2database
h2
test
org.easymock
easymock
test
org.springframework
spring-jdbc
test
net.shrine
shrine-test-commons
${project.version}
test-jar
test
net.shrine
shrine-data-commons
${project.version}
test-jar
test
net.shrine
shrine-config
${project.version}
test-jar
test
org.codehaus.mojo
findbugs-maven-plugin
2.3.1
Max
org.codehaus.mojo
cobertura-maven-plugin
2.3
org.apache.maven.plugins
maven-checkstyle-plugin
2.5
org.apache.maven.plugins
maven-pmd-plugin
2.4
1.6
diff --git a/qep/service/src/main/scala/net/shrine/service/AbstractShrineService.scala b/qep/service/src/main/scala/net/shrine/qep/AbstractQepService.scala
similarity index 79%
rename from qep/service/src/main/scala/net/shrine/service/AbstractShrineService.scala
rename to qep/service/src/main/scala/net/shrine/qep/AbstractQepService.scala
index 0fb1fc180..5057366c5 100644
--- a/qep/service/src/main/scala/net/shrine/service/AbstractShrineService.scala
+++ b/qep/service/src/main/scala/net/shrine/qep/AbstractQepService.scala
@@ -1,226 +1,210 @@
-package net.shrine.service
+package net.shrine.qep
import net.shrine.log.Loggable
-import net.shrine.service.audit.QepAuditDb
-import net.shrine.service.dao.AuditDao
+import net.shrine.qep.audit.QepAuditDb
+import net.shrine.qep.dao.AuditDao
import net.shrine.authentication.Authenticator
import net.shrine.authorization.QueryAuthorizationService
import net.shrine.broadcaster.BroadcastAndAggregationService
+import net.shrine.qep.queries.QepQueryDb
import scala.concurrent.duration.Duration
import net.shrine.util.XmlDateHelper
import scala.concurrent.Future
import scala.concurrent.Await
-import net.shrine.protocol.RunQueryRequest
+import net.shrine.protocol.{ReadPreviousQueriesResponse, RunQueryRequest, BaseShrineRequest, AuthenticationInfo, Credential, BaseShrineResponse, ReadQueryInstancesRequest, QueryInstance, ReadQueryInstancesResponse, ReadQueryDefinitionRequest, DeleteQueryRequest, ReadApprovedQueryTopicsRequest, ReadInstanceResultsRequest, ReadPreviousQueriesRequest, RenameQueryRequest, ReadPdoRequest, FlagQueryRequest, UnFlagQueryRequest, ReadResultOutputTypesRequest, ReadResultOutputTypesResponse, ResultOutputType}
import net.shrine.authorization.AuthorizationResult.{Authorized, NotAuthorized}
-import net.shrine.protocol.BaseShrineRequest
-import net.shrine.protocol.AuthenticationInfo
-import net.shrine.protocol.Credential
import net.shrine.authentication.AuthenticationResult
import net.shrine.authentication.NotAuthenticatedException
import net.shrine.aggregation.RunQueryAggregator
import net.shrine.aggregation.Aggregators
-import net.shrine.protocol.BaseShrineResponse
import net.shrine.aggregation.Aggregator
-import net.shrine.protocol.ReadQueryInstancesRequest
-import net.shrine.protocol.QueryInstance
-import net.shrine.protocol.ReadQueryInstancesResponse
-import net.shrine.protocol.ReadQueryDefinitionRequest
import net.shrine.aggregation.ReadQueryDefinitionAggregator
-import net.shrine.protocol.DeleteQueryRequest
-import net.shrine.aggregation.ReadPreviousQueriesAggregator
import net.shrine.aggregation.DeleteQueryAggregator
import net.shrine.aggregation.ReadPdoResponseAggregator
import net.shrine.aggregation.RenameQueryAggregator
import net.shrine.aggregation.ReadInstanceResultsAggregator
-import net.shrine.protocol.ReadApprovedQueryTopicsRequest
-import net.shrine.protocol.ReadInstanceResultsRequest
-import net.shrine.protocol.ReadPreviousQueriesRequest
-import net.shrine.protocol.RenameQueryRequest
-import net.shrine.protocol.ReadPdoRequest
-import net.shrine.protocol.FlagQueryRequest
import net.shrine.aggregation.FlagQueryAggregator
-import net.shrine.protocol.UnFlagQueryRequest
import net.shrine.aggregation.UnFlagQueryAggregator
-import net.shrine.protocol.ReadResultOutputTypesRequest
-import net.shrine.protocol.ReadResultOutputTypesResponse
-import net.shrine.protocol.ResultOutputType
/**
* @author clint
* @since Feb 19, 2014
*/
-//todo rename? This is the heart of the QEP.
-trait AbstractShrineService[BaseResp <: BaseShrineResponse] extends Loggable {
+trait AbstractQepService[BaseResp <: BaseShrineResponse] extends Loggable {
val commonName:String
val auditDao: AuditDao
val authenticator: Authenticator
val authorizationService: QueryAuthorizationService
val includeAggregateResult: Boolean
val broadcastAndAggregationService: BroadcastAndAggregationService
val queryTimeout: Duration
val breakdownTypes: Set[ResultOutputType]
val collectQepAudit:Boolean
protected def doReadResultOutputTypes(request: ReadResultOutputTypesRequest): BaseResp = {
info(s"doReadResultOutputTypes($request)")
authenticateAndThen(request) { authResult =>
val resultOutputTypes = ResultOutputType.nonErrorTypes ++ breakdownTypes
//TODO: XXX: HACK: Would like to remove the cast
ReadResultOutputTypesResponse(resultOutputTypes).asInstanceOf[BaseResp]
}
}
protected def doFlagQuery(request: FlagQueryRequest, shouldBroadcast: Boolean = true): BaseResp = {
+ info(s"Flag request is $request")
+
+ QepQueryDb.db.insertQepQueryFlag(request)
doBroadcastQuery(request, new FlagQueryAggregator, shouldBroadcast)
}
protected def doUnFlagQuery(request: UnFlagQueryRequest, shouldBroadcast: Boolean = true): BaseResp = {
+ QepQueryDb.db.insertQepQueryFlag(request)
doBroadcastQuery(request, new UnFlagQueryAggregator, shouldBroadcast)
}
protected def doRunQuery(request: RunQueryRequest, shouldBroadcast: Boolean): BaseResp = {
info(s"doRunQuery($request,$shouldBroadcast) with $runQueryAggregatorFor")
+ //store the query in the qep's database
+
doBroadcastQuery(request, runQueryAggregatorFor(request), shouldBroadcast)
}
protected def doReadQueryDefinition(request: ReadQueryDefinitionRequest, shouldBroadcast: Boolean): BaseResp = {
doBroadcastQuery(request, new ReadQueryDefinitionAggregator, shouldBroadcast)
}
protected def doReadPdo(request: ReadPdoRequest, shouldBroadcast: Boolean): BaseResp = {
doBroadcastQuery(request, new ReadPdoResponseAggregator, shouldBroadcast)
}
protected def doReadInstanceResults(request: ReadInstanceResultsRequest, shouldBroadcast: Boolean): BaseResp = {
doBroadcastQuery(request, new ReadInstanceResultsAggregator(request.shrineNetworkQueryId, false), shouldBroadcast)
}
protected def doReadQueryInstances(request: ReadQueryInstancesRequest, shouldBroadcast: Boolean): BaseResp = {
info(s"doReadQueryInstances($request)")
authenticateAndThen(request) { authResult =>
val now = XmlDateHelper.now
val networkQueryId = request.queryId
val username = request.authn.username
val groupId = request.projectId
//NB: Return a dummy response, with a dummy QueryInstance containing the network (Shrine) id of the query we'd like
//to get "instances" for. This allows the legacy web client to formulate a request for query results that Shrine
//can understand, while meeting the conversational requirements of the legacy web client.
val instance = QueryInstance(networkQueryId.toString, networkQueryId.toString, username, groupId, now, now)
//TODO: XXX: HACK: Would like to remove the cast
//NB: Munge in username from authentication result
ReadQueryInstancesResponse(networkQueryId, authResult.username, groupId, Seq(instance)).asInstanceOf[BaseResp]
}
}
- protected def doReadPreviousQueries(request: ReadPreviousQueriesRequest, shouldBroadcast: Boolean): BaseResp = {
- doBroadcastQuery(request, new ReadPreviousQueriesAggregator, shouldBroadcast)
+ protected def doReadPreviousQueries(request: ReadPreviousQueriesRequest, shouldBroadcast: Boolean): ReadPreviousQueriesResponse = {
+ //pull results from the local database.
+ QepQueryDb.db.selectPreviousQueries(request)
}
protected def doRenameQuery(request: RenameQueryRequest, shouldBroadcast: Boolean): BaseResp = {
doBroadcastQuery(request, new RenameQueryAggregator, shouldBroadcast)
}
protected def doDeleteQuery(request: DeleteQueryRequest, shouldBroadcast: Boolean): BaseResp = {
doBroadcastQuery(request, new DeleteQueryAggregator, shouldBroadcast)
}
protected def doReadApprovedQueryTopics(request: ReadApprovedQueryTopicsRequest, shouldBroadcast: Boolean): BaseResp = authenticateAndThen(request) { _ =>
info(s"doReadApprovedQueryTopics($request)")
- //TODO: Is authenticating necessary?
//TODO: XXX: HACK: Would like to remove the cast
authorizationService.readApprovedEntries(request) match {
case Left(errorResponse) => errorResponse.asInstanceOf[BaseResp]
case Right(validResponse) => validResponse.asInstanceOf[BaseResp]
}
}
import broadcastAndAggregationService.sendAndAggregate
protected def doBroadcastQuery(request: BaseShrineRequest, aggregator: Aggregator, shouldBroadcast: Boolean): BaseResp = {
authenticateAndThen(request) { authResult =>
debug(s"doBroadcastQuery($request) authResult is $authResult")
//NB: Use credentials obtained from Authenticator (oddly, we authenticate with one set of credentials and are "logged in" under (possibly!) another
//When making BroadcastMessages
val networkAuthn = AuthenticationInfo(authResult.domain, authResult.username, Credential("", isToken = false))
//NB: Only audit RunQueryRequests
request match {
case runQueryRequest: RunQueryRequest =>
// inject modified, authorized runQueryRequest
auditAuthorizeAndThen(runQueryRequest) { authorizedRequest =>
debug(s"doBroadcastQuery authorizedRequest is $authorizedRequest")
// tuck the ACT audit metrics data into a database here
- //todo network id is -1 !
if (collectQepAudit) QepAuditDb.db.insertQepQuery(authorizedRequest,commonName)
+ QepQueryDb.db.insertQepQuery(authorizedRequest)
doSynchronousQuery(networkAuthn,authorizedRequest,aggregator,shouldBroadcast)
}
case _ => doSynchronousQuery(networkAuthn,request,aggregator,shouldBroadcast)
}
}
}
private def doSynchronousQuery(networkAuthn: AuthenticationInfo,request: BaseShrineRequest, aggregator: Aggregator, shouldBroadcast: Boolean) = {
info(s"doSynchronousQuery($request) started")
val response = waitFor(sendAndAggregate(networkAuthn, request, aggregator, shouldBroadcast)).asInstanceOf[BaseResp]
info(s"doSynchronousQuery($request) completed with response $response")
response
}
- private[service] val runQueryAggregatorFor: RunQueryRequest => RunQueryAggregator = Aggregators.forRunQueryRequest(includeAggregateResult)
+ private[qep] val runQueryAggregatorFor: RunQueryRequest => RunQueryAggregator = Aggregators.forRunQueryRequest(includeAggregateResult)
protected def waitFor[R](futureResponse: Future[R]): R = {
XmlDateHelper.time("Waiting for aggregated results")(debug(_)) {
Await.result(futureResponse, queryTimeout)
}
}
- private[service] def auditAuthorizeAndThen[T](request: RunQueryRequest)(body: (RunQueryRequest => T)): T = {
+ private[qep] def auditAuthorizeAndThen[T](request: RunQueryRequest)(body: (RunQueryRequest => T)): T = {
auditTransactionally(request) {
debug(s"auditAuthorizeAndThen($request) with $authorizationService")
val authorizedRequest = authorizationService.authorizeRunQueryRequest(request) match {
case na: NotAuthorized => throw na.toException
- case authorized: Authorized => {
- request.copy(topicName = authorized.topicIdAndName.map(x => x._2))
- }
+ case authorized: Authorized => request.copy(topicName = authorized.topicIdAndName.map(x => x._2))
}
body(authorizedRequest)
}
}
- private[service] def auditTransactionally[T](request: RunQueryRequest)(body: => T): T = {
+ private[qep] def auditTransactionally[T](request: RunQueryRequest)(body: => T): T = {
try { body } finally {
auditDao.addAuditEntry(
request.projectId,
request.authn.domain,
request.authn.username,
request.queryDefinition.toI2b2String, //TODO: Use i2b2 format Still?
request.topicId)
}
}
import AuthenticationResult._
- private[service] def authenticateAndThen[T](request: BaseShrineRequest)(f: Authenticated => T): T = {
+ private[qep] def authenticateAndThen[T](request: BaseShrineRequest)(f: Authenticated => T): T = {
val AuthenticationInfo(domain, username, _) = request.authn
val authResult = authenticator.authenticate(request.authn)
authResult match {
case a: Authenticated => f(a)
case NotAuthenticated(_, _, reason) => throw new NotAuthenticatedException(s"User $domain:$username could not be authenticated: $reason")
}
}
}
\ No newline at end of file
diff --git a/qep/service/src/main/scala/net/shrine/service/AllowsAllAuthenticator.scala b/qep/service/src/main/scala/net/shrine/qep/AllowsAllAuthenticator.scala
similarity index 84%
rename from qep/service/src/main/scala/net/shrine/service/AllowsAllAuthenticator.scala
rename to qep/service/src/main/scala/net/shrine/qep/AllowsAllAuthenticator.scala
index 714753c28..4b0a6a2ae 100644
--- a/qep/service/src/main/scala/net/shrine/service/AllowsAllAuthenticator.scala
+++ b/qep/service/src/main/scala/net/shrine/qep/AllowsAllAuthenticator.scala
@@ -1,18 +1,18 @@
-package net.shrine.service
+package net.shrine.qep
import net.shrine.authentication.Authenticator
import net.shrine.authentication.AuthenticationResult.Authenticated
import net.shrine.protocol.AuthenticationInfo
/**
* @author clint
* @date Dec 13, 2013
*
* This needs to be instantiatable, since Spring doesn't like Scala objects very much. :(
*/
class AllowsAllAuthenticator extends Authenticator {
override def authenticate(authn: AuthenticationInfo) = Authenticated(authn.domain, authn.username)
}
object AllowsAllAuthenticator extends AllowsAllAuthenticator
\ No newline at end of file
diff --git a/qep/service/src/main/scala/net/shrine/service/I2b2BroadcastResource.scala b/qep/service/src/main/scala/net/shrine/qep/I2b2BroadcastResource.scala
similarity index 99%
rename from qep/service/src/main/scala/net/shrine/service/I2b2BroadcastResource.scala
rename to qep/service/src/main/scala/net/shrine/qep/I2b2BroadcastResource.scala
index c98fd6a1d..a1494fb62 100644
--- a/qep/service/src/main/scala/net/shrine/service/I2b2BroadcastResource.scala
+++ b/qep/service/src/main/scala/net/shrine/qep/I2b2BroadcastResource.scala
@@ -1,101 +1,101 @@
-package net.shrine.service
+package net.shrine.qep
import net.shrine.log.Loggable
import scala.util.Try
import javax.ws.rs.POST
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.core.Response
import net.shrine.util.{StackTrace, XmlUtil}
import javax.ws.rs.core.MediaType
import net.shrine.protocol.ReadI2b2AdminPreviousQueriesRequest
import net.shrine.protocol.ShrineRequestHandler
import net.shrine.protocol.ReadQueryDefinitionRequest
import net.shrine.protocol.HandleableShrineRequest
import net.shrine.protocol.ShrineRequest
import net.shrine.protocol.ErrorResponse
import javax.ws.rs.core.Response.ResponseBuilder
import net.shrine.serialization.I2b2Marshaller
import net.shrine.protocol.BaseShrineRequest
import net.shrine.protocol.HandleableShrineRequest
import net.shrine.protocol.I2b2RequestHandler
import net.shrine.protocol.HandleableI2b2Request
import scala.util.control.NonFatal
import net.shrine.protocol.ResultOutputType
import scala.xml.NodeSeq
/**
* @author Bill Simons
* @since 3/10/11
* @see http://cbmi.med.harvard.edu
* @see http://chip.org
*
* NOTICE: This software comes with NO guarantees whatsoever and is
* licensed as Lgpl Open Source
* @see http://www.gnu.org/licenses/lgpl.html
*/
@Path("/i2b2")
@Produces(Array(MediaType.APPLICATION_XML))
final case class I2b2BroadcastResource(i2b2RequestHandler: I2b2RequestHandler, breakdownTypes: Set[ResultOutputType]) extends Loggable {
//NB: Always broadcast when receiving requests from the legacy i2b2/Shrine webclient, since we can't retrofit it to
//Say whether broadcasting is desired for a praticular query/operation
val shouldBroadcast = true
@POST
@Path("request")
def doRequest(i2b2Request: String): Response = processI2b2Message(i2b2Request)
@POST
@Path("pdorequest")
def doPDORequest(i2b2Request: String): Response = processI2b2Message(i2b2Request)
def processI2b2Message(i2b2Request: String): Response = {
info(s"${this.getClass}.processI2b2Message()")// todo would be good to log $i2b2Request)")
def errorResponse(e: Throwable): ErrorResponse = ErrorResponse(s"Error processing message: ${e.getMessage}: Stack trace follows: ${StackTrace.stackTraceAsString(e)}")
def prettyPrint(xml: NodeSeq): String = XmlUtil.prettyPrint(xml.head).trim
//NB: The legacy webclient can't deal with non-200 status codes.
//It also can't deal with ErrorResponses in several cases, but we have nothing better to return for now.
//TODO: Return a 500 status here, once we're using the new web client
def i2b2HttpErrorResponse(e: Throwable): ResponseBuilder = Response.ok.entity(prettyPrint(errorResponse(e).toI2b2))
def handleRequest(shrineRequest: ShrineRequest with HandleableI2b2Request): Try[ResponseBuilder] = Try {
info(s"Running request from user: ${shrineRequest.authn.username} of type ${shrineRequest.requestType.toString}")
val shrineResponse = shrineRequest.handleI2b2(i2b2RequestHandler, shouldBroadcast)
//TODO: Revisit this. For now, we bail if we get something that isn't i2b2able
val responseString: String = shrineResponse match {
case i2b2able: I2b2Marshaller => prettyPrint(i2b2able.toI2b2)
case _ => throw new Exception(s"Shrine response $shrineResponse has no i2b2 representation")
}
Response.ok.entity(responseString)
}.recover {
case NonFatal(e) => {
error("Error processing request: ", e)
i2b2HttpErrorResponse(e)
}
}
def handleParseError(e: Throwable): Try[ResponseBuilder] = Try {
debug(s"Failed to unmarshal i2b2 request.")//todo would be good to log : $i2b2Request")
error("Couldn't understand request: ", e)
//NB: The legacy webclient can't deal with non-200 status codes.
//It also can't deal with ErrorResponses in several cases, but we have nothing better to return for now.
//TODO: Return a 400 status here, once we're using the new web client
i2b2HttpErrorResponse(e)
}
val builder = HandleableI2b2Request.fromI2b2String(breakdownTypes)(i2b2Request).transform(handleRequest, handleParseError).get
builder.build()
}
}
\ No newline at end of file
diff --git a/qep/service/src/main/scala/net/shrine/service/I2b2BroadcastService.scala b/qep/service/src/main/scala/net/shrine/qep/I2b2QepService.scala
similarity index 78%
rename from qep/service/src/main/scala/net/shrine/service/I2b2BroadcastService.scala
rename to qep/service/src/main/scala/net/shrine/qep/I2b2QepService.scala
index 280487533..89207f3d4 100644
--- a/qep/service/src/main/scala/net/shrine/service/I2b2BroadcastService.scala
+++ b/qep/service/src/main/scala/net/shrine/qep/I2b2QepService.scala
@@ -1,73 +1,62 @@
-package net.shrine.service
+package net.shrine.qep
import net.shrine.protocol.I2b2RequestHandler
import net.shrine.protocol.DeleteQueryRequest
-import net.shrine.aggregation.ReadPreviousQueriesAggregator
-import net.shrine.aggregation.DeleteQueryAggregator
-import net.shrine.aggregation.ReadTranslatedQueryDefinitionAggregator
-import net.shrine.aggregation.ReadQueryResultAggregator
-import net.shrine.aggregation.ReadPdoResponseAggregator
-import net.shrine.aggregation.ReadQueryDefinitionAggregator
-import net.shrine.aggregation.RenameQueryAggregator
-import net.shrine.aggregation.ReadInstanceResultsAggregator
-import net.shrine.protocol.BaseShrineResponse
import net.shrine.protocol.ReadApprovedQueryTopicsRequest
import net.shrine.protocol.ReadInstanceResultsRequest
import net.shrine.protocol.ReadPreviousQueriesRequest
import net.shrine.protocol.RenameQueryRequest
import net.shrine.protocol.RunQueryRequest
import net.shrine.protocol.ReadPdoRequest
import net.shrine.protocol.ReadQueryDefinitionRequest
import net.shrine.protocol.ReadQueryInstancesRequest
-import net.shrine.protocol.ReadQueryResultRequest
import net.shrine.protocol.ShrineResponse
-import net.shrine.service.dao.AuditDao
+import net.shrine.qep.dao.AuditDao
import net.shrine.authentication.Authenticator
import net.shrine.authorization.QueryAuthorizationService
import net.shrine.broadcaster.BroadcastAndAggregationService
import scala.concurrent.duration.Duration
import net.shrine.protocol.FlagQueryRequest
-import net.shrine.aggregation.FlagQueryAggregator
import net.shrine.protocol.UnFlagQueryRequest
import net.shrine.protocol.ReadResultOutputTypesRequest
import net.shrine.protocol.ResultOutputType
/**
* @author clint
* @since Feb 19, 2014
*/
-final case class I2b2BroadcastService(
+final case class I2b2QepService(
commonName:String,
auditDao: AuditDao,
authenticator: Authenticator,
authorizationService: QueryAuthorizationService,
includeAggregateResult: Boolean,
broadcastAndAggregationService: BroadcastAndAggregationService,
queryTimeout: Duration,
breakdownTypes: Set[ResultOutputType],
- collectQepAudit:Boolean) extends AbstractShrineService[ShrineResponse] with I2b2RequestHandler {
+ collectQepAudit:Boolean) extends AbstractQepService[ShrineResponse] with I2b2RequestHandler {
override def readResultOutputTypes(request: ReadResultOutputTypesRequest): ShrineResponse = doReadResultOutputTypes(request)
override def runQuery(request: RunQueryRequest, shouldBroadcast: Boolean): ShrineResponse = doRunQuery(request, shouldBroadcast)
override def readQueryDefinition(request: ReadQueryDefinitionRequest, shouldBroadcast: Boolean) = doReadQueryDefinition(request, shouldBroadcast)
override def readPdo(request: ReadPdoRequest, shouldBroadcast: Boolean) = doReadPdo(request, shouldBroadcast)
override def readInstanceResults(request: ReadInstanceResultsRequest, shouldBroadcast: Boolean) = doReadInstanceResults(request, shouldBroadcast)
override def readQueryInstances(request: ReadQueryInstancesRequest, shouldBroadcast: Boolean) = doReadQueryInstances(request, shouldBroadcast)
override def readPreviousQueries(request: ReadPreviousQueriesRequest, shouldBroadcast: Boolean) = doReadPreviousQueries(request, shouldBroadcast)
override def renameQuery(request: RenameQueryRequest, shouldBroadcast: Boolean) = doRenameQuery(request, shouldBroadcast)
override def deleteQuery(request: DeleteQueryRequest, shouldBroadcast: Boolean) = doDeleteQuery(request, shouldBroadcast)
override def readApprovedQueryTopics(request: ReadApprovedQueryTopicsRequest, shouldBroadcast: Boolean) = doReadApprovedQueryTopics(request, shouldBroadcast)
override def flagQuery(request: FlagQueryRequest, shouldBroadcast: Boolean = true) = doFlagQuery(request, shouldBroadcast)
override def unFlagQuery(request: UnFlagQueryRequest, shouldBroadcast: Boolean = true) = doUnFlagQuery(request, shouldBroadcast)
}
\ No newline at end of file
diff --git a/qep/service/src/main/scala/net/shrine/service/QepConfig.scala b/qep/service/src/main/scala/net/shrine/qep/QepConfig.scala
similarity index 99%
rename from qep/service/src/main/scala/net/shrine/service/QepConfig.scala
rename to qep/service/src/main/scala/net/shrine/qep/QepConfig.scala
index 5db5f03e6..37e29a839 100644
--- a/qep/service/src/main/scala/net/shrine/service/QepConfig.scala
+++ b/qep/service/src/main/scala/net/shrine/qep/QepConfig.scala
@@ -1,85 +1,85 @@
-package net.shrine.service
+package net.shrine.qep
import com.typesafe.config.Config
import net.shrine.authentication.AuthenticationType
import net.shrine.authorization.AuthorizationType
import net.shrine.authorization.steward.StewardConfig
import net.shrine.client.EndpointConfig
import net.shrine.config.{DurationConfigParser, Keys,ConfigExtensions}
import net.shrine.crypto.SigningCertStrategy
import net.shrine.log.Log
import net.shrine.protocol.CredentialConfig
import scala.concurrent.duration.Duration
/**
* @author clint
* @since Feb 28, 2014
*/
final case class QepConfig (
authenticationType: AuthenticationType,
authorizationType: AuthorizationType,
//NB: optional, only needed for HMS
sheriffEndpoint: Option[EndpointConfig],
//NB: optional, only needed for HMS
sheriffCredentials: Option[CredentialConfig],
//steward config, only needed for a data steward
stewardConfig: Option[StewardConfig],
includeAggregateResults: Boolean,
maxQueryWaitTime: Duration,
broadcasterServiceEndpoint: Option[EndpointConfig],
signingCertStrategy: SigningCertStrategy,
collectQepAudit:Boolean) {
Log.debug(s"QepConfig collectQepAudit is $collectQepAudit")
def broadcasterIsLocal: Boolean = broadcasterServiceEndpoint.isEmpty
}
object QepConfig {
val defaultAuthenticationType: AuthenticationType = AuthenticationType.Pm
val defaultAuthorizationType: AuthorizationType = AuthorizationType.NoAuthorization
def apply(config: Config): QepConfig = {
import Keys._
QepConfig(
//todo put these default values in reference.conf if you decide to use one
optionalAuthenticationType(authenticationType,config).getOrElse(defaultAuthenticationType),
optionalAuthorizationType(authorizationType,config).getOrElse(defaultAuthorizationType),
endpointOption(sheriffEndpoint,config),
credentialsOption(sheriffCredentials,config),
stewardOption(shrineSteward,config),
config.getBoolean(includeAggregateResults),
DurationConfigParser(config.getConfig(maxQueryWaitTime)),
endpointOption(broadcasterServiceEndpoint,config),
signingCertAttachmentStrategy(attachSigningCert,config),
//todo change to shrine.queryEntryPoint...
QepConfigSource.config.getBoolean("shrine.queryEntryPoint.audit.collectQepAudit")
)
}
def optionalAuthorizationType(k: String,config: Config): Option[AuthorizationType] = {
config.getOption(k,_.getString).flatMap(AuthorizationType.valueOf)
}
def optionalAuthenticationType(k: String,config: Config): Option[AuthenticationType] = {
config.getOption(k,_.getString).flatMap(AuthenticationType.valueOf)
}
def signingCertAttachmentStrategy(k: String,config: Config): SigningCertStrategy = {
val attachSigningCerts: Boolean = config.getOption(k, _.getBoolean).getOrElse(false)
import SigningCertStrategy._
if(attachSigningCerts) Attach else DontAttach
}
def stewardOption(k: String,config: Config): Option[StewardConfig] = config.getOptionConfigured(k, StewardConfig(_))
def credentialsOption(k: String,config: Config):Option[CredentialConfig] = config.getOptionConfigured(k, CredentialConfig(_))
def endpointOption(k: String,config: Config): Option[EndpointConfig] = config.getOptionConfigured(k, EndpointConfig(_))
}
diff --git a/qep/service/src/main/scala/net/shrine/service/QepConfigSource.scala b/qep/service/src/main/scala/net/shrine/qep/QepConfigSource.scala
similarity index 97%
rename from qep/service/src/main/scala/net/shrine/service/QepConfigSource.scala
rename to qep/service/src/main/scala/net/shrine/qep/QepConfigSource.scala
index 24458c202..9d4789fdd 100644
--- a/qep/service/src/main/scala/net/shrine/service/QepConfigSource.scala
+++ b/qep/service/src/main/scala/net/shrine/qep/QepConfigSource.scala
@@ -1,39 +1,39 @@
-package net.shrine.service
+package net.shrine.qep
import com.typesafe.config.{Config, ConfigFactory}
import net.shrine.config.AtomicConfigSource
import net.shrine.log.Log
/**
* Source of config for the Qep. Put new config fields here, not in QepConfig, to enable Config-based apply() methods.
*
* @author david
* @since 8/18/15
*/
object QepConfigSource {
val atomicConfig = new AtomicConfigSource(ConfigFactory.load("shrine"))
def config:Config = {
atomicConfig.config
}
Log.debug(s"shrine.queryEntryPoint.audit.collectQepAudit is ${config.getBoolean("shrine.queryEntryPoint.audit.collectQepAudit")}")
def configForBlock[T](key:String,value:AnyRef,origin:String)(block: => T):T = {
atomicConfig.configForBlock(key,value,origin)(block)
}
//todo move this to common code somewhere
def objectForName[T](objectName:String):T = {
import scala.reflect.runtime.universe
val runtimeMirror = universe.runtimeMirror(getClass.getClassLoader)
val module = runtimeMirror.staticModule(objectName)
val reflectedObj = runtimeMirror.reflectModule(module)
val obj = reflectedObj.instance
obj.asInstanceOf[T]
}
}
\ No newline at end of file
diff --git a/qep/service/src/main/scala/net/shrine/service/ShrineService.scala b/qep/service/src/main/scala/net/shrine/qep/QepService.scala
similarity index 93%
rename from qep/service/src/main/scala/net/shrine/service/ShrineService.scala
rename to qep/service/src/main/scala/net/shrine/qep/QepService.scala
index 53f72992b..10b2c6ecc 100644
--- a/qep/service/src/main/scala/net/shrine/service/ShrineService.scala
+++ b/qep/service/src/main/scala/net/shrine/qep/QepService.scala
@@ -1,65 +1,64 @@
-package net.shrine.service
+package net.shrine.qep
-import com.typesafe.config.Config
import net.shrine.aggregation.{ReadQueryResultAggregator, ReadTranslatedQueryDefinitionAggregator}
import net.shrine.authentication.Authenticator
import net.shrine.authorization.QueryAuthorizationService
import net.shrine.broadcaster.BroadcastAndAggregationService
import net.shrine.protocol.{BaseShrineResponse, DeleteQueryRequest, FlagQueryRequest, ReadApprovedQueryTopicsRequest, ReadInstanceResultsRequest, ReadPdoRequest, ReadPreviousQueriesRequest, ReadQueryDefinitionRequest, ReadQueryInstancesRequest, ReadQueryResultRequest, ReadTranslatedQueryDefinitionRequest, RenameQueryRequest, ResultOutputType, RunQueryRequest, ShrineRequestHandler, UnFlagQueryRequest}
-import net.shrine.service.dao.AuditDao
+import net.shrine.qep.dao.AuditDao
import scala.concurrent.duration.Duration
/**
* @author Bill Simons
* @since 3/23/11
* @see http://cbmi.med.harvard.edu
* @see http://chip.org
*
* NOTICE: This software comes with NO guarantees whatsoever and is
* licensed as Lgpl Open Source
* @see http://www.gnu.org/licenses/lgpl.html
*/
-final case class ShrineService(
+final case class QepService(
commonName:String,
auditDao: AuditDao,
authenticator: Authenticator,
authorizationService: QueryAuthorizationService,
includeAggregateResult: Boolean,
broadcastAndAggregationService: BroadcastAndAggregationService,
queryTimeout: Duration,
breakdownTypes: Set[ResultOutputType],
- collectQepAudit:Boolean) extends AbstractShrineService[BaseShrineResponse] with ShrineRequestHandler {
+ collectQepAudit:Boolean) extends AbstractQepService[BaseShrineResponse] with ShrineRequestHandler {
debug(s"ShrineService collectQepAudit is $collectQepAudit")
override def flagQuery(request: FlagQueryRequest, shouldBroadcast: Boolean = true) = doFlagQuery(request, shouldBroadcast)
override def unFlagQuery(request: UnFlagQueryRequest, shouldBroadcast: Boolean = true) = doUnFlagQuery(request, shouldBroadcast)
override def readTranslatedQueryDefinition(request: ReadTranslatedQueryDefinitionRequest, shouldBroadcast: Boolean = true): BaseShrineResponse = {
doBroadcastQuery(request, new ReadTranslatedQueryDefinitionAggregator, shouldBroadcast)
}
override def runQuery(request: RunQueryRequest, shouldBroadcast: Boolean) = doRunQuery(request, shouldBroadcast)
override def readQueryDefinition(request: ReadQueryDefinitionRequest, shouldBroadcast: Boolean) = doReadQueryDefinition(request, shouldBroadcast)
override def readPdo(request: ReadPdoRequest, shouldBroadcast: Boolean) = doReadPdo(request, shouldBroadcast)
override def readInstanceResults(request: ReadInstanceResultsRequest, shouldBroadcast: Boolean) = doReadInstanceResults(request, shouldBroadcast)
override def readQueryInstances(request: ReadQueryInstancesRequest, shouldBroadcast: Boolean) = doReadQueryInstances(request, shouldBroadcast)
override def readPreviousQueries(request: ReadPreviousQueriesRequest, shouldBroadcast: Boolean) = doReadPreviousQueries(request, shouldBroadcast)
override def renameQuery(request: RenameQueryRequest, shouldBroadcast: Boolean) = doRenameQuery(request, shouldBroadcast)
override def deleteQuery(request: DeleteQueryRequest, shouldBroadcast: Boolean) = doDeleteQuery(request, shouldBroadcast)
override def readApprovedQueryTopics(request: ReadApprovedQueryTopicsRequest, shouldBroadcast: Boolean) = doReadApprovedQueryTopics(request, shouldBroadcast)
override def readQueryResult(request: ReadQueryResultRequest, shouldBroadcast: Boolean) = {
doBroadcastQuery(request, new ReadQueryResultAggregator(request.queryId, includeAggregateResult), shouldBroadcast)
}
}
\ No newline at end of file
diff --git a/qep/service/src/main/scala/net/shrine/service/ShrineResource.scala b/qep/service/src/main/scala/net/shrine/qep/ShrineResource.scala
similarity index 98%
rename from qep/service/src/main/scala/net/shrine/service/ShrineResource.scala
rename to qep/service/src/main/scala/net/shrine/qep/ShrineResource.scala
index 1104f92d6..5dc61b430 100644
--- a/qep/service/src/main/scala/net/shrine/service/ShrineResource.scala
+++ b/qep/service/src/main/scala/net/shrine/qep/ShrineResource.scala
@@ -1,257 +1,257 @@
-package net.shrine.service
+package net.shrine.qep
import javax.ws.rs.Consumes
import javax.ws.rs.DELETE
import javax.ws.rs.GET
import javax.ws.rs.HeaderParam
import javax.ws.rs.POST
import javax.ws.rs.Path
import javax.ws.rs.PathParam
import javax.ws.rs.Produces
import javax.ws.rs.QueryParam
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
import net.shrine.log.Loggable
import net.shrine.protocol.AuthenticationInfo
import net.shrine.protocol.DeleteQueryRequest
import net.shrine.protocol.OutputTypeSet
import net.shrine.protocol.ReadApprovedQueryTopicsRequest
import net.shrine.protocol.ReadInstanceResultsRequest
import net.shrine.protocol.ReadPdoRequest
import net.shrine.protocol.ReadPreviousQueriesRequest
import net.shrine.protocol.ReadQueryDefinitionRequest
import net.shrine.protocol.ReadQueryInstancesRequest
import net.shrine.protocol.ReadQueryResultRequest
import net.shrine.protocol.RenameQueryRequest
import net.shrine.protocol.RunQueryRequest
import net.shrine.protocol.ShrineRequestHandler
import net.shrine.protocol.query.QueryDefinition
import scala.xml.XML
import net.shrine.protocol.BaseShrineResponse
import net.shrine.protocol.ReadTranslatedQueryDefinitionRequest
import net.shrine.protocol.FlagQueryRequest
import net.shrine.protocol.UnFlagQueryRequest
/**
* @author Bill Simons
* @author Clint Gilbert
* @since 8/30/11
* @see http://cbmi.med.harvard.edu
* @see http://chip.org
*
* NOTICE: This software comes with NO guarantees whatsoever and is
* licensed as Lgpl Open Source
* @see http://www.gnu.org/licenses/lgpl.html
*/
@Path("/shrine")
@Produces(Array(MediaType.APPLICATION_XML)) //NB: Is a case class to get apply on the companion object, for smoother testing
final case class ShrineResource(shrineRequestHandler: ShrineRequestHandler) extends Loggable {
import ShrineResource.waitTime
@Consumes(Array(MediaType.TEXT_PLAIN))
@POST
@Path("/queries/{queryId}/flag")
def flagQuery(
@HeaderParam("projectId") projectId: String,
//authorization will be constructed by JAXRS using the String value of the 'Authorization' header
@HeaderParam("Authorization") authorization: AuthenticationInfo,
@PathParam("queryId") networkQueryId: Long,
@HeaderParam("shouldBroadcast") shouldBroadcast: Boolean,
flagMessage: String): String = {
-
- val flagMessageOption = Option(flagMessage).filter(!_.trim.isEmpty)
+
+ val flagMessageOption: Option[String] = Option(flagMessage).filter(!_.trim.isEmpty)
//TODO: What should we return, if anything?
performAndSerialize(_.flagQuery(FlagQueryRequest(projectId, waitTime, authorization, networkQueryId, flagMessageOption), shouldBroadcast))
}
@POST
@Path("/queries/{queryId}/unflag")
def unFlagQuery(
@HeaderParam("projectId") projectId: String,
//authorization will be constructed by JAXRS using the String value of the 'Authorization' header
@HeaderParam("Authorization") authorization: AuthenticationInfo,
@PathParam("queryId") networkQueryId: Long,
@HeaderParam("shouldBroadcast") shouldBroadcast: Boolean): String = {
//TODO: What should we return, if anything?
performAndSerialize(_.unFlagQuery(UnFlagQueryRequest(projectId, waitTime, authorization, networkQueryId), shouldBroadcast))
}
@GET
@Path("{userId}/approved-topics")
def readApprovedQueryTopics(
@HeaderParam("projectId") projectId: String,
//authorization will be constructed by JAXRS using the String value of the 'Authorization' header
@HeaderParam("Authorization") authorization: AuthenticationInfo,
@PathParam("userId") userId: String,
@HeaderParam("shouldBroadcast") shouldBroadcast: Boolean): String = {
performAndSerialize(_.readApprovedQueryTopics(ReadApprovedQueryTopicsRequest(projectId, waitTime, authorization, userId), shouldBroadcast))
}
@GET
@Path("{userId}/queries")
def readPreviousQueries(
@HeaderParam("projectId") projectId: String,
//authorization will be constructed by JAXRS using the String value of the 'Authorization' header
@HeaderParam("Authorization") authorization: AuthenticationInfo,
@PathParam("userId") userId: String,
@QueryParam("fetchSize") fetchSize: Int,
@HeaderParam("shouldBroadcast") shouldBroadcast: Boolean): Response = {
if (userId != authorization.username) {
Response.status(403).build
} else {
val fSize = if (fetchSize != 0) fetchSize else 20
Response.ok.entity {
performAndSerialize(_.readPreviousQueries(ReadPreviousQueriesRequest(projectId, waitTime, authorization, userId, fSize), shouldBroadcast))
}.build
}
}
@POST
@Path("/queries")
@Consumes(Array(MediaType.APPLICATION_XML))
def runQuery(
@HeaderParam("projectId") projectId: String,
//authorization will be constructed by JAXRS using the String value of the 'Authorization' header
@HeaderParam("Authorization") authorization: AuthenticationInfo,
@HeaderParam("topicId") topicId: String,
@HeaderParam("topicName") topicName: String,
//outputTypes will be constructed by JAXRS using the String value of the 'outputTypes' header
@HeaderParam("outputTypes") outputTypes: OutputTypeSet,
queryDefinitionXml: String,
@HeaderParam("shouldBroadcast") shouldBroadcast: Boolean): String = {
val queryDef = QueryDefinition.fromXml(queryDefinitionXml).get
val topicIdOption = Option(topicId).filter(!_.trim.isEmpty)
val topicNameOption = Option(topicName).filter(!_.trim.isEmpty)
debug(s"runQuery() with $shrineRequestHandler and $queryDef")
performAndSerialize(_.runQuery(RunQueryRequest(projectId, waitTime, authorization, topicIdOption, topicNameOption, outputTypes.toSet, queryDef), shouldBroadcast))
}
@GET
@Path("/queries/{queryId}/instances")
def readQueryInstances(
@HeaderParam("projectId") projectId: String,
//authorization will be constructed by JAXRS using the String value of the 'Authorization' header
@HeaderParam("Authorization") authorization: AuthenticationInfo,
@PathParam("queryId") queryId: Long,
@HeaderParam("shouldBroadcast") shouldBroadcast: Boolean): String = {
performAndSerialize(_.readQueryInstances(ReadQueryInstancesRequest(projectId, waitTime, authorization, queryId), shouldBroadcast))
}
@GET
@Path("/instances/{instanceId}/results")
def readInstanceResults(
@HeaderParam("projectId") projectId: String,
//authorization will be constructed by JAXRS using the String value of the 'Authorization' header
@HeaderParam("Authorization") authorization: AuthenticationInfo,
@PathParam("instanceId") instanceId: Long,
@HeaderParam("shouldBroadcast") shouldBroadcast: Boolean): String = {
performAndSerialize(_.readInstanceResults(ReadInstanceResultsRequest(projectId, waitTime, authorization, instanceId), shouldBroadcast))
}
@POST //This must be POST, since we're sending content in the request body
@Path("/patient-set/{patientSetCollId}")
@Consumes(Array(MediaType.APPLICATION_XML))
def readPdo(
@HeaderParam("projectId") projectId: String,
//authorization will be constructed by JAXRS using the String value of the 'Authorization' header
@HeaderParam("Authorization") authorization: AuthenticationInfo,
@PathParam("patientSetCollId") patientSetCollId: String,
optionsXml: String,
@HeaderParam("shouldBroadcast") shouldBroadcast: Boolean): String = {
import XML.loadString
performAndSerialize(_.readPdo(ReadPdoRequest(projectId, waitTime, authorization, patientSetCollId, loadString(optionsXml)), shouldBroadcast))
}
@GET
@Path("/queries/{queryId}")
def readQueryDefinition(
@HeaderParam("projectId") projectId: String,
//authorization will be constructed by JAXRS using the String value of the 'Authorization' header
@HeaderParam("Authorization") authorization: AuthenticationInfo,
@PathParam("queryId") queryId: Long,
@HeaderParam("shouldBroadcast") shouldBroadcast: Boolean): String = {
performAndSerialize(_.readQueryDefinition(ReadQueryDefinitionRequest(projectId, waitTime, authorization, queryId), shouldBroadcast))
}
@DELETE
@Path("/queries/{queryId}")
def deleteQuery(
@HeaderParam("projectId") projectId: String,
//authorization will be constructed by JAXRS using the String value of the 'Authorization' header
@HeaderParam("Authorization") authorization: AuthenticationInfo,
@PathParam("queryId") queryId: Long,
@HeaderParam("shouldBroadcast") shouldBroadcast: Boolean): String = {
performAndSerialize(_.deleteQuery(DeleteQueryRequest(projectId, waitTime, authorization, queryId), shouldBroadcast))
}
@POST
@Path("/queries/{queryId}/name")
@Consumes(Array(MediaType.TEXT_PLAIN))
def renameQuery(
@HeaderParam("projectId") projectId: String,
//authorization will be constructed by JAXRS using the String value of the 'Authorization' header
@HeaderParam("Authorization") authorization: AuthenticationInfo,
@PathParam("queryId") queryId: Long,
queryName: String,
@HeaderParam("shouldBroadcast") shouldBroadcast: Boolean): String = {
performAndSerialize(_.renameQuery(RenameQueryRequest(projectId, waitTime, authorization, queryId, queryName), shouldBroadcast))
}
@GET
@Path("/queries/{queryId}/results")
@Consumes(Array(MediaType.TEXT_PLAIN))
def readQueryResults(
@HeaderParam("projectId") projectId: String,
//authorization will be constructed by JAXRS using the String value of the 'Authorization' header
@HeaderParam("Authorization") authorization: AuthenticationInfo,
@PathParam("queryId") queryId: Long,
@HeaderParam("shouldBroadcast") shouldBroadcast: Boolean): String = {
performAndSerialize(_.readQueryResult(ReadQueryResultRequest(projectId, waitTime, authorization, queryId), shouldBroadcast))
}
@POST
@Path("/queries/translated")
@Consumes(Array(MediaType.APPLICATION_XML))
def readTranslatedQueryDefinition(
@HeaderParam("projectId") projectId: String,
//authorization will be constructed by JAXRS using the String value of the 'Authorization' header
@HeaderParam("Authorization") authorization: AuthenticationInfo,
@HeaderParam("shouldBroadcast") shouldBroadcast: Boolean,
queryDefinitionXml: String): String = {
val queryDef = QueryDefinition.fromXml(queryDefinitionXml).get
//NB: Create the RunQueryRequest with a dummy networkQueryId of '-1';
//this will be filled in with an appropriately-generated value by the ShrineRequestHandler
performAndSerialize(_.readTranslatedQueryDefinition(ReadTranslatedQueryDefinitionRequest(authorization, waitTime, queryDef), shouldBroadcast))
}
private def performAndSerialize[R <: BaseShrineResponse](op: ShrineRequestHandler => R): String = {
op(shrineRequestHandler).toXmlString
}
}
//NB: extends ShrineRequestHandler => ShrineResource for smoother testing syntax
object ShrineResource extends (ShrineRequestHandler => ShrineResource) {
import scala.concurrent.duration._
val waitTime = 10.seconds
}
diff --git a/qep/service/src/main/scala/net/shrine/service/audit/QepAuditDb.scala b/qep/service/src/main/scala/net/shrine/qep/audit/QepAuditDb.scala
similarity index 71%
rename from qep/service/src/main/scala/net/shrine/service/audit/QepAuditDb.scala
rename to qep/service/src/main/scala/net/shrine/qep/audit/QepAuditDb.scala
index ee6302eb4..4be710634 100644
--- a/qep/service/src/main/scala/net/shrine/service/audit/QepAuditDb.scala
+++ b/qep/service/src/main/scala/net/shrine/qep/audit/QepAuditDb.scala
@@ -1,227 +1,180 @@
-package net.shrine.service.audit
+package net.shrine.qep.audit
-import java.io.PrintWriter
-import java.sql.{DriverManager, Connection, SQLException}
-import java.util.logging.Logger
-import javax.naming.InitialContext
+import java.sql.SQLException
import javax.sql.DataSource
import com.typesafe.config.Config
+import net.shrine.audit.{NetworkQueryId, QueryName, QueryTopicId, QueryTopicName, ShrineNodeId, Time, UserName}
import net.shrine.log.Loggable
import net.shrine.protocol.RunQueryRequest
-import net.shrine.service.QepConfigSource
-import net.shrine.audit.{QueryTopicName, QueryTopicId, Time, QueryName, NetworkQueryId, UserName, ShrineNodeId}
-
+import net.shrine.qep.QepConfigSource
+import net.shrine.slick.TestableDataSourceCreator
import slick.driver.JdbcProfile
-import scala.concurrent.{Future, Await}
-import scala.concurrent.duration.{Duration,DurationInt}
-import scala.language.postfixOps
-
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.blocking
+import scala.concurrent.duration.{Duration, DurationInt}
+import scala.concurrent.{Await, Future, blocking}
+import scala.language.postfixOps
/**
* DB code for the QEP audit metrics.
*
* @author david
* @since 8/18/15
*/
case class QepAuditDb(schemaDef:QepAuditSchema,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 insertQepQuery(runQueryRequest:RunQueryRequest,commonName:String):Unit = {
debug(s"insertQepQuery $runQueryRequest")
insertQepQuery(QepQueryAuditData.fromRunQueryRequest(runQueryRequest,commonName))
}
def insertQepQuery(qepQueryAuditData: QepQueryAuditData):Unit = {
dbRun(allQepQueryQuery += qepQueryAuditData)
}
def selectAllQepQueries:Seq[QepQueryAuditData] = {
dbRun(allQepQueryQuery.result)
}
}
object QepAuditDb extends Loggable {
- val dataSource:DataSource = {
-
- val dataSourceFrom = QepAuditSchema.config.getString("dataSourceFrom")
- if(dataSourceFrom == "JNDI") {
- val jndiDataSourceName = QepAuditSchema.config.getString("jndiDataSourceName")
- val initialContext:InitialContext = new InitialContext()
-
- initialContext.lookup(jndiDataSourceName).asInstanceOf[DataSource]
-
- }
- else if (dataSourceFrom == "testDataSource") {
-
- val testDataSourceConfig = QepAuditSchema.config.getConfig("testDataSource")
- val driverClassName = testDataSourceConfig.getString("driverClassName")
- val url = testDataSourceConfig.getString("url")
-
- //Creating an instance of the driver register it. (!) From a previous epoch, but it works.
- Class.forName(driverClassName).newInstance()
-
- object TestDataSource extends DataSource {
- override def getConnection: Connection = {
- DriverManager.getConnection(url)
- }
-
- override def getConnection(username: String, password: String): Connection = {
- DriverManager.getConnection(url, username, password)
- }
-
- //unused methods
- override def unwrap[T](iface: Class[T]): T = ???
- override def isWrapperFor(iface: Class[_]): Boolean = ???
- override def setLogWriter(out: PrintWriter): Unit = ???
- override def getLoginTimeout: Int = ???
- override def setLoginTimeout(seconds: Int): Unit = ???
- override def getParentLogger: Logger = ???
- override def getLogWriter: PrintWriter = ???
- }
-
- TestDataSource
- }
- else throw new IllegalArgumentException(s"shrine.steward.database.dataSourceFrom must be either JNDI or testDataSource, not $dataSourceFrom")
- }
+ val dataSource:DataSource = TestableDataSourceCreator.dataSource(QepAuditSchema.config)
val db = QepAuditDb(QepAuditSchema.schema,dataSource)
val createTablesOnStart = QepAuditSchema.config.getBoolean("createTablesOnStart")
if(createTablesOnStart) QepAuditDb.db.createTables()
}
/**
* Separate class to support schema generation without actually connecting to the database.
*
* @param jdbcProfile Database profile to use for the schema
*/
case class QepAuditSchema(jdbcProfile: JdbcProfile) extends Loggable {
import jdbcProfile.api._
def ddlForAllTables = {
allQepQueryQuery.schema
}
//to get the schema, use the REPL
//println(QepAuditSchema.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 QueriesSent(tag:Tag) extends Table[QepQueryAuditData](tag,"queriesSent") {
def shrineNodeId = column[ShrineNodeId]("shrineNodeId")
def userName = column[UserName]("userName")
def networkQueryId = column[NetworkQueryId]("networkQueryId")
def queryName = column[QueryName]("queryName")
def queryTopicId = column[Option[QueryTopicId]]("queryTopicId")
def queryTopicName = column[Option[QueryTopicName]]("queryTopicName")
def timeQuerySent = column[Time]("timeQuerySent")
def * = (shrineNodeId,userName,networkQueryId,queryName,queryTopicId,queryTopicName,timeQuerySent) <> (QepQueryAuditData.tupled,QepQueryAuditData.unapply)
}
val allQepQueryQuery = TableQuery[QueriesSent]
}
object QepAuditSchema {
val allConfig:Config = QepConfigSource.config
val config:Config = allConfig.getConfig("shrine.queryEntryPoint.audit.database")
val slickProfileClassName = config.getString("slickProfileClassName")
val slickProfile:JdbcProfile = QepConfigSource.objectForName(slickProfileClassName)
val schema = QepAuditSchema(slickProfile)
}
/**
* Container for QEP audit data for ACT metrics
*
* @author david
* @since 8/17/15
*/
case class QepQueryAuditData(
shrineNodeId:ShrineNodeId,
userName:UserName,
networkQueryId:NetworkQueryId,
queryName:QueryName,
queryTopicId:Option[QueryTopicId],
queryTopicName:Option[QueryTopicName],
timeQuerySent:Time
) {}
object QepQueryAuditData extends ((
ShrineNodeId,
UserName,
NetworkQueryId,
QueryName,
Option[QueryTopicId],
Option[QueryTopicName],
Time
) => QepQueryAuditData) {
def apply(
shrineNodeId:String,
userName:String,
networkQueryId:Long,
queryName:String,
queryTopicId:Option[String],
queryTopicName: Option[QueryTopicName]
):QepQueryAuditData = QepQueryAuditData(
shrineNodeId,
userName,
networkQueryId,
queryName,
queryTopicId,
queryTopicName,
System.currentTimeMillis()
)
def fromRunQueryRequest(request:RunQueryRequest,commonName:String):QepQueryAuditData = {
QepQueryAuditData(
commonName,
request.authn.username,
request.networkQueryId,
request.queryDefinition.name,
request.topicId,
request.topicName
)
}
}
\ No newline at end of file
diff --git a/qep/service/src/main/scala/net/shrine/service/audit/package.scala b/qep/service/src/main/scala/net/shrine/qep/audit/package.scala
similarity index 80%
rename from qep/service/src/main/scala/net/shrine/service/audit/package.scala
rename to qep/service/src/main/scala/net/shrine/qep/audit/package.scala
index fefffb0d1..1712185ce 100644
--- a/qep/service/src/main/scala/net/shrine/service/audit/package.scala
+++ b/qep/service/src/main/scala/net/shrine/qep/audit/package.scala
@@ -1,9 +1,9 @@
-package net.shrine.service
+package net.shrine.qep
/**
* Audit code for capturing ACT "metrics".
*
* @author david
* @since 8/17/15
*/
package object audit {}
diff --git a/qep/service/src/main/scala/net/shrine/service/dao/AuditDao.scala b/qep/service/src/main/scala/net/shrine/qep/dao/AuditDao.scala
similarity index 89%
rename from qep/service/src/main/scala/net/shrine/service/dao/AuditDao.scala
rename to qep/service/src/main/scala/net/shrine/qep/dao/AuditDao.scala
index f0da86029..411b9cc02 100644
--- a/qep/service/src/main/scala/net/shrine/service/dao/AuditDao.scala
+++ b/qep/service/src/main/scala/net/shrine/qep/dao/AuditDao.scala
@@ -1,35 +1,35 @@
-package net.shrine.service.dao
+package net.shrine.qep.dao
-import net.shrine.service.dao.model.AuditEntry
+import net.shrine.qep.dao.model.AuditEntry
import java.util.Date
/**
* @author ??
* @author Clint Gilbert
* @date ??
* Ported to Scala on Feb 28, 2012
*
* DAO that reads and writes audit entries
*/
trait AuditDao {
def addAuditEntry(
project: String,
domain: String,
username: String,
queryText: String,
queryTopic: Option[String]): Unit = addAuditEntry(new Date, project, domain, username, queryText, queryTopic)
def addAuditEntry(
time: Date,
project: String,
domain: String,
username: String,
queryText: String,
queryTopic: Option[String]): Unit
def findRecentEntries(limit: Int): Seq[AuditEntry]
def inTransaction[T](f: => T): T
}
diff --git a/qep/service/src/main/scala/net/shrine/service/dao/model/AuditEntry.scala b/qep/service/src/main/scala/net/shrine/qep/dao/model/AuditEntry.scala
similarity index 89%
rename from qep/service/src/main/scala/net/shrine/service/dao/model/AuditEntry.scala
rename to qep/service/src/main/scala/net/shrine/qep/dao/model/AuditEntry.scala
index d62580bbb..4a27a8421 100644
--- a/qep/service/src/main/scala/net/shrine/service/dao/model/AuditEntry.scala
+++ b/qep/service/src/main/scala/net/shrine/qep/dao/model/AuditEntry.scala
@@ -1,18 +1,18 @@
-package net.shrine.service.dao.model
+package net.shrine.qep.dao.model
import java.sql.Timestamp
/**
* @author ???
* @author clint
* @date Jan 25, 2013
*/
//NB: Needs to be non-final for Squeryl :\
case class AuditEntry(
id: Long,
project: String,
domain: String,
username: String,
time: Timestamp,
queryText: Option[String],
queryTopic: Option[String])
diff --git a/qep/service/src/main/scala/net/shrine/service/dao/squeryl/SquerylAuditDao.scala b/qep/service/src/main/scala/net/shrine/qep/dao/squeryl/SquerylAuditDao.scala
similarity index 85%
rename from qep/service/src/main/scala/net/shrine/service/dao/squeryl/SquerylAuditDao.scala
rename to qep/service/src/main/scala/net/shrine/qep/dao/squeryl/SquerylAuditDao.scala
index 39b9a26f3..259594f77 100644
--- a/qep/service/src/main/scala/net/shrine/service/dao/squeryl/SquerylAuditDao.scala
+++ b/qep/service/src/main/scala/net/shrine/qep/dao/squeryl/SquerylAuditDao.scala
@@ -1,40 +1,40 @@
-package net.shrine.service.dao.squeryl
+package net.shrine.qep.dao.squeryl
-import net.shrine.service.dao.AuditDao
-import net.shrine.service.dao.squeryl.tables.Tables
+import net.shrine.qep.dao.AuditDao
+import net.shrine.qep.dao.squeryl.tables.Tables
import java.util.Date
import java.sql.Timestamp
-import net.shrine.service.dao.model.AuditEntry
+import net.shrine.qep.dao.model.AuditEntry
import org.squeryl.KeyedEntity
import net.shrine.dao.squeryl.SquerylInitializer
/**
* @author clint
* @date May 21, 2013
*/
final class SquerylAuditDao(initializer: SquerylInitializer, tables: Tables) extends AuditDao {
initializer.init
override def inTransaction[T](f: => T): T = SquerylEntryPoint.inTransaction(f)
import SquerylEntryPoint._
import tables.auditEntries
override def addAuditEntry(time: Date, project: String, domain: String, username: String, queryText: String, queryTopic: Option[String]) {
val timestamp = new Timestamp(time.getTime)
inTransaction {
//NB: Squeryl steers us toward inserting with dummy ids :(
auditEntries.insert(AuditEntry(0L, project, domain, username, timestamp, Option(queryText), queryTopic))
}
}
override def findRecentEntries(limit: Int): Seq[AuditEntry] = {
inTransaction {
from(auditEntries) { entry =>
select(entry).orderBy(entry.time.desc)
}.take(limit).toSeq
}
}
}
\ No newline at end of file
diff --git a/qep/service/src/main/scala/net/shrine/service/dao/squeryl/SquerylEntryPoint.scala b/qep/service/src/main/scala/net/shrine/qep/dao/squeryl/SquerylEntryPoint.scala
similarity index 84%
rename from qep/service/src/main/scala/net/shrine/service/dao/squeryl/SquerylEntryPoint.scala
rename to qep/service/src/main/scala/net/shrine/qep/dao/squeryl/SquerylEntryPoint.scala
index 5e6d78ce8..a42fce1d8 100644
--- a/qep/service/src/main/scala/net/shrine/service/dao/squeryl/SquerylEntryPoint.scala
+++ b/qep/service/src/main/scala/net/shrine/qep/dao/squeryl/SquerylEntryPoint.scala
@@ -1,19 +1,19 @@
-package net.shrine.service.dao.squeryl
+package net.shrine.qep.dao.squeryl
import org.squeryl.PrimitiveTypeMode
-import net.shrine.service.dao.model.AuditEntry
+import net.shrine.qep.dao.model.AuditEntry
import org.squeryl.KeyedEntityDef
/**
* @author clint
* @date May 22, 2013
*/
object SquerylEntryPoint extends PrimitiveTypeMode {
implicit val auditEntryKED: KeyedEntityDef[AuditEntry, Long] = new KeyedEntityDef[AuditEntry, Long] {
override def getId(entry: AuditEntry): Long = entry.id
override def isPersisted(entry: AuditEntry): Boolean = entry.id != 0
override val idPropertyName: String = "id"
}
}
\ No newline at end of file
diff --git a/qep/service/src/main/scala/net/shrine/service/dao/squeryl/tables/AuditEntryComponent.scala b/qep/service/src/main/scala/net/shrine/qep/dao/squeryl/tables/AuditEntryComponent.scala
similarity index 86%
rename from qep/service/src/main/scala/net/shrine/service/dao/squeryl/tables/AuditEntryComponent.scala
rename to qep/service/src/main/scala/net/shrine/qep/dao/squeryl/tables/AuditEntryComponent.scala
index f350710dc..eb6c5f190 100644
--- a/qep/service/src/main/scala/net/shrine/service/dao/squeryl/tables/AuditEntryComponent.scala
+++ b/qep/service/src/main/scala/net/shrine/qep/dao/squeryl/tables/AuditEntryComponent.scala
@@ -1,29 +1,29 @@
-package net.shrine.service.dao.squeryl.tables
+package net.shrine.qep.dao.squeryl.tables
import org.squeryl.Schema
-import net.shrine.service.dao.model.AuditEntry
-import net.shrine.service.dao.squeryl.SquerylEntryPoint
+import net.shrine.qep.dao.model.AuditEntry
+import net.shrine.qep.dao.squeryl.SquerylEntryPoint
import net.shrine.dao.squeryl.tables.AbstractTableComponent
/**
* @author clint
* @date May 21, 2013
*/
trait AuditEntryComponent extends AbstractTableComponent { self: Schema =>
import SquerylEntryPoint._
val auditEntries = table[AuditEntry]("AUDIT_ENTRY")
declareThat(auditEntries) (
_.id is (primaryKey, oracleSafeAutoIncremented("AUDIT_ENTRY_ID"), named("AUDIT_ENTRY_ID")),
_.project is (named("PROJECT")),
_.queryTopic is (named("QUERY_TOPIC")),
_.queryTopic is (named("QUERY_TOPIC")),
_.username is (named("USERNAME")),
_.domain is (named("DOMAIN_NAME")),
//Still can't express non-constant default value for TIME column :(
_.time is (named("TIME")),
_.queryText is (dbType("TEXT"), named("QUERY_TEXT")),
auditEntry => columns(auditEntry.domain, auditEntry.username, auditEntry.queryTopic) are (indexed("IDX_AUDIT_ENTRY_DOMAIN_USERNAME_QUERY_TOPIC"))
)
}
\ No newline at end of file
diff --git a/qep/service/src/main/scala/net/shrine/qep/dao/squeryl/tables/Tables.scala b/qep/service/src/main/scala/net/shrine/qep/dao/squeryl/tables/Tables.scala
new file mode 100644
index 000000000..685c91e11
--- /dev/null
+++ b/qep/service/src/main/scala/net/shrine/qep/dao/squeryl/tables/Tables.scala
@@ -0,0 +1,10 @@
+package net.shrine.qep.dao.squeryl.tables
+
+import org.squeryl.Schema
+import net.shrine.qep.dao.squeryl.SquerylEntryPoint._
+
+/**
+ * @author clint
+ * @date May 21, 2013
+ */
+final class Tables extends Schema with AuditEntryComponent
\ No newline at end of file
diff --git a/qep/service/src/main/scala/net/shrine/qep/queries/QepQueryDb.scala b/qep/service/src/main/scala/net/shrine/qep/queries/QepQueryDb.scala
new file mode 100644
index 000000000..7d04a171d
--- /dev/null
+++ b/qep/service/src/main/scala/net/shrine/qep/queries/QepQueryDb.scala
@@ -0,0 +1,264 @@
+package net.shrine.qep.queries
+
+import java.sql.SQLException
+import java.util.GregorianCalendar
+import javax.sql.DataSource
+import javax.xml.datatype.DatatypeFactory
+
+import com.typesafe.config.Config
+import net.shrine.audit.{NetworkQueryId, QueryName, Time, UserName}
+import net.shrine.log.Loggable
+import net.shrine.protocol.{UnFlagQueryRequest, FlagQueryRequest, QueryMaster, ReadPreviousQueriesRequest, ReadPreviousQueriesResponse, RunQueryRequest}
+import net.shrine.qep.QepConfigSource
+import net.shrine.slick.TestableDataSourceCreator
+import slick.driver.JdbcProfile
+
+import scala.concurrent.duration.{Duration, DurationInt}
+import scala.concurrent.{Await, Future, blocking}
+import scala.language.postfixOps
+
+/**
+ * DB code for the QEP's query instances and query results.
+ *
+ * @author david
+ * @since 1/19/16
+ */
+case class QepQueryDb(schemaDef:QepQuerySchema,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 insertQepQuery(runQueryRequest: RunQueryRequest):Unit = {
+ debug(s"insertQepQuery $runQueryRequest")
+
+ insertQepQuery(QepQuery(runQueryRequest))
+ }
+
+ def insertQepQuery(qepQuery: QepQuery):Unit = {
+ dbRun(allQepQueryQuery += qepQuery)
+ }
+
+ def selectAllQepQueries:Seq[QepQuery] = {
+ dbRun(allQepQueryQuery.result)
+ }
+
+ def selectPreviousQueries(request: ReadPreviousQueriesRequest):ReadPreviousQueriesResponse = {
+ val previousQueries: Seq[QepQuery] = selectPreviousQueriesByUserAndDomain(request.authn.username,request.authn.domain)
+ val flags:Map[NetworkQueryId,QepQueryFlag] = selectMostRecentQepQueryFlagsFor(previousQueries.map(_.networkId).to[Set])
+ val queriesAndFlags = previousQueries.map(x => (x,flags.get(x.networkId)))
+
+ ReadPreviousQueriesResponse(queriesAndFlags.map(x => x._1.toQueryMaster(x._2)))
+ }
+
+ def selectPreviousQueriesByUserAndDomain(userName: UserName,domain: String):Seq[QepQuery] = {
+ dbRun(allQepQueryQuery.filter(_.userName === userName).filter(_.userDomain === domain).result)
+ }
+
+ def insertQepQueryFlag(flagQueryRequest: FlagQueryRequest):Unit = {
+ insertQepQueryFlag(QepQueryFlag(flagQueryRequest))
+ }
+
+ def insertQepQueryFlag(unflagQueryRequest: UnFlagQueryRequest):Unit = {
+ insertQepQueryFlag(QepQueryFlag(unflagQueryRequest))
+ }
+
+ def insertQepQueryFlag(qepQueryFlag: QepQueryFlag):Unit = {
+ debug(s"insertQepQueryFlag $qepQueryFlag")
+ dbRun(allQepQueryFlags += qepQueryFlag)
+ }
+
+ def selectMostRecentQepQueryFlagsFor(networkIds:Set[NetworkQueryId]):Map[NetworkQueryId,QepQueryFlag] = {
+ val flags:Seq[QepQueryFlag] = dbRun(mostRecentQueryFlags.filter(_.networkId inSet networkIds).result)
+
+ flags.map(x => x.networkQueryId -> x).toMap
+ }
+}
+
+object QepQueryDb extends Loggable {
+
+ val dataSource:DataSource = TestableDataSourceCreator.dataSource(QepQuerySchema.config)
+
+ val db = QepQueryDb(QepQuerySchema.schema,dataSource)
+
+ val createTablesOnStart = QepQuerySchema.config.getBoolean("createTablesOnStart")
+ if(createTablesOnStart) QepQueryDb.db.createTables()
+
+}
+
+/**
+ * Separate class to support schema generation without actually connecting to the database.
+ *
+ * @param jdbcProfile Database profile to use for the schema
+ */
+case class QepQuerySchema(jdbcProfile: JdbcProfile) extends Loggable {
+ import jdbcProfile.api._
+
+ def ddlForAllTables: jdbcProfile.DDL = {
+ allQepQueryQuery.schema ++ allQepQueryFlags.schema
+ }
+
+ //to get the schema, use the REPL
+ //println(QepQuerySchema.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)
+ }
+
+ /**
+ * The adapter's query table looks like this:
+ *
+ mysql> describe SHRINE_QUERY;
++------------------+--------------+------+-----+-------------------+----------------+
+| Field | Type | Null | Key | Default | Extra |
++------------------+--------------+------+-----+-------------------+----------------+
+| id | int(11) | NO | PRI | NULL | auto_increment |
+| local_id | varchar(255) | NO | MUL | NULL | |
+| network_id | bigint(20) | NO | MUL | NULL | |
+| username | varchar(255) | NO | MUL | NULL | |
+| domain | varchar(255) | NO | | NULL | |
+| query_name | varchar(255) | NO | | NULL | |
+| query_expression | text | YES | | NULL | |
+| date_created | timestamp | NO | | CURRENT_TIMESTAMP | |
+| has_been_run | tinyint(1) | NO | | 0 | |
+| flagged | tinyint(1) | NO | | 0 | |
+| flag_message | varchar(255) | YES | | NULL | |
+| query_xml | text | YES | | NULL | |
++------------------+--------------+------+-----+-------------------+----------------+
+ */
+
+ class QepQueries(tag:Tag) extends Table[QepQuery](tag,"previousQueries") {
+ def networkId = column[NetworkQueryId]("networkId")
+ def userName = column[UserName]("userName")
+ def userDomain = column[String]("domain")
+ def queryName = column[QueryName]("queryName")
+ def expression = column[String]("expression")
+ def dateCreated = column[Time]("dateCreated")
+ def queryXml = column[String]("queryXml")
+
+ def * = (networkId,userName,userDomain,queryName,expression,dateCreated,queryXml) <> (QepQuery.tupled,QepQuery.unapply)
+
+ }
+
+ val allQepQueryQuery = TableQuery[QepQueries]
+
+ class QepQueryFlags(tag:Tag) extends Table[QepQueryFlag](tag,"queryFlags") {
+ def networkId = column[NetworkQueryId]("networkId")
+ def flagged = column[Boolean]("flagged")
+ def flagMessage = column[String]("flagMessage")
+ def changeDate = column[Long]("changeDate")
+
+ def * = (networkId,flagged,flagMessage,changeDate) <> (QepQueryFlag.tupled,QepQueryFlag.unapply)
+ }
+
+ val allQepQueryFlags = TableQuery[QepQueryFlags]
+ val mostRecentQueryFlags: Query[QepQueryFlags, QepQueryFlag, Seq] = for(
+ queryFlags <- allQepQueryFlags if !allQepQueryFlags.filter(_.networkId === queryFlags.networkId).filter(_.changeDate > queryFlags.changeDate).exists
+ ) yield queryFlags
+}
+
+object QepQuerySchema {
+
+ val allConfig:Config = QepConfigSource.config
+ val config:Config = allConfig.getConfig("shrine.queryEntryPoint.audit.database")
+
+ val slickProfileClassName = config.getString("slickProfileClassName")
+ val slickProfile:JdbcProfile = QepConfigSource.objectForName(slickProfileClassName)
+
+ val schema = QepQuerySchema(slickProfile)
+}
+
+case class QepQuery(
+ networkId:NetworkQueryId,
+ userName: UserName,
+ userDomain: String,
+ queryName: QueryName,
+ expression: String,
+ dateCreated: Time,
+ queryXml:String
+ ){
+
+ def toQueryMaster(qepQueryFlag:Option[QepQueryFlag]):QueryMaster = {
+
+ val gregorianCalendar = new GregorianCalendar()
+ gregorianCalendar.setTimeInMillis(dateCreated)
+ val xmlGregorianCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(gregorianCalendar)
+ QueryMaster(
+ queryMasterId = networkId.toString,
+ networkQueryId = networkId,
+ name = queryName,
+ userId = userName,
+ groupId = userDomain,
+ createDate = xmlGregorianCalendar,
+ held = None, //todo if a query is held at the adapter, how will we know? do we care? Question out to Bill and leadership
+ flagged = qepQueryFlag.map(_.flagged),
+ flagMessage = qepQueryFlag.map(_.flagMessage)
+ )
+ }
+}
+
+object QepQuery extends ((NetworkQueryId,UserName,String,QueryName,String,Time,String) => QepQuery) {
+ def apply(runQueryRequest: RunQueryRequest):QepQuery = {
+ new QepQuery(
+ networkId = runQueryRequest.networkQueryId,
+ userName = runQueryRequest.authn.username,
+ userDomain = runQueryRequest.authn.domain,
+ queryName = runQueryRequest.queryDefinition.name,
+ expression = runQueryRequest.queryDefinition.expr.getOrElse("No Expression").toString,
+ dateCreated = System.currentTimeMillis(),
+ queryXml = runQueryRequest.toXmlString
+ )
+ }
+}
+
+case class QepQueryFlag(
+ networkQueryId: NetworkQueryId,
+ flagged:Boolean,
+ flagMessage:String,
+ changeDate:Long
+ )
+
+object QepQueryFlag extends ((NetworkQueryId,Boolean,String,Long) => QepQueryFlag) {
+ def apply(flagQueryRequest: FlagQueryRequest):QepQueryFlag = {
+ QepQueryFlag(
+ networkQueryId = flagQueryRequest.networkQueryId,
+ flagged = true,
+ flagMessage = flagQueryRequest.message.getOrElse(""),
+ changeDate = System.currentTimeMillis()
+ )
+ }
+
+ def apply(unflagQueryRequest: UnFlagQueryRequest):QepQueryFlag = {
+ QepQueryFlag(
+ networkQueryId = unflagQueryRequest.networkQueryId,
+ flagged = false,
+ flagMessage = "",
+ changeDate = System.currentTimeMillis()
+ )
+ }
+
+}
+
diff --git a/qep/service/src/main/scala/net/shrine/qep/queries/package.scala b/qep/service/src/main/scala/net/shrine/qep/queries/package.scala
new file mode 100644
index 000000000..684d12f38
--- /dev/null
+++ b/qep/service/src/main/scala/net/shrine/qep/queries/package.scala
@@ -0,0 +1,13 @@
+package net.shrine.qep
+
+/**
+ * Stores query instances and query results at the qep.
+ *
+ * @author david
+ * @since 1/19/16
+ */
+package object queries {
+ type QueryId = Int
+
+
+}
diff --git a/qep/service/src/main/scala/net/shrine/service/dao/squeryl/tables/Tables.scala b/qep/service/src/main/scala/net/shrine/service/dao/squeryl/tables/Tables.scala
deleted file mode 100644
index cb4fa3d98..000000000
--- a/qep/service/src/main/scala/net/shrine/service/dao/squeryl/tables/Tables.scala
+++ /dev/null
@@ -1,10 +0,0 @@
-package net.shrine.service.dao.squeryl.tables
-
-import org.squeryl.Schema
-import net.shrine.service.dao.squeryl.SquerylEntryPoint._
-
-/**
- * @author clint
- * @date May 21, 2013
- */
-final class Tables extends Schema with AuditEntryComponent
\ No newline at end of file
diff --git a/qep/service/src/main/resources/create_broadcaster_audit_table.sql b/qep/service/src/main/sql/create_broadcaster_audit_table.sql
similarity index 100%
rename from qep/service/src/main/resources/create_broadcaster_audit_table.sql
rename to qep/service/src/main/sql/create_broadcaster_audit_table.sql
diff --git a/qep/service/src/main/sql/mysql.ddl b/qep/service/src/main/sql/mysql.ddl
index fa69cc90a..33535a719 100644
--- a/qep/service/src/main/sql/mysql.ddl
+++ b/qep/service/src/main/sql/mysql.ddl
@@ -1 +1,3 @@
-create table `queriesSent` (`shrineNodeId` TEXT NOT NULL,`userName` TEXT NOT NULL,`networkQueryId` BIGINT NOT NULL,`queryName` TEXT NOT NULL,`queryTopicId` TEXT,`queryTopicName` TEXT,`timeQuerySent` BIGINT NOT NULL);
\ No newline at end of file
+create table `queriesSent` (`shrineNodeId` TEXT NOT NULL,`userName` TEXT NOT NULL,`networkQueryId` BIGINT NOT NULL,`queryName` TEXT NOT NULL,`queryTopicId` TEXT,`queryTopicName` TEXT,`timeQuerySent` BIGINT NOT NULL);
+create table `previousQueries` (`networkId` BIGINT NOT NULL,`userName` TEXT NOT NULL,`domain` TEXT NOT NULL,`queryName` TEXT NOT NULL,`expression` TEXT NOT NULL,`dateCreated` BIGINT NOT NULL,`queryXml` TEXT NOT NULL);
+create table `queryFlags` (`networkId` BIGINT NOT NULL,`flagged` BOOLEAN NOT NULL,`flagMessage` TEXT NOT NULL,`changeDate` BIGINT NOT NULL);
\ No newline at end of file
diff --git a/qep/service/src/main/sql/report.sql b/qep/service/src/main/sql/mysql_qep_audit.sql
similarity index 100%
rename from qep/service/src/main/sql/report.sql
rename to qep/service/src/main/sql/mysql_qep_audit.sql
diff --git a/qep/service/src/test/scala/net/shrine/service/I2b2BroadcastServiceTest.scala b/qep/service/src/test/scala/net/shrine/qep/I2B2QepServiceTest.scala
similarity index 87%
rename from qep/service/src/test/scala/net/shrine/service/I2b2BroadcastServiceTest.scala
rename to qep/service/src/test/scala/net/shrine/qep/I2B2QepServiceTest.scala
index 6fef3439a..ed3912acd 100644
--- a/qep/service/src/test/scala/net/shrine/service/I2b2BroadcastServiceTest.scala
+++ b/qep/service/src/test/scala/net/shrine/qep/I2B2QepServiceTest.scala
@@ -1,57 +1,57 @@
-package net.shrine.service
+package net.shrine.qep
import net.shrine.util.ShouldMatchersForJUnit
import org.junit.Test
import net.shrine.authentication.Authenticator
import net.shrine.protocol.AuthenticationInfo
import net.shrine.authentication.AuthenticationResult
import net.shrine.protocol.ReadResultOutputTypesRequest
import net.shrine.protocol.Credential
import net.shrine.protocol.ReadResultOutputTypesResponse
import net.shrine.protocol.ResultOutputType
import net.shrine.protocol.DefaultBreakdownResultOutputTypes
/**
* @author clint
* @since Oct 23, 2014
*/
-final class I2b2BroadcastServiceTest extends ShouldMatchersForJUnit {
+final class I2B2QepServiceTest extends ShouldMatchersForJUnit {
private val knownUsername = "some-user"
private val unknownUsername = "some-unknown-user"
import scala.concurrent.duration._
@Test
def testReadResultOutputTypes(): Unit = {
val authenticator: Authenticator = new Authenticator {
override def authenticate(authn: AuthenticationInfo): AuthenticationResult = {
if (authn.username == knownUsername) {
AuthenticationResult.Authenticated(authn.domain, authn.username)
} else {
AuthenticationResult.NotAuthenticated(authn.domain, authn.username, "blarg")
}
}
}
val breakdownResultOutputTypes = DefaultBreakdownResultOutputTypes.toSet
- val service = I2b2BroadcastService("example.com",null, authenticator, null, includeAggregateResult = true, null, 1.day, breakdownResultOutputTypes,false)
+ val service = I2b2QepService("example.com",null, authenticator, null, includeAggregateResult = true, null, 1.day, breakdownResultOutputTypes,false)
{
val req = ReadResultOutputTypesRequest("project-id", 1.minute, AuthenticationInfo("d", knownUsername, Credential("foo", isToken = false)))
val resp = service.readResultOutputTypes(req)
resp.asInstanceOf[ReadResultOutputTypesResponse].outputTypes should equal(ResultOutputType.nonErrorTypes ++ breakdownResultOutputTypes)
}
{
val req = ReadResultOutputTypesRequest("project-id", 1.minute, AuthenticationInfo("d", unknownUsername, Credential("foo", isToken = false)))
intercept[Exception] {
service.readResultOutputTypes(req)
}
}
}
}
\ No newline at end of file
diff --git a/qep/service/src/test/scala/net/shrine/service/I2b2BroadcastResourceTest.scala b/qep/service/src/test/scala/net/shrine/qep/I2b2BroadcastResourceTest.scala
similarity index 98%
rename from qep/service/src/test/scala/net/shrine/service/I2b2BroadcastResourceTest.scala
rename to qep/service/src/test/scala/net/shrine/qep/I2b2BroadcastResourceTest.scala
index 6357df9eb..2efa81a69 100644
--- a/qep/service/src/test/scala/net/shrine/service/I2b2BroadcastResourceTest.scala
+++ b/qep/service/src/test/scala/net/shrine/qep/I2b2BroadcastResourceTest.scala
@@ -1,63 +1,63 @@
-package net.shrine.service
+package net.shrine.qep
import junit.framework.TestCase
import net.shrine.util.ShouldMatchersForJUnit
import org.junit.Test
import javax.ws.rs.core.Response
import org.scalatest.mock.EasyMockSugar
import net.shrine.protocol.ShrineRequestHandler
import net.shrine.protocol.ReadI2b2AdminPreviousQueriesRequest
import net.shrine.protocol.AuthenticationInfo
import net.shrine.protocol.Credential
import net.shrine.protocol.ErrorResponse
import net.shrine.protocol.I2b2RequestHandler
import net.shrine.protocol.DefaultBreakdownResultOutputTypes
/**
* @author clint
* @date Mar 25, 2013
*/
final class I2b2BroadcastResourceTest extends ShouldMatchersForJUnit with EasyMockSugar {
@Test
def testHandleBadInput {
def doTestHandleBadInput(resourceMethod: I2b2BroadcastResource => String => Response) {
val resource = I2b2BroadcastResource(mock[I2b2RequestHandler], DefaultBreakdownResultOutputTypes.toSet)
def checkIsErrorResponse(resp: Response) {
val responseBody = resp.getEntity.asInstanceOf[String]
val errorResponse = ErrorResponse.fromI2b2(responseBody)
errorResponse.errorMessage should not be(null)
errorResponse.errorMessage.take(5) should equal("Error")
}
//Just junk data
{
val resp = resourceMethod(resource)("sadlkhjksafhjksafhjkasgfgjskdfhsjkdfhgjsdfg")
resp.getStatus should equal(200)
checkIsErrorResponse(resp)
}
//A correctly-serialized request that we can't handle also counts as a bad request
{
val authn = AuthenticationInfo("d", "u", Credential("p", false))
import scala.concurrent.duration._
import ReadI2b2AdminPreviousQueriesRequest.Username._
val resp = resourceMethod(resource)(ReadI2b2AdminPreviousQueriesRequest("p", 123.milliseconds, authn, Exactly("@"), "foo", 20, None).toI2b2String)
resp.getStatus should equal(200)
checkIsErrorResponse(resp)
}
}
doTestHandleBadInput(_.doPDORequest)
doTestHandleBadInput(_.doRequest)
}
}
\ No newline at end of file
diff --git a/qep/service/src/test/scala/net/shrine/service/QepConfigTest.scala b/qep/service/src/test/scala/net/shrine/qep/QepConfigTest.scala
similarity index 98%
rename from qep/service/src/test/scala/net/shrine/service/QepConfigTest.scala
rename to qep/service/src/test/scala/net/shrine/qep/QepConfigTest.scala
index 26d5c0adc..fceaa59f8 100644
--- a/qep/service/src/test/scala/net/shrine/service/QepConfigTest.scala
+++ b/qep/service/src/test/scala/net/shrine/qep/QepConfigTest.scala
@@ -1,60 +1,60 @@
-package net.shrine.service
+package net.shrine.qep
import com.typesafe.config.ConfigFactory
import net.shrine.authentication.AuthenticationType
import net.shrine.authorization.AuthorizationType
import net.shrine.config.Keys
import net.shrine.util.ShouldMatchersForJUnit
import org.junit.Test
/**
* @author clint
* @since Mar 3, 2014
*/
final class QepConfigTest extends ShouldMatchersForJUnit {
private def entryPointServiceConfig(baseFileName: String) = QepConfig(ConfigFactory.load(baseFileName).getConfig(s"shrine.${Keys.queryEntryPoint}"))
@Test
def testApply {
val conf: QepConfig = entryPointServiceConfig("shrine")
import scala.concurrent.duration._
conf.authenticationType should be(AuthenticationType.Ecommons)
conf.authorizationType should be(AuthorizationType.HmsSteward)
conf.broadcasterIsLocal should be(false)
conf.broadcasterServiceEndpoint.get.acceptAllCerts should be(true)
conf.broadcasterServiceEndpoint.get.timeout should be(1.second)
conf.broadcasterServiceEndpoint.get.url.toString should equal("http://example.com/shrine/rest/broadcaster/broadcast")
conf.includeAggregateResults should equal(false)
conf.maxQueryWaitTime should equal(5.minutes)
conf.sheriffCredentials.get.domain should be(None)
conf.sheriffCredentials.get.username should be("sheriffUsername")
conf.sheriffCredentials.get.password should be("sheriffPassword")
conf.sheriffEndpoint.get.acceptAllCerts should be(true)
conf.sheriffEndpoint.get.timeout should be(1.second)
conf.sheriffEndpoint.get.url.toString should be("http://localhost:8080/shrine-hms-authorization/queryAuthorization")
}
@Test
def testApplyOptionalFields {
val conf = entryPointServiceConfig("shrine-some-optional-props")
conf.authenticationType should be(AuthenticationType.Pm)
conf.authorizationType should be(AuthorizationType.NoAuthorization)
conf.broadcasterIsLocal should be(true)
conf.broadcasterServiceEndpoint should be(None)
conf.sheriffCredentials should be(None)
conf.sheriffEndpoint should be(None)
}
}
\ No newline at end of file
diff --git a/qep/service/src/test/scala/net/shrine/service/ShrineServiceTest.scala b/qep/service/src/test/scala/net/shrine/qep/QepServiceTest.scala
similarity index 86%
rename from qep/service/src/test/scala/net/shrine/service/ShrineServiceTest.scala
rename to qep/service/src/test/scala/net/shrine/qep/QepServiceTest.scala
index b0d17e069..e9fde7ecb 100644
--- a/qep/service/src/test/scala/net/shrine/service/ShrineServiceTest.scala
+++ b/qep/service/src/test/scala/net/shrine/qep/QepServiceTest.scala
@@ -1,206 +1,206 @@
-package net.shrine.service
+package net.shrine.qep
import org.junit.Test
import org.scalatest.mock.EasyMockSugar
import net.shrine.authorization.QueryAuthorizationService
-import net.shrine.service.dao.AbstractAuditDaoTest
+import net.shrine.qep.dao.AbstractAuditDaoTest
import net.shrine.protocol.AuthenticationInfo
import net.shrine.protocol.Credential
import net.shrine.protocol.ReadApprovedQueryTopicsRequest
import net.shrine.protocol.ReadApprovedQueryTopicsResponse
import net.shrine.protocol.ReadQueryInstancesRequest
import net.shrine.protocol.ReadQueryInstancesResponse
import net.shrine.protocol.RunQueryRequest
import net.shrine.protocol.query.QueryDefinition
import net.shrine.protocol.query.Term
import net.shrine.authorization.AuthorizationResult
import net.shrine.authentication.Authenticator
import net.shrine.authentication.AuthenticationResult
import net.shrine.authentication.NotAuthenticatedException
import net.shrine.protocol.ErrorResponse
/**
* @author Bill Simons
* @author Clint Gilbert
* @since 3/30/11
* @see http://cbmi.med.harvard.edu
* @see http://chip.org
*
* NOTICE: This software comes with NO guarantees whatsoever and is
* licensed as Lgpl Open Source
* @see http://www.gnu.org/licenses/lgpl.html
*/
-final class ShrineServiceTest extends AbstractAuditDaoTest with EasyMockSugar {
+final class QepServiceTest extends AbstractAuditDaoTest with EasyMockSugar {
import scala.concurrent.duration._
@Test
def testReadQueryInstances() {
val projectId = "foo"
val queryId = 123L
val authn = AuthenticationInfo("some-domain", "some-username", Credential("blarg", isToken = false))
val req = ReadQueryInstancesRequest(projectId, 1.millisecond, authn, queryId)
- val service = ShrineService("example.com",null, AllowsAllAuthenticator, null, includeAggregateResult = true, null, null, Set.empty,collectQepAudit = false)
+ val service = QepService("example.com",null, AllowsAllAuthenticator, null, includeAggregateResult = true, null, null, Set.empty,collectQepAudit = false)
val response = service.readQueryInstances(req).asInstanceOf[ReadQueryInstancesResponse]
response should not be (null)
response.groupId should equal(projectId)
response.queryMasterId should equal(queryId)
response.userId should equal(authn.username)
val Seq(instance) = response.queryInstances
instance.startDate should not be (null)
instance.endDate should not be (null)
instance.startDate should equal(instance.endDate)
instance.groupId should equal(projectId)
instance.queryInstanceId should equal(queryId.toString)
instance.queryMasterId should equal(queryId.toString)
instance.userId should equal(authn.username)
}
private val authn = AuthenticationInfo("some-domain", "some-user", Credential("some-password", isToken = false))
private val projectId = "projectId"
private val queryDef = QueryDefinition("yo", Term("foo"))
private val request = RunQueryRequest(projectId, 1.millisecond, authn, Some("topicId"), Some("Topic Name"), Set.empty, queryDef)
@Test
def testRunQueryAggregatorFor() {
def doTestRunQueryAggregatorFor(addAggregatedResult: Boolean) {
- val service = ShrineService("example.com",null, null, null, addAggregatedResult, null, null, Set.empty,collectQepAudit = false)
+ val service = QepService("example.com",null, null, null, addAggregatedResult, null, null, Set.empty,collectQepAudit = false)
val aggregator = service.runQueryAggregatorFor(request)
aggregator should not be (null)
aggregator.queryId should be(-1L)
aggregator.groupId should be(projectId)
aggregator.userId should be(authn.username)
aggregator.requestQueryDefinition should be(queryDef)
aggregator.addAggregatedResult should be(addAggregatedResult)
}
doTestRunQueryAggregatorFor(true)
doTestRunQueryAggregatorFor(false)
}
@Test
def testAuditTransactionally() = afterMakingTables {
def doTestAuditTransactionally(shouldThrow: Boolean) {
- val service = ShrineService("example.com",auditDao, null, null, includeAggregateResult = true, null, null, Set.empty,collectQepAudit = false)
+ val service = QepService("example.com",auditDao, null, null, includeAggregateResult = true, null, null, Set.empty,collectQepAudit = false)
if (shouldThrow) {
intercept[Exception] {
service.auditTransactionally(request)(throw new Exception)
}
} else {
val x = 1
val actual = service.auditTransactionally(request)(x)
actual should be(x)
}
//We should have recorded an audit entry no matter what
val Seq(entry) = auditDao.findRecentEntries(1)
entry.domain should be(authn.domain)
entry.username should be(authn.username)
entry.project should be(projectId)
entry.queryText should be(Some(queryDef.toI2b2String))
entry.queryTopic should be(request.topicId)
entry.time should not be (null)
}
doTestAuditTransactionally(false)
doTestAuditTransactionally(true)
}
- import ShrineServiceTest._
+ import QepServiceTest._
@Test
def testAfterAuthenticating() {
def doTestAfterAuthenticating(shouldAuthenticate: Boolean) {
- val service = ShrineService("example.com",auditDao, new MockAuthenticator(shouldAuthenticate), new MockAuthService(true), includeAggregateResult = true, null, null, Set.empty,collectQepAudit = false)
+ val service = QepService("example.com",auditDao, new MockAuthenticator(shouldAuthenticate), new MockAuthService(true), includeAggregateResult = true, null, null, Set.empty,collectQepAudit = false)
if (shouldAuthenticate) {
var foo = false
service.authenticateAndThen(request) { _ =>
foo = true
}
foo should be(right = true)
} else {
intercept[NotAuthenticatedException] {
service.authenticateAndThen(request) { _ => () }
}
}
}
doTestAfterAuthenticating(true)
doTestAfterAuthenticating(false)
}
@Test
def testAfterAuditingAndAuthorizing() = afterMakingTables {
def doAfterAuditingAndAuthorizing(shouldBeAuthorized: Boolean, shouldThrow: Boolean) {
- val service = ShrineService("example.com",auditDao, AllowsAllAuthenticator, new MockAuthService(shouldBeAuthorized), includeAggregateResult = true, null, null, Set.empty,collectQepAudit = false)
+ val service = QepService("example.com",auditDao, AllowsAllAuthenticator, new MockAuthService(shouldBeAuthorized), includeAggregateResult = true, null, null, Set.empty,collectQepAudit = false)
if (shouldThrow || !shouldBeAuthorized) {
intercept[Exception] {
service.auditAuthorizeAndThen(request)(request => throw new Exception)
}
} else {
val x = 1
val actual = service.auditAuthorizeAndThen(request)(request => x)
actual should be(x)
}
//We should have recorded an audit entry no matter what
val Seq(entry) = auditDao.findRecentEntries(1)
entry.domain should be(authn.domain)
entry.username should be(authn.username)
entry.project should be(projectId)
entry.queryText should be(Some(queryDef.toI2b2String))
entry.queryTopic should be(request.topicId)
entry.time should not be (null)
}
doAfterAuditingAndAuthorizing(shouldBeAuthorized = true, shouldThrow = true)
doAfterAuditingAndAuthorizing(shouldBeAuthorized = true, shouldThrow = false)
doAfterAuditingAndAuthorizing(shouldBeAuthorized = false, shouldThrow = true)
doAfterAuditingAndAuthorizing(shouldBeAuthorized = false, shouldThrow = false)
}
}
-object ShrineServiceTest {
+object QepServiceTest {
final class MockAuthenticator(shouldAuthenticate: Boolean) extends Authenticator {
override def authenticate(authn: AuthenticationInfo): AuthenticationResult = {
if (shouldAuthenticate) { AuthenticationResult.Authenticated(authn.domain, authn.username) }
else { AuthenticationResult.NotAuthenticated(authn.domain, authn.username, "blarg") }
}
}
final class MockAuthService(shouldWork: Boolean) extends QueryAuthorizationService {
def authorizeRunQueryRequest(request: RunQueryRequest): AuthorizationResult = {
if (shouldWork) {
val topicIdAndName = (request.topicId,request.topicName) match {
case (Some(id),Some(name)) => Some((id,name))
case (None,None) => None
}
AuthorizationResult.Authorized(topicIdAndName)}
else { AuthorizationResult.NotAuthorized("blarg") }
}
def readApprovedEntries(request: ReadApprovedQueryTopicsRequest): Either[ErrorResponse, ReadApprovedQueryTopicsResponse] = ???
}
}
\ No newline at end of file
diff --git a/qep/service/src/test/scala/net/shrine/service/ShrineResourceJaxrsTest.scala b/qep/service/src/test/scala/net/shrine/qep/ShrineResourceJaxrsTest.scala
similarity index 99%
rename from qep/service/src/test/scala/net/shrine/service/ShrineResourceJaxrsTest.scala
rename to qep/service/src/test/scala/net/shrine/qep/ShrineResourceJaxrsTest.scala
index 3a9d95c4e..0af2a5fcf 100644
--- a/qep/service/src/test/scala/net/shrine/service/ShrineResourceJaxrsTest.scala
+++ b/qep/service/src/test/scala/net/shrine/qep/ShrineResourceJaxrsTest.scala
@@ -1,636 +1,636 @@
-package net.shrine.service
+package net.shrine.qep
import org.junit.Test
import net.shrine.util.ShouldMatchersForJUnit
import com.sun.jersey.api.client.UniformInterfaceException
import com.sun.jersey.test.framework.JerseyTest
import net.shrine.client.JerseyShrineClient
import net.shrine.crypto.TrustParam.AcceptAllCerts
import net.shrine.protocol.AggregatedReadInstanceResultsResponse
import net.shrine.protocol.AggregatedReadQueryResultResponse
import net.shrine.protocol.AggregatedRunQueryResponse
import net.shrine.protocol.ApprovedTopic
import net.shrine.protocol.AuthenticationInfo
import net.shrine.protocol.Credential
import net.shrine.protocol.DeleteQueryRequest
import net.shrine.protocol.DeleteQueryResponse
import net.shrine.protocol.EventResponse
import net.shrine.protocol.ObservationResponse
import net.shrine.protocol.ParamResponse
import net.shrine.protocol.PatientResponse
import net.shrine.protocol.QueryResult
import net.shrine.protocol.ReadApprovedQueryTopicsRequest
import net.shrine.protocol.ReadApprovedQueryTopicsResponse
import net.shrine.protocol.ReadInstanceResultsRequest
import net.shrine.protocol.ReadPdoRequest
import net.shrine.protocol.ReadPdoResponse
import net.shrine.protocol.ReadPreviousQueriesRequest
import net.shrine.protocol.ReadPreviousQueriesResponse
import net.shrine.protocol.ReadQueryDefinitionRequest
import net.shrine.protocol.ReadQueryDefinitionResponse
import net.shrine.protocol.ReadQueryInstancesRequest
import net.shrine.protocol.ReadQueryInstancesResponse
import net.shrine.protocol.ReadQueryResultRequest
import net.shrine.protocol.RenameQueryRequest
import net.shrine.protocol.RenameQueryResponse
import net.shrine.protocol.RequestType
import net.shrine.protocol.ResultOutputType
import net.shrine.protocol.RunQueryRequest
import net.shrine.protocol.ShrineRequest
import net.shrine.protocol.ShrineRequestHandler
import net.shrine.protocol.ShrineResponse
import net.shrine.protocol.query.QueryDefinition
import net.shrine.protocol.query.Term
import net.shrine.util.JerseyAppDescriptor
import net.shrine.util.XmlDateHelper
import net.shrine.protocol.BaseShrineResponse
import net.shrine.protocol.ReadTranslatedQueryDefinitionRequest
import net.shrine.protocol.AggregatedReadTranslatedQueryDefinitionResponse
import net.shrine.protocol.FlagQueryRequest
import net.shrine.protocol.FlagQueryResponse
import net.shrine.protocol.UnFlagQueryRequest
import net.shrine.protocol.UnFlagQueryResponse
import org.junit.Before
import org.junit.After
import net.shrine.util.AbstractPortSearchingJerseyTest
import net.shrine.protocol.DefaultBreakdownResultOutputTypes
/**
*
* @author Clint Gilbert
* @date Sep 14, 2011
*
* @link http://cbmi.med.harvard.edu
*
* This software is licensed under the LGPL
* @link http://www.gnu.org/licenses/lgpl.html
*
* Starts a ShrineResource in an embedded HTTP server, sends requests to it, then verifies that the requests don't fail,
* and that the parameters made it from the client to the ShrineResource successfully. Uses a mock ShrineRequestHandler, so
* it doesn't test that correct values are returned by the ShrineResource.
*/
final class ShrineResourceJaxrsTest extends AbstractPortSearchingJerseyTest with ShouldMatchersForJUnit {
private val projectId = "some-project-id"
private val topicId = "some-topic-id"
private val userId = "some-user-id"
private val authenticationInfo = AuthenticationInfo("some-domain", userId, new Credential("some-val", false))
private val shrineClient = new JerseyShrineClient(resource.getURI.toString, projectId, authenticationInfo, DefaultBreakdownResultOutputTypes.toSet, AcceptAllCerts)
/**
* We invoked the no-arg superclass constructor, so we must override configure() to provide an AppDescriptor
* That tells Jersey to instantiate and expose ShrineResource
*/
override def configure = JerseyAppDescriptor.thatCreates(ShrineResource).using(MockShrineRequestHandler)
@Before
override def setUp(): Unit = super.setUp()
@After
override def tearDown(): Unit = super.tearDown()
@Test
def testReadApprovedQueryTopics {
val response = shrineClient.readApprovedQueryTopics(userId)
response should not(be(null))
MockShrineRequestHandler.readPreviousQueriesParam should be(null)
MockShrineRequestHandler.runQueryParam should be(null)
MockShrineRequestHandler.readQueryInstancesParam should be(null)
MockShrineRequestHandler.readInstanceResultsParam should be(null)
MockShrineRequestHandler.readPdoParam should be(null)
MockShrineRequestHandler.readQueryDefinitionParam should be(null)
MockShrineRequestHandler.deleteQueryParam should be(null)
MockShrineRequestHandler.renameQueryParam should be(null)
MockShrineRequestHandler.readQueryResultParam should be(null)
val param = MockShrineRequestHandler.readApprovedQueryTopicsParam
validateCachedParam(param, RequestType.SheriffRequest)
param.userId should equal(userId)
}
@Test
def testReadPreviousQueries = resetMockThen {
val fetchSize = 123
val response = shrineClient.readPreviousQueries(userId, fetchSize)
response should not(be(null))
MockShrineRequestHandler.readApprovedQueryTopicsParam should be(null)
MockShrineRequestHandler.runQueryParam should be(null)
MockShrineRequestHandler.readQueryInstancesParam should be(null)
MockShrineRequestHandler.readInstanceResultsParam should be(null)
MockShrineRequestHandler.readPdoParam should be(null)
MockShrineRequestHandler.readQueryDefinitionParam should be(null)
MockShrineRequestHandler.deleteQueryParam should be(null)
MockShrineRequestHandler.renameQueryParam should be(null)
MockShrineRequestHandler.readQueryResultParam should be(null)
val param = MockShrineRequestHandler.readPreviousQueriesParam
validateCachedParam(param, RequestType.UserRequest)
param.fetchSize should equal(fetchSize)
param.userId should equal(userId)
}
@Test
def testReadPreviousQueriesUsernameMismatch = resetMockThen {
intercept[UniformInterfaceException] {
shrineClient.readPreviousQueries("foo", 123)
}
MockShrineRequestHandler.readApprovedQueryTopicsParam should be(null)
MockShrineRequestHandler.runQueryParam should be(null)
MockShrineRequestHandler.readQueryInstancesParam should be(null)
MockShrineRequestHandler.readInstanceResultsParam should be(null)
MockShrineRequestHandler.readPdoParam should be(null)
MockShrineRequestHandler.readQueryDefinitionParam should be(null)
MockShrineRequestHandler.deleteQueryParam should be(null)
MockShrineRequestHandler.renameQueryParam should be(null)
MockShrineRequestHandler.readQueryResultParam should be(null)
MockShrineRequestHandler.readPreviousQueriesParam should be(null)
}
@Test
def testRunQuery = resetMockThen {
val queryDef = QueryDefinition("foo", Term("nuh"))
def doTestRunQueryResponse(response: AggregatedRunQueryResponse, expectedOutputTypes: Set[ResultOutputType]) {
response should not(be(null))
MockShrineRequestHandler.readApprovedQueryTopicsParam should be(null)
MockShrineRequestHandler.readPreviousQueriesParam should be(null)
MockShrineRequestHandler.readQueryInstancesParam should be(null)
MockShrineRequestHandler.readInstanceResultsParam should be(null)
MockShrineRequestHandler.readPdoParam should be(null)
MockShrineRequestHandler.readQueryDefinitionParam should be(null)
MockShrineRequestHandler.deleteQueryParam should be(null)
MockShrineRequestHandler.renameQueryParam should be(null)
MockShrineRequestHandler.readQueryResultParam should be(null)
val param = MockShrineRequestHandler.runQueryParam
validateCachedParam(param, RequestType.QueryDefinitionRequest)
param.outputTypes should equal(expectedOutputTypes)
param.queryDefinition should equal(queryDef)
param.topicId should equal(Some(topicId))
}
def doTestRunQuery(outputTypes: Set[ResultOutputType]) {
val responseScalaSet = shrineClient.runQuery(topicId, outputTypes, queryDef)
doTestRunQueryResponse(responseScalaSet, outputTypes)
val responseJavaSet = shrineClient.runQuery(topicId, outputTypes, queryDef)
doTestRunQueryResponse(responseJavaSet, outputTypes)
}
Seq(ResultOutputType.values.toSet,
Set(ResultOutputType.PATIENT_COUNT_XML),
Set(ResultOutputType.PATIENTSET),
Set.empty[ResultOutputType]).foreach(doTestRunQuery)
}
@Test
def testReadQueryInstances = resetMockThen {
val queryId = 123L
val response = shrineClient.readQueryInstances(queryId)
response should not(be(null))
MockShrineRequestHandler.readApprovedQueryTopicsParam should be(null)
MockShrineRequestHandler.readPreviousQueriesParam should be(null)
MockShrineRequestHandler.runQueryParam should be(null)
MockShrineRequestHandler.readInstanceResultsParam should be(null)
MockShrineRequestHandler.readPdoParam should be(null)
MockShrineRequestHandler.readQueryDefinitionParam should be(null)
MockShrineRequestHandler.deleteQueryParam should be(null)
MockShrineRequestHandler.renameQueryParam should be(null)
MockShrineRequestHandler.readQueryResultParam should be(null)
val param = MockShrineRequestHandler.readQueryInstancesParam
validateCachedParam(param, RequestType.MasterRequest)
param.queryId should equal(queryId)
}
@Test
def testReadInstanceResults = resetMockThen {
val shrineNetworkQueryId = 98765L
val response = shrineClient.readInstanceResults(shrineNetworkQueryId)
response should not(be(null))
MockShrineRequestHandler.readApprovedQueryTopicsParam should be(null)
MockShrineRequestHandler.readPreviousQueriesParam should be(null)
MockShrineRequestHandler.runQueryParam should be(null)
MockShrineRequestHandler.readQueryInstancesParam should be(null)
MockShrineRequestHandler.readPdoParam should be(null)
MockShrineRequestHandler.readQueryDefinitionParam should be(null)
MockShrineRequestHandler.deleteQueryParam should be(null)
MockShrineRequestHandler.renameQueryParam should be(null)
MockShrineRequestHandler.readQueryResultParam should be(null)
val param = MockShrineRequestHandler.readInstanceResultsParam
validateCachedParam(param, RequestType.InstanceRequest)
param.shrineNetworkQueryId should equal(shrineNetworkQueryId)
}
@Test
def testReadPdo = resetMockThen {
val patientSetId = "patientSetId"
val optionsXml =
val response = shrineClient.readPdo(patientSetId, optionsXml)
response should not(be(null))
MockShrineRequestHandler.readApprovedQueryTopicsParam should be(null)
MockShrineRequestHandler.readPreviousQueriesParam should be(null)
MockShrineRequestHandler.runQueryParam should be(null)
MockShrineRequestHandler.readQueryInstancesParam should be(null)
MockShrineRequestHandler.readInstanceResultsParam should be(null)
MockShrineRequestHandler.readQueryDefinitionParam should be(null)
MockShrineRequestHandler.deleteQueryParam should be(null)
MockShrineRequestHandler.renameQueryParam should be(null)
MockShrineRequestHandler.readQueryResultParam should be(null)
val param = MockShrineRequestHandler.readPdoParam
validateCachedParam(param, RequestType.GetPDOFromInputListRequest)
param.patientSetCollId should equal(patientSetId)
//Turn NodeSeqs to Strings for reliable comparisons
param.optionsXml.toString should equal(optionsXml.toString)
}
@Test
def testReadQueryDefinition = resetMockThen {
val queryId = 3789894L
val response = shrineClient.readQueryDefinition(queryId)
response should not(be(null))
MockShrineRequestHandler.readApprovedQueryTopicsParam should be(null)
MockShrineRequestHandler.readPreviousQueriesParam should be(null)
MockShrineRequestHandler.runQueryParam should be(null)
MockShrineRequestHandler.readQueryInstancesParam should be(null)
MockShrineRequestHandler.readPdoParam should be(null)
MockShrineRequestHandler.readInstanceResultsParam should be(null)
MockShrineRequestHandler.deleteQueryParam should be(null)
MockShrineRequestHandler.renameQueryParam should be(null)
MockShrineRequestHandler.readQueryResultParam should be(null)
val param = MockShrineRequestHandler.readQueryDefinitionParam
validateCachedParam(param, RequestType.GetRequestXml)
param.queryId should equal(queryId)
}
@Test
def testDeleteQuery = resetMockThen {
val queryId = 3789894L
val response = shrineClient.deleteQuery(queryId)
response should not(be(null))
MockShrineRequestHandler.readApprovedQueryTopicsParam should be(null)
MockShrineRequestHandler.readPreviousQueriesParam should be(null)
MockShrineRequestHandler.runQueryParam should be(null)
MockShrineRequestHandler.readQueryInstancesParam should be(null)
MockShrineRequestHandler.readPdoParam should be(null)
MockShrineRequestHandler.readInstanceResultsParam should be(null)
MockShrineRequestHandler.renameQueryParam should be(null)
MockShrineRequestHandler.readQueryResultParam should be(null)
val param = MockShrineRequestHandler.deleteQueryParam
validateCachedParam(param, RequestType.MasterDeleteRequest)
param.queryId should equal(queryId)
}
@Test
def testRenameQuery = resetMockThen {
val queryId = 3789894L
val queryName = "aslkfhkasfh"
val response = shrineClient.renameQuery(queryId, queryName)
response should not(be(null))
MockShrineRequestHandler.readApprovedQueryTopicsParam should be(null)
MockShrineRequestHandler.readPreviousQueriesParam should be(null)
MockShrineRequestHandler.runQueryParam should be(null)
MockShrineRequestHandler.readQueryInstancesParam should be(null)
MockShrineRequestHandler.readPdoParam should be(null)
MockShrineRequestHandler.readInstanceResultsParam should be(null)
MockShrineRequestHandler.deleteQueryParam should be(null)
MockShrineRequestHandler.readQueryResultParam should be(null)
val param = MockShrineRequestHandler.renameQueryParam
validateCachedParam(param, RequestType.MasterRenameRequest)
param.queryId should equal(queryId)
param.queryName should equal(queryName)
}
@Test
def testReadQueryResult = resetMockThen {
val queryId = 3789894L
val response = shrineClient.readQueryResult(queryId)
response should not(be(null))
MockShrineRequestHandler.readApprovedQueryTopicsParam should be(null)
MockShrineRequestHandler.readPreviousQueriesParam should be(null)
MockShrineRequestHandler.runQueryParam should be(null)
MockShrineRequestHandler.readQueryInstancesParam should be(null)
MockShrineRequestHandler.readPdoParam should be(null)
MockShrineRequestHandler.readInstanceResultsParam should be(null)
MockShrineRequestHandler.deleteQueryParam should be(null)
MockShrineRequestHandler.renameQueryParam should be(null)
val param = MockShrineRequestHandler.readQueryResultParam
MockShrineRequestHandler.shouldBroadcastParam should be(true)
param should not(be(null))
param.projectId should equal(projectId)
param.authn should equal(authenticationInfo)
param.requestType should equal(RequestType.GetQueryResult)
param.waitTime should equal(ShrineResource.waitTime)
param.queryId should equal(queryId)
}
@Test
def testReadTranslatedQueryDefinition = resetMockThen {
val queryDef = QueryDefinition("foo", Term("network"))
val response = shrineClient.readTranslatedQueryDefinition(queryDef)
response should not(be(null))
MockShrineRequestHandler.readApprovedQueryTopicsParam should be(null)
MockShrineRequestHandler.readPreviousQueriesParam should be(null)
MockShrineRequestHandler.runQueryParam should be(null)
MockShrineRequestHandler.readQueryInstancesParam should be(null)
MockShrineRequestHandler.readPdoParam should be(null)
MockShrineRequestHandler.readInstanceResultsParam should be(null)
MockShrineRequestHandler.deleteQueryParam should be(null)
MockShrineRequestHandler.renameQueryParam should be(null)
MockShrineRequestHandler.readQueryResultParam should be(null)
val param = MockShrineRequestHandler.readTranslatedQueryDefinitionParam
MockShrineRequestHandler.shouldBroadcastParam should be(true)
param should not(be(null))
param.authn should equal(authenticationInfo)
param.requestType should equal(RequestType.ReadTranslatedQueryDefinitionRequest)
param.queryDef should equal(queryDef)
}
@Test
def testFlagQuery: Unit = resetMockThen {
val queryId = 12345L
val message = "laskfhdklsjfhksdf"
val response = shrineClient.flagQuery(queryId, Some(message), true)
response should equal(FlagQueryResponse)
MockShrineRequestHandler.readApprovedQueryTopicsParam should be(null)
MockShrineRequestHandler.readPreviousQueriesParam should be(null)
MockShrineRequestHandler.runQueryParam should be(null)
MockShrineRequestHandler.readQueryInstancesParam should be(null)
MockShrineRequestHandler.readPdoParam should be(null)
MockShrineRequestHandler.readInstanceResultsParam should be(null)
MockShrineRequestHandler.deleteQueryParam should be(null)
MockShrineRequestHandler.renameQueryParam should be(null)
MockShrineRequestHandler.readQueryResultParam should be(null)
MockShrineRequestHandler.readTranslatedQueryDefinitionParam should be(null)
val param = MockShrineRequestHandler.flagQueryRequestParam
MockShrineRequestHandler.shouldBroadcastParam should be(true)
param should not(be(null))
param.authn should equal(authenticationInfo)
param.requestType should equal(RequestType.FlagQueryRequest)
param.networkQueryId should equal(queryId)
param.message should equal(Some(message))
}
@Test
def testUnFlagQuery: Unit = resetMockThen {
val queryId = 12345L
val response = shrineClient.unFlagQuery(queryId, true)
response should equal(UnFlagQueryResponse)
MockShrineRequestHandler.readApprovedQueryTopicsParam should be(null)
MockShrineRequestHandler.readPreviousQueriesParam should be(null)
MockShrineRequestHandler.runQueryParam should be(null)
MockShrineRequestHandler.readQueryInstancesParam should be(null)
MockShrineRequestHandler.readPdoParam should be(null)
MockShrineRequestHandler.readInstanceResultsParam should be(null)
MockShrineRequestHandler.deleteQueryParam should be(null)
MockShrineRequestHandler.renameQueryParam should be(null)
MockShrineRequestHandler.readQueryResultParam should be(null)
MockShrineRequestHandler.readTranslatedQueryDefinitionParam should be(null)
MockShrineRequestHandler.flagQueryRequestParam should be(null)
val param = MockShrineRequestHandler.unFlagQueryRequestParam
MockShrineRequestHandler.shouldBroadcastParam should be(true)
param should not(be(null))
param.authn should equal(authenticationInfo)
param.requestType should equal(RequestType.UnFlagQueryRequest)
param.networkQueryId should equal(queryId)
}
private def validateCachedParam(param: ShrineRequest, expectedRequestType: RequestType) {
MockShrineRequestHandler.shouldBroadcastParam should be(true)
param should not(be(null))
param.projectId should equal(projectId)
param.authn should equal(authenticationInfo)
param.requestType should equal(expectedRequestType)
param.waitTime should equal(ShrineResource.waitTime)
}
private def resetMockThen(body: => Any) {
MockShrineRequestHandler.reset()
//(Healthy?) paranoia
MockShrineRequestHandler.readApprovedQueryTopicsParam should be(null)
MockShrineRequestHandler.readPreviousQueriesParam should be(null)
MockShrineRequestHandler.runQueryParam should be(null)
MockShrineRequestHandler.readQueryInstancesParam should be(null)
MockShrineRequestHandler.readInstanceResultsParam should be(null)
MockShrineRequestHandler.readPdoParam should be(null)
MockShrineRequestHandler.readQueryDefinitionParam should be(null)
MockShrineRequestHandler.deleteQueryParam should be(null)
MockShrineRequestHandler.renameQueryParam should be(null)
MockShrineRequestHandler.readQueryResultParam should be(null)
MockShrineRequestHandler.flagQueryRequestParam should be(null)
MockShrineRequestHandler.unFlagQueryRequestParam should be(null)
MockShrineRequestHandler.readTranslatedQueryDefinitionParam should be(null)
body
}
/**
* Mock ShrineRequestHandler; stores passed parameters for later inspection.
* Private, since this is (basically) the enclosing test class's state
*/
private object MockShrineRequestHandler extends ShrineRequestHandler {
var shouldBroadcastParam = false
var readApprovedQueryTopicsParam: ReadApprovedQueryTopicsRequest = _
var readPreviousQueriesParam: ReadPreviousQueriesRequest = _
var runQueryParam: RunQueryRequest = _
var readQueryInstancesParam: ReadQueryInstancesRequest = _
var readInstanceResultsParam: ReadInstanceResultsRequest = _
var readPdoParam: ReadPdoRequest = _
var readQueryDefinitionParam: ReadQueryDefinitionRequest = _
var deleteQueryParam: DeleteQueryRequest = _
var renameQueryParam: RenameQueryRequest = _
var readQueryResultParam: ReadQueryResultRequest = _
var readTranslatedQueryDefinitionParam: ReadTranslatedQueryDefinitionRequest = _
var flagQueryRequestParam: FlagQueryRequest = _
var unFlagQueryRequestParam: UnFlagQueryRequest = _
def reset() {
shouldBroadcastParam = false
readApprovedQueryTopicsParam = null
readPreviousQueriesParam = null
runQueryParam = null
readQueryInstancesParam = null
readInstanceResultsParam = null
readPdoParam = null
readQueryDefinitionParam = null
deleteQueryParam = null
renameQueryParam = null
readQueryResultParam = null
readTranslatedQueryDefinitionParam = null
flagQueryRequestParam = null
unFlagQueryRequestParam = null
}
import XmlDateHelper.now
private def setShouldBroadcastAndThen(shouldBroadcast: Boolean)(f: => BaseShrineResponse): BaseShrineResponse = {
try { f } finally {
shouldBroadcastParam = shouldBroadcast
}
}
override def readApprovedQueryTopics(request: ReadApprovedQueryTopicsRequest, shouldBroadcast: Boolean): BaseShrineResponse = setShouldBroadcastAndThen(shouldBroadcast) {
readApprovedQueryTopicsParam = request
ReadApprovedQueryTopicsResponse(Seq(new ApprovedTopic(123L, "some topic")))
}
override def readPreviousQueries(request: ReadPreviousQueriesRequest, shouldBroadcast: Boolean): BaseShrineResponse = setShouldBroadcastAndThen(shouldBroadcast) {
readPreviousQueriesParam = request
ReadPreviousQueriesResponse(Seq.empty)
}
override def readQueryInstances(request: ReadQueryInstancesRequest, shouldBroadcast: Boolean): BaseShrineResponse = setShouldBroadcastAndThen(shouldBroadcast) {
readQueryInstancesParam = request
ReadQueryInstancesResponse(999L, "userId", "groupId", Seq.empty)
}
override def readInstanceResults(request: ReadInstanceResultsRequest, shouldBroadcast: Boolean): BaseShrineResponse = setShouldBroadcastAndThen(shouldBroadcast) {
readInstanceResultsParam = request
AggregatedReadInstanceResultsResponse(1337L, Seq(new QueryResult(123L, 1337L, Some(ResultOutputType.PATIENT_COUNT_XML), 789L, None, None, Some("description"), QueryResult.StatusType.Finished, Some("statusMessage"))))
}
override def readPdo(request: ReadPdoRequest, shouldBroadcast: Boolean): BaseShrineResponse = setShouldBroadcastAndThen(shouldBroadcast) {
readPdoParam = request
def randomString = java.util.UUID.randomUUID.toString
def paramResponse = new ParamResponse(randomString, randomString, randomString)
ReadPdoResponse(Seq(new EventResponse("event", "patient", None, None, Seq.empty)), Seq(new PatientResponse("patientId", Seq(paramResponse))), Seq(new ObservationResponse(None, "eventId", None, "patientId", None, None, None, "observerCode", "startDate", None, "valueTypeCode", None, None, None, None, None, None, None, Seq(paramResponse))))
}
override def readQueryDefinition(request: ReadQueryDefinitionRequest, shouldBroadcast: Boolean): BaseShrineResponse = setShouldBroadcastAndThen(shouldBroadcast) {
readQueryDefinitionParam = request
ReadQueryDefinitionResponse(87456L, "name", "userId", now, "")
}
override def runQuery(request: RunQueryRequest, shouldBroadcast: Boolean): BaseShrineResponse = setShouldBroadcastAndThen(shouldBroadcast) {
runQueryParam = request
AggregatedRunQueryResponse(123L, now, "userId", "groupId", request.queryDefinition, 456L, Seq.empty)
}
override def deleteQuery(request: DeleteQueryRequest, shouldBroadcast: Boolean): BaseShrineResponse = setShouldBroadcastAndThen(shouldBroadcast) {
deleteQueryParam = request
DeleteQueryResponse(56834756L)
}
override def renameQuery(request: RenameQueryRequest, shouldBroadcast: Boolean): BaseShrineResponse = setShouldBroadcastAndThen(shouldBroadcast) {
renameQueryParam = request
RenameQueryResponse(873468L, "some-name")
}
override def readQueryResult(request: ReadQueryResultRequest, shouldBroadcast: Boolean): BaseShrineResponse = setShouldBroadcastAndThen(shouldBroadcast) {
readQueryResultParam = request
AggregatedReadQueryResultResponse(1234567890L, Seq.empty)
}
override def readTranslatedQueryDefinition(request: ReadTranslatedQueryDefinitionRequest, shouldBroadcast: Boolean = true): BaseShrineResponse = setShouldBroadcastAndThen(shouldBroadcast) {
readTranslatedQueryDefinitionParam = request
AggregatedReadTranslatedQueryDefinitionResponse(Seq.empty)
}
override def flagQuery(request: FlagQueryRequest, shouldBroadcast: Boolean = true): BaseShrineResponse = setShouldBroadcastAndThen(shouldBroadcast) {
flagQueryRequestParam = request
FlagQueryResponse
}
override def unFlagQuery(request: UnFlagQueryRequest, shouldBroadcast: Boolean = true): BaseShrineResponse = setShouldBroadcastAndThen(shouldBroadcast) {
unFlagQueryRequestParam = request
UnFlagQueryResponse
}
}
}
\ No newline at end of file
diff --git a/qep/service/src/test/scala/net/shrine/service/ShrineResourceTest.scala b/qep/service/src/test/scala/net/shrine/qep/ShrineResourceTest.scala
similarity index 99%
rename from qep/service/src/test/scala/net/shrine/service/ShrineResourceTest.scala
rename to qep/service/src/test/scala/net/shrine/qep/ShrineResourceTest.scala
index c0ccc2852..e9867c0fd 100644
--- a/qep/service/src/test/scala/net/shrine/service/ShrineResourceTest.scala
+++ b/qep/service/src/test/scala/net/shrine/qep/ShrineResourceTest.scala
@@ -1,236 +1,236 @@
-package net.shrine.service
+package net.shrine.qep
import net.shrine.util.ShouldMatchersForJUnit
import org.scalatest.mock.EasyMockSugar
import org.easymock.EasyMock.{ eq => isEqualTo, expect => invoke, reportMatcher }
import net.shrine.protocol._
import net.shrine.protocol.query.QueryDefinition
import net.shrine.protocol.query.Term
import org.easymock.IArgumentMatcher
import org.easymock.internal.ArgumentToString
import org.junit.Test
import net.shrine.util.XmlDateHelper
import org.junit.Before
/**
* @author Clint Gilbert
* @since 9/13/2011
* @see http://cbmi.med.harvard.edu
* @see http://chip.org
*
* NOTICE: This software comes with NO guarantees whatsoever and is
* licensed as Lgpl Open Source
* @see http://www.gnu.org/licenses/lgpl.html
*/
final class ShrineResourceTest extends ShouldMatchersForJUnit with EasyMockSugar {
private var handler: ShrineRequestHandler = _
private var resource: ShrineResource = _
private val projectId = "projectId"
private val authenticationInfo = new AuthenticationInfo("domain", "username", new Credential("secret", true))
private val userId = "userId"
private val shouldBroadcast = true
@Before
def setUp(): Unit = {
handler = mock[ShrineRequestHandler]
resource = new ShrineResource(handler)
}
import ShrineResource.waitTime
@Test
def testReadApprovedQueryTopics {
val expectedRequest = ReadApprovedQueryTopicsRequest(projectId, waitTime, authenticationInfo, userId)
val expectedResponse = ReadApprovedQueryTopicsResponse(Seq(ApprovedTopic(123L, "foo")))
setExpectations(_.readApprovedQueryTopics, expectedRequest, expectedResponse)
execute {
resource.readApprovedQueryTopics(projectId, authenticationInfo, userId, shouldBroadcast)
}
}
@Test
def testReadPreviousQueries {
def doTestReadPreviousQueries(userId: String, fetchSize: Int, expectedFetchSize: Int) {
//Call setUp again create a new mock and new ShrinResource;
//each pair of expecting/whenExecuting calls needs a fresh mock.
this.setUp()
val expectedRequest = ReadPreviousQueriesRequest(projectId, waitTime, authenticationInfo, userId, expectedFetchSize)
val expectedResponse = ReadPreviousQueriesResponse(Seq.empty)
setExpectations(_.readPreviousQueries, expectedRequest, expectedResponse)
execute {
resource.readPreviousQueries(projectId, authenticationInfo, userId, fetchSize, shouldBroadcast)
}
}
doTestReadPreviousQueries(authenticationInfo.username, -100, -100)
doTestReadPreviousQueries(authenticationInfo.username, 0, 20)
doTestReadPreviousQueries(authenticationInfo.username, 1, 1)
doTestReadPreviousQueries(authenticationInfo.username, 100, 100)
}
@Test
def testRunQuery {
val outputTypes = ResultOutputType.values.toSet
val queryDef = QueryDefinition("foo", Term("nuh"))
val topicId = Some("topicId")
val topicName = Some("topicName")
val networkQueryId: Long = 999L
val expectedRequest = RunQueryRequest(projectId, waitTime, authenticationInfo, networkQueryId, topicId, topicName, outputTypes, queryDef)
val expectedResponse = RunQueryResponse(networkQueryId, null, "userId", "groupId", queryDef, 0L, QueryResult(1L, 0L, Some(ResultOutputType.PATIENT_COUNT_XML), 123L, None, None, None, QueryResult.StatusType.Finished, None))
def isEqualToExceptForQueryId(expected: RunQueryRequest): RunQueryRequest = {
reportMatcher(new IArgumentMatcher {
override def matches(argument: AnyRef): Boolean = {
argument.isInstanceOf[RunQueryRequest] && {
val actual = argument.asInstanceOf[RunQueryRequest]
//Everything *but* queryId, which is randomly generated by ShrineResource :\
actual.authn == expected.authn &&
actual.outputTypes == expected.outputTypes &&
actual.projectId == expected.projectId &&
actual.queryDefinition == expected.queryDefinition &&
actual.requestType == expected.requestType &&
actual.topicId == expected.topicId &&
actual.waitTime == expected.waitTime
}
}
override def appendTo(buffer: StringBuffer): Unit = ArgumentToString.appendArgument(expected, buffer)
})
null
}
expecting {
invoke(handler.runQuery(isEqualToExceptForQueryId(expectedRequest), isEqualTo(shouldBroadcast))).andReturn(expectedResponse)
}
execute {
resource.runQuery(projectId, authenticationInfo, topicId.get, topicName.get, new OutputTypeSet(outputTypes), queryDef.toXmlString, shouldBroadcast)
}
}
@Test
def testReadQueryInstances {
val queryId = 999L
val expectedRequest = ReadQueryInstancesRequest(projectId, waitTime, authenticationInfo, queryId)
val expectedResponse = ReadQueryInstancesResponse(queryId, "userId", "groupId", Seq.empty)
setExpectations(_.readQueryInstances, expectedRequest, expectedResponse)
execute {
resource.readQueryInstances(projectId, authenticationInfo, queryId, shouldBroadcast)
}
}
@Test
def testReadInstanceResults {
val instanceId = 123456789L
val expectedRequest = ReadInstanceResultsRequest(projectId, waitTime, authenticationInfo, instanceId)
val expectedResponse = AggregatedReadInstanceResultsResponse(instanceId, Seq.empty)
setExpectations(_.readInstanceResults, expectedRequest, expectedResponse)
execute {
resource.readInstanceResults(projectId, authenticationInfo, instanceId, shouldBroadcast)
}
}
@Test
def testReadPdo {
val patientSetCollId = "123456789L"
val optionsXml =
def paramResponse = ParamResponse("foo", "bar", "baz")
val expectedRequest = ReadPdoRequest(projectId, waitTime, authenticationInfo, patientSetCollId, optionsXml)
val expectedResponse = ReadPdoResponse(Seq(EventResponse("event", "patient", None, None, Seq.empty)), Seq(PatientResponse("patientId", Seq(paramResponse))),
Seq(ObservationResponse(None, "eventId", None, "patientId", None, None, None, "observerCode", "startDate", None, "valueTypeCode",None,None,None,None,None,None,None, Seq(paramResponse))))
setExpectations(_.readPdo, expectedRequest, expectedResponse)
execute {
resource.readPdo(projectId, authenticationInfo, patientSetCollId, optionsXml.toString, shouldBroadcast)
}
}
@Test
def testReadQueryDefinition {
val queryId = 123456789L
val expectedRequest = ReadQueryDefinitionRequest(projectId, waitTime, authenticationInfo, queryId)
val expectedResponse = ReadQueryDefinitionResponse(queryId, "name", "userId", XmlDateHelper.now, "")
setExpectations(_.readQueryDefinition, expectedRequest, expectedResponse)
execute {
resource.readQueryDefinition(projectId, authenticationInfo, queryId, shouldBroadcast)
}
}
@Test
def testDeleteQuery {
val queryId = 123456789L
val expectedRequest = DeleteQueryRequest(projectId, waitTime, authenticationInfo, queryId)
val expectedResponse = DeleteQueryResponse(queryId)
setExpectations(_.deleteQuery, expectedRequest, expectedResponse)
execute {
resource.deleteQuery(projectId, authenticationInfo, queryId, shouldBroadcast)
}
}
@Test
def testRenameQuery {
val queryId = 123456789L
val queryName = "asjkdhkahsf"
val expectedRequest = RenameQueryRequest(projectId, waitTime, authenticationInfo, queryId, queryName)
val expectedResponse = RenameQueryResponse(queryId, queryName)
setExpectations(_.renameQuery, expectedRequest, expectedResponse)
execute {
resource.renameQuery(projectId, authenticationInfo, queryId, queryName, shouldBroadcast)
}
}
def testReadQueryResult {
val queryId = 123456789L
val expectedRequest = ReadQueryResultRequest(projectId, waitTime, authenticationInfo, queryId)
val expectedResponse = AggregatedReadQueryResultResponse(queryId, Seq.empty)
setExpectations(_.readQueryResult, expectedRequest, expectedResponse)
execute {
resource.readQueryResults(projectId, authenticationInfo, queryId, shouldBroadcast)
}
}
private def execute(f: => Unit) = whenExecuting(handler)(f)
private def setExpectations[Req <: BaseShrineRequest, Resp <: BaseShrineResponse](handlerMethod: ShrineRequestHandler => (Req, Boolean) => BaseShrineResponse, expectedRequest: Req, expectedResponse: Resp) {
expecting {
invoke(handlerMethod(handler)(isEqualTo(expectedRequest), isEqualTo(shouldBroadcast))).andReturn(expectedResponse)
}
}
}
\ No newline at end of file
diff --git a/qep/service/src/test/scala/net/shrine/service/audit/QepQueryAuditTest.scala b/qep/service/src/test/scala/net/shrine/qep/audit/QepQueryAuditTest.scala
similarity index 90%
rename from qep/service/src/test/scala/net/shrine/service/audit/QepQueryAuditTest.scala
rename to qep/service/src/test/scala/net/shrine/qep/audit/QepQueryAuditTest.scala
index a79bfe226..e94b2d90c 100644
--- a/qep/service/src/test/scala/net/shrine/service/audit/QepQueryAuditTest.scala
+++ b/qep/service/src/test/scala/net/shrine/qep/audit/QepQueryAuditTest.scala
@@ -1,37 +1,37 @@
-package net.shrine.service.audit
+package net.shrine.qep.audit
-import net.shrine.service.QepConfigSource
+import net.shrine.qep.QepConfigSource
import net.shrine.util.ShouldMatchersForJUnit
import org.junit.{After, Before, Test}
/**
* @author david
* @since 8/18/15
*/
class QepQueryAuditTest extends ShouldMatchersForJUnit {// with TestWithDatabase {
val qepAuditData = QepQueryAuditData("example.com","ben",-1,"ben's query",Some("61"),Some("ben's topic"))
@Test
def testApply() {
QepConfigSource.configForBlock("shrine.qep.audit.useQepAudit","true",this.getClass.getSimpleName){
QepAuditDb.db.insertQepQuery(qepAuditData)
val results = QepAuditDb.db.selectAllQepQueries
results should equal(Seq(qepAuditData))
}
}
@Before
def beforeEach() = {
QepAuditDb.db.createTables()
}
@After
def afterEach() = {
QepAuditDb.db.dropTables()
}
}
diff --git a/qep/service/src/test/scala/net/shrine/service/dao/AbstractAuditDaoTest.scala b/qep/service/src/test/scala/net/shrine/qep/dao/AbstractAuditDaoTest.scala
similarity index 77%
rename from qep/service/src/test/scala/net/shrine/service/dao/AbstractAuditDaoTest.scala
rename to qep/service/src/test/scala/net/shrine/qep/dao/AbstractAuditDaoTest.scala
index fe26b7a56..a6502f09e 100644
--- a/qep/service/src/test/scala/net/shrine/service/dao/AbstractAuditDaoTest.scala
+++ b/qep/service/src/test/scala/net/shrine/qep/dao/AbstractAuditDaoTest.scala
@@ -1,27 +1,27 @@
-package net.shrine.service.dao
+package net.shrine.qep.dao
import net.shrine.util.ShouldMatchersForJUnit
import org.springframework.beans.factory.annotation.Autowired
-import net.shrine.service.dao.squeryl.tables.Tables
-import net.shrine.service.dao.squeryl.SquerylEntryPoint
+import net.shrine.qep.dao.squeryl.tables.Tables
+import net.shrine.qep.dao.squeryl.SquerylEntryPoint
/**
* @author clint
* @date Mar 14, 2013
*/
trait AbstractAuditDaoTest extends Wiring with ShouldMatchersForJUnit {
protected def afterMakingTables(f: => Any) {
import SquerylEntryPoint._
inTransaction {
try {
tables.auditEntries.schema.create
f
} finally {
tables.auditEntries.schema.drop
}
}
}
}
\ No newline at end of file
diff --git a/qep/service/src/test/scala/net/shrine/service/dao/AuditDaoTest.scala b/qep/service/src/test/scala/net/shrine/qep/dao/AuditDaoTest.scala
similarity index 92%
rename from qep/service/src/test/scala/net/shrine/service/dao/AuditDaoTest.scala
rename to qep/service/src/test/scala/net/shrine/qep/dao/AuditDaoTest.scala
index f333f9497..baa551631 100644
--- a/qep/service/src/test/scala/net/shrine/service/dao/AuditDaoTest.scala
+++ b/qep/service/src/test/scala/net/shrine/qep/dao/AuditDaoTest.scala
@@ -1,85 +1,85 @@
-package net.shrine.service.dao
+package net.shrine.qep.dao
import java.util.Date
import org.junit.Test
-import net.shrine.service.dao.model.AuditEntry
-import net.shrine.service.dao.squeryl.tables.Tables
-import net.shrine.service.dao.squeryl.SquerylEntryPoint
+import net.shrine.qep.dao.model.AuditEntry
+import net.shrine.qep.dao.squeryl.tables.Tables
+import net.shrine.qep.dao.squeryl.SquerylEntryPoint
import scala.util.Random
/**
* @author ??
* @author Clint Gilbert
* @date ??
* Ported to Scala on Feb 28, 2012
*
*/
final class AuditDaoTest extends AbstractAuditDaoTest {
private val numDummyEntries = 20
@Test
def testGetRecentEntries = withDummyEntries {
val limit = numDummyEntries / 2
val entries = auditDao.findRecentEntries(limit)
entries should not be (null)
entries.isEmpty should be(false)
entries.size should equal(limit)
assertDateInDescendingOrder(entries)
}
private def assertDateInDescendingOrder(entries: Seq[AuditEntry]) {
for (Seq(left, right) <- entries.sliding(2)) {
(left.time.getTime > right.time.getTime) should be(true)
}
entries should equal(entries.sortBy(_.time.getTime).reverse)
}
private def withDummyEntries(f: => Any) = afterMakingTables {
addDummyEntries()
f
}
private def addDummyEntries() {
SquerylEntryPoint.inTransaction {
val entries = for {
i <- 0 until numDummyEntries
} yield addNewAuditEntry(i, new Date(1000 * i))
//Make sure ids are autogenerated as expected, that is, a new entry should have an id that's 1 greater than the last entry's
entries.sliding(2).foreach {
case Seq(lhs, rhs) =>
(rhs.id - lhs.id) should equal(1L)
}
}
}
private val rand = new Random
private def addNewAuditEntry(i: Int, date: Date): AuditEntry = {
val project = s"project$i"
val username = s"username$i"
val domain = s"domain$i"
val queryText = s"query$i"
val queryTopic: Option[String] = if(rand.nextBoolean) Some(s"topic$i") else None
auditDao.addAuditEntry(date, project, domain, username, queryText, queryTopic)
val Seq(entry) = auditDao.findRecentEntries(1)
entry.id.asInstanceOf[AnyRef] should not be (null)
entry.domain should equal(domain)
entry.username should equal(username)
entry.project should equal(project)
entry.queryText should equal(Some(queryText))
entry.queryTopic should equal(queryTopic)
entry.time.getTime should equal(date.getTime)
entry
}
}
diff --git a/qep/service/src/test/scala/net/shrine/service/dao/Wiring.scala b/qep/service/src/test/scala/net/shrine/qep/dao/Wiring.scala
similarity index 72%
rename from qep/service/src/test/scala/net/shrine/service/dao/Wiring.scala
rename to qep/service/src/test/scala/net/shrine/qep/dao/Wiring.scala
index 903946d77..2a8da2b45 100644
--- a/qep/service/src/test/scala/net/shrine/service/dao/Wiring.scala
+++ b/qep/service/src/test/scala/net/shrine/qep/dao/Wiring.scala
@@ -1,21 +1,21 @@
-package net.shrine.service.dao
+package net.shrine.qep.dao
-import net.shrine.service.dao.squeryl.SquerylAuditDao
-import net.shrine.service.dao.squeryl.tables.Tables
+import net.shrine.qep.dao.squeryl.SquerylAuditDao
+import net.shrine.qep.dao.squeryl.tables.Tables
import net.shrine.wiring.HasH2SquerylInitializer
/**
* @author clint
* @date Jan 15, 2014
*/
trait Wiring {
val auditDao: AuditDao = Wiring.auditDao
val tables: Tables = Wiring.tables
}
object Wiring extends HasH2SquerylInitializer {
lazy val auditDao: AuditDao = new SquerylAuditDao(initializer, tables)
lazy val tables: Tables = new Tables
}
\ No newline at end of file
diff --git a/qep/service/src/test/scala/net/shrine/qep/queries/QepQueryDbTest.scala b/qep/service/src/test/scala/net/shrine/qep/queries/QepQueryDbTest.scala
new file mode 100644
index 000000000..c24a69b1e
--- /dev/null
+++ b/qep/service/src/test/scala/net/shrine/qep/queries/QepQueryDbTest.scala
@@ -0,0 +1,83 @@
+package net.shrine.qep.queries
+
+import net.shrine.util.ShouldMatchersForJUnit
+import org.junit.{After, Before, Test}
+
+/**
+ * @author david
+ * @since 1/20/16
+ */
+class QepQueryDbTest extends ShouldMatchersForJUnit {// with TestWithDatabase {
+
+ val qepQuery = QepQuery(
+ networkId = 1L,
+ userName = "ben",
+ userDomain = "testDomain",
+ queryName = "testQuery",
+ expression = "testExpression",
+ dateCreated = System.currentTimeMillis(),
+ queryXml = "testXML"
+ )
+
+ val secondQepQuery = QepQuery(
+ networkId = 2L,
+ userName = "dave",
+ userDomain = "testDomain",
+ queryName = "testQuery",
+ expression = "testExpression",
+ dateCreated = System.currentTimeMillis(),
+ queryXml = "testXML"
+ )
+
+ val flag = QepQueryFlag(
+ networkQueryId = 1L,
+ flagged = true,
+ flagMessage = "This query is flagged",
+ changeDate = System.currentTimeMillis()
+ )
+
+ @Test
+ def testInsertQepQuery() {
+
+ QepQueryDb.db.insertQepQuery(qepQuery)
+ QepQueryDb.db.insertQepQuery(secondQepQuery)
+
+ val results = QepQueryDb.db.selectAllQepQueries
+ results should equal(Seq(qepQuery,secondQepQuery))
+ }
+
+ @Test
+ def testSelectQepQueriesForUser() {
+
+ QepQueryDb.db.insertQepQuery(qepQuery)
+ QepQueryDb.db.insertQepQuery(secondQepQuery)
+
+ val results = QepQueryDb.db.selectPreviousQueriesByUserAndDomain("ben","testDomain")
+ results should equal(Seq(qepQuery))
+ }
+
+ @Test
+ def testSelectQueryFlags() {
+
+ val results1 = QepQueryDb.db.selectMostRecentQepQueryFlagsFor(Set(1L,2L))
+ results1 should equal(Map.empty)
+
+ QepQueryDb.db.insertQepQueryFlag(flag)
+
+ val results2 = QepQueryDb.db.selectMostRecentQepQueryFlagsFor(Set(1L,2L))
+ results2 should equal(Map(1L -> flag))
+
+ }
+
+
+ @Before
+ def beforeEach() = {
+ QepQueryDb.db.createTables()
+ }
+
+ @After
+ def afterEach() = {
+ QepQueryDb.db.dropTables()
+ }
+
+}