Controllers

Introduction

Controllers are methods that are exposed through HFactory’s REST API. Arguments to a controller can be specified in two ways:

  • Either in query form in the request’s URL with keys that match the method’s parameters;
  • or in JSON object form in the request’s body, as an object whose keys match the method’s parameters.

NOTE: For a given request, all arguments to a controller must be specified in the same way. You cannot specify some arguments in the URL and other arguments in the body – those would be ignored.

NOTE: Large values (in size) and collection values (lists, for instance) should be specified in JSON form in the request body. While HFactory currently supports such values in the URL, it is not convenient nor reasonable to do so. URL-specified arguments should only be base values (strings, ints, floats, etc).

A controller parameter can be of any type, provided there exists a JsonConv instance for that type: that instance is used to convert the values from the request (be it from the URL itself or the request’s body) to the types as defined in the method definition. Optional parameters are supported: if no matching key appears in the query, the argument in the method call is None.

If a parameter cannot be converted to the proper type, the controller cannot be invoked and HFactory will reply with400 Bad Request.

There are two kinds of controllers: app controllers, which are defined at the app level, and entity controllers which are defined at the entity level.

App controllers

An app controller is a method in an HApp instance, annotated with @HAppController.

An app controller ctl is exposed by HFactory through route /<app>/<ctl>. When a request is received on that route, the controller is executed with the request parameters as input and its output is returned as response.

As a simple example, here is an app controller that returns the parity of an integer:

class UserDirectory extends HApp {
  @HAppController
  def parity(k: Int): String = s"$k is " + (if (k % 2 == 0) "even" else "odd")
}

Example requests:

  • GET /userdir/parity?k=3 would return response 200 OK with body 3 is odd (“3” is a valid input: it’s an integer) *GET /userdir/parity?k=test would return 400 Bad Request (“test” is not a valid input: it’s not an integer)
  • GET /userdir/parity with the request body containing the JSON object { "k": 3 } is equivalent to the first request above.

As an example using a collection value, here’s an app controller that takes a list of floats and a threshold and only returns only those floats that are greater than the threshold:

@HAppController
def filterAbove(threshold: Float, data: List[Float]): List[String] =
  data.filter(_ > threshold)

Example request:

  • GET /smartbuilding/filterAbove with the body containing the JSON object { "threshold": 20.0, data: [ 1.5, 23.7, 32.0, 9.0, 12.4, 20.0 ] } would return response 200 OK with a JSON list in the body: [ 23.7, 32.0 ].

Controllers can make use of the HTalk DSL to access HBase:

import com.ubeeko.htalk.criteria._

class UserDirectory extends HApp {
  @HAppController
  def listGroupNames(): Iterable[String] =
    ("group" get rows qualifiers("name")) ~ (_.getValue("name").as[String])
}

The request GET /userdir/listGroupNames (no parameter) whould then return the list of group names in JSON form. For example: [ "admin", "dev" ].

Note that controllers are plain regular methods and as such can be called from other methods.

Entity controllers

An entity controller is a method in an entity companion object, annotated with @HEntityController.

An entity controller ctl defined in the companion object of entity entity is exposed by HFactory through route /<app>/<entity>/<ctl>.

Entity controllers work very much the same as app controllers, but differ when they require access to HTalk or the entity registry: in that case, they must be defined with an implicit HContext parameter or an implicit HEntityRegistry parameter, respectively. (HApp readily provides both the HTalk context and the entity registry as implicits, which is not the case of an entity’s companion object.)

Example 1 - Entity controller that doesn’t use HTalk:

case class Dummy(something: Int)

object Dummy {
  @HEntityController
  def inverse(f: Float): Double = 1.0 / i.toFloat
}

In this example, method inverse doesn’t make use of HTalk and thus has no implicit HContext parameter.

Example 2 - Entity controller using HTalk:

case class Emorum(id: String, satisfaction: Float, neutral: Float)

object Emorum {
    @HEntityController
    def onlyNeutrals()(implicit context: HContext): Iterable[(String, Float)] = {
      import com.ubeeko.htalk.criteria._
      ("emorum" get rows qualifiers("id", "neutral")) ~ (res => (res("id").as[String], res("neutral").as[Float]))
    }
}

In this example, method onlyNeutrals makes use of HTalk (as evidenced by the local import) and thus has to have an implicit HContext parameter.

Example 3 - Entity controller using HTalk and the entity registry:

case class Hotspot(id: Int, name: String, latitude: Double, longitude: Double) {
  ...
}

object Hostpot {
  @HEntityController
  def getClosest(lat: Double, long: Double, count: Int)
                (implicit entityReg: HEntityRegistry, hContext: HContext): List[Hotspot] = {
    val hotspotEntity = entityReg.getEntity[Hotspot]
    val locs = hotspotEntity.io.list()
    // Filter the hotspots wrt specified (lat, long).
    ...
  }
}