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() + } + +}