diff --git a/adapter/adapter-api/src/main/scala/net/shrine/adapter/client/RemoteAdapterClient.scala b/adapter/adapter-api/src/main/scala/net/shrine/adapter/client/RemoteAdapterClient.scala
index 5d956e957..db928c738 100644
--- a/adapter/adapter-api/src/main/scala/net/shrine/adapter/client/RemoteAdapterClient.scala
+++ b/adapter/adapter-api/src/main/scala/net/shrine/adapter/client/RemoteAdapterClient.scala
@@ -1,131 +1,136 @@
package net.shrine.adapter.client
import java.net.{SocketTimeoutException, URL}
import org.xml.sax.SAXParseException
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.concurrent.blocking
import scala.concurrent.duration.Duration
import scala.concurrent.duration.DurationInt
import scala.util.control.NonFatal
import scala.xml.{NodeSeq, XML}
import com.sun.jersey.api.client.ClientHandlerException
import net.shrine.client.{HttpResponse, Poster, TimeoutException}
import net.shrine.problem.{AbstractProblem, ProblemNotYetEncoded, ProblemSources}
import net.shrine.protocol.BroadcastMessage
import net.shrine.protocol.ErrorResponse
import net.shrine.protocol.NodeId
import net.shrine.protocol.Result
import scala.util.{Failure, Success, Try}
import net.shrine.protocol.ResultOutputType
/**
* @author clint
* @since Nov 15, 2013
*
*
*/
final class RemoteAdapterClient private (nodeId:NodeId,val poster: Poster, val breakdownTypes: Set[ResultOutputType]) extends AdapterClient {
import RemoteAdapterClient._
//NB: Overriding apply in the companion object screws up case-class code generation for some reason, so
//we add the would-have-been-generated methods here
override def toString = s"RemoteAdapterClient($poster)"
override def hashCode: Int = 31 * (if(poster == null) 1 else poster.hashCode)
override def equals(other: Any): Boolean = other match {
case that: RemoteAdapterClient if that != null => poster == that.poster
case _ => false
}
def url:Option[URL] = Some(new URL(poster.url))
//TODO: Revisit this
import scala.concurrent.ExecutionContext.Implicits.global
override def query(request: BroadcastMessage): Future[Result] = {
val requestXml = request.toXml
Future {
blocking {
val response: HttpResponse = poster.post(requestXml.toString())
interpretResponse(response)
}
}.recover {
case e if isTimeout(e) => throw new TimeoutException(s"Invoking adapter at ${poster.url} timed out", e)
}
}
def interpretResponse(response:HttpResponse):Result = {
if(response.statusCode <= 400){
val responseXml = response.body
import scala.concurrent.duration._
//Should we know the NodeID here? It would let us make a better error response.
Try(XML.loadString(responseXml)).flatMap(Result.fromXml(breakdownTypes)) match {
case Success(result) => result
case Failure(x) => {
val errorResponse = x match {
case sx: SAXParseException => ErrorResponse(CouldNotParseXmlFromAdapter(poster.url,response.statusCode,responseXml,sx))
case _ => ErrorResponse(ProblemNotYetEncoded(s"Couldn't understand response from adapter at '${poster.url}': $responseXml", x))
}
Result(nodeId, 0.milliseconds, errorResponse)
}
}
}
else {
Result(nodeId,0.milliseconds,ErrorResponse(HttpErrorCodeFromAdapter(poster.url,response.statusCode,response.body)))
}
}
}
object RemoteAdapterClient {
def apply(nodeId:NodeId,poster: Poster, breakdownTypes: Set[ResultOutputType]): RemoteAdapterClient = {
//NB: Replicate URL-munging that used to be performed by JerseyAdapterClient
val posterToUse = {
if(poster.url.endsWith("requests")) { poster }
else { poster.mapUrl(_ + "/requests") }
}
new RemoteAdapterClient(nodeId,posterToUse, breakdownTypes)
}
def isTimeout(e: Throwable): Boolean = e match {
case e: SocketTimeoutException => true
case e: ClientHandlerException => {
val cause = e.getCause
cause != null && cause.isInstanceOf[SocketTimeoutException]
}
case _ => false
}
}
case class HttpErrorCodeFromAdapter(url:String,statusCode:Int,responseBody:String) extends AbstractProblem(ProblemSources.Adapter) {
override def summary: String = "Hub received a fatal error response"
override def description: String = s"Hub received error code $statusCode from the adapter at $url"
- override def detailsXml:NodeSeq =
'+sanitizeString(JSON.stringify(detailsObject))+'' } else if (typeof(detailsField) === 'string') { return detailsTag + '
'+sanitizeString(detailsField)+'
'; } else if (typeof(detailsField) === 'object' && detailsField.hasOwnProperty('exception')) { return detailsTag + parseException(detailsField['exception']); } else { return detailsTag + ''+sanitizeString(JSON.stringify(detailsField))+'' } } function parseException(exceptionObject) { var exceptionTag = '
'+sanitizeString(exceptionObject['message'])+'
'; var stackTrace = exceptionObject['stacktrace']; - return exceptionTag + nameTag + messageTag + (stackTrace == null? '': parseStackTrace(stackTrace)); + return exceptionTag + nameTag + messageTag + (stackTrace === undefined? '': parseStackTrace(stackTrace)); } function parseStackTrace(stackTraceObject) { if (stackTraceObject.hasOwnProperty('exception')) { return ''+sanitizeString(stackTraceObject['line'])+'
' + parseException(stackTraceObject['exception']); } else if (stackTraceObject.hasOwnProperty('line')) { return '';
for (var i =0; i < lineArray.length; i++) {
result += sanitizeString(lineArray[i]) + '
';
}
result += '