Book Store - User Interfaces - JBoss Weld CDI for Java Platform (2013)

JBoss Weld CDI for Java Platform (2013)

Chapter 10. Book Store - User Interfaces

This chapter will expand the book store application we developed the services for in Chapter 9, Book Store – CDI Services, by developing a user interface for the customer and administrator using two different technologies. This will enable us to see how CDI services can be reused to create two distinct user interfaces.

The customer user interface will be developed with AngularJS, though it could have been developed with any other JavaScript or view technology currently available. It will interact with CDI through REST to retrieve and store the data as required.

The administration interface will instead be developed with JSF2 and RichFaces for additional components for our interface. We will develop CDI beans for use in the JSF views to interact with our services, as we deem necessary.

Note

AngularJS information can be found at http://angularjs.org/.

REST services

To enable us to access our CDI services from AngularJS, we need to expose them over REST, though the use of REST is just one of the many choices that could have been made.

For retrieving books from our database, we need a REST service similar to the following:

@Path("/books")

public class BookListResource {

@Inject

BookService bookService;

@GET

@Produces(MediaType.APPLICATION_JSON)

public List<Book> getAvailableBooks() {

List<Book> results = bookService.getAvailableBooks();

Collections.sort(results, new BookTitleSorter());

return results;

}

@GET

@Path("/{categoryId}")

@Produces(MediaType.APPLICATION_JSON)

public List<Book> getAvailableBooksByCategory(@PathParam("categoryId") long categoryId) {

Map<String, String> params = new HashMap<>();

params.put("available", "true");

params.put("categoryId", Long.toString(categoryId));

List<Book> results = bookService.getAll(params);

Collections.sort(results, new BookTitleSorter());

return results;

}

}

Most of the annotations are related to configuring how the REST endpoints will be exposed and under which paths, but we can see that utilizing our CDI service is only two lines of code to inject it. Then it's available for any of our methods on this REST implementation.

To retrieve a single book, given its ID—such as when selecting a book from a list to see more details—we need the following code:

@Path("/book")

public class BookResource {

@Inject

BookService bookService;

@GET

@Path("/{bookId}")

@Produces(MediaType.APPLICATION_JSON)

public Book getBook(@PathParam("bookId") Long bookId) {

try {

return bookService.get(bookId);

} catch (ServiceException e) {

return null;

}

}

}

And when we need to interact with our UserService and AccountService, we need the following REST endpoint:

@Path("/account")

public class AccountResource {

@Inject

@LoggedIn

Instance<CurrentUser> currentUserInstance;

@Inject

AccountService accountService;

@Inject

UserService userService;

@GET

@Produces(MediaType.APPLICATION_JSON)

public CurrentUser getCurrentUser() {

return currentUserInstance.get();

}

@POST

@Path("/register")

@Consumes(MediaType.APPLICATION_JSON)

@Produces(MediaType.APPLICATION_JSON)

public RestResult register(GuestUser guestUser) {

User user = new User(guestUser.getName(), guestUser.getEmail(), RoleType.USER, guestUser.getPassword());

Account acct = new Account(user);

RestResult result = new RestResult();

try {

accountService.register(acct);

result.setSuccess(true);

} catch (ServiceException e) {

result.setSuccess(false);

}

return result;

}

@POST

@Path("/login")

@Consumes(MediaType.APPLICATION_JSON)

@Produces(MediaType.APPLICATION_JSON)

public RestResult login(GuestUser guestUser) {

RestResult result = new RestResult();

try {

userService.login(guestUser.getEmail(), guestUser.getPassword());

result.setSuccess(true);

} catch (ServiceException e) {

result.setSuccess(false);

}

return result;

}

@GET

@Path("/logout")

public void logout() {

userService.logout();

}

}

Note

We inject the CurrentUser entity into the endpoint in order to use it when we need to retrieve the details of the logged-in user.

User interface for customers

Now that we have the REST endpoints in place, we are now able to call them from AngularJS! To create a service for login, logout, and register, it only needs to interact with $resource, from AngularJS, to call the REST endpoints as shown in the following code:

angular.module('user.services', ['ngResource']).

service('User', function ($resource) {

var loadUser = function (success) {

$resource('/chapter10/rest/account').get(function (userData) {

if (userData.userId) {

if (success) {

success(userData);

}

}

});

};

this.register = function (user, success, error) {

$resource('/chapter10/rest/account/register').save(user, function (response) {

if (response.success) {

success();

} else {

error();

}

}, error);

};

this.login = function (user, success, error) {

$resource('/chapter10/rest/account/login').save({

email : user.email,

password : user.password

}, function (response) {

if (response.success) {

loadUser(success);

} else {

error();

}

}, error);

};

this.logout = function () {

$resource('/chapter10/rest/account/logout').get();

};

});

Our registration page that uses the REST endpoint looks like the following screenshot:

User interface for customers

Administration interface

To enable an administrator to login to the site, we need a CDI bean that JSF can use in the page, as shown in the following code:

@Named("login")

@RequestScoped

public class LoginBean {

private String email;

private String password;

@Inject

UserService userService;

public String getEmail() {

return email;

}

public void setEmail(String email) {

this.email = email;

}

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

public void submit() throws ServiceException {

userService.login(email, password);

}

}

This bean will capture the login credentials and perform the login operation on UserService. The following is the JSF view that will be used for logging in:

<rich:panel>

<f:facet name="header">

<h:outputText value="Login" />

</f:facet>

<h:form>

<h:panelGrid columns="2">

<h:outputText value="Email: " />

<rich:inplaceInput defaultLabel="click to enter your email" value="#{login.email}" />

<h:outputText value="Password: " />

<rich:inplaceInput defaultLabel="click to enter your password" value="#{login.password}" />

</h:panelGrid>

<h:commandButton value="Login" action="#{login.submit}" />

</h:form>

</rich:panel>

The bean used to retrieve the current users present in the database is very basic, as shown in the following code:

@Named("users")

@ViewScoped

public class UsersBean implements Serializable {

@Inject

UserService userService;

public List<User> getAllUsers() {

return userService.getAll(null);

}

}

Note

Given how basic this method call to our service is, we could easily have marked UserService with @Named and used it directly within our JSF page. At the moment that would not have caused any issues with the REST services using the service, but that may not be the case as development continues. Sometimes it is best to keep a separation between the service and how it is used by a view technology.

To display the users on our page we would use the following code:

<rich:tabPanel switchType="client">

<rich:tab header="Users">

<rich:dataTable value="#{users.allUsers}" var="user">

<rich:column>

<f:facet name="header">Name</f:facet>

<h:outputText value="#{user.name}" />

</rich:column>

<rich:column>

<f:facet name="header">Email</f:facet>

<h:outputText value="#{user.email}" />

</rich:column>

</rich:dataTable>

</rich:tab>

</rich:tabPanel>

This would look like the following screenshot:

Administration interface

Summary

Though brief, and far from being a complete application, we've seen in this chapter how quickly we can connect our CDI services to AngularJS or JSF. This makes it extremely easy to take advantage of whichever technology we prefer for our development, or it might be the newest that we want to try.