Skip to content

Tutorial: a Java EE Web Profile application with JButler (old)

Vítor E. Silva Souza edited this page Sep 5, 2018 · 2 revisions

This tutorial is deprecated. Please follow the new tutorial.

Welcome to the tutorial "a Java EE Web Profile application with JButler". In this tutorial, we start from scratch and give step-by-step instructions on how to use many of the features present in JButler.

The running example for this tutorial is Marvin, a system which helps academics in various tasks related to their job, such as teaching, research, group administration, etc.

Tool installation and configuration

The following tools and versions were used in this tutorial (newer versions of these tools might also work the same way):

  • Java SE Development Kit 8 (download);
  • Eclipse IDE for Java EE Developers, codename Neon (version 4.6) (download);
  • WildFly 10.1, certified Java EE 7 (download);
  • MySQL Community Server 5.7 (download);
  • MySQL Workbench 6.3 (download);
  • MySQL Connector/J JDBC Driver 5.1 (download).

Installation instructions vary according to operating system (Linux, MacOS or Windows), therefore we have not included detailed, step-by-step instructions on how to install these tools. In general, for Eclipse and WildFly it's enough to unpack them somewhere in your hard drive. We'll refer to the folder where you unpacked these tools as $ECLIPSE_HOME and $WILDFLY_HOME, respectively. The JDK and the MySQL server and workbench can be installed using an install wizard downloaded from their website or through your system's package manager (e.g.: apt-get in Ubuntu Linux or any other Debian-based distributions). Finally, the Connector/J driver will be used during WildFly's configuration.

Set-up Eclipse to work with WildFly

To deploy our application in WildFly during development more easily, it's recommended to integrate the IDE Eclipse with the application server WildFly. This is done with some tools provided by JBoss/Red Hat, which can now be installed in a more straightforward way in the latest Eclipse version:

  1. Open Eclipse;
  2. Open the Servers view (if not visible, use the Window > Show View menu);
  3. Click on the "No servers are available. Click this link to create a new server..." link, which is shown when the Servers view is empty. Alternatively, right-click the blank space at the Servers view and select New > Server;
  4. Open the Red Hat JBoss Middleware folder and select JBoss AS, Wildfly, & EAP Server Tools;
  5. Click Next, wait for the download, accept the license terms, click Finish and restart Eclipse;
  6. Repeat steps 2 and 3 to open the New Server dialog again;
  7. This time, open the JBoss Community folder and select WildFly 10.x. Click Next twice;
  8. Fill in the server's directory ($WILDFLY_HOME) and click Finish.

At the end of the wizard, the WildFly server should be at the Servers view. Right-clicking on it allows you to start, stop, restart the server. Try starting the server and if everything is OK you should see as the last message in the Console view something like shown below. Then open http://localhost:8080/ and you should see WildFly's welcome page.

WildFly Full 10.1.0.Final (WildFly Core 2.2.0.Final) started in 9936ms - Started 331 of 577 services (393 services are lazy, passive or on-demand)

Set-up WildFly to connect to MySQL

WildFly comes with an H2 Database driver configured. In this tutorial, however, we use MySQL, so we need to add its driver to WildFly's configuration. Follow these steps to do it (make sure the server is stopped for this):

  1. In the folder $WILDFLY_HOME/modules, create the following directory structure: com/mysql/main (we are using / to separate sub-directories; Windows users should replace it with \);

  2. Unpack the MySQL Connector/J JDBC Driver you downloaded earlier and copy the file mysql-connector-java-5.1.41-bin.jar to the newly created folder $WILDFLY_HOME/modules/com/mysql/main. If you downloaded a different version of Connector/J, adjust the name accordingly;

  3. Still at $WILDFLY_HOME/modules/com/mysql/main, create a file named module.xml with the following contents (again if you downloaded a different version of Connector/J, adjust the name accordingly):

    <?xml version="1.0" encoding="UTF-8"?>
    <module xmlns="urn:jboss:module:1.1" name="com.mysql">
    	<resources>
    		<resource-root path="mysql-connector-java-5.1.41-bin.jar"/>
    	</resources>
    	<dependencies>
    		<module name="javax.api"/>
    	</dependencies>
    </module>
  4. Now open the file $WILDFLY_HOME/standalone/configuration/standalone.xml and look for the tag <subsystem xmlns="urn:jboss:domain:datasources:4.0">. Inside this tag, locate <datasources> and then <drivers>. You should find the H2 Database driver configuration there. Next to it, add the configuration for MySQL Connector/J, as following:

    <driver name="mysql" module="com.mysql">
    	<driver-class>com.mysql.jdbc.Driver</driver-class>
    </driver>

You should now be ready to develop a Java EE project in Eclipse, deploying it in WildFly and configuring it to use MySQL database for persistence. The above steps need to be done just once for all projects which will use these tools. In the next step we start Marvin with some project-specific configurations.


In this step we create the Marvin project and set it up to use JButler.

Note:

This tutorial could be used in two ways: (a) to implement a feature in Marvin using JButler to experiment it; or (b) to bootstrap a new system. In the latter case, you should replace all occurrences of marvin or Marvin with your system's name.

Database creation and set-up

We will use JPA (Java Persistence API), one of the Java EE standards, persisting our objects in a relational database stored in the MySQL server. We need, therefore, to:

  1. Create a database schema named marvin;
  2. Create a database user named marvin with password marvin;
  3. Give user marvin full permission for the schema marvin.

To do that, use MySQL Workbench. Once you open it, connect to the server using the root user (the administrator) and you should see a screen similar to the figure below. If you see an error message at the bottom of the screen indicating that a connection to the server could not be established, click on Server > Startup/Shutdown and click the button to start the server.

MySQL Workbench after connecting to the server as the root user.

To create the database, click the Create a new schema button in the toolbar, indicated by the red arrow in the above figure. Fill in marvin as Schema Name and select utf8 - default collation as Default Collation. Finally, click Apply and then Apply again to create the database schema.

Next, click on Users and Privileges at the left-hand side of the Workbench's window and the Administration - Users and Privileges tab should open (see figure below). Click on the Add Account button and fill in the marvin user information like shown in the figure below (the password, which is hidden in the figure, should be marvin as well):

Creating the marvin user in MySQL Workbench.

Click Apply and then switch to the Schema Privileges section of the user details (see figure below). Click the Add Entry button, choose the Selected schema radio button and select Marvin at the list of schemas. Click OK to go back to the Schema Privileges screen, click the Select "ALL" button to give the marvin user all the necessary permissions over the marvin schema and, finally, click Apply. The schema privileges configuration should end up like shown in the figure below:

Setting up schema privileges for the marvin user and the marvin schema.

You can now close MySQL Workbench.

Datasource configuration in WildFly

While we could configure JPA to connect to the database we have just created in a configuration file in our project, creating a datasource for it in WildFly allows us to use JTA (Java Transaction API), another standard from Java EE, which provides us with automatic transaction management.

To create a JTA datasource for Marvin in WildFly, open the file $WILDFLY_HOME/standalone/configuration/standalone.xml and look for the tag <subsystem xmlns="urn:jboss:domain:datasources:4.0">. Inside this tag, there is a <datasources> tag which holds the configuration for the java:jboss/datasources/ExampleDS datasource that WildFly comes with. Next to it, add a datasource for the marvin database in MySQL:

<datasource jta="true" jndi-name="java:jboss/datasources/Marvin" pool-name="MarvinPool" enabled="true" use-java-context="true">
    <connection-url>jdbc:mysql://localhost:3306/marvin</connection-url>
    <driver>mysql</driver>
    <security>
        <user-name>marvin</user-name>
        <password>marvin</password>
    </security>
</datasource>

If you have SSL problems connecting to the MySQL database with Connector/J, try to append ?useSSL=false to the connection URL. (Thanks Claudenir for the tip!).

It's a good idea, at this point, to test if the MySQL driver and the data source were properly configured in WildFly. To do this, start the server from within Eclipse's Servers view and verify that the console outputs no errors or exceptions. Instead, you should see messages similar to these (... indicates other messages in between):

INFO  [org.jboss.modules] (main) JBoss Modules version 1.5.2.Final
...
INFO  [org.jboss.as.connector.deployers.jdbc] (MSC service thread 1-4) WFLYJCA0018: Started Driver service with driver-name = mysql
...
INFO  [org.jboss.as.connector.subsystems.datasources] (MSC service thread 1-3) WFLYJCA0001: Bound data source [java:jboss/datasources/Marvin]
...
INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: WildFly Full 10.1.0.Final (WildFly Core 2.2.0.Final) started in 22253ms - Started 734 of 970 services (405 services are lazy, passive or on-demand)

Project creation and JButler set-up in Eclipse

We are finally ready to move to Eclipse and start developing our application. Instead of creating a new project, however, we'll use a base project created for this tutorial and based on the Marvin project.

Why do it this way?

JButler's base project is basically a copy of Marvin before any of its modules were developed, so the only features that are implemented are Install System, Log in and Log out, which are fairly common in many information systems, so you might as well reuse them instead of creating them from scratch.

Moreover, the base project already has Marvin's decorator, which is based on AdminLTE, a responsive, HTML5 free web design. Also, some base files that are used by JButler are also already there.

If, however, you'd like to start your own project from scratch, what you need to do is to create a new Dynamic Web Project targeted to the Wildfly server, convert it to a Maven project and then add JButler as a dependency, as explained in JButler's README file. You'd have to create your own main decorator and facelet components decorators.

Enough explanation, let's move on.

Here are the steps to copy JButler's base project contents into a new Eclipse project:

  1. Download the base project: clone or download the base project from Github. If you clone it, make sure to remove the remote repository (git remote rm origin) as you will not be able to push changes to it (and it doesn't make sense, anyways). You can always add a new remote repository later (git remote add <git url>);

  2. Manually rename the project: Eclipse uses the name jbutler-base-project as the name of the project in a few files. If we manually change it to another name (e.g. Marvin), that's the name Eclipse will recognize when we import it next. To do that, edit the following files using your favorite text editor, and replace the occurrences of the project name:

    • .project (1 occurrence);
    • .settings/org.eclipse.wst.common.component (2 occurrences, including one with a version number -- remove the number);
    • pom.xml (1 occurrence, and you might want to change the groupId as well).
  3. Import the project in Eclipse: in Eclipse, click on the File > Import... menu, select General > Existing Projects into Workspace and click Next. Point to the directory of the downloaded/cloned project (which you can also rename, if you want) in the Select root directory field and click Finish;

  4. Check everything is fine: Check if your project has any errors (in the Markers view). Some common problems can be fixed by right-clicking the project, selecting Maven > Update project... and clicking OK. Another common source of projects is the name of your Application Server in your Eclipse installation being different than the one used in the base project. In that case, right-click your project, select Properties, open Targeted Runtimes and select the name of the application server installed in your Eclipse instance and click Apply and Close. Finally, it is possible that Maven will fail to retrieve some of the project's dependencies due to a problem with SSL certificates. If that happens, please read the README file at Marvin's repository to learn how to solve it.

Everything should be error-free at this point and if you open Java Resources > Libraries > Maven Dependencies you should see some JARs: primefaces-6.1.jar, bootstrap-1.0.10.jar and jbutler-wp-1.2.4.jar (versions may vary if these components are updated after this tutorial was written).

Testing the application

To make sure everything is in order before we actually start developing the application, deploy and test it:

  1. Open the Servers view, right-click the WildFly server and choose Add and Remove.... Move the Marvin project from the Available list to the Configured list and click Finish;

  2. Make sure the MySQL server is already started, start the WildFly server and open http://localhost:8080/Marvin/ -- the initial screen should be shown, as in the figure below;

    Marvin's start screen, with the Install System feature available in the menu.

  3. Open the MySQL Workbench tool and see that JPA has already created tables for some domain entities that already exist in the base project;

  4. Click on Install System menu of Marvin, fill in your data as administrator and click on Register admin. Then fill in your university's acronym (e.g., UFES) in the next screen and click Save configuration;

  5. You can now log in (at the left-hand side menu) and log out (at the top-right menu that shows if you click your own name).

Try resizing the browser screen to simulate lower screen sizes (tablets and smartphones) and you'll see AdminLTE's responsive features in action...


In this step we explain the architecture of JButler's base project (i.e., Marvin), which we suggest for most Web-based Information Systems (WIS) and on which JButler is based.

This step of the tutorial is optional and recommended only if you wish to understand what has already been done in the Marvin-based base project you copied in the previous step. If you don't need to (or don't care), you can skip to the next step and implement the CRUD using JButler.

The WIS architecture

JButler's mini CRUD framework is built for use in WISs that follow a three-tier architecture on top of Java Enterprise Edition, as shown in the figure below.

Proposed architecture for Web-based Information Systems that use Java EE.

At the Presentation Tier, the View is composed of Web pages and related resources (scripts, stylesheets, images, etc.). Facelets applies a template to our pages, whereas Primefaces, a JSF component library that offers a large set of components, is used to assemble web forms and such.

The Control package contains JSF Managed Beans, which are classes responsible for establishing the communication between "the Java world" and "the Web world": JSF Web pages at the View refer to attributes and methods of JSF Beans at the Control using an Expression Language (EL), allowing users to send and obtain data from the WIS using a Web-based user interface.

At the Business Tier, the Application and Domain packages implement the business rules independently of the presentation and persistence technologies. In Domain we have the entity (persistent) classes that represent elements of the domain of the problem (in this case, academics, students, classes, etc.), whereas in Application there are Session EJBs which implement the use cases of the system (register an academic, manage an academic's research, etc.).

Finally, the Data Access Tier is composed of a single package, Persistence, which is where the Data Access Objects are. The DAOs are responsible for storing, retrieving and deleting data from the domain entities in the data storage (in our case, the MySQL database) and uses JPA to do it through Object/Relational Mapping.

The dependencies between the three tiers -- Control depends on Application to execute the system's use cases upon user request and, in its turn, Application depends on Persistence to store data in the database -- are satisfied by CDI, which, along with JSF, EJBs and JPA, is part of Java EE.

In what follows, we'll describe what Marvin brings in each of the three layers of its architecture.

The presentation tier

Marvin uses JSF and, therefore, its Eclipse project has the JSF Facet configured. If you right-click on the project and open its properties, under Project Facets you'll see JavaServer Faces is checked. Moving on to Project Facets > Java Server Faces, you'll see that we chose to use the JSF implementation provided by the target runtime, i.e., WildFly.

The JSF configuration also maps any request that ends in .faces to JSF. Eclipse, unfortunately, doesn't show that anywhere, unless we perform a trick: under Project Facets, uncheck JavaServer Faces and click Apply, then check JavaServer Faces back again so the Further configuration available... link shows up. Clicking on that link will allow you to configure the URL Mapping Patterns. You can also change that directly in WebContent/WEB-INF/web.xml, in the part that is shown below:

  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
  </servlet-mapping>

The web.xml file

If you take this opportunity to explore Marvin's web.xml, you'll see that it comes with a bunch of other configuration items. Here's what each of them does:

  • javax.faces.PROJECT_STAGE = Development: when under development, error messages are more informative, but at the cost of performance. When you're ready to put your WIS under production, change it to Production to emphasize performance and have some of those error messages turned off. See this blog;

  • javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL = true: does exactly what is says, sending null values to String parameters when their respective form fields are empty. This is required for Bean Validation to enforce the @NotNull validation for Strings;

  • javax.faces.DATETIMECONVERTER_DEFAULT_TIMEZONE_IS_SYSTEM_TIMEZONE = true: this changes the default time zone of JSF date/time converters from UTC to the time zone of the machine that runs the application server. In practice, this solves a bug that adjusts all date/time values you send to such converters to UTC before setting them at their destination objects, which depending on where you are in the World might not be a good thing! For instance, in Brazil the converters subtract 3 hours and all dates end up being saved as the day before (for when you want just the date, the time part is sent as 00:00);

  • The org.primefaces.examples.filter.CharacterEncodingFilter filter: this is a Servlet filter that forces Unicode (UTF-8) character encoding to all requests. Implemented by PrimeFaces development team and available at GitHub under the Apache License, Version 2.0, it was copied as such to JButler;

  • com.sun.faces.allowTextChildren = true: this configuration allows children of <h:inputText /> and <h:outputText /> to be rendered in JSF 2.0. It's required by PrimeFaces 2.x. Checking if this is still required is on our TO-DO list;

  • primefaces.THEME = bootstrap: this selects one of PrimeFaces' community themes, namely the bootstrap theme, which was also defined as a dependency in our pom.xml file (so if you want to switch themes, remember to change also the Maven dependency);

  • <session-timeout>30</session-timeout>: this sets the timeout for HTTP sessions for 30 minutes;

  • <welcome-file-list>: come on, you should know this one!

  • Error page for javax.faces.application.ViewExpiredException: defines which Web page to render when a ViewExpiredException is thrown, i.e., when you're sending a postback request to the server and the associated view is no longer there. See this for a lot more information on this issue;

  • Error page for javax.ejb.EJBAccessException: defines which Web page to render when a EJBAccessException is thrown, i.e., when a client access to a business method was denied;

  • Error page for javax.ejb.EJBException: defines which Web page to render when a EJBException is thrown, i.e., when the invoked business method or callback method could not be completed because of an unexpected error (e.g. the instance failed to open a database connection);

  • Error page for javax.faces.FacesException: defines which Web page to render when a FacesException is thrown, i.e., when something went wrong while JSF was processing the request.

Facelets

Marvin uses the AdminLTE HTML 5, responsive template, applying it to every page using a Facelets decorator. The decorator can be found at WebContent/resources/default/decorator.xhtml and is the result of simplifying AdminLTE and adapting it to use with JSF/Facelets. Explaining it would make this part of the tutorial even longer than it already is (sorry!).

If you open WebContent/index.xhtml you'll see how the decorator is applied. A web pages is a <ui:composition />, defining four sections: head, title, description and body. <ui:insert /> tags at the decorator show where each of these sections go.

You'll notice that the resources folder has a single subfolder called default. This subfolder represents the default decorator. One can create other decorators for the same WIS and even change them at the request of the user or using some other logic. Inside the resources/default folder you'll find:

  • images: images of the default template;
  • stylesheets: CSS style sheets of the default template;
  • decorator.xhtml: the decorator itself;
  • form*.xhtml: form and form field decorations, mini-templates done in Facelets to reuse code that is useful in HTML forms (e.g., validation).

You can see an example of use of the latter in the Install System feature we used in the previous step of the tutorial. If you open the file WebContent/core/installSystem/index.xhtml, you'll see the following code starting at line 26:

<!-- The form to be filled with information on the administrator. -->
<ui:decorate template="/resources/#{coreInfo.decorator}/form.xhtml">
    <ui:param name="formName" value="form" /> 
    <ui:param name="formLabel" value="#{msgsCore['installSystem.form.admin']}" />
    <p:focus />

    <ui:decorate template="/resources/#{coreInfo.decorator}/formfield.xhtml">
        <ui:param name="fieldName" value="name" />
        <ui:param name="label" value="#{msgsCore['installSystem.field.admin.name']}" />
        <p:inputText id="name" value="#{installSystemController.admin.name}" required="true" pt:placeholder="#{msgsCore['installSystem.field.admin.name.placeholder']}">
            <f:ajax event="blur" render="nameField shortNameField" listener="#{installSystemController.suggestShortName}" />
        </p:inputText>
    </ui:decorate>

The <ui:decorate /> tags specify which mini-template is to be applied. There's one for the entire form and one for each field (we show above only the first field, called name). For each <ui:decorate />, you can specify parameters (<ui:param />) and sections (<ui:define />, not currently used), the latter working just like the main decorator. Anything inside the <ui:decorate /> tag that is not a parameter nor a section is the main section to be inserted.

The mini-template has the generic code that is used in every form/field, whereas in the page you only have to specify that which is specific for that form/field. For instance, this is the main contents of the form.xhtml mini-template:

<div class="box box-solid box-info">
    <div class="box-header with-border">
        <h3 class="box-title">
            <h:outputText value="#{formLabel}" />
        </h3>
    </div>
    <div class="box-body">
        <f:validateBean>
            <h:form id="#{formName}" pt:class="form-horizontal">
                <ui:insert />
            </h:form>
        </f:validateBean>
    </div>
</div>

This means that every form will be validated (<f:validateBean />) and will use a layout inspired by the bootstrap front-end framework. No need to repeat this code in every form. The <ui:insert /> tag without attributes indicates where to insert the main section when applying the mini-template. Something similar takes place in formfield.xhtml, but enough about this...

A final remark, since we mentioned the WebContent/core/installSystem/ folder, across the system, features are divided in subsystems and use cases. Inside WebContent/core/installSystem/, then, lie the web pages that belong to the Install System use case from the Core subsystem.

Internationalization (i18n)

Marvin is prepared for internationalization (i18n), meaning that all strings are externalized and could be translated to other languages in order to give users the possibility of switching.

Two files are provided in the Java Resources/src/ folder under package br.ufes.inf.nemo.marvin: faces.properties and messages.properties. The former contains messages displayed by JSF in various ocasions such as, e.g., conversion and validation. This file is useful in case you want to customize some of these standard messages or if you want to translate them as part of your application's i18n effort.

The latter contains messages used by JButler, e.g., in its CRUD framework, and also global Marvin messages. Note that messages that are specific to a subsystem should be placed under that subsystem's view package, e.g., Java Resources/src/br/ufes/inf/nemo/marvin/core/view/messages.properties (again, we specify packages hierarchically: Organization (Nemo) > WIS (Marvin) > Subsystem (Core) > Architectural Package (View)).

These resource bundles (name given in Java to .properties files containing i18n strings) are then given to JSF via a configuration in WebContent/WEB-INF/faces-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
	version="2.2">

	<application>
		<!-- Defines the resource bundle that contains the standard JSF messages (overriding the ones provided). -->
		<message-bundle>br.ufes.inf.nemo.marvin.faces</message-bundle>

		<!-- Loads resource bundles for i18n messages and assigns names to them. -->
		<resource-bundle>
			<base-name>br.ufes.inf.nemo.marvin.messages</base-name>
			<var>msgs</var>
		</resource-bundle>
		<resource-bundle>
			<base-name>br.ufes.inf.nemo.marvin.core.view.messages</base-name>
			<var>msgsCore</var>
		</resource-bundle>

		<!-- Defines the default locale. -->
		<locale-config>
			<default-locale>en-US</default-locale>
		</locale-config>
	</application>

</faces-config>

Under <application />, the <message-bundle /> tag specifies the fully qualified name (FQN) of the JSF messages bundle, whereas the Marvin ones are specified using <resource-bundle />: msgs and msgsCore, the latter belonging to the Core subsystem.

The business tier

Marvin's Core subsystem has classes in this tier, divided in two packages: application and domain.

The domain package contains the entities that are registered during the Install System and Log in use cases: the Academic which is the administrator of the website and the MarvinConfiguration object that holds the institution acronym (the only thing configured so far). Each domain class is properly annotated for Object/Relational Mapping with JPA and has their respective JPA static metamodels (in a different source folder called src-metamodel and generated automatically by Eclipse) so we can use the Criteria API.

Domain classes already benefit from JButler's code by extending the PersistentObjectSupport mapped superclass. We will discuss JButler's features, however, in the next step of the tutorial.

The application package has a CoreInformation bean that holds package-wide information (provides the name of the current decorator, checks if the system is installed, etc.) and, following the program to an interface, not an implementation principle, one pair of stateless session EJB interface/implementation for each use case: InstallSystemService and SessionInformation (the latter is for the Log In use case). The Bean prefix is used in the implementation classes. In these classes you can see that CDI was used to inject DAOs from the data access tier where they are needed.

The data access tier

The base project comes with the JPA facet and persistence.xml configured. Open JPA Content/persistence.xml and switch to the Source tab of the editor to see the following contents:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
	<persistence-unit name="Marvin">
		<provider>org.hibernate.ejb.HibernatePersistence</provider>
		<jta-data-source>java:jboss/datasources/Marvin</jta-data-source>
		<class>br.ufes.inf.nemo.jbutler.ejb.persistence.PersistentObjectSupport</class>
		<exclude-unlisted-classes>false</exclude-unlisted-classes>
		<properties>
			<property name="hibernate.hbm2ddl.auto" value="update" />
		</properties>
	</persistence-unit>
</persistence>

Deploying on WildFly, we will use Hibernate as JPA implementation, since it already comes with the application server. We provide the name of the JTA data source we configured in the previous step and, at the end, we configure Hibernate to create database tables automatically when needed.

The <class> and <exclude-unlisted-classes> are also in the file because of an Eclipse bug that fails to recognize persistence mappings inherited from mapped superclasses that are located in JARs listed as dependencies of the project. By adding the mapped superclass to the list of persistent classes (in case of JButler, it's PersistentObjectSupport), Eclipse stops behaving strangely. Since we add a persistent class to the configuration, we also tell JPA not to consider only this class, but search for other JPA entities in the code.

Marvin's data access tier for the Core subsystem is basically the persistence package, which contains a pair of stateless session EJB interface/implementation for each domain class that needs a DAO (in our case, both Academic and MarvinConfiguration). The JPA infix (between the name of the domain class and the DAO suffix) indicates the implementation.

JButler also offers code for this tier, as each DAO interface extends BaseDAO and each implementation implements BaseJPADAO. We will discuss JButler's features, however, in the next step of the tutorial.

The people package

The base project comes with Marvin's br.ufes.inf.nemo.marvin.people package, which contains some useful domain classes (mapped superclasses) useful for representing people (and their phone numbers), plus their respective static metamodels (in the same way as Marvin's Core subsystem) and DAOs. They were given their own package because subsystems other than Core might have use for them.

Maven's pom.xml

Last, but not least, JButler's base project comes with Maven's pom.xml properly configured to:

  • Look for dependencies also in PrimeFaces' and Nemo's repositories;

  • Use Java 8 (sorry if this is a problem for you, I'm a bleeding edge kind of guy);

  • Retrieve JButler, PrimeFaces' bootstrap theme and their transitive dependencies automatically (we saw that in the previous step of the tutorial).


Now that you understand how JButler's base project (i.e., Marvin) is organized, we can go back to talking about JButler itself and its CRUD framework, next.


In this step we develop a simple CRUD (Create, Retrieve, Update and Delete) functionality in the Marvin project using JButler's CRUD mini-framework. The feature to be developed is the Manage Academics use case, which allows the administrator to create, retrieve, update and delete academics from the system.

The feature will be developed following the architecture described in the previous step of this tutorial. Before we begin, however, it's important to explain how JButler is dependant on the previously described architecture. If you want to have less trouble, you should follow these guidelines:

  1. Your packages should follow this naming convention: (organization).(system).(subsystem).(module). For instance, br.ufes.inf.nemo.marvin.core.controller refers to the controller's module of the core subsystem of marvin, from the organization nemo.inf.ufes.br;
  2. Your controller modules should be called controller (e.g. br.ufes.inf.nemo.marvin.core.controller, br.ufes.inf.nemo.marvin.research.controller, etc.);
  3. Your controller classes' names should end with Controller (e.g., ManageAcademicsController, ManageCoursesController, etc.);
  4. Web pages for CRUD features should be called exactly list.xhtml and form.xhtml and be placed in a standard directory structure following this convention: (module)/(controller-base-name), where (controller-base-name) is the name of the controller class, without the Controller suffix and with first letter lower case. For instance, the web pages (list.xhtml and form.xhtml) for br.ufes.inf.nemo.marvin.core.controller.ManageAcademicsController should be in WebContent/core/manageAcademics/, whereas those of br.ufes.inf.nemo.marvin.sae.controller.ManageSeminarsController should be in WebContent/sae/manageSeminars;
  5. Your i18n resource bundle should be registered in WebContent/WEB-INF/faces-config.xml under a name following this convention: msgs(Subsystem), where (Subsystem) is the name of the subsystem with the first letter capitalized. For instance, msgsCore is the bundle name for the core subsystem and msgsResearch is the bundle name for the research subsystem;
  6. Keys in your i18n resource bundle should be prefixed with (controller-base-name) (as above with the folder for the web pages). For instance, i18n messages for ManageAcademicsController should be prefixed with manageAcademics., whereas those of ManageSeminarsController should be prefixed with manageSeminars..

If you don't want to observe so many rules, you will have to override some methods in your controller classes, as they expect things to follow the above rules. This is what you need to override (you can do it for each controller or create a new superclass with your own conventions):

  • JSFController::getBundleName(): determines the name of the i18n resource bundle, based on rules #1, #2 and #5;
  • JSFController::getBundlePrefix(): determines the prefix for the keys in the i18n resource bundle, based on rules #3 and #6;
  • ListingController::getViewPath(): determines the folder in which the list.xhtml and form.xhtml Web pages can be found, based on rules #1, #2 and #4 (unfortunately, the names of the Web pages cannot be customized yet -- this is in our TO-DO list).

That being said, let's begin. As before, we describe each layer of the architecture separately, but this time we begin with the data access and move towards the presentation.

The data access tier

For the Manage Academics CRUD, we need the persistence package to provide a DAO for Academic objects (the latter belonging to the business tier). JButler offers a base interface and class to create such a DAO, which has already been created at the base project. Here's its interface:

package br.ufes.inf.nemo.marvin.core.persistence;

import javax.ejb.Local;

import br.ufes.inf.nemo.jbutler.ejb.persistence.BaseDAO;
import br.ufes.inf.nemo.jbutler.ejb.persistence.exceptions.MultiplePersistentObjectsFoundException;
import br.ufes.inf.nemo.jbutler.ejb.persistence.exceptions.PersistentObjectNotFoundException;
import br.ufes.inf.nemo.marvin.core.domain.Academic;

@Local
public interface AcademicDAO extends BaseDAO<Academic> {
	Academic retrieveByEmail(String email) throws PersistentObjectNotFoundException, MultiplePersistentObjectsFoundException;
}

The @Local EJB interface extends JButler's BaseDAO specifying the Academic class as the generic type. This would be enough for a CRUD, but this particular DAO is also used in the Log in feature, which needs to retrieve academics given their e-mail addresses. The generic BaseDAO interface specifies the following methods:

  • long retrieveCount(): returns how many instances of the @Entity class are found in the database;
  • long retrieveFilteredCount(Filter<?> filter, String value): returns how many instances of the Entity class that match the specified filter (with the given value) are found in the database;
  • List<T> retrieveAll(): returns all the instances of the Entity class from the database;
  • List<T> retrieveWithFilter(Filter<?> filter, String value): returns all the instances of the Entity class that match the specified filter (with the given value) from the database;
  • List<T> retrieveSome(int[] interval): same as retrieveAll(), but with pagination (returns instances from interval[0] up to interval[1]);
  • List<T> retrieveSomeWithFilter(Filter<?> filter, String value, int[] interval): same as retrieveWithFilter(), but with pagination;
  • T retrieveById(Long id): returns the instance of the Entity class that has the given ID;
  • T retrieveByUuid(String uuid): returns the instance of the Entity class that has the given UUID;
  • void save(T object): saves the instance in the database;
  • void delete(T object): deletes the instance from the database;
  • T merge(T object): merges the instance with the current entity manager (c.f. the life-cycle of a persistent object);
  • T refresh(T object): refreshes the instance with the current entity manager (c.f. the life-cycle of a persistent object).

Don't worry that there are a lot of methods, they are all implemented by BaseJPADAO, so you're not inheriting the obligation to write them all, but instead you inherit all of them already implemented! Other than retrieveByEmail(), the AcademicJPADAO implementation needs to provide only a single method needed by the base DAO implementation, as we can see below:

package br.ufes.inf.nemo.marvin.core.persistence;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

import br.ufes.inf.nemo.jbutler.ejb.persistence.BaseJPADAO;
import br.ufes.inf.nemo.jbutler.ejb.persistence.exceptions.MultiplePersistentObjectsFoundException;
import br.ufes.inf.nemo.jbutler.ejb.persistence.exceptions.PersistentObjectNotFoundException;
import br.ufes.inf.nemo.marvin.core.domain.Academic;
import br.ufes.inf.nemo.marvin.core.domain.Academic_;

@Stateless
public class AcademicJPADAO extends BaseJPADAO<Academic> implements AcademicDAO {
	/** Serialization id. */
	private static final long serialVersionUID = 1L;

	/** The logger. */
	private static final Logger logger = Logger.getLogger(AcademicJPADAO.class.getCanonicalName());

	/** The application's persistent context provided by the application server. */
	@PersistenceContext
	private EntityManager entityManager;

	/** @see br.ufes.inf.nemo.util.ejb3.persistence.BaseJPADAO#getEntityManager() */
	@Override
	protected EntityManager getEntityManager() {
		return entityManager;
	}

	/** @see br.ufes.inf.nemo.marvin.core.persistence.AcademicDAO#retrieveByEmail(java.lang.String) */
	@Override
	public Academic retrieveByEmail(String email) throws PersistentObjectNotFoundException, MultiplePersistentObjectsFoundException {
		logger.log(Level.FINE, "Retrieving the academic whose e-mail is \"{0}\"...", email);

		// Constructs the query over the Academic class.
		CriteriaBuilder cb = entityManager.getCriteriaBuilder();
		CriteriaQuery<Academic> cq = cb.createQuery(Academic.class);
		Root<Academic> root = cq.from(Academic.class);

		// Filters the query with the email.
		cq.where(cb.equal(root.get(Academic_.email), email));
		Academic result = executeSingleResultQuery(cq, email);
		logger.log(Level.INFO, "Retrieve academic by the email \"{0}\" returned \"{1}\"", new Object[] { email, result });
		return result;
	}
}

AcademicJPADAO extends BaseJPADAO<Academic>, inheriting all the good stuff, already adapted to Academic (the @Entity) as the choice for the generic type. The method that needs to be implemented is getEntityManager(), which should return the entity manager, injected into AcademicJPADAO by CDI via @PersistenceContext.

In summary, we provided a DAO (divided in interface and implementation) that inherits all basic DAO features (save, retrieve, delete, etc.) from the CRUD mini-framework. The DAO class asks JPA for the persistence context, receiving automagically from CDI an entity manager object capable of communicating with the database through object/relational mapping (i.e. Hibernate). Being a stateless EJB, this class can be injected by CDI wherever it's needed.

The business tier

At this tier, concerning the Manage Academics CRUD, we must have the domain class that represents academics (i.e., the Academic class) at the domain package and the service class at the application package. The former is already provided by the base project for the same reason the DAO was: to implement the Install System and Log In features that come with it. The latter will be implemented at this step of the tutorial.

The domain part of the CRUD

The Academic class extends the Person class from the people package described in the previous step of this tutorial. Bottom line, the Academic class has the following attributes we should consider when implementing the CRUD feature: String name, Date birthDate, String shortName and String email. Some attributes will not be handled:

  • String password: will be left null, the password should be set by the academic herself, not the administrator;

  • Set<Telephone> telephones: will not be handled in this step of the tutorial;

  • Date creationDate, Date lastUpdateDate and Date lastLoginDate: will be set automatically by the system.

As said before, the Academic class extends the Person class. This class, in its turn, extends JButler's PersistentObjectSupport, which is the domain part of the CRUD mini-framework. This part of the framework is actually divided in two classes, each of which providing also an interface:

  • PersistentObjectSupport (PersistentObject interface): provides JPA @Id and @Version attributes so each domain entity has an artificial ID and implements optimistic locking. This class extends DomainObjectSupport;

  • DomainObjectSupport (DomainObject interface): provides an unique universal identifier (UUID) based on java.util.UUID and implements equals() and hashCode() based on it.

The interfaces are provided in case the domain entities cannot extend a class from JButler but the developer still wants to use its CRUD mini-framework.

The application part of the CRUD

The application package contribution for this CRUD is the service that implements the CRUD scenarios that compose the use case. As with the DAOs, service classes are also separated in interface and implementation. As opposed to DAOs, they are not already implemented in the base project, so we begin coding new classes now, finally.

Right-click Java Resources/src/br.ufes.inf.nemo.marvin.core.application, choose New > Interface and fill in:

  • Name: ManageAcademicsService;
  • In Extended interfaces, click Add... and add br.ufes.inf.nemo.jbutler.ejb.application.CrudService<br.ufes.inf.nemo.marvin.core.domain.Academic>.

The service interface should also be a @Local EJB. For the implementation, right-click Java Resources/src/br.ufes.inf.nemo.marvin.core.application, select New > Class and fill in:

  • Name: ManageAcademicsServiceBean;
  • Superclass: br.ufes.inf.nemo.jbutler.ejb.application.CrudServiceBean<br.ufes.inf.nemo.marvin.core.domain.Academic>;
  • In Extended interfaces, click Add... and add br.ufes.inf.nemo.marvin.core.application.ManageAcademicsService.

Then, there are three simple steps to (almost) finish:

  1. Mark the class as @Stateless (like the DAO) and @PermitAll (because we are not yet implementing authentication and authorization);
  2. Add the annotated attribute @EJB private AcademicDAO academicDAO; (notice we refer to the DAO's interface and ask CDI to automatically inject it using the javax.ejb.EJB annotation); and
  3. Have the getDAO() method return the academicDAO attribute.

In summary, this creates a basic CRUD service, without any validation. You can add validation later in order to, for instance, prevent the users from creating two academics with the same e-mail address. The service inherits methods from the CRUD mini-framework which implement the basic CRUD use case scenarios (the CRUD acronym plus listing). The service implementation class asks CDI to inject the DAO and uses its methods to operationalize the scenarios.

The above steps would be enough for a CRUD. However, as mentioned before, Academic inherits some date properties (creation date, last update date) which should be set by the system. If you don't, by the way, it won't work, because they are set as @NotNull using Bean Validation. We can insert this code by overriding the validate() method inherited from JButler's superclass (CrudServiceBean), as below:

@Override
protected Academic validate(Academic newEntity, Academic oldEntity) {
	// New academics must have their creation date set.
	Date now = new Date(System.currentTimeMillis());
	if (oldEntity == null) newEntity.setCreationDate(now);

	// All academics have their last update date set when persisted.
	newEntity.setLastUpdateDate(now);

	return newEntity;
}

The validate() method is called every time an object is persisted by JButler. In case of updates, the oldEntity refers to the entity retrieved from the database before the changes. This parameter is null when persisting new objects.

The presentation tier

At this tier, concerning the Manage Academics CRUD, we must have the controller and the necessary web pages, respectively at the controller and view packages.

The controller part of the CRUD

The controller package -- the Java part of the presentation tier -- contributes with a single class to the CRUD (no need to split between interface and implementation, as the controller is used by the Web pages via EL, not injected by CDI as the service and the DAO are). As before, JButler has a base class to help. Right-click Java Resources/src/br.ufes.inf.nemo.marvin.core.controller and select New > Class. Then fill in:

  • Name = ManageAcademicsController;
  • Superclass = br.ufes.inf.nemo.jbutler.ejb.controller.CrudController<br.ufes.inf.nemo.marvin.core.domain.Academic>.

Annotate the class with @Named (from javax.inject) and @SessionScoped (from javax.enterprise.context). Here, make sure to pay close attention and not use javax.faces.bean.SessionScoped! Although we are writing a JSF bean, since we have used CDI elsewhere, we have to always use CDI annotations (i.e., javax.enterprise.context ones) instead of the JSF ones. Yes, it's weird and CDI could have been developed in a way to consider JSF annotations, but it wasn't so trust me on this.

We finish our class with three more steps:

  1. Add the annotated attribute @EJB private ManageAcademicsService manageAcademicsService; so the service class is injected at the controller by CDI;
  2. Have the getCrudService() method return the manageAcademicsService attribute.
  3. Add the following line of code to the initFilters() method to create a simple filter by academic name:
addFilter(new LikeFilter("manageAcademics.filter.byName", "name", getI18nMessage("msgsCore", "manageAcademics.text.filter.byName")));

The getI18nMessage() method inherited from JButler retrieves text from a resource bundle registered in JSF. The msgsCore resource bundle is already registered in the base project. Don't worry about the manageAcademics.text.filter.byName key right now, we will add this and other messages to the msgsCore bundle when we deal with the view part.

This concludes the controller package and the Java part of the CRUD. In summary, we created a CRUD controller inheriting all basic features from the CRUD mini-framework, including methods to display existing objects in a paginated list, switching to a form to create new objects, or to see/update object details and to delete existing objects from the list. Like with the application before, CDI also plays a part here, injecting the service class so the controller can fulfill its role as the mediator between the Web pages and the application's functionality.

We can now finally provide the view.

The view part of the CRUD

In the last (or first?) package of our architecture, we leave the Java Resources part of our Eclipse project and start working with the WebContent section. For the CRUD, we have basically two pages: the listing and the form. The former shows a list with the existing objects and allows the user to delete some of them, whereas the latter contains form fields to display an entity's attributes or to create/update entities.

By now you must have noticed how CRUD functionality is repetitive and even the things that are specific for our domain (the Academic class, for instance) could have been inserted as a parameter to a code generation tool (which doesn't exist, by the way, and you're welcome to create one and share!) which would do most of the things we have done so far.

So both the listing and the form pages have pre-written templates that allow you to quickly write them, even if they have dozens of lines of code. They are located in the jbutler-templates folder directly under the project and are called crud-form.xhtml and crud-list.xhtml. So, right-click the WebContent/core folder of your project and select New > Folder to create a folder named manageAcademics. Then copy the template files to this new folder and rename them form.xhtml and list.xhtml.

In these files, some variables in EL expressions have been used to represent the domain-specific aspects of the CRUD. For example:

  • #{msgs['manage${Entities}.title']}: title of the CRUD page;
  • #{manage${Entities}Controller}: the controller class;
  • #{entity.${attribute1}}: one of the attributes of the managed entity.

The ${} notation is not part of EL, though. It's just a way to make it easier for us to replace them with their proper names. Given that our controller is called ManageAcademicsController and that the academic attributes to show in the listing are name and email, to start customizing these generic templates to our "Manage Academics" CRUD pages you should use Eclipse's Edit > Find/Replace... feature, check the options Case sensitive and Wrap search and perform the following replacements in both form.xhtml and list.xhtml:

  • ${Entities} -> Academics;
  • ${attribute1} -> name;
  • ${attribute2} -> email.

This sets up the listing page to show a table with the existing academics, showing their names and e-mails. Feel free to add/remove columns to show/hide attributes depending on your case. Just copy the <p:column /> code and change it accordingly. It also sets up the form page to show a single field, for the name attribute. We thus need to replicate (and adjust) it for the other attributes: Date birthDate, String shortName and String email.

To make that task easier, I suggest you copy the <ui:decorate /> tag from the JButler crud-form.xhtml template that renders the ${attribute1} field and perform the Find/Replace... again, for each attribute of the Academic class. Things to consider after you do this:

  • The <p:inputText /> tag comes with the pt:placeholder attribute, which uses JSF passthrough to set an HTML5 placeholder attribute (like a tooltip, but better) in the input field. It should contain an explanation of the field, so if you think the label is enough to explain what a given field means, just delete the pt:placeholder attribute;

  • That same tag in the template contains the required="true" attribute and you should remove it from the fields that are optional (not requried);

  • For the birth date field, we should provide a date/time converter specifying we're interested only in the date part and which format is expected (e.g., dd/MM/yyyy). Moreover, we could use a masked input <p:inputMask /> instead for an enhanced user experience:

<ui:decorate template="/resources/#{coreInfo.decorator}/formfield.xhtml">
	<ui:param name="fieldName" value="birthDate" />
	<ui:param name="label" value="#{msgsCore['manageAcademics.form.birthDate']}" />
	<p:inputMask id="birthDate" mask="#{msgs['jbutler.format.date.primefaces']}" value="#{manageAcademicsController.selectedEntity.birthDate}" disabled="#{manageAcademicsController.readOnly}">
		<p:ajax event="blur" update="birthDateField" />
		<f:convertDateTime type="date" pattern="#{msgs['jbutler.format.date.java']}" />
	</p:inputMask>
	<h:outputText value=" (#{msgs['jbutler.format.date.label']})" />
</ui:decorate>

Finally, we need to provide the strings for the internationalized messages from our XHTML pages. Note that the pages refer to two message bundles in the various expression language terms: #{msgs['']} and #{msgsCore['']}. The former refers to br/ufes/inf/nemo/marvin/messages.properties (global messages for Marvin) file, whereas the latter points to br/ufes/inf/nemo/marvin/core/view/messages.properties (messages for the CORE package, where Install System and Log In use cases were already implemented). This configuration is present in WebContent/WEB-INF/faces-config.xml and was brought from the base project. By the way, if you're not implementing a feature from the core subsystem, you should create a new bundle and replace msgsCore with its name.

Back to our CRUD (which is in the core subsystem of Marvin), the references to the global messages are already OK. What we need to provide are the messages for our CRUD in br/ufes/inf/nemo/marvin/core/view/messages.properties. As before, JButler provides a template, in jbutler-templates/crud-messages.properties. Copy it to the core message bundle and perform the replacements, again using Eclipse's Edit > Find/Replace... feature with the options Case sensitive and Wrap search:

  • ${Entities} -> Academics;
  • ${entities} -> academics;
  • ${Entity} -> Academic;
  • ${entity} -> academic;
  • ${Attribute1} -> Name;
  • ${attribute1} -> name.

Finally, and again, replicate the name keys to birthDate, shortName and email, changing the text value accordingly, plus providing placeholder text for the fields that you judge necessary (or, as said before, remove the placeholder attribute from the field).

Try it out!

Deploy the application and open http://localhost:8080/Marvin/core/manageAcademics/list.faces to try it out. Later, you can add a link to the system menu.

The CRUD feature should look like the figures below:

Listing of existing semesters, as a result of step 3 of the tutorial.

Form to view, add and edit semesters, as a result of step 3 of the tutorial.


This is as far as the tutorial goes currently, but there's more to JButler. Hopefully in the future more steps of the tutorial are written, showing advanced CRUD filters, CRUD validation, the object converter, etc.

The source code of the Marvin project used in this tutorial can be found here, in JButler's repository.