diff --git a/commons/protocol/src/main/scala/net/shrine/protocol/DeleteQueryRequest.scala b/commons/protocol/src/main/scala/net/shrine/protocol/DeleteQueryRequest.scala index f5d383425..29cc60bea 100644 --- a/commons/protocol/src/main/scala/net/shrine/protocol/DeleteQueryRequest.scala +++ b/commons/protocol/src/main/scala/net/shrine/protocol/DeleteQueryRequest.scala @@ -1,85 +1,89 @@ package net.shrine.protocol import scala.concurrent.duration.Duration -import scala.util.Try +import scala.util.{Failure, Success, Try} import scala.xml.NodeSeq import net.shrine.util.XmlUtil import net.shrine.util.NodeSeqEnrichments import net.shrine.serialization.I2b2UnmarshallingHelpers /** * @author Bill Simons * @since 3/28/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 DeleteQueryRequest( override val projectId: String, override val waitTime: Duration, override val authn: AuthenticationInfo, networkQueryId: Long) extends ShrineRequest(projectId, waitTime, authn) with CrcRequest with TranslatableRequest[DeleteQueryRequest] with HandleableShrineRequest with HandleableI2b2Request { override val requestType = RequestType.MasterDeleteRequest override def handle(handler: ShrineRequestHandler, shouldBroadcast: Boolean) = handler.deleteQuery(this, shouldBroadcast) override def handleI2b2(handler: I2b2RequestHandler, shouldBroadcast: Boolean) = handler.deleteQuery(this, shouldBroadcast) override def toXml: NodeSeq = XmlUtil.stripWhitespace { { headerFragment } { networkQueryId } } def withId(id: Long) = this.copy(networkQueryId = id) override def withAuthn(ai: AuthenticationInfo) = this.copy(authn = ai) override def withProject(proj: String) = this.copy(projectId = proj) protected override def i2b2MessageBody = XmlUtil.stripWhitespace { { i2b2PsmHeader } { authn.username } { networkQueryId } } } object DeleteQueryRequest extends I2b2XmlUnmarshaller[DeleteQueryRequest] with ShrineXmlUnmarshaller[DeleteQueryRequest] with ShrineRequestUnmarshaller with I2b2UnmarshallingHelpers { override def fromI2b2(breakdownTypes: Set[ResultOutputType])(xml: NodeSeq): Try[DeleteQueryRequest] = { import NodeSeqEnrichments.Strictness._ for { projectId <- i2b2ProjectId(xml) waitTime <- i2b2WaitTime(xml) authn <- i2b2AuthenticationInfo(xml) - masterId <- (xml withChild "message_body" withChild "request" withChild "query_master_id").map(_.text.toLong) + masterIdString <- (xml withChild "message_body" withChild "request" withChild "query_master_id").map(_.text) + masterId <- Try(masterIdString.toLong).transform(id => Success(id), + x => Failure(I2B2MessageFormatException(s"query_master_id of $masterIdString could not be interpreted as a Long integer",x))) } yield { DeleteQueryRequest(projectId, waitTime, authn, masterId) } } override def fromXml(breakdownTypes: Set[ResultOutputType])(xml: NodeSeq): Try[DeleteQueryRequest] = { import NodeSeqEnrichments.Strictness._ for { waitTime <- shrineWaitTime(xml) authn <- shrineAuthenticationInfo(xml) queryId <- xml.withChild("queryId").map(_.text.toLong) projectId <- shrineProjectId(xml) } yield { DeleteQueryRequest(projectId, waitTime, authn, queryId) } } -} \ No newline at end of file +} + +case class I2B2MessageFormatException(message:String,cause:Throwable) extends Exception(message,cause) \ No newline at end of file diff --git a/qep/service/src/main/scala/net/shrine/qep/I2b2BroadcastResource.scala b/qep/service/src/main/scala/net/shrine/qep/I2b2BroadcastResource.scala index ad4e4a949..73c2d7d68 100644 --- a/qep/service/src/main/scala/net/shrine/qep/I2b2BroadcastResource.scala +++ b/qep/service/src/main/scala/net/shrine/qep/I2b2BroadcastResource.scala @@ -1,97 +1,106 @@ package net.shrine.qep import java.sql.SQLException import javax.ws.rs.{POST, Path, Produces} import javax.ws.rs.core.{MediaType, Response} import javax.ws.rs.core.Response.ResponseBuilder import net.shrine.authentication.NotAuthenticatedException import net.shrine.log.Loggable -import net.shrine.problem.ProblemNotYetEncoded -import net.shrine.protocol.{ErrorResponse, HandleableI2b2Request, I2b2RequestHandler, ResultOutputType, ShrineRequest} +import net.shrine.problem.{AbstractProblem, ProblemNotYetEncoded, ProblemSources} +import net.shrine.protocol.{ErrorResponse, HandleableI2b2Request, I2B2MessageFormatException, I2b2RequestHandler, ResultOutputType, ShrineRequest} import net.shrine.qep.queries.QepDatabaseProblem import net.shrine.serialization.I2b2Marshaller import net.shrine.slick.CouldNotRunDbIoActionException import net.shrine.util.XmlUtil import scala.util.Try import scala.util.control.NonFatal import scala.xml.NodeSeq /** * @author Bill Simons * @since 3/10/11 * @see http://cbmi.med.harvard.edu * @see http://chip.org *

* NOTICE: This software comes with NO guarantees whatsoever and is * licensed as Lgpl Open Source * @see http://www.gnu.org/licenses/lgpl.html */ @Path("/i2b2") @Produces(Array(MediaType.APPLICATION_XML)) final case class I2b2BroadcastResource(i2b2RequestHandler: I2b2RequestHandler, breakdownTypes: Set[ResultOutputType]) extends Loggable { //NB: Always broadcast when receiving requests from the legacy i2b2/Shrine webclient, since we can't retrofit it to //Say whether broadcasting is desired for a praticular query/operation val shouldBroadcast = true @POST @Path("request") def doRequest(i2b2Request: String): Response = processI2b2Message(i2b2Request) @POST @Path("pdorequest") def doPDORequest(i2b2Request: String): Response = processI2b2Message(i2b2Request) def processI2b2Message(i2b2Request: String): Response = { // todo would be good to log $i2b2Request)") def errorResponse(e: Throwable): ErrorResponse = e match { case nax:NotAuthenticatedException => ErrorResponse(nax.problem) case cnrdax:CouldNotRunDbIoActionException => ErrorResponse(QepDatabaseProblem(cnrdax)) case sqlx:SQLException => ErrorResponse(QepDatabaseProblem(sqlx)) + case imfx:I2B2MessageFormatException => ErrorResponse(QepCouldNotInterpretRequest(imfx)) case _ => ErrorResponse(ProblemNotYetEncoded("The QEP encountered an unforeseen problem while processing an i2b2 request",e)) } def prettyPrint(xml: NodeSeq): String = XmlUtil.prettyPrint(xml.head).trim //NB: The legacy webclient can't deal with non-200 status codes. //It also can't deal with ErrorResponses in several cases, but we have nothing better to return for now. //TODO: Return a 500 status here, once we're using the new web client def i2b2HttpErrorResponse(e: Throwable): ResponseBuilder = Response.ok.entity(prettyPrint(errorResponse(e).toI2b2)) def handleRequest(shrineRequest: ShrineRequest with HandleableI2b2Request): Try[ResponseBuilder] = Try { info(s"Running request from user: ${shrineRequest.authn.username} of type ${shrineRequest.requestType.toString}") val shrineResponse = shrineRequest.handleI2b2(i2b2RequestHandler, shouldBroadcast) //TODO: Revisit this. For now, we bail if we get something that isn't i2b2able val responseString: String = shrineResponse match { case i2b2able: I2b2Marshaller => prettyPrint(i2b2able.toI2b2) case _ => throw new Exception(s"Shrine response $shrineResponse has no i2b2 representation") } Response.ok.entity(responseString) }.recover { case NonFatal(e) => error("Error processing request: ", e) i2b2HttpErrorResponse(e) } def handleParseError(e: Throwable): Try[ResponseBuilder] = Try { debug(s"Failed to unmarshal i2b2 request.")//todo would be good to log : $i2b2Request") error("Couldn't understand request: ", e) //NB: The legacy webclient can't deal with non-200 status codes. //It also can't deal with ErrorResponses in several cases, but we have nothing better to return for now. //TODO: Return a 400 status here, once we're using the new web client i2b2HttpErrorResponse(e) } val builder = HandleableI2b2Request.fromI2b2String(breakdownTypes)(i2b2Request).transform(handleRequest, handleParseError).get builder.build() } +} + +case class QepCouldNotInterpretRequest(x:Exception) extends AbstractProblem(ProblemSources.Qep){ + override val summary = "The QEP could not interpret a request." + + override val throwable = Some(x) + + override val description = x.getMessage } \ No newline at end of file