diff --git a/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala
index 176d273cc..af0db79d5 100644
--- a/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala
+++ b/apps/dashboard-app/src/main/scala/net/shrine/dashboard/DashboardService.scala
@@ -1,479 +1,480 @@
package net.shrine.dashboard
import akka.actor.Actor
import akka.event.Logging
import net.shrine.authentication.UserAuthenticator
import net.shrine.authorization.steward.OutboundUser
import net.shrine.dashboard.jwtauth.ShrineJwtAuthenticator
import net.shrine.i2b2.protocol.pm.User
import net.shrine.status.protocol.{Config => StatusProtocolConfig}
import net.shrine.dashboard.httpclient.HttpClientDirectives.{forwardUnmatchedPath, requestUriThenRoute}
import net.shrine.log.Loggable
import net.shrine.problem.{ProblemDigest, Problems}
import net.shrine.serialization.NodeSeqSerializer
import shapeless.HNil
import spray.http.{HttpRequest, HttpResponse, StatusCodes, Uri}
import spray.httpx.Json4sSupport
import spray.routing.directives.LogEntry
import spray.routing._
import org.json4s.{DefaultFormats, Formats}
import org.json4s.native.JsonMethods.{parse => json4sParse}
import scala.collection.immutable.Iterable
import scala.concurrent.duration.{Duration, FiniteDuration, SECONDS}
import scala.concurrent.ExecutionContext.Implicits.global
/**
* Mixes the DashboardService trait with an Akka Actor to provide the actual service.
*/
class DashboardServiceActor extends Actor with DashboardService {
// the HttpService trait defines only one abstract member, which
// connects the services environment to the enclosing actor or test
def actorRefFactory = context
// this actor only runs our route, but you could add
// other things here, like request stream processing
// or timeout handling
def receive = runRoute(route)
}
/**
* A web service that provides the Dashboard endpoints. It is a trait to support testing independent of Akka.
*/
trait DashboardService extends HttpService with Json4sSupport with Loggable {
implicit def json4sFormats: Formats = DefaultFormats
val userAuthenticator = UserAuthenticator(DashboardConfigSource.config)
//don't need to do anything special for unauthorized users, but they do need access to a static form.
lazy val route:Route = gruntWatchCorsSupport{
redirectToIndex ~ staticResources ~ makeTrouble ~ about ~ authenticatedInBrowser ~ authenticatedDashboard
}
/** logs the request method, uri and response at info level */
def logEntryForRequestResponse(req: HttpRequest): Any => Option[LogEntry] = {
case res: HttpResponse => Some(LogEntry(s"\n Request: $req\n Response: $res", Logging.InfoLevel))
case _ => None // other kind of responses
}
/** logs just the request method, uri and response status at info level */
def logEntryForRequest(req: HttpRequest): Any => Option[LogEntry] = {
case res: HttpResponse => Some(LogEntry(s"\n Request: $req\n Response status: ${res.status}", Logging.InfoLevel))
case _ => None // other kind of responses
}
def authenticatedInBrowser: Route = pathPrefixTest("user"|"admin"|"toDashboard") {
logRequestResponse(logEntryForRequestResponse _) { //logging is controlled by Akka's config, slf4j, and log4j config
reportIfFailedToAuthenticate {
authenticate(userAuthenticator.basicUserAuthenticator) { user =>
pathPrefix("user") {
userRoute(user)
} ~
- pathPrefix("admin") {
- adminRoute(user)
- } ~
- pathPrefix("toDashboard") {
- toDashboardRoute(user)
- }
+ pathPrefix("admin") {
+ adminRoute(user)
+ } ~
+ pathPrefix("toDashboard") {
+ toDashboardRoute(user)
+ }
}
}
}
}
val reportIfFailedToAuthenticate = routeRouteResponse {
case Rejected(List(AuthenticationFailedRejection(_,_))) =>
complete("AuthenticationFailed")
}
def authenticatedDashboard:Route = pathPrefix("fromDashboard") {
logRequestResponse(logEntryForRequestResponse _) { //logging is controlled by Akka's config, slf4j, and log4j config
get { //all remote dashboard calls are gets.
authenticate(ShrineJwtAuthenticator.authenticate) { user =>
adminRoute(user)
}
}
}
}
def makeTrouble = pathPrefix("makeTrouble") {
complete(throw new IllegalStateException("fake trouble"))
}
lazy val redirectToIndex = pathEnd {
redirect("shrine-dashboard/client/index.html", StatusCodes.PermanentRedirect) //todo pick up "shrine-dashboard" programatically
- } ~
+ } ~
( path("index.html") | pathSingleSlash) {
redirect("client/index.html", StatusCodes.PermanentRedirect)
- }
+ }
lazy val staticResources = pathPrefix("client") {
pathEnd {
redirect("client/index.html", StatusCodes.PermanentRedirect)
} ~
pathSingleSlash {
redirect("index.html", StatusCodes.PermanentRedirect)
} ~ {
getFromResourceDirectory("client")
}
}
lazy val about = pathPrefix("about") {
complete("Nothing here yet") //todo
}
def userRoute(user:User):Route = get {
pathPrefix("whoami") {
complete(OutboundUser.createFromUser(user))
}
}
//todo check that this an admin.
def adminRoute(user:User):Route = get {
pathPrefix("happy") {
val happyBaseUrl: String = DashboardConfigSource.config.getString("shrine.dashboard.happyBaseUrl")
forwardUnmatchedPath(happyBaseUrl)
} ~
- pathPrefix("messWithHappyVersion") { //todo is this used?
+ pathPrefix("messWithHappyVersion") { //todo is this used?
val happyBaseUrl: String = DashboardConfigSource.config.getString("shrine.dashboard.happyBaseUrl")
- def pullClasspathFromConfig(httpResponse:HttpResponse,uri:Uri):Route = {
- ctx => {
- val result = httpResponse.entity.asString
- ctx.complete(s"Got '$result' from $uri")
+ def pullClasspathFromConfig(httpResponse:HttpResponse,uri:Uri):Route = {
+ ctx => {
+ val result = httpResponse.entity.asString
+ ctx.complete(s"Got '$result' from $uri")
+ }
}
- }
- requestUriThenRoute(happyBaseUrl+"/version",pullClasspathFromConfig)
- } ~
- pathPrefix("ping") {complete("pong")}~
- pathPrefix("status"){statusRoute(user)}
+ requestUriThenRoute(happyBaseUrl+"/version",pullClasspathFromConfig)
+ } ~
+ pathPrefix("ping") {complete("pong")}~
+ pathPrefix("status"){statusRoute(user)}
}
//Manually test this by running a curl command
//curl -k -w "\n%{response_code}\n" -u dave:kablam "https://shrine-dev1.catalyst:6443/shrine-dashboard/toDashboard/shrine-dev2.catalyst/shrine-dashboard/fromDashboard/ping"
/**
* Forward a request from this dashboard to a remote dashboard
*/
def toDashboardRoute(user:User):Route = get {
pathPrefix(Segment) { dnsName =>
val remoteDashboardProtocol = DashboardConfigSource.config.getString("shrine.dashboard.remoteDashboard.protocol")
val remoteDashboardPort = DashboardConfigSource.config.getString("shrine.dashboard.remoteDashboard.port")
val remoteDashboardPathPrefix = DashboardConfigSource.config.getString("shrine.dashboard.remoteDashboard.pathPrefix")
val baseUrl = s"$remoteDashboardProtocol$dnsName$remoteDashboardPort/$remoteDashboardPathPrefix"
forwardUnmatchedPath(baseUrl,Some(ShrineJwtAuthenticator.createOAuthCredentials(user)))
}
}
def statusRoute(user:User):Route = get {
pathPrefix("config"){getConfig}~
- pathPrefix("classpath"){getClasspath}~
- pathPrefix("options"){getOptionalParts}~ //todo rename path to optionalParts
- pathPrefix("summary"){getSummary}~
- pathPrefix("problems"){getProblems}
+ pathPrefix("classpath"){getClasspath}~
+ pathPrefix("options"){getOptionalParts}~ //todo rename path to optionalParts
+ pathPrefix("summary"){getSummary}~
+ pathPrefix("problems"){getProblems}
}
val statusBaseUrl = DashboardConfigSource.config.getString("shrine.dashboard.statusBaseUrl")
lazy val getConfig:Route = {
def completeConfigRoute(httpResponse:HttpResponse,uri:Uri):Route = {
ctx => {
val config = ParsedConfig(httpResponse.entity.asString)
ctx.complete(
ShrineConfig(config)
)
}
}
requestUriThenRoute(statusBaseUrl + "/config", completeConfigRoute)
}
lazy val getClasspath:Route = {
def pullClasspathFromConfig(httpResponse:HttpResponse,uri:Uri):Route = {
ctx => {
val result = httpResponse.entity.asString
val shrineConfig = ShrineConfig(ParsedConfig(result))
ctx.complete(shrineConfig)
}
}
requestUriThenRoute(statusBaseUrl + "/config",pullClasspathFromConfig)
}
lazy val getOptionalParts:Route = {
requestUriThenRoute(statusBaseUrl + "/optionalParts")
}
lazy val getSummary:Route = {
requestUriThenRoute(statusBaseUrl + "/summary")
}
// table based view, can see N problems at a time. Front end sends how many problems that they want
// to skip, and it will take N the 'nearest N' ie with n = 20, 0-19 -> 20, 20-39 -> 20-40
lazy val getProblems:Route = {
def floorMod(x: Int, y: Int) = {
x - (x % y)
}
val db = Problems.DatabaseConnector
- parameters("offset" ? 0, "n" ? 20, "epoch" ? -1l) { (offsetPreMod: Int, nPreMax: Int, epoch: Long) =>
- val n = Math.max(0, nPreMax)
- val moddedOffset = floorMod(Math.max(0, offsetPreMod), n)
-
- // Constructing the query with comprehension means we only use one database connection.
- // TODO: review with Dave to see if it's too clever
- val query =
- if (epoch == -1l)
- for {
- result <- db.IO.sizeAndProblemDigest(n, moddedOffset)
- } yield (result._2, floorMod(Math.max(0, moddedOffset), n), n, result._1)
- else
- for {
- dateOffset <- db.IO.findIndexOfDate(epoch)
- moddedOffset = floorMod(dateOffset, n)
- result <- db.IO.sizeAndProblemDigest(n, moddedOffset)
- } yield (result._2, moddedOffset, n, result._1)
-
- val response: ProblemResponse = ProblemResponse.tupled(db.runBlocking(query))
- implicit val formats = response.json4sMarshaller
- complete(response)
+ parameters("offset" ? 0, "n" ? 20, "epoch" ? -1l) {
+ (offsetPreMod: Int, nPreMax: Int, epoch: Long) => {
+ val n = Math.max(0, nPreMax)
+ val moddedOffset = floorMod(Math.max(0, offsetPreMod), n)
+
+ // Constructing the query with comprehension means we only use one database connection.
+ // TODO: review with Dave to see if it's too clever
+ val query = for {
+ result <- db.IO.sizeAndProblemDigest(n, moddedOffset)
+ } yield (result._2, floorMod(Math.max(0, moddedOffset), n), n, result._1)
+
+
+ val query2 = for {
+ dateOffset <- db.IO.findIndexOfDate(epoch)
+ moddedOffset = floorMod(dateOffset, n)
+ result <- db.IO.sizeAndProblemDigest(n, moddedOffset)
+ } yield (result._2, moddedOffset, n, result._1)
+
+ val response: ProblemResponse = ProblemResponse.tupled(db.runBlocking(if (epoch == -1l) query else query2))
+ implicit val formats = response.json4sMarshaller
+ complete(response)
+ }
}
}
}
case class ProblemResponse(size: Int, offset: Int, n: Int, problems: Seq[ProblemDigest]) extends Json4sSupport {
override implicit def json4sFormats: Formats = DefaultFormats + new NodeSeqSerializer
}
/**
- * Centralized parsing logic for map of shrine.conf
- * the class literal `T.class` in Java.
- */
+ * Centralized parsing logic for map of shrine.conf
+ * the class literal `T.class` in Java.
+ */
//todo most of this info should come directly from the status service in Shrine, not from reading the config
case class ParsedConfig(configMap:Map[String, String]){
private val trueVal = "true"
private val rootKey = "shrine"
def isHub =
getOrElse(rootKey + ".hub.create", "")
.toLowerCase == trueVal
def stewardEnabled =
configMap.keySet
.contains(rootKey + ".queryEntryPoint.shrineSteward")
def shouldQuerySelf =
getOrElse(rootKey + ".hub.shouldQuerySelf", "")
.toLowerCase == trueVal
def fromJsonString(jsonString:String): String = jsonString.split("\"").mkString("")
def get(key:String): Option[String] = configMap.get(key).map(fromJsonString)
def getOrElse(key:String, elseVal:String = ""): String = get(key).getOrElse(elseVal)
}
object ParsedConfig {
def apply(jsonString:String):ParsedConfig = {
implicit def json4sFormats: Formats = DefaultFormats
ParsedConfig(json4sParse(jsonString).extract[StatusProtocolConfig].keyValues.filterKeys(_.toLowerCase.startsWith("shrine")))
}
}
case class DownstreamNode(name:String, url:String)
object DownstreamNode {
def create(configMap:Map[String,String]):Iterable[DownstreamNode] = {
for ((k, v) <- configMap.filterKeys(_.toLowerCase.startsWith
("shrine.hub.downstreamnodes")))
yield DownstreamNode(k.split('.').last,v.split("\"").mkString(""))
}
}
//todo replace with the actual config, scrubbed of passwords
case class ShrineConfig(isHub:Boolean,
hub:Hub,
pmEndpoint:Endpoint,
ontEndpoint:Endpoint,
hiveCredentials: HiveCredentials,
adapter: Adapter,
queryEntryPoint:QEP,
networkStatusQuery:String
)
object ShrineConfig{
def apply(config:ParsedConfig):ShrineConfig = {
val hub = Hub(config)
val isHub = config.isHub
val pmEndpoint = Endpoint("pm",config)
val ontEndpoint = Endpoint("ont",config)
val hiveCredentials = HiveCredentials(config)
val adapter = Adapter(config)
val queryEntryPoint = QEP(config)
val networkStatusQuery = config.configMap("shrine.networkStatusQuery")
ShrineConfig(isHub, hub, pmEndpoint, ontEndpoint, hiveCredentials, adapter, queryEntryPoint, networkStatusQuery)
}
}
case class Endpoint(acceptAllCerts:Boolean, url:String, timeoutSeconds:Int)
object Endpoint{
def apply(endpointType:String,parsedConfig:ParsedConfig):Endpoint = {
val prefix = "shrine." + endpointType.toLowerCase + "Endpoint."
val acceptAllCerts = parsedConfig.configMap.getOrElse(prefix + "acceptAllCerts", "") == "true"
val url = parsedConfig.configMap.getOrElse(prefix + "url","")
val timeoutSeconds = parsedConfig.configMap.getOrElse(prefix + "timeout.seconds", "0").toInt
Endpoint(acceptAllCerts, url, timeoutSeconds)
}
}
case class HiveCredentials(domain:String,
username:String,
password:String,
crcProjectId:String,
ontProjectId:String)
object HiveCredentials{
def apply(parsedConfig:ParsedConfig):HiveCredentials = {
val key = "shrine.hiveCredentials."
val domain = parsedConfig.configMap.getOrElse(key + "domain","")
val username = parsedConfig.configMap.getOrElse(key + "username","")
val password = "REDACTED"
val crcProjectId = parsedConfig.configMap.getOrElse(key + "crcProjectId","")
val ontProjectId = parsedConfig.configMap.getOrElse(key + "ontProjectId","")
HiveCredentials(domain, username, password, crcProjectId, ontProjectId)
}
}
// -- hub only -- //
//todo delete when the Dashboard front end can use the status service's hub method
case class Hub(shouldQuerySelf:Boolean,
create:Boolean,
downstreamNodes:Iterable[DownstreamNode])
object Hub{
def apply(parsedConfig:ParsedConfig):Hub = {
val shouldQuerySelf = parsedConfig.shouldQuerySelf
val create = parsedConfig.isHub
val downstreamNodes = DownstreamNode.create(parsedConfig.configMap)
Hub(shouldQuerySelf, create, downstreamNodes)
}
}
// -- adapter info -- //
case class Adapter(crcEndpointUrl:String, setSizeObfuscation:Boolean, adapterLockoutAttemptsThreshold:Int,
adapterMappingsFilename:String)
object Adapter{
def apply(parsedConfig:ParsedConfig):Adapter = {
val key = "shrine.adapter."
val crcEndpointUrl = parsedConfig.configMap.getOrElse(key + "crcEndpoint.url","")
val setSizeObfuscation = parsedConfig.configMap.getOrElse(key + "setSizeObfuscation","").toLowerCase == "true"
val adapterLockoutAttemptsThreshold = parsedConfig.configMap.getOrElse(key + "adapterLockoutAttemptsThreshold", "0").toInt
val adapterMappingsFileName = parsedConfig.configMap.getOrElse(key + "adapterMappingsFileName","")
Adapter(crcEndpointUrl, setSizeObfuscation, adapterLockoutAttemptsThreshold, adapterMappingsFileName)
}
}
case class Steward(qepUserName:String, stewardBaseUrl:String)
object Steward {
def apply (parsedConfig:ParsedConfig):Steward = {
val key = "shrine.queryEntryPoint.shrineSteward."
val qepUserName = parsedConfig.configMap.getOrElse(key + "qepUserName","")
val stewardBaseUrl = parsedConfig.configMap.getOrElse(key + "stewardBaseUrl","")
Steward(qepUserName, stewardBaseUrl)
}
}
// -- if needed -- //
case class TimeoutInfo (timeUnit:String, description:String)
case class DatabaseInfo(createTablesOnStart:Boolean, dataSourceFrom:String,
jndiDataSourceName:String, slickProfileClassName:String)
case class Audit(database:DatabaseInfo, collectQepAudit:Boolean)
object Audit{
def apply(parsedConfig:ParsedConfig):Audit = {
val key = "shrine.queryEntryPoint.audit."
val createTablesOnStart = parsedConfig.configMap.getOrElse(key + "database.createTablesOnStart","") == "true"
val dataSourceFrom = parsedConfig.configMap.getOrElse(key + "database.dataSourceFrom","")
val jndiDataSourceName = parsedConfig.configMap.getOrElse(key + "database.jndiDataSourceName","")
val slickProfileClassName = parsedConfig.configMap.getOrElse(key + "database.slickProfileClassName","")
val collectQepAudit = parsedConfig.configMap.getOrElse(key + "collectQepAudit","") == "true"
val database = DatabaseInfo(createTablesOnStart, dataSourceFrom, jndiDataSourceName, slickProfileClassName)
Audit(database, collectQepAudit)
}
}
case class QEP(
- maxQueryWaitTimeMinutes:Int,
- create:Boolean,
- attachSigningCert:Boolean,
- authorizationType:String,
- includeAggregateResults:Boolean,
- authenticationType:String,
- audit:Audit,
- shrineSteward:Steward,
- broadcasterServiceEndpointUrl:Option[String]
-)
+ maxQueryWaitTimeMinutes:Int,
+ create:Boolean,
+ attachSigningCert:Boolean,
+ authorizationType:String,
+ includeAggregateResults:Boolean,
+ authenticationType:String,
+ audit:Audit,
+ shrineSteward:Steward,
+ broadcasterServiceEndpointUrl:Option[String]
+ )
object QEP{
val key = "shrine.queryEntryPoint."
def apply(parsedConfig:ParsedConfig):QEP = QEP(
maxQueryWaitTimeMinutes = parsedConfig.configMap.getOrElse(key + "maxQueryWaitTime.minutes", "0").toInt,
create = parsedConfig.configMap.getOrElse(key + "create","") == "true",
attachSigningCert = parsedConfig.configMap.getOrElse(key + "attachSigningCert","") == "true",
authorizationType = parsedConfig.configMap.getOrElse(key + "authorizationType",""),
includeAggregateResults = parsedConfig.configMap.getOrElse(key + "includeAggregateResults","") == "true",
authenticationType = parsedConfig.configMap.getOrElse(key + "authenticationType", ""),
audit = Audit(parsedConfig),
shrineSteward = Steward(parsedConfig),
broadcasterServiceEndpointUrl = parsedConfig.configMap.get(key + "broadcasterServiceEndpoint.url")
)
}
//adapted from https://gist.github.com/joseraya/176821d856b43b1cfe19
object gruntWatchCorsSupport extends Directive0 with RouteConcatenation {
import spray.http.HttpHeaders.{`Access-Control-Allow-Methods`, `Access-Control-Max-Age`, `Access-Control-Allow-Headers`,`Access-Control-Allow-Origin`}
import spray.routing.directives.RespondWithDirectives.respondWithHeaders
import spray.routing.directives.MethodDirectives.options
import spray.routing.directives.RouteDirectives.complete
import spray.http.HttpMethods.{OPTIONS,GET,POST}
import spray.http.AllOrigins
private val allowOriginHeader = `Access-Control-Allow-Origin`(AllOrigins)
private val optionsCorsHeaders = List(
`Access-Control-Allow-Headers`("Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding, Accept-Language, Host, Referer, User-Agent, Authorization"),
`Access-Control-Max-Age`(1728000)) //20 days
val gruntWatch:Boolean = DashboardConfigSource.config.getBoolean("shrine.dashboard.gruntWatch")
override def happly(f: (HNil) => Route): Route = {
if(gruntWatch) {
options {
respondWithHeaders(`Access-Control-Allow-Methods`(OPTIONS, GET, POST) :: allowOriginHeader :: optionsCorsHeaders){
complete(StatusCodes.OK)
}
} ~ f(HNil)
}
else f(HNil)
}
}
diff --git a/apps/steward-app/src/main/scala/net/shrine/steward/StewardService.scala b/apps/steward-app/src/main/scala/net/shrine/steward/StewardService.scala
index 98bf63e4a..89cc0b751 100644
--- a/apps/steward-app/src/main/scala/net/shrine/steward/StewardService.scala
+++ b/apps/steward-app/src/main/scala/net/shrine/steward/StewardService.scala
@@ -1,366 +1,373 @@
package net.shrine.steward
import akka.actor.Actor
import akka.event.Logging
import net.shrine.authentication.UserAuthenticator
-
-import net.shrine.authorization.steward.{TopicIdAndName, Date, TopicId, InboundTopicRequest, InboundShrineQuery, StewardsTopics, TopicState, OutboundUser, OutboundTopic, UserName}
+import net.shrine.authorization.steward._
import net.shrine.i2b2.protocol.pm.User
-import net.shrine.steward.db.{DetectedAttemptByWrongUserToChangeTopic, ApprovedTopicCanNotBeChanged, TopicDoesNotExist, SortOrder, QueryParameters, StewardDatabase}
+import net.shrine.steward.db._
import net.shrine.steward.pmauth.Authorizer
import shapeless.HNil
-
-import spray.http.{HttpResponse, HttpRequest, StatusCodes}
+import spray.http.{HttpRequest, HttpResponse, StatusCodes}
import spray.httpx.Json4sSupport
import spray.routing.directives.LogEntry
-import spray.routing.{AuthenticationFailedRejection, Rejected, RouteConcatenation, Directive0, Route, HttpService}
-
+import spray.routing._
import org.json4s.{DefaultFormats, Formats}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success, Try}
// we don't implement our route structure directly in the service actor because
// we want to be able to test it independently, without having to spin up an actor
class StewardServiceActor extends Actor with StewardService {
// the HttpService trait defines only one abstract member, which
// connects the services environment to the enclosing actor or test
def actorRefFactory = context
// this actor only runs our route, but you could add
// other things here, like request stream processing
// or timeout handling
def receive = runRoute(route)
}
// this trait defines our service behavior independently from the service actor
trait StewardService extends HttpService with Json4sSupport {
implicit def json4sFormats: Formats = DefaultFormats
val userAuthenticator = UserAuthenticator(StewardConfigSource.config)
//don't need to do anything special for unauthorized users, but they do need access to a static form.
lazy val route:Route = gruntWatchCorsSupport{
requestLogRoute ~ fullLogRoute
}
lazy val requestLogRoute = logRequestResponse(logEntryForRequest _) {
redirectToIndex ~ staticResources ~ makeTrouble ~ about
}
lazy val fullLogRoute = logRequestResponse(logEntryForRequestResponse _) {
qepRoute ~ authenticatedInBrowser
}
// logs just the request method, uri and response at info level
//logging is controlled by Akka's config, slf4j, and log4j config
def logEntryForRequestResponse(req: HttpRequest): Any => Option[LogEntry] = {
case res: HttpResponse => {
Some(LogEntry(s"\n Request: $req \n Response: $res", Logging.InfoLevel))
}
case _ => None // other kind of responses
}
// logs just the request method, uri and response status at info level
def logEntryForRequest(req: HttpRequest): Any => Option[LogEntry] = {
case res: HttpResponse => {
Some(LogEntry(s"\n Request: $req \n Response status: ${res.status}", Logging.InfoLevel))
}
case _ => None // other kind of responses
}
//pathPrefixTest shields the QEP code from the redirect.
def authenticatedInBrowser: Route = pathPrefixTest("user"|"steward"|"researcher") {
reportIfFailedToAuthenticate {
authenticate(userAuthenticator.basicUserAuthenticator) { user =>
StewardDatabase.db.upsertUser(user)
pathPrefix("user") {userRoute(user)} ~
pathPrefix("steward") {stewardRoute(user)} ~
pathPrefix("researcher") {researcherRoute(user)}
}
}
}
val reportIfFailedToAuthenticate = routeRouteResponse {
case Rejected(List(AuthenticationFailedRejection(_,_))) =>
complete("AuthenticationFailed")
}
def makeTrouble = pathPrefix("makeTrouble") {
complete(throw new IllegalStateException("fake trouble"))
}
lazy val redirectToIndex = pathEnd {
redirect("steward/client/index.html", StatusCodes.PermanentRedirect) //todo pick up "steward" programatically
} ~
( path("index.html") | pathSingleSlash) {
redirect("client/index.html", StatusCodes.PermanentRedirect)
}
lazy val staticResources = pathPrefix("client") {
pathEnd {
redirect("client/index.html", StatusCodes.PermanentRedirect)
} ~
pathSingleSlash {
redirect("index.html", StatusCodes.PermanentRedirect)
} ~ {
getFromResourceDirectory("client")
}
}
lazy val about = pathPrefix("about") {
path("createTopicsMode") {
get {
complete(StewardConfigSource.createTopicsInState.name)
}
}
}
def userRoute(user:User):Route = get {
pathPrefix("whoami") {
complete(OutboundUser.createFromUser(user))
}
}
def qepRoute:Route = pathPrefix("qep") {
authenticate(userAuthenticator.basicUserAuthenticator) { user =>
StewardDatabase.db.upsertUser(user)
authorize(Authorizer.authorizeQep(user)) {
pathPrefix("requestQueryAccess") ( requestQueryAccess ) ~
pathPrefix("approvedTopics") ( getApprovedTopicsForUser )
}
}
}
def requestQueryAccess:Route = post {
requestQueryAccessWithTopic ~ requestQueryAccessWithoutTopic
}
def requestQueryAccessWithTopic:Route = path("user" /Segment/ "topic" / IntNumber) { (userId,topicId) =>
entity(as[InboundShrineQuery]) { shrineQuery:InboundShrineQuery =>
//todo really pull the user out of the shrine query and check vs the PM. If they aren't there, reject them for this new reason
val result: (TopicState, Option[TopicIdAndName]) = StewardDatabase.db.logAndCheckQuery(userId,Some(topicId),shrineQuery)
respondWithStatus(result._1.statusCode) {
if(result._1.statusCode == StatusCodes.OK) complete (result._2.getOrElse(""))
else complete(result._1.message)
}
}
}
def requestQueryAccessWithoutTopic:Route = path("user" /Segment) { userId =>
entity(as[InboundShrineQuery]) { shrineQuery:InboundShrineQuery =>
//todo really pull the user out of the shrine query and check vs the PM. If they aren't there, reject them for this new reason
val result = StewardDatabase.db.logAndCheckQuery(userId,None,shrineQuery)
respondWithStatus(result._1.statusCode) {
if(result._1.statusCode == StatusCodes.OK) complete (result._2)
else complete(result._1.message)
}
}
}
lazy val getApprovedTopicsForUser:Route = get {
//todo change to "researcher"
path("user" /Segment) { userId =>
//todo really pull the user out of the shrine query and check vs the PM. If they aren't there, reject them for this new reason
val queryParameters = QueryParameters(researcherIdOption = Some(userId),stateOption = Some(TopicState.approved))
val researchersTopics = StewardDatabase.db.selectTopicsForResearcher(queryParameters)
complete(researchersTopics)
}
}
def researcherRoute(user:User):Route = authorize(Authorizer.authorizeResearcher(user)) {
pathPrefix("topics") { getUserTopics(user.username) } ~
pathPrefix("queryHistory") { getUserQueryHistory(Some(user.username)) } ~
pathPrefix("requestTopicAccess") { requestTopicAccess(user) } ~
pathPrefix("editTopicRequest") { editTopicRequest(user) }
}
def getUserTopics(userId:UserName):Route = get {
//lookup topics for this user in the db
matchQueryParameters(Some(userId)){queryParameters:QueryParameters =>
val researchersTopics = StewardDatabase.db.selectTopicsForResearcher(queryParameters)
complete(researchersTopics)
}
}
def matchQueryParameters(userName: Option[UserName])(parameterRoute:QueryParameters => Route): Route = {
parameters('state.?,'skip.as[Int].?,'limit.as[Int].?,'sortBy.as[String].?,'sortDirection.as[String].?,'minDate.as[Date].?,'maxDate.as[Date].?) { (stateStringOption,skipOption,limitOption,sortByOption,sortOption,minDate,maxDate) =>
val stateTry = TopicState.stateForStringOption(stateStringOption)
stateTry match {
case Success(stateOption) =>
val qp = QueryParameters(userName,
stateOption,
skipOption,
limitOption,
sortByOption,
SortOrder.sortOrderForStringOption(sortOption),
minDate,
maxDate
)
parameterRoute(qp)
case Failure(ex) => badStateRoute(stateStringOption)
}
}
}
def badStateRoute(stateStringOption:Option[String]):Route = {
respondWithStatus(StatusCodes.UnprocessableEntity) {
complete(s"Topic state ${stateStringOption.getOrElse(s"$stateStringOption (stateStringOption should never be None at this point)")} unknown. Please specify one of ${TopicState.namesToStates.keySet}")
}
}
def getUserQueryHistory(userIdOption:Option[UserName]):Route = get {
- path("topic"/IntNumber) { topicId:TopicId =>
- getQueryHistoryForUserByTopic(userIdOption,Some(topicId))
- } ~
- getQueryHistoryForUserByTopic(userIdOption,None)
+ parameter('asJson.as[Boolean].?) { (asJson:Option[Boolean]) =>
+ path("topic" / IntNumber) { topicId: TopicId =>
+ getQueryHistoryForUserByTopic(userIdOption, Some(topicId), asJson)
+ } ~
+ getQueryHistoryForUserByTopic(userIdOption, None, asJson)
+ }
}
- def getQueryHistoryForUserByTopic(userIdOption:Option[UserName],topicIdOption:Option[TopicId]) = get {
+ def getQueryHistoryForUserByTopic(userIdOption:Option[UserName],topicIdOption:Option[TopicId],jsonOption:Option[Boolean] = None) = get {
matchQueryParameters(userIdOption) { queryParameters:QueryParameters =>
val queryHistory = StewardDatabase.db.selectQueryHistory(queryParameters, topicIdOption)
-
- complete(queryHistory)
+ if (jsonOption.getOrElse(false))
+ complete(queryHistoryWithJson(queryHistory))
+ else
+ complete(queryHistory)
}
}
+ def queryHistoryWithJson(history: QueryHistory):QueryHistory = {
+ history.copy(queryRecords = history.queryRecords.map((record: OutboundShrineQuery) => {
+ record.copy(queryContents = org.json4s.native.Serialization.write(scala.xml.XML.loadString(record.queryContents)))
+ }))
+ }
+
def requestTopicAccess(user:User):Route = post {
entity(as[InboundTopicRequest]) { topicRequest: InboundTopicRequest =>
//todo notify the data stewards
StewardDatabase.db.createRequestForTopicAccess(user,topicRequest)
complete(StatusCodes.Accepted)
}
}
def editTopicRequest(user:User):Route = post {
path(IntNumber) { topicId =>
entity(as[InboundTopicRequest]) { topicRequest: InboundTopicRequest =>
//todo notify the data stewards
val updatedTopicTry:Try[OutboundTopic] = StewardDatabase.db.updateRequestForTopicAccess(user, topicId, topicRequest)
updatedTopicTry match {
case Success(updatedTopic) =>
respondWithStatus(StatusCodes.Accepted) {
complete(updatedTopic)
}
case Failure(x) => x match {
case x:TopicDoesNotExist => respondWithStatus(StatusCodes.NotFound) {
complete(x.getMessage)
}
case x:ApprovedTopicCanNotBeChanged => respondWithStatus(StatusCodes.Forbidden) {
complete(x.getMessage)
}
case x:DetectedAttemptByWrongUserToChangeTopic => respondWithStatus(StatusCodes.Forbidden) {
complete(x.getMessage)
}
case _ => throw x
}
}
}
}
}
def stewardRoute(user:User):Route = authorize(Authorizer.authorizeSteward(user)) {
pathPrefix("queryHistory" / "user") {getUserQueryHistory } ~
pathPrefix("queryHistory") {getQueryHistory} ~
pathPrefix("topics" / "user")(getUserTopicsForSteward) ~
path("topics"){getTopicsForSteward} ~
pathPrefix("approveTopic")(approveTopicForUser(user)) ~
pathPrefix("rejectTopic")(rejectTopicForUser(user)) ~
pathPrefix("statistics"){getStatistics}
}
lazy val getUserQueryHistory:Route = pathPrefix(Segment) { userId =>
getUserQueryHistory(Some(userId))
}
lazy val getQueryHistory:Route = getUserQueryHistory(None)
lazy val getTopicsForSteward:Route = getTopicsForSteward(None)
lazy val getUserTopicsForSteward:Route = path(Segment) { userId =>
getTopicsForSteward(Some(userId))
}
def getTopicsForSteward(userIdOption:Option[UserName]):Route = get {
//lookup topics for this user in the db
matchQueryParameters(userIdOption) { queryParameters: QueryParameters =>
val stewardsTopics:StewardsTopics = StewardDatabase.db.selectTopicsForSteward(queryParameters)
complete(stewardsTopics)
}
}
def approveTopicForUser(user:User):Route = changeStateForTopic(TopicState.approved,user)
def rejectTopicForUser(user:User):Route = changeStateForTopic(TopicState.rejected,user)
def changeStateForTopic(state:TopicState,user:User):Route = post {
path("topic" / IntNumber) { topicId =>
StewardDatabase.db.changeTopicState(topicId, state, user.username).fold(respondWithStatus(StatusCodes.UnprocessableEntity){
complete(s"No topic found for $topicId")
})(topic => complete(StatusCodes.OK))
}
}
def getStatistics:Route = pathPrefix("queriesPerUser"){getQueriesPerUser} ~
pathPrefix("topicsPerState"){getTopicsPerState}
def getQueriesPerUser:Route = get{
matchQueryParameters(None) { queryParameters: QueryParameters =>
val result = StewardDatabase.db.selectShrineQueryCountsPerUser(queryParameters)
complete(result)
}
}
def getTopicsPerState:Route = get{
matchQueryParameters(None) { queryParameters: QueryParameters =>
val result = StewardDatabase.db.selectTopicCountsPerState(queryParameters)
complete(result)
}
}
}
//adapted from https://gist.github.com/joseraya/176821d856b43b1cfe19
object gruntWatchCorsSupport extends Directive0 with RouteConcatenation {
import spray.http.HttpHeaders.{`Access-Control-Allow-Methods`, `Access-Control-Max-Age`, `Access-Control-Allow-Headers`,`Access-Control-Allow-Origin`}
import spray.routing.directives.RespondWithDirectives.respondWithHeaders
import spray.routing.directives.MethodDirectives.options
import spray.routing.directives.RouteDirectives.complete
import spray.http.HttpMethods.{OPTIONS,GET,POST}
import spray.http.AllOrigins
private val allowOriginHeader = `Access-Control-Allow-Origin`(AllOrigins)
private val optionsCorsHeaders = List(
`Access-Control-Allow-Headers`("Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding, Accept-Language, Host, Referer, User-Agent, Authorization"),
`Access-Control-Max-Age`(1728000)) //20 days
val gruntWatch:Boolean = StewardConfigSource.config.getBoolean("shrine.steward.gruntWatch")
override def happly(f: (HNil) => Route): Route = {
if(gruntWatch) {
options {
respondWithHeaders(`Access-Control-Allow-Methods`(OPTIONS, GET, POST) :: allowOriginHeader :: optionsCorsHeaders){
complete(StatusCodes.OK)
}
} ~ f(HNil)
}
else f(HNil)
}
}
diff --git a/commons/util/src/main/scala/net/shrine/problem/Problem.scala b/commons/util/src/main/scala/net/shrine/problem/Problem.scala
index 7f4672b6a..0a6928570 100644
--- a/commons/util/src/main/scala/net/shrine/problem/Problem.scala
+++ b/commons/util/src/main/scala/net/shrine/problem/Problem.scala
@@ -1,225 +1,216 @@
package net.shrine.problem
import java.net.InetAddress
import java.util.Date
import java.util.concurrent.Executors
import net.shrine.log.Loggable
import net.shrine.serialization.{XmlMarshaller, XmlUnmarshaller}
+import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.xml.{Elem, Node, NodeSeq}
/**
* Describes what information we have about a problem at the site in code where we discover it.
*
* @author david
* @since 8/6/15
*/
trait Problem {
def summary:String
def problemName = getClass.getName
def throwable:Option[Throwable] = None
def stamp:Stamp
def description:String
def exceptionXml(exception:Option[Throwable]): Option[Elem] = {
exception.map{x =>
{x.getClass.getName}
{x.getMessage}
{x.getStackTrace.map(line => {line})}{exceptionXml(Option(x.getCause)).getOrElse("")}
}}
def throwableDetail: Option[Elem] = exceptionXml(throwable)
def detailsXml: NodeSeq = NodeSeq.fromSeq({throwableDetail.getOrElse("")} )
def toDigest:ProblemDigest = ProblemDigest(problemName,stamp.pretty,summary,description,detailsXml,stamp.time)
/**
* Temporary replacement for onCreate, which will be released come Scala 2.13
* TODO: remove when Scala 2.13 releases
*/
def hackToHandleAfterInitialization(handler:ProblemHandler):Future[Unit] = {
- import ProblemExecutionContext.ioThreadPool
+ import scala.concurrent.blocking
Future {
var continue = true
while (continue) {
try {
- synchronized(handler.handleProblem(this))
+ blocking(synchronized(handler.handleProblem(this)))
continue = false
} catch {
case un:UninitializedFieldError =>
Thread.sleep(5)
continue = true
}
}
Unit
}
}
}
case class ProblemDigest(codec: String, stampText: String, summary: String, description: String, detailsXml: NodeSeq, epoch: Long) extends XmlMarshaller {
override def toXml: Node = {
{codec}
{stampText}
{summary}
{description}
{epoch}
{detailsXml}
}
/**
* Ignores detailXml. equals with scala.xml is impossible. See http://www.scala-lang.org/api/2.10.3/index.html#scala.xml.Equality$
*/
override def equals(other: Any): Boolean =
other match {
case that: ProblemDigest =>
(that canEqual this) &&
codec == that.codec &&
stampText == that.stampText &&
summary == that.summary &&
description == that.description &&
epoch == that.epoch
case _ => false
}
/**
* Ignores detailXml
*/
override def hashCode: Int = {
val prime = 67
codec.hashCode + prime * (stampText.hashCode + prime *(summary.hashCode + prime * (description.hashCode + prime * epoch.hashCode())))
}
}
object ProblemDigest extends XmlUnmarshaller[ProblemDigest] with Loggable {
override def fromXml(xml: NodeSeq): ProblemDigest = {
val problemNode = xml \ "problem"
require(problemNode.nonEmpty,s"No problem tag in $xml")
def extractText(tagName:String) = (problemNode \ tagName).text
val codec = extractText("codec")
val stampText = extractText("stamp")
val summary = extractText("summary")
val description = extractText("description")
val detailsXml: NodeSeq = problemNode \ "details"
val epoch =
try { extractText("epoch").toLong }
catch { case nx:NumberFormatException =>
error(s"While parsing xml representing a ProblemDigest, the epoch could not be parsed into a long", nx)
0
}
ProblemDigest(codec,stampText,summary,description,detailsXml,epoch)
}
}
case class Stamp(host:InetAddress,time:Long,source:ProblemSources.ProblemSource) {
def pretty = s"${new Date(time)} on ${host.getHostName} ${source.pretty}"
}
object Stamp {
//TODO: val dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")?
//TODO: Currently the stamp text is locale specific, which can change depending on the jre/computer running it...
def apply(source:ProblemSources.ProblemSource, timer: => Long): Stamp = Stamp(InetAddress.getLocalHost, timer, source)
}
abstract class AbstractProblem(source:ProblemSources.ProblemSource) extends Problem {
def timer = System.currentTimeMillis
override val stamp = Stamp(source, timer)
private val config = ProblemConfigSource.config.getConfig("shrine.problem")
hackToHandleAfterInitialization(ProblemConfigSource.getObject("problemHandler", config))
}
trait ProblemHandler {
def handleProblem(problem:Problem)
}
/**
* An example problem handler
*/
object LoggingProblemHandler extends ProblemHandler with Loggable {
override def handleProblem(problem: Problem): Unit = {
problem.throwable.fold(error(problem.toString))(throwable =>
error(problem.toString,throwable)
)
}
}
object DatabaseProblemHandler extends ProblemHandler {
override def handleProblem(problem: Problem): Unit = {
- Thread.sleep(10)
Problems.DatabaseConnector.insertProblem(problem.toDigest)
}
}
object ProblemSources{
sealed trait ProblemSource {
def pretty = getClass.getSimpleName.dropRight(1)
}
case object Adapter extends ProblemSource
case object Hub extends ProblemSource
case object Qep extends ProblemSource
case object Dsa extends ProblemSource
case object Unknown extends ProblemSource
def problemSources = Set(Adapter,Hub,Qep,Dsa,Unknown)
}
case class ProblemNotYetEncoded(internalSummary:String,t:Option[Throwable] = None) extends AbstractProblem(ProblemSources.Unknown){
override val summary = "An unanticipated problem encountered."
override val throwable = {
val rx = t.fold(new IllegalStateException(s"$summary"))(
new IllegalStateException(s"$summary",_)
)
rx.fillInStackTrace()
Some(rx)
}
val reportedAtStackTrace = new IllegalStateException("Capture reporting stack trace.")
override val description = "This problem is not yet classified in Shrine source code. Please report the details to the Shrine dev team."
override val detailsXml: NodeSeq = NodeSeq.fromSeq(
{internalSummary}
{throwableDetail.getOrElse("")}
)
}
object ProblemNotYetEncoded {
def apply(summary:String,x:Throwable):ProblemNotYetEncoded = ProblemNotYetEncoded(summary,Some(x))
-}
-
-object ProblemExecutionContext {
-
- private val processes = Runtime.getRuntime.availableProcessors()
- private val factor = 3
- private val threads = processes * factor
- implicit val ioThreadPool: ExecutionContext = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(threads))
-
}
\ No newline at end of file
diff --git a/integration/src/test/scala/net/shrine/integration/ProblemCreation.scala b/integration/src/test/scala/net/shrine/integration/ProblemCreation.scala
index 6886f41af..cca8028a8 100644
--- a/integration/src/test/scala/net/shrine/integration/ProblemCreation.scala
+++ b/integration/src/test/scala/net/shrine/integration/ProblemCreation.scala
@@ -1,140 +1,140 @@
package net.shrine.integration
import java.net.{URL, URLConnection, URLStreamHandler, URLStreamHandlerFactory}
import java.sql.SQLException
import net.shrine.adapter.AbstractQueryRetrievalTestCase.BogusRequest
import net.shrine.adapter._
import net.shrine.adapter.client.{CouldNotParseXmlFromAdapter, HttpErrorCodeFromAdapter}
import net.shrine.adapter.components.QueryNotInDatabase
import net.shrine.adapter.dao.BotDetectedException
import net.shrine.adapter.service.{CouldNotVerifySignature, UnknownRequestType}
import net.shrine.aggregation._
import net.shrine.authentication.{NotAuthenticatedException, NotAuthenticatedProblem}
import net.shrine.authorization.{CouldNotInterpretResponseFromPmCell, CouldNotReachPmCell, ErrorStatusFromDataStewardApp, MissingRequiredRoles}
import net.shrine.broadcaster.CouldNotParseResultsException
import net.shrine.client.HttpResponse
import net.shrine.hms.authorization.HMSNotAuthenticatedProblem
import net.shrine.problem._
import net.shrine.protocol.QueryResult.StatusType
import net.shrine.protocol.query.QueryDefinition
import net.shrine.protocol._
import net.shrine.qep.queries.QepDatabaseProblem
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.{FlatSpec, Matchers}
import slick.driver.H2Driver.api._
import scala.concurrent.duration.FiniteDuration
import scala.xml.{NodeSeq, SAXParseException}
/**
* Created by ty on 8/29/16.
* Tests that we can successfully log every problem in the codebase.
* Due to the time nature of logging problems, we create the succeed
* early loop at the bottom to give every problem a chance at being
* created on time.
*/
@RunWith(classOf[JUnitRunner])
class ProblemCreation extends FlatSpec with Matchers {
val throwable = new IllegalArgumentException("Boo")
val credential: Credential = Credential("string", isToken = true)
val authInfo = AuthenticationInfo("domain", "username", credential)
val authExecption = AdapterLockoutException(authInfo, "url")
val bogus: ShrineRequest = new BogusRequest
val seconds = new FiniteDuration(10, java.util.concurrent.TimeUnit.SECONDS)
val queryDefinition = QueryDefinition("string", None)
val runQueryRequest = new RunQueryRequest("id", seconds, authInfo, 10, None, None, Set(), queryDefinition)
val saxxException: SAXParseException = new SAXParseException("hey", null)
val xmlResponse: String = ""
val someXml = Heyo!
val teapot: HttpResponse = HttpResponse(418, "body")
val nodeId: NodeId = NodeId("name")
val couldNotParseException: CouldNotParseResultsException = CouldNotParseResultsException(5, "url", "body", throwable)
val queryResult = QueryResult(5l, 5l, None, 5l, None, None, None, StatusType("name", isDone=false), None)
val readyQueryResponse = ReadQueryResultResponse(5l, queryResult)
val foo: NonI2b2ableResponse = new Foo()
"Problems" should "all be successfully created and logged" in {
URL.setURLStreamHandlerFactory(new BogusUrlFactory)
val db = Problems.DatabaseConnector
val queries = Problems.Queries
val problemSize = () => db.runBlocking(queries.size.result)
problemSize() shouldBe 0
val problems: Seq[AbstractProblem] = Seq(
HttpErrorCodeFromAdapter("url", 5, "string response body"),
CouldNotParseXmlFromAdapter("url", 6, "responseBody", saxxException),
QueryNotFound(10l),
QueryResultNotAvailable(10l),
CouldNotRetrieveQueryFromCrc(10l, throwable),
AdapterLockout(authInfo, authExecption),
CrcCouldNotBeInvoked("crcUrl", bogus, CrcInvocationException("url", bogus, throwable)),
AdapterMappingProblem(AdapterMappingException(runQueryRequest, "message", throwable)),
AdapterDatabaseProblem(new SQLException("reason", "state", 5)),
BotDetected(BotDetectedException("domain", "user", 5l, 5l, 5l)),
CannotParseXmlFromCrc(saxxException, xmlResponse),
ExceptionWhileLoadingCrcResponse(throwable, xmlResponse),
ErrorFromCrcBreakdown(ErrorFromCrcException("message")),
CannotInterpretCrcBreakdownXml(MissingCrCXmlResultException(someXml, throwable)),
QueryNotInDatabase(I2b2AdminReadQueryDefinitionRequest("project", seconds, authInfo, 5l)),
// Difficult to test, as I would have to pull it out of the defining code,
// Change it from an object to a case class, and make sure that I'm not breaking
// Any breakdown logic by doing so.
// BreakdownFailure,
CouldNotVerifySignature(BroadcastMessage(5l, authInfo, bogus)),
UnknownRequestType(RequestType("apple", None)),
NotAuthenticatedProblem(NotAuthenticatedException("string", "string", "message", throwable)),
MissingRequiredRoles("pid", Set(), authInfo),
CouldNotReachPmCell("url", authInfo, throwable),
CouldNotInterpretResponseFromPmCell("url", authInfo, teapot, throwable),
ErrorStatusFromDataStewardApp(spray.http.HttpResponse(), new URL("bogus", "host", 5, "file")),
CouldNotConnectToAdapter(nodeId, throwable),
TimedOutWithAdapter(nodeId),
CouldNotParseResultsProblem(couldNotParseException),
HttpErrorResponseProblem(couldNotParseException),
NoValidResponsesToAggregate(),
// Difficult to test since I can't grab private value:
- // InvalidResultProblem(Invalid(None, "error")),
+ //InvalidResultProblem(Invalid(None, "error")),
HMSNotAuthenticatedProblem(authInfo),
ErrorStatusFromCrc(None, ""),
QepDatabaseProblem(throwable),
ProblemNotYetEncoded("summary", Some(throwable)),
NoI2b2AnalogExists(foo.getClass),
TestProblem()
)
var count = 0
// give it up to 1 second to finish
while(problemSize() != problems.length && count < 20) {
Thread.sleep(50)
count+=1
}
problemSize() shouldBe problems.length
db.runBlocking(queries.result) should contain theSameElementsAs problems.map(_.toDigest)
}
}
class Foo extends ShrineResponse with NonI2b2ableResponse {
override def toXml: NodeSeq = Yay
}
class BogusUrlFactory extends URLStreamHandlerFactory {
override def createURLStreamHandler(protocol: String): URLStreamHandler = new BogusUrlHandler
}
class BogusUrlHandler extends URLStreamHandler {
override def openConnection(u: URL): URLConnection = new BogusUrlConnection(u)
}
class BogusUrlConnection(u: URL) extends URLConnection(u) {
override def connect(): Unit = {}
}
\ No newline at end of file