diff --git a/adapter/adapter-service/src/main/scala/net/shrine/adapter/components/HeldQueries.scala b/adapter/adapter-service/src/main/scala/net/shrine/adapter/components/HeldQueries.scala index 606bf713a..a2d58af4d 100644 --- a/adapter/adapter-service/src/main/scala/net/shrine/adapter/components/HeldQueries.scala +++ b/adapter/adapter-service/src/main/scala/net/shrine/adapter/components/HeldQueries.scala @@ -1,59 +1,60 @@ package net.shrine.adapter.components import net.shrine.protocol.RunHeldQueryRequest import net.shrine.protocol.ShrineResponse import net.shrine.protocol.BroadcastMessage import net.shrine.adapter.dao.model.ShrineQueryResult import net.shrine.protocol.RunQueryRequest import net.shrine.protocol.ResultOutputType import net.shrine.protocol.ErrorResponse import net.shrine.protocol.query.QueryDefinition import net.shrine.protocol.RunQueryResponse import net.shrine.adapter.dao.AdapterDao import net.shrine.adapter.RunQueryAdapter import net.shrine.protocol.AuthenticationInfo import net.shrine.protocol.Credential /** * @author clint * @date May 2, 2014 */ final case class HeldQueries(dao: AdapterDao, runQueryAdapter: RunQueryAdapter) { def run(req: RunHeldQueryRequest): ShrineResponse = { val queryId = req.networkQueryId //TODO: Revisit this, we might want to store/retrieve the output types used originally. val outputTypes: Set[ResultOutputType] = Set(ResultOutputType.PATIENT_COUNT_XML) dao.findQueryByNetworkId(queryId) match { case Some(savedQuery) => { //Re-un the query with the original credentials, not an admin's val savedAuthn = AuthenticationInfo(savedQuery.domain, savedQuery.username, Credential("", false)) val runQueryReq = RunQueryRequest( req.projectId, req.waitTime, savedAuthn, queryId, - None, //topic id + topicId = None, + topicName = None, outputTypes, savedQuery.queryDefinition) val newBroadcastMessage = BroadcastMessage(savedAuthn, runQueryReq) dao.inTransaction { //Delete previous records for this query from the DB, so we don't have obsolete records with //SHRINE_QUERY.HAS_BEEN_RUN = false for queries like the current one that ended up getting run. //Invoking runQueryAdapter.processRequest() will add correct values to the DB for the //actually-got-run query. dao.deleteQueryResultsFor(queryId) dao.deleteQuery(queryId) runQueryAdapter.processRequest(newBroadcastMessage) } } case None => ErrorResponse(s"Couldn't find query qith networkQueryId '${}'") } } } \ No newline at end of file diff --git a/adapter/adapter-service/src/test/scala/net/shrine/adapter/CrcInvocationExceptionTest.scala b/adapter/adapter-service/src/test/scala/net/shrine/adapter/CrcInvocationExceptionTest.scala index 6c6d3f489..138419249 100644 --- a/adapter/adapter-service/src/test/scala/net/shrine/adapter/CrcInvocationExceptionTest.scala +++ b/adapter/adapter-service/src/test/scala/net/shrine/adapter/CrcInvocationExceptionTest.scala @@ -1,45 +1,45 @@ package net.shrine.adapter import net.shrine.util.ShouldMatchersForJUnit import org.junit.Test import net.shrine.protocol.DeleteQueryRequest import net.shrine.protocol.AuthenticationInfo import net.shrine.protocol.Credential import net.shrine.protocol.RunQueryRequest /** * @author clint - * @date Oct 23, 2014 + * @since Oct 23, 2014 */ final class CrcInvocationExceptionTest extends ShouldMatchersForJUnit { @Test def testApply: Unit = { import scala.concurrent.duration._ val authn = AuthenticationInfo("d", "p", Credential("alksfh", false)) val rootCause = new Exception with scala.util.control.NoStackTrace val url = "http://example.com" val deleteReq = DeleteQueryRequest("project-id", 1.minute, authn, 12345L) - val runQueryReq = RunQueryRequest("project-id", 1.minute, authn, 123245L, None, Set.empty, null) + val runQueryReq = RunQueryRequest("project-id", 1.minute, authn, 123245L, None, None, Set.empty, null) { val e = CrcInvocationException(url, deleteReq, rootCause) e.invokedUrl should equal(url) e.rootCause should equal(rootCause) e.request should equal(deleteReq) } { val e = CrcInvocationException(url, runQueryReq, rootCause) e.invokedUrl should equal(url) e.rootCause should equal(rootCause) e.request should equal(runQueryReq.elideAuthenticationInfo) } } } \ No newline at end of file diff --git a/adapter/adapter-service/src/test/scala/net/shrine/adapter/RunQueryAdapterTest.scala b/adapter/adapter-service/src/test/scala/net/shrine/adapter/RunQueryAdapterTest.scala index 7f1f4522f..a34182755 100644 --- a/adapter/adapter-service/src/test/scala/net/shrine/adapter/RunQueryAdapterTest.scala +++ b/adapter/adapter-service/src/test/scala/net/shrine/adapter/RunQueryAdapterTest.scala @@ -1,961 +1,962 @@ package net.shrine.adapter import scala.concurrent.duration.DurationInt import org.junit.Test import net.shrine.util.ShouldMatchersForJUnit import ObfuscatorTest.within3 import net.shrine.adapter.dao.AdapterDao import net.shrine.adapter.dao.squeryl.AbstractSquerylAdapterTest import net.shrine.adapter.translators.ExpressionTranslator import net.shrine.adapter.translators.QueryDefinitionTranslator import net.shrine.client.HttpClient import net.shrine.client.HttpResponse import net.shrine.client.Poster import net.shrine.protocol.{HiveCredentials, AuthenticationInfo, BroadcastMessage, CrcRequest, Credential, I2b2ResultEnvelope, QueryResult, RawCrcRunQueryResponse, ReadResultRequest, ReadResultResponse, ResultOutputType, RunQueryRequest, RunQueryResponse, ErrorResponse, BaseShrineResponse, DefaultBreakdownResultOutputTypes} import net.shrine.protocol.RawCrcRunQueryResponse.toQueryResultMap import net.shrine.protocol.DefaultBreakdownResultOutputTypes.PATIENT_AGE_COUNT_XML import net.shrine.protocol.ResultOutputType.PATIENT_COUNT_XML import net.shrine.protocol.DefaultBreakdownResultOutputTypes.PATIENT_GENDER_COUNT_XML import net.shrine.protocol.DefaultBreakdownResultOutputTypes.PATIENT_RACE_COUNT_XML import net.shrine.protocol.query.OccuranceLimited import net.shrine.protocol.query.Or import net.shrine.protocol.query.QueryDefinition import net.shrine.protocol.query.Term import net.shrine.util.XmlDateHelper import net.shrine.util.XmlUtil import scala.util.Success import net.shrine.dao.squeryl.SquerylEntryPoint import scala.concurrent.duration.Duration import net.shrine.adapter.dao.model.ShrineError import net.shrine.adapter.dao.model.QueryResultRow /** * @author Bill Simons * @author Clint Gilbert * @since 4/19/11 * @see http://cbmi.med.harvard.edu */ final class RunQueryAdapterTest extends AbstractSquerylAdapterTest with ShouldMatchersForJUnit { private val queryDef = QueryDefinition("foo", Term("foo")) private val broadcastMessageId = 1234563789L private val queryId = 123L private val expectedNetworkQueryId = 999L private val expectedLocalMasterId = queryId.toString private val masterId = 99L private val instanceId = 456L private val resultId = 42L private val projectId = "projectId" private val setSize = 17L private val xmlResultId = 98765L private val userId = "userId" private val groupId = "groupId" private val topicId = "some-topic-id-123-foo" + private val topicName = "Topic Name" private val justCounts = Set(PATIENT_COUNT_XML) private val now = XmlDateHelper.now private val countQueryResult = QueryResult(resultId, instanceId, Some(PATIENT_COUNT_XML), setSize, Some(now), Some(now), None, QueryResult.StatusType.Finished, None) private val dummyBreakdownData = Map("x" -> 99L, "y" -> 42L, "z" -> 3000L) private val hiveCredentials = HiveCredentials("some-hive-domain", "hive-username", "hive-password", "hive-project") private val authn = AuthenticationInfo("some-domain", "username", Credential("jksafhkjaf", false)) private val adapterLockoutThreshold = 99 private val altI2b2ErrorXml = XmlUtil.stripWhitespace { 1.1 2.4 edu.harvard.i2b2.crc 1.5 i2b2 Hive i2b2_QueryTool 0.2 i2b2 Hive 1 i2b2 Log information DONE Query result instance id 3126 not found }.toString @Test def testProcessRawCrcRunQueryResponseCountQueryOnly: Unit = afterCreatingTables{ val outputTypes = Set(PATIENT_COUNT_XML) val translator = new QueryDefinitionTranslator(new ExpressionTranslator(Map("network" -> Set("local1a", "local1b")))) val adapter = new RunQueryAdapter( Poster("crc-url", null), dao, hiveCredentials, translator, adapterLockoutThreshold, doObfuscation = false, runQueriesImmediately = true, breakdownTypes = DefaultBreakdownResultOutputTypes.toSet, collectAdapterAudit = false ) - val request = RunQueryRequest(projectId, 1.second, authn, expectedNetworkQueryId, Option(topicId), outputTypes, queryDef) + val request = RunQueryRequest(projectId, 1.second, authn, expectedNetworkQueryId, Option(topicId), Option(topicName), outputTypes, queryDef) val networkAuthn = AuthenticationInfo("some-domain", "username", Credential("sadasdasdasd", false)) val broadcastMessage = BroadcastMessage(queryId, networkAuthn, request) val rawRunQueryResponse = RawCrcRunQueryResponse( queryId = queryId, createDate = XmlDateHelper.now, userId = request.authn.username, groupId = request.authn.domain, requestXml = request.queryDefinition, queryInstanceId = 12345L, singleNodeResults = toQueryResultMap(Seq(countQueryResult))) val resp = adapter.processRawCrcRunQueryResponse(networkAuthn, request, rawRunQueryResponse).asInstanceOf[RunQueryResponse] resp should not be (null) //Validate the response resp.createDate should not be(null) resp.groupId should be(request.authn.domain) resp.userId should be(request.authn.username) resp.queryId should be(queryId) resp.queryInstanceId should be(12345L) resp.requestXml should equal(request.queryDefinition) (countQueryResult eq resp.singleNodeResult) should be(false) within3(resp.singleNodeResult.setSize, countQueryResult.setSize) should be(true) resp.singleNodeResult.resultType.get should equal(PATIENT_COUNT_XML) resp.singleNodeResult.breakdowns should equal(Map.empty) //validate the DB val expectedNetworkTerm = queryDef.expr.get.asInstanceOf[Term] //We should have one row in the shrine_query table, for the query just performed val Seq(queryRow) = list(queryRows) { queryRow.dateCreated should not be (null) queryRow.domain should equal(request.authn.domain) queryRow.name should equal(queryDef.name) queryRow.localId should equal(expectedLocalMasterId) queryRow.networkId should equal(expectedNetworkQueryId) queryRow.username should equal(authn.username) queryRow.queryDefinition.expr.get should equal(expectedNetworkTerm) } //We should have one row in the count_result table, with the right obfuscated value, which is within the expected amount from the original count val Seq(countRow) = list(countResultRows) { countRow.creationDate should not be (null) countRow.originalValue should equal(countQueryResult.setSize) within3(countRow.obfuscatedValue, countRow.originalValue) should be(true) } } @Test def testProcessRawCrcRunQueryResponseCountAndBreakdownQuery: Unit = afterCreatingTables { val allBreakdownTypes = DefaultBreakdownResultOutputTypes.toSet val breakdownTypes = Seq(PATIENT_GENDER_COUNT_XML) val outputTypes = Set(PATIENT_COUNT_XML) ++ breakdownTypes val translator = new QueryDefinitionTranslator(new ExpressionTranslator(Map("network" -> Set("local1a", "local1b")))) - val request = RunQueryRequest(projectId, 1.second, authn, expectedNetworkQueryId, Option(topicId), outputTypes, queryDef) + val request = RunQueryRequest(projectId, 1.second, authn, expectedNetworkQueryId, Option(topicId), Option(topicName), outputTypes, queryDef) val networkAuthn = AuthenticationInfo("some-domain", "username", Credential("sadasdasdasd", false)) val broadcastMessage = BroadcastMessage(queryId, networkAuthn, request) val breakdownQueryResults = breakdownTypes.zipWithIndex.map { case (rt, i) => countQueryResult.withId(resultId + i + 1).withResultType(rt) } val singleNodeResults = toQueryResultMap(countQueryResult +: breakdownQueryResults) val rawRunQueryResponse = RawCrcRunQueryResponse( queryId = queryId, createDate = XmlDateHelper.now, userId = request.authn.username, groupId = request.authn.domain, requestXml = request.queryDefinition, queryInstanceId = 12345L, singleNodeResults = singleNodeResults) //Set up our mock CRC val poster = Poster("crc-url", new HttpClient { def post(input: String, url: String): HttpResponse = HttpResponse.ok { (RunQueryRequest.fromI2b2String(allBreakdownTypes)(input) orElse ReadResultRequest.fromI2b2String(allBreakdownTypes)(input)).get match { case runQueryReq: RunQueryRequest => rawRunQueryResponse.toI2b2String case readResultReq: ReadResultRequest => ReadResultResponse(xmlResultId = 42L, metadata = breakdownQueryResults.head, data = I2b2ResultEnvelope(PATIENT_GENDER_COUNT_XML, dummyBreakdownData)).toI2b2String case _ => sys.error(s"Unknown request: '$input'") //Fail loudly } } }) val adapter = new RunQueryAdapter( poster, dao, hiveCredentials, translator, adapterLockoutThreshold, doObfuscation = false, runQueriesImmediately = true, breakdownTypes = DefaultBreakdownResultOutputTypes.toSet, collectAdapterAudit = false ) val resp = adapter.processRawCrcRunQueryResponse(networkAuthn, request, rawRunQueryResponse).asInstanceOf[RunQueryResponse] resp should not be (null) //Validate the response resp.createDate should not be(null) resp.groupId should be(request.authn.domain) resp.userId should be(request.authn.username) resp.queryId should be(queryId) resp.queryInstanceId should be(12345L) resp.requestXml should equal(request.queryDefinition) (countQueryResult eq resp.singleNodeResult) should be(false) within3(resp.singleNodeResult.setSize, countQueryResult.setSize) should be(true) resp.singleNodeResult.resultType.get should equal(PATIENT_COUNT_XML) resp.singleNodeResult.breakdowns.keySet should equal(Set(PATIENT_GENDER_COUNT_XML)) val breakdownEnvelope = resp.singleNodeResult.breakdowns.values.head breakdownEnvelope.resultType should equal(PATIENT_GENDER_COUNT_XML) breakdownEnvelope.data.keySet should equal(dummyBreakdownData.keySet) //All breakdowns are obfuscated for { (key, value) <- breakdownEnvelope.data } { within3(value, dummyBreakdownData(key)) should be(true) } //validate the DB val expectedNetworkTerm = queryDef.expr.get.asInstanceOf[Term] //We should have one row in the shrine_query table, for the query just performed val Seq(queryRow) = list(queryRows) { queryRow.dateCreated should not be (null) queryRow.domain should equal(request.authn.domain) queryRow.name should equal(queryDef.name) queryRow.localId should equal(expectedLocalMasterId) queryRow.networkId should equal(expectedNetworkQueryId) queryRow.username should equal(authn.username) queryRow.queryDefinition.expr.get should equal(expectedNetworkTerm) } //We should have one row in the count_result table, with the right obfuscated value, which is within the expected amount from the original count val Seq(countRow) = list(countResultRows) { countRow.creationDate should not be (null) countRow.originalValue should equal(countQueryResult.setSize) within3(countRow.obfuscatedValue, countRow.originalValue) should be(true) } val breakdownRows @ Seq(xRow, yRow, zRow) = list(breakdownResultRows) breakdownRows.map(_.dataKey).toSet should equal(dummyBreakdownData.keySet) within3(xRow.obfuscatedValue, xRow.originalValue) should be(true) xRow.originalValue should be(dummyBreakdownData(xRow.dataKey)) within3(yRow.obfuscatedValue, yRow.originalValue) should be(true) yRow.originalValue should be(dummyBreakdownData(yRow.dataKey)) within3(zRow.obfuscatedValue, zRow.originalValue) should be(true) zRow.originalValue should be(dummyBreakdownData(zRow.dataKey)) } //NB: See https://open.med.harvard.edu/jira/browse/SHRINE-745 @Test def testParseAltErrorXml { val adapter = new RunQueryAdapter( Poster("crc-url", null), null, hiveCredentials, null, adapterLockoutThreshold, doObfuscation = false, runQueriesImmediately = false, DefaultBreakdownResultOutputTypes.toSet, collectAdapterAudit = false ) val resp = adapter.parseShrineErrorResponseWithFallback(altI2b2ErrorXml).asInstanceOf[ErrorResponse] resp should not be (null) resp.errorMessage should be("Query result instance id 3126 not found") } @Test def testParseErrorXml { val xml = { 1.1 2.4 edu.harvard.i2b2.crc 1.4 i2b2 Hive i2b2web 1.4 i2b2 Hive 1 Demo Log information Message error connecting Project Management cell admin 0 0 CRC_QRY_runQueryInstance_fromQueryDefinition Age 0 1 0 0 1 2 Age \\i2b2\i2b2\Demographics\Age\ concept_dimension concept_path \i2b2\Demographics\Age\ T concept_cd false }.toString val adapter = new RunQueryAdapter( Poster("crc-url", null), null, hiveCredentials, null, adapterLockoutThreshold, doObfuscation = false, runQueriesImmediately = true, DefaultBreakdownResultOutputTypes.toSet, collectAdapterAudit = false ) val resp = adapter.parseShrineErrorResponseWithFallback(xml).asInstanceOf[ErrorResponse] resp should not be (null) resp.errorMessage should not be ("") } @Test def testObfuscateBreakdowns { val breakdown1 = I2b2ResultEnvelope(PATIENT_AGE_COUNT_XML, Map.empty) val breakdown2 = I2b2ResultEnvelope(PATIENT_GENDER_COUNT_XML, Map("foo" -> 123, "bar" -> 345)) val breakdown3 = I2b2ResultEnvelope(PATIENT_RACE_COUNT_XML, Map("x" -> 999, "y" -> 888)) val original = Map.empty ++ Seq(breakdown1, breakdown2, breakdown3).map(env => (env.resultType, env)) val obfuscated = RunQueryAdapter.obfuscateBreakdowns(original) original.keySet should equal(obfuscated.keySet) original.keySet.forall(resultType => original(resultType).data.keySet == obfuscated(resultType).data.keySet) should be(true) val localTerms = Set("local1a", "local1b") for { (resultType, origBreakdown) <- original mappings = Map("network" -> localTerms) translator = new QueryDefinitionTranslator(new ExpressionTranslator(mappings)) obfscBreakdown <- obfuscated.get(resultType) key <- origBreakdown.data.keySet } { (origBreakdown eq obfscBreakdown) should be(false) ObfuscatorTest.within3(origBreakdown.data(key), obfscBreakdown.data(key)) should be(true) } } @Test def testTranslateNetworkToLocalDoesntLeakCredentialsViaException: Unit = { val mappings = Map.empty[String, Set[String]] val translator = new QueryDefinitionTranslator(new ExpressionTranslator(mappings)) val adapter = new RunQueryAdapter( Poster("crc-url", MockHttpClient), null, null, translator, adapterLockoutThreshold, doObfuscation = false, runQueriesImmediately = true, DefaultBreakdownResultOutputTypes.toSet, collectAdapterAudit = false ) val queryDefinition = QueryDefinition("foo", Term("blah")) val authn = AuthenticationInfo("d", "u", Credential("p", false)) - val req = RunQueryRequest("projectId", Duration.Inf, authn, 12345L, None, Set.empty, queryDef) + val req = RunQueryRequest("projectId", Duration.Inf, authn, 12345L, None, None, Set.empty, queryDef) try { adapter.translateNetworkToLocal(req) fail("Expected an AdapterMappingException") } catch { case e: AdapterMappingException => { e.getMessage.contains(authn.rawToString) should be(false) e.getMessage.contains(AuthenticationInfo.elided.toString) should be(true) } } } @Test def testTranslateQueryDefinitionXml { val localTerms = Set("local1a", "local1b") val mappings = Map("network" -> localTerms) val translator = new QueryDefinitionTranslator(new ExpressionTranslator(mappings)) val adapter = new RunQueryAdapter( Poster("crc-url", MockHttpClient), null, null, translator, adapterLockoutThreshold, doObfuscation = false, runQueriesImmediately = true, DefaultBreakdownResultOutputTypes.toSet, collectAdapterAudit = false ) val queryDefinition = QueryDefinition("10-17 years old@14:39:20", OccuranceLimited(1, Term("network"))) val newDef = adapter.conceptTranslator.translate(queryDefinition) val expected = QueryDefinition("10-17 years old@14:39:20", Or(Term("local1a"), Term("local1b"))) newDef should equal(expected) } @Test def testQueuedRegularCountQuery: Unit = afterCreatingTables { val adapter = RunQueryAdapter( Poster("crc-url", MockHttpClient), dao, null, null, adapterLockoutThreshold, doObfuscation = false, runQueriesImmediately = false, DefaultBreakdownResultOutputTypes.toSet, collectAdapterAudit = false ) val networkAuthn = AuthenticationInfo("nd", "nu", Credential("np", false)) import scala.concurrent.duration._ - val req = RunQueryRequest(projectId, 1.second, authn, expectedNetworkQueryId, Option(topicId), Set(PATIENT_COUNT_XML), queryDef) + val req = RunQueryRequest(projectId, 1.second, authn, expectedNetworkQueryId, Option(topicId), Option(topicName), Set(PATIENT_COUNT_XML), queryDef) val broadcastMessage = BroadcastMessage(queryId, networkAuthn, req) val resp = adapter.processRequest(broadcastMessage).asInstanceOf[RunQueryResponse] resp.groupId should equal(networkAuthn.domain) resp.createDate should not be (null) // :\ resp.queryId should equal(-1L) resp.queryInstanceId should equal(-1L) resp.requestXml should equal(queryDef) resp.userId should equal(networkAuthn.username) resp.singleNodeResult.breakdowns should equal(Map.empty) resp.singleNodeResult.description.isDefined should be(true) resp.singleNodeResult.elapsed should equal(Some(0L)) resp.singleNodeResult.endDate.isDefined should be(true) resp.singleNodeResult.startDate.isDefined should be(true) resp.singleNodeResult.instanceId should equal(-1L) resp.singleNodeResult.isError should be(false) resp.singleNodeResult.resultId should equal(-1L) resp.singleNodeResult.resultType should be(Some(PATIENT_COUNT_XML)) resp.singleNodeResult.setSize should equal(-1L) resp.singleNodeResult.statusMessage.isDefined should be(true) resp.singleNodeResult.statusType should be(QueryResult.StatusType.Held) resp.singleNodeResult.endDate.isDefined should be(true) val Some(storedQuery) = dao.findQueryByNetworkId(expectedNetworkQueryId) storedQuery.dateCreated should not be (null) // :\ storedQuery.domain should equal(networkAuthn.domain) storedQuery.hasBeenRun should equal(false) storedQuery.isFlagged should equal(false) storedQuery.localId should equal(-1L.toString) storedQuery.name should equal(queryDef.name) storedQuery.networkId should equal(expectedNetworkQueryId) storedQuery.queryDefinition should equal(queryDef) storedQuery.username should equal(networkAuthn.username) } private def doTestRegularCountQuery(status: QueryResult.StatusType, count: Long) = afterCreatingTables { require(!status.isError) val countQueryResultToUse = countQueryResult.copy(statusType = status, setSize = count) val outputTypes = justCounts val resp = doQuery(outputTypes) { import RawCrcRunQueryResponse.toQueryResultMap RawCrcRunQueryResponse(queryId, now, userId, groupId, queryDef, instanceId, toQueryResultMap(Seq(countQueryResultToUse))).toI2b2String }.asInstanceOf[RunQueryResponse] doBasicRunQueryResponseTest(resp) val firstResult = resp.results.head resp.results should equal(Seq(firstResult)) val Some(savedQuery) = dao.findResultsFor(expectedNetworkQueryId) savedQuery.wasRun should equal(true) savedQuery.isFlagged should equal(false) savedQuery.networkQueryId should equal(expectedNetworkQueryId) savedQuery.breakdowns should equal(Nil) savedQuery.count.creationDate should not be (null) savedQuery.count.localId should equal(countQueryResultToUse.resultId) //savedQuery.count.resultId should equal(resultId) TODO: REVISIT savedQuery.count.statusType should equal(status) if (status.isDone && !status.isError) { savedQuery.count.data.get.startDate should not be (null) savedQuery.count.data.get.endDate should not be (null) savedQuery.count.data.get.originalValue should be(count) ObfuscatorTest.within3(savedQuery.count.data.get.obfuscatedValue, count) should be(true) } else { savedQuery.count.data should be(None) } } @Test def testRegularCountQuery = doTestRegularCountQuery(QueryResult.StatusType.Finished, countQueryResult.setSize) @Test def testRegularCountQueryComesBackProcessing = doTestRegularCountQuery(QueryResult.StatusType.Processing, -1L) @Test def testRegularCountQueryComesBackQueued = doTestRegularCountQuery(QueryResult.StatusType.Queued, -1L) @Test def testRegularCountQueryComesBackError = afterCreatingTables { val errorQueryResult = QueryResult.errorResult(Some("some-description"), "some-status-message") val outputTypes = justCounts val resp = doQuery(outputTypes) { import RawCrcRunQueryResponse.toQueryResultMap RawCrcRunQueryResponse(queryId, now, userId, groupId, queryDef, instanceId, toQueryResultMap(Seq(errorQueryResult))).toI2b2String } doBasicRunQueryResponseTest(resp) //TODO: Why are status and description messages from CRC dropped when unmarshalling QueryResults? //resp.results should equal(Seq(errorQueryResult)) resp.asInstanceOf[RunQueryResponse].results.head.statusType should be(QueryResult.StatusType.Error) dao.findResultsFor(expectedNetworkQueryId) should be(None) val Some(savedQueryRow) = dao.findQueryByNetworkId(expectedNetworkQueryId) val Seq(queryResultRow: QueryResultRow) = { import SquerylEntryPoint._ implicit val breakdownTypes = DefaultBreakdownResultOutputTypes.toSet inTransaction { from(tables.queryResults) { row => where(row.queryId === savedQueryRow.id). select(row.toQueryResultRow) }.toSeq } } val Seq(errorRow: ShrineError) = { import SquerylEntryPoint._ inTransaction { from(tables.errorResults) { row => where(row.resultId === queryResultRow.id). select(row.toShrineError) }.toSeq } } errorRow should not be (null) //TODO: //errorRow.message should equal(errorQueryResult.statusMessage) } private def doTestBreakdownsAreObfuscated(result: QueryResult): Unit = { result.breakdowns.values.map(_.data).foreach { actualBreakdowns => actualBreakdowns.keySet should equal(dummyBreakdownData.keySet) for { breakdownName <- actualBreakdowns.keySet } { within3(actualBreakdowns(breakdownName), dummyBreakdownData(breakdownName)) should be(true) } } } @Test def testGetBreakdownsWithRegularCountQuery { val breakdowns = DefaultBreakdownResultOutputTypes.values.map(breakdownFor) val resp = doTestGetBreakdowns(breakdowns) val firstResult = resp.results.head firstResult.resultType should equal(Some(PATIENT_COUNT_XML)) firstResult.setSize should equal(setSize) firstResult.description should equal(None) firstResult.breakdowns.keySet should equal(DefaultBreakdownResultOutputTypes.toSet) //NB: Verify that breakdowns are obfuscated doTestBreakdownsAreObfuscated(firstResult) resp.results.size should equal(1) } @Test def testGetBreakdownsSomeFailures { val resultTypesExpectedToSucceed = Seq(PATIENT_AGE_COUNT_XML, PATIENT_GENDER_COUNT_XML) val breakdowns = resultTypesExpectedToSucceed.map(breakdownFor) val resp = doTestGetBreakdowns(breakdowns) val firstResult = resp.results.head firstResult.resultType should equal(Some(PATIENT_COUNT_XML)) firstResult.setSize should equal(setSize) firstResult.description should equal(None) firstResult.breakdowns.keySet should equal(resultTypesExpectedToSucceed.toSet) //NB: Verify that breakdowns are obfuscated doTestBreakdownsAreObfuscated(firstResult) resp.results.size should equal(1) } @Test def testErrorResponsesArePassedThrough: Unit = { val errorResponse = ErrorResponse("blarg!") val resp = doQuery(Set(PATIENT_COUNT_XML)) { errorResponse.toI2b2String } resp should equal(errorResponse) } private def breakdownFor(resultType: ResultOutputType) = I2b2ResultEnvelope(resultType, dummyBreakdownData) private def doTestGetBreakdowns(successfulBreakdowns: Seq[I2b2ResultEnvelope]): RunQueryResponse = { val outputTypes = justCounts ++ DefaultBreakdownResultOutputTypes.toSet val resp = doQueryThatReturnsSpecifiedBreakdowns(outputTypes, successfulBreakdowns) doBasicRunQueryResponseTest(resp) resp } private def doBasicRunQueryResponseTest(r: BaseShrineResponse) { val resp = r.asInstanceOf[RunQueryResponse] resp.createDate should equal(now) resp.groupId should equal(groupId) resp.queryId should equal(queryId) resp.queryInstanceId should equal(instanceId) resp.queryName should equal(queryDef.name) resp.requestXml should equal(queryDef) } private def doQueryThatReturnsSpecifiedBreakdowns(outputTypes: Set[ResultOutputType], successfulBreakdowns: Seq[I2b2ResultEnvelope]): RunQueryResponse = afterCreatingTablesReturn { val breakdownQueryResults = DefaultBreakdownResultOutputTypes.values.zipWithIndex.map { case (rt, i) => countQueryResult.withId(resultId + i + 1).withResultType(rt) } //Need this rigamarole to ensure that resultIds line up such that the type of breakdown the adapter asks for //(PATIENT_AGE_COUNT_XML, etc) is what the mock HttpClient actually returns. Here, we build up maps of QueryResults //and I2b2ResultEnvelopes, keyed on resultIds generated in the previous expression, to use to look up values to use //to build ReadResultResponses val successfulBreakdownsByType = successfulBreakdowns.map(e => e.resultType -> e).toMap val successfulBreakdownTypes = successfulBreakdownsByType.keySet val breakdownQueryResultsByResultId = breakdownQueryResults.collect { case qr if successfulBreakdownTypes(qr.resultType.get) => qr.resultId -> qr }.toMap val breakdownsToBeReturnedByResultId = breakdownQueryResultsByResultId.map { case (resultId, queryResult) => (resultId, successfulBreakdownsByType(queryResult.resultType.get)) } val expectedLocalTerm = Term("bar") val httpClient = new HttpClient { override def post(input: String, url: String): HttpResponse = { val resp = CrcRequest.fromI2b2String(DefaultBreakdownResultOutputTypes.toSet)(input) match { case Success(req: RunQueryRequest) => { //NB: Terms should be translated req.queryDefinition.expr.get should equal(expectedLocalTerm) //Credentials should be "translated" req.authn.username should equal(hiveCredentials.username) req.authn.domain should equal(hiveCredentials.domain) //I2b2 Project ID should be translated req.projectId should equal(hiveCredentials.projectId) val queryResultMap = RawCrcRunQueryResponse.toQueryResultMap(countQueryResult +: breakdownQueryResults) RawCrcRunQueryResponse(queryId, now, "userId", "groupId", queryDef, instanceId, queryResultMap) } //NB: return a ReadResultResponse with new breakdown data each time, but will throw if the asked-for breakdown //is not one of the ones passed to the enclosing method, simulating an error calling the CRC case Success(req: ReadResultRequest) => { val resultId = req.localResultId.toLong ReadResultResponse(xmlResultId, breakdownQueryResultsByResultId(resultId), breakdownsToBeReturnedByResultId(resultId)) } case _ => ??? //fail loudly } HttpResponse.ok(resp.toI2b2String) } } val result = doQuery(outputTypes, dao, httpClient) validateDb(successfulBreakdowns, breakdownQueryResultsByResultId) result.asInstanceOf[RunQueryResponse] } private def validateDb(breakdownsReturned: Seq[I2b2ResultEnvelope], breakdownQueryResultsByResultId: Map[Long, QueryResult]) { val expectedNetworkTerm = Term("foo") //We should have one row in the shrine_query table, for the query just performed val queryRow = first(queryRows) { queryRow.dateCreated should not be (null) queryRow.domain should equal(authn.domain) queryRow.name should equal(queryDef.name) queryRow.localId should equal(expectedLocalMasterId) queryRow.networkId should equal(expectedNetworkQueryId) queryRow.username should equal(authn.username) queryRow.queryDefinition.expr.get should equal(expectedNetworkTerm) } list(queryRows).size should equal(1) //We should have one row in the count_result table, with the right obfuscated value, which is within the expected amount from the original count val countRow = first(countResultRows) { countRow.creationDate should not be (null) countRow.originalValue should equal(countQueryResult.setSize) within3(countRow.obfuscatedValue, countQueryResult.setSize) should be(true) within3(countRow.obfuscatedValue, countRow.originalValue) should be(true) } list(countResultRows).size should equal(1) //We should have 5 rows in the query_result table, one for the count result and one for each of the 4 requested breakdown types val queryResults = list(queryResultRows) { val countQueryResultRow = queryResults.find(_.resultType == PATIENT_COUNT_XML).get countQueryResultRow.localId should equal(countQueryResult.resultId) countQueryResultRow.queryId should equal(queryRow.id) val resultIdsByResultType = breakdownQueryResultsByResultId.map { case (resultId, queryResult) => queryResult.resultType.get -> resultId }.toMap for (breakdownType <- DefaultBreakdownResultOutputTypes.values) { val breakdownQueryResultRow = queryResults.find(_.resultType == breakdownType).get breakdownQueryResultRow.queryId should equal(queryRow.id) //We'll have a result id if this breakdown type didn't fail if (resultIdsByResultType.contains(breakdownQueryResultRow.resultType)) { breakdownQueryResultRow.localId should equal(resultIdsByResultType(breakdownQueryResultRow.resultType)) } } } queryResults.size should equal(5) val returnedBreakdownTypes = breakdownsReturned.map(_.resultType).toSet val notReturnedBreakdownTypes = DefaultBreakdownResultOutputTypes.toSet -- returnedBreakdownTypes val errorResults = list(errorResultRows) //We should have a row in the error_result table for each breakdown that COULD NOT be retrieved { for { queryResult <- queryResults if notReturnedBreakdownTypes.contains(queryResult.resultType) resultType = queryResult.resultType resultId = queryResult.id } { errorResults.find(_.resultId == resultId).isDefined should be(true) } } errorResults.size should equal(notReturnedBreakdownTypes.size) //We should have properly-obfuscated rows in the breakdown_result table for each of the breakdown types that COULD be retrieved val breakdownResults = list(breakdownResultRows) val bdrs = breakdownResults.toIndexedSeq { for { queryResult <- queryResults if returnedBreakdownTypes.contains(queryResult.resultType) resultType = queryResult.resultType resultId = queryResult.id } { //Find all the rows for a particular breakdown type val rowsWithType = breakdownResults.filter(_.resultId == resultId) //Combining the rows should give the expected dummy data rowsWithType.map(row => row.dataKey -> row.originalValue).toMap should equal(dummyBreakdownData) for (breakdownRow <- rowsWithType) { within3(breakdownRow.obfuscatedValue, dummyBreakdownData(breakdownRow.dataKey)) should be(true) } } } } private def doQuery(outputTypes: Set[ResultOutputType])(i2b2XmlToReturn: => String): BaseShrineResponse = { doQuery(outputTypes, dao, MockHttpClient(i2b2XmlToReturn)) } private def doQuery(outputTypes: Set[ResultOutputType], adapterDao: AdapterDao, httpClient: HttpClient): BaseShrineResponse = { val translator = new QueryDefinitionTranslator(new ExpressionTranslator(Map("foo" -> Set("bar")))) //NB: Don't obfuscate, for simpler testing val adapter = new RunQueryAdapter( Poster("crc-url", httpClient), adapterDao, hiveCredentials, translator, adapterLockoutThreshold, doObfuscation = false, runQueriesImmediately = true, DefaultBreakdownResultOutputTypes.toSet, collectAdapterAudit = false ) import scala.concurrent.duration._ - val req = RunQueryRequest(projectId, 1.second, authn, expectedNetworkQueryId, Option(topicId), outputTypes, queryDef) + val req = RunQueryRequest(projectId, 1.second, authn, expectedNetworkQueryId, Option(topicId), Option(topicName), outputTypes, queryDef) val networkAuthn = AuthenticationInfo("some-domain", "username", Credential("sadasdasdasd", false)) val broadcastMessage = BroadcastMessage(queryId, networkAuthn, req) adapter.processRequest(broadcastMessage) } } \ No newline at end of file diff --git a/adapter/adapter-service/src/test/scala/net/shrine/adapter/service/I2b2AdminResourceEndToEndJaxrsTest.scala b/adapter/adapter-service/src/test/scala/net/shrine/adapter/service/I2b2AdminResourceEndToEndJaxrsTest.scala index 45d289aa5..f7fa9d870 100644 --- a/adapter/adapter-service/src/test/scala/net/shrine/adapter/service/I2b2AdminResourceEndToEndJaxrsTest.scala +++ b/adapter/adapter-service/src/test/scala/net/shrine/adapter/service/I2b2AdminResourceEndToEndJaxrsTest.scala @@ -1,188 +1,188 @@ package net.shrine.adapter.service import org.junit.Test import net.shrine.adapter.HasI2b2AdminDao import net.shrine.protocol.{HiveCredentials, ReadI2b2AdminPreviousQueriesRequest, ReadI2b2AdminQueryingUsersRequest, ReadI2b2AdminQueryingUsersResponse, I2b2AdminUserWithRole, ErrorResponse, RunHeldQueryRequest, RunQueryResponse, RunQueryRequest, ResultOutputType, QueryResult, BroadcastMessage, AuthenticationInfo, Credential, DefaultBreakdownResultOutputTypes} import net.shrine.client.Poster import net.shrine.adapter.RunQueryAdapter import net.shrine.adapter.translators.QueryDefinitionTranslator import net.shrine.adapter.translators.ExpressionTranslator import net.shrine.client.HttpClient import net.shrine.client.HttpResponse import net.shrine.protocol.query.Term import scala.util.Success import net.shrine.util.XmlDateHelper import net.shrine.protocol.query.QueryDefinition /** * @author clint - * @date Apr 12, 2013 + * @since Apr 12, 2013 * * NB: Ideally we would extend JerseyTest here, but since we have to extend AbstractDependencyInjectionSpringContextTests, * we get into a diamond-problem when extending JerseyTest as well, even when both of them are extended by shim traits. * * We work around this issue by mising in JerseyTestCOmponent, which brings in a JerseyTest by composition, and ensures * that it is set up and torn down properly. */ final class I2b2AdminResourceEndToEndJaxrsTest extends AbstractI2b2AdminResourceJaxrsTest with HasI2b2AdminDao { private[this] val dummyUrl = "http://example.com" private[this] val dummyText = "This is dummy text" private[this] val dummyMasterId = 873456L private[this] val dummyInstanceId = 99L private[this] val dummyResultId = 42L private[this] val dummySetSize = 12345L private[this] val networkAuthn = AuthenticationInfo("network-domain", "network-username", Credential("network-password", false)) private lazy val runQueryAdapter: RunQueryAdapter = { val translator = new QueryDefinitionTranslator(new ExpressionTranslator(Map("n1" -> Set("l1")))) val poster = new Poster(dummyUrl, new HttpClient { override def post(input: String, url: String): HttpResponse = { RunQueryRequest.fromI2b2String(DefaultBreakdownResultOutputTypes.toSet)(input) match { case Success(req) => { val queryResult = QueryResult(dummyResultId, dummyInstanceId, Some(ResultOutputType.PATIENT_COUNT_XML), dummySetSize, Some(XmlDateHelper.now), Some(XmlDateHelper.now), Some("desc"), QueryResult.StatusType.Finished, Some("status")) val resp = RunQueryResponse(dummyMasterId, XmlDateHelper.now, networkAuthn.username, networkAuthn.domain, req.queryDefinition, 123L, queryResult) HttpResponse.ok(resp.toI2b2String) } case _ => ??? } } }) RunQueryAdapter( poster, dao, HiveCredentials("d", "u", "pwd", "pid"), translator, 1000, false, true, DefaultBreakdownResultOutputTypes.toSet, collectAdapterAudit = false ) } override def makeHandler = new I2b2AdminService(dao, i2b2AdminDao, Poster(dummyUrl, AlwaysAuthenticatesMockPmHttpClient), runQueryAdapter) @Test def testReadQueryDefinition = afterLoadingTestData { doTestReadQueryDefinition(networkQueryId1, Some((queryName1, queryDef1))) } @Test def testReadQueryDefinitionUnknownQueryId = afterLoadingTestData { doTestReadQueryDefinition(87134682364L, None) } import ReadI2b2AdminPreviousQueriesRequest.{Username, Category, SortOrder} import Username._ @Test def testReadI2b2AdminPreviousQueries = afterLoadingTestData { val searchString = queryName1 val maxResults = 123 val sortOrder = ReadI2b2AdminPreviousQueriesRequest.SortOrder.Ascending val categoryToSearchWithin = ReadI2b2AdminPreviousQueriesRequest.Category.All val searchStrategy = ReadI2b2AdminPreviousQueriesRequest.Strategy.Exact val request = ReadI2b2AdminPreviousQueriesRequest(projectId, waitTime, authn, All, searchString, maxResults, None, sortOrder, searchStrategy, categoryToSearchWithin) doTestReadI2b2AdminPreviousQueries(request, Seq(queryMaster1)) } @Test def testReadI2b2AdminPreviousQueriesNoResultsExpected = afterLoadingTestData { //A request that won't return anything val request = ReadI2b2AdminPreviousQueriesRequest(projectId, waitTime, authn, All, "askjdhakfgkafgkasf", 123, None) doTestReadI2b2AdminPreviousQueries(request, Nil) } @Test def testReadI2b2AdminPreviousQueriesExcludeUser: Unit = afterLoadingTestData { val request = ReadI2b2AdminPreviousQueriesRequest(projectId, waitTime, authn, Except(authn2.username), "", 10, None) doTestReadI2b2AdminPreviousQueries(request, Seq(queryMaster2, queryMaster1)) } @Test def testReadI2b2AdminPreviousQueriesOnlyFlagged: Unit = afterLoadingTestData { val request = ReadI2b2AdminPreviousQueriesRequest(projectId, waitTime, authn, All, "", 10, None, categoryToSearchWithin = Category.Flagged) doTestReadI2b2AdminPreviousQueries(request, Seq(queryMaster4, queryMaster1)) } @Test def testReadPreviousQueriesOnlyFlaggedExcludingUser: Unit = afterLoadingTestData { val request = ReadI2b2AdminPreviousQueriesRequest(projectId, waitTime, authn, Except(authn.username), "", 10, None, categoryToSearchWithin = Category.Flagged) doTestReadI2b2AdminPreviousQueries(request, Seq(queryMaster4)) } @Test def testReadPreviousQueriesExcludingUserWithSearchString: Unit = afterLoadingTestData { val request = ReadI2b2AdminPreviousQueriesRequest(projectId, waitTime, authn, All, queryName1, 10, None, categoryToSearchWithin = Category.Flagged) doTestReadI2b2AdminPreviousQueries(request, Seq(queryMaster1)) } @Test def testReadI2b2QueryingUsers = afterLoadingTestData { val request = ReadI2b2AdminQueryingUsersRequest(projectId, waitTime, authn, "foo") val ReadI2b2AdminQueryingUsersResponse(users) = adminClient.readI2b2AdminQueryingUsers(request) users.toSet should equal(Set(I2b2AdminUserWithRole(shrineProjectId, authn.username, "USER"), I2b2AdminUserWithRole(shrineProjectId, authn2.username, "USER"))) } @Test def testReadI2b2QueryingUsersNoResultsExpected = afterCreatingTables { val request = ReadI2b2AdminQueryingUsersRequest(projectId, waitTime, authn, "foo") val ReadI2b2AdminQueryingUsersResponse(users) = adminClient.readI2b2AdminQueryingUsers(request) //DB is empty, so no users will be returned users should equal(Nil) } @Test def testRunHeldQueryUnknownQuery = afterCreatingTables { val request = RunHeldQueryRequest(projectId, waitTime, authn, 12345L) val resp = adminClient.runHeldQuery(request) resp.isInstanceOf[ErrorResponse] should be(true) } @Test def testRunHeldQueryKnownQuery = afterCreatingTables { val networkQueryId = 12345L val request = RunHeldQueryRequest(projectId, waitTime, authn, networkQueryId) val queryName = "aslkdjasljkd" val queryExpr = Term("n1") - val runQueryReq = RunQueryRequest(projectId, waitTime, authn, networkQueryId, None, Set(ResultOutputType.PATIENT_COUNT_XML), QueryDefinition(queryName, queryExpr)) + val runQueryReq = RunQueryRequest(projectId, waitTime, authn, networkQueryId, None, None, Set(ResultOutputType.PATIENT_COUNT_XML), QueryDefinition(queryName, queryExpr)) runQueryAdapter.copy(runQueriesImmediately = false).processRequest(BroadcastMessage(networkAuthn, runQueryReq)) val resp = adminClient.runHeldQuery(request) val runQueryResp = resp.asInstanceOf[RunQueryResponse] runQueryResp.createDate should not be(null) runQueryResp.groupId should be(networkAuthn.domain) runQueryResp.userId should equal(networkAuthn.username) runQueryResp.queryId should equal(dummyMasterId) runQueryResp.singleNodeResult.setSize should equal(dummySetSize) runQueryResp.singleNodeResult.resultType should equal(Some(ResultOutputType.PATIENT_COUNT_XML)) //TODO runQueryResp.requestXml.name should equal(queryName) runQueryResp.requestXml.expr.get should equal(Term("l1")) } } diff --git a/apps/admin-app/src/main/js/happy/happy-adapter.xml b/apps/admin-app/src/main/js/happy/happy-adapter.xml index f46ee9b6a..613aaf0dd 100644 --- a/apps/admin-app/src/main/js/happy/happy-adapter.xml +++ b/apps/admin-app/src/main/js/happy/happy-adapter.xml @@ -1,13 +1,13 @@ NodeId(shrine-qa1) 9 milliseconds - Error mapping query terms from network to local forms. request: RunQueryRequest(Demo,3 minutes,AuthenticationInfo(*******,*******,Credential(*******,false)),6623773019075051117,None,Set(PATIENT_COUNT_XML),QueryDefinition(PDD,Some(OccuranceLimited(1,Term(\\SHRINE\SHRINE\Diagnoses\Mental Illness\Disorders usually diagnosed in infancy, childhood, or adolescence\Pervasive developmental disorders\Infantile autism, current or active state\))),Some(ANY),None,None,None,List())) + Error mapping query terms from network to local forms. request: RunQueryRequest(Demo,3 minutes,AuthenticationInfo(*******,*******,Credential(*******,false)),6623773019075051117,None,None,Set(PATIENT_COUNT_XML),QueryDefinition(PDD,Some(OccuranceLimited(1,Term(\\SHRINE\SHRINE\Diagnoses\Mental Illness\Disorders usually diagnosed in infancy, childhood, or adolescence\Pervasive developmental disorders\Infantile autism, current or active state\))),Some(ANY),None,None,None,List())) \ No newline at end of file diff --git a/apps/admin-app/src/main/js/happy/happy-all.xml b/apps/admin-app/src/main/js/happy/happy-all.xml index 984177104..84178210a 100644 --- a/apps/admin-app/src/main/js/happy/happy-all.xml +++ b/apps/admin-app/src/main/js/happy/happy-all.xml @@ -1,220 +1,220 @@ 1.20.0-SNAPSHOT UNKNOWN Unknown (not available) UNKNOWN_BRANCH 2015-07-24 11:50:33 /opt/shrine/shrine.keystore JKS shrine-qa1 CN=shrine-qa1.hms.harvard.edu, OU=SHRINE, O=Harvard Medical School, L=Boston, ST=Massachusetts, C=US 14000929453842384735 CN=shrine-qa1.hms.harvard.edu, OU=SHRINE, O=Harvard Medical School, L=Boston, ST=Massachusetts, C=US 14000929453842384735 shrine-qa4 https://shrine-qa4.hms.harvard.edu:6443/shrine/rest/adapter/requests shrine-qa2 https://shrine-qa2.hms.harvard.edu:6443/shrine/rest/adapter/requests shrine-qa3 https://shrine-qa3.hms.harvard.edu:6443/shrine/rest/adapter/requests http://134.174.149.146/i2b2/services/QueryToolService/ http://134.174.149.146/i2b2/services/OntologyService/ true shrine-qa4 https://shrine-qa4.hms.harvard.edu:6443/shrine/rest/adapter/requests shrine-qa2 https://shrine-qa2.hms.harvard.edu:6443/shrine/rest/adapter/requests shrine-qa3 https://shrine-qa3.hms.harvard.edu:6443/shrine/rest/adapter/requests true 4 4 0 0 shrine-qa4 https://shrine-qa4.hms.harvard.edu:6443/shrine/rest/adapter/requests shrine-qa2 https://shrine-qa2.hms.harvard.edu:6443/shrine/rest/adapter/requests shrine-qa3 https://shrine-qa3.hms.harvard.edu:6443/shrine/rest/adapter/requests NodeId(shrine-qa1) 12 milliseconds - Error mapping query terms from network to local forms. request: RunQueryRequest(Demo,3 minutes,AuthenticationInfo(*******,*******,Credential(*******,false)),4846597226961470790,None,Set(PATIENT_COUNT_XML),QueryDefinition(PDD,Some(OccuranceLimited(1,Term(\\SHRINE\SHRINE\Diagnoses\Mental Illness\Disorders usually diagnosed in infancy, childhood, or adolescence\Pervasive developmental disorders\Infantile autism, current or active state\))),Some(ANY),None,None,None,List())) + Error mapping query terms from network to local forms. request: RunQueryRequest(Demo,3 minutes,AuthenticationInfo(*******,*******,Credential(*******,false)),4846597226961470790,None,None,Set(PATIENT_COUNT_XML),QueryDefinition(PDD,Some(OccuranceLimited(1,Term(\\SHRINE\SHRINE\Diagnoses\Mental Illness\Disorders usually diagnosed in infancy, childhood, or adolescence\Pervasive developmental disorders\Infantile autism, current or active state\))),Some(ANY),None,None,None,List())) 21919 multi 21918 multi 21917 multi 21916 shrine 21915 shrine 21914 shrine 21913 ben 21912 ben 21911 multi 21910 ben 6902629377756732433 2015-07-24T15:24:26.000-04:00 18-34 years old@15:24:22 6133246977110179750 2015-07-24T12:51:11.000-04:00 18-34 years old@12:51:10 8256422801215165846 2015-07-24T12:13:18.000-04:00 Male@12:13:14 8557902601708996934 2015-07-22T16:38:03.000-04:00 0-9 years old@16:37:44 7777183656691231494 2015-07-14T13:23:41.000-04:00 (008.00) Intest@13:23:38 7739657654723985967 2015-07-06T17:37:02.000-04:00 Chinese@17:37:04 600779631669429047 2015-07-06T17:31:30.000-04:00 18-34 years old@17:31:26 3837429920413190323 2015-07-01T15:29:27.000-04:00 Wampanoag@15:29:30 164713467528345031 2015-07-01T14:32:45.000-04:00 (008.00) Intest@00:02:56 3103824735355637345 2015-07-01T14:18:50.000-04:00 Female@23:48:56 \ No newline at end of file diff --git a/apps/admin-app/src/main/resources/client/happy/happy-adapter.xml b/apps/admin-app/src/main/resources/client/happy/happy-adapter.xml index f46ee9b6a..613aaf0dd 100644 --- a/apps/admin-app/src/main/resources/client/happy/happy-adapter.xml +++ b/apps/admin-app/src/main/resources/client/happy/happy-adapter.xml @@ -1,13 +1,13 @@ NodeId(shrine-qa1) 9 milliseconds - Error mapping query terms from network to local forms. request: RunQueryRequest(Demo,3 minutes,AuthenticationInfo(*******,*******,Credential(*******,false)),6623773019075051117,None,Set(PATIENT_COUNT_XML),QueryDefinition(PDD,Some(OccuranceLimited(1,Term(\\SHRINE\SHRINE\Diagnoses\Mental Illness\Disorders usually diagnosed in infancy, childhood, or adolescence\Pervasive developmental disorders\Infantile autism, current or active state\))),Some(ANY),None,None,None,List())) + Error mapping query terms from network to local forms. request: RunQueryRequest(Demo,3 minutes,AuthenticationInfo(*******,*******,Credential(*******,false)),6623773019075051117,None,None,Set(PATIENT_COUNT_XML),QueryDefinition(PDD,Some(OccuranceLimited(1,Term(\\SHRINE\SHRINE\Diagnoses\Mental Illness\Disorders usually diagnosed in infancy, childhood, or adolescence\Pervasive developmental disorders\Infantile autism, current or active state\))),Some(ANY),None,None,None,List())) \ No newline at end of file diff --git a/apps/admin-app/src/main/resources/client/happy/happy-all.xml b/apps/admin-app/src/main/resources/client/happy/happy-all.xml index 984177104..84178210a 100644 --- a/apps/admin-app/src/main/resources/client/happy/happy-all.xml +++ b/apps/admin-app/src/main/resources/client/happy/happy-all.xml @@ -1,220 +1,220 @@ 1.20.0-SNAPSHOT UNKNOWN Unknown (not available) UNKNOWN_BRANCH 2015-07-24 11:50:33 /opt/shrine/shrine.keystore JKS shrine-qa1 CN=shrine-qa1.hms.harvard.edu, OU=SHRINE, O=Harvard Medical School, L=Boston, ST=Massachusetts, C=US 14000929453842384735 CN=shrine-qa1.hms.harvard.edu, OU=SHRINE, O=Harvard Medical School, L=Boston, ST=Massachusetts, C=US 14000929453842384735 shrine-qa4 https://shrine-qa4.hms.harvard.edu:6443/shrine/rest/adapter/requests shrine-qa2 https://shrine-qa2.hms.harvard.edu:6443/shrine/rest/adapter/requests shrine-qa3 https://shrine-qa3.hms.harvard.edu:6443/shrine/rest/adapter/requests http://134.174.149.146/i2b2/services/QueryToolService/ http://134.174.149.146/i2b2/services/OntologyService/ true shrine-qa4 https://shrine-qa4.hms.harvard.edu:6443/shrine/rest/adapter/requests shrine-qa2 https://shrine-qa2.hms.harvard.edu:6443/shrine/rest/adapter/requests shrine-qa3 https://shrine-qa3.hms.harvard.edu:6443/shrine/rest/adapter/requests true 4 4 0 0 shrine-qa4 https://shrine-qa4.hms.harvard.edu:6443/shrine/rest/adapter/requests shrine-qa2 https://shrine-qa2.hms.harvard.edu:6443/shrine/rest/adapter/requests shrine-qa3 https://shrine-qa3.hms.harvard.edu:6443/shrine/rest/adapter/requests NodeId(shrine-qa1) 12 milliseconds - Error mapping query terms from network to local forms. request: RunQueryRequest(Demo,3 minutes,AuthenticationInfo(*******,*******,Credential(*******,false)),4846597226961470790,None,Set(PATIENT_COUNT_XML),QueryDefinition(PDD,Some(OccuranceLimited(1,Term(\\SHRINE\SHRINE\Diagnoses\Mental Illness\Disorders usually diagnosed in infancy, childhood, or adolescence\Pervasive developmental disorders\Infantile autism, current or active state\))),Some(ANY),None,None,None,List())) + Error mapping query terms from network to local forms. request: RunQueryRequest(Demo,3 minutes,AuthenticationInfo(*******,*******,Credential(*******,false)),4846597226961470790,None,None,Set(PATIENT_COUNT_XML),QueryDefinition(PDD,Some(OccuranceLimited(1,Term(\\SHRINE\SHRINE\Diagnoses\Mental Illness\Disorders usually diagnosed in infancy, childhood, or adolescence\Pervasive developmental disorders\Infantile autism, current or active state\))),Some(ANY),None,None,None,List())) 21919 multi 21918 multi 21917 multi 21916 shrine 21915 shrine 21914 shrine 21913 ben 21912 ben 21911 multi 21910 ben 6902629377756732433 2015-07-24T15:24:26.000-04:00 18-34 years old@15:24:22 6133246977110179750 2015-07-24T12:51:11.000-04:00 18-34 years old@12:51:10 8256422801215165846 2015-07-24T12:13:18.000-04:00 Male@12:13:14 8557902601708996934 2015-07-22T16:38:03.000-04:00 0-9 years old@16:37:44 7777183656691231494 2015-07-14T13:23:41.000-04:00 (008.00) Intest@13:23:38 7739657654723985967 2015-07-06T17:37:02.000-04:00 Chinese@17:37:04 600779631669429047 2015-07-06T17:31:30.000-04:00 18-34 years old@17:31:26 3837429920413190323 2015-07-01T15:29:27.000-04:00 Wampanoag@15:29:30 164713467528345031 2015-07-01T14:32:45.000-04:00 (008.00) Intest@00:02:56 3103824735355637345 2015-07-01T14:18:50.000-04:00 Female@23:48:56 \ No newline at end of file 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 e01ac140f..43957f93e 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,318 @@ 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.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("PDD", OccuranceLimited(1, Term(config.adapterStatusQuery))) import scala.concurrent.duration._ RunQueryRequest( "happyProject", 3.minutes, authn, BroadcastMessage.Ids.next, 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/commons/crypto/src/test/scala/net/shrine/crypto/DefaultSignerVerifierTest.scala b/commons/crypto/src/test/scala/net/shrine/crypto/DefaultSignerVerifierTest.scala index eb9d49b4c..a6692d696 100644 --- a/commons/crypto/src/test/scala/net/shrine/crypto/DefaultSignerVerifierTest.scala +++ b/commons/crypto/src/test/scala/net/shrine/crypto/DefaultSignerVerifierTest.scala @@ -1,536 +1,536 @@ package net.shrine.crypto import net.shrine.util.{Base64, ShouldMatchersForJUnit, XmlGcEnrichments} import org.junit.Test import net.shrine.protocol.BroadcastMessage import net.shrine.protocol.AuthenticationInfo import net.shrine.protocol.Credential import net.shrine.protocol.DeleteQueryRequest import net.shrine.protocol.RunQueryRequest import net.shrine.protocol.ResultOutputType import net.shrine.protocol.query.Term import net.shrine.protocol.query.QueryDefinition import net.shrine.protocol.query.Term import net.shrine.protocol.query.Modifiers import net.shrine.protocol.query.Or import net.shrine.protocol.ReadQueryResultRequest import net.shrine.protocol.DefaultBreakdownResultOutputTypes import net.shrine.protocol.query.Constrained import net.shrine.protocol.query.ValueConstraint import net.shrine.protocol.CertId import java.math.BigInteger import java.security.cert.X509Certificate import java.security.cert.CertificateFactory import scala.io.Source import java.io.ByteArrayInputStream /** * @author clint * @since Nov 27, 2013 */ final class DefaultSignerVerifierTest extends ShouldMatchersForJUnit { private val authn = AuthenticationInfo("some-domain", "some-username", Credential("sadkljlajdl", false)) private val certCollection = TestKeystore.certCollection private val signerVerifier = new DefaultSignerVerifier(certCollection) import SigningCertStrategy._ import scala.concurrent.duration._ @Test def testIssuersMatchBetweenCertsWithIPsInDistinguishedNames: Unit = { def readCert(fileName: String): X509Certificate = { val factory = CertificateFactory.getInstance("X.509") val source = Source.fromInputStream(getClass.getClassLoader.getResourceAsStream(fileName)) val encodedCertData = try { source.mkString } finally { source.close() } val byteStream = new ByteArrayInputStream(Base64.fromBase64(encodedCertData)) try { factory.generateCertificate(byteStream).asInstanceOf[X509Certificate] } finally { byteStream.close() } } val ca = readCert("test-caroot.pem") val alpha = readCert("test-alpha-signed.pem") val beta = readCert("test-beta-signed.pem") val gamma = readCert("test-gamma-signed.pem") def shouldMatch[F](field: X509Certificate => F)(a: X509Certificate, b: X509Certificate) { field(a) should equal(field(b)) //Use options to handle null fields Option(field(a)).map(_.hashCode) should equal(Option(field(b)).map(_.hashCode)) } shouldMatch(_.getIssuerDN)(ca, alpha) shouldMatch(_.getIssuerDN)(ca, beta) shouldMatch(_.getIssuerDN)(ca, gamma) shouldMatch(_.getIssuerX500Principal)(ca, alpha) shouldMatch(_.getIssuerX500Principal)(ca, beta) shouldMatch(_.getIssuerX500Principal)(ca, gamma) shouldMatch(_.getIssuerUniqueID)(ca, alpha) shouldMatch(_.getIssuerUniqueID)(ca, beta) shouldMatch(_.getIssuerUniqueID)(ca, gamma) ca.getSerialNumber should not equal(alpha.getSerialNumber) ca.getSerialNumber should not equal(beta.getSerialNumber) ca.getSerialNumber should not equal(gamma.getSerialNumber) } @Test def testSigningAndVerificationQueryDefWithSubQueries: Unit = { //A failing case reported by Ben C. val queryDef = QueryDefinition.fromI2b2 { (t) (493.90) Asthma(250.00) Diabet@15:32:58 ANY 0 Event 1 STARTDATE FIRST LESS Event 2 STARTDATE FIRST GREATEREQUAL 365 DAY Event 1 EVENT Event 1 SAMEINSTANCENUM 0 1 100 0 SAMEINSTANCENUM 1 6 (493.90) Asthma, unspecified type, unspecified \\SHRINE\SHRINE\Diagnoses\Diseases of the respiratory system (460-519.99)\Chronic obstructive pulmonary disease and allied conditions (490-496.99)\Asthma (493)\Asthma, unspecified (493.9)\(493.90) Asthma, unspecified type, unspecified\ Diagnoses\Diseases of the respiratory system (460-519.99)\Chronic obstructive pulmonary disease and allied conditions (490-496.99)\Asthma (493)\Asthma, unspecified (493.9)\(493.90) Asthma, unspecified type, unspecified\ ENC LA false 6 (493.91) Asthma, unspecified type, with status asthmaticus \\SHRINE\SHRINE\Diagnoses\Diseases of the respiratory system (460-519.99)\Chronic obstructive pulmonary disease and allied conditions (490-496.99)\Asthma (493)\Asthma, unspecified (493.9)\(493.91) Asthma, unspecified type, with status asthmaticus\ Diagnoses\Diseases of the respiratory system (460-519.99)\Chronic obstructive pulmonary disease and allied conditions (490-496.99)\Asthma (493)\Asthma, unspecified (493.9)\(493.91) Asthma, unspecified type, with status asthmaticus\ ENC LA false 6 (493.92) Asthma, unspecified type, with (acute) exacerbation \\SHRINE\SHRINE\Diagnoses\Diseases of the respiratory system (460-519.99)\Chronic obstructive pulmonary disease and allied conditions (490-496.99)\Asthma (493)\Asthma, unspecified (493.9)\(493.92) Asthma, unspecified type, with (acute) exacerbation\ Diagnoses\Diseases of the respiratory system (460-519.99)\Chronic obstructive pulmonary disease and allied conditions (490-496.99)\Asthma (493)\Asthma, unspecified (493.9)\(493.92) Asthma, unspecified type, with (acute) exacerbation\ ENC LA false Event 2 EVENT Event 2 SAMEINSTANCENUM 0 1 100 0 SAMEINSTANCENUM 1 6 (250.00) Diabetes mellitus without mention of complication, type II or unspecified type, not stated as uncontrolled \\SHRINE\SHRINE\Diagnoses\Endocrine, nutritional and metabolic diseases, and immunity disorders (240-279.99)\Diseases of other endocrine glands (249-259.99)\Diabetes mellitus (250)\Diabetes mellitus without mention of complication (250.0)\(250.00) Diabetes mellitus without mention of complication, type II or unspecified type, not stated as uncontrolled\ Diagnoses\Endocrine, nutritional and metabolic diseases, and immunity disorders (240-279.99)\Diseases of other endocrine glands (249-259.99)\Diabetes mellitus (250)\Diabetes mellitus without mention of complication (250.0)\(250.00) Diabetes mellitus without mention of complication, type II or unspecified type, not stated as uncontrolled\ ENC LA false 6 (530.81) Esophageal reflux \\SHRINE\SHRINE\Diagnoses\Diseases of the digestive system (520-579.99)\Diseases of esophagus, stomach, and duodenum (530-539.99)\Diseases of esophagus (530)\Other specified disorders of esophagus (530.8)\(530.81) Esophageal reflux\ Diagnoses\Diseases of the digestive system (520-579.99)\Diseases of esophagus, stomach, and duodenum (530-539.99)\Diseases of esophagus (530)\Other specified disorders of esophagus (530.8)\(530.81) Esophageal reflux\ ENC LA false }.get def shouldVerify(signingCertStrategy: SigningCertStrategy): Unit = { val resultTypes = DefaultBreakdownResultOutputTypes.toSet + ResultOutputType.PATIENT_COUNT_XML - val unsignedMessage = BroadcastMessage(authn, RunQueryRequest("some-project-id", 12345.milliseconds, authn, 8934765L, Some("topic-id"), resultTypes, queryDef)) + val unsignedMessage = BroadcastMessage(authn, RunQueryRequest("some-project-id", 12345.milliseconds, authn, 8934765L, Some("topic-id"), Some("Topic Name"), resultTypes, queryDef)) val signedMessage = signerVerifier.sign(unsignedMessage, signingCertStrategy) signerVerifier.verifySig(signedMessage, 1.hour) should be(true) //NB: Simulate going from one machine to another val roundTripped = xmlRoundTrip(signedMessage) roundTripped.signature should equal(signedMessage.signature) signerVerifier.verifySig(roundTripped, 1.hour) should be(true) } //shouldVerify(Attach) shouldVerify(DontAttach) } //See https://open.med.harvard.edu/jira/browse/SHRINE-859 @Test def testSigningAndVerificationQueryNameWithSpaces: Unit = { def shouldVerify(queryName: String, signingCertStrategy: SigningCertStrategy): Unit = { val queryDef = QueryDefinition(queryName, Term("""\\PCORNET\PCORI\DEMOGRAPHIC\Age\>= 65 years old\65\""")) val resultTypes = DefaultBreakdownResultOutputTypes.toSet + ResultOutputType.PATIENT_COUNT_XML - val unsignedMessage = BroadcastMessage(authn, RunQueryRequest("some-project-id", 12345.milliseconds, authn, 8934765L, Some("topic-id"), resultTypes, queryDef)) + val unsignedMessage = BroadcastMessage(authn, RunQueryRequest("some-project-id", 12345.milliseconds, authn, 8934765L, Some("topic-id"), Some("Topic Name"), resultTypes, queryDef)) val signedMessage = signerVerifier.sign(unsignedMessage, signingCertStrategy) signerVerifier.verifySig(signedMessage, 1.hour) should be(true) //NB: Simulate going from one machine to another val roundTripped = xmlRoundTrip(signedMessage) roundTripped.signature should equal(signedMessage.signature) signerVerifier.verifySig(roundTripped, 1.hour) should be(true) } shouldVerify("foo", Attach) shouldVerify(" foo", Attach) shouldVerify("foo ", Attach) shouldVerify("fo o", Attach) shouldVerify(" 65 years old@13:23:00", Attach) shouldVerify("foo", DontAttach) shouldVerify(" foo", DontAttach) shouldVerify("foo ", DontAttach) shouldVerify("fo o", DontAttach) shouldVerify(" 65 years old@13:23:00", DontAttach) } @Test def testSigningAndVerification: Unit = doTestSigningAndVerification(signerVerifier) @Test def testSigningAndVerificationAttachedKnownCertNotSignedByCA: Unit = { //Messages will be signed with a key that's in our keystore, but is not signed by a CA val descriptor = KeyStoreDescriptor( "shrine.keystore.multiple-private-keys", "chiptesting", Some("private-key-2"), Seq("carra ca"), KeyStoreType.JKS) val mySignerVerifier = new DefaultSignerVerifier(KeyStoreCertCollection.fromClassPathResource(descriptor)) val unsignedMessage = BroadcastMessage(authn, DeleteQueryRequest("some-project-id", 12345.milliseconds, authn, 87356L)) val signedMessage = mySignerVerifier.sign(unsignedMessage, Attach) mySignerVerifier.verifySig(signedMessage, 1.hour) should be(false) } @Test def testSigningAndVerificationAttachedUnknownCertNotSignedByCA: Unit = { //Messages will be signed with a key that's NOT in our keystore, but is not signed by a CA val signerDescriptor = KeyStoreDescriptor( "shrine.keystore.multiple-private-keys", "chiptesting", Some("private-key-2"), //This cert is NOT in TestKeystore.certCollection Seq("carra ca"), KeyStoreType.JKS) val signer: Signer = new DefaultSignerVerifier(KeyStoreCertCollection.fromClassPathResource(signerDescriptor)) val verifier: Verifier = new DefaultSignerVerifier(TestKeystore.certCollection) val unsignedMessage = BroadcastMessage(authn, DeleteQueryRequest("some-project-id", 12345.milliseconds, authn, 87356L)) val signedMessage = signer.sign(unsignedMessage, Attach) verifier.verifySig(signedMessage, 1.hour) should be(false) } private def doTestSigningAndVerification(signerVerifier: Signer with Verifier): Unit = { def doTest(signingCertStrategy: SigningCertStrategy) { val unsignedMessage = BroadcastMessage(authn, DeleteQueryRequest("some-project-id", 12345.milliseconds, authn, 87356L)) val signedMessage = signerVerifier.sign(unsignedMessage, signingCertStrategy) (unsignedMessage eq signedMessage) should be(false) unsignedMessage should not equal (signedMessage) signedMessage.networkAuthn should equal(unsignedMessage.networkAuthn) signedMessage.request should equal(unsignedMessage.request) signedMessage.requestId should equal(unsignedMessage.requestId) unsignedMessage.signature should be(None) signedMessage.signature.isDefined should be(true) val sig = signedMessage.signature.get sig.timestamp should not be (null) sig.signedBy should equal(certCollection.myCertId.get) sig.value should not be (null) //The signed message should verify signerVerifier.verifySig(signedMessage, 1.hour) should be(true) def shouldNotVerify(message: BroadcastMessage) { signerVerifier.verifySig(xmlRoundTrip(message), 1.hour) should be(false) } //The unsigned one should not shouldNotVerify(unsignedMessage) //Expired sigs shouldn't verify signerVerifier.verifySig(signedMessage, 0.hours) should be(false) //modifying anything should prevent verification shouldNotVerify { val anotherRequest = signedMessage.request.asInstanceOf[DeleteQueryRequest].copy(queryId = 123L) signedMessage.withRequest(anotherRequest) } shouldNotVerify { signedMessage.withRequestId(99999L) } shouldNotVerify { signedMessage.copy(networkAuthn = signedMessage.networkAuthn.copy(domain = "askldjlakjsd")) } shouldNotVerify { signedMessage.copy(networkAuthn = signedMessage.networkAuthn.copy(username = "askldjlakjsd")) } shouldNotVerify { signedMessage.copy(networkAuthn = signedMessage.networkAuthn.copy(credential = signedMessage.networkAuthn.credential.copy(isToken = true))) } shouldNotVerify { signedMessage.copy(networkAuthn = signedMessage.networkAuthn.copy(credential = signedMessage.networkAuthn.credential.copy(value = "oieutorutoirutioerutoireuto"))) } shouldNotVerify { val timestamp = signedMessage.signature.get.timestamp import scala.concurrent.duration._ import XmlGcEnrichments._ val newTimestamp = timestamp + 123.minutes signedMessage.withSignature(signedMessage.signature.get.copy(timestamp = newTimestamp)) } shouldNotVerify { val timestamp = signedMessage.signature.get.timestamp import scala.concurrent.duration._ import XmlGcEnrichments._ val newTimestamp = timestamp + (-99).minutes signedMessage.withSignature(signedMessage.signature.get.copy(timestamp = newTimestamp)) } } doTest(Attach) doTest(DontAttach) } @Test def testSigningAndVerificationModifiedTerm: Unit = { import scala.concurrent.duration._ def doVerificationTest(signingCertStrategy: SigningCertStrategy, queryDef: QueryDefinition): Unit = { - val req = RunQueryRequest("some-project-id", 12345.milliseconds, authn, 8934765L, Some("topic-id"), Set(ResultOutputType.PATIENT_COUNT_XML), queryDef) + val req = RunQueryRequest("some-project-id", 12345.milliseconds, authn, 8934765L, Some("topic-id"), Some("Topic Name"), Set(ResultOutputType.PATIENT_COUNT_XML), queryDef) val unsignedMessage = BroadcastMessage(authn, req) val signedMessage = signerVerifier.sign(unsignedMessage, signingCertStrategy) (unsignedMessage eq signedMessage) should be(false) unsignedMessage should not equal (signedMessage) signedMessage.networkAuthn should equal(unsignedMessage.networkAuthn) signedMessage.request should equal(unsignedMessage.request) signedMessage.requestId should equal(unsignedMessage.requestId) unsignedMessage.signature should be(None) signedMessage.signature.isDefined should be(true) val sig = signedMessage.signature.get sig.timestamp should not be (null) sig.signedBy should equal(certCollection.myCertId.get) sig.value should not be (null) //The signed message should verify signerVerifier.verifySig(xmlRoundTrip(signedMessage), 1.hour) should be(true) } val t1 = Term("t1") val t2 = Term("t2") val t3 = Term("t3") doVerificationTest(Attach, QueryDefinition("foo", Term(""))) doVerificationTest(Attach, QueryDefinition("foo", Constrained(t1, Some(Modifiers("n", "ap", t2.value)), None))) doVerificationTest(Attach, QueryDefinition("foo", Or(t1, Constrained(t2, Some(Modifiers("n", "ap", t3.value)), None)))) doVerificationTest(Attach, QueryDefinition("foo", Or(t1, Constrained(t2, None, Some(ValueConstraint("foo", Some("bar"), "baz", "Nuh")))))) doVerificationTest(Attach, QueryDefinition("foo", Or(t1, Constrained(t2, None, Some(ValueConstraint("foo", None, "baz", "Nuh")))))) doVerificationTest(DontAttach, QueryDefinition("foo", Term(""))) doVerificationTest(DontAttach, QueryDefinition("foo", Constrained(t1, Some(Modifiers("n", "ap", t2.value)), None))) doVerificationTest(DontAttach, QueryDefinition("foo", Or(t1, Constrained(t2, Some(Modifiers("n", "ap", t3.value)), None)))) doVerificationTest(DontAttach, QueryDefinition("foo", Or(t1, Constrained(t2, None, Some(ValueConstraint("foo", Some("bar"), "baz", "Nuh")))))) doVerificationTest(DontAttach, QueryDefinition("foo", Or(t1, Constrained(t2, None, Some(ValueConstraint("foo", None, "baz", "Nuh")))))) } @Test def testSigningAndVerificationReadQueryResultRequest: Unit = { def doTest(signingCertStrategy: SigningCertStrategy) { val localAuthn = AuthenticationInfo("i2b2demo", "shrine", Credential("SessionKey:PX4LlvLrMhybWRQfoobarbaz", true)) import scala.concurrent.duration._ val unsignedMessage = BroadcastMessage(authn, ReadQueryResultRequest("SHRINE", 180.seconds, localAuthn, 7923919416951966472L)) val signedMessage = signerVerifier.sign(unsignedMessage, signingCertStrategy) (unsignedMessage eq signedMessage) should be(false) unsignedMessage should not equal (signedMessage) signedMessage.networkAuthn should equal(unsignedMessage.networkAuthn) signedMessage.request should equal(unsignedMessage.request) signedMessage.requestId should equal(unsignedMessage.requestId) unsignedMessage.signature should be(None) signedMessage.signature.isDefined should be(true) val sig = signedMessage.signature.get sig.timestamp should not be (null) sig.signedBy should equal(certCollection.myCertId.get) sig.value should not be (null) import scala.concurrent.duration._ //The signed message should verify signerVerifier.verifySig(xmlRoundTrip(signedMessage), 1.hour) should be(true) } doTest(Attach) doTest(DontAttach) } private def getCertByAlias(alias: String) = certCollection.asInstanceOf[KeyStoreCertCollection].getX509Cert(alias).get @Test def testIsSignedByTrustedCA: Unit = { import signerVerifier.isSignedByTrustedCA val signedByCa = getCertByAlias("test-cert") val notSignedByCa = getCertByAlias("spin-t1") isSignedByTrustedCA(signedByCa).get should be(true) isSignedByTrustedCA(notSignedByCa).isFailure should be(true) //TODO: Test case where isSignedByTrustedCA produces Success(false): //where CA cert (param's issuer-DN) IS in our keystore, but X509Certificate.verify(PublicKey) returns false } @Test def testObtainAndValidateSigningCert: Unit = { import signerVerifier.obtainAndValidateSigningCert import scala.concurrent.duration._ val unsignedMessage = BroadcastMessage(authn, ReadQueryResultRequest("SHRINE", 180.seconds, authn, 7923919416951966472L)) //attached signing cert signed by known CA { val signedMessageWithAttachedSigner = signerVerifier.sign(unsignedMessage, SigningCertStrategy.Attach) val signerCert = obtainAndValidateSigningCert(signedMessageWithAttachedSigner.signature.get).get signerCert should equal(getCertByAlias("test-cert")) } //Known signer, no attached signing cert { val signedMessageWithoutAttachedSigner = signerVerifier.sign(unsignedMessage, SigningCertStrategy.DontAttach) val signerCert = obtainAndValidateSigningCert(signedMessageWithoutAttachedSigner.signature.get).get signerCert should equal(getCertByAlias("test-cert")) } //No attached cert, unknown signer: obtaining signing cert should fail { val signedMessage = signerVerifier.sign(unsignedMessage, SigningCertStrategy.DontAttach) val unknownSigner = CertId(new BigInteger("-1")) val signedMessageWithUnknownSigner = signedMessage.copy(signature = Some(signedMessage.signature.get.copy(signedBy = unknownSigner))) val signerCertAttempt = obtainAndValidateSigningCert(signedMessageWithUnknownSigner.signature.get) signerCertAttempt.isFailure should be(true) } } private def xmlRoundTrip(message: BroadcastMessage): BroadcastMessage = { val roundTripped = BroadcastMessage.fromXml(message.toXml).get roundTripped should equal(message) message } } \ No newline at end of file diff --git a/commons/protocol/src/main/scala/net/shrine/protocol/RunQueryRequest.scala b/commons/protocol/src/main/scala/net/shrine/protocol/RunQueryRequest.scala index 7c9d71b55..905e65c2e 100644 --- a/commons/protocol/src/main/scala/net/shrine/protocol/RunQueryRequest.scala +++ b/commons/protocol/src/main/scala/net/shrine/protocol/RunQueryRequest.scala @@ -1,165 +1,171 @@ package net.shrine.protocol -import net.shrine.util.{Tries, XmlUtil, NodeSeqEnrichments, OptionEnrichments, XmlDateHelper} +import net.shrine.util.{Tries, XmlUtil, NodeSeqEnrichments, OptionEnrichments} import net.shrine.protocol.query.QueryDefinition import scala.xml.NodeSeq import scala.xml.Elem import scala.concurrent.duration.Duration import scala.util.Try import net.shrine.serialization.I2b2UnmarshallingHelpers /** * @author Bill Simons * @since 3/9/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 * * NB: this is a case class to get a structural equality contract in hashCode and equals, mostly for testing */ final case class RunQueryRequest( override val projectId: String, override val waitTime: Duration, override val authn: AuthenticationInfo, networkQueryId: Long, - topicId: Option[String], //data steward when required only + topicId: Option[String], //data steward when required only todo would be better to use a tuple Option[(TopicId,TopicName)] + topicName: Option[String], //data steward when required only outputTypes: Set[ResultOutputType], queryDefinition: QueryDefinition) extends ShrineRequest(projectId, waitTime, authn) with CrcRequest with TranslatableRequest[RunQueryRequest] with HandleableShrineRequest with HandleableI2b2Request { override val requestType = RequestType.QueryDefinitionRequest override def handle(handler: ShrineRequestHandler, shouldBroadcast: Boolean) = handler.runQuery(this, shouldBroadcast) override def handleI2b2(handler: I2b2RequestHandler, shouldBroadcast: Boolean) = handler.runQuery(this, shouldBroadcast) def elideAuthenticationInfo: RunQueryRequest = copy(authn = AuthenticationInfo.elided) //NB: Sort ResultOutputTypes, for deterministic testing private def sortedOutputTypes: Seq[ResultOutputType] = outputTypes.toSeq.sortBy(_.name) override def toXml: NodeSeq = XmlUtil.stripWhitespace { import OptionEnrichments._ { headerFragment } { networkQueryId } { topicId.toXml() } + { topicName.toXml() } { sortedOutputTypes.map(_.toXml) } { queryDefinition.toXml } } protected override def i2b2MessageBody: NodeSeq = XmlUtil.stripWhitespace { import OptionEnrichments._ { i2b2PsmHeaderWithDomain } { queryDefinition.toI2b2 } { for { (outputType, i) <- sortedOutputTypes.zipWithIndex priorityIndex = outputType.id.getOrElse(i + 1) } yield { } } { topicId.toXml() } + { topicName.toXml() } } override def withProject(proj: String) = this.copy(projectId = proj) override def withAuthn(ai: AuthenticationInfo) = this.copy(authn = ai) def withQueryDefinition(qDef: QueryDefinition) = this.copy(queryDefinition = qDef) def mapQueryDefinition(f: QueryDefinition => QueryDefinition) = this.withQueryDefinition(f(queryDefinition)) def withNetworkQueryId(id: Long) = this.copy(networkQueryId = id) } object RunQueryRequest extends I2b2XmlUnmarshaller[RunQueryRequest] with ShrineXmlUnmarshaller[RunQueryRequest] with ShrineRequestUnmarshaller with I2b2UnmarshallingHelpers { val neededI2b2Namespace = "http://www.i2b2.org/xsd/cell/crc/psm/1.1/" override def fromI2b2(breakdownTypes: Set[ResultOutputType])(xml: NodeSeq): Try[RunQueryRequest] = { val queryDefNode = xml \ "message_body" \ "request" \ "query_definition" val queryDefXml = queryDefNode.head match { //NB: elem.scope.getPrefix(neededI2b2Namespace) will return null if elem isn't part of a larger XML chunk that has //the http://www.i2b2.org/xsd/cell/crc/psm/1.1/ declared case elem: Elem => elem.copy(elem.scope.getPrefix(neededI2b2Namespace)) case _ => throw new Exception("When unmarshalling a RunQueryRequest, encountered unexpected XML: '" + queryDefNode + "', might be missing.") } val attempt = for { projectId <- i2b2ProjectId(xml) waitTime <- i2b2WaitTime(xml) authn <- i2b2AuthenticationInfo(xml) topicId = (xml \ "message_body" \ "shrine" \ "queryTopicID").headOption.map(XmlUtil.trim) + topicName = (xml \ "message_body" \ "shrine" \ "queryTopicName").headOption.map(XmlUtil.trim) outputTypes = determineI2b2OutputTypes(breakdownTypes)(xml \ "message_body" \ "request" \ "result_output_list") queryDef <- QueryDefinition.fromI2b2(queryDefXml) } yield { RunQueryRequest( projectId, waitTime, authn, - -1L, //TODO: appropriate? + -1L, //TODO: appropriate? todo really annoying for ACT metrics audit topicId, + topicName, outputTypes, queryDef) } attempt.map(addPatientCountXmlIfNecessary) } private def determineI2b2OutputTypes(breakdownTypes: Set[ResultOutputType])(nodeSeq: NodeSeq): Set[ResultOutputType] = { val sequence = (nodeSeq \ "result_output").flatMap { breakdownXml => val breakdownName = XmlUtil.trim(breakdownXml \ "@name") ResultOutputType.valueOf(breakdownTypes)(breakdownName) } sequence.toSet } private def determineShrineOutputTypes(nodeSeq: NodeSeq): Set[ResultOutputType] = { val attempts = (nodeSeq \ "resultType").map(ResultOutputType.fromXml) Tries.sequence(attempts).map(_.toSet).get } private[protocol] def addPatientCountXmlIfNecessary(req: RunQueryRequest): RunQueryRequest = { import ResultOutputType.PATIENT_COUNT_XML if (req.outputTypes.contains(PATIENT_COUNT_XML)) { req } else { req.copy(outputTypes = req.outputTypes + PATIENT_COUNT_XML) } } override def fromXml(breakdownTypes: Set[ResultOutputType])(xml: NodeSeq): Try[RunQueryRequest] = { import NodeSeqEnrichments.Strictness._ val attempt = for { projectId <- shrineProjectId(xml) waitTime <- shrineWaitTime(xml) authn <- shrineAuthenticationInfo(xml) queryId <- xml.withChild("queryId").map(XmlUtil.toLong) topicId = (xml \ "topicId").headOption.map(XmlUtil.trim) + topicName = (xml \ "topicName").headOption.map(XmlUtil.trim) outputTypes <- xml.withChild("outputTypes").map(determineShrineOutputTypes) queryDef <- xml.withChild(QueryDefinition.rootTagName).flatMap(QueryDefinition.fromXml) } yield { - RunQueryRequest(projectId, waitTime, authn, queryId, topicId, outputTypes, queryDef) + RunQueryRequest(projectId, waitTime, authn, queryId, topicId, topicName, outputTypes, queryDef) } attempt.map(addPatientCountXmlIfNecessary) } } \ No newline at end of file diff --git a/commons/protocol/src/test/scala/net/shrine/protocol/HandleableI2b2RequestTest.scala b/commons/protocol/src/test/scala/net/shrine/protocol/HandleableI2b2RequestTest.scala index dd16750d2..a6edc1b49 100644 --- a/commons/protocol/src/test/scala/net/shrine/protocol/HandleableI2b2RequestTest.scala +++ b/commons/protocol/src/test/scala/net/shrine/protocol/HandleableI2b2RequestTest.scala @@ -1,87 +1,87 @@ package net.shrine.protocol import net.shrine.util.ShouldMatchersForJUnit import org.junit.Test import net.shrine.protocol.query.QueryDefinition import net.shrine.protocol.query.Term /** * @author clint - * @date Jun 18, 2014 + * @since Jun 18, 2014 */ final class HandleableI2b2RequestTest extends ShouldMatchersForJUnit { type Req = ShrineRequest with HandleableI2b2Request import HandleableI2b2Request.fromI2b2 import scala.concurrent.duration._ private val authn = AuthenticationInfo("some-domain", "some-user", Credential("some-password", false)) private val waitTime = 1.minute private val projectId = "some-project-id" @Test def testFromI2b2ReadResultOutputTypesRequest: Unit = { val i2b2Xml = ReadResultOutputTypesRequestTest.i2b2Xml(CrcRequestType.GetResultOutputTypes) val req = fromI2b2(DefaultBreakdownResultOutputTypes.toSet)(i2b2Xml).get req.authn should equal(ReadResultOutputTypesRequestTest.authn) req.projectId should equal(ReadResultOutputTypesRequestTest.projectId) req.waitTime should equal(ReadResultOutputTypesRequestTest.waitTime) } @Test def testFromI2b2: Unit = { def roundTrip(req: Req): Unit = { val xml = req.toI2b2 val unmarshalled = fromI2b2(DefaultBreakdownResultOutputTypes.toSet)(xml).get (req, unmarshalled) match { //NB: Special handling for RunQueryRequest, which does not preserve networkQueryIds when serializing to i2b2 format case (expected: RunQueryRequest, actual: RunQueryRequest) => { //NB: When unmarshalling from i2b2 format, networkQueryId will always be -1; other fields should be fine actual should equal(expected.withNetworkQueryId(-1L)) } case _ => unmarshalled should equal(req) } } //Junk input fromI2b2(DefaultBreakdownResultOutputTypes.toSet)(null).isFailure should be(true) fromI2b2(DefaultBreakdownResultOutputTypes.toSet)().isFailure should be(true) val queryDef = QueryDefinition("foo", Term("foo")) //A request that isn't a HandleableI2b2Request val invalidReq = ReadTranslatedQueryDefinitionRequest(authn, waitTime, queryDef) fromI2b2(DefaultBreakdownResultOutputTypes.toSet)(invalidReq.toXml).isFailure should be(true) val networkQueryId = 12345L val userId = "some-user" fromI2b2(DefaultBreakdownResultOutputTypes.toSet)(ReadPdoRequest(projectId, waitTime, authn, "patient-set-coll-id", ).toI2b2).isFailure should be(true) roundTrip(DeleteQueryRequest(projectId, waitTime, authn, networkQueryId)) roundTrip(FlagQueryRequest(projectId, waitTime, authn, networkQueryId, None)) roundTrip(FlagQueryRequest(projectId, waitTime, authn, networkQueryId, Some("some-message"))) roundTrip(UnFlagQueryRequest(projectId, waitTime, authn, networkQueryId)) roundTrip(UnFlagQueryRequest(projectId, waitTime, authn, networkQueryId)) roundTrip(ReadApprovedQueryTopicsRequest(projectId, waitTime, authn, userId)) roundTrip(ReadInstanceResultsRequest(projectId, waitTime, authn, networkQueryId)) //Make minimal PDO request (kind of finnicky due to embedded XML) val patientSetCollId = "patient-set-coll-id" val optionsXml = { CrcRequestType.GetPDOFromInputListRequestType.i2b2RequestType }{ patientSetCollId } val pdoXml = ReadPdoRequest.updateCollId(optionsXml.head, patientSetCollId).toSeq roundTrip(ReadPdoRequest(projectId, waitTime, authn, patientSetCollId, pdoXml)) roundTrip(ReadPreviousQueriesRequest(projectId, waitTime, authn, userId, 123)) roundTrip(ReadQueryDefinitionRequest(projectId, waitTime, authn, networkQueryId)) roundTrip(ReadQueryInstancesRequest(projectId, waitTime, authn, networkQueryId)) roundTrip(RenameQueryRequest(projectId, waitTime, authn, networkQueryId, "new-name")) - roundTrip(RunQueryRequest(projectId, waitTime /*.toMillis.milliseconds*/ , authn, networkQueryId, Some("some-topic-id"), Set(ResultOutputType.PATIENT_COUNT_XML), queryDef)) - roundTrip(RunQueryRequest(projectId, waitTime /*.toMillis.milliseconds*/ , authn, networkQueryId, None, Set(ResultOutputType.PATIENT_COUNT_XML), queryDef)) + roundTrip(RunQueryRequest(projectId, waitTime /*.toMillis.milliseconds*/ , authn, networkQueryId, Some("some-topic-id"),Some("some-topic-name"), Set(ResultOutputType.PATIENT_COUNT_XML), queryDef)) + roundTrip(RunQueryRequest(projectId, waitTime /*.toMillis.milliseconds*/ , authn, networkQueryId, None, None, Set(ResultOutputType.PATIENT_COUNT_XML), queryDef)) } } \ No newline at end of file diff --git a/commons/protocol/src/test/scala/net/shrine/protocol/HiveCredentialsTest.scala b/commons/protocol/src/test/scala/net/shrine/protocol/HiveCredentialsTest.scala index 7a90bfece..86151f06a 100644 --- a/commons/protocol/src/test/scala/net/shrine/protocol/HiveCredentialsTest.scala +++ b/commons/protocol/src/test/scala/net/shrine/protocol/HiveCredentialsTest.scala @@ -1,23 +1,22 @@ package net.shrine.protocol -import net.shrine.protocol.HiveCredentials import net.shrine.util.ShouldMatchersForJUnit /** * @author clint * @since Oct 4, 2012 */ final class HiveCredentialsTest extends ShouldMatchersForJUnit { - def testToAuthenticationInfo { + def testToAuthenticationInfo() { val creds = HiveCredentials("domain", "username", "password", "project") val authn = creds.toAuthenticationInfo - authn should not be(null) + authn should not be null authn.domain should equal(creds.domain) authn.username should equal(creds.username) authn.credential.value should equal(creds.password) - authn.credential.isToken should be(false) + authn.credential.isToken should be(right = false) } } \ No newline at end of file diff --git a/commons/protocol/src/test/scala/net/shrine/protocol/RunQueryRequestTest.scala b/commons/protocol/src/test/scala/net/shrine/protocol/RunQueryRequestTest.scala index 57fb67f03..13273eb90 100644 --- a/commons/protocol/src/test/scala/net/shrine/protocol/RunQueryRequestTest.scala +++ b/commons/protocol/src/test/scala/net/shrine/protocol/RunQueryRequestTest.scala @@ -1,468 +1,476 @@ package net.shrine.protocol import org.junit.Test import xml.Utility import scala.xml.XML import net.shrine.util.XmlUtil import net.shrine.protocol.query.QueryDefinition import net.shrine.protocol.query.OccuranceLimited import net.shrine.protocol.query.Term /** * @author Bill Simons - * @date 3/17/11 - * @link http://cbmi.med.harvard.edu - * @link http://chip.org + * @since 3/17/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 - * @link http://www.gnu.org/licenses/lgpl.html + * @see http://www.gnu.org/licenses/lgpl.html */ final class RunQueryRequestTest extends ShrineRequestValidator { private val queryId = 98765L private val topicId = "1" + private val topicName = "Test Topic" private val queryDefinition = QueryDefinition("Ostium secundum@14:01:35", Term("""\\SHRINE\SHRINE\Diagnoses\Congenital anomalies\Cardiac and circulatory congenital anomalies\Atrial septal defect\Ostium secundum type atrial septal defect\""")) private val resultOutputTypes = { import ResultOutputType._ (Seq(PATIENTSET, PATIENT_COUNT_XML) ++ DefaultBreakdownResultOutputTypes.values).sortBy(_.name).zipWithIndex.map { case (rot, i) => rot.withId(i + 1) } } private val nonCountResultOutputTypes = resultOutputTypes.filterNot(_ == ResultOutputType.PATIENT_COUNT_XML) //add weird casing to make sure the code isn't case senstive, the client will send all sorts of weirdness private val resultOutputTypesI2b2Xml = XmlUtil.stripWhitespace { } override def messageBody = XmlUtil.stripWhitespace { { username } 0 0 CRC_QRY_runQueryInstance_fromQueryDefinition { queryDefinition.toI2b2 } { resultOutputTypesI2b2Xml } { topicId } } private def messageBodyNoTopicId = XmlUtil.stripWhitespace { { username } 0 0 CRC_QRY_runQueryInstance_fromQueryDefinition { queryDefinition.toI2b2 } { resultOutputTypesI2b2Xml } } private def messageBodyNoOutputTypes = XmlUtil.stripWhitespace { { username } 0 0 CRC_QRY_runQueryInstance_fromQueryDefinition { queryDefinition.toI2b2 } { topicId } } private def messageBodyNoCountOutputType = XmlUtil.stripWhitespace { { username } 0 0 CRC_QRY_runQueryInstance_fromQueryDefinition { queryDefinition.toI2b2 } { topicId } } private val runQueryRequest = XmlUtil.stripWhitespace { { requestHeaderFragment } { queryId } { topicId } + { topicName } { resultOutputTypes.map(_.toXml) } { queryDefinition.toXml } } private val runQueryRequestNoCountOutputType = XmlUtil.stripWhitespace { { requestHeaderFragment } { queryId } { topicId } { nonCountResultOutputTypes.map(_.toXml) } { queryDefinition.toXml } } private val runQueryRequestNoOutputTypes = XmlUtil.stripWhitespace { { requestHeaderFragment } { queryId } { topicId } { queryDefinition.toXml } } private val runQueryRequestNoTopicId = XmlUtil.stripWhitespace { { requestHeaderFragment } { queryId } { resultOutputTypes.map(_.toXml) } { queryDefinition.toXml } } @Test def testAddPatientCountXmlIfNecessary: Unit = { import scala.concurrent.duration._ import ResultOutputType._ import RunQueryRequest.addPatientCountXmlIfNecessary val breakdownOutputType = ResultOutputType("PATIENT_GENDER_COUNT_XML", true, I2b2Options("Number of patients by gender"), Some(99)) val countReq = RunQueryRequest( "project-Id", 1.minute, AuthenticationInfo("d", "u", Credential("p", false)), 12345L, None, + None, Set(PATIENT_COUNT_XML, breakdownOutputType), QueryDefinition("foo", Some(Term("bar")))) val nonCountReq = countReq.copy(outputTypes = countReq.outputTypes - PATIENT_COUNT_XML) val noOutputTypesReq = countReq.copy(outputTypes = Set.empty) countReq.outputTypes.contains(PATIENT_COUNT_XML) should be(true) nonCountReq.outputTypes.contains(PATIENT_COUNT_XML) should be(false) addPatientCountXmlIfNecessary(countReq) should equal(countReq) addPatientCountXmlIfNecessary(nonCountReq) should equal(countReq) addPatientCountXmlIfNecessary(noOutputTypesReq).outputTypes should equal(Set(PATIENT_COUNT_XML)) } @Test def testElideAuthenticationInfo: Unit = { val req = RunQueryRequest.fromI2b2(DefaultBreakdownResultOutputTypes.toSet)(request()).get req.authn.username should equal(username) req.authn.domain should equal(domain) req.authn.credential.value should equal(passwd) val elided = req.elideAuthenticationInfo elided.authn.username should equal("*******") elided.authn.domain should equal("*******") elided.authn.credential.value should equal("*******") } @Test override def testFromI2b2 { val translatedRequest = RunQueryRequest.fromI2b2(DefaultBreakdownResultOutputTypes.toSet)(request()).get validateRequestWith(translatedRequest) { translatedRequest.topicId should equal(Some(topicId)) translatedRequest.outputTypes should equal(ResultOutputType.nonErrorTypes.toSet ++ DefaultBreakdownResultOutputTypes.toSet) translatedRequest.queryDefinition should equal(queryDefinition) val queryDefNode = translatedRequest.queryDefinition.toI2b2 queryDefNode.head.prefix should equal(queryDefNode.head.scope.getPrefix(RunQueryRequest.neededI2b2Namespace)) } } @Test def testFromI2b2NoTopicId { val translatedRequest = RunQueryRequest.fromI2b2(DefaultBreakdownResultOutputTypes.toSet)(request(() => messageBodyNoTopicId)).get validateRequestWith(translatedRequest) { translatedRequest.topicId should equal(None) translatedRequest.outputTypes should equal(ResultOutputType.nonErrorTypes.toSet ++ DefaultBreakdownResultOutputTypes.toSet) translatedRequest.queryDefinition should equal(queryDefinition) val queryDefNode = translatedRequest.queryDefinition.toI2b2 queryDefNode.head.prefix should equal(queryDefNode.head.scope.getPrefix(RunQueryRequest.neededI2b2Namespace)) } } @Test def testFromI2b2NoOutputTypes { val translatedRequest = RunQueryRequest.fromI2b2(DefaultBreakdownResultOutputTypes.toSet)(request(() => messageBodyNoOutputTypes)).get validateRequestWith(translatedRequest) { translatedRequest.topicId should equal(Some(topicId)) translatedRequest.outputTypes should equal(Set(ResultOutputType.PATIENT_COUNT_XML)) translatedRequest.queryDefinition should equal(queryDefinition) val queryDefNode = translatedRequest.queryDefinition.toI2b2 queryDefNode.head.prefix should equal(queryDefNode.head.scope.getPrefix(RunQueryRequest.neededI2b2Namespace)) } } @Test def testFromI2b2NoCountOutputType { val translatedRequest = RunQueryRequest.fromI2b2(DefaultBreakdownResultOutputTypes.toSet)(request(() => messageBodyNoCountOutputType)).get validateRequestWith(translatedRequest) { translatedRequest.topicId should equal(Some(topicId)) translatedRequest.outputTypes should equal(DefaultBreakdownResultOutputTypes.toSet + ResultOutputType.PATIENT_COUNT_XML) translatedRequest.queryDefinition should equal(queryDefinition) val queryDefNode = translatedRequest.queryDefinition.toI2b2 queryDefNode.head.prefix should equal(queryDefNode.head.scope.getPrefix(RunQueryRequest.neededI2b2Namespace)) } } @Test def testMapQueryDefinition { val outputTypes = ResultOutputType.nonBreakdownTypes.toSet - val req = new RunQueryRequest(projectId, waitTime, authn, queryId, Option(topicId), outputTypes, queryDefinition) + val req = new RunQueryRequest(projectId, waitTime, authn, queryId, Option(topicId), Option(topicName), outputTypes, queryDefinition) val bogusTerm = Term("sa;ldk;alskd") val mapped = req.mapQueryDefinition(_.transform(_ => bogusTerm)) (mapped eq req) should not be (true) mapped should not equal (req) mapped.projectId should equal(projectId) mapped.waitTime should equal(waitTime) mapped.authn should equal(authn) mapped.topicId should equal(Option(topicId)) mapped.outputTypes should equal(outputTypes) mapped.queryDefinition.name should equal(queryDefinition.name) mapped.queryDefinition.expr.get should equal(bogusTerm) } @Test override def testShrineRequestFromI2b2 { val shrineRequest = CrcRequest.fromI2b2(DefaultBreakdownResultOutputTypes.toSet)(request()).get shrineRequest.isInstanceOf[RunQueryRequest] should be(true) } @Test def testDoubleDispatchingShrineRequestFromI2b2 { val shrineRequest = HandleableShrineRequest.fromI2b2(DefaultBreakdownResultOutputTypes.toSet)(request()).get shrineRequest.isInstanceOf[RunQueryRequest] should be(true) } @Test def testShrineXmlRoundTrip: Unit = { def doTest(outputTypes: Set[ResultOutputType]) { val req = RunQueryRequest( projectId, waitTime, authn, queryId, Option(topicId), + Option(topicName), outputTypes, queryDefinition) val roundTripped = RunQueryRequest.fromXml(Set.empty)(req.toXml).get roundTripped should equal(req) } doTest(ResultOutputType.nonErrorTypes.toSet) doTest(ResultOutputType.nonErrorTypes.toSet ++ DefaultBreakdownResultOutputTypes.toSet) } @Test override def testToXml { import scala.concurrent.duration._ RunQueryRequest( projectId, waitTime, authn, queryId, Option(topicId), + Option(topicName), resultOutputTypes.toSet, queryDefinition).toXml should equal(runQueryRequest) } @Test def testToXmlNoTopicId { RunQueryRequest( projectId, waitTime, authn, queryId, None, + None, resultOutputTypes.toSet, queryDefinition).toXml should equal(runQueryRequestNoTopicId) } @Test override def testToI2b2 { val req = RunQueryRequest( projectId, waitTime, authn, queryId, Option(topicId), + Option(topicName), resultOutputTypes.toSet, queryDefinition) val actual = RunQueryRequest.fromI2b2(DefaultBreakdownResultOutputTypes.toSet)(req.toI2b2).get validateRequestWith(actual) { actual.networkQueryId should equal(-1L) actual.topicId should equal(Some(topicId)) actual.outputTypes should equal(resultOutputTypes.toSet) actual.queryDefinition should equal(queryDefinition) } } @Test def testToI2b2NoTopicId { val req = RunQueryRequest( projectId, waitTime, authn, queryId, None, + None, resultOutputTypes.toSet, queryDefinition) val actual = RunQueryRequest.fromI2b2(DefaultBreakdownResultOutputTypes.toSet)(req.toI2b2).get validateRequestWith(actual) { actual.networkQueryId should equal(-1L) actual.topicId should equal(None) actual.outputTypes should equal(resultOutputTypes.toSet) actual.queryDefinition should equal(queryDefinition) } } @Test override def testFromXml { val actual = RunQueryRequest.fromXml(DefaultBreakdownResultOutputTypes.toSet)(runQueryRequest).get validateRequestWith(actual) { actual.networkQueryId should equal(queryId) actual.topicId should equal(Some(topicId)) actual.outputTypes should equal(resultOutputTypes.toSet) actual.queryDefinition should equal(queryDefinition) val queryDefNode = actual.queryDefinition.toXml.head queryDefNode.prefix should be(null) queryDefNode.namespace should be(null) } } @Test def testFromXmlNoTopicId { val actual = RunQueryRequest.fromXml(DefaultBreakdownResultOutputTypes.toSet)(runQueryRequestNoTopicId).get validateRequestWith(actual) { actual.networkQueryId should equal(queryId) actual.topicId should equal(None) actual.outputTypes should equal(resultOutputTypes.toSet) actual.queryDefinition should equal(queryDefinition) val queryDefNode = actual.queryDefinition.toXml.head queryDefNode.prefix should be(null) queryDefNode.namespace should be(null) } } @Test def testFromXmlNoOutputTypes { val actual = RunQueryRequest.fromXml(DefaultBreakdownResultOutputTypes.toSet)(runQueryRequestNoOutputTypes).get validateRequestWith(actual) { actual.networkQueryId should equal(queryId) actual.topicId should equal(Some(topicId)) actual.outputTypes should equal(Set(ResultOutputType.PATIENT_COUNT_XML)) actual.queryDefinition should equal(queryDefinition) val queryDefNode = actual.queryDefinition.toXml.head queryDefNode.prefix should be(null) queryDefNode.namespace should be(null) } } @Test def testFromXmlNoCountOutputType { val actual = RunQueryRequest.fromXml(DefaultBreakdownResultOutputTypes.toSet)(runQueryRequestNoCountOutputType).get validateRequestWith(actual) { actual.networkQueryId should equal(queryId) actual.topicId should equal(Some(topicId)) actual.outputTypes should equal(nonCountResultOutputTypes.toSet + ResultOutputType.PATIENT_COUNT_XML) actual.queryDefinition should equal(queryDefinition) val queryDefNode = actual.queryDefinition.toXml.head queryDefNode.prefix should be(null) queryDefNode.namespace should be(null) } } @Test def testShrineRequestFromXml { ShrineRequest.fromXml(DefaultBreakdownResultOutputTypes.toSet)(runQueryRequest).get.isInstanceOf[RunQueryRequest] should be(true) } } \ No newline at end of file diff --git a/commons/protocol/src/test/scala/net/shrine/protocol/ShrineMessageTest.scala b/commons/protocol/src/test/scala/net/shrine/protocol/ShrineMessageTest.scala index 50b4386d0..971c9ea17 100644 --- a/commons/protocol/src/test/scala/net/shrine/protocol/ShrineMessageTest.scala +++ b/commons/protocol/src/test/scala/net/shrine/protocol/ShrineMessageTest.scala @@ -1,174 +1,175 @@ package net.shrine.protocol import net.shrine.serialization.XmlMarshaller import net.shrine.serialization.XmlUnmarshaller import net.shrine.util.ShouldMatchersForJUnit import org.junit.Test import scala.util.Try import scala.xml.NodeSeq import net.shrine.protocol.query.QueryDefinition import net.shrine.protocol.query.Term import net.shrine.util.XmlDateHelper import java.math.BigInteger /** * @author clint - * @date Feb 24, 2014 + * @since Feb 24, 2014 */ final class ShrineMessageTest extends ShouldMatchersForJUnit { @Test def testRoundTrips { val projectId = "salkdjksaljdkla" import scala.concurrent.duration._ val waitTime: Duration = 98374L.milliseconds val userId = "foo-user" val groupId = "foo-group" val authn = AuthenticationInfo("blarg-domain", userId, Credential("sajkhdkjsadh", true)) val queryId = 485794359L val patientSetCollId = "ksaldjksal" val optionsXml: NodeSeq = x val fetchSize = 12345 val queryName = "saljkd;salda" val topicId = "saldjkasljdasdsadsadasdas" + val topicName = "Topic Name" val outputTypes = ResultOutputType.nonBreakdownTypes.toSet val queryDefinition = QueryDefinition(queryName, Term("oiweruoiewkldfhsofi")) val queryDefinition2 = QueryDefinition(queryName, Term("a;slkjflfjlsdkjf")) val localResultId = "aoiduaojsdpaojcmsal" val nodeId = NodeId("foo") val nodeId2 = NodeId("bar") val queryTopicId1 = 123L val queryTopicId2 = 456L val queryTopicName1 = "nuh" val queryTopicName2 = "zuh" val shrineNetworkQueryId = 1287698235L val start = Some(XmlDateHelper.now) val end = Some(XmlDateHelper.now) val singleNodeResult1 = QueryResult.errorResult(Some("blarg"), "glarg") val singleNodeResult2 = QueryResult( 42L, 99L, Option(ResultOutputType.PATIENT_COUNT_XML), 123L, start, end, Some("description"), QueryResult.StatusType.Finished, Some("status")) val param1 = ParamResponse("foo", "bar", "baz") val queryMaster1 = QueryMaster("kjasdh", 12345L, "name1", userId, groupId, start.get) val queryMaster2 = QueryMaster("skdjlhlasf", 873563L, "name2", userId, groupId, end.get) val queryInstance1 = QueryInstance("asd", "42", userId, groupId, start.get, end.get) val queryInstance2 = QueryInstance("asdasd", "99", userId, groupId, start.get, end.get) val envelope = I2b2ResultEnvelope(DefaultBreakdownResultOutputTypes.PATIENT_AGE_COUNT_XML, Map("x" -> 1, "y" -> 2)) //BroadcastMessage //Non-CA-signed signing cert doMarshallingRoundTrip(BroadcastMessage(123456L, authn, DeleteQueryRequest(projectId, waitTime, authn, queryId), Some(Signature(XmlDateHelper.now, CertId(new BigInteger("1234567890")), None, "asdf".getBytes)))) //CA-signed signing cert doMarshallingRoundTrip(BroadcastMessage(123456L, authn, DeleteQueryRequest(projectId, waitTime, authn, queryId), Some(Signature(XmlDateHelper.now, CertId(new BigInteger("1234567890")), Some(CertData("cert signed by ca".getBytes)), "asdf".getBytes)))) //Non-i2b2able requests doMarshallingRoundTrip(ReadTranslatedQueryDefinitionRequest(authn, waitTime, queryDefinition)) doMarshallingRoundTrip(ReadQueryResultRequest(projectId, waitTime, authn, queryId)) //I2b2able requests doMarshallingRoundTrip(DeleteQueryRequest(projectId, waitTime, authn, queryId)) doMarshallingRoundTrip(ReadApprovedQueryTopicsRequest(projectId, waitTime, authn, userId)) doMarshallingRoundTrip(ReadInstanceResultsRequest(projectId, waitTime, authn, queryId)) doMarshallingRoundTrip(ReadPdoRequest(projectId, waitTime, authn, patientSetCollId, optionsXml)) doMarshallingRoundTrip(ReadPreviousQueriesRequest(projectId, waitTime, authn, userId, fetchSize)) doMarshallingRoundTrip(ReadQueryDefinitionRequest(projectId, waitTime, authn, queryId)) doMarshallingRoundTrip(ReadQueryInstancesRequest(projectId, waitTime, authn, queryId)) doMarshallingRoundTrip(RenameQueryRequest(projectId, waitTime, authn, queryId, queryName)) - doMarshallingRoundTrip(RunQueryRequest(projectId, waitTime, authn, queryId, Option(topicId), outputTypes, queryDefinition)) - doMarshallingRoundTrip(RunQueryRequest(projectId, waitTime, authn, queryId, None, outputTypes, queryDefinition)) + doMarshallingRoundTrip(RunQueryRequest(projectId, waitTime, authn, queryId, Option(topicId), Option(topicName), outputTypes, queryDefinition)) + doMarshallingRoundTrip(RunQueryRequest(projectId, waitTime, authn, queryId, None, None, outputTypes, queryDefinition)) doMarshallingRoundTrip(ReadResultRequest(projectId, waitTime, authn, localResultId)) //Non-i2b2able responses doMarshallingRoundTrip(SingleNodeReadTranslatedQueryDefinitionResponse(SingleNodeTranslationResult(nodeId, queryDefinition))) doMarshallingRoundTrip(AggregatedReadTranslatedQueryDefinitionResponse(Seq(SingleNodeTranslationResult(nodeId, queryDefinition), SingleNodeTranslationResult(nodeId2, queryDefinition2)))) //I2b2able responses doMarshallingRoundTrip(DeleteQueryResponse(queryId)) doMarshallingRoundTrip(ReadApprovedQueryTopicsResponse(Seq(ApprovedTopic(queryTopicId1, queryTopicName1), ApprovedTopic(queryTopicId2, queryTopicName2)))) doMarshallingRoundTrip(ReadInstanceResultsResponse(shrineNetworkQueryId, singleNodeResult2)) doMarshallingRoundTrip(AggregatedReadInstanceResultsResponse(shrineNetworkQueryId, Seq(singleNodeResult1, singleNodeResult2))) doMarshallingRoundTrip(ReadPdoResponse(Seq(EventResponse("foo", "bar", start, end, Seq(param1))), Seq(PatientResponse("nuh", Seq(param1))), Nil)) doMarshallingRoundTrip(ReadPreviousQueriesResponse(Seq(queryMaster1, queryMaster2))) doMarshallingRoundTrip(ReadQueryDefinitionResponse(42L, "name", userId, start.get, queryDefinition.toXmlString)) doMarshallingRoundTrip(ReadQueryInstancesResponse(42L, userId, groupId, Seq(queryInstance1, queryInstance2))) doMarshallingRoundTrip(RenameQueryResponse(12345L, queryName)) doMarshallingRoundTrip(RunQueryResponse(queryId, start.get, userId, groupId, queryDefinition, 12345L, singleNodeResult1)) doMarshallingRoundTrip(AggregatedRunQueryResponse(queryId, start.get, userId, groupId, queryDefinition, 12345L, Seq(singleNodeResult1, singleNodeResult2))) doMarshallingRoundTrip(ReadResultResponse(42L, singleNodeResult2, envelope)) } private def doMarshallingRoundTrip[T <: ShrineMessage](message: T) { val xml = message.toXml val unmarshalled = ShrineMessage.fromXml(DefaultBreakdownResultOutputTypes.toSet)(xml).get message match { //NB: Special handling of ReadPdoRequest due to fiddly serialization and equality issues with its //NodeSeq field. :( :( case readPdoRequest: ReadPdoRequest => { val unmarshalledReadPdoRequest = unmarshalled.asInstanceOf[ReadPdoRequest] readPdoRequest.projectId should equal(unmarshalledReadPdoRequest.projectId) readPdoRequest.waitTime should equal(unmarshalledReadPdoRequest.waitTime) readPdoRequest.authn should equal(unmarshalledReadPdoRequest.authn) readPdoRequest.patientSetCollId should equal(unmarshalledReadPdoRequest.patientSetCollId) //NB: Ugh :( readPdoRequest.optionsXml.toString should equal(unmarshalledReadPdoRequest.optionsXml.toString) } //NB: Special handling of ReadInstanceResultsResponse because its member QueryRequests are munged //before serialization case readInstanceResultsResponse: ReadInstanceResultsResponse => { val unmarshalledResp = unmarshalled.asInstanceOf[ReadInstanceResultsResponse] val expected = readInstanceResultsResponse.withQueryResult(readInstanceResultsResponse.singleNodeResult.copy(instanceId = readInstanceResultsResponse.shrineNetworkQueryId)) unmarshalledResp should equal(expected) } //NB: Special handling of AggregatedReadInstanceResultsResponse because its member QueryRequests are munged //before serialization case aggReadInstanceResultsResponse: AggregatedReadInstanceResultsResponse => { val unmarshalledResp = unmarshalled.asInstanceOf[AggregatedReadInstanceResultsResponse] val expected = aggReadInstanceResultsResponse.withResults(aggReadInstanceResultsResponse.results.map(_.copy(instanceId = aggReadInstanceResultsResponse.shrineNetworkQueryId))) unmarshalledResp should equal(expected) } //NB: Special handling of ReadQueryInstancesResponse because its member QueryInstances are not exactly preserved //on serialization round trips case readQueryInstancesResponse: ReadQueryInstancesResponse => { val unmarshalledResp = unmarshalled.asInstanceOf[ReadQueryInstancesResponse] val expected = unmarshalledResp.withInstances(unmarshalledResp.queryInstances.map(_.copy(queryMasterId = unmarshalledResp.queryMasterId.toString))) } //NB: Special handling of RunQueryResponse because its member QueryRequest is munged //during serialization case runQueryResponse: RunQueryResponse => { val unmarshalledResp = unmarshalled.asInstanceOf[RunQueryResponse] val expected = runQueryResponse.withResult(runQueryResponse.singleNodeResult.copy(instanceId = runQueryResponse.queryInstanceId)) unmarshalledResp should equal(expected) } //NB: Special handling of AggregatedRunQueryResponse because its member QueryRequests are munged //during serialization case aggRunQueryResponse: AggregatedRunQueryResponse => { val unmarshalledResp = unmarshalled.asInstanceOf[AggregatedRunQueryResponse] val expected = aggRunQueryResponse.withResults(aggRunQueryResponse.results.map(_.copy(instanceId = aggRunQueryResponse.queryInstanceId))) unmarshalledResp should equal(expected) } case _ => unmarshalled should equal(message) } } } \ No newline at end of file diff --git a/commons/protocol/src/test/scala/net/shrine/protocol/ShrineRequestTest.scala b/commons/protocol/src/test/scala/net/shrine/protocol/ShrineRequestTest.scala index 95bec0ef5..5d0de6ed1 100644 --- a/commons/protocol/src/test/scala/net/shrine/protocol/ShrineRequestTest.scala +++ b/commons/protocol/src/test/scala/net/shrine/protocol/ShrineRequestTest.scala @@ -1,78 +1,79 @@ package net.shrine.protocol import scala.xml.NodeSeq import org.junit.Test import net.shrine.util.ShouldMatchersForJUnit import net.shrine.protocol.query.QueryDefinition import net.shrine.protocol.query.Term import scala.xml.XML /** * @author clint - * @date Mar 22, 2013 + * @since Mar 22, 2013 */ final class ShrineRequestTest extends ShouldMatchersForJUnit { @Test def testFromXmlThrowsOnBadInput { intercept[Exception] { ShrineRequest.fromXml(DefaultBreakdownResultOutputTypes.toSet)(XML.loadString("asdasdasddas")) } } @Test def testFromXml { val projectId = "salkdjksaljdkla" import scala.concurrent.duration._ val waitTime: Duration = 98374L.milliseconds val userId = "foo-user" val authn = AuthenticationInfo("blarg-domain", userId, Credential("sajkhdkjsadh", true)) val queryId = 485794359L val patientSetCollId = "ksaldjksal" val optionsXml: NodeSeq = x val fetchSize = 12345 val queryName = "saljkd;salda" val topicId = "saldjkasljdasdsadsadasdas" + val topicName = "Topic Name" val outputTypes = ResultOutputType.nonBreakdownTypes.toSet val queryDefinition = QueryDefinition(queryName, Term("oiweruoiewkldfhsofi")) val localResultId = "aoiduaojsdpaojcmsal" def doMarshallingRoundTrip(req: ShrineRequest) { val xml = req.toXml val unmarshalled = ShrineRequest.fromXml(DefaultBreakdownResultOutputTypes.toSet)(xml) req match { //NB: Special handling of ReadPdoRequest due to fiddly serialization and equality issues with its NodeSeq field. :( :( case readPdoRequest: ReadPdoRequest => { val unmarshalledReadPdoRequest = unmarshalled.get.asInstanceOf[ReadPdoRequest] readPdoRequest.projectId should equal(unmarshalledReadPdoRequest.projectId) readPdoRequest.waitTime should equal(unmarshalledReadPdoRequest.waitTime) readPdoRequest.authn should equal(unmarshalledReadPdoRequest.authn) readPdoRequest.patientSetCollId should equal(unmarshalledReadPdoRequest.patientSetCollId) //NB: Ugh :( readPdoRequest.optionsXml.toString should equal(unmarshalledReadPdoRequest.optionsXml.toString) } case _ => unmarshalled.get should equal(req) } } //doMarshallingRoundTrip(ReadQueryResultRequest(projectId, waitTime, authn, queryId)) doMarshallingRoundTrip(DeleteQueryRequest(projectId, waitTime, authn, queryId)) doMarshallingRoundTrip(ReadApprovedQueryTopicsRequest(projectId, waitTime, authn, userId)) doMarshallingRoundTrip(ReadInstanceResultsRequest(projectId, waitTime, authn, queryId)) doMarshallingRoundTrip(ReadPdoRequest(projectId, waitTime, authn, patientSetCollId, optionsXml)) doMarshallingRoundTrip(ReadPreviousQueriesRequest(projectId, waitTime, authn, userId, fetchSize)) doMarshallingRoundTrip(ReadQueryDefinitionRequest(projectId, waitTime, authn, queryId)) doMarshallingRoundTrip(ReadQueryInstancesRequest(projectId, waitTime, authn, queryId)) doMarshallingRoundTrip(RenameQueryRequest(projectId, waitTime, authn, queryId, queryName)) - doMarshallingRoundTrip(RunQueryRequest(projectId, waitTime, authn, queryId, Option(topicId), outputTypes, queryDefinition)) - doMarshallingRoundTrip(RunQueryRequest(projectId, waitTime, authn, queryId, None, outputTypes, queryDefinition)) + doMarshallingRoundTrip(RunQueryRequest(projectId, waitTime, authn, queryId, Option(topicId), Option(topicName), outputTypes, queryDefinition)) + doMarshallingRoundTrip(RunQueryRequest(projectId, waitTime, authn, queryId, None, None, outputTypes, queryDefinition)) doMarshallingRoundTrip(ReadResultRequest(projectId, waitTime, authn, localResultId)) doMarshallingRoundTrip(FlagQueryRequest(projectId, waitTime, authn, queryId, None)) doMarshallingRoundTrip(FlagQueryRequest(projectId, waitTime, authn, queryId, Some("some-message"))) doMarshallingRoundTrip(UnFlagQueryRequest(projectId, waitTime, authn, queryId)) } } \ No newline at end of file diff --git a/hms-support/hms-core/src/test/scala/net/shrine/hms/authorization/HmsDataStewardAuthorizationServiceTest.scala b/hms-support/hms-core/src/test/scala/net/shrine/hms/authorization/HmsDataStewardAuthorizationServiceTest.scala index ca52d56dd..bbde17919 100644 --- a/hms-support/hms-core/src/test/scala/net/shrine/hms/authorization/HmsDataStewardAuthorizationServiceTest.scala +++ b/hms-support/hms-core/src/test/scala/net/shrine/hms/authorization/HmsDataStewardAuthorizationServiceTest.scala @@ -1,162 +1,162 @@ package net.shrine.hms.authorization import net.shrine.util.ShouldMatchersForJUnit import org.junit.Test import net.shrine.protocol.ApprovedTopic import org.scalatest.mock.EasyMockSugar import net.shrine.authentication.AuthenticationResult import net.shrine.authentication.Authenticator import net.shrine.protocol.AuthenticationInfo import net.shrine.protocol.Credential import net.shrine.protocol.ReadApprovedQueryTopicsRequest import net.shrine.protocol.ErrorResponse import net.shrine.protocol.RunQueryRequest import net.shrine.protocol.query.QueryDefinition import net.shrine.protocol.query.Term import net.shrine.protocol.ReadApprovedQueryTopicsResponse /** * @author Bill Simons - * @date 1/30/12 - * @link http://cbmi.med.harvard.edu - * @link http://chip.org + * @since 1/30/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 class HmsDataStewardAuthorizationServiceTest extends ShouldMatchersForJUnit { @Test def testIdentifyEcommonsUsername: Unit = { import HmsDataStewardAuthorizationService.identifyEcommonsUsername import AuthenticationResult._ identifyEcommonsUsername(NotAuthenticated("", "", "")) should be(None) val ecommonsId = "foo" identifyEcommonsUsername(Authenticated("", ecommonsId)) should be(Some(ecommonsId)) } import HmsDataStewardAuthorizationServiceTest._ import scala.concurrent.duration._ private val authn = AuthenticationInfo("d", "u", Credential("p", false)) @Test def testReadApprovedEntriesNotAuthenticated { val service = HmsDataStewardAuthorizationService(null, NeverAuthenticatesAuthenticator) val result = service.readApprovedEntries(ReadApprovedQueryTopicsRequest("projectId", 0.minutes, authn, authn.username)) val Left(errorResponse: ErrorResponse) = result errorResponse.errorMessage should not be (null) } @Test def testReadApprovedEntriesAuthenticated { val topic = ApprovedTopic(123L, "blarg") val ecommonsUsername = "abc123" val mockSheriffClient = MockSheriffClient(topics = Seq(topic)) val service = HmsDataStewardAuthorizationService(mockSheriffClient, AlwaysAuthenticatesAuthenticator(ecommonsUsername)) val result = service.readApprovedEntries(ReadApprovedQueryTopicsRequest("projectId", 0.minutes, authn, authn.username)) val Right(ReadApprovedQueryTopicsResponse(Seq(actualTopic))) = result actualTopic should equal(topic) mockSheriffClient.Params.user should be(null) mockSheriffClient.Params.topicId should be(null) mockSheriffClient.Params.queryText should be(null) mockSheriffClient.Params.ecommonsUsername should be(ecommonsUsername) } @Test def testAuthorizeRunQueryRequestNotAuthenticated { val service = HmsDataStewardAuthorizationService(null, NeverAuthenticatesAuthenticator) - def doTest(topicId: Option[String]): Unit = { - val result = service.authorizeRunQueryRequest(RunQueryRequest("projectId", 0.minutes, authn, 12345L, topicId, Set.empty, QueryDefinition("foo", Term("foo")))) + def doTest(topicId: Option[String],topicName:Option[String]): Unit = { + val result = service.authorizeRunQueryRequest(RunQueryRequest("projectId", 0.minutes, authn, 12345L, topicId, topicName, Set.empty, QueryDefinition("foo", Term("foo")))) result.isAuthorized should be(false) } - doTest(None) - doTest(Some("topicId")) + doTest(None,None) + doTest(Some("topicId"),Some("Topic Name")) } @Test def testAuthorizeRunQueryRequestAuthenticated { - def doTest(isAuthorized: Boolean, topicId: Option[String]): Unit = { + def doTest(isAuthorized: Boolean, topicId: Option[String], topicName:Option[String]): Unit = { val ecommonsUsername = "abc123" val queryDef = QueryDefinition("foo", Term("foo")) val mockSheriffClient = MockSheriffClient(authorized = isAuthorized) val service = HmsDataStewardAuthorizationService(mockSheriffClient, AlwaysAuthenticatesAuthenticator(ecommonsUsername)) - val result = service.authorizeRunQueryRequest(RunQueryRequest("projectId", 0.minutes, authn, 12345L, topicId, Set.empty, queryDef)) + val result = service.authorizeRunQueryRequest(RunQueryRequest("projectId", 0.minutes, authn, 12345L, topicId, topicName, Set.empty, queryDef)) val expectedIsAuthorized = isAuthorized && topicId.isDefined result.isAuthorized should be(expectedIsAuthorized) if (topicId.isDefined) { mockSheriffClient.Params.user should equal(ecommonsUsername) mockSheriffClient.Params.topicId should equal(topicId.get) mockSheriffClient.Params.queryText should equal(queryDef.toI2b2String) mockSheriffClient.Params.ecommonsUsername should be(null) } else { mockSheriffClient.Params.user should be(null) mockSheriffClient.Params.topicId should be(null) mockSheriffClient.Params.queryText should be(null) mockSheriffClient.Params.ecommonsUsername should be(null) } } - doTest(true, Some("topic123")) - doTest(false, Some("topic123")) - doTest(true, None) - doTest(false, None) + doTest(true, Some("topic123"), Some("Topic Name")) + doTest(false, Some("topic123"), Some("Topic Name")) + doTest(true, None, None) + doTest(false, None, None) } } object HmsDataStewardAuthorizationServiceTest { object NeverAuthenticatesAuthenticator extends Authenticator { override def authenticate(authn: AuthenticationInfo) = AuthenticationResult.NotAuthenticated(authn.domain, authn.username, "foo") } final case class AlwaysAuthenticatesAuthenticator(ecommonsUsername: String) extends Authenticator { override def authenticate(authn: AuthenticationInfo) = AuthenticationResult.Authenticated(authn.domain, ecommonsUsername) } final case class MockSheriffClient(authorized: Boolean = false, topics: Seq[ApprovedTopic] = Nil) extends SheriffClient { object Params { var ecommonsUsername: String = _ var user: String = _ var topicId: String = _ var queryText: String = _ } override def getApprovedEntries(ecommonsUsername: String): Seq[ApprovedTopic] = { Params.ecommonsUsername = ecommonsUsername topics } override def isAuthorized(user: String, topicId: String, queryText: String): Boolean = { Params.user = user Params.topicId = topicId Params.queryText = queryText authorized } } } \ No newline at end of file diff --git a/hub/broadcaster-aggregator/src/test/scala/net/shrine/aggregation/AggregatorsTest.scala b/hub/broadcaster-aggregator/src/test/scala/net/shrine/aggregation/AggregatorsTest.scala index 36d0ebeca..3bde4a3c5 100644 --- a/hub/broadcaster-aggregator/src/test/scala/net/shrine/aggregation/AggregatorsTest.scala +++ b/hub/broadcaster-aggregator/src/test/scala/net/shrine/aggregation/AggregatorsTest.scala @@ -1,42 +1,42 @@ package net.shrine.aggregation import net.shrine.util.ShouldMatchersForJUnit import org.junit.Test import net.shrine.protocol.AuthenticationInfo import net.shrine.protocol.Credential import net.shrine.protocol.query.QueryDefinition import net.shrine.protocol.query.Term import net.shrine.protocol.RunQueryRequest /** * @author clint - * @date Mar 14, 2013 + * @since Mar 14, 2013 */ final class AggregatorsTest extends ShouldMatchersForJUnit { @Test - def testForRunQueryRequest { + def testForRunQueryRequest() { - val authn = AuthenticationInfo("some-domain", "some-user", Credential("some-password", false)) + val authn = AuthenticationInfo("some-domain", "some-user", Credential("some-password", isToken = false)) val projectId = "projectId" val queryDef = QueryDefinition("yo", Term("foo")) import scala.concurrent.duration._ - val request = RunQueryRequest(projectId, 1.millisecond, authn, 0L, Some("topicId"), Set.empty, queryDef) + val request = RunQueryRequest(projectId, 1.millisecond, authn, 0L, Some("topicId"), Some("Topic Name"), Set.empty, queryDef) def doTestRunQueryAggregatorFor(addAggregatedResult: Boolean) { val aggregator = Aggregators.forRunQueryRequest(addAggregatedResult)(request) - aggregator should not be (null) + 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) } } \ No newline at end of file diff --git a/hub/broadcaster-aggregator/src/test/scala/net/shrine/broadcaster/AdapterClientBroadcasterLoggingTest.scala b/hub/broadcaster-aggregator/src/test/scala/net/shrine/broadcaster/AdapterClientBroadcasterLoggingTest.scala index 3836dd459..2c51990f0 100644 --- a/hub/broadcaster-aggregator/src/test/scala/net/shrine/broadcaster/AdapterClientBroadcasterLoggingTest.scala +++ b/hub/broadcaster-aggregator/src/test/scala/net/shrine/broadcaster/AdapterClientBroadcasterLoggingTest.scala @@ -1,169 +1,169 @@ package net.shrine.broadcaster import net.shrine.protocol.AuthenticationInfo import net.shrine.protocol.Credential import net.shrine.protocol.BroadcastMessage import net.shrine.protocol.DeleteQueryRequest import org.junit.Test import net.shrine.protocol.NodeId import net.shrine.adapter.client.InJvmAdapterClient import net.shrine.protocol.Result import scala.concurrent.Future import net.shrine.adapter.client.AdapterClient import net.shrine.protocol.DeleteQueryResponse import net.shrine.aggregation.DeleteQueryAggregator import net.shrine.util.ShouldMatchersForJUnit import scala.concurrent.Await import net.shrine.dao.squeryl.SquerylEntryPoint import net.shrine.protocol.RunQueryRequest import net.shrine.protocol.ResultOutputType import net.shrine.protocol.query.Term import net.shrine.protocol.query.Or import net.shrine.protocol.query.QueryDefinition import net.shrine.protocol.RunQueryResponse import net.shrine.protocol.QueryResult import net.shrine.util.XmlDateHelper import net.shrine.aggregation.RunQueryAggregator import net.shrine.protocol.AggregatedRunQueryResponse import net.shrine.protocol.AggregatedRunQueryResponse import net.shrine.protocol.ShrineResponse import net.shrine.protocol.ErrorResponse import net.shrine.broadcaster.dao.model.squeryl.SquerylHubQueryResultRow import net.shrine.broadcaster.dao.model.HubQueryResultRow import net.shrine.broadcaster.dao.model.HubQueryStatus /** * @author clint * @date Dec 15, 2014 */ final class AdapterClientBroadcasterLoggingTest extends AbstractSquerylHubDaoTest with ShouldMatchersForJUnit { private def makeBroadcaster(nodes: Set[NodeHandle]): AdapterClientBroadcaster = AdapterClientBroadcaster(nodes, dao) import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ private final class TestAdapterClient(toReturn: => Result) extends AdapterClient { override def query(message: BroadcastMessage): Future[Result] = Future { toReturn } } private object TestAdapterClient { def apply(toReturn: => Result): TestAdapterClient = new TestAdapterClient(toReturn) } private val authn = AuthenticationInfo("domain", "username", Credential("asdasd", false)) private val queryDef = QueryDefinition("foo", Or(Term("x"), Term("y"))) private val broadcastMessageDelete = { BroadcastMessage(authn, DeleteQueryRequest("projectId", 12345.milliseconds, authn, 12345L)) } import ResultOutputType.PATIENT_COUNT_XML private val broadcastMessageRunQuery = { - BroadcastMessage(authn, RunQueryRequest("projectId", 12345.milliseconds, authn, -1L, None, Set(PATIENT_COUNT_XML), queryDef)) + BroadcastMessage(authn, RunQueryRequest("projectId", 12345.milliseconds, authn, -1L, None, None, Set(PATIENT_COUNT_XML), queryDef)) } private val deleteQueryAggregator = new DeleteQueryAggregator import SquerylEntryPoint._ @Test def testShouldntLogAllNodesSucceed: Unit = afterCreatingTables { val workingNodes = Set( NodeHandle(NodeId("X"), TestAdapterClient(Result(NodeId("X"), 1.second, DeleteQueryResponse(12345L)))), NodeHandle(NodeId("Y"), TestAdapterClient(Result(NodeId("Y"), 1.second, DeleteQueryResponse(12345L))))) doTestShouldntLog(workingNodes) } @Test def testShouldntLogSomeNodesSucceed: Unit = afterCreatingTables { val nodes = Set( NodeHandle(NodeId("X"), TestAdapterClient(Result(NodeId("X"), 1.second, DeleteQueryResponse(12345L)))), NodeHandle(NodeId("Y"), TestAdapterClient(throw new Exception))) doTestShouldntLog(nodes) } @Test def testShouldntLogNoNodesSucceed: Unit = afterCreatingTables { val failingNodes = Set( NodeHandle(NodeId("X"), TestAdapterClient(throw new Exception)), NodeHandle(NodeId("Y"), TestAdapterClient(throw new Exception))) doTestShouldntLog(failingNodes, classOf[ErrorResponse]) } private def doTestShouldntLog(nodeHandles: Set[NodeHandle], expectedAggregatedResponseType: Class[_ <: ShrineResponse] = classOf[DeleteQueryResponse]): Unit = { val broadcaster = makeBroadcaster(nodeHandles) val responses = Await.result(broadcaster.broadcast(broadcastMessageDelete).responses, Duration.Inf) responses.size should equal(nodeHandles.size) //Only log RunQueryRequests list(queryRows) should be(Nil) //Only log RunQueryRequests list(queryResultRows) should be(Nil) } import net.shrine.broadcaster.dao.model.{HubQueryStatus => hqs} @Test def testShouldLogAllNodesSucceed: Unit = doTestShouldLog(Map("X" -> hqs.Success, "Y" -> hqs.Success)) @Test def testShouldLogSomeNodesSucceed: Unit = doTestShouldLog(Map("X" -> hqs.Success, "Y" -> hqs.Failure)) @Test def testShouldLogNoNodesSucceed: Unit = doTestShouldLog(Map("X" -> hqs.Failure, "Y" -> hqs.Failure)) private def doTestShouldLog(expectedStatusesByNodeName: Map[String, HubQueryStatus]): Unit = afterCreatingTables { val queryResult = QueryResult(99L, 11L,Some(PATIENT_COUNT_XML), 42L, Some(XmlDateHelper.now), Some(XmlDateHelper.now), Some("desc"), QueryResult.StatusType.Finished, Some("status")) val nodes: Set[NodeHandle] = expectedStatusesByNodeName.map { case (nodeName, status) => val adapterClient = status match { case hqs.Success => TestAdapterClient(Result(NodeId(nodeName), 1.second, RunQueryResponse(12345L, XmlDateHelper.now, "uid", "gid", queryDef, 42L, queryResult))) case hqs.Failure => TestAdapterClient(throw new Exception) } NodeHandle(NodeId(nodeName), adapterClient) }.toSet val broadcaster = makeBroadcaster(nodes) val responses = Await.result(broadcaster.broadcast(broadcastMessageRunQuery).responses, Duration.Inf) responses.size should equal(expectedStatusesByNodeName.size) val Seq(queryRow) = list(queryRows).map(_.toHubQueryRow) queryRow.networkQueryId should be(broadcastMessageRunQuery.requestId) queryRow.queryDefinition should be(queryDef) queryRow.domain should be(authn.domain) queryRow.username should be(authn.username) queryRow.time should not be(null) val resultRows @ Seq(resultRow1, resultRow2) = list(queryResultRows).map(_.toHubQueryResultRow) resultRow1.networkQueryId should be(broadcastMessageRunQuery.requestId) resultRow2.networkQueryId should be(broadcastMessageRunQuery.requestId) val resultRowsByNode: Map[String, HubQueryResultRow] = resultRows.map(row => row.nodeName -> row).toMap resultRowsByNode.keySet should be(expectedStatusesByNodeName.keySet) for { (nodeName, expectedStatus) <- expectedStatusesByNodeName } { resultRowsByNode(nodeName).status should be(expectedStatus) } resultRow1.timestamp should not be(null) resultRow2.timestamp should not be(null) } } \ No newline at end of file diff --git a/hub/broadcaster-aggregator/src/test/scala/net/shrine/broadcaster/BroadcastAndAggregationServiceTest.scala b/hub/broadcaster-aggregator/src/test/scala/net/shrine/broadcaster/BroadcastAndAggregationServiceTest.scala index 101c64449..a6678546c 100644 --- a/hub/broadcaster-aggregator/src/test/scala/net/shrine/broadcaster/BroadcastAndAggregationServiceTest.scala +++ b/hub/broadcaster-aggregator/src/test/scala/net/shrine/broadcaster/BroadcastAndAggregationServiceTest.scala @@ -1,174 +1,174 @@ package net.shrine.broadcaster import net.shrine.util.ShouldMatchersForJUnit import org.junit.Test import net.shrine.protocol.DeleteQueryRequest import net.shrine.protocol.BroadcastMessage import net.shrine.protocol.ShrineResponse import net.shrine.aggregation.Aggregator import scala.concurrent.Future import net.shrine.protocol.RunQueryRequest import net.shrine.protocol.AuthenticationInfo import net.shrine.protocol.Credential import net.shrine.protocol.query.QueryDefinition import net.shrine.protocol.query.Term import net.shrine.aggregation.DeleteQueryAggregator import net.shrine.aggregation.RunQueryAggregator import net.shrine.aggregation.ReadQueryResultAggregator /** * @author clint - * @date Mar 14, 2013 + * @since Mar 14, 2013 */ final class BroadcastAndAggregationServiceTest extends ShouldMatchersForJUnit { import BroadcastAndAggregationServiceTest._ - private val authn = AuthenticationInfo("some-domain", "some-user", Credential("some-password", false)) + private val authn = AuthenticationInfo("some-domain", "some-user", Credential("some-password", isToken = false)) private val queryDef = QueryDefinition("yo", Term("foo")) import scala.concurrent.duration._ @Test - def testSendAndAggregateShrineRequest { + def testSendAndAggregateShrineRequest() { val service = new TestBroadcastAndAggregationService { val req = DeleteQueryRequest("projectId", 1.millisecond, authn, 123L) val aggregator = new DeleteQueryAggregator - val networkAuthn = AuthenticationInfo("d", "u", Credential("p", false)) + val networkAuthn = AuthenticationInfo("d", "u", Credential("p", isToken = false)) - service.sendAndAggregate(networkAuthn, req, aggregator, true) + service.sendAndAggregate(networkAuthn, req, aggregator, shouldBroadcast = true) service.args.shouldBroadcast should be(Some(true)) - service.sendAndAggregate(networkAuthn, req, aggregator, false) + service.sendAndAggregate(networkAuthn, req, aggregator, shouldBroadcast = false) service.args.shouldBroadcast should be(Some(false)) service.args.aggregator should be(aggregator) service.args.message.networkAuthn should be(networkAuthn) service.args.message.request should be(req) - (service.args.message.requestId > 0) should be(true) + (service.args.message.requestId > 0) should be(right = true) } { val invalidQueryId = -1L - val req = RunQueryRequest("projectId", 1.millisecond, authn, invalidQueryId, Some("topicId"), Set.empty, queryDef) + val req = RunQueryRequest("projectId", 1.millisecond, authn, invalidQueryId, Some("topicId"), Some("Topic Name"), Set.empty, queryDef) val aggregator = new RunQueryAggregator(invalidQueryId, authn.username, authn.domain, queryDef, true) - val networkAuthn = AuthenticationInfo("d", "u", Credential("p", false)) + val networkAuthn = AuthenticationInfo("d", "u", Credential("p", isToken = false)) - service.sendAndAggregate(networkAuthn, req, aggregator, true) + service.sendAndAggregate(networkAuthn, req, aggregator, shouldBroadcast = true) service.args.shouldBroadcast should be(Some(true)) - (service.args.message.requestId > 0) should be(true) - service.args.message.request should not be(req) + (service.args.message.requestId > 0) should be(right = true) + service.args.message.request should not be req service.args.message.request.asInstanceOf[RunQueryRequest].networkQueryId should be(service.args.message.requestId) service.args.message.networkAuthn should be(networkAuthn) - service.args.aggregator should not be(aggregator) + service.args.aggregator should not be aggregator service.args.aggregator.asInstanceOf[RunQueryAggregator].queryId should be(service.args.message.requestId) } } @Test - def testAddQueryIdAggregator { + def testAddQueryIdAggregator() { val service = new TestBroadcastAndAggregationService { val aggregator = new DeleteQueryAggregator val munged = service.addQueryId(null, aggregator) - (munged eq aggregator) should be(true) + (munged eq aggregator) should be(right = true) } { val aggregator = new RunQueryAggregator(-1L, authn.username, authn.domain, queryDef, true) val message = BroadcastMessage(999L, authn, null) val munged = service.addQueryId(message, aggregator).asInstanceOf[RunQueryAggregator] munged.queryId should be(message.requestId) munged.userId should equal(aggregator.userId) munged.groupId should equal(aggregator.groupId) munged.requestQueryDefinition should equal(aggregator.requestQueryDefinition) munged.addAggregatedResult should equal(aggregator.addAggregatedResult) } def doTestWithReadQueryResultAggregator(showAggregation: Boolean) { val aggregator = new ReadQueryResultAggregator(-1L, showAggregation) val message = BroadcastMessage(999L, authn, null) val munged = service.addQueryId(message, aggregator).asInstanceOf[ReadQueryResultAggregator] - munged should not be(aggregator) + munged should not be aggregator munged.shrineNetworkQueryId should be(message.requestId) munged.showAggregation should be(aggregator.showAggregation) } doTestWithReadQueryResultAggregator(true) doTestWithReadQueryResultAggregator(false) } @Test - def testAddQueryIdShrineRequest { + def testAddQueryIdShrineRequest() { val service = new TestBroadcastAndAggregationService { val req = DeleteQueryRequest("projectId", 1.millisecond, authn, 123L) val (queryIdOption, transformedReq) = service.addQueryId(req) queryIdOption should be(None) transformedReq should be(req) } { - val req = RunQueryRequest("projectId", 1.millisecond, authn, -1L, Some("topicId"), Set.empty, QueryDefinition("yo", Term("foo"))) + val req = RunQueryRequest("projectId", 1.millisecond, authn, -1L, Some("topicId"), Some("Topic Name"), Set.empty, QueryDefinition("yo", Term("foo"))) val (queryIdOption, transformedReq: RunQueryRequest) = service.addQueryId(req) - queryIdOption should not be(None) + queryIdOption should not be None - (queryIdOption.get > 0) should be(true) + (queryIdOption.get > 0) should be(right = true) transformedReq.networkQueryId should be(queryIdOption.get) transformedReq.projectId should be(req.projectId) transformedReq.waitTime should be(req.waitTime) transformedReq.authn should be(req.authn) transformedReq.topicId should be(req.topicId) transformedReq.outputTypes should be(req.outputTypes) transformedReq.queryDefinition should be(req.queryDefinition) } } } object BroadcastAndAggregationServiceTest { private final class TestBroadcastAndAggregationService extends BroadcastAndAggregationService { object args { var message: BroadcastMessage = _ var aggregator: Aggregator = _ var shouldBroadcast: Option[Boolean] = None } override def sendAndAggregate(message: BroadcastMessage, aggregator: Aggregator, shouldBroadcast: Boolean): Future[ShrineResponse] = { args.message = message args.aggregator = aggregator args.shouldBroadcast = Some(shouldBroadcast) Future.successful(null) } } } \ No newline at end of file diff --git a/integration/src/test/scala/net/shrine/integration/I2b2ShrineClient.scala b/integration/src/test/scala/net/shrine/integration/I2b2ShrineClient.scala index f8fab7e2c..8b840fa4e 100644 --- a/integration/src/test/scala/net/shrine/integration/I2b2ShrineClient.scala +++ b/integration/src/test/scala/net/shrine/integration/I2b2ShrineClient.scala @@ -1,88 +1,88 @@ package net.shrine.integration import net.shrine.client.ShrineClient import net.shrine.crypto.TrustParam import net.shrine.protocol.AuthenticationInfo import net.shrine.util.XmlDateHelper import net.shrine.client.Poster import net.shrine.protocol.ResultOutputType import scala.xml.NodeSeq import net.shrine.protocol.AggregatedReadTranslatedQueryDefinitionResponse import net.shrine.protocol.ReadQueryDefinitionResponse import net.shrine.protocol.UnFlagQueryResponse import net.shrine.protocol.ReadPdoResponse import net.shrine.protocol.RenameQueryResponse import net.shrine.protocol.ReadQueryInstancesResponse import net.shrine.protocol.query.QueryDefinition import net.shrine.protocol.AggregatedRunQueryResponse import net.shrine.protocol.FlagQueryResponse import net.shrine.protocol.DeleteQueryResponse import net.shrine.protocol.ReadPreviousQueriesResponse import net.shrine.protocol.ReadApprovedQueryTopicsResponse import net.shrine.protocol.AggregatedReadInstanceResultsResponse import net.shrine.protocol.AggregatedReadQueryResultResponse import net.shrine.protocol.DeleteQueryRequest import net.shrine.client.HttpResponse import net.shrine.protocol.FlagQueryRequest import net.shrine.serialization.I2b2Marshaller import net.shrine.protocol.UnFlagQueryRequest import net.shrine.protocol.RunQueryRequest import net.shrine.protocol.RunQueryResponse import net.shrine.protocol.DefaultBreakdownResultOutputTypes import scala.xml.Node import scala.xml.XML import net.shrine.util.XmlUtil final case class I2b2ShrineClient(poster: Poster, projectId: String, authorization: AuthenticationInfo) extends ShrineClient { import scala.concurrent.duration._ override def readApprovedQueryTopics(userId: String, shouldBroadcast: Boolean): ReadApprovedQueryTopicsResponse = ??? override def readPreviousQueries(userId: String, fetchSize: Int, shouldBroadcast: Boolean): ReadPreviousQueriesResponse = ??? override def runQuery(topicId: String, outputTypes: Set[ResultOutputType], queryDefinition: QueryDefinition, shouldBroadcast: Boolean): AggregatedRunQueryResponse = { - val req = RunQueryRequest(projectId, 1.minute, authorization, -1, None, outputTypes, queryDefinition) + val req = RunQueryRequest(projectId, 1.minute, authorization, -1, None, None, outputTypes, queryDefinition) def stripWhitespace(xml: String): String = XmlUtil.stripWhitespace(XML.loadString(xml)).toString doSend(req, xml => AggregatedRunQueryResponse.fromI2b2String(DefaultBreakdownResultOutputTypes.toSet)(stripWhitespace(xml)).get) } override def readQueryInstances(queryId: Long, shouldBroadcast: Boolean): ReadQueryInstancesResponse = ??? override def readInstanceResults(instanceId: Long, shouldBroadcast: Boolean): AggregatedReadInstanceResultsResponse = ??? override def readPdo(patientSetCollId: String, optionsXml: NodeSeq, shouldBroadcast: Boolean): ReadPdoResponse = ??? override def readQueryDefinition(queryId: Long, shouldBroadcast: Boolean): ReadQueryDefinitionResponse = ??? override def deleteQuery(queryId: Long, shouldBroadcast: Boolean): DeleteQueryResponse = { val req = DeleteQueryRequest(projectId, 1.minute, authorization, queryId) doSend(req, DeleteQueryResponse.fromI2b2) } override def renameQuery(queryId: Long, queryName: String, shouldBroadcast: Boolean): RenameQueryResponse = ??? override def readTranslatedQueryDefinition(queryDef: QueryDefinition, shouldBroadcast: Boolean): AggregatedReadTranslatedQueryDefinitionResponse = ??? override def flagQuery(networkQueryId: Long, message: Option[String], shouldBroadcast: Boolean): FlagQueryResponse = { val req = FlagQueryRequest(projectId, 1.minute, authorization, networkQueryId, message) doSend(req, FlagQueryResponse.fromI2b2(_).get) } override def unFlagQuery(networkQueryId: Long, shouldBroadcast: Boolean): UnFlagQueryResponse = { val req = UnFlagQueryRequest(projectId, 1.minute, authorization, networkQueryId) doSend(req, UnFlagQueryResponse.fromI2b2(_).get) } override def readQueryResult(queryId: Long, shouldBroadcast: Boolean): AggregatedReadQueryResultResponse = ??? private def doSend[R](req: I2b2Marshaller, unmarshal: String => R): R = { val HttpResponse(_, i2b2Resp) = poster.post(req.toI2b2String) unmarshal(i2b2Resp) } } \ 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 b3cfe58e1..74d144e84 100644 --- a/integration/src/test/scala/net/shrine/integration/NetworkSimulationTest.scala +++ b/integration/src/test/scala/net/shrine/integration/NetworkSimulationTest.scala @@ -1,336 +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.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 = { val destinations: Set[NodeHandle] = { (for { (nodeId, adapterRequestHandler) <- adaptersByNodeId } yield { NodeHandle(nodeId, MockAdapterClient(nodeId, adapterRequestHandler)) }).toSet } ShrineService( "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), Set(ResultOutputType.PATIENT_COUNT_XML), QueryDefinition(queryName, Term("n1"))) + 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) 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/qep/service/src/main/scala/net/shrine/service/AbstractShrineService.scala b/qep/service/src/main/scala/net/shrine/service/AbstractShrineService.scala index 0aa6e4ad8..9b2a42d62 100644 --- a/qep/service/src/main/scala/net/shrine/service/AbstractShrineService.scala +++ b/qep/service/src/main/scala/net/shrine/service/AbstractShrineService.scala @@ -1,221 +1,221 @@ package net.shrine.service import net.shrine.log.Loggable -import net.shrine.service.audit.{QepAuditDb, QepQueryAuditData} +import net.shrine.service.audit.QepAuditDb import net.shrine.service.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.util.XmlDateHelper import scala.concurrent.Future import scala.concurrent.Await import net.shrine.protocol.RunQueryRequest import net.shrine.authorization.AuthorizationResult.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 { 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)") afterAuthenticating(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 = { doBroadcastQuery(request, new FlagQueryAggregator, shouldBroadcast) } protected def doUnFlagQuery(request: UnFlagQueryRequest, shouldBroadcast: Boolean = true): BaseResp = { doBroadcastQuery(request, new UnFlagQueryAggregator, shouldBroadcast) } protected def doRunQuery(request: RunQueryRequest, shouldBroadcast: Boolean): BaseResp = { info(s"doRunQuery($request,$shouldBroadcast) with $runQueryAggregatorFor") val result = doBroadcastQuery(request, runQueryAggregatorFor(request), shouldBroadcast) debug(s"collectQepAudit is $collectQepAudit") // tuck the ACT audit metrics data into a database here //todo network id is -1 ! if (collectQepAudit) QepAuditDb.db.insertQepQuery(request,commonName) result } 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)") afterAuthenticating(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 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 = afterAuthenticating(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 = { info(s"doBroadcastQuery($request)") //TODO: XXX: HACK: Would like to remove the cast def doSynchronousQuery(networkAuthn: AuthenticationInfo) = waitFor(sendAndAggregate(networkAuthn, request, aggregator, shouldBroadcast)).asInstanceOf[BaseResp] afterAuthenticating(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 => afterAuditingAndAuthorizing(runQueryRequest) (doSynchronousQuery(networkAuthn)) case _ => doSynchronousQuery(networkAuthn) } } } private[service] 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 afterAuditingAndAuthorizing[T](request: RunQueryRequest)(body: => T): T = { auditTransactionally(request) { debug(s"afterAuditingAndAuthorizing($request) with $authorizationService") authorizationService.authorizeRunQueryRequest(request) match { case na: NotAuthorized => throw na.toException case _ => () } body } } private[service] 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 afterAuthenticating[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/ShrineResource.scala b/qep/service/src/main/scala/net/shrine/service/ShrineResource.scala index 3a9970b06..111b4f082 100644 --- a/qep/service/src/main/scala/net/shrine/service/ShrineResource.scala +++ b/qep/service/src/main/scala/net/shrine/service/ShrineResource.scala @@ -1,258 +1,259 @@ package net.shrine.service 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.ShrineResponse 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 - * @date 8/30/11 - * @link http://cbmi.med.harvard.edu - * @link http://chip.org + * @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 - * @link http://www.gnu.org/licenses/lgpl.html + * @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) //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") //NB: Create the RunQueryRequest with a dummy networkQueryId of '-1'; //this will be filled in with an appropriately-generated value by the ShrineRequestHandler - performAndSerialize(_.runQuery(RunQueryRequest(projectId, waitTime, authorization, -1, topicIdOption, outputTypes.toSet, queryDef), shouldBroadcast)) + performAndSerialize(_.runQuery(RunQueryRequest(projectId, waitTime, authorization, -1, 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/test/scala/net/shrine/service/ShrineResourceTest.scala b/qep/service/src/test/scala/net/shrine/service/ShrineResourceTest.scala index 7f9d45bb4..fa073f637 100644 --- a/qep/service/src/test/scala/net/shrine/service/ShrineResourceTest.scala +++ b/qep/service/src/test/scala/net/shrine/service/ShrineResourceTest.scala @@ -1,233 +1,234 @@ package net.shrine.service 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 - * @date 9/13/2011 - * @link http://cbmi.med.harvard.edu - * @link http://chip.org + * @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 - * @link http://www.gnu.org/licenses/lgpl.html + * @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 expectedRequest = RunQueryRequest(projectId, waitTime, authenticationInfo, 999L, topicId, outputTypes, queryDef) + val expectedRequest = RunQueryRequest(projectId, waitTime, authenticationInfo, 999L, topicId, topicName, outputTypes, queryDef) val expectedResponse = RunQueryResponse(999L, null, "userId", "groupId", queryDef, 0L, QueryResult(1L, 0L, Some(ResultOutputType.PATIENT_COUNT_XML), 123L, None, None, None, QueryResult.StatusType.Finished, None, Map.empty)) 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, new OutputTypeSet(outputTypes), queryDef.toXmlString, shouldBroadcast) + 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/ShrineServiceTest.scala b/qep/service/src/test/scala/net/shrine/service/ShrineServiceTest.scala index 9bdc609b4..4f48a105a 100644 --- a/qep/service/src/test/scala/net/shrine/service/ShrineServiceTest.scala +++ b/qep/service/src/test/scala/net/shrine/service/ShrineServiceTest.scala @@ -1,201 +1,201 @@ package net.shrine.service import org.junit.Test import org.scalatest.mock.EasyMockSugar import net.shrine.authorization.QueryAuthorizationService import net.shrine.service.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 { import scala.concurrent.duration._ @Test def testReadQueryInstances { val projectId = "foo" val queryId = 123L val authn = AuthenticationInfo("some-domain", "some-username", Credential("blarg", false)) val req = ReadQueryInstancesRequest(projectId, 1.millisecond, authn, queryId) val service = ShrineService("example.com",null, AllowsAllAuthenticator, null, true, null, null, Set.empty,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", false)) private val projectId = "projectId" private val queryDef = QueryDefinition("yo", Term("foo")) - private val request = RunQueryRequest(projectId, 1.millisecond, authn, 0L, Some("topicId"), Set.empty, queryDef) + private val request = RunQueryRequest(projectId, 1.millisecond, authn, 0L, 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,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, true, null, null, Set.empty,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._ @Test def testAfterAuthenticating { def doTestAfterAuthenticating(shouldAuthenticate: Boolean) { val service = ShrineService("example.com",auditDao, new MockAuthenticator(shouldAuthenticate), new MockAuthService(true), true, null, null, Set.empty,false) if (shouldAuthenticate) { var foo = false service.afterAuthenticating(request) { _ => foo = true } foo should be(true) } else { intercept[NotAuthenticatedException] { service.afterAuthenticating(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), true, null, null, Set.empty,false) if (shouldThrow || !shouldBeAuthorized) { intercept[Exception] { service.afterAuditingAndAuthorizing(request)(throw new Exception) } } else { val x = 1 val actual = service.afterAuditingAndAuthorizing(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(true, true) doAfterAuditingAndAuthorizing(true, false) doAfterAuditingAndAuthorizing(false, true) doAfterAuditingAndAuthorizing(false, false) } } object ShrineServiceTest { 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) { AuthorizationResult.Authorized } else { AuthorizationResult.NotAuthorized("blarg") } } def readApprovedEntries(request: ReadApprovedQueryTopicsRequest): Either[ErrorResponse, ReadApprovedQueryTopicsResponse] = ??? } } \ No newline at end of file diff --git a/tools/scanner/src/main/scala/net/shrine/utilities/scanner/BroadcastServiceScannerClient.scala b/tools/scanner/src/main/scala/net/shrine/utilities/scanner/BroadcastServiceScannerClient.scala index 6b9e0037c..edbaac74b 100644 --- a/tools/scanner/src/main/scala/net/shrine/utilities/scanner/BroadcastServiceScannerClient.scala +++ b/tools/scanner/src/main/scala/net/shrine/utilities/scanner/BroadcastServiceScannerClient.scala @@ -1,96 +1,96 @@ package net.shrine.utilities.scanner import net.shrine.log.Loggable import scala.concurrent.ExecutionContext import scala.concurrent.Future import Scanner.QueryDefaults.outputTypes import Scanner.QueryDefaults.topicId import ScannerClient.errorTermResult import ScannerClient.toQueryDef import net.shrine.aggregation.Aggregators import net.shrine.aggregation.ReadQueryResultAggregator import net.shrine.broadcaster.BroadcastAndAggregationService import net.shrine.protocol.AggregatedReadQueryResultResponse import net.shrine.protocol.AggregatedRunQueryResponse import net.shrine.protocol.AuthenticationInfo import net.shrine.protocol.ReadQueryResultRequest import net.shrine.protocol.RunQueryRequest import scala.concurrent.duration.Duration import net.shrine.authentication.AuthenticationResult import net.shrine.protocol.Credential import net.shrine.authentication.Authenticator import net.shrine.authentication.NotAuthenticatedException /** * @author clint * @date Mar 14, 2013 */ class BroadcastServiceScannerClient( val projectId: String, override val authn: AuthenticationInfo, val broadcastAndAggregationService: BroadcastAndAggregationService, override val authenticator: Authenticator, implicit val executionContext: ExecutionContext) extends ScannerClient with Loggable { //TODO: Make this configurable private val waitTime: Duration = { import scala.concurrent.duration._ 10.seconds } //Don't ask for an aggregated (summed) result, since we'll get at most one result back in any case private val runQueryAggregatorSource = Aggregators.forRunQueryRequest(false) _ private def toAuthn(authResult: AuthenticationResult.Authenticated) = AuthenticationInfo(authResult.domain, authResult.username, Credential("", false)) override def query(term: String): Future[TermResult] = afterAuthenticating { authResult => import Scanner.QueryDefaults._ info(s"Querying for '$term'") - val request = RunQueryRequest(projectId, waitTime, authn, -1L, Option(topicId), outputTypes, toQueryDef(term)) + val request = RunQueryRequest(projectId, waitTime, authn, -1L, Option(topicId), Option(topicName), outputTypes, toQueryDef(term)) val futureResponse = broadcastAndAggregationService.sendAndAggregate(toAuthn(authResult), request, runQueryAggregatorSource(request), false) def toTermResult(runQueryResponse: AggregatedRunQueryResponse): TermResult = { val termResultOption = for { shrineQueryResult <- runQueryResponse.results.headOption } yield TermResult(runQueryResponse.queryId, shrineQueryResult.resultId, term, shrineQueryResult.statusType, shrineQueryResult.setSize) //TODO: Is this the right query id to use here? termResultOption.getOrElse(errorTermResult(runQueryResponse.queryId, term)) } futureResponse.collect { case resp: AggregatedRunQueryResponse => resp }.map(toTermResult) } override def retrieveResults(termResult: TermResult): Future[TermResult] = afterAuthenticating { authResult => info(s"Retrieving results for previously-incomplete query for '${termResult.term}'") val request = ReadQueryResultRequest(projectId, waitTime, authn, termResult.networkQueryId) val futureResponse = broadcastAndAggregationService.sendAndAggregate(toAuthn(authResult), request, new ReadQueryResultAggregator(termResult.networkQueryId, false), false) def toTermResult(readQueryResultResponse: AggregatedReadQueryResultResponse): TermResult = { val termResultOption = for { shrineQueryResult <- readQueryResultResponse.results.headOption } yield { def elapsed = for { start <- shrineQueryResult.startDate.map(_.toGregorianCalendar.getTimeInMillis) end <- shrineQueryResult.endDate.map(_.toGregorianCalendar.getTimeInMillis) } yield end - start debug(s"CRC Query result: ${ shrineQueryResult.statusType }: (${ elapsed } ms) '${ shrineQueryResult.description.getOrElse("No Description") }', '${ shrineQueryResult.statusMessage.getOrElse("No status message") }'") termResult.copy(status = shrineQueryResult.statusType, count = shrineQueryResult.setSize) } //TODO: Is this the right query id to use here? termResultOption.getOrElse(errorTermResult(termResult.networkQueryId, termResult.term)) } futureResponse.collect { case resp: AggregatedReadQueryResultResponse => resp }.map(toTermResult) } } \ No newline at end of file diff --git a/tools/scanner/src/main/scala/net/shrine/utilities/scanner/Scanner.scala b/tools/scanner/src/main/scala/net/shrine/utilities/scanner/Scanner.scala index 715cfc189..5b55eb832 100644 --- a/tools/scanner/src/main/scala/net/shrine/utilities/scanner/Scanner.scala +++ b/tools/scanner/src/main/scala/net/shrine/utilities/scanner/Scanner.scala @@ -1,161 +1,162 @@ package net.shrine.utilities.scanner import net.shrine.log.Loggable import scala.concurrent.Await import scala.concurrent.Future import scala.concurrent.duration.Duration import scala.concurrent.duration.DurationInt import scala.util.control.NonFatal import net.shrine.config.mappings.AdapterMappingsSource import net.shrine.ont.data.OntologyDao import net.shrine.protocol.ResultOutputType /** * @author clint - * @date Mar 5, 2013 + * @since Mar 5, 2013 */ final class Scanner( val maxTimeToWaitForResult: Duration, val reScanTimeout: Duration, val adapterMappingsSource: AdapterMappingsSource, val ontologyDao: OntologyDao, val client: ScannerClient) extends Loggable { def scan(): ScanResults = doScan() protected def doScan(): ScanResults = { info("Shrine Scanner starting") val mappedNetworkTerms = adapterMappingsSource.load.get.networkTerms def allShrineOntologyTerms = ontologyDao.ontologyEntries.map(_.path).toSet val termsExpectedToBeUnmapped = allShrineOntologyTerms -- mappedNetworkTerms info(s"We expect ${mappedNetworkTerms.size} to be mapped, and ${termsExpectedToBeUnmapped.size} to be unmapped.") doScan(mappedNetworkTerms, termsExpectedToBeUnmapped) } import scala.concurrent.duration._ private def obtainVia[T](get: T => Future[TermResult]): T => ScanQueryResult = { input => try { //TODO: Evaluate, possibly don't block? val result = Await.result(get(input), maxTimeToWaitForResult) debug(s"Status ${result.status} received, queried for $input") result } catch { case NonFatal(e) => { warn(s"Error obtaining results for input $input: ", e) QueryFailure(input, e) } } } private def obtainResultsAndFailures[T](terms: Iterable[T], queryFor: T => ScanQueryResult): (Iterable[TermResult], Iterable[QueryFailure[T]]) = { val results = terms.map(queryFor) (results.collect { case t: TermResult => t }, results.collect { case f: QueryFailure[T] => f }) } private def doScan(mappedNetworkTerms: Set[String], termsExpectedToBeUnmapped: Set[String]): ScanResults = { val queryFor = obtainVia(client.query) val (resultsForMappedTerms, failuresForMappedTerms) = obtainResultsAndFailures(mappedNetworkTerms, queryFor) val (resultsForUnMappedTerms, failuresForUnMappedTerms) = obtainResultsAndFailures(termsExpectedToBeUnmapped, queryFor) val (finishedAndShouldHaveBeenMapped, didntFinishAndShouldHaveBeenMapped) = resultsForMappedTerms.partition(_.status.isDone) val (finishedAndShouldNotHaveBeenMapped, didntFinishAndShouldNotHaveBeenMapped) = resultsForUnMappedTerms.partition(_.status.isDone) //Terms that we expected to BE mapped, but were NOT mapped val shouldHaveBeenMapped = finishedAndShouldHaveBeenMapped.filter(_.status.isError) //Terms that we expected to NOT be mapped, but ARE mapped val shouldNotHaveBeenMapped = finishedAndShouldNotHaveBeenMapped.filterNot(_.status.isError) val reScanResults = reScan(didntFinishAndShouldHaveBeenMapped, didntFinishAndShouldNotHaveBeenMapped) val finalSouldHaveBeenMappedSet = toTermSet(shouldHaveBeenMapped) ++ reScanResults.shouldHaveBeenMapped val finalSouldNotHaveBeenMappedSet = toTermSet(shouldNotHaveBeenMapped) ++ reScanResults.shouldNotHaveBeenMapped val failed = toTermSet(failuresForMappedTerms ++ failuresForUnMappedTerms) ++ reScanResults.failed //Split query results into those that completed on the first try, and those that didn't ScanResults(finalSouldHaveBeenMappedSet, finalSouldNotHaveBeenMappedSet, reScanResults.neverFinished, failed) } private def reScan(neverFinishedShouldHaveBeenMapped: Iterable[TermResult], neverFinishedShouldNotHaveBeenMapped: Iterable[TermResult]): ScanResults = { if (neverFinishedShouldHaveBeenMapped.isEmpty && neverFinishedShouldNotHaveBeenMapped.isEmpty) { ScanResults.empty } else { val total = neverFinishedShouldHaveBeenMapped.size + neverFinishedShouldNotHaveBeenMapped.size info(s"Sleeping for ${reScanTimeout} before retreiving results for $total incomplete queries...") Thread.sleep(reScanTimeout.toMillis) val retrieve = obtainVia(client.retrieveResults) val (neverFinishedShouldHaveBeenMappedRetries, failedShouldHaveBeenMappedRetries) = obtainResultsAndFailures(neverFinishedShouldHaveBeenMapped, retrieve) val (neverFinishedShouldNotHaveBeenMappedRetries, failedShouldNotHaveBeenMappedRetries) = obtainResultsAndFailures(neverFinishedShouldNotHaveBeenMapped, retrieve) val (doneShouldHaveBeenMapped, stillNotFinishedShouldHaveBeenMapped) = neverFinishedShouldHaveBeenMappedRetries.partition(_.status.isDone) val (doneShouldNotHaveBeenMapped, stillNotFinishedShouldNotHaveBeenMapped) = neverFinishedShouldNotHaveBeenMappedRetries.partition(_.status.isDone) val shouldHaveBeenMapped = doneShouldHaveBeenMapped.filter(_.status.isError) val shouldNotHaveBeenMapped = doneShouldNotHaveBeenMapped.filterNot(_.status.isError) val stillNotFinished = stillNotFinishedShouldHaveBeenMapped ++ stillNotFinishedShouldNotHaveBeenMapped val failed = failedShouldHaveBeenMappedRetries ++ failedShouldNotHaveBeenMappedRetries ScanResults(toTermSet(shouldHaveBeenMapped), toTermSet(shouldNotHaveBeenMapped), toTermSet(stillNotFinished), toTermSet(failed)) } } private def toTermSet(results: Iterable[TermResult]): Set[String] = results.map(_.term).toSet import Scanner.Termable private def toTermSet[T : Termable](results: Iterable[QueryFailure[T]]): Set[String] = results.map(implicitly[Termable[T]].getTerm).toSet } object Scanner { private[scanner] trait Termable[T] { def getTerm(t: T): String def getTerm(t: QueryFailure[T]): String } private[scanner] object Termable { implicit val stringIsTermable: Termable[String] = new Termable[String] { override def getTerm(t: String) = t override def getTerm(f: QueryFailure[String]) = f.input } implicit val termResultIsTermable: Termable[TermResult] = new Termable[TermResult] { override def getTerm(t: TermResult) = t.term override def getTerm(f: QueryFailure[TermResult]) = f.input.term } } final object QueryDefaults { val topicId = "Scanner Util - Unknown Topic ID" //??? + val topicName = "Scanner Util - Unknown Topic Name" val outputTypes = Set(ResultOutputType.PATIENT_COUNT_XML) } } \ No newline at end of file