Implementing
Information Management Concepts and TechniquesWarning: This tutorial may still contain errors and may still be incomplete in certain respects. Please report any issue to Gerd Wagner at G[email protected] or Mircea Diaconescu at [email protected].
This tutorial is also available in the following formats: PDF. See also the project page, or run the example app from our server, or download it as a ZIP archive file.
Copyright © 2015-2018 Gerd Wagner, Mircea Diaconescu
This tutorial article, along with any associated source code, is licensed under The Code Project Open License (CPOL), implying that the associated code is provided "as-is", can be modified to create derivative works, can be redistributed, and can be used in commercial applications, but the article must not be distributed or republished without the authors' consent.
2018-06-06
Revision History | ||
---|---|---|
Revision 0.3 | 20150724 | gw |
improve entity class model, use both simple enums and code list enums | ||
Revision 0.2 | 20150630 | gw |
various revisions | ||
Revision 0.1 | 20150529 | md |
create first version |
Table of Contents
List of Figures
select
element with no
selected optionBook
Book
List of Tables
This tutorial is Part 3 of our series of six tutorials about model-based development of back-end web applications with Java, using JPA and JSF. It shows how to build a simple web app where model classes have enumeration attributes that need to be rendered in the user interface in the form of selection lists and other choice widgets.
A distributed web app is composed of at least two parts: a front-end part, which, at least, renders the user interface (UI) pages, and a back-end part, which, at least, takes care of persistent data storage. A back-end web app is a distributed web app where essentially all work is performed by the back-end component, including data validation and UI page creation, while the front-end only consists of a web browser's rendering of HTML-forms-based UI pages. Normally, a distributed web app can be accessed by multiple users, possibly at the same time, over HTTP connections.
In the case of a Java/JPA/JSF back-end app, the back-end part of the app can be executed by a server machine that runs a web server supporting the Java EE specifications Java Servlets, Java Expression Language (EL), JPA and JSF, such as the open source server Tomcat/TomEE.
The app supports the four standard data management operations (Create/Read/Update/Delete). The other parts of the tutorial are:
Part 1: Building a minimal app.
Part 2: Handling constraint validation.
Part 4: Managing unidirectional associations, such as the associations between books and publishers, assigning a publisher to a book, and between books and authors, assigning authors to a book.
Part 5: Managing bidirectional associations, such as the associations between books and publishers and between books and authors, also assigning books to authors and to publishers.
Part 6: Handling subtype (inheritance) relationships between object types.
Table of Contents
In all application domains, there are string-valued attributes with
a fixed list of possible string values. These attributes are called
enumeration attributes, and the fixed
value lists defining their possible string values are called enumerations. For instance, when we have to
manage data about people, we often need to include information about their
gender. The possible values of a gender
attribute may be
restricted to one of the enumeration labels "male","female" and
"undetermined", or to one of the enumeration codes "M", "F" and "U".
Whenever we deal with codes, we also need to have their corresponding
labels, at least in a legend explaining the meaning of each code.
Instead of using the enumeration string values as the internal
values of an enumeration attribute, it is preferable to use a simplified
internal representation for them, such as the positive integers 1, 2, 3,
etc., which enumerate the possible values. However, since these integers
do not reveal their meaning (which is indicated by the enumeration label)
in program code, for readability we rather use special constants, called
enumeration
literals, such as MALE
or
M
, prefixed by the name of the enumeration like in
this.gender = GenderEL.MALE
. Notice that we follow the
convention that the names of enumeration literals are written all upper
case, and that we also use the convention to suffix the name of an
enumeration datatype with "EL" standing for "enumeration literal" (such
that we can recognize from the name GenderEL
that each
instance of this datatype is a "gender enumeration literal").
There are also enumerations having records as their instances, such that one of the record fields provides the name of the enumeration literals. An example of such an enumeration is the following list of units of measurement:
Table 1.1. Representing an enumeration of records as a table
Units of Measurement | ||
---|---|---|
Unit Symbol | Unit Name | Dimension |
m | meter | length |
kg | kilogram | mass |
g | gram | mass |
s | second | time |
ms | milisecond | time |
Notice that since both the "Unit Symbol" and the "Unit Name" fields are unique, either of them could be used for the name of the enumeration literals.
In summary, we can distinguish between the following three forms of enumerations:
simple enumerations define a list of self-explanatory enumeration labels;
code lists define a list of code/label pairs.
record enumerations consist of a list of records, so they are defined like classes with simple attributes defining the record fields.
These three forms of enumerations are discussed in more detail below.
Notice that, since enumerations are used as the range of enumeration attributes, they are considered to be datatypes.
Enumerations may have further features. For instance, we may want to be able to define a new enumeration by extending an existing enumeration. In programming languages and in other computational languages, enumerations are implemented with different features in different ways. See also the Wikipedia article on enumerations.
A simple enumeration defines a
fixed list of self-explanatory enumeration labels, like in the example
of a GenderEL
enumeration shown in the following UML class
diagram:
Since the labels of a simple enumeration are being used, in
capitalized form, as the names of the corresponding enumeration literals
(GenderEL.MALE
, GenderEL.FEMALE
, etc.), we may
also list the (all upper case) enumeration literals in the UML
enumeration datatype, instead of the corresponding (lower or mixed case)
enumeration labels.
A code list is an enumeration that defines a fixed list of code/label pairs. Unfortunately, the UML concept of an enumeration datatype does not support the distinction between codes as enumeration literals and their labels. For defining both codes and labels in a UML class diagram in the form of an enumeration datatype, we may use the attribute compartment of the data type rectangle and use the codes as attribute names defining the enumeration literals, and set their initial values to the corresponding label. This approach results in a visual representation as in the following diagram:
In the case of a code list, we can use both the codes or the
labels as the names of enumeration literals, but using the codes seems
preferable for brevity (GenderEL.M
,
GenderEL.F
, etc.). For displaying the value of an
enumeration attribute, it's an option to show not only the label, but
also the code, like "male (M)", provided that there is sufficient space.
If space is an issue, only the code can be shown.
A record enumeration defines a record type with a unique field designated to provide the enumeration literals, and a fixed list of records of that type. In general, a record type is defined by a set of field definitions (in the form of primitive datatype attributes), such that one of the unique fields is defined to be the enumeration literal field, and a set of operation definitions.
Unfortunately, record enumerations, as the most general form of an enumeration datatype, are not supported by the current version of UML (2.5) where the general form of an enumeration is defined as a special kind of datatype (with optional field and operation definitions) having an additional list of unique strings as enumeration literals (shown in a fourth compartment). The UML definition does neither allow designating one of the unique fields as the enumeration literal field, nor does it allow populating an enumeration with records.
Consequently, for showing a record enumeration in a UML class diagram, we need to find a workaround. For instance, if our modeling tool allows adding a drawing, we could draw a rectangle with four compartments, such that the first three of them correspond to the name, properties and operations compartments of a datatype rectangle, and the fourth one is a table with the names of properties/fields defined in the second compartment as column headers, as shown in the following figure.
UnitEL | ||
---|---|---|
«el» unitSymbol: String unitName: String dimension: String |
||
Unit Symbol | Unit Name | Dimension |
m | meter | length |
kg | kilogram | mass |
g | gram | mass |
s | second | time |
ms | millisecond | time |
There may be cases of enumerations that need to be extensible, that is, it must be possible to extend their list of enumeration values (labels or code/label pairs) by adding a new one. This can be expressed in a class diagram by appending an ellipsis to the list of enumeration values, as shown in Figure 1.1.
Since enumeration values are internally represented by enumeration literals, which are normally stored as plain positive integers in a database, a new enumeration value can only be added at the end of the value list such that it can be assigned a new index integer without re-assigning the indexes of other enumeration values. Otherwise, the mapping of enumeration indexes to corresponding enumeration values would not be preserved.
Alternatively, if new enumeration values have to be inserted in-between other enumeration values, and their indexes re-assigned, this implies that
enumeration indexes are plain sequence numbers and do no longer identify an enumeration value;
the value of an enumeration literal can no longer be an enumeration index, but rather has to be an identifying string: preferably the enumeration code in the case of a code list, or the enumeration label, otherwise.
.
An enumeration attribute is an attribute that has an enumeration as its range.
In the user interface, an output field for an enumeration attribute would display the enumeration label, rather than its internal value, the corresponding enumeration index.
For allowing user input to an enumeration attribute, we can use the UI concept of a
(drop-down) selection list, which may be implemented with an
HTML select
element, such that the enumeration labels would be used as the text
content of its option
elements, while the enumeration indexes would be used as their
values. We have to distinguish between single-valued and
multi-valued enumeration attributes. In the case of a
single-valued enumeration
attribute, we use a standard select
element. In the case of a multi-valued enumeration attribute, we use a
multi-select
element with the HTML attribute setting
multiple="multiple"
.
In the case of using a single-select
element for an optional enumeration
attribute, we need to include in its options an element like "---" for indicating that nothing
has been selected. Then, the UI page for the CRUD use case "Create" shows "---" as the initially
selected option.
For both cases, an example is shown in Figure 1.2. While the
single select
element for "Original language" shows the
initially selected option "---" denoting "nothing selected", the multiple
select
element "Other available languages" shows a small
window displaying four of the options that can be selected.
For usability, the multiple selection list can only be implemented
with an HTML select
element, if the number of enumeration
literals does not exceed a certain threshold (like 20), which depends on
the number of options the user can see on the screen without
scrolling.
For user input for a single-valued enumeration attribute, a
radio button
group can be used instead of a single selection
list, if the number of enumeration literals is sufficiently small (say,
not larger than 7). A radio button group is implemented with an HTML
fieldset
element acting as a container of labeled
input
elements of type "radio", all having the same name,
which is normally equal to the name of the represented enumeration
attribute.
For user input for a multi-valued enumeration attribute, a
checkbox
group can be used instead of a multiple selection
list, if the number of enumeration literals is sufficiently small (say,
not larger than 7). A checkbox group is implemented with an HTML
fieldset
element acting as a container of labeled
input
elements of type "checkbox", all having the same name,
which is normally equal to the name of the represented enumeration
attribute.
Defining enumerations is directly supported in information modeling languages (such as in UML Class Diagrams), in data schema languages (such as in XML Schema, but not in SQL), and in many programming languages (such as in C++ and Java, but not in JavaScript).
Unfortunately, standard SQL does not support enumerations. Some DBMS, such as MySQL and Postgres, provide their own extensions of SQL column definitions in the CREATE TABLE statement allowing to define enumeration-valued columns.
A MySQL
enumeration is specified as a list of enumeration labels with the
keyword ENUM
within a column definition, like
so:
CREATE TABLE people ( name VARCHAR(40), gender ENUM('MALE', 'FEMALE', 'UNDETERMINED') );
A Postgres enumeration is specified as a special user-defined type that can be used in column definitions:
CREATE TYPE GenderEL AS ENUM ('MALE', 'FEMALE', 'UNDETERMINED'); CREATE TABLE people ( name text, gender GenderEL )
In XML Schema, an enumeration datatype can be defined as a simple
type restricting the primitive type xs:string
in the
following way:
<xs:simpleType name="BookCategoryEL"> <xs:restriction base="xs:string"> <xs:enumeration value="NOVEL"/> <xs:enumeration value="BIOGRAPHY"/> <xs:enumeration value="TEXTBOOK"/> <xs:enumeration value="OTHER"/> </xs:restriction> </xs:simpleType>
In JavaScript, we can define an enumeration as a special JS object
having a property for each enumeration literal such that the property's
name is the enumeration literal's name (the enumeration label or code in
upper case) and its value is the corresponding enumeration index. One
approach for implementing this is using the
Object.defineProperties
method:
var BookCategoryEL = null; Object.defineProperties( BookCategoryEL, { NOVEL: {value: 1, writable: false}, BIOGRAPHY: {value: 2, writable: false}, TEXTBOOK: {value: 3, writable: false}, OTHER: {value: 4, writable: false}, MAX: {value: 4, writable: false}, labels: {value:["novel","biography","textbook","other"], writable: false} });
This definition allows using the enumeration literals
BookCategoryEL.NOVEL
, BookCategoryEL.BIOGRAPHY
etc., standing for the enumeration indexes 1, 2 , 3 and 4, in program
statements. Notice how this definition takes care of the requirement
that enumeration literals like BookCategoryEL.NOVEL
are
constants, the value of which cannot be changed during program
execution. This is achieved with the help of the property descriptor
writable: false
in the Object.defineProperties
statement.
We can also use a more generic approach and define a meta-class
Enumeration
for creating enumerations in the form of
special JS objects:
function Enumeration( enumLabels) { var i=0, LBL=""; this.MAX = enumLabels.length; this.labels = enumLabels; // generate the enum literals as capitalized keys/properties for (i=1; i <= enumLabels.length; i++) { LBL = enumLabels[i-1].toUpperCase(); this[LBL] = i; } // prevent any runtime change to the enumeration Object.freeze( this); };
Using this Enumeration
class allows to define
a new enumeration in the following way:
var BookCategoryEL = new Enumeration(["novel","biography","textbook","other"])
Having an enumeration like BookCategoryEL
, we can
then check if an enumeration attribute like category
has an
admissible value by testing if its value is not smaller than 1 and not
greater than BookCategoryEL.MAX
. Also, the label can be
retrieved in the following way:
formEl.category.value = BookCategoryEL.labels[this.category - 1];
As an example, we consider the following model class
Book
with the enumeration attribute
category
:
function Book( slots) { this.isbn = ""; // string this.title = ""; // string this.category = 0; // number (BookCategoryEL) if (arguments.length > 0) { this.setIsbn( slots.isbn); this.setTitle( slots.title); this.setCategory( slots.category); } };
For validating input values for the enumeration attribute
category
, we can use the following check function:
Book.checkCategory = function (c) { if (!c) { return new MandatoryValueConstraintViolation( "A category must be provided!"); } else if (!Number.isInteger(c) || c < 1 || c > BookCategoryEL.MAX) { return new RangeConstraintViolation( "The category must be a positive integer " + "not greater than "+ BookCategoryEL.MAX +" !"); } else { return new NoConstraintViolation(); } };
Notice how the range constraint defined by the enumeration
BookCategoryEL
is checked: it is tested if the input value
c
is a positive integer and if it is not greater than
BookCategoryEL.MAX
.
We again consider the simple data management problem that we have considered before. So, again, the purpose of our app is to manage information about books. But now we have four additional enumeration attributes, as shown in the UML class diagram in Figure 1.5 below:
the single-valued mandatory attribute
originalLanguage
with the enumeration datatype
LanguageEL
as its range,
the multi-valued optional attribute
otherAvailableLanguages
with range
LanguageEL
,
the single-valued mandatory attribute category
with range BookCategoryEL
the multi-valued mandatory attribute
publicationForms
with range
PublicationFormEL
Notice that the attributes otherAvailableLanguages
and
publicationForms
are multivalued, as indicated by their multiplicity
expressions [*] and [1..*]. This means that the possible values of these
attributes are sets of enumeration literals, such as the set {ePub, PDF},
which can be represented in JavaScript as a corresponding array list of
enumeration literals, [PublicationFormEL.EPUB,
PublicationFormEL.PDF]
.
The meaning of the design model and its enumeration attributes can be illustrated by a sample data population:
Table 1.2. Sample data for Book
ISBN | Title | Original language | Other languages | Category | Publication forms |
---|---|---|---|---|---|
0553345842 | The Mind's I | English (en) | de, es, fr | novel | paperback, ePub, PDF |
1463794762 | The Critique of Pure Reason | German (de) | de, es, fr, pt, ru | other | paperback, PDF |
1928565379 | The Critique of Practical Reason | German (de) | de, es, fr, pt, ru | other | paperback |
0465030793 | I Am A Strange Loop | English (en) | es | textbook | hardcover, ePub |
Table of Contents
In this chapter, we show how to build a back-end web application with enumeration attributes, using Java with JPA and JSF. In addition to the topic of enumeration attributes, we also show how to deal with multi-valued attributes.
Compared to the Validation App discussed in Part 2 (Validation App Tutorial), we now deal with the following new issues:
Enumeration datatypes have to be defined in a suitable way as part of the model code.
Enumeration attributes have to be defined in model classes and handled in the user interface with the help of suitable choice widgets.
In terms of coding, the new issues are:
In the model code we have to take care of
enumerations to be defined
in the form of Java enum
classes;
single-valued enumeration
attributes, like Book::originalLanguage
, requiring the
JPA annotation @Enumerated
for defining the conversion between Java
enumeration literals and corresponding database table column values;
multi-valued enumeration
attributes, like Book::publicationForms
, requiring the
JPA annotation @Convert
with suitable arguments defining a JPA custom
converter for (de-)serializing collections of Java enumeration literals;
extending the methods Book.create
, and
Book.update
such that they take care of the
enumeration attributes.
In the user interface code we have to take care of
adding new table columns in
retrieveAndListAll.xhtml
;
adding suitable choice widgets in
create.xhtml
and
upate.xhtml
;
rendering multi-valued enumeration attributes in the
table view of retrieveAndListAll.xhtml
and in the choice widgets of create.xhtml
and upate.xhtml
(with the help of an
array of JSF SelectItem
s, each consisting of an
enumeration value and its label).
Using the information design model shown in Figure 1.5 above as the starting point, we make an entity class model, essentially by adding getters/setters, constraint annotations and CRUD methods, as explained before:
Simple enumerations, like BookCategoryEL
and
PublicationFormEL
, are coded in the following way with the
help of Java's enum
construct:
public enum BookCategoryEL { NOVEL, BIOGRAPHY, TEXTBOOK, OTHER; }
Notice how the enumeration literals are defined with capitalized names in a comma-separated list.
For a code list, like LanguageEL
, where each enumeration instance consists
of a code and a label, we use the codes as enumeration literals and define an attribute
label
for storing the labels, as well as a private constructor that allows
creating enumeration instances consisting of a code and a label. This is possible because in
Java, an enum
is a special kind of class. Each entry in the list of enumeration
literals, auch as EN("English")
, represents an invocation of the
enum
's constructor:
public enum LanguageEL { EN( "English"), DE( "German"), FR( "French"), ES( "Spanish"); private final String label; private LanguageEL( String label) {this.label = label;} public String getLabel() {return this.label;} }
The entity class Book
is coded with the help of JPA
annotations for class attributes, as well as the setter and getter
methods of every class attribute.
@Entity @Table( name="books") @ViewScoped @ManagedBean( name="book") public class Book { @Id @Column( length=10) @NotNull( message="An ISBN value is required!") @Pattern( regexp="\\b\\d{9}(\\d|X)\\b", message = "...") private String isbn; @Column( nullable=false) @NotNull( message="A title is required!") private String title; @Column( nullable=false) @NotNull( message="An original language is required!") @Enumerated( EnumType.STRING) private LanguageEL originalLanguage; @Column( nullable=false) @NotNull( message="A category is required!") @Enumerated( EnumType.STRING) private BookCategoryEL category; @Convert( converter = pl.m.converter.OtherAvailableLanguagesConverter.class) private Set<LanguageEL> otherAvailableLanguages; @Column( nullable=false) @Size( min=1, message="At least one publication form is required!") @Convert( converter=pl.m.converter.PublicationFormsConverter.class) private Set<PublicationFormEL> publicationForms; ... }
For the case of single-valued enumeration attributes (like
originalLanguage
and category
), the JPA
annotation @Enumerated
is used for specifying the storage
serialization. It takes one of the following two parameter values:
EnumType.STRING
means that enumeration literals
are converted to strings when they are serialized ( e.g., in the
case of the originalLanguage
attribute of the
Book
class, the values saved in the database are one
of "EN", "DE", "FR" or "ES") ;
EnumType.ORDINAL
means that enumeration
literals are converted to their index integer when they are
serialized (i.e.,the values saved in the database are 1, 2 ,3
etc.).
We store the collection values of a multi-valued
enumeration attribute (like otherAvailableLanguages
and
publicationForms
) in a database table column as serialized
arrays in the form of ["value1", "value2", ...]
. In the
case of the originalLanguage
attribute, an example of a
saved value would be ["EN", "FR", "DE"]
. Achieving this
behavior is possible with JPA by using custom converters to map database
table column values to Java types and vice versa. The
@Convert
annotation allows specifying a Java converter that
is responsible for the mappings.
Also, in the case of a multi-valued enumeration attribute, we use
the @Size
annotation to specify the minimum, and if
required also the maximum, number of elements stored by this attribute.
For example, in the case of publicationForms
, we use
@Size( min=1, message = "At least one publication form is
required!")
, which enforces to have at least one value for this
attribute, otherwise the error message is displayed.
@Entity @Table( name="books")
@ViewScoped @ManagedBean( name="book")
public class Book {
...
public Book( String isbn, String title, Integer year,
LanguageEL originalLanguage,
Set<LanguageEL> otherAvailableLanguages,
BookCategoryEL category,
Set<PublicationFormEL> publicationForms) { ...}
public LanguageEL getOriginalLanguage() {...}
public void setOriginalLanguage(
LanguageEL originalLanguage) {...}
public Set<LanguageEL> getOtherAvailableLanguages() {...}
public void setOtherAvailableLanguages(
Set<LanguageEL> otherAvailableLanguages) {...}
public BookCategoryEL getCategory() {...}
public void setCategory( BookCategoryEL category) {...}
public Set<PublicationFormEL> getPublicationForms() {...}
public void setPublicationForms(
Set<PublicationFormEL> publicationForms) {...}
public static Book retrieve(...) {...}
public static List<Book> retrieveAll(...) {...}
public static void create(...) throws Exception {...}
public static void update(...) throws Exception {...}
public static void delete(...) throws Exception {...}
public String getOtherAvailableLanguagesValues() {...}
public SelectItem[] getLanguageItems() {...}
public SelectItem[] getCategoryItems() {...}
public String getPublicationFormsValues() {...}
public SelectItem[] getPublicationFormsItems() {...}
}
Notice the new methods in the last code block, like
getPublicationFormsValues
and getPublicationFormsItems
, which are
used for handling the enumeration attributes in the UI. We discuss each of them in the following
subsections.
A JPA attribute converter is a special Java class that implements the
AttributeConverter
interface with the methods convertToDatabaseColumn
and convertToEntityAttribute
. For the otherAvailableLanguages
attribute, we define the following converter
class:
@Converter public class OtherAvailableLanguagesConverter implements AttributeConverter< Set<LanguageEL>, String> { @Override public String convertToDatabaseColumn( Set<LanguageEL> attrValue) { JSONArray jsonArr = new JSONArray( attrValue); return jsonArr.toString(); } @Override public Set<LanguageEL> convertToEntityAttribute( String colValue) { Set<LanguageEL> result = new HashSet<LanguageEL>(); try { JSONArray jsonArr = new JSONArray( colValue); for (int i = 0, length = jsonArr.length(); i < length; i++) { result.add( LanguageEL.valueOf( jsonArr.getString( i))); } } catch (JSONException e) { e.printStackTrace(); } return result; } }
An attribute converter class needs to be annotated with
@Converter
. In our example, the
convertToDatabaseColumn
method is responsible to convert
the entity attribute value (e.g., otherAvailableLanguages
)
to a JSON array which is stored in the database as a
String
.
The convertToEntityAttribute
method is responsible for de-serializing a
table column's value to the corresponding entity attribute value. In our example, this means to
map the JSON array string to a Java Set
of enumeration literals of type
LanguageEL
.
The code above shows the custom attribute converter class for the
otherAvailableLanguages
attribute. The attribute converter
class for the publicationForms
attribute is defined in the
same way.
Both for single-valued and for multi-valued enumeration attributes
an ordinary setter is defined. In the case of a multi-valued enumeration
attribute, this setter assigns an entire set of values (in the form of a
Java Set
) to the attribute.
The object serialization function now needs to include the values of enumeration attributes:
public String toString() {
int i = 0, n = this.publicationForms.size();
String result = "{ isbn: '" + this.isbn + "', title:'" + this.title
+ "', year: " + this.year + ", originalLanguage: '"
+ this.originalLanguage.getLabel();
result += "', otherAvailableLanguages: [";
for (LanguageEL oal : this.otherAvailableLanguages) {
result += "'" + oal.getLabel() + "'";
if (i < n-1) result += ", ";
i++;
}
result += "]";
result += ", category: '" + this.category.name().toLowerCase();
result += "', publicationForms: [";
i = 0;
for (PublicationFormEL pf : this.publicationForms) {
result += "'" + pf.name().toLowerCase() + "'";
if (i < n-1) result += ", ";
i++;
}
result += "]}";
return result;
}
As explained in the Minimal App
Tutorial, the database schema can be automatically generated by a Java EE
server like TomEE. The generated schema for our Book
entity class is like so:
CREATE TABLE IF NOT EXISTS `books` ( `ISBN` varchar(10) NOT NULL, `CATEGORY` varchar(255) NOT NULL, `ORIGINALLANGUAGE` varchar(255) NOT NULL, `OTHERAVAILABLELANGUAGES` varchar(255) DEFAULT NULL, `PUBLICATIONFORMS` varchar(255) NOT NULL, `TITLE` varchar(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
For every attribute (like category
), a column with
the same name but using upper case is created, (e.g.,
CATEGORY
). This is the default naming, which is fine for
our example application, but it can be changed, if required, by using
the @Column( name="...")
annotation, as in @Column(
name="book_category")
.
The single-valued and multi-valued enumeration attributes are by
default created as varchar
columns with the default maximum
length of 255
characters. However, if a longer (or shorter)
length is desirable, this can be enforced by using the @Column(
length=...)
annotation.
In the test data objects that are created by Book.createTestData
, we now
have to provide values for single- and multi-valued enumeration
attributes:
public static void createTestData(
EntityManager em, UserTransaction ut)
throws NotSupportedException, ..., RollbackException {
Book book = null;
// first clear existing data
Book.clearData( em, ut);
ut.begin();
Book book = new Book( "006251587X", "Weaving the Web", 2000,
LanguageEL.EN,
new HashSet<LanguageEL>() {
{ add( LanguageEL.DE); add( LanguageEL.FR);}
},
BookCategoryEL.TEXTBOOK,
new HashSet<PublicationFormEL>() {
{ add( PublicationFormEL.HARDCOVER);
add( PublicationFormEL.PDF);}
}
);
em.persist( book);
}
The example app's user interface for creating a new book record with ISBN, title and four enumeration attributes looks as in Figure 2.2 below.
We use JSF selection lists for rendering the enumeration
attributes originalLanguage
and
otherAvailableLanguages
in the code of the facelet files
WebContent/views/books/create.xhtml
and
.../update.xhtml
:
<ui:composition template="/WEB-INF/templates/page.xhtml"> ... <ui:define name="main"> <h:form id="createBookForm"> ... <div> <h:outputLabel for="originalLanguage" value="Original language"> <h:selectOneMenu id="originalLanguage" value="#{book.originalLanguage}"> <f:selectItem itemValue="" itemLabel="---" /> <f:selectItems value="#{book.languageItems}" /> </h:selectOneMenu> </h:outputLabel> <h:message for="originalLanguage" errorClass="error" /> </div> <div class="multi-sel"> <h:outputLabel for="otherAvailableLanguages" value="Also available in"> <h:selectManyListbox id="otherAvailableLanguages" value="#{book.otherAvailableLanguages}"> <f:selectItems value="#{book.languageItems}" /> </h:selectManyListbox> </h:outputLabel> <h:message for="otherAvailableLanguages" errorClass="error" /> </div> ... <div> <h:commandButton value="Save" action="#{bookCtrl.create( book.isbn, book.title, book.year, book.originalLanguage, book.otherAvailableLanguages, book.category, book.publicationForms)}" /> </div> </h:form> </ui:define> </ui:composition>
The JSF element h:selectOneMenu
allows creating
single selection lists with the HTML select
element. The
list is populated with language options due to its child element
<f:selectItems value="#{book.languageItems}"/>
. Using
the expression #{book.languageItems}
results in calling the
method getLanguageItems()
on the book
object.
This method returns a set of SelectItem
objects, which are
used to populate the selection list. The corresponding method code is as
follows:
public SelectItem[] getLanguageItems() { SelectItem[] items = new SelectItem[LanguageEL.values().length]; int i = 0; for (LanguageEL lang : LanguageEL.values()) { items[i++] = new SelectItem( lang.name(), lang.getLabel()); } return items; }
A multiple selection list, corresponding to an HTML element
<select multiple="multiple" .../>
, is created with
the JSF element h:selectManyListbox
using the same
getLanguageItems
method for obtaining the selection list
items.
Since the enumeration attributes category
and publicationForms
have not more than seven possible values, we can use a radio button
group and a checkbox group for rendering
them:
<ui:composition template="/WEB-INF/templates/page.xhtml"> ... <ui:define name="main"> <h:form id="createBookForm"> <div> <h:outputLabel for="category" value="Category"> <h:selectOneRadio id="category" value="#{book.category}"> <f:selectItems value="#{book.categoryItems}" /> </h:selectOneRadio> </h:outputLabel> <h:message for="category" errorClass="error" /> </div> <div> <h:outputLabel for="publicationForms" value="Publication forms "> <h:selectManyCheckbox id="publicationForms" value="#{book.publicationForms}"> <f:selectItems value="#{book.publicationFormsItems}" /> </h:selectManyCheckbox> </h:outputLabel> <h:message for="publicationForms" errorClass="error" /> </div> <div> <h:commandButton value="Save" action="#{bookCtrl.create( book.isbn, book.title, book.year, book.originalLanguage, book.otherAvailableLanguages, book.category, book.publicationForms)}" /> </div> </h:form> </ui:define> </ui:composition>
The radio button group is obtained by using the JSF element
h:selectOneRadio
. It renders a set of <input
type="radio" ... />
elements. Using the same technique as for
selection lists, the radio button group is populated with a set of
SelectItem
objects. The corresponding
getCategoryItems
method from the Book
class is
similar to getLanguageItems
.
The checkbox group, consisting of <input type="checkbox"
... />
elements, is created with the JSF element
h:selectManyCheckbox
and populated in the same way as a
radio button group or a selection list.
In the case of a multi-valued enumeration attribute like
otherAvailableLanguages
or publicationForms
, the
Retrieve/List All view must show a
value in the form of a comma-separated list, like "English, German, Spanish", as shown in the
following table:
ISBN | Title | Year | Orig. lang. | Other avail. lang. | Category | Publication forms |
---|---|---|---|---|---|---|
006251587X | Weaving the Web | 2000 | English | German, French | textbook | pdf, hardcover |
0465026567 | Gödel, Escher, Bach | 1999 | French | other | paperback, epub | |
0465030793 | I Am A Strange Loop | 2008 | Spanish | English, German | textbook | pdf, epub |
For this purpose, we define a method that creates the desired
serialization of a multi-valued attribute and use it in the code of the
facelet file retrieveAndListAll.xhtml
from
WebContent/views/books/
. For the
publicationForms
attribute, the method code is as
follows:
public String getPublicationFormsValues() {
String result = "";
if (this.publicationForms != null) {
int i=0, n = this.publicationForms.size();
for (PublicationFormEL pf : this.publicationForms) {
result += pf.name().toLowerCase();
if (i < n-1) result += ", ";
i++;
}
}
return result;
}
Notice that in the case of the publicationForms
attribute, the underlying enumeration PublicationFormEL
does
not have a label property. Instead, we use the enumeration literal name in
lowercase as the label.
You can run the enumeration app on our server or download the code as a ZIP archive file.
Follow our instructions to get your environment prepared for running Java EE web applications.
If you have any questions about how to carry out the following projects, you can ask them on our discussion forum.
The purpose of the app to be built is managing information about
movies. The app deals with just one object type, Movie
, and
with two enumerations, as depicted in the following class
diagram.
First make a list of all the constraints that have been defined in this model and express them with validation annotations. Then make an entity class model as a starting for coding the app by following the guidance of this tutorial and the Validation Tutorial.
Compared to the validation app practice project, two attributes
have been added: the optional single-valued enumeration attribute
rating
, and the multi-valued enumeration attribute
genres
.