JavaEE + CanJS: uncap your dev!


Writing a website today often looks like writing a REST server to provide data to a rich client side application.

JavaEE is a killing stack to quickly deploy a JAX-RS application and everybody is using angularjs for the client side.

However there are several alternatives that worth a look. One of them I particularly like is CanJS. In my opinion, it is lighter and more classical than angular, by letting you integrate “default” javascript libraries and not the “ng-” version which doesn’t always work as good as the original. Of course it needs a bit more work but once setup it is really smooth.

Let’s get started with this modern stack!

Create the project

Write the server

The server will simply retrieve TomEE closed issues from github public api and cache it.

Here is a sample of the loaded data once cached:

[
    {
        "body": "",
        "title": "Added an initial readme",
        "url": "https://api.github.com/repos/apache/tomee/issues/13"
    },
    {
        "body": "",
        "title": "TOMEE-1253",
        "url": "https://api.github.com/repos/apache/tomee/issues/12"
    },
    {
        "body": "",
        "title": "TOMEE-1253",
        "url": "https://api.github.com/repos/apache/tomee/issues/11"
    },
    {
        "body": "",
        "title": "TOMEE-1252",
        "url": "https://api.github.com/repos/apache/tomee/issues/10"
    },
    {
        "body": "",
        "title": "TOMEE-1233 updated deltaspike example (window-id)",
        "url": "https://api.github.com/repos/apache/tomee/issues/9"
    },
    {
        "body": "",
        "title": "TOMEE-1232 myfaces extval 2.0.8 upgrade",
        "url": "https://api.github.com/repos/apache/tomee/issues/8"
    },
    {
        "body": "",
        "title": "TOMEE-1232 myfaces extval 2.0.8 upgrade",
        "url": "https://api.github.com/repos/apache/tomee/issues/7"
    },
    {
        "body": "",
        "title": "TOMEE-1154 DeltaSpike full-stack demo",
        "url": "https://api.github.com/repos/apache/tomee/issues/6"
    },
    {
        "body": "* Added test case to prove the presence of the desired warning\r\n* Fixed spelling in Messages.properties\r\n* Inlined redundant variables in CheckAnnotationTest test cases",
        "title": "OPENEJB-1836 Added test case to prove behaviour",
        "url": "https://api.github.com/repos/apache/tomee/issues/5"
    },
    {
        "body": "Added test case for CheckAsynchronous (@Asynchronous validation)",
        "title": "OPENEJB-2071 Added test cases for @Asynchronous validation",
        "url": "https://api.github.com/repos/apache/tomee/issues/4"
    },
    {
        "body": "Corrected the package for commons-lang from org.apache.commons.lang to org.apache.commons.lang3 due to the fact that the package name changed for version >=3.0",
        "title": "Update pom.xml",
        "url": "https://api.github.com/repos/apache/tomee/issues/3"
    },
    {
        "body": "",
        "title": "Validation: Explicit check for InvocationContext incorrectly used in bean callbacks",
        "url": "https://api.github.com/repos/apache/tomee/issues/2"
    },
    {
        "body": "",
        "title": "Trunk",
        "url": "https://api.github.com/repos/apache/tomee/issues/1"
    }
]

Note: to simplify things in this post, github is here just a quick way to get some data, we completely ignore pagination, eviction etc.

To do so, we simply use JAX-RS 2 client API:

Future<Collection<Issue>> issues = ClientBuilder.newClient()
             .target("https://api.github.com")
             .path("/repos/apache/tomee/issues")
             .queryParam("state", "closed")
             .request(MediaType.APPLICATION_JSON_TYPE)
             .async()
             .get(new GenericType<Collection<Issue>>() {});

Our Issue looks like:

@Data // I use lombock but it mainly means getters/setters for this case
public class Issue {
    private String title;
    private String body;
    private String url;

    @JohnzonProperty("created_at")
    private Date createdAt;

    @JohnzonProperty("closed_at")
    private Date closedAt;
}

We used Apache Johnzon as default JAX-RS mapper. Thus, we rely on “@JohnzonProperty” not to map java friendly names to the json
but to except it nothing special.

Now, our resource simply returns the future value:

@Log
@Path("tomee")
@ApplicationScoped
@Startup // tomee feature but allows to preload the cache at bootstrap
public class TomEEResource {
    private Future<Collection<Issue>> cache;

    @PostConstruct
    private void download() {
        cache = ClientBuilder.newClient()
                    .target("https://api.github.com")
                    .path("/repos/apache/tomee/issues")
                    .queryParam("state", "closed")
                    .request(MediaType.APPLICATION_JSON_TYPE)
                    .async()
                    .get(new GenericType<Collection<Issue>>() {});
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Collection<Issue> issues() {
        try {
            return cache.get();
        } catch (InterruptedException e) {
            Thread.interrupted();
        } catch (ExecutionException e) {
            // no-op
        }
        return Collections.emptyList();
    }
}

Note: the @javax.ejb.Startup on a CDI bean (and not an EJB) is not a feature of JavaEE but is a TomEE feature. However, it is important here
to avoid to lazily lookup the issues on github (on the first request). An alternative way would be the use of a real EJB (@javax.ejb.Singleton @Lock(READ)).

Write the JS

First, we create a src/main/webapp/js/ folder to put our js code.
For the jquery and can.js libraries, we’ll use their CDN versions so we don’t download/paste any dependency.

In the js folder we just created, let’s create:
– an app.js,
– a model.js,
– a controller.js,
– and a router.js.

Now, we’ll create a basic index.html in src/main/webapp importing our scripts (we don’t use an AMD loader in this sample but Can.JS works smoothly with require.js):

<html>
  <head>
    <title>CanJS + TomEE</title>
  </head>
  <body>
    <div id="content"></div>

    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.js"></script>
    <script src="//canjs.com/release/2.2.5/can.jquery.js"></script>
    <script src="js/app.js"></script>
  </body>
</html>

All our structure is ready! Time to write our application (app.js). We’ll start by designing our
routing. We’ll get two routes:

– the default one listing issues
– and a specific one showing only one issue.

Now that we know what we’ll show, we need some data. Defining our model is essential. For that, we’ll use a simple model by mapping
issues:

var Issue = can.Model.extend({}, {});

can.Model defines a model class, which is interesting. In fact, with mustache views, you get live bindings quite easily. Extending a model takes two parameters: static and instance properties/methods.

In our case, we want to show either the list of all the issues (findAll) or a particular one (findById).

findAll is designed in Can.JS as a shortcut for the HTTP + model conversion logic. Just assign it to the endpoint path directly and Can.JS will handle the request (GET if not specified) and the JSON to Can.JS model conversion:

findAll: 'api/tomee'

There is also another way to get a findById using Can.JS conventions. But I would like to show you, with this second method, how to implement a custom ajax request with Can.JS and map the result to a model.

To do the request, the idea is to use can.ajax : if you know JQuery $.ajax, it is almost the exact same thing. Then just use can.Deferred to return the result in your function implementation with the ability to add the conversion only when we get the result. Finally, the conversion is pretty simple as can.Model provides parseModel for this purpose:

findById: function (params) {
    var deferred = new can.Deferred();
    var self = this;
    can.ajax({
        type: 'GET',
        url: 'api/tomee/' + params.id
    }).then(function (issue) {
        deferred.resolve(self.parseModel(issue));
    }, function (xhr, textStatus, err) {
        deferred.reject(err);
    });
    return deferred;
}

This kind of pattern really allows to go further in custom mappings without breaking the framework.

To put it together, here is the full model:

var Issue = can.Model.extend({
    findAll: 'api/tomee',
    findById: function (params) {
        var deferred = new can.Deferred();
        var self = this;
        can.ajax({
            type: 'GET',
            url: 'api/tomee/' + params.id
        }).then(function (issue) {
            deferred.resolve(self.parseModel(issue));
        }, function (xhr, textStatus, err) {
            deferred.reject(err);
        });
        return deferred;
    }
}, {} /* no instance property/method */);

Now that we have a model, we’ll implement our routes.

Here is how it can look like:

var Router = can.Control({
    'issue/:id route': IssueController,
    'route': IssueListController
});

Then, to start the application, just run the router:

new Router(document);
can.route.ready();

Can.JS uses can.Control, a particular Can base object that can be seen as an event listener (in the page hash). route means this is a route event (hash change).

In other words, we define the following urls:

– home (/xxx/ or /xxx/#!): use IssueListController
– /xxx/#!/issue/{id} : use IssueController

Now we need to define our controllers. As you can guess the easiest one is the IssueController.

A controller is a function that can take an object as a parameter. In this last case, the parameter matches the route parameters
(Id for us).

Since we return a Deferred in our findById implementation, we can use then hook to wait the model loading before showing anything. To show the view, we call :
can.view with the path to the view we want to use,
– the data the template needs,
– and finally the function called with the result of the templating. This last point is quite easy and shows the content.

In our case:

IssueController = function (params) {
    Issue.findById(params)
        .then(function (issue) {
            can.view('partial/issue.mustache', issue, function (html) {
                    $('#content').html(html);
            });
        });
};

Here, the template is a standard mustache one, reusing an issue instance as a set of values:

<h2>{{ title }}</h2>
<p>
    {{ body }}
</p>

Going further!

We saw how to write a simple page. Let’s make it a bit more complicated. We’ll now write the list view and we want a list we can filter with an input box (using the title as matcher).

The view will look like:

Filter (hit ENTER): <input id="issuesFilter" can-enter="{ filterIssues @element }" />
<ul id="issues">
    {{#issues}}
        <li style="display: {{ display }}">
        <a href="#!issue/{{ id }}">{{ title }}</a> : {{ body }}
    </li>
    {{/issues}}
</ul>

Nothing particular except the link #!issue/{{ id }} to match our routing and the can-enter. Basically, can-enter provides a way to configure a hook on “ENTER” event. The syntax looks weird but means “call filterIssues method
with the current element (the input) as parameter”.

In our controller, this means we need to provide the list of issues ({{#issues}} iterates over it automatically as it is a list) and a filterIssues method.

 IssueListController = function () {
    Issue.findAll({}, function (issues) {
        can.view(
            'partial/issues.mustache',
            new can.Map({
                issues: new can.List(issues),
                filterIssues: function (element) {
                    var value = element.val();
                    this.issues.forEach(function (issue) {
                        var hide = !!value &amp;&amp; issue.title.indexOf(value) &lt; 0;
                        if (hide != issue.hide) {
                            issue.attr('display', hide ? 'none' : 'list-item');
                        }
                    });
                }
            }),
            function (html) {
                $('#content').html(html);
            });
    });
}

NOTE: issues are wrapped in a can.List to support live binding (dynamic updates).

The implementation of filterIssues is here quite simlpe. We extract the text entered, then adjust the attribute display of the issue. Since our view defines the style with this attribute, and since we have live binding, the style is auto updated and the issue appears/disappears depending on what is entered in the input box when hitting “ENTER”.

Conclusion

CanJS gives you a lot of flexibility to develop rich client applications, and JAX-RS provides you the strength of JavaEE. Bridging both makes your application development very fast and efficient, particularly when using TomEE Embedded to get hot reload of your resources.

This post is a simple getting started section. For real applications, you can go further using RequireJS (or any other AMD) to properly load your resources on the client side and add some security to ensure your user only see allowed informations. However, this is not that hard
as, on both sides, everything is provided to do it quickly.

Even if you sometimes feel like the feature should be there out of the box (coming from AngularJS, the filtering seems missing), it has actually the benefit of staying closer to javascript than AngularJS. It is better integrated with javascript libraries (no need to be in AngularJS lifecycle) and it stays very powerful.

Finally, relying on mustache forces allows you to limit the view logic (“logic-less views”) which is really sane in my opinion. It also makes the development, and especially the maintenance, much easier.

The sample can be found at https://github.com/rmannibucau/canjs-tomee-sample. Run mvn compile tomee-embedded:run and go
to http://localhost:8080.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s