Friday, November 27, 2015

accessing the new web api service document ala odatav4 and scala with akka-http

If you need to access the root service document at the MS CRM Dynamics website, you can access it using standad OData techniques.
I typically use akka-http. Here's how you can obatin the service document at the start of your program. I'm not going to show all the related code but just enough to make it easier for you to access your service document.
...
val http = Http()
...
 val rootFuture = async {
      val response = await(http.singleRequest(HttpRequest(GET, "http://yourorg.crm.dynamics.com" + "/api/data", odataHeaders)))
      if(logger.underlying.isDebugEnabled()) {
        val body = Await.result(response.entity.toStrict(1 seconds).map(_.data.utf8String), 5 seconds)
        logger.debug(s"Service root document: $body")
      }
      await(Unmarshal(response).to[ServiceDocument])
    }
    val root = Await.result(rootFuture, 5 seconds)
I use async/await because any program should not proceed without first getting the root document then deriving all the URLs from it. Hence the use of awaits.
Here's I have defined a spray-json unmarshaller by defining a few case classes and then defining my own spray-json "formatter." Once you do this you can use the Unmarshal function (which is akka-http):
case class EntitySet(val name: String, val uri: Uri, val title: Option[String] = None, val kind: Option[String] = Some("EntitySet"))
case class ServiceDocument(val serviceRoot: Uri, val metadata: Uri, val entitySets: Seq[EntitySet]) {
  /**
   * Return the URI for obtaining an EntitySet.
   */
  def /(entitySetName: String): Option[Uri] = entitySets
    .find(_.name == entitySetName)
    .map(_.uri.resolvedAgainst(serviceRoot))
}

object OdataJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol {

  implicit object UriReader extends JsonReader[Uri] {
    def read(value: JsValue): Uri = value match {
      case JsString(uri) => Uri(uri)
      case x@_ => throw new DeserializationException(s"Uri must be deserialized from a string not a $x")
    }
  }

  implicit object EntitySetFormat extends JsonReader[EntitySet] {
    def read(value: JsValue): EntitySet = value.asJsObject.getFields("name", "kind", "url", "title") match {
      case Seq(JsString(name), kind, JsString(url)) => EntitySet(name, uri = url, kind = kind.convertTo[Option[String]])
      case Seq(JsString(name), kind, JsString(url), JsString(title)) => EntitySet(name, uri=url, kind = kind.convertTo[Option[String]], title = Some(title))
      case x@_ => throw new DeserializationException(s"Unable to parse $x into an EntitySet")
    }
  }

  implicit object ServiceDocumentFormat extends RootJsonReader[ServiceDocument] {
    def read(value: JsValue): ServiceDocument = value.asJsObject.getFields("@odata.context", "value") match {
      case Seq(JsString(context), JsArray(values)) =>
        val entitySets = values.map { _.convertTo[EntitySet] }
        ServiceDocument(context.substring(0, context.indexOf("/$metadata")), context, entitySets)
      case _ => throw new DeserializationException("Error obtaining root document")
    }
  }
}
Notice the use of SprayJsonSupport. That allows normal spray-json to be used in the Marshal framework which is akka-http.
You'll also need some headers:
object OData {
  val odataHeaders = List(
    Accept(MediaTypes.`application/json`),
    RawHeader("OData-MaxVersion", "4.0"),
    RawHeader("OData-Version", "4.0"))
}
My odataHeaders are slightly modified to add the Bearer token (see my other blogs):
  val odataHeaders = List(Authorization(headers.OAuth2BearerToken(yourBearerToken))) ++ OData.odataHeaders

No comments:

Post a Comment