draptik

mostly tech stuff

RESTful CRUD With AngularJS

This post will show how to perform typical CRUD (create, read, update and delete) operations in AngularJS when consuming a RESTful web service.

A prerequisite for this demo is a working RESTful web service. For a basic introduction on creating a Java based RESTful web service, see my introduction on how to consume a RESTful web service with AngularJS created by a Java backend. For completeness sake I’ve added a Java based sample at the end of this post.

Frontend (AngularJS)

Views (Partials)

We will create three views.

The first view will display all users (user-list.html):

The view also provides links to edit (ng-click="editUser(user.id)") and delete (ng-click="deleteUser(user.id)") specific users as well as a link to create a new user (ng-click="createUser()").

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div class="span6">
    <table class="table table-striped table-condensed">
        <thead>
        <tr>
            <th style="min-width: 80px;">First name</th>
            <th style="min-width: 80px;">Last name</th>
            <th style="width:20px;"> </th>
            <th style="width:20px;"> </th>
        </tr>
        </thead>
        <tbody>
        <tr ng-repeat="user in users">
            <td>{{ user.firstName }}</td>
            <td>{{ user.lastName }}</td>
            <td><a ng-click="editUser(user.id)" class="btn btn-small btn-primary">edit</a></td>
            <td><a ng-click="deleteUser(user.id)" class="btn btn-small btn-danger">delete</a></td>
        </tr>
        </tbody>
    </table>
    <a ng-click="createNewUser()" class="btn btn-small">create new user</a>
</div>

The second and third view (user-detail.html and user-creation.html) both provide a form for entering the user properties.

They only differ in the actions provided. These actions (cancel(), updateUser(), createNewUser()) are invoked using ng-click:

user-[detail|creation].html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<div class="container">
    <h1>User detail</h1>

    <form novalidate="novalidate" class="form-horizontal">
        <div class="control-group">
            <label class="control-label" for="inputFirstName">First name:</label>
            <div class="controls">
                <input type="text" id="inputFirstName" ng-model="user.firstName"/>
            </div>
        </div>
        <div class="control-group">
            <label class="control-label" for="inputLastName">Last name:</label>
            <div class="controls">
                <input type="text" id="inputLastName" ng-model="user.lastName"/>
            </div>
        </div>
        <div class="control-group">
            <div class="controls">
              <!-- user-detail.html: -->
                <a ng-click="cancel()" class="btn btn-small">cancel</a>
                <a ng-click="updateUser()" class="btn btn-small btn-primary">update user</a>

              <!-- user-creation.html: -->
                <a ng-click="createNewUser()" class="btn btn-small btn-primary">create new user</a>
            </div>
        </div>
    </form>
</div>

Controller

Next we will create three controllers corresponding to the three views.

UserListCtrl

UserListCtrl provides three functions editUser, deleteUser and createUser.

  • editUser and createUser merely redirect to a different partial view using AngularJs’s $location function.
  • deleteUser calls the UserFactory service method delete (which we will create shortly).

Furthermore the $scope.users is filled with the result from the UsersFactory.query() function.

Note that all required dependencies are injected into the controller’s signature (function ($scope, UsersFactory, UserFactory, $location)).

controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var app = angular.module('ngdemo.controllers', []);

app.controller('UserListCtrl', ['$scope', 'UsersFactory', 'UserFactory', '$location',
    function ($scope, UsersFactory, UserFactory, $location) {

        // callback for ng-click 'editUser':
        $scope.editUser = function (userId) {
            $location.path('/user-detail/' + userId);
        };

        // callback for ng-click 'deleteUser':
        $scope.deleteUser = function (userId) {
            UserFactory.delete({ id: userId });
            $scope.users = UsersFactory.query();
        };

        // callback for ng-click 'createUser':
        $scope.createNewUser = function () {
            $location.path('/user-creation');
        };

        $scope.users = UsersFactory.query();
    }]);
  /* ... */

UserDetailCtrl and UserCreationCtrl

UserDetailCtrl provides the function updateUser, which in turn invokes the service method UserFactory.update. The $scope.user is filled with the result from calling UserFactory.show. cancel is just a convenient link redirecting back to the user-list view.

UserCreationCtrl provides the function createNewUser, calling UsersFactory.create.

Again, both controllers use $location to redirect back to the user-list partial view.

controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* ... */
app.controller('UserDetailCtrl', ['$scope', '$routeParams', 'UserFactory', '$location',
    function ($scope, $routeParams, UserFactory, $location) {

        // callback for ng-click 'updateUser':
        $scope.updateUser = function () {
            UserFactory.update($scope.user);
            $location.path('/user-list');
        };

        // callback for ng-click 'cancel':
        $scope.cancel = function () {
            $location.path('/user-list');
        };

        $scope.user = UserFactory.show({id: $routeParams.id});
    }]);

app.controller('UserCreationCtrl', ['$scope', 'UsersFactory', '$location',
    function ($scope, UsersFactory, $location) {

        // callback for ng-click 'createNewUser':
        $scope.createNewUser = function () {
            UsersFactory.create($scope.user);
            $location.path('/user-list');
        }
    }]);

Don’t forget to map the views to the corresponding controllers in app.js using the $routeProvider:

app.js
1
2
3
4
5
6
7
angular.module('ngdemo', ['ngdemo.filters', 'ngdemo.services', 'ngdemo.directives', 'ngdemo.controllers']).
    config(['$routeProvider', function ($routeProvider) {
        $routeProvider.when('/user-list', {templateUrl: 'partials/user-list.html', controller: 'UserListCtrl'});
        $routeProvider.when('/user-detail/:id', {templateUrl: 'partials/user-detail.html', controller: 'UserDetailCtrl'});
        $routeProvider.when('/user-creation', {templateUrl: 'partials/user-creation.html', controller: 'UserCreationCtrl'});
        $routeProvider.otherwise({redirectTo: '/user-list'});
    }]);

Service

AngularJS can consume the web service using $resource. This module is injected via 'ngResource'.

We create two factories:

  • UsersFactory (note the plural s) calls the web service with methods not requiring an id (query and create).
  • UserFactory calls the web service with methods requiring a user id (show, update and delete).
services.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var services = angular.module('ngdemo.services', ['ngResource']);

services.factory('UsersFactory', function ($resource) {
    return $resource('/ngdemo/web/users', {}, {
        query: { method: 'GET', isArray: true },
        create: { method: 'POST' }
    })
});

services.factory('UserFactory', function ($resource) {
    return $resource('/ngdemo/web/users/:id', {}, {
        show: { method: 'GET' },
        update: { method: 'PUT', params: {id: '@id'} },
        delete: { method: 'DELETE', params: {id: '@id'} }
    })
});

Backend (Java)

Here is an example of a RESTful web service created with Java:

UserRestService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package ngdemo.web.rest;

import com.google.inject.Inject;
import ngdemo.domain.User;
import ngdemo.service.contract.UserService;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.List;

@Path("/users")
public class UserRestService {

    private final UserService userService;

    @Inject
    public UserRestService(UserService userService) {
        this.userService = userService;
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<User> getAllUsersInJSON() {
        return userService.getAllUsers();
    }

    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public User getUserById(@PathParam("id") int id) {
        return userService.getById(id);
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public User create(User user) {
        return userService.createNewUser(user);
    }

    @PUT
    @Path("{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public User update(User user) {
        return userService.update(user);
    }

    @DELETE
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public void remove(@PathParam("id") int id) {
        userService.remove(id);
    }
}

You can clone a copy of this project here: https://github.com/draptik/angulardemorestful.

To checkout the correct version for this demo, use the following code:

1
2
3
git clone git@github.com:draptik/angulardemorestful.git
cd angulardemorestful
git checkout -f step4-angularjs-crud

In case you are not using git you can also download the project as ZIP or tar.gz file here: https://github.com/draptik/angulardemorestful/releases/tag/step4-angularjs-crud

Unit Testing RESTful Services

In my two previous posts I gave an introduction on how to consume a RESTful web service with AngularJS created by a Java backend and use Guice in the Java backend.

In this post I will show how to create a unit test for this web service.

Most of this code is inspired by a blog post from Paulo Renato de Athaydes.

We will need to install some new dependencies:

  • jetty-maven-plugin
  • junit
  • jersey-client
  • jersey-grizzly2

Grizzly will be our web server for testing.

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <name>ngdemo Maven Webapp</name>
    <groupId>ngdemo</groupId>
    <artifactId>ngdemo</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <jersey.version>1.17.1</jersey.version>
        <guice.version>3.0</guice.version>
    </properties>

    <build>
        <finalName>ngdemo</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.0</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>8.1.11.v20130520</version>
                <configuration>
                    <scanIntervalSeconds>10</scanIntervalSeconds>
                    <connectors>
                        <connector implementation="org.eclipse.jetty.nio.SelectChannelConnector">
                            <port>8080</port>
                            <maxIdleTime>60000</maxIdleTime>
                        </connector>
                    </connectors>
                    <stopKey/>
                    <stopPort/>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>

        <!-- javax: XML binding -->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.1</version>
        </dependency>

        <!-- RESTful web service: Jersey ====================================== -->
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-server</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-servlet</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-json</artifactId>
            <version>${jersey.version}</version>
        </dependency>

        <!-- Guice ============================================================= -->
        <dependency>
            <groupId>com.google.inject</groupId>
            <artifactId>guice</artifactId>
            <version>${guice.version}</version>
        </dependency>

        <dependency>
            <groupId>com.google.inject.extensions</groupId>
            <artifactId>guice-servlet</artifactId>
            <version>${guice.version}</version>
        </dependency>

        <dependency>
            <groupId>com.sun.jersey.contribs</groupId>
            <artifactId>jersey-guice</artifactId>
            <version>${jersey.version}</version>
        </dependency>

        <!-- Required for bypassing web.xml via Guice. Used in TestServlet.java -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>


        <!-- Unit testing ====================================================== -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-client</artifactId>
            <version>${jersey.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-grizzly2</artifactId>
            <version>${jersey.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Our class under test is UserRestService.java:

UserRestService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package ngdemo.rest;

import com.google.inject.Inject;
import ngdemo.domain.User;
import ngdemo.service.contract.UserService;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;


@Path("/users")
public class UserRestService {

    private final UserService userService;

    @Inject
    public UserRestService(UserService userService) {
        this.userService = userService;
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public User getDefaultUserInJSON() {
        return userService.getDefaultUser();
    }
}

Here is the corresponding unit test class UserRestServiceTest.java:

UserRestServiceTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package ngdemo.tests;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.ServletModule;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.container.grizzly2.GrizzlyServerFactory;
import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.core.spi.component.ioc.IoCComponentProviderFactory;
import com.sun.jersey.guice.spi.container.GuiceComponentProviderFactory;
import ngdemo.repositories.contract.UserRepository;
import ngdemo.repositories.impl.UserRepositoryImpl;
import ngdemo.service.contract.UserService;
import ngdemo.service.impl.UserServiceImpl;
import org.glassfish.grizzly.http.server.HttpServer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.net.URI;

import static junit.framework.Assert.assertEquals;

public class UserRestServiceTest {

    static final URI BASE_URI = getBaseURI();
    HttpServer server;

    private static URI getBaseURI() {
        return UriBuilder.fromUri("http://localhost/").port(9998).build();
    }

    @Before
    public void startServer() throws IOException {
        System.out.println("Starting grizzly...");

        Injector injector = Guice.createInjector(new ServletModule() {
            @Override
            protected void configureServlets() {
                bind(UserService.class).to(UserServiceImpl.class);
                bind(UserRepository.class).to(UserRepositoryImpl.class);
            }
        });

        ResourceConfig rc = new PackagesResourceConfig("ngdemo.rest");
        IoCComponentProviderFactory ioc = new GuiceComponentProviderFactory(rc, injector);
        server = GrizzlyServerFactory.createHttpServer(BASE_URI + "rest/", rc, ioc);

        System.out.println(String.format("Jersey app started with WADL available at "
                + "%srest/application.wadl\nTry out %sngdemo\nHit enter to stop it...",
                BASE_URI, BASE_URI));
    }

    @After
    public void stopServer() {
        server.stop();
    }

    @Test
    public void testGetDefaultUser() throws IOException {
        Client client = Client.create(new DefaultClientConfig());
        WebResource service = client.resource(getBaseURI());
        ClientResponse resp = service.path("rest").path("users")
                .accept(MediaType.APPLICATION_JSON)
                .get(ClientResponse.class);
        System.out.println("Got stuff: " + resp);
        String text = resp.getEntity(String.class);

        assertEquals(200, resp.getStatus());
        assertEquals("{\"firstName\":\"JonFromREST\",\"lastName\":\"DoeFromREST\"}", text);
    }
}

In the startServer method we create an injector for Guice, which we can then pass into the GuiceComponentProviderFactory to create the inversion of control (IoC) container.

Together with the ResourceConfig the IoC container is passed to Grizzly’s server factory to create the web server for testing.

Within the actual test method testGetDefaultUser we only have to setup the Jersey Client to retrieve the response (from the Grizzly server).

Here’s the test output from Maven:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$ mvn test
...
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running ngdemo.tests.UserRestServiceTest
Starting grizzly...
Jul 19, 2013 1:50:35 PM com.sun.jersey.api.core.PackagesResourceConfig init
INFO: Scanning for root resource and provider classes in the packages:
  ngdemo.rest
Jul 19, 2013 1:50:35 PM com.sun.jersey.api.core.ScanningResourceConfig logClasses
INFO: Root resource classes found:
  class ngdemo.rest.UserRestService
Jul 19, 2013 1:50:35 PM com.sun.jersey.api.core.ScanningResourceConfig init
INFO: No provider classes found.
Jul 19, 2013 1:50:35 PM com.sun.jersey.server.impl.application.WebApplicationImpl _initiate
INFO: Initiating Jersey application, version 'Jersey: 1.17.1 02/28/2013 12:47 PM'
Jul 19, 2013 1:50:36 PM com.sun.jersey.guice.spi.container.GuiceComponentProviderFactory getComponentProvider
INFO: Binding ngdemo.rest.UserRestService to GuiceInstantiatedComponentProvider
Jul 19, 2013 1:50:37 PM org.glassfish.grizzly.http.server.NetworkListener start
INFO: Started listener bound to [localhost:9998]
Jul 19, 2013 1:50:37 PM org.glassfish.grizzly.http.server.HttpServer start
INFO: [HttpServer] Started.
Jersey app started with WADL available at http://localhost:9998/rest/application.wadl
Try out http://localhost:9998/ngdemo
Hit enter to stop it...
Got stuff: GET http://localhost:9998/rest/users returned a response status of 200 OK
Jul 19, 2013 1:50:37 PM org.glassfish.grizzly.http.server.NetworkListener stop
INFO: Stopped listener bound to [localhost:9998]
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.604 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.378s

Test time is 2.6 sec. Not bad considering we are starting a web server, deploying our app, creating a client, running the test and shutting down the web server.

Running this test from within IntelliJ takes: 0.009 sec…

You can clone a copy of this project here: https://github.com/draptik/angulardemorestful.

To checkout the correct version for this demo, use the following code:

1
2
3
git clone git@github.com:draptik/angulardemorestful.git
cd angulardemorestful
git checkout -f step3-backend-test

In case you are not using git you can also download the project as ZIP or tar.gz file here: https://github.com/draptik/angulardemorestful/releases/tag/step3-backend-test

Guice in Java Web Application

Google’s Guice framework promises to be a lightweight(!) Inversion-of-Control (IoC) container.

Advantages compared to Spring:

  • Spring is much more than an IoC container, and therefore overkill for many projects.
  • Configuration by code. NO XML.

Based on my previous post showing how to use AngularJS with a Java RESTful backend I extended the simple demo application to use Guice.

Let’s say we have a UserServiceImpl class which depends on a UserFactory interface. The UserFactory interface is injected into the constructor of the UserServiceImpl class.

The only thing we have to do is add the @Inject annotation to the constructor so that Guice can do its job.

UserServiceImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package ngdemo.service.impl;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import ngdemo.domain.User;
import ngdemo.service.contract.UserFactory;
import ngdemo.service.contract.UserService;

import java.util.List;

public class UserServiceImpl implements UserService {

    private final UserFactory userFactory;

    @Inject
    public UserServiceImpl(UserFactory userFactory) {
        this.userFactory = userFactory;
    }

    @Override
    public List<User> getDefaultUsers() {
        return this.userFactory.createUsers();
    }

    @Override
    public User getDefaultUser() {
        return this.userFactory.createUser();
    }

}

For the IoC container to know which implementation to inject we have to create a Guice Module which derives from AbstractModule:

UserModule.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package ngdemo.infrastructure;

import com.google.inject.AbstractModule;
import ngdemo.service.contract.UserFactory;
import ngdemo.service.contract.UserService;
import ngdemo.service.impl.UserFactoryImpl;
import ngdemo.service.impl.UserServiceImpl;

public class UserModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(UserFactory.class).to(UserFactoryImpl.class);
        bind(UserService.class).to(UserServiceImpl.class);
    }
}

The UserModule class demonstrates the advantage of Guice vs. Spring: NO XML. When using Spring you normally would have to create Spring beans in an XML file like this:

applicationContext.xml (pseudo code)
1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

  <bean id="userService" class="ngdemo.service.impl.UserServiceImpl">
      <constructor-arg ref="userFactory"/>
  </bean>
  
  <bean id="userFactory" class="ngdemo.service.impl.UserFactoryImpl" />
      
</beans>

Next we have to create a replacement for the servlets required by the servlet container:

ngdemo.infrastructure.NgDemoApplicationSetup.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package ngdemo.infrastructure;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.servlet.ServletModule;
import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;

public class NgDemoApplicationSetup extends GuiceServletContextListener {

    @Override
    protected Injector getInjector() {

        return Guice.createInjector(new ServletModule() {

            @Override
            protected void configureServlets() {

                super.configureServlets();

                // Configuring Jersey via Guice:
                ResourceConfig resourceConfig = new PackagesResourceConfig("ngdemo/rest");
                for (Class<?> resource : resourceConfig.getClasses()) {
                    bind(resource);
                }
                serve("/rest/*").with(GuiceContainer.class);
            }
        }, new UserModule()); // <-- Adding other Guice Dependency Injection Modules
    }
}

And finally the file web.xml:

web.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app id="WebApp_ID" version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
  http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>Restful Web Application</display-name>

    <filter>
        <filter-name>guiceFilter</filter-name>
        <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>guiceFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <listener>
        <listener-class>ngdemo.infrastructure.NgDemoApplicationSetup</listener-class>
    </listener>
</web-app>

The file web.xml is now free of any <servlet> tags. The only thing that has to be configured in XML is the <listener-class>. The value of the <listener-class> is our Java class NgDemoApplicationSetup, so all further configuration can be defined in a type safe manner.

You can clone a copy of this project here: https://github.com/draptik/angulardemorestful.

To checkout the correct version for this demo, use the following code:

1
2
3
git clone git@github.com:draptik/angulardemorestful.git
cd angulardemorestful
git checkout -f step2-guice

In case you are not using git you can also download the project as ZIP or tar.gz file here: https://github.com/draptik/angulardemorestful/releases/tag/step2-guice

AngularJS Example Using a Java RESTful Web Service

AngularJS is the current MVV-Whatever JavaScript framework by Google. Among other things, it provides bidirectional data binding.

Although I’m neither a Java nor a JavaScript expert, I choose the following scenario for my ‘Hello-World’ example:

  1. Java backend provides a RESTful web service.

  2. AngularJS consumes the web service.

That’s it.

Project structure

I intentionally put the backend and frontend code in the same project to simplify the example. In a real project you probably want to have seperate projects for front- and backend.

1
2
3
4
5
6
7
8
+---------------------------------------------------+
| demo project                                      |
|                                                   |
| +----------------+              +---------------+ |
| | backend (Java) | < -(REST)- > | frontend (JS) | |
| +----------------+              +---------------+ |
|                                                   |
+---------------------------------------------------+

Since the backend is Java based, I used a Maven default structure (maven-archetype-site-simple):

project structure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
├── _documentation
│   └── readme.txt
├── ngdemo.iml
├── pom.xml
└── src
    └── main
        ├── java
        │   └── ngdemo
        │       ├── domain
        │       │   └── User.java
        │       ├── rest
        │       │   └── UserRestService.java
        │       └── service
        │           └── UserService.java
        └── webapp
            ├── css
            │   └── app.css
            ├── img
            ├── index-async.html
            ├── index.html
            ├── index.jsp
            ├── js
            │   ├── app.js
            │   ├── controllers.js
            │   ├── directives.js
            │   ├── filters.js
            │   └── services.js
            ├── lib
            │   └── angular
            │       ├── angular-cookies.js
            │       ├── angular-cookies.min.js
            │       ├── angular.js
            │       ├── angular-loader.js
            │       ├── angular-loader.min.js
            │       ├── angular.min.js
            │       ├── angular-resource.js
            │       ├── angular-resource.min.js
            │       ├── angular-sanitize.js
            │       ├── angular-sanitize.min.js
            │       └── version.txt
            ├── partials
            │   └── partial1.html
            └── WEB-INF
                └── web.xml

src/main/java is the backend.

src/main/webapp/js is the frontend.

src/main/webapp/ also includes a copy of angular-seed.

RESTful web service (backend)

Jersey is the Java reference implementation for providing REST.

Install the following dependencies in your pom.xml:

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- .. -->
<!-- RESTful web service: Jersey -->
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-server</artifactId>
    <version>1.17.1</version>
</dependency>
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-servlet</artifactId>
    <version>1.17.1</version>
</dependency>
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-json</artifactId>
    <version>1.17.1</version>
</dependency>
<!-- .. -->

Add the following servlet snippet to your web.xml:

web.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- .. -->
<servlet>
    <servlet-name>jersey-serlvet</servlet-name>

    <servlet-class>
        com.sun.jersey.spi.container.servlet.ServletContainer
    </servlet-class>

    <init-param>
        <param-name>com.sun.jersey.config.property.packages</param-name>
        <param-value>ngdemo.rest</param-value>
    </init-param>

    <init-param>
        <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
        <param-value>true</param-value>
    </init-param>

    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>jersey-serlvet</servlet-name>
    <url-pattern>/rest/*</url-pattern>
</servlet-mapping>
<!-- .. -->

Enough configuration for now: Create a simple User object…

User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package ngdemo.domain;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class User {

    private String firstName;
    private String lastName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

…and a service class…

UserService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
package ngdemo.service;

import ngdemo.domain.User;

public class UserService {

    public User getDefaultUser() {
        User user = new User();
        user.setFirstName("JonFromREST");
        user.setLastName("DoeFromREST");
        return user;
    }
}

…and finally the RESTful Service… (Update 2015-08-07 small fix, thanks Jason):

UserRestService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package ngdemo.rest;

import ngdemo.domain.User;
import ngdemo.service.UserService;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/users")
public class UserRestService {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public User getDefaultUserInJSON() {
        UserService userService = new UserService();
        return userService.getDefaultUser();
    }
}

Converting the User object to JSON via @Produces(MediaType.APPLICATION_JSON) requires jersey-json in web.xml (POJOMappingFeature).

Consuming web service from AngularJS (frontend)

Don’t forget to add angular-resources.js to your index.html

Consuming the web service:

services.js
1
2
3
4
5
6
7
8
9
10
11
var services = angular.module('ngdemo.services', ['ngResource']);

services.factory('UserFactory', function ($resource) {
    return $resource('/ngdemo/rest/users', {}, {
        query: {
            method: 'GET',
            params: {},
            isArray: false
        }
    })
});

Usage in controller:

controller.js
1
2
3
4
5
6
7
var app = angular.module('ngdemo.controllers', []);

app.controller('MyCtrl1', ['$scope', 'UserFactory', function ($scope, UserFactory) {
    UserFactory.get({}, function (userFactory) {
        $scope.firstname = userFactory.firstName;
    })
}]);

Usage in view:

1
2
3
4
5
<div>
    <p>
        Result from RESTful service is: {{ firstname }}
    </p>
</div>

Et voila:

Update (2013-07-18):

You can clone a copy of this project here: https://github.com/draptik/angulardemorestful.

To checkout the correct version for this demo, use the following code:

1
2
3
git clone git@github.com:draptik/angulardemorestful.git
cd angulardemorestful
git checkout -f step1

In case you are not using git you can also download the project as ZIP or tar.gz file here: https://github.com/draptik/angulardemorestful/releases/tag/step1

Listing Environment Variables (Linux)

Just a note to self: Get a list of all linux environment variables and their values using the export command.

Here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pi@rpidev ~ $ export
declare -x HOME="/home/pi"
declare -x JAVA_HOME="/opt/java/jdk1.7.0_25"
declare -x LANG="en_GB.UTF-8"
declare -x LOGNAME="pi"
declare -x LS_COLORS="rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:"
declare -x MAIL="/var/mail/pi"
declare -x OLDPWD="/home/pi/myinstall_notes"
declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games:/opt/java/jdk1.7.0_25/bin"
declare -x PWD="/home/pi"
declare -x SHELL="/bin/bash"
declare -x SHLVL="1"
declare -x SSH_CLIENT="192.168.179.32 39295 22"
declare -x SSH_CONNECTION="192.168.179.32 39295 192.168.179.148 22"
declare -x SSH_TTY="/dev/pts/0"
declare -x TERM="xterm"
declare -x USER="pi"

ReorderList Demo for AjaxControlToolkit

AjaxControlToolkit’s ReorderList provides drag and drop functionality within a list.

ReorderListDemo.aspxSource Article
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<div class="CssReorderList">
    <!-- ClientMode="AutoID" is required for certain versions of AjaxControlToolkit  -->
    <ajaxToolkit:ReorderList ID="MyReorderList" runat="server"
                             DataKeyField="MyId"
                             SortOrderField="MyPosition"
                             PostBackOnReorder="False"
                             ClientIDMode="AutoID"
                             DragHandleAlignment="Left"
                             ItemInsertLocation="Beginning"
                             AllowReorder="true"
        >
        <ItemTemplate>
            <div style="background-color: yellow;" class="CssItemArea">
                <asp:HiddenField runat="server" ID="hdfMyId"
                  Value="<%# ((DummyViewModel)Container.DataItem).MyId %>" />
                <asp:Label runat="server" ID="lblName"
                  Text="<%# ((DummyViewModel)Container.DataItem).MyName %>"/>
                <asp:Label runat="server" ID="lblPosition"
                  Text="<%# ((DummyViewModel)Container.DataItem).MyPosition %>"/>
            </div>
        </ItemTemplate>
        <DragHandleTemplate>
            <div class="CssDragHandle"><strong>DRAG ME</strong></div>
        </DragHandleTemplate>
    </ajaxToolkit:ReorderList>
<div>

I’ve placed a working example project on github.

RPi/XBMC: Keeping Content in Sync

If you have multiple Rasperry Pi/XBMC clients and want to

  • keep your “history” in sync (watch a movie in room1, stop, start the same movie in room2 and resume)
  • do not want to individually update every RPi/XBMC client if the content of your data storage changes

then this post might be for you.

The XBMC Wiki decribes this in detail. This post just describes an actual implementation using Raspberry Pi clients and server.

To do this you will need a MySQL database on a machine in your network which is always accessible from your Raspberry Pi/XBMC clients (called RPI-Client from now on). Since I didn’t have a machine like this in my network I decided to buy another RPi for this purpose (referred to as RPi-Server from now on).

The content of the MySQL database only contains metadata for each entry, not the actual data (information like (a) the file path and (b) the last timestamp within the movie you were watching).

Here is a diagram showing my network setup for two Rasperry Pi clients running XBMC:

network
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+---+
|   |   |---------------------------------|
|   |---| RPi-Client-1 (XBMC living room) |
|   |   |---------------------------------|
|   |
| L |   |------------------------------|
| A |---| RPi-Client-2 (XBMC bed room) |
| N |   |------------------------------|
|   |
|   |   |-----------------------|
|   |---| RPi-Server (Raspbian) |
|   |   |-----------------------|
|   |
|   |   |-----|
|   |---| NAS |
|   |   |-----|
+---+
  • The clients RPi-Client-X are default OpenELEC installations.
  • The server RPi-Server is a default Raspbian installation.

RPi-Server Setup

The RPi-Server has to provide a MySQL database which can be accessed from each RPi-Client-X. Just follow the simple setup instructions on the XBMC-Wiki.

This is how you can check the status of your MySQL server:

rpi-server$ sudo /etc/init.d/mysql status

RPi-Client Setup

Each RPi-Client-X needs to be configured to use the RPi-Server database.

RPi-Server: 192.168.179.36

advancedsettings.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<advancedsettings>
  <videodatabase>
        <type>mysql</type>
        <host>192.168.179.36</host>
        <port>3306</port>
        <user>xbmc</user>
        <pass>xbmc</pass>
  </videodatabase>
  <musicdatabase>
        <type>mysql</type>
        <host>192.168.179.36</host>
        <port>3306</port>
        <user>xbmc</user>
        <pass>xbmc</pass>
  </musicdatabase>
</advancedsettings>

Note: The location of the file advancedsettings.xml might very depending on your XBMC installation. For OpenELEC the file is located at /storage/.xbmc/userdata/advancedsettings.xml.

XBMC Library Update (optional)

Once the above setup works you can install the XBMC Addon:Library Auto Update. This plugin provides a GUI to setup a cron job to sync the content your RPi-Client-X has acces to (i.e. your NAS) with your MySQL database. Just install it on one of your RPi-Client-X.

MySQL Backup (optional)

A bit overkill, but I also backup the MySQL Database from the RPi-Server to my NAS using automysqlbackup.

Mapping Missing Context Menu in OpenELEC With Remote Control HAMA MCE

Remote control HAMA MCE The IR remote control HAMA MCE works out of the box with XBMC. Except that XBMC’s context menu is not mapped to any key.

An alternative solution to the one provided here is using your smart phone or tablet as a remote control with the excellent Yatse app.

Setup

Problem

Remote control does not have a key which is mapped to the context menu.

For the impatient

  • Login to your Raspberry Pi with SSH
  • Create file /storage/.xbmc/userdata/Lircmap.xml.
  • Add following content:
/storage/.xbmc/userdata/Lircmap.xml
1
2
3
4
5
  <lircmap>
      <remote device="devinput">
          <title>KEY_INFO</title>
      </remote>
  </lircmap>
  • Restart Raspberry Pi

Solution

File locations

  • OpenELEC’s system location for keymap related configs are located here: /usr/share/xbmc/system/ (I found this information in the openelec forum)
  • System default keymaps folder: /usr/share/xbmc/system/keymaps/
  • System default Lircmap.xml: /usr/share/xbmc/system/Lircmap.xml
  • Root user’s keymaps folder (empty by default): /storage/.xbmc/userdata/keymaps/
  • Root user’s Lircmap.xml does not exist by default. It has to be created: /storage/.xbmc/userdata/Lircmap.xml

Workflow

Get device name and name of desired button on remote control (‘irw’)

  • Adapted from Guide To Test a Remote and Remap Keys
  • Login via SSH
  • Type command irw
  • Press the button on remote control where you would like to map the context menu (I pressed the button with the ‘right mouse click’ symbol). This will output something like:
1
2
3
root# irw
17b 0 KEY_INFO devinput
17b 0 KEY_INFO_UP devinput

KEY_INFO is the key name, devinput is the device name.

The next sentence in the guide was not clear to me (userdata SMB share is /usr/share/xbmc/system/…):

Make sure you have a proper remote.xml in your keymaps folder which is inside your userdata SMB share (or located at /storage/.xbmc/userdata/keymaps/remote.xml)

With OpenELEC the location of userdata SMB share is /usr/share/xbmc/system/Lircmap.xml.

Get function name for context menu

The function name can be read from /usr/share/xbmc/system/Lircmap.xml. Search for ContextMenu. This should look like:

/usr/share/xbmc/system/Lircmap.xml
1
2
3
...
<title>ContextMenu</title>
...

The function name for context menu is title.

Create customized Lircmap.xml

Create /storage/.xbmc/userdata/Lircmap.xml:

1
$ nano /storage/.xbmc/userdata/Lircmap.xml

and add the following content (adapt to your needs):

/storage/.xbmc/userdata/Lircmap.xml
1
2
3
4
5
<lircmap>
  <remote device=”devinput”>
      <title>KEY_INFO</title>
  </remote>
</lircmap>

That’s it. Restart Raspberry Pi and the context menu should now work.

Pygments and Python

Frameworks like Octopress and Jekyll use the Python library Pygments for syntax highlighting.

Currenty (2013-05-23) Pygments does not work flawlessly with Python3 (see this post and this issue on pygment’s issue tracker).

This is a problem because many operating systems already provide Python3 as the default python installation.

To circumvent this issue, I found 2 different solutions:

  1. Rewire your system to always use python version X (i.e. 2.7) when python is called: See this post for an example solution.

  2. Sandbox Python installations.

Personally I prefer the approach of having a sandbox system. This way I don’t change the system’s default setting. Whenever I require a different Python version I can just switch it (this is similar to the Ruby Version Manager rvm).

This blog post explains some further details on how to install Octopress in Arch Linux. It comes down to installing python-virtualenvwrapper and configuring a new custom environment blog_env which uses Python 2.7:

Paraphrazing the original post:

To switch to the newly created blog_env, run workon blog_env. To exit a virtualenv, run deactivate.