Polyglot Java

Posted by Jan Machacek

Find me on:

03-May-2012 17:17:00

I have presented the application I have shown in the previous post at the Progressive Java Tutorials at Skills Matter today; and I have made some modifications. The main change was to simplify the Spring machinery--I don't want to have to look up beans; I would like the dependencies to be injected into the actors that need them. Also, I have decided to include some user interface; specifically a jQuery application running in the browser.

If you are impatient and want to see the code right now, head over to my Github repository and download the slides from the talk there, too!

Right. The simplification for the actors first. If you remember, we had actors that want to receive Spring beans that looked like this:

 

class MyActor extends Actor {

  protected def receive = {
    case Msg() =>
       val repo = BeanLookup[SomeRepository]
       repo.findOne(1)
       ...
  }
}

This approach is OK, but we can really simplify it. With that in mind, let's build a full application that is a trivial user manager. Figure 1 shows its main components.

Figure 1. The application

arch

Now, to the actors. Just like in the previous example, we start with core actors that represent the main components of our application. We will have the SpringContextActor and the ApplicationActor. The actors react to the Start() message by preparing the Spring ApplicationContext and the actors that contain the "useful" code, respectively.

class Boot(system: ActorSystem) {
  implicit val timeout = Timeout(30000)

  val spring = system.actorOf(
    props = Props[SpringContextActor],
    name = "spring")
  val application = system.actorOf(
    props = Props[ApplicationActor],
    name = "application"
  )

  // Send the spring and application actors the Start() message.
  // Await until ready for at most timeout.

  Await.ready(spring ? Start(), timeout.duration)
  Await.ready(application ? Start(), timeout.duration)
}

Excellent. This class boots the core of our application. Turning to the SpringContextActor, we have the familiar new GenericXmlApplicationConetx in the response to the Start() message. (We also reply back with the Started() message once the ApplicationContext is ready.)

class SpringContextActor extends Actor {

  override val supervisorStrategy = OneForOneStrategy() {
    case _ => Resume
  }

  protected def receive = {
    case Start() =>
      new GenericXmlApplicationContext(
        "classpath*:/META-INF/spring/module-context.xml")

      sender ! Started()
    case Stop() =>
  }
}

Now, let's turn to our user actors, which are created in the usual fashion in the ApplicationActor; picking the EntityActor as an example.

case class ListEntities(clazz: Class[_], firstResult: Int = 0, maxResults: Int = Int.MaxValue)

case class GetEntity(clazz: Class[_], id: Long)

case class CountEntities(clazz: Class[_])

@Configurable
@Transactional
class EntityActor extends Actor {
  @Autowired
  var sessionFactory: SessionFactory = _

  protected def receive = {
    case GetEntity(clazz, id) =>
      val entity =
        sessionFactory.getCurrentSession.get(clazz, id)
      if (entity == null)
        sender ! None
      else
        sender ! Some(entity)

    case CountEntities(clazz) =>
      val criteria =
        sessionFactory.getCurrentSession.createCriteria(clazz)
      criteria.setProjection(Projections.rowCount())
      val rowCount =
        criteria.uniqueResult().asInstanceOf[java.lang.Long].longValue()

      sender ! rowCount
      //}
    case ListEntities(clazz, firstResult, maxResults) =>
      val criteria = sessionFactory.getCurrentSession.createCriteria(clazz)
      criteria.setFirstResult(firstResult)
      criteria.setMaxResults(maxResults)

      sender ! criteria.list().toList
  }
}

The bodies of pattern match are not that interesting (it's the usual boring Hibernate code). What is interesting are the annotations (boo, hiss!--I know, bear with me!) on the actor. The @Configurable annotation, together with <context:spring-configured /> and <context:load-time-weaver /> in the Spring's module-context.xml file intercepts the calls to the constructors and, injects the dependencies according to the Spring annotations! (Plus, we also have the @Transactional annotation, which makes every function in the actor transactional--something we need to easily use ORM.) Amazing!

Running it

The code we have written would be useless without exposing its services through accessible API. To do that, we're going to use Spray and Spray-can. In other words, we'll need to construct the UserService, with the appropriate directives and marshallers:

trait UserService extends Directives with DefaultMarshallers with CustomMarshallers {
  implicit val timeout = Timeout(10000)

  val userService = {
    get {
      path("user/list") {
        completeWith {
          val actor = actorSystem.actorFor("/user/application/entity")
          Await.result(
            (actor ? ListEntities(classOf[User])).mapTo[List[User]],
            timeout.duration)
        }
      } ~
      path("user" / LongNumber) {
        id =>
          completeWith {
            val actor = actorSystem.actorFor("/user/application/entity")
            Await.result(
              (actor ? GetEntity(classOf[User], id)).mapTo[Option[User]],
              timeout.duration)
          }
      }
...
}

Combine this with the usual Spray Can boot and you have a RESTful API on top of your actors. The only thing that's left is the client app. We don't have space for iOS or Android code, but I can show you jQuery-based rich client application!

&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;Polyglot Java&lt;/title&gt;
    &lt;script type="text/javascript" src="jquery-1.7.2.js"&gt;&lt;/script&gt;
    &lt;script type="text/javascript" src="jquery.dataTables.js"&gt;&lt;/script&gt;
    &lt;style type="text/css" title="currentStyle"&gt;
        @import "jquery.dataTables.css";
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;table cellpadding="0" cellspacing="0" border="0" class="display" id="users"&gt;
    &lt;thead&gt;
    &lt;tr&gt;
        &lt;th width="15%"&gt;Id&lt;/th&gt;
        &lt;th width="25%"&gt;Username&lt;/th&gt;
        &lt;th width="30%"&gt;First name&lt;/th&gt;
        &lt;th width="30%"&gt;Last name&lt;/th&gt;
    &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;

    &lt;/tbody&gt;
&lt;/table&gt;

&lt;a id="generate" href="#"&gt;More!&lt;/a&gt;
&lt;a id="refresh" href="#"&gt;Refresh!&lt;/a&gt;
&lt;a id="import" href="#"&gt;Import!&lt;/a&gt;

&lt;div id="status"&gt;Ready&lt;/div&gt;
&lt;/body&gt;
&lt;script type="text/javascript"&gt;
    $(document).ready(function () {
        (function () {
            countUsers();
            setTimeout(arguments.callee, 5000);
        })();

        var url = "http://localhost:8080/";

        var table = $('#users').dataTable({
            "bProcessing":true,
            "bServerSide":true,
            "bPaginate":false,
            "bLengthChange":false,
            "bFilter":false,
            "bSort":false,
            "bInfo":false,
            "bAutoWidth":false,
            "sAjaxSource":url + 'user/list',
            "aoColumns":[
                { "mDataProp":"id" },
                { "mDataProp":"username" },
                { "mDataProp":"firstName" },
                { "mDataProp":"lastName" }
            ]
        });

        function refresh() {
            table.fnDraw();
        }

        $("#generate").bind("click", function () { $.ajax(...); });
        $("#refresh").bind("click", function () { refresh(); });
        $("#import").bind("click", function() { $.ajax(...); });

        function countUsers() {
            $.ajax({
                url:url + 'user/count',
                success:function (data) {
                    $("#status").html("Managing " + data + " users.");
                }
            });
        }

    });
&lt;/script&gt;
&lt;/html&gt;

Summary

So, these are the most important portions of the code. If you want to see the whole beast, head over to my Github repository and download the slides from the talk there, too!

Topics: Java, Akka, spring

Subscribe to Email Updates