Table of contents : Contents Foreword List of Figures List of Tables Part I. Getting Started 1. A Quick Tour of the Foundations of Web Apps 2. More on JavaScript 3. Building a Minimal Web App with Plain JS in Seven Steps 4. Building a Minimal Web App with Java EE in Seven Steps 5. Information Modeling 6. Application Architecture Part II. Constraint Validation 7. Integrity Constraints and Data Validation 8. Implementing Constraint Validation in a Plain JS Web App 9. Implementing Constraint Validation in a Java EE Web App Part III. Enumerations 10. Enumerations and Enumeration Attributes 11. Implementing Enumeration Attributes in a Plain JS Web App 12. Implementing Enumeration Attributes in a Java EE Web App Part IV. Special Datatypes and Derived Properties 13. Special Datatypes 14. Derived Properties Glossary Index
Like this paper and download? You can publish your own PDF file online for free in a few minutes!Sign Up
File loading please wait...
Citation preview
Gerd Wagner and Mircea Diaconescu Web Applications with Javascript or Java De Gruyter Graduate
Also of interest Web Applications with Javascript or Java, Volume 2 G. Wagner, M. Diaconescu, 2017 ISBN 978-3-11-050024-0, e-ISBN 978-3-11-050032-5, e-ISBN (EPUB) 978-3-11-049756-4
IBM z/OS ISPF Smart Practices 1 F. Lanz, 2015 ISBN 978-3-11-037548-0, e-ISBN 978-3-11-037559-6, e-ISBN (EPUB) 978-3-11-039788-8, Set-ISBN 978-3-11-037560-2
IBM z/OS ISPF Smart Practices 2 F. Lanz, 2015 ISBN 978-3-11-040753-2, e-ISBN 978-3-11-040761-7, e-ISBN (EPUB) 978-3-11-040765-5, Set-ISBN 978-3-11-040762-4
Color Image Watermarking Q. Su, 2017 ISBN 978-3-11-048757-2, e-ISBN 978-3-11-048773-2, e-ISBN (EPUB) 978-3-11-048763-3, Set-ISBN 978-3-11-048776-3
Gerd Wagner and Mircea Diaconescu
Web Applications with Javascript or Java
Volume 1: Constraint Validation, Enumerations, Special Datatypes
Authors Prof. Dr. Gerd Wagner BTU Cottbus-Senftenberg Fachgebiet Internettechnologie Konrad-Wachsmann-Allee 5 03046 Cottbus [email protected] Mircea Diaconescu [email protected] The book‘s associated source code is licensed under The Code Project Open License (CPOL), see http://www.codeproject.com/info/cpol10.aspx, implying that the code is provided „as-is“, can be modified to create derivative works, can be redistributed, and can be used in commercial applications. The book itself, however, is protected by German copyright law and must not be distributed or republished without the publisher‘s consent.
XI Mastering App Development Requires More than Coding Skills Why is JavaScript a Good Choice for Building Web Apps? XII Why is Java a Good Choice for Building Web Apps? XIII XIII Comparing Java with JavaScript Good Programs Are: Effective, Robust, Efficient and Maintainable XIV XIV Run the Apps and Get Their Code
List of Figures
XV
List of Tables
XVII
Part I
Getting Started
A Quick Tour of the Foundations of Web Apps 3 3 The World Wide Web (WWW) . . HTML and XML 3 . 9 Styling Web Documents and User Interfaces with CSS 10 . JavaScript – “the assembly language of the Web” . Accessibility for Web Apps 11 Quiz Questions 12 .
More on JavaScript 14 . JavaScript Basics 14 . Storing Database Tables with JavaScript’s localStorage API
Building a Minimal Web App with Plain JS in Seven Steps . Step 1 – Set up the Folder Structure 38 . Step 2 – Write the Model Code 39 . Step 3 – Initialize the Application 44 . Step 4 – Implement the Create Use Case 44 Step 5 – Implement the Retrieve/List All Use Case . . Step 6 – Implement the Update Use Case 47 49 . Step 7 – Implement the Delete Use Case . Run the App and Get the Code 50 . Possible Variations and Extensions 50 . Points of Attention 52 . Practice Projects 55
37
46
35
XII
VIII
Contents
Building a Minimal Web App with Java EE in Seven Steps . 59 Java Basics . 61 Step 1 – Set up the Folder Structure . Step 2 – Write the Model Code 63 . Step 3 – Configure the App 68 . Step 4 – Implement the Create Use Case 75 . Step 5 – Implement the Retrieve/List All Use Case . Step 6 – Implement the Update Use Case 79 Step 7 – Implement the Delete Use Case 82 . . Style the User Interface with CSS 83 . Run the App and Get the Code 84 85 . Possible Variations and Extensions . Points of Attention 86 87 . Practice Projects 88 . Quiz Questions
77
Information Modeling 91 . Classes with Properties and Methods 92 95 Connecting Classes with Associations . . From a Conceptual Model via a Design Model to Class Models . Excursion: Formalizing Information Models with RDF and OWL 105 . Summary . Exercises 106 Application Architecture 107 . The Model-View-Controller (MVC) Architecture Metaphor The Onion Architecture Metaphor 110 . . “Logical” versus “Physical” User Interface 111 . MVC Web Applications 112 113 . Deployment Architectures
Part II
58
95 97
108
Constraint Validation
Integrity Constraints and Data Validation 117 . Introduction 117 118 . Integrity Constraints 129 . Responsive Validation . Constraint Validation in MVC Applications 130 131 . Adding Constraints to a Design Model . Summary 132 . Criteria for Evaluating the Validation Support of Frameworks . Quiz Questions 133
133
Contents
Implementing Constraint Validation in a Plain JS Web App 136 . 136 Introduction . 136 New Issues . Make a JavaScript Class Model 137 . Set up the Folder Structure Adding Some Library Files 139 . Write the Model Code 140 . 146 Write the View Code . Run the App and Get the Code 150 Possible Variations and Extensions 151 . . Points of Attention 152 . Practice Projects 153 156 . Quiz Questions
159 Implementing Constraint Validation in a Java EE Web App . Java Annotations for Persistent Data Management and Constraint Validation 159 . New Issues 162 . Make an Entity Class Model 163 164 Write the Model Code . . Write the View Code 168 . Defining a Custom Validation Annotation 171 173 . Run the App and Get the Code . Possible Variations and Extensions 173 Practice Projects 177 . 179 . Quiz Questions
Part III Enumerations Enumerations and Enumeration Attributes 183 . Enumerations 183 187 . Enumeration Attributes 188 . Enumerations in Computational Languages . Dealing with Enumeration Attributes in a Design Model 192 . Quiz Questions Implementing Enumeration Attributes in a Plain JS Web App 194 . New Issues . Make a JavaScript Class Model 195 196 . Add the Library File Enumeration.js . The Meta-Class Enumeration 196 . Write the Model Code 198 . Write the View Code 203
191
194
IX
X
Contents
. . .
Run the App and Get the Code Practice Projects 207 210 Quiz Questions
207
Implementing Enumeration Attributes in a Java EE Web App 211 . New Issues 211 . Make an Entity Class Model 212 . Write the Model Code 212 218 . Write the View Code . Displaying Value Sets for Multi-Valued Enumeration Attributes . Run the App and Get the Code 222 222 . Practice Projects . Quiz Questions 224
Part IV Special Datatypes and Derived Properties Special Datatypes 229 230 . Boolean Values . String Patterns 230 . Special Numeric Data Types . Calendar Dates and Times . Quantities 239 241 . Complex Data Types
232 234
Derived Properties 243 243 . Virtual Derived Properties . Materialized Derived Properties 244 . Dealing with Derived Properties in the User Interface Glossary Index
246 252
245
221
Foreword This two-volume book shows how to design and implement web applications with a model-based engineering approach, using the two most relevant technology platforms for web development: JavaScript and Java. Web apps are designed with the help of information models in the form of UML class diagrams and implemented as: 1. JavaScript front-end apps, using one of the two local storage technologies offered by web browsers; 2. Java back-end apps, using Java Server Faces (JSF) as the user interface technology, the Java Persistence API (JPA) for object-to-storage mapping, and a MySQL/ MariaDB database management system; 3. distributed JavaScript apps with a. a front-end component for managing the user interface and taking care of certain parts of the application logic (including constraint-based data validation), and b. a NodeJS-based back-end component for managing persistent storage and taking care of certain parts of the application logic (including constraintbased data validation). The focus of the book are general concepts and techniques concerning the fundamental information management issues of 1. integrity constraints and data validation, 2. enumerations and enumeration attributes 3. unidirectional and bidirectional associations between object types, 4. subtyping and inheritance in class hierarchies. The first two issues of this list are discussed in Volume 1 of the book, while the last two issues are discussed in Volume 2. We assume that the reader is already familiar with HTML and with object-oriented programming, preferably with Java (or, otherwise, with C++, PHP, Python or C#). In addition, we assume that the reader has some familiarity with CSS and JavaScript, and is willing to learn more about programming with modern JavaScript. The book can be used 1) in web development courses for students of Computer Science and related disciplines; 2) by JavaScript developers for learning Java; 3) by Java developers for learning JavaScript; 4) by JavaScript and Java (and PHP/Python/C# etc.) developers for learning ‒ object-oriented (OO) programming with JavaScript and Java EE, ‒ how to implement basic information management concepts and build complete web apps with plain JavaScript and Java EE.
XII
Foreword
The book comes with the complete source code of six example apps that you can run, or download, from the book’s website http://web-engineering.info/WebAppBook. Readers can ask questions about any chapter or topic of the book in one of the discussion forums provided at the web-engineering.info website.
1 Mastering App Development Requires More than Coding Skills For mastering app development, it’s not sufficient that you master coding in your favorite programming language. It’s essential that you understand all basic concepts and techniques, not just in programming, but also in information management. A programming (or software development) technology typically combines a programming language, such as JavaScript, Java or C#, with a framework or a number of libraries, such as JavaScript with BackboneJS, Java with JPA and JSF, or C# with ASP.NET MVC, for programming user interfaces and data storage management. Many developers seem to believe that all they need to learn and master for their professional career are the right programming technologies. However, they fail to master app development, if they don’t manage to learn the general concepts and techniques needed for being able to apply programming technologies in the right way. Understanding general concepts and techniques, in programming and information management, helps you to – better understand the elements of a language or framework by recognizing them as specific incarnations of general concepts, – compare different technologies and assess their strengths and weaknesses, – make an informed choice of a new promising language/technology suitable for your purposes. While technology-specific knowledge is important (in fact, it is the basis) for mastering programming and app development, it is also true that its half-time value is pretty short. In this book, we do not only explain programming and information management concepts and techniques in a technology-independent way, we also explain in which way these concepts and techniques are supported in the two most relevant technologies: JavaScript and Java.
2 Why is JavaScript a Good Choice for Building Web Apps? Today, JavaScript is not just a programming language, but rather a platform, which offers many advantages over other platforms: 1. It’s the only language that enjoys native support in web browsers. 2. It’s the only (“universal”) language that allows a. building web apps with just one programming language; all other languages (like Java and C#) can only be used on the back-end and need to be com-
4 Comparing Java with JavaScript
3.
4. 5.
XIII
bined with front-end JavaScript, so developers need to master at least two programming languages. b. executing the same code (e. g., for constraint validation) on the back-end and the front-end. It’s the only language that allows dynamic distribution, that is, executing the same code (e. g., for business computations) either in the back-end or the front-end, depending on run-time conditions such as the available front-end resources. It combines object-oriented with functional programming. Its dynamism allows various forms of meta-programming, which means it enables developers to program their own programming concepts, like classes and enumerations.
3 Why is Java a Good Choice for Building Web Apps? In 2017, and for many years to come, the enterprise software platform Java Enterprise Edition (Java EE), which bundles the Java language together with a number of libraries for enterprise software systems, is the most important programming platform for enterprise information technologies, including all kinds of business software applications. Its strong points mainly result from the fact that it is very mature: 1. it has a rich ecosystem, including a large number of libraries, frameworks and tools; 2. Java code executes faster than scripting languages, like Python, PHP, NodeJS or Ruby; 3. Java web applications can be scaled, e. g., by using distributed database transactions and cluster technologies; 4. it provides a lot of advanced security technologies.
4 Comparing Java with JavaScript As opposed to Java, JavaScript is relatively easy to learn. One reason for this is the need of explicit type definitions for variables, parameters and return values in Java, which are not needed in JavaScript. Both Java and JavaScript are “object-oriented” programming languages, but JavaScript is even more “object-oriented” than Java, which is rather “class-oriented”. There is no explicit class concept in JavaScript. Classes have to be defined in the form of special objects. However, objects can also be created without instantiating a class, and properties as well as methods can be defined for specific objects independently of any class definition. At run time, properties and methods can be added to, or removed from, any object and class. While JavaScript classes can be redefined at runtime, immediately af-
XIV
Foreword
fecting all objects that instantiate the class, Java classes cannot be redefined in such a way. JavaScript allows defining methods that are specific for an object, independently of its class. The methods of a Java object are defined for its class, it is not possible to define object-specific methods. JavaScript has better support for functional programming. For instance, it supports “closures”, which are function execution contexts. Java only supports a limited version of closures for anonymous classes and “lambdas”.
5 Good Programs Are: Effective, Robust, Efficient and Maintainable … as observed by Nick Gonzalez¹. A good program is effective: it solves the problem it is supposed to solve. A good program must not crash when it receives bad inputs from users: it is robust. Good programs are efficient: it does the job it is supposed to do quickly. Since the requirements for a program may change over time, a good program should be maintainable, so it can be modified easily when requirements do change. A program can only be maintainable, if its code is written in a clear manner. Any damn fool can write code that a computer can understand, the trick is to write code that humans can understand. (Martin Fowler in After the Program Runs²)
Code is often “unnecessarily complicated, convoluted, disorganized, and stitched together with disdain”, as observed in a post by Santiago L. Valdarrama³ who recommends using comments when necessary, only to explain things that can’t be made clear enough, but rather make your code reveal its intention through the use of better names, proper structure, and correct spacing and indentation.
6 Run the Apps and Get Their Code For running any of the example apps from our web server, and for downloading their code, please visit http://web-engineering.info/WebAppBook-CodeLinks.
The built-in JavaScript classes Object and Function 31 37 The object type Book 58 The object type Book 97 From a conceptual model via a design model to a JavaScript class model From a conceptual model via design models to implementation models 98 108 The MVC code base architecture with the model as the foundation 113 Architectures for front-end web apps 114 Architectures for back-end and distributed web apps An example of an object-level constraint 130 A design model defining the object type Book with two invariants 131 From an information design model to a JS class model 138 Deriving an Entity class model from an information design model 165 An example of an extensible enumeration 186 An information design model for the object type Book 191 A JavaScript class model for the object type Book 195 The UI for creating a new book record with enumeration attributes 203 A JPA entity class model for the object type Book 212 218 The user interface for creating a new book record 240 Quantity types
An example of an entity table representing a collection of books Required and desirable features of JS code patterns for classes 37 A collection of book objects represented as a table 56 Sample data about movies Sample data about countries 57 58 Sample data about books 60 Java Visibility Level 132 Sample data for Book Datatype mapping 141 Sample data about movies 154 Sample data about countries 156 Bean Validation annotations for properties 161 Datatype mapping to Java 164 Datatype mapping to MySQL 164 Representing an enumeration of records as a table 183 Sample data for Book 192 Sample data about movies with rating and genres 208 Sample data about countries with country code and religions
23 28
209
Part I Getting Started In this first part of the book we provide a brief introduction to the Web and show how to build 1. a simple front-end web app using plain JavaScript and local storage, 2. a simple back-end web app using Java with JSF, JPA and a MySQL/MariaDB database. We also provide brief introductions to information modeling and to software application architectures. The minimal examples of data management apps discussed in this part of the book only include a minimum of the overall functionality required for a complete app. They take care of only one object type (“books”) and support the four standard data management use cases (Create/Retrieve/Update/Delete), but they need to be extended by adding further important parts of an app’s overall functionality.
1 A Quick Tour of the Foundations of Web Apps If you are already familiar with HTML, XML and JavaScript, you may skip this chapter and immediately start developing a minimal web application by going to the next chapter.
1.1 The World Wide Web (WWW) After the Internet had been established in the 1980’ies, Tim Berners-Lee¹ developed the idea and the first implementation of the WWW in 1989 at the European research institution CERN in Geneva, Switzerland. The WWW (or, simply, “the Web”) is based on the Internet technologies TCP/IP (the Internet Protocol) and DNS (the Domain Name System). Initially, the Web consisted of 1. the Hypertext Transfer Protocol (HTTP), 2. the Hypertext Markup Language (HTML), and 3. web server programs, acting as HTTP servers, as well as web ’user agents’ (such as browsers), acting as HTTP clients. Later, further important technology components have been added to this set of basic web technologies: – the page/document style language Cascading Style Sheets (CSS) in 1995, – the web programming language JavaScript in 1995, – the Extensible Markup Language (XML), as the basis of web formats (like SVG and RDF/XML), in 1998, – the XML-based Scalable Vector Graphics (SVG) format in 2001, – the Resource Description Framework (RDF) for knowledge representation on the Web in 2004.
1.2 HTML and XML HTML allows to mark up (or describe) the structure of a human-readable web document or web user interface, while XML allows to mark up the structure of all kinds of documents, data files and messages, whether they are human-readable or not. XML can also be used as the basis for defining a version of HTML that is called XHTML.
1.2.1 XML documents XML provides a syntax for expressing structured information in the form of an XML document with nested elements and their attributes. The specific elements and attributes used in an XML document can come from any vocabulary, such as public standards or (private) user-defined XML formats. XML is used for specifying – document formats, such as XHTML5, the Scalable Vector Graphics (SVG) format or the DocBook format, – data interchange file formats, such as the Mathematical Markup Language (MathML) or the Universal Business Language (UBL), – message formats, such as the web service message format SOAP²
1.2.2 Unicode and UTF-8 XML is based on Unicode, which is a platform-independent character set that includes almost all characters from most of the world’s script languages including Hindi, Burmese and Gaelic. Each character is assigned a unique integer code in the range between 0 and 1,114,111. For example, the Greek letter π has the code 960, so it can be inserted in an XML document as π using the XML entity syntax. Unicode includes legacy character sets like ASCII and ISO-8859 – 1 (Latin-1) as subsets. The default encoding of an XML document is UTF-8, which uses only a single byte for ASCII characters, but three bytes for less common characters. Almost all Unicode characters are legal in a well-formed XML document. Illegal characters are the control characters with code 0 through 31, except for the carriage return, line feed and tab. It is therefore dangerous to copy text from another (nonXML) text to an XML document (often, the form feed character creates a problem).
1.2.3 XML namespaces Generally, namespaces help to avoid name conflicts. They allow to reuse the same (local) name in different namespace contexts. Many computational languages have some form of namespace concept, for instance, Java and PHP. XML namespaces are identified with the help of a namespace URI, such as the SVG namespace URI “http://www.w3.org/2000/svg”, which is associated with a namespace prefix, such as svg. Such a namespace represents a collection of names, both for elements and attributes, and allows namespace-qualified names
http://www.w3.org/TR/soap12-part0/
1.2 HTML and XML
5
of the form prefix:name, such as svg:circle as a namespace-qualified name for SVG circle elements. A default namespace is declared in the start tag of an element in the following way:
This example shows the start tag of the HTML root element, in which the XHTML namespace is declared as the default namespace. The following example shows an SVG namespace declaration for an svg element embedded in an HTML document:
…
Figure 1: A blue circle
1.2.4 Correct XML documents XML defines two syntactic correctness criteria. An XML document must be wellformed, and if it is based on a grammar (or schema), then it must also be valid with respect to that grammar, or, in other words, satisfy all rules of the grammar. An XML document is called well-formed, if it satisfies the following syntactic conditions: 1. There must be exactly one root element. 2. Each element has a start tag and an end tag; however, empty elements can be closed as instead of . 3. Tags don’t overlap. For instance, we cannot have Lee Hong 4. Attribute names are unique within the scope of an element. For instance, the following code is not correct:
An XML document is called valid against a particular grammar (such as a DTD or an XML Schema), if 1. it is well-formed, 2. and it respects the grammar.
6
1 A Quick Tour of the Foundations of Web Apps
1.2.5 The evolution of HTML The World-Wide Web Committee (W3C) has developed the following important versions of HTML: – 1997: HTML 4 as an SGML-based language, – 2000: XHTML 1 as an XML-based clean-up of HTML 4, – 2014: (X)HTML 5 in cooperation (and competition) with the WHAT working group³ supported by browser vendors. As the inventor of the Web, Tim Berners-Lee developed a first version of HTML⁴ in 1990. A few years later, in 1995, Tim Berners-Lee and Dan Connolly wrote the HTML 2⁵ standard, which captured the common use of HTML elements at that time. In the following years, HTML has been used and gradually extended by a growing community of early WWW adopters. This evolution of HTML, which has led to a messy set of elements and attributes (called “tag soup”), has been mainly controlled by browser vendors and their competition with each other. The development of XHTML in 2000 was an attempt by the W3C to clean up this mess, but it neglected to advance HTML’s functionality towards a richer user interface, which was the focus of the WHAT working group⁶ led by Ian Hickson⁷ who can be considered as the mastermind and main author of HTML 5 and many of its accompanying JavaScript APIs that made HTML fit for mobile apps.
HTML was originally designed as a structure description language, and not as a presentation description language. But HTML4 has a lot of purely presentational elements such as font. XHTML has been taking HTML back to its roots, dropping presentational elements and defining a simple and clear syntax, in support of the goals of – device independence, – accessibility, and – usability. We adopt the symbolic equation HTML = HTML5 = XHTML5 stating that when we say “HTML” or “HTML5”, we actually mean XHTML5 because we prefer the clear syntax of XML documents over the liberal and confusing HTML4-style syntax that is also allowed by HTML5. The following simple example shows the basic code template to be used for any HTML document:
Notice that in line 1, the HTML5 document type is declared, such that browsers are instructed to use the HTML5 document object model (DOM). In the html start tag in line 2, using the default namespace declaration attribute xmlns, the XHTML namespace URI http://www.w3.org/1999/xhtml is declared as the default namespace for making sure that browsers, and other tools, understand that all non-qualified element names like html, head, body, etc. are from the XHTML namespace. Also in the html start tag, we set the (default) language for the text content of all elements (here to “en” standing for English) using both the xml:lang attribute and the HTML lang attribute. This attribute duplication is a small price to pay for having a hybrid document that can be processed both by HTML and by XML tools. Finally, in line 4, using an (empty) meta element with a charset attribute, we set the HTML document’s character encoding to UTF-8, which is also the default for XML documents.
1.2.6 HTML forms For user-interactive web applications, the web browser needs to render a user interface (UI). The traditional metaphor for a software application’s UI is that of a form. The special elements for data input, data output and user actions are called form controls or UI widgets. In HTML, a form element is a section of a web page consisting of block elements that contain form controls and labels on those controls. Users complete a form by entering text into input fields and by selecting items from choice controls, including dropdown selection lists, radio button groups and checkbox groups. A completed form is submitted with the help of a submit button. When a user submits a form, it is normally sent to a web server either with the HTTP GET method or with the HTTP POST method. The standard encoding for the submission is called URL-encoded. It is represented by the Internet media type application/x-www-form-urlencoded. In this encoding, spaces become plus signs, and any other reserved characters become encoded as a percent sign and hexadecimal digits, as defined in RFC 1738.
8
1 A Quick Tour of the Foundations of Web Apps
Each form control has both an initial value and a current value, both of which are strings. The initial value is specified with the control element’s value attribute, except for the initial value of a textarea element, which is given by its initial contents. The control’s current value is first set to the initial value. Thereafter, the control’s current value may be modified through user interaction or scripts. When a form is submitted for processing, some controls have their name paired with their current value and these pairs are submitted with the form. Labels are associated with a control by including the control as a child element within a label element (implicit labels), or by giving the control an id value and referencing this ID in the for attribute of the label element (explicit labels). In the simple user interfaces of our “Getting Started” applications, we only need four types of form controls: 1. single line input fields created with an element, 2. single line output fields created with an element, 3. push buttons created with a … element, and 4. dropdown selection lists created with a select element of the following form:
option1 option2 …
An example of an HTML form with implicit labels for creating such a user interface is
ISBN:
Title:
Year:
Save
In an HTML-form-based data management user interface, we have a correspondence between the different kinds of properties defined in the model classes of an app and the form controls used for the input and output of their values. We have to distinguish between various kinds of model class attributes, which are mapped to various kinds of form fields. This mapping is also called data binding. In general, an attribute of a model class can always be represented in the user interface by a plain input control (with the default setting type="text"), no matter which datatype has been defined as the range of the attribute in the model class. However, in special cases, other types of input controls (for instance, type= "date"), or other widgets, may be used. For instance, if the attribute’s range is an enumeration, a select control or, if the number of possible choices is small enough (say, less than 8), a radio button group can be used.
1.3 Styling Web Documents and User Interfaces with CSS
9
1.3 Styling Web Documents and User Interfaces with CSS While HTML is used for defining the content structure of a web document or a web user interface, the Cascading Style Sheets (CSS) language is used for defining the presentation style of web pages, which means that you use it for telling the browser how you want your HTML (or XML) rendered: using which layout of content elements, which fonts and text styles, which colors, which backgrounds, and which animations. Normally, these settings are made in a separate CSS file that is associated with an HTML file via a special link element in the HTML’s head. A first sketch of CSS⁸ was proposed in October 1994 by Håkon W. Lie⁹ who later became the CTO of the browser vendor Opera. While the official CSS1¹⁰ standard dates back to December 1996, “most of it was hammered out on a whiteboard in Sophia-Antipolis” by Håkon W. Lie together with Bert Bos in July 1995 (as he explains in an interview¹¹).
CSS is based on a form of rules that consist of selectors, which select the document element(s) to which a rule applies, and a list of property-value pairs that define the styling of the selected element(s) with the help of CSS properties such as fontsize or color. There are two fundamental mechanisms for computing the CSS property values for any page element as a result of applying the given set of CSS rules: inheritance and the cascade. The basic element of a CSS layout¹² is a rectangle, also called “box”, with an inner content area, an optional border, an optional padding (between content and border) and an optional margin around the border. This structure is defined by the CSS box model. We will not go deeper into CSS in this book, since our focus here is on the logic and functionality of an app, and not so much on its beauty.
1.4 JavaScript – “the assembly language of the Web” JavaScript was developed in 10 days in May 1995 by Brendan Eich¹³, then working at Netscape¹⁴, as the HTML scripting language for their browser Navigator 2 (more about history¹⁵). Brendan Eich said (at the O’Reilly Fluent conference in San Francisco in April 2015): “I did JavaScript in such a hurry, I never dreamed it would become the assembly language for the Web”.
JavaScript is a dynamic functional object-oriented programming language that can be used for 1. Enriching a web page by – generating browser-specific HTML content or CSS styling, – inserting dynamic HTML content, – producing special audio-visual effects (animations). 2. Enriching a web user interface by – implementing advanced user interface components, – validating user input on the client side, – automatically pre-filling certain form fields. 3. Implementing a front-end web application with local or remote data storage¹⁶. 4. Implementing a front-end component for a distributed web application with remote data storage managed by a back-end component, which is a server-side program that is traditionally written in a server-side language such as PHP, Java or C#, but can nowadays also be written in JavaScript with NodeJS. 5. Implementing a complete distributed web application where both the front-end and the back-end components are JavaScript programs. The version of JavaScript that is currently fully supported by web browsers is called “ECMAScript 5.1”, or simply “ES5”, but the next two versions, called “ES6” and “ES7” (or “ES 2015” and “ES 2016”), are already partially supported by current browsers and back-end JS environments. In fact, in May 2017, ES6 is fully supported in nonmobile browsers, except its important new module concept.
1.4.1 JavaScript as an object-oriented language JavaScript is object-oriented, but in a different way than classical OO programming languages such as Java and C++. In JavaScript, classes, unlike objects and functions, are not first-class citizens. Rather, classes have to be defined by following some code
pattern in the form of special JS objects: either as constructor functions (possibly using the syntactic sugar of ES6 class declarations) or as factory objects. However, objects can also be created without instantiating a class, in which case they are untyped, and properties as well as methods can be defined for specific objects independently of any class definition. At run time, properties and methods can be added to, or removed from, any object and class. This dynamism of JavaScript allows powerful forms of meta-programming, such as defining your own concepts of classes and enumerations (and other special datatypes).
1.4.2 Further reading about JavaScript Good open access books about JavaScript are – Speaking JavaScript¹⁷, by Dr. Axel Rauschmayer. – Eloquent JavaScript¹⁸, by Marijn Haverbeke.
1.5 Accessibility for Web Apps The recommended approach to providing accessibility for web apps is defined by the Accessible Rich Internet Applications (ARIA) standard. As summarized by Bryan Garaventa¹⁹ in his article on different forms of accessibility²⁰, there are 3 main aspects of accessibility for interactive web technologies: 1) keyboard accessibility, 2) screen reader accessibility, and 3) cognitive accessibility. Further reading on ARIA: 1. How browsers interact with screen readers, and where ARIA fits in the mix²¹ by Bryan Garaventa 2. The Accessibility Tree Training Guide²² by whatsock.com 3. The ARIA Role Conformance Matrices²³ by whatsock.com 4. Mozilla’s ARIA overview article²⁴ 5. W3C’s ARIA overview page²⁵
1.6 Quiz Questions If you would like to look up the answers for the following quiz questions, you can check our discussion forum²⁶. If you don’t find an answer in the forum, you may create a post asking for an answer to a particular question.
1.6.1 Question 1: Well-Formed XML Which of the following fragments represent well-formed XML? Select one or many: 1. ☐ This is some text and this is more text. Here is even more. text. 2. ☐ This text is bold. And this is italicized and bold.And this is just italics. 3. ☐ This text is bold. And this is italicized and bold. And this is just italics. 4. ☐ This text is bold. and this is italicized and bold.and this is just italics.
http://web-engineering.info/forum/11
1.6 Quiz Questions
13
1.6.2 Question 2: HTML Forms Recall that an HTML form is a section of an HTML document consisting of block elements that contain controls and labels on those controls. Which of the following form elements represent correct forms? Select one or many: 1. ☐
ISBN: title:
2.
☐
ISBN: title:
3.
☐
ISBN: Title:
4.
☐
ISBN: title:
5.
☐
ISBN: Title:
2 More on JavaScript 2.1 JavaScript Basics In this summary we try to take all important points of the classical JavaScript summary¹ by Douglas Crockford² into consideration.
2.1.1 Types and data literals in JavaScript JavaScript has three primitive datatypes: string, number and boolean, and we can test if a variable v holds a value of such a type with the help of the JS operator typeof as, for instance, in typeof v === "number". There are five reference types: Object, Array, Function, Date and RegExp. Arrays, functions, dates and regular expressions are special types of objects, but, conceptually, dates and regular expressions are primitive data values, and happen to be implemented in the form of wrapper objects. The types of variables, array elements, function parameters and return values are not declared and are normally not checked by JavaScript engines. Type conversion (casting) is performed automatically. The value of a variable may be – a data value: either a string, a number, or a boolean; – an object reference: either referencing an ordinary object, or an array, function, date, or regular expression; – the special data value null, which is typically used as a default value for initializing an object variable; – the special data value undefined, which is the implicit initial value of all variables that have been declared, but not initialized. A string is a sequence of Unicode characters. String literals, like "Hello world!", 'A3F0', or the empty string "", are enclosed in single or double quotes. Two string expressions can be concatenated with the + operator, and checked for equality with the triple equality operator: if (firstName + lastName === "JamesBond") …
The number of characters of a string can be obtained by applying the length attribute to a string: console.log("Hello world!".length);
All numeric data values are represented in 64-bit floating point format with an optional exponent (like in the numeric data literal 3.1e10). There is no explicit type distinction between integers and floating point numbers. If a numeric expression cannot be evaluated to a number, its value is set to NaN (“not a number”), which can be tested with the built-in predicate isNaN(expr). Unfortunately, a built-in function, Number.isInteger, for testing if a number is an integer has only been introduced in ES6, so a polyfill³ is needed for using it in browsers that do not yet support it. For making sure that a numeric value is an integer, or that a string representing a number is converted to an integer, one has to apply the predefined function parseInt. Similarly, a string representing a decimal number can be converted to this number with parseFloat. For converting a number n to a string, the best method is using String(n). There are two predefined Boolean data literals, true and false, and the Boolean operator symbols are the exclamation mark ! for NOT, the double ampersand && for AND, and the double bar || for OR. When a non-Boolean value is used in a condition, or as an operand of a Boolean expression, it is implicitly converted into a Boolean value according to the following rules. The empty string, the (numerical) data literal 0, as well as undefined and null, are mapped to false, and all other values are mapped to true. This conversion can be performed explicitly with the help of the double negation operation, like in the equality test !!undefined === false, which evaluates to true. In addition to strings, numbers and Boolean values, also calendar dates and times are important types of primitive data values, although they are not implemented as primitive values, but in the form of wrapper objects instantiating Date. Notice that Date objects do, in fact, not really represent dates, but rather date-time instants represented internally as the number of milliseconds since 1 January, 1970 UTC. For converting the internal value of a Date object to a human-readable string, we have several options. The two most important options are using either the standard format of ISO date/time strings of the form “2015 – 01– 27”, or localized formats of date/time strings like “27.1. 2015” (for simplicity, we have omitted the time part of the date/time strings in these examples). When x instanceof Date, then x.toISOString() provides the ISO date/time string, and x.toLocaleDateString() provides the localized date/time string. Given any date string ds, ISO or localized, new Date(ds) creates a corresponding date object. For testing the equality (or inequality) of two primitive data vales, always use the triple equality symbol === (and !==) instead of the double equality symbol == (and !=). Otherwise, for instance, the number 2 would be the same as the string “2”, since the condition (2 == "2") evaluates to true in JavaScript.
Assigning an empty array literal, as in var a = [] is the same as, but more concise than and therefore preferred to, invoking the Array() constructor without arguments, as in var a = new Array(). Assigning an empty object literal, as in var o = {} is the same as, but more concise than and therefore preferred to, invoking the Object() constructor without arguments, as in var o = new Object(). Notice, however, that an empty object literal {} is not really an empty object, as it contains property slots and method slots inherited from Object.prototype⁴. So, a truly empty object (without any slots) has to be created with null as prototype, like in var emptyObject = Object. create(null). A summary of type testing is provided in the following table: Type
Example values
Test if x of type
string
“Hello world!”, ’AF’
typeof x === "string"
boolean
true, false
typeof x === "boolean"
(floating point) number
-., , , ., .e
typeof x === "number"
integer
-, , ,
Number.isInteger(x)*)
Object
{}, {num:, denom:}, {isbn:”X,” title:”Weaving the Web“}, {”one”:, “two”:, “three”:}
excluding null: x instanceof Object including null: typeof x === "object"
A summary of type conversions is provided in the following table: Type
Convert to string
Convert string to type
boolean
String(x)
Boolean(y)
(floating point) number
String(x)
parseFloat(y)
integer
String(x)
parseInt(y)
Object
x.toString() or JSON.stringify(x)
JSON.parse(y)
Array
x.toString() or JSON.stringify(x)
y.split() or JSON.parse(y)
Function
x.toString()
new Function(y)
Date
x.toISOString()
new Date(y)
RegExp
x.toString()
new RegExp(y)
2.1.2 Variable scope In the current version of JavaScript, ES5, there are only two kinds of scope for variables: the global scope (with window as the context object) and function scope, but no block scope. Consequently, declaring a variable within a code block is confusing and should be avoided. For instance, although this is a frequently used pattern, even by experienced JavaScript programmers, it is a pitfall to declare the counter variable of a for loop in the loop, as in function foo() { for (var i=0; i < 10; i++) { … // do something with i } }
Instead, and this is exactly how JavaScript is interpreting this code (by means of “hoisting” variable declarations), we should write: function foo() { var i=0; for (i=0; i < 10; i++) { … // do something with i } }
All variables should be declared at the beginning of a function. Only in the next version of JavaScript, ES6, block scope will be supported by means of a new form of variable declaration with the keyword let.
18
2 More on JavaScript
2.1.3 Strict Mode Starting from ES5, we can use strict mode⁹ for getting more runtime error checking. For instance, in strict mode, all variables must be declared. An assignment to an undeclared variable throws an exception. We can turn strict mode on by typing the following statement as the first line in a JavaScript file or inside a
…
The start page provides a menu for choosing one of the CRUD data management use cases. Each use case is performed by a corresponding page such as, for instance, createBook.html. The menu also contains options for creating test data with the help of the procedure Book.createTestData() and for clearing all data with Book.clearData():
Public Library An Example of a Minimal JavaScript Front-End App
This app supports the following operations:
List all books
Add a new book
Update a book
Delete a book
Clear database
Create test data
3.2 Step 2 – Write the Model Code In the second step, we write the code of our model class and save it in a specific model class file. In an MVC app, the model code is the most important part of the app. It’s also the basis for writing the view and controller code. In fact, large parts of the view and controller code could be automatically generated from the model code. Many MVC frameworks provide this kind of code generation. In the information design model shown in Figure 3.1 above, there is only one class, representing the object type Book. So, in the folder src/m, we create a file Book.js that initially contains the following code: function Book( slots) { this.isbn = slots.isbn; this.title = slots.title; this.year = slots.year; };
The model class Book is coded as a JavaScript constructor function with a single slots parameter, which is a record object with fields isbn, title and year, representing the constructor parameters to be assigned to the ISBN, the title and the year attributes of the class Book. Notice that, for getting a simple name, we have put the class name Book in the global scope, which is okay for a small app with only a few classes. In general, however, we should use the model namespace for model classes, which requires class/constructor definitions like pl.m.Book = function (slots) {…}
40
3 Building a Minimal Web App with Plain JS in Seven Steps
In addition to defining the model class in the form of a constructor function, we also define the following items in the Book.js file: 1. A class-level property Book.instances representing the collection of all Book instances managed by the application in the form of an entity table. 2. A class-level method Book.retrieveAll for loading all managed Book instances from the persistent data store. 3. A class-level method Book.saveAll for saving all managed Book instances to the persistent data store. 4. A class-level method Book.add for creating a new Book instance. 5. A class-level method Book.update for updating an existing Book instance. 6. A class-level method Book.destroy for deleting a Book instance. 7. A class-level method Book.createTestData for creating a few example book records to be used as test data. 8. A class-level method Book.clearData for clearing the book datastore.
3.2.1 Representing the collection of all Book instances For representing the collection of all Book instances managed by the application, we define and initialize the class-level property Book.instances in the following way: Book.instances = {};
So, initially our collection of books is empty. In fact, it’s defined as an empty object literal, since we want to represent it in the form of an entity table (a map of entity records) where an ISBN is a key for accessing the corresponding book record (as the value associated with the key). We can visualize the structure of an entity table in the form of a lookup table: Key
Value
X
{ isbn:”X,” title:”Weaving the Web”, year:} { isbn:”,” title:”Gödel, Escher, Bach”, year:} { isbn:”,” title:”I Am A Strange Loop”, year:}
Notice that the values of such a map are records corresponding to table rows. Consequently, we could also represent them in a simple table, as shown in Table 3.1.
3.2.2 Creating a new Book instance The Book.add procedure takes care of creating a new Book instance and adding it to the Book.instances collection:
3.2 Step 2 – Write the Model Code
41
Book.add = function (slots) { var book = new Book( slots); // add book to the collection of Book.instances Book.instances[slots.isbn] = book; console.log("Book " + slots.isbn + " created!"); };
3.2.3 Loading all Book instances For persistent data storage, we use the Local Storage API supported by modern web browsers. Loading the book records from Local Storage involves three steps: 1. Retrieving the book table that has been stored as a large string with the key “books” from Local Storage with the help of the assignment booksString = localStorage["books"]; 2. Converting the book table string into a corresponding entity table books with book rows as elements, with the help of the built-in procedure JSON.parse: books = JSON.parse( booksString); This conversion is called de-serialization. 3. Converting each row of books, representing a record (an untyped object), into a corresponding object of type Book stored as an element of the entity table Book.instances, with the help of the procedure convertRec2Obj defined as a “static” (class-level) method in the Book class: Book.convertRec2Obj = function (bookRec) { var book = new Book( bookRec); return book; }; Here is the full code of the procedure: Book.retrieveAll = function () { var key="", keys=[], i=0, booksString="", books={}; try { if (localStorage["books"]) { booksString = localStorage["books"]; } } catch (e) { alert("Error when reading from Local Storage\n" + e); } if (booksString) { books = JSON.parse( booksString); keys = Object.keys( books); console.log( keys.length +" books loaded."); for (i=0; i < keys.length; i++) { key = keys[i];
42
3 Building a Minimal Web App with Plain JS in Seven Steps
Notice that since an input operation like localStorage["books"] may fail, we perform it in a try-catch block, where we can follow up with an error message whenever the input operation fails.
3.2.4 Updating a Book instance For updating an existing Book instance we first retrieve it from Book.instances, and then re-assign those attributes the value of which has changed: Book.update = function (slots) { var book = Book.instances[slots.isbn]; var year = parseInt( slots.year); if (book.title !== slots.title) { book.title = slots.title;} if (book.year !== year) { book.year = year;} console.log("Book " + slots.isbn + " modified!"); };
3.2.5 Deleting a Book instance A Book instance is deleted from the entity table Book.instances by first testing if the table has a row with the given key (line 2), and then applying the JavaScript builtin delete operator, which deletes a slot from an object, or an entry from a map: Book.destroy = function (isbn) { if (Book.instances[isbn]) { console.log("Book " + isbn + " deleted"); delete Book.instances[isbn]; } else { console.log("There is no book with ISBN " + isbn + " in the database!"); } };
3.2.6 Saving all Book instances Saving all book objects from the Book.instances collection in main memory to Local Storage in secondary memory involves two steps:
3.2 Step 2 – Write the Model Code
1.
2.
43
Converting the entity table Book.instances into a string with the help of the predefined JavaScript procedure JSON.stringify: booksString = JSON.stringify( Book.instances); This conversion is called serialization. Writing the resulting string as the value of the key “books” to Local Storage: localStorage["books"] = booksString;
These two steps are performed in line 5 and in line 6 of the following program listing: Book.saveAll = function () { var booksString="", error=false, nmrOfBooks = Object.keys( Book.instances).length; try { booksString = JSON.stringify( Book.instances); localStorage["books"] = booksString; } catch (e) { alert("Error when writing to Local Storage\n" + e); error = true; } if (!error) console.log( nmrOfBooks + " books saved."); };
3.2.7 Creating test data For being able to test our code, we may create some test data and save it in our Local Storage database. We can use the following procedure for this: Book.createTestData = function () { Book.instances["006251587X"] = new Book( {isbn:"006251587X", title:"Weaving the Web", year:2000}); Book.instances["0465026567"] = new Book( {isbn:"0465026567", title:"Gödel, Escher, Bach", year:1999}); Book.instances["0465030793"] = new Book( {isbn:"0465030793", title:"I Am A Strange Loop", year:2008}); Book.saveAll(); };
3.2.8 Clearing all data The following procedure clears all data from Local Storage:
44
3 Building a Minimal Web App with Plain JS in Seven Steps
Book.clearData = function () { if (confirm("Do you really want to delete all book data?")) { localStorage["books"] = "{}"; } };
3.3 Step 3 – Initialize the Application We initialize the application by defining its namespace and MVC sub-namespaces. Namespaces are an important concept in software engineering and many programming languages, including Java and PHP, provide specific support for namespaces, which help grouping related pieces of code and avoiding name conflicts. Since there is no specific support for namespaces in JavaScript, we use special objects for this purpose (we may call them “namespace objects”). First we define a root namespace (object) for our app, and then we define three sub-namespaces, one for each of the three parts of the application code: model, view and controller. In the case of our example app, we may use the following code for this: var pl = { m:{}, v:{}, c:{} };
Here, the main namespace is defined to be pl, standing for “Public Library”, with the three sub-namespaces m, v and c being initially empty objects. We put this code in a separate file initialize.js in the c folder, because such a namespace definition belongs to the controller part of the application code.
3.4 Step 4 – Implement the Create Use Case For our example app, the user interface page for the CRUD use case Create is called createBook.html located in the MinimalApp folder. In its head element, it loads the app initialization file initialize.js, the model class file Book.js and the view code file createBook.js, and adds a load event listener for setting up the Create user interface:
Minimal JS Front-End App Example
3.4 Step 4 – Implement the Create Use Case
45
…
For a data management use case with user input, such as “Create”, an HTML form is required as a user interface. The form typically has a labelled input or select field for each attribute of the model class:
Create a new book record
ISBN: Title: Year: Save
Back to main menu
The view code file src/v/createBook.js contains two procedures: 1. setupUserInterface takes care of retrieving the collection of all objects from the persistent data store and setting up an event handler (handleSaveButtonClickEvent) on the save button for handling click button events by saving the user input data; 2. handleSaveButtonClickEvent reads the user input data from the form fields and then saves this data by calling the Book.add procedure. pl.v.createBook = { setupUserInterface: function () { var saveButton = document.forms['Book'].commit; // load all book objects Book.retrieveAll(); // set an event handler for the save/submit button saveButton.addEventListener("click", pl.v.createBook.handleSaveButtonClickEvent); // handle the event when the browser window/tab is closed window.addEventListener("beforeunload", function () { Book.saveAll(); }); }, handleSaveButtonClickEvent: function () { var formEl = document.forms['Book']; var slots = { isbn: formEl.isbn.value,
46
3 Building a Minimal Web App with Plain JS in Seven Steps
3.5 Step 5 – Implement the Retrieve/List All Use Case The user interface for the CRUD use case Retrieve consists of an HTML table for displaying the data of all model objects. For our example app, this page is called retrieveAndListAllBooks.html, located in the main folder MinimalApp, and it contains the following code in its head element:
Simple JS Front-End App Example
Notice that, in addition to loading the app initialization JS file and the model class JS file, we load the view code file (here: retrieveAndListAllBooks.js) and invoke its setupUserInterface procedure via a load event listener. This is the pattern we use for all four CRUD use cases.
Retrieve and list all book records
ISBN
Title
Year
Back to main menu
In the setupUserInterface procedure, we first set up the data management context by retrieving all book data from the database and then fill the table by creating a table row for each book object from Book.instances:
3.6 Step 6 – Implement the Update Use Case
47
pl.v.retrieveAndListAllBooks = { setupUserInterface: function () { var tableBodyEl = document.querySelector("table#books>tbody"); var keys=[], key="", row={}, i=0; // load all book objects Book.retrieveAll(); keys = Object.keys( Book.instances); // for each book, create a table row with cells for the 3 attributes for (i=0; i < keys.length; i++) { key = keys[i]; row = tableBodyEl.insertRow(); row.insertCell(‐1).textContent = Book.instances[key].isbn; row.insertCell(‐1).textContent = Book.instances[key].title; row.insertCell(‐1).textContent = Book.instances[key].year; } } };
More specifically, the procedure setupUserInterface creates the view table in a loop over all objects of Book.instances. In each step of this loop, a new row is created in the table body element with the help of the JavaScript DOM operation insertRow(), and then three cells are created in this row with the help of the DOM operation insertCell(): the first one for the isbn property value of the book object, and the second and third ones for its title and year property values. Both insertRow and insertCell have to be invoked with the argument -1 for making sure that new elements are appended to the list of rows and cells.
3.6 Step 6 – Implement the Update Use Case Also for the Update use case, we have an HTML page for the user interface (updateBook.html) and a view code file (src/v/updateBook.js). The HTML form for the UI of the “update book” operation has a selection field for choosing the book to be updated, an output field for the standard identifier attribute isbn, and an input field for each attribute of the Book class that can be updated. Notice that by using an output field for the standard identifier attribute, we do not allow changing the standard identifier of an existing object.
Select book:
—
ISBN: Title:
48
3 Building a Minimal Web App with Plain JS in Seven Steps
Year:
Save Changes
Notice that we include a kind of empty option element, with a value of "" and a display text of—, as a default choice in the selectBook selection list element. So, by default, the value of the selectBook form control is empty, requiring the user to choose one of the available options for filling the form. The setupUserInterface procedure now has to populate the select element’s option list by loading the collection of all book objects from the data store and creating an option element for each book object: pl.v.updateBook = { setupUserInterface: function () { var formEl = document.forms['Book'], saveButton = formEl.commit, selectBookEl = formEl.selectBook; var key="", keys=[], book=null, optionEl=null, i=0; Book.retrieveAll(); // populate the selection list with books keys = Object.keys( Book.instances); for (i=0; i < keys.length; i++) { key = keys[i]; book = Book.instances[key]; optionEl = document.createElement("option"); optionEl.text = book.title; optionEl.value = book.isbn; selectBookEl.add( optionEl, null); } // when a book is selected, populate the form selectBookEl.addEventListener("change", pl.v.updateBook.handleBookSelectionEvent); // set an event handler for the submit/save button saveButton.addEventListener("click", pl.v.updateBook.handleSaveButtonClickEvent); // handle the event when the browser window/tab is closed window.addEventListener("beforeunload", Book.saveAll); }, … }
A book selection event is caught via a listener for change events on the select element. When a book is selected, the form is filled with its data: handleBookSelectionEvent: function () { var formEl = document.forms['Book']; var selectBookEl = formEl.selectBook,
When the save button is activated, a slots record is created from the form field values and used as the argument for calling Book.update: handleSaveButtonClickEvent: function () { var formEl = document.forms['Book']; var slots = { isbn: formEl.isbn.value, title: formEl.title.value, year: formEl.year.value }; Book.update( slots); formEl.reset(); }
3.7 Step 7 – Implement the Delete Use Case The user interface for the Delete use case just has a select field for choosing the book to be deleted:
Select book: —
Delete
Like in the Update case, the setupUserInterface procedure in the view code in src/v/deleteBook.js loads the book data into main memory, populates the book selection list and adds some event listeners. The event handler for Delete button click events. handleDeleteButtonClickEvent: function () { var selectEl = document.forms['Book'].selectBook; var isbn = selectEl.value; if (isbn) {
50
3 Building a Minimal Web App with Plain JS in Seven Steps
Book.destroy( isbn); // remove deleted book from select options selectEl.remove( selectEl.selectedIndex); } }
3.8 Run the App and Get the Code You can run the minimal app² from our server or download the code³ as a ZIP archive file.
3.9 Possible Variations and Extensions 3.9.1 Using IndexedDB as an Alternative to LocalStorage Instead of using the Local Storage API, the IndexedDB⁴ API could be used for locally storing the application data. With Local Storage you only have one database (which you may have to share with other apps from the same domain) and there is no support for database tables (we have worked around this limitation in our approach). With IndexedDB you can set up a specific database for your app, and you can define database tables, called ’object stores’, which may have indexes for accessing records with the help of an indexed attribute instead of the standard identifier attribute. Also, since IndexedDB supports larger databases, its access methods are asynchronous and can only be invoked in the context of a database transaction. Alternatively, for remotely storing the application data with the help of a web API one can either use a back-end solution component or a cloud storage service. The remote storage approach allows managing larger databases and supports multi-user apps.
3.9.2 Styling the User Interface For simplicity, we have used raw HTML without any CSS styling. But a user interface should be appealing. So, the code of this app should be extended by adding suitable CSS style rules. Today, the UI pages of a web app have to be adaptive (frequently called “responsive”) for being rendered on different devices with different screen sizes and resolu-
tions, which can be detected with CSS media queries. The main issue of an adaptive UI is to have a fluid layout, in addition to proper viewport settings. Whenever images are used in a UI, we also need an approach for adaptive bitmap images: serving images in smaller sizes for smaller screens and in higher resolutions for high resolution screens, while preferring scalable SVG images for diagrams and artwork. In addition, we may decrease the font-size of headings and suppress unimportant content items on smaller screens. For our purposes, and for keeping things simple, we customize the adaptive web page design defined by the HTML5 Boilerplate⁵ project (more precisely, the minimal “responsive” configuration available on www.initializr.com). It just consists of an HTML template file and two CSS files: the browser style normalization file normalize.css (in its minified form) and a main.css, which contains the HTML5 Boilerplate style and our customizations. Consequently, we use a new css subfolder containing these two CSS files: MinimalApp-with-CSS css main.css normalize.min.css src c m v index.html
One customization change we have made in index.html is to replace the container element with the new HTML 5.1 element such that we obtain a simple and clear UI page structure provided by the sequence of the three container elements , and . This change in the HTML file requires corresponding changes in main.css. In addition, we define our own styles for
, and elements. Concerning the styling of HTML forms, we define a simple style for implicitly labeled form control elements. The start page index.html now must take care of loading the CSS page styling files with the help of the following two link elements:
Since the styling of user interfaces is not our primary concern, we do not discuss the details of it and leave it to our readers to take a closer look. You can run the CSSstyled minimal app⁶ from our server or download its code⁷ as a ZIP archive file.
3 Building a Minimal Web App with Plain JS in Seven Steps
3.10 Points of Attention 3.10.1 Catching invalid data The app discussed in this chapter is limited to support the minimum functionality of a data management app only. It does not take care of preventing users from entering invalid data into the app’s database. In Chapter 8, we show how to express integrity constraints in a model class, and how to perform data validation both in the model/ storage code of the app and in the HTML5-based user interface.
3.10.2 Database size and memory management Notice that in this chapter, we have made the assumption that all application data can be loaded into main memory (like all book data is loaded into the map Book.instances). This approach only works in the case of local data storage of smaller databases, say, with not more than 2 MB of data, roughly corresponding to 10 tables with an average population of 1000 rows, each having an average size of 200 Bytes. When larger databases are to be managed, or when data is stored remotely, it’s no longer possible to load the entire population of all tables into main memory, but we have to use a technique where only parts of the table contents are loaded.
3.10.3 Boilerplate code Another issue with the do-it-yourself code of this example app is the boilerplate code needed per model class for the data storage management methods add, retrieve, update, and destroy. While it is good to write this code a few times for learning app development, you don’t want to write it again and again later when you work on real projects. In Volume 2, we present an approach how to put these methods in a generic form in a meta-class, such that they can be reused in all model classes of an app.
3.10.4 Serializing and de-serializing attribute values Serializing an attribute value means to convert it to a suitable string value. For standard datatypes, such as numbers, a standard serialization is provided by the predefined conversion function String. When a string value, like “13” or “yes”, represents the value of a non-string-valued attribute, it has to be de-serialized, that is, converted to the range type of the attribute, before it is assigned to the attribute. This is the situation, for instance, when a user has entered a value in a form input field for an integer-valued attribute. The value of the form field is of type string,
3.10 Points of Attention
53
so it has to be converted (de-serialized) to an integer using the predefined conversion function parseInt. For instance, in our example app, we have the integer-valued attribute year. When the user has entered a value for this attribute in a corresponding form field, in the Create or Update user interface, the form field holds a string value, which has to be converted to an integer in an assignment like the following: this.year = parseInt( formEl.year.value);
One important question is: where should we take care of de-serialization: in the “view” (before the value is passed to the “model” layer), or in the “model”? Since attribute range types are a business concern, and the business logic of an app is supposed to be encapsulated in the “model”, de-serialization should be performed in the “model” layer, and not in the “view”.
3.10.5 Implicit versus explicit form field labels The explicit labeling of form fields requires to add an id value to the input element and a for-reference to its label element as in the following example:
ISBN:
This technique for associating a label with a form field is getting quite inconvenient when we have many form fields on a page because we have to make up a great many of unique id values and have to make sure that they don’t conflict with any of the id values of other elements on the same page. It’s therefore preferable to use an approach, called implicit labeling, where these id references are not needed. In this approach, the input element is a child element of its label element, as in
ISBN:
Having input elements as child elements of their label elements doesn’t seem very logical. Rather, one would expect the label to be a child of an input element. But that’s the way it is defined in HTML5. A small disadvantage of using implicit labels may be the lack of support by certain CSS libraries. In the following parts of this tutorial, we will use our own CSS styling for implicitly labeled form fields.
54
3 Building a Minimal Web App with Plain JS in Seven Steps
3.10.6 Synchronizing views with the model When an app is used by more than one user at the same time, we have to take care of somehow synchronizing the possibly concurrent read/write actions of users such that users always have current data in their “views” and are prevented from interfering with each other. This is a very difficult problem, which is attacked in different ways by different approaches. It has been mainly investigated for multi-user database management systems and large enterprise applications built on top of them. The original MVC proposal included a data binding mechanism for automated one-way model-to-view synchronization (updating the model’s views whenever a change in the model data occurs). We didn’t take care of this in our minimal app because a front-end app with local storage doesn’t really have multiple concurrent users. However, we can create a (rather artificial) situation that illustrates the issue: 1. Open the Update UI page of the minimal app twice (for instance, by opening updateLearningUnit.html twice), such that you get two browser tabs rendering the same page. 2. Select the same learning unit on both tabs, such that you see its data in the Update view. 3. Change one data item of this learning unit on one of the tabs and save your change. 4. When you now go to the other tab, you still see the old data value, while you may have expected that it would have been automatically updated. A mechanism for automatically updating all views of a model object whenever a change in its property values occurs is provided by the observer pattern that treats any view as an observer of its model object. Applying the observer pattern requires that (1) model objects can have a multi-valued reference property like observers, which holds a set of references to view objects; (2) a notify method can be invoked on view objects by the model object whenever one of its property values is changed; and (3) the notify method defined for view objects takes care of refreshing the user interface. Notice, however, that the general model-view synchronization problem is not really solved by automatically updating all (other users’) views of a model object whenever a change in its data occurs. Because this would only help, if the users of these views didn’t make themselves any change of the data item concerned, meanwhile. Otherwise, their changed data value would be overwritten by the automated refresh, and they may not even notice this, which is not acceptable in terms of usability.
3.11 Practice Projects
55
3.10.7 Architectural separation of concerns From an architectural point of view, it is important to keep the app’s model classes independent of 1. the user interface (UI) code because it should be possible to re-use the same model classes with different UI technologies; 2. the storage management code because it should be possible to re-use the same model classes with different storage technologies. In this chapter, we have kept the model class Book independent of the UI code, since it does not contain any references to UI elements, nor does it invoke any view method. However, for simplicity, we didn’t keep it independent of storage management code, since we have included the method definitions for add, update, destroy, etc., which invoke the storage management methods of JavaScrpt’s localStorage API. Therefore, the separation of concerns is incomplete in our minimal example app. We show in Volume 2 how to achieve a more complete separation of concerns by defining abstract storage management methods in a special storage manager class, which is complemented by libraries of concrete storage management methods for specific storage technologies, called storage adapters.
3.11 Practice Projects In most parts of the following projects you can follow, or even copy, the code of the book data management app presented in this chapter. Like in the book data management app, you can make the simplifying assumption that all the data can be kept in main memory. So, on application start up, the data is read from the persistent data store. When the user quits the application, the data has to be saved to the persistent data store, which should be implemented with JavaScript’s Local Storage API, as shown in this chapter, or with the more powerful IndexedDB⁸ API. For developing the apps, simply follow the sequence of seven steps described above: 1. Step 1 – Set up the Folder Structure 2. Step 2 – Write the Model Code 3. Step 3 – Initialize the Application 4. Step 4 – Implement the Retrieve/List All Objects Use Case 5. Step 5 – Implement the Create Object Use Case 6. Step 6 – Implement the Update Object Use Case 7. Step 7 – Implement the Delete Object Use Case
3 Building a Minimal Web App with Plain JS in Seven Steps
Also make sure that 1. your HTML pages comply with the XML syntax of HTML5, preferably by checking with XHTML5 validation⁹ (setting the validator field Preset to “HTML5 + SVG 1. 1 + MathML 3.0”), 2. international characters are supported by using UTF-8 encoding for all HTML files, 3. your JavaScript code complies with our Coding Guidelines¹⁰ and its style is checked with JSHint¹¹ (for instance, instead of the unsafe equality test with “==”, always the strict equality test with “===” has to be used). If you have any questions about how to carry out the following projects, you can ask them on our discussion forum¹².
3.11.1 Project 1 – Managing information about movies The purpose of the app to be developed is managing information about movies. The app deals with just one object type: Movie, as depicted in the following class diagram:
Notice that in the Movie class there is an attribute with range Date, which is a special datatype, discussed in Chapter 13. You can use the sample data shown in Table 3.2 for testing your app. Table 3.2 Sample data about movies Movie ID
More movie data can be found on the IMDb website¹³. Variation: Improve your app by replacing the use of the localStorage API for persistent data storage with using the more powerful IndexedDB¹⁴ API.
3.11.2 Project 2 – Managing information about countries The purpose of the app to be developed is managing information about countries. The app deals with just one object type: Country, as depicted in the following class diagram:
You can use the sample data shown below in Table 3.3 for testing your app. Table 3.3 Sample data about countries Name
Population
Life expectancy
Germany France Russia Monaco
,, ,, ,, ,
. . . .
More data about countries can be found in the CIA World Factbook¹⁵.
4 Building a Minimal Web App with Java EE in Seven Steps In this chapter, we show how to build a simple Java EE web application using the Java Persistence API (JPA) for object-to-storage mapping and Java Server Faces (JSF) as the user interface (or “view”) technology. Such an application requires a web server that supports the Java EE specifications Java Servlets, Java Expression Language (EL), JPA and JSF, such as the open source web server Tomcat/TomEE¹ (pronounced “Tommy”). In addition to the Java code executed on the back-end computer that runs the TomEE web server, a Java EE web app also consists of HTML, CSS and possibly some auxiliary JavaScript code that is executed by a web browser running on the user’s front-end computer. Since essentially all data processing is performed on the back-end, and the front-end only renders the user interface, we classify Java EE web applications as back-end web apps. We show how to develop, deploy and run a simple example app using the TomEE web server, which is Apache Tomcat extended by adding basic Java EE features for providing an execution environment for light-weight Java EE web apps. We assume that you have already installed the TomEE Plume² server and MySQL³. Then simply follow our installation and configuration instructions⁴. The purpose of our minimal example app is to manage information about books. For simplicity, we deal with a single object type Book, as depicted in Figure 4.1.
Figure 4.1 The object type Book.
The following table shows a sample data population for the model class Book. Table 4.1 Sample data about books ISBN
Title
Year
X
Weaving the Web Gödel, Escher, Bach I Am A Strange Loop
What do we need for a data management app? There are four standard us e cases, which have to be supported by the app: 1. Create a new book record by allowing the user to enter the data of a book that is to be added to the collection of stored book records. 2. Retrieve (or read) all books from the data store and show them in the form of a list. 3. Update the data of a book record. 4. Delete a book record. These four standard use cases, and the corresponding data management operations, are often summarized with the acronym CRUD. For entering data with the help of the keyboard and the screen of our computer, we use HTML forms, which provide the user interface technology for web applications. For any data management app, we need a technology that allows to store data in persistent records on a secondary storage device, such as a hard-disk or a solid state disk. JPA allows using a great number of different data storage technologies, including many SQL database management systems (DBMS) such as Oracle, MySQL and PostgreSQL. We don’t have to change much in the application code for switching from one storage technology to another. Adding the right driver implementation to our Java runtime environment, properly setting up the DBMS and changing the database access configuration is in general all we need to do. In our example application, we explain how to set up the JPA configuration for MySQL.
4.1 Java Basics Compared to JavaScript, what is different in Java? We list a few observations: 1. No program without a class: Any Java program must include at least one class. 2. Java is strongly typed: Properties, parameters and variables must be declared to be of some type. 3. No global variables, no global procedures: In Java, variables, procedures and functions must be defined in the context of a class, which provides their name space. 4. Arrays are static: Arrays have a fixed size, which cannot be changed at runtime. 5. No object without a class: For creating an object, a class has to be used (or defined) for ‒ defining the properties of the object’s property slots ‒ defining the methods and functions that can be applied to the object (and all other objects instantiating the class) 6. Classes, properties and methods are defined with a visibility level (public, protected or private), which restricts their accessibility.
60
4 Building a Minimal Web App with Java EE in Seven Steps
Type parameters: Classes and complex data structures (such as lists) can be defined with the help of type parameters. See, for instance, the generics tutorial⁵ by Oracle. 8. Java programs must be compiled before they can be executed. 9. Speed: Java is about twice as fast as optimized JavaScript. 7.
In Java, visibility levels are used to define the possible levels of access: Table 4.2 Java Visibility Level
public protected no modifier private
Class
Package
Subclass
World
y y y y
y y y n
y y n n
y n n n
Normally, properties are defined as private, such that they can only be assigned with a public setter, which allows to control, and possibly log, any change of the property value. Unfortunately, Java does not allow to protect only write, but not read access. Therefore, we always have to define getters along with setters, even if they are not needed. A Java bean class (or, simply, bean class) is a Java class with a parameter-free constructor where all properties are serializable and have a get and set method (also called “getter” and “setter”). A Java bean is an object created with the help of a bean class. A JPA entity class (or, simply, entity class) is a Java bean class with an @Entity annotation, implying that a Java EE runtime environment (such as provided by the TomEE PLUS⁶ web server) will take care of the persistent storage of its instances. JPA is a Java API for the management of persistent data in Java applications. It uses the Java Persistence Query Language (JPQL), a platform-independent object-oriented query language based on SQL. JPQL query expressions look very similar to SQL query expressions, but they are executed in the context of JPA entity objects. JSF is a Java specification for building component-based user interfaces for web applications. Its current version, JSF 2, by default, uses Facelets as its view technology. By contrast, JSF 1 has used JavaServer Pages (JSP) as its default view technology. A JSF facelet is an XML-based template that combines static UI content items with data variables for generating an XHTML user interface page.
4.2 Step 1 – Set up the Folder Structure In the first step, we set up our folder structure for the application program code. The application name is “Public Library”, so it would be natural to use a corresponding name for the application folder, like “PublicLibrary”, but we prefer using MinimalApp. Then we create the application structure. There are many ways to do this, like for example to use the Eclipse⁷ development environment (and create and configure a Dynamic Web Project). In this chapter we show how to do it manually, so there is no need to use special tools. For your convenience, we also provide an ANT script (see our guide⁸ for a download link), allowing to automatically create the folder structure of the web application, compile it to a Web application Archive (WAR) file and then deploy it to a TomEE server for execution. The application structure (which is compatible with the Dynamic Web Project structure of Eclipse, so it can be imported in Eclipse) is the following: MinimalApp src pl m c META-INF persistence.xml WebContent resources media img views books WEB-INF templates faces-config.xml web.xml
This folder structure has the following parts: 1. The src folder contains the app code folder pl, defining the Java package name for the app (as a shortcut of ’public library’), and the folder META-INF with configuration files: a. the app code folder pl contains the model and controller code in its subfolders m and c, while the view/UI related code is contained in the WebContent folder; b. the most important configuration file is the persistence.xml file. It contains the configuration for the database connection. The content of this file is discussed in Section 4.2.
4 Building a Minimal Web App with Java EE in Seven Steps
The WebContent folder contains various web resources, including template files and custom view files: a. the resources folder is used for storing resources, such as downloadable documents, images or videos. b. the views folder stores our custom view files for the application, so it represents the View part of the MVC paradigm. Please note that it is not strictly required to name it “views”, but it makes a lot of sense to do it so, since this is what this folder represents. c. the WEB-INF folder contains project libraries (in the form of jar files in the lib subfolder (in our case we don’t need such libraries), the facelet files for the UI pages (as part of the templates subfolder), the faces-config. xml file, which stores the facelet configuration and the web.xml configuration file, specific to the TomEE server used to run our application.
We create a “Main Menu” page for our application, thus we add an index.xhtml file to our views folder with the following content:
Minimal App with Java and JPA/JSF
Public Library Minimal App with Java and JPA/JSF
a new book
and list all books
a book
a book
database
test data
4.3 Step 2 – Write the Model Code
63
The code consists of HTML elements and JSF elements, which are discussed below.
4.3 Step 2 – Write the Model Code In the second step, we create the model classes for our app, using a separate Java source code file (with extension .java) for each model class. In the information design model shown in Figure 4.1 above, there is only one class, representing the object type Book. So, we create a file Book.java in the folder src/pl/m with the following code: package pl.m; @Entity @Table( name="books") public class Book { @Id private String isbn; private String title; private int year; // default constructor (required for entity classes) public Book() {} // constructor public Book( String isbn, String title, int year) { this.setIsbn( isbn); this.setTitle( title); this.setYear( year); } // getter and setter methods … }
Notice that the model class Book is coded as a JPA entity class, which is a JavaBean class enriched with the following JPA annotations: 1. The annotation @Entity designates a class as an entity class implying that the instances of this class will be stored persistently. 2. The annotation @Table( name="books") specifies the name of the database table to be used for storing the Book entities. This annotation is optional and defaults to a table name being the same as the class name but in lower case (that is, it would be book in our case). 3. The @Id annotation marks the standard identifier attribute, implying that the corresponding column of the underlying SQL database table is designated as the PRIMARY KEY. In our example, isbn is used as the standard identifier at-
64
4 Building a Minimal Web App with Java EE in Seven Steps
tribute, and the corresponding isbn column of the books table stores the primary key values. In the entity class Book, we also define the following static (class-level) methods: 1. Book.create for creating a new Book instance. 2. Book.retrieveAll for retrieving all Book instances from the persistent data store. 3. Book.retrieve for retrieving a specific Book instance from the persistent data store by means of its standard identifier. 4. Book.update for updating an existing Book instance. 5. Book.delete for deleting a Book instance. 6. Book.generateTestData for creating a few example book records to be used as test data. 7. Book.clearData for clearing the book database table. The signatures of these methods, which are discussed in more detail in the following subsections, are shown in the following program listing:. // getter and setter methods public String getIsbn() {return isbn;} public void setIsbn( String isbn) {this.isbn = isbn;} public String getTitle() {return title;} public void setTitle( String title) {this.title = title;} public int getYear() {return year;} public void setYear( int year) {this.year = year;} // CRUD data management methods public static void create(…) {…} public static List retrieveAll(…) {…} public static Book retrieve(…) {…} public static void update(…) {…} public static void delete(…) {…} public static void clearData(…) {…} public static void generateTestData(…) {…}
The JPA architecture for data management and object-to-storage mapping is based on the concept of an entity manager, which provides the data management methods persist for saving a newly created or updated entity, find for retrieving an entity, and remove for deleting an entity. Since the database access operations of an entity manager are executed in the context of a transaction, our data management methods have a parameter ut of type UserTransaction. Before the entity manager can invoke the database write method persist, a transaction needs to be started with ut.begin(). After all write (and state change) operations have been performed, the transaction is completed (and all changes are committed) with ut.commit().
4.3 Step 2 – Write the Model Code
65
4.3.1 Storing Book objects in a database table books The instances of our entity class Book are Java objects representing “entities” (or business objects), which can be serialized, or, in other words, converted to records (or rows) of a database table, as shown in Table 4.1. SQL The Structured Query Language (SQL), created in the early 1970s by Donald D. Chamberlin and Raymond F. Boyce, is an ISO standard that is based on the Relational Database model of Edgar F. Codd and defines 1. a Data Definition Language (based on CREATE TABLE statements) for creating databases consisting of tables containing primitive data; 2. a Data Manipulation Language (based on INSERT, UPDATE and DELETE statements) for creating, updating and deleting the contents of database tables; 3. a Query Language (based on SELECT statements) for retrieving the information stored in database tables. SQL is widely used in Database Management Systems (DBMSs) and in programming languages for persistent data storage. Despite the existence of the SQL standard, each SQL DBMS has its own dialect of SQL. Consequently, SQL code is in practice not completely portable among different DBMSs without adjustments.
The data storage technology used in our example app is MySQL⁹, and the SQL code used to create the schema for the database table books is the following: CREATE TABLE IF NOT EXISTS books ( isbn VARCHAR(10) NOT NULL PRIMARY KEY, title VARCHAR(128), year SMALLINT );
While it is also possible to create the database schema manually (with the help of CREATE TABLE statements such as the one above), we show below how the database schema can be automatically generated by JPA. In both cases, the database setup, including a user account and the associated rights (create, update, etc), must be done manually before the JPA application can connect to it.
4.3.2 Creating a new Book instance and storing it The Book.create method takes care of creating a new Book instance and saving it to a database with the help of an ’entity manager’:
http://www.mysql.com/
66
4 Building a Minimal Web App with Java EE in Seven Steps
public static void create( EntityManager em, UserTransaction ut, String isbn, String title, int year) throws Exception { ut.begin(); Book book = new Book( isbn, title, year); em.persist( book); ut.commit(); }
To store the new object, the persist method of the given ’entity manager’ is invoked. It is responsible for creating the corresponding SQL INSERT statement and executing it.
4.3.3 Retrieving all Book instances The instances of an entity class, such as Book, are retrieved from the database with the help of a corresponding query expressed in the Java Persistence Query Language (JPQL¹⁰). These queries are similar to SQL queries. They use class names Instead of table names, property names instead of column names, and object variables instead of row variables. In the Book.retrieveAll method, first a query asking for all Book instances is created, and then this query is executed with query.getResultList() assigning its answer set to the list variable books: public static List retrieveAll( EntityManager em) { Query query = em.createQuery( "SELECT b FROM Book b", Book.class); List books = query.getResultList(); return books; }
4.3.4 Updating a Book instance To update an existing Book instance, first we need to retrieve it from the database, by using em.find, and then set those attributes the value of which has changed: public static void update( EntityManager em, UserTransaction ut, String isbn, String title, int year) throws Exception { ut.begin(); Book book = em.find( Book.class, isbn); if (!title.equals( book.getTitle())) book.setTitle( title);
if (year != book.getYear()) book.setYear( year); ut.commit(); }
Notice that, when invoking the find method for retrieving an entity, the first argument must be a reference to the entity class concerned (here: Book.class), so the JPA runtime environment can identify the database table from which to retrieve the entity’s data. The second argument must be the value of the entity’s primary key. Notice that in the update case, we do not have to use persist for saving the changes. Saving is automatically managed by the JPA runtime environment when we complete the transaction with ut.commit().
4.3.5 Deleting a Book instance A book entity can be deleted from the database as shown in the following example code: public static void delete( EntityManager em, UserTransaction ut, String isbn) throws Exception { ut.begin(); Book book = em.find( Book.class, isbn); em.remove( book); ut.commit(); }
To delete an entity from the database, we first need to retrieve it with the help of the find method as in the update case. Then, the remove method has to be invoked by the ’entity manager’, and finally the transaction is completed with ut.commit().
4.3.6 Creating test data For being able to test our code, we may create some test data and save it in our database. We can use the following procedure for this: public static void generateTestData( EntityManager em, UserTransaction ut) throws Exception { Book book = null; Book.clearData( em, ut); // first clear the books table ut.begin(); book = new Book("006251587X","Weaving the Web", 2000); em.persist( book); book = new Book("0465026567","Gödel, Escher, Bach", 1999); em.persist( book); book = new Book("0465030793","I Am A Strange Loop", 2008);
68
4 Building a Minimal Web App with Java EE in Seven Steps
em.persist( book); ut.commit(); }
After clearing the database, we successively create 3 instances of the Book entity class and save them with the help of persist.
4.3.7 Clearing all data The following procedure clears our database by deleting all rows: public static void clearData( EntityManager em, UserTransaction ut) throws Exception { ut.begin(); Query deleteStatement = em.createQuery( "DELETE FROM Book"); deleteStatement.executeUpdate(); ut.commit(); }
JPA does not provide a direct method to drop the entire population of a specific class from the database. However, this can be easily obtained by using a JPQL statement as shown in the above code. The JPQL code can be read as: delete all rows from the database table associated with the entity class Book.
4.4 Step 3 – Configure the App In this section we show how to 1. configure an app to connect with a database in a controller class, 2. obtain the EntityManager and UserTransaction instances required for performing database operations, 3. wrap an app in a WAR file and deploy it to a web server for execution.
4.4.1 Create the EntityManager and UserTransaction objects A controller class contains the code that glues the views to the model, as well as all methods that do neither belong to the model nor to the view, like getting a connection with the database server. In our example app, this class is pl.c.BookController in the src/pl/c folder. JPA requires an EntityManager object for executing JPQL queries (with SELECT) and data manipulation statements (with INSERT, UPDATE and DELETE). Also, in order to perform database write operations, a UserTransaction object is required for starting and completing transactions. In a standalone application,
4.4 Step 3 – Configure the App
69
the programmer has to create an entity manager and a transaction manually, using a factory pattern as shown in the following code fragment: EntityManagerFactory emf = Persistence.createEntityManagerFactory("MinimalApp"); EntityManager em = emf.createEntityManager(); EntityTransaction et = em.getTransaction();
A JPA-enabled Java web application normally runs in an environment called “container” (in our case this is TomEE), which takes care of creating an EntityManager and a UserTransaction object if the right annotations are used. The code responsible for this is part of the controller class ( e. g., pl.c.BookController) since the controller is responsible for managing the database connections. public class BookController { @PersistenceContext( unitName="MinimalApp") private EntityManager em; @Resource() UserTransaction ut; public List getBooks() {…} public void refreshObject( Book book) {…} public String create( String isbn, String title, int year) {…} public String update( String isbn, String title, int year) {…} public String delete( String isbn) {…} }
A closer look at this code shows that it is sufficient to use the @PersistenceContext annotation and provide a unitName (see the next section) for obtaining an EntityManager instance at runtime. Also, obtaining a UserTransaction instance at runtime is as simple as using the @Resource annotation for the user transaction reference property ut. Not only that the required code is short and simple, but if the database type is changed (e. g. when we switch from MySQL to an Oracle database), this code remains the same.
4.4.2 Configure the JPA database connection In the previous section, discussing the BookController class, we have shown how to obtain the EntityManager and UserTransaction objects required for performing database operations. The @PersistenceContext annotation of the EntityManager reference property requires a unitName, which is just a name used for identifying the storage management configuration defined in the src/ META-INF/persistence.xml file. In our example app this file has the following content:
70
4 Building a Minimal Web App with Java EE in Seven Steps
pl.m.Book
jdbc/MinimalApp
The configuration name (“MinimalApp”) is defined by the name attribute of the persistence-unit element. This is the value we have to use for the unitName property of the @PersistenceContext annotation. The persistence-unit element has three content parts: 1. One or more class elements, each one containing the fully qualified name of an entity class of the app (like pl.m.Book in our example app). 2. A set of configuration property elements used for providing further configuration settings. 3. A jta-data-source element for specifying the configuration block in the config/TomEE.xml web server configuration file in the web server installation folder. In our persistence.xml file, two configuration properties have been set: – javax.persistence.schema-generation.database.action, with the possible values: none (default), create, drop-and-create and drop. It specifies if the database schema is to be automatically created and additionally allows to drop the existing tables before creating the new ones (with drop or drop-and-create).. – javax.persistence.schema-generation.create-source, with the possible values metadata (default), script, metadata-then-script and script-then-metadata. It specifies the source of information used to create the database schema. The value metadata enforces using the JPA annotations while the value script allows using an external script for defining the schema.
4.4 Step 3 – Configure the App
71
The jta-data-source element of our persistence.xml file refers to the Resource element with id value “MinimalApp” in the config/TomEE.xml file, which has the following content:
The Resource element contains the information required to connect with the database (user name, password, access URL and the Java class name of the connection driver). Notice that in order to use a connection driver, one needs to download the jar file for that specific technology and copy it into the lib subfolder of the TomEE installation folder. Finally, TomEE needs to be restarted in order to load the new driver. See also our instructions page¹¹, for additional details related to JDBC drivers, and in particular how to use the official MySQL JDBC driver “Connector/J”.
4.4.3 Create the main template The main template, called page.xhtml, is shown below. It has two sub-templates: 1. header.xhtml defines the general header information items (such as the application name) 2. footer.xhtml defines the general footer information items (such as a copyrights notice) Both sub-templates are included in the main template with the help of a ui:include element. We add all three template files to the WebContent/WEB-INF/ templates folder. The content of our HTML5-compliant main template page.xhtml is the following:
4 Building a Minimal Web App with Java EE in Seven Steps
Public Library
In the code, one can see that some HTML elements are used (e. g., title, header, main and footer) while others like h:head and ui:insert are not HTML elements, but have been defined by JSF. For instance, JSF defines its own head element h:head for injecting special HTML code such as script elements that load JavaScript code for HTTP messaging between the web browser and the back-end Java program by invoking methods of the JavaScript API XML HTTP Request (XHR). Our main template defines three content regions: header, main and footer. The header and footer regions are defined by sub-templates included with the help of the ui:include element. The header.xhtml sub-template contains the application menu, corresponding to the CRUD operations:
C
R
U
D
The footer.xhtml sub-template contains the “Back to main menu” link, which we like to show on each application page, except the index page:
Back to main menu
4.4 Step 3 – Configure the App
73
Notice that the h namespace must be defined once again in footer.xhtml even if it has already been defined in page.xhtml. In the footer template component, we define the “Back to main menu” operation, which is going to be injected into each page that uses this template. The main region is dynamic, and will be replaced with the content generated by a facelet template. JSF is using the following namespaces: – xmlns:ui="http://java.sun.com/jsf/facelets" for the JSF Facelets Tag Library providing templating elements (like ui:insert for specifying the region of a template where to inject the facelet content). – xmlns:h="http://java.sun.com/jsf/html" for the JSF HTML Tag Library providing JSF versions of HTML elements, which are then mapped to HTML elements. For example h:inputText, which is mapped to an HTML input element. – xmlns:f="http://java.sun.com/jsf/core" for the JSF Core Tag Library providing custom actions or elements that are independent of any particular render kit. For example, f:actionListener can be used to define a Java method which is executed when the user clicks a button. – xmlns:p="http://xmlns.jcp.org/jsf/passthrough" for using HTML attributes in JSF HTML elements and passing them through to the generated HTML. For example, with p:type in, an HTML5 input type attribute can be created in the generated HTML: . – xmlns:c="http://java.sun.com/jsp/jstl/core" for the JSTL Core Tag Library providing all kinds of features, like dealing with loops and defining variables. For example, we can use to create a variable named isbn which can then be used in the view code for conditional expressions. – xmlns:fn="http://java.sun.com/jsp/jstl/functions" for the JSTL Functions Tag Library providing various utility functions, such as string converters. For example, we can use fn:toUpperCase to convert a string to its uppercase representation.
4.4.4 Define the managed beans needed in facelets JavaBean classes, including entity classes, can be used for creating ’managed beans’ with the help of the @ManagedBean annotation, which allows defining the name of a variable for accessing the created bean in the view code, typically in an EL expression. In our example app, we want to access a Book bean as well as a BookController bean, therefore both classes have to be annotated as follows:
74
4 Building a Minimal Web App with Java EE in Seven Steps
@Entity @Table( name="books") @RequestScoped @ManagedBean( name="book") public class Book { … } @SessionScoped @ManagedBean( name="bookCtrl") public class BookController { … }
Notice how a lifetime scope can be specified for a managed bean with a scope annotation. In our example the book bean is @RequestScoped, this means the instance exists as long as the HTTP request and the associated response are being processed. The bookCtrl bean is @SessionScoped, which means it is created when the session starts, and destroyed when the session is closed. Other scopes are available, but in our example we only need these two.
4.4.5 Build the WAR file and deploy it to TomEE There are multiple ways in which we could compile the source code, create the war file and deploy it to a TomEE server. For example one can use Eclipse IDE¹² or Netbeans¹³, or one can use ANT and create a script that takes care of all these tasks. We provide an ANT¹⁴ script that allows to automatically create the structure of a Java web application, compile the Java source code, build the WAR file and deploy it to a TomEE web server. Our ANT script generates an Eclipse IDE compatible folder structure, so in case you want to use Eclipse, simply create a project from the existing application code. The ANT installation instructions, detailed description of the available tasks as well as download links are available on our instructions page¹⁵. For our “PublicLibrary” example application, we have used the ANT script and the following steps were needed: – create the application folder structure: ant create app -Dappname=publicLibrary -Dpkgname=pl. – compile the Java code and build the war file: ant war -Dappname=publicLibrary. – deploy the application to TomEE server: ant deploy -Dappname=publicLibrary. This operation also builds the war file, if it was not already created using the war task, as shown before. Hint: we do not recommend using spaces in folder names, but if for any reason, the application name needs to contain spaces, then it has to be enclosed in double quotes, e. g. create app -Dpgkname=hw -Dappname="Hellow World". In
this case it was required to provide the pkgname parameter, while “Hello World” is not a valid Java identifier, since it contains a white space.
4.5 Step 4 – Implement the Create Use Case The CRUD use case Create involves creating a new object in main memory and then saving it to persistent storage with the help of the create method. The corresponding create action method code from the src/pl/c/BookController.java is shown below: public class BookController { … public String create( String isbn, String title, int year) { try { Book.create( em, ut, isbn, title, year); // clear the form after saving the Book record FacesContext fContext = FacesContext.getCurrentInstance(); fContext.getExternalContext().getRequestMap().remove("book"); } catch ( Exception e) { e.printStackTrace(); } return "create"; } }
The BookController::create action method invokes the Book.create model class method for creating and saving a Book instance. It returns the name of the view file found in the same folder as the view that triggered the action. This file (create. xhtml in our case) will be displayed after executing the action. Using the FacesContext object, the form is cleared after creating a Book instance. The code of the create method in src/pl/m/Book.java is the following: public class Book { … public static void create( EntityManager em, UserTransaction ut, String isbn, String title, int year) throws Exception { ut.begin(); Book book = new Book( isbn, title, year); em.persist( book); ut.commit(); } }
Now we need to create the facelet template for the view of the Create use case, WebContent/views/books/create.xhtml. Such a facelet template essentially defines an HTML form with data binding and action binding.
76
4 Building a Minimal Web App with Java EE in Seven Steps
Data binding refers to the binding of model class properties to form (input or output) fields. For instance, in the following facelet code fragment, the entity property book.isbn is bound to the form input field “isbn”:
In JSF, for the inputText elements of a form, the id attribute is used with a given value, e. g., id="isbn". The rendered HTML5 input elements have both, the id and the name attributes, and their values are obtained by using the form id and element id values separated by a colon, i. e., id="createBookForm:isbn" and name="createBookForm:isbn". We can also see a first example of an expression in the Java EE Expression Language (EL), where an expression starts with # and is enclosed in curly brackets, like # {expression}. Such an expression allows reading the value of a property of, or invoking a method on, a Java bean or a context object. The value of the expression is assigned to the value attribute of the generated HTML input element. The example in our JSF code above is the expression #{book.isbn}, which retrieves the value of the isbn property of the book bean. Action binding refers to the binding of method invocation expressions to actionable UI elements, where the invoked methods typically are controller action methods, and the actionable UI elements typically are form buttons. For instance, in the following facelet code fragment, the method invocation expression bookCtrl. create(…) is bound to the form’s submit button:
After discussing data binding and action binding, it’s time to look at the complete code of the facelet:
Create a new book record
4.6 Step 5 – Implement the Retrieve/List All Use Case
77
This facelet replaces the main region of the template defined in page.xhtml, because the name attribute of the ui:define element has been set to “main”, but it also replaces the headerTitle region of the template, which is part of the header and displays the current operations allowed by the page. h:outputLabel elements can be used for creating form field labels, while h: inputText elements are used for creating HTML input elements. It is possible to specify an HTML5 type of an input element by using a special namespace prefix (xmlns:p = "http://xmlns.jcp.org/jsf/passthrough") for the type attribute, enforcing it to be ’passed through’. In this way the year input field can be defined with type number, so it’s rendered by the corresponding number widget in the browser. The h:commandButton element allows creating submit buttons rendered as a input elements with type="submit", and binding them to an action to be performed when the button is clicked. The value of the action attribute is a method invocation expression. In our Create use case we want that, when the button is clicked, a Book instance with the property values provided by corresponding form fields is created and saved.
4.6 Step 5 – Implement the Retrieve/List All Use Case For the Retrieve use case we have to add a method in the controller class (src/pl/ c/BookController.java file), which reads all Book records from the books database table and then delivers this information to the view. The controller action method code is shown below: public class BookController { … public List getBooks() { return Book.retrieveAll( em);
78
4 Building a Minimal Web App with Java EE in Seven Steps
} … }
The getBooks method returns a list of Book instances which are obtained by calling the static retrieveAll method of the Book model class. The code of the Book.retrieveAll method is: public class Book { … public static List retrieveAll( EntityManager em) { Query query = em.createQuery( "SELECT b FROM Book b"); List books = query.getResultList(); return books; } … }
The code is simple, and as already discussed in Section 3.3, it uses a JPQL statement to retrieve the Book records from the books table and create the corresponding Book instances. The EntityManager object required for being able to perform the JPQL query is passed to Book.retrieveAll from the BookController object as discussed in Section 4.1 section. Now it is the time to define the facelet template for displaying a table with all book records found in the database. The view template files corresponding to the model classes of our app are located in model class subfolders of the WebContent/views/ folder. For the Retrieve/list all books use case, we create a file named retrieveAndListAll.xhtml in WebContent/views/books/ with the following content:
Retrieve and list all book records
ISBN #{b.isbn}
Title #{b.title}
Year
4.7 Step 6 – Implement the Update Use Case
79
#{b.year}
The ui:composition element specifies which template is applied (i. e. template="/WEB-INF/templates/page.xhtml") and which view block () is replaced by this facelet at render time. The h:dataTable element defines a table view for a set of records, which is then rendered as an HTML table. Its value attribute defines a data binding to a record collection, while the var attribute defines a variable name for iteratively accessing records from this collection. The expression provided in the value attribute normally specifies a collection-valued property (here: books) which is accessed via a corresponding getter (here: getBooks) as defined in the controller class BookController. In this particular case it is just sufficient to define the getBooks method since there is no need of a books property in the controller class. In any case, value does not allow to invoke a method, so we cannot call getBooks directly. Instead we have to use the (possibly virtual) property books, which internally evaluates to a call of getBooks without checking if a books property really exists. The h:button element allows to create redirect buttons. The value of the outcome attribute specifies a name of a facelet file (omitting the .xhtml extension, such that, in the example, the file name is index.xhtml).
4.7 Step 6 – Implement the Update Use Case Like for Create, for the Update use case also a controller action method is defined in the BookController class: public class BookController { … public String update( String isbn, String title, int year) { try { Book.update( em, ut, isbn, title, year); } catch ( Exception e) { e.printStackTrace(); } return "update"; } … }
The Book.update takes care of saving property value changes for a book object identified by its isbn value as shown below:
80
4 Building a Minimal Web App with Java EE in Seven Steps
public class Book { … public static void update( EntityManager em, UserTransaction ut, String isbn, String title, int year) throws Exception { ut.begin(); Book book = em.find( Book.class, isbn); if (title != null && !title.equals( book.title)) { book.setTitle( title); } if (year != book.year) { book.setYear( year); } ut.commit(); } … }
Now, we create the view where a Book can be selected so the user can edit the title and year properties, and then save the changes. The code for this view is stored in the WebContent/views/books/update.xhtml file which has the following content:
Update a book record
4.7 Step 6 – Implement the Update Use Case
81
In this facelet template, a single selection list (that is, a single-select HTML element) is created with the help of the JSF element h:selectOneMenu, where the selection list items (the HTML option elements) are defined by the JSF elements f: selectItem or f:selectItems. The value attribute of h:selectOneMenu binds book.isbn to the value of the selected item (or option element). The selection list is populated with book records with the help of a f:selectItems element bound to bookCtrl.books. The attributes itemLabel and itemValue define the option elements’ text and value. In the update view, when the user selects a book from the selection list, the form fields are filled with the (ISBN, title and year) property values of the selected book. While the ISBN is immediately available in the view (on the front-end) as the value of the selected option element, the values of the title and year properties have to be fetched from the back-end database. This can be done with the help of the JSF element f:ajax, which sends an HTTP request message for invoking a remote method, bookCtrl.refreshObject, on the back-end using XHR. This method takes the managed book bean, and updates its title and year properties with the current values retrieved from the database. Its code is the following: public class BookController { … public void refreshObject( Book book) { Book foundBook = Book.retrieve( em, book.getIsbn()); book.setTitle( foundBook.getTitle()); book.setYear( foundBook.getYear()); } … }
To enforce a refresh of the HTML form after the user’s selection, such that it displays the values of isbn, title and year, the f:ajax element allows specifying form fields to be updated with the render attribute like, in our case, render="isbn title year". Finally, the h:commandButton element is used for invoking the update action method of the BookController with the parameters isbn, title and year, for making the changes persistent.
82
4 Building a Minimal Web App with Java EE in Seven Steps
4.8 Step 7 – Implement the Delete Use Case For the Delete use case, the delete action method of the BookController invokes the Book.delete method by providing the ISBN of the Book object to be deleted: public class BookController { … public String delete( String isbn) { try { Book.delete( em, ut, isbn); } catch ( Exception e) { e.printStackTrace(); } return "delete"; } … }
The Book.delete method first retrieves the Book object to be deleted, and then invokes the entity manager’s remove method on it: public class Book { … public static void delete( EntityManager em, UserTransaction ut, String isbn) throws Exception, HeuristicRollbackException, RollbackException { ut.begin(); Book book = em.find( Book.class, isbn); em.remove( book); ut.commit(); } … }
The view for the Delete action provides a selection list for selecting the book to be deleted. A “Delete” button allows performing the deletion of the selected book. The code of the view in WebContent/views/books/delete.xhtml is as follows:
Delete a book record
4.9 Style the User Interface with CSS
83
As in the Update use case, a h:selectOneMenu element is used to create and populate a selection list containing all the books to choose from. Clicking on the “Delete” command button results in invoking the delete action method of the controller with the isbn value of the selected book, thus resulting in the deletion of the Book object from the database.
4.9 Style the User Interface with CSS We style the UI with the help of the HTML5 Boilerplate¹⁶ CSS and the browser style normalization file normalize.css¹⁷. The two CSS files main.css and normalize. min.css are located in the WebContent/resources/css folder. Adding the CSS to the HTML pages requires to edit the WebContent/WEB-INF/ templates/page.xhtml file and add the style elements referencing the CSS files:
Public Library
Notice that we also need to add the CSS to the WebContent/views/books/ index.xhtml file, which is not generated with the page.xhtml template. The facesContext.externalContext.requestContextPath expression allows
4 Building a Minimal Web App with Java EE in Seven Steps
to get the path to WebContent folder. We then need to append the rest of the path, including the CSS file name, for each of the CSS files. With JSF, there are two main ways to apply CSS styles to specific elements. For the elements created with JSF, we need to use the @styleClass attribute, e. g.:
…
For the HTML elements, we simply use the CSS classes, as in any normal HTML document, e. g.:
…
For creating advanced user interfaces, additional block elements that act as containers are used for defining paddings, margins, various background colors and other styles. Thus, in various HTML pages you may find DOM structures as shown below, where the div element containing the @class attribute is used as container for styling purposes:
4.10 Run the App and Get the Code You can run the minimal app¹⁸ (or run the minimal app with CSS¹⁹) on our server or download the code²⁰ as a ZIP archive file. Follow our instructions²¹ to get your environment prepared for running back-end Java EE web apps with JPA and JSF.
4.11 Possible Variations and Extensions 4.11.1 Using resource URLs Whenever an app provides public information about entities, such as the books available in a public library, it is desirable to publish this information with the help of self-descriptive resource URLs, such as http://publiclibrary.norfolk.city/books/006251587X, which would be the resource URL for retrieving information about the book “Weaving the Web” available in the public library of Norfolk. However, resource URLs are not supported by JSF. In the Java world, we would have to use JAX-RS, instead of JSF, for programming a web API with resource URLs. But this would imply that we need to take care of the front-end UI in a different way, since JAX-RS is a pure back-end API, not providing any support for building user interfaces. A natural option would be to use a JavaScript front-end framework, such as BackboneJS or ReactJS, for rendering the UI.
4.11.2 Using an alternative DBMS Instead of MySQL, one can use various other database management systems for persistent storage. The following four steps are required to specify the used DBMS (only one DBMS at a time is possible): – Configure TomEE so it uses the corresponding resource for your application. For a list of resource configuration examples used for common DBMS’s, check Common DataSource Configurations²². For example, if PostgreSQL was chosen as DBMS, then edit the conf/tomee.xml file and add the following code:
JdbcDriver JdbcUrl
org.postgresql.Driver
jdbc:postgresql://host/database-name
UserName
dbms-username
Password
user-password
– – –
Copy the jar file corresponding to the DBMS driver implementation to the lib folder. After this, the TomEE server needs to be restarted. Install the DBMS, if not already installed. Installation instructions are usually available on the corresponding DBMS web page. Create the corresponding DBMS user and database to be used for your application.
4 Building a Minimal Web App with Java EE in Seven Steps
4.12 Points of Attention The code of this app should be extended by adding constraint validation. We show how to do this in Chapter 9. We briefly discuss three further issues.
4.12.1 Boilerplate code Another issue with the code of this Java example app is the repetitious boilerplate code needed per entity class for the storage management methods create, retrieve, update and delete, and their wrappers in the corresponding controller classes. While it may be instructive to write this code a few times for learning app development, you don’t want to write it again and again later when you work on real projects.
4.12.2 Offline availability It is desirable that a web app can still be used when the user is offline. However, this is not possible with a back-end web application architecture as implied by Java EE. Offline availability can only be obtained with a truly distributed architecture where essential application components can be executed both on the back-end and on the front-end.
4.12.3 Architectural separation of concerns From an architectural point of view, it is important to keep the app’s model classes independent of 1. the user interface (UI) code because it should be possible to re-use the same model classes with different UI technologies; 2. the storage management code because it should be possible to re-use the same model classes with different storage technologies. We have kept the model class Book independent of the UI code, since it does not contain any references to UI elements, nor does it invoke any view method. However, for simplicity, we didn’t keep it independent of storage management code. First, due to using JPA annotations, the model class is bound to the JPA object-to-storage mapping technology. Second, we have included the method definitions for create, update, delete, etc., which invoke the storage management methods of a JPA environment’s entity manager, in the model class. Therefore, the separation of concerns is incomplete in our minimal app.
4.13 Practice Projects
87
4.13 Practice Projects In most parts of the following projects you can follow, or even copy, the code of the book data management app discussed above. For developing the apps, simply follow the sequence of seven steps described above: 1. Step 1 – Set up the Folder Structure 2. Step 2 – Write the Model Code 3. Step 3 – Configure and Initialize the Application 4. Step 4 – Implement the Retrieve/List All Use Case 5. Step 5 – Implement the Create Use Case 6. Step 6 – Implement the Update Use Case 7. Step 7 – Implement the Delete Use Case Make sure that international characters are supported by using UTF-8 encoding for all HTML files.
4.13.1 Project 1 – Managing information about movies The purpose of the app is managing information about movies. The app deals with just one object type: Movie, as depicted in the following class diagram:
Notice that in most parts of this project you can follow, or even copy, the code of the book data management app, except that in the Movie class there is an attribute with range Date, so you have to find out how to handle such an attribute. You can use the sample data shown in Table 3.2 for testing your app.
4.13.2 Project 2 – Managing information about countries The purpose of the app to be developed is managing information about countries. The app deals with just one object type: Country, as depicted in the following class diagram:
88
4 Building a Minimal Web App with Java EE in Seven Steps
You can use the sample data shown in Table 3.3 for testing your app.
4.14 Quiz Questions If you would like to look up the answers for the following quiz questions, you can check our discussion forum²³. If you don’t find an answer in the forum, you may create a post asking for an answer to a particular question.
4.14.1 Question 1: Casting types in Java Which 1. ☐ 2. ☐ 3. ☐ 4. ☐
of the following are valid type casts in Java? int x = Integer.parseInt("123"); int x = (int) Double.parseDouble("123.47"); int x = (int) 123.47; int x = (int) "123";
4.14.2 Question 2: JPA standard identifier property Complete the following code, so that the Book JPA entity class contains a property named isbn, with String type and playing the role of the standard identifier: @Entity @Table( name="books") public class Book { @_________________ ___________ isbn; }
4.14.3 Question 3: Valid Java Bean classes Which of the following classes are valid JavaBean classes:
☐ public class Person { private String name; public String getName() {return this.name;} public void setName( String n) {this.name = } ☐ public class Person { private String name; public Person() {} public Person( String n) {this.setName(n);} public String getName() {return this.name;} public void setName( String n) {this.name = } ☐ public class Person { private String name; public Person( String n) {this.setName(n);} public String getName() {return this.name;} public void setName( String n) {this.name = } ☐ public class Person { String name; public Person() {} public Person( String n) {this.setName(n);} public String getName() {return this.name;} public void setName( String n) {this.name = }
89
n;}
n;}
n;}
n;}
4.14.4 Question 4: HTML5 attributes with JSF forms Complete the following code with the JSF construct which results in using date as value of the @type attribute of the rendered input HTML5 element.:
…
…
90
4 Building a Minimal Web App with Java EE in Seven Steps
4.14.5 Question 5: JPQL query Consider a JPA entity class named Customer. Write down the JPQL query to extract all its instances managed by the EntityManager (e. g., stored in DB or memory storage): SELECT c FROM _____________________________________
5 Information Modeling UML The Unified Modeling Language (UML¹), created in the mid-1990s, is an industry standard that defines a set of modeling languages for making various kinds of models and diagrams in support of object-oriented problem analysis and software design. Its core languages are Class Diagrams for information/data modeling, and Sequence Diagrams, Activity Diagrams and State Diagrams (or State Charts) for process/behavior modeling.
UML class diagrams provide a visual syntax for expressing UML class models, which allow defining information and data models. They can be used both at the more abstract level of conceptual modeling for requirements engineering and at the more detailed level of design modeling for designing the model classes of an app. Their main building blocks are class rectangles and association lines. A class rectangle has one, two or three compartments, containing the name of the class, its properties, and its methods. The purpose of a class is to classify objects and to define their properties and the methods that can be invoked on them. RDF and OWL The Resource Description Framework (RDF²) has been defined by the W3C in 2004 as a logical formalism that allows (1) formalizing information models in the form of RDF vocabularies, and (2) representing propositional information (e. g., meta-data) on the Web. The core of a UML class model can be expressed as an RDF vocabulary. However, many types of integrity constraints cannot be expressed in RDF. The W3C has therefore defined an extension of RDF, called the Web Ontology Language (OWL³), which allows to formalize the core logic (classes, properties and integrity constraints) of a UML class model in the form of an OWL ontology.
An association line connects two class rectangles. The purpose of an association is to classify relationships (links) between objects. While UML classes have a direct counterpart in the class concepts of object-oriented programming (OOP) languages, UML associations do not have such a direct OOP counterpart. They are therefore often more difficult to understand for developers. Only in the special case of a unidirectional functional association there is a direct OOP counterpart: a reference property for referencing the objects that are linked to a given object by the association. From a logical point of view, a class model defines a vocabulary, or language, for expressing various types of fact statements about objects. The knowledge representation languages RDF and OWL allow to formalize the vocabularies defined by class models and the fact statements made when instantiating their classes by creating objects. In this way, they help to understand the semantics of information models.
5.1 Classes with Properties and Methods In a UML class diagram, a class has a name (shown in the first compartment of the class rectangle), and it may have properties (shown in the second compartment) and methods (shown in the third compartment). Properties and methods may be described with or without details. The following diagrams illustrate these options using the example of a class books or Book for describing books as information objects. A class can be expressed in UML by just providing its name, without any further detail, like so
This option is useful for making sketches and overview diagrams. Using an ordinary English plural name like books makes the class diagram more readable for nontech-savvy people. A more informative description of a class is obtained by listing its properties, possibly without any further detail, like in the following example:
However, for better understanding the meaning of properties and for being able to code a class in an OO programming language, we need to know the range of each property, which is the type of its values. The range of a property can be either a primitive datatype or another class. In the following diagram we use general implementation-agnostic datatype names (like “Integer”), for which a specific programming language may have specific names (like “int” in Java). Notice that we now use a common OOP naming convention of giving classes a capitalized singular (mixed-case) name like Book (or LearningUnit). This allows saying that “an instance of a class C is a C (object)”, like “an instance of Book is a book (object)”.
Notice how the standard identifier attribute isbn is marked with the keyword id appended to the property declaration in curly braces. This is the UML syntax for defining several kinds of property constraints discussed in the next chapter.
5.1 Classes with Properties and Methods
93
Finally, we can also define the methods and functions of a class in a third compartment, like so:
In this example, the Book class has a function checkISBN, which returns a string. Given a class diagram in this form, it is straightforward to code it in an OO programming language like JavaScript or Java. Recall that in JavaScript a class is defined in the form of a constructor function that assigns the values of its parameters to the properties of the newly created object, like so: // **JavaScript code** var Book = function (i, t, y) { this.isbn = i; // string this.title = t; // string this.year = y; // number (integer) }
In JavaScript, the (instance-level) methods of a class are defined as method slots of the constructor’s built-in prototype object. This is how we code the checkISBN method: // **JavaScript code** Book.prototype.checkISBN = function () { // regular expression pattern matching test if (!/\b\d{9}(\d|X)\b/.test( this.isbn)) { return "The ISBN must be a 10-digit string or " + "a 9-digit string followed by 'X'!"; } else return ""; }
If we don’t have to care about older web browsers, such as Internet Explorer 9, we can also use the new class definition syntax (introduced in the ES6 version of JavaScript) and combine the definition of properties and methods in one piece of code: class Book { // **JavaScript (ES6) code** constructor( i, t, y) { this.isbn = i; // string this.title = t; // string this.year = y; // number (integer) } // instance-level methods checkISBN() {
94
5 Information Modeling
// regular expression pattern matching test if (!/\b\d{9}(\d|X)\b/.test( this.isbn)) { return "The ISBN must be a 10-digit string or " + "a 9-digit string followed by 'X'!"; } else return ""; } }
As opposed to JavaScript, Java has always had a language element class for defining classes: public class Book { // **Java code** private String isbn; private String title; private int year; // Constructor public Book( String i, String t, int y) { this.isbn = i; this.title = t; this.year = y; } // instance-level methods public checkISBN() { … } }
We need to be aware of the ambiguity of the term “object”. We have to distinguish between objects in the sense of real-world objects (also called “business objects” or “entities”) and objects in an OO program, such as JS objects or Java objects. When we want to manage information about business objects of some type in an app, we represent them in the form of JS/Java objects instantiating a JS/Java class that represents their (business) object type. We call these classes model classes for two reasons: first because they implement the classes defined in an app’s data model, and second because they represent the ‘model’ part of an app’s ModelView-Controller codebase architecture. Therefore, in a JS/Java app, a business object is a JS/Java object, but not every JS/ Java object represents a business object because we use JS/Java objects for many purposes (e. g., in JavaScript, an array is a a JS object, but it’s not a business object). The same applies to classes: (business) object types are represented as model classes, but not every JS/Java class is a model class because we may use JS/Java classes also for other purposes (e. g., in Java, a class can be used as a container for a method library, but such a class is not a model class).
5.3 From a Conceptual Model via a Design Model to Class Models
95
5.2 Connecting Classes with Associations Whenever an app has to manage the data of more than one object type, it is very likely that there are associations between some of them. For instance, in the following class diagram, there is an association between publishers and books and an association between books and people as authors.
An association between two classes can be read in both directions. The association between publishers and books associates 1. the books published by a publisher with this publisher, as indicated by the association end name published books, 2. inversely (from right to left), the publisher of a book with this book. The association between books and people as authors associates 1. the authors of a book with this book, as indicated by the association end name authors, 2. inversely (from right to left), the books authored by a person with this person, as indicated by the association end name authored books. As will be discussed in Volume 2, associations are characterized by multiplicity constraints, which restrict the possibilities of how many objects of the associated class can be linked to an object of the given class. In our example, we have a one-to-many association between publishers and books and a many-to-many association between books and people. For keeping things simple, we only include one object type and no association in the apps discussed in this volume of the book. In Volume 2, we will discuss how to model associations and how to implement them.
5.3 From a Conceptual Model via a Design Model to Class Models In a new development project, we start our analysis and modeling effort with making a conceptual information model. This type of model is also called domain model since it describes the entities of a given (real-world) problem domain, and does not model software entities. Recall the conceptual information model for books obtained as the result of the inception phase:
96
5 Information Modeling
Taking this conceptual model as a starting point, we have to make a number of design decisions for obtaining an information design model: 1. What are the ranges of the properties isbn, title and year? 2. Which property is the standard identifier (ID) attribute? 3. Which constraints should be defined? 4. Which methods/functions should be part of the design? The result of this design phase is a design model like the following:
It is important to understand that such a design model provides an implementationagnostic (platform-independent) computational design, that is, it does not use any concept or syntax of any specific programming language or technology. Therefore, the same design model can be used for deriving different platform-specific implementation models for different programming languages or technologies, such as for a Java- or PHP-based framework, or for a plain JavaScript approach. Based on the design model, by replacing the platform-independent datatype names with JavaScript-specific datatype names, and by adding “setter” methods, we obtain the following JavaScript implementation model, which we prefer to call a JavaScript class model:
Notice that in this model, we have used the JavaScript datatypes string and number, and we have added the methods setISBN, setTitle and setYear. These “setter” methods are supposed to be used for setting a property to a new value, instead of directly assigning the value to the property.
5.4 Excursion: Formalizing Information Models with RDF and OWL
97
Having a setter method for each property is a best-practice approach that allows more control over property value assignments. For instance, we could check the validity of values before they are assigned, or we could notify other modules of the app about the assignment event. The implementation phase consists of making an implementation model for a specific technology platform, and then coding this model and testing the resulting program code. In this book, we make both JavaScript class models and Java class models, which are subsequently coded in plain JavaScript and in Java EE, respectively. The entire transformation chain, from a conceptual model via a design model to a JavaScript class model (as a special type of implementation model), is summarized in the following figure.
Figure 5.1 From a conceptual model via a design model to a JavaScript class model
In summary, the process of model-based development takes a conceptual model as the starting point for making a general (platform-independent) design model, from which one or more implementation models for a (set of) specific target technologies can be derived. Typically, they include a class model for an object-oriented programming language and a database model for an SQL DBMS. This process is illustrated by the following diagram:
5.4 Excursion: Formalizing Information Models with RDF and OWL The Resource Description Framework (RDF), together with its extension RDF Schema, is a logical formalism that allows 1. formalizing information models in the form of RDF vocabularies consisting of class definitions and property definitions, where both class names and property names are URIs (representing globally unique identifiers); 2. representing propositional information (in the form of statements about individuals) on the Web, embedded in web pages or in the form of special web data sources.
98
5 Information Modeling
Figure 5.2 From a conceptual model via design models to implementation models
RDF is the basis of the Semantic Web. It has several syntaxes, including the textual XML-based syntax of RDF/XML and the visual syntax of RDF Graphs.
5.4.1 RDF vocabularies Consider the Book class defined in the following class diagram
The corresponding RDF vocabulary, with one class definition and three property definitions, is defined in the following RDF graph:
5.4 Excursion: Formalizing Information Models with RDF and OWL
99
In an RDF graph, nodes with an elliptic shape represent “resources” (like properties and classes), and arrows represent relationships defined by a property. Each arrow between two nodes represents a statement (also called “triple”). For instance the rdf:range arrow between year and xs:int represents the statement that the range of the property year is the XML Schema datatype xs:int, where xs is a namespace prefix for the XML Schema namespace. Notice that RDF has the predefined meta-classes rdfs:Class and rdf:Property, used to define classes and their properties with the help of the predefined property rdf:type. For instance the rdfs:type arrow between year and rdf: Property represents the statement that year is of type rdf:Property, that is, it is defined to be an RDF property. RDF graphs are a formalism for theoretical purposes. They can be used for illustrating simple examples. As opposed to UML class diagrams, they are not useful for visually expressing realistic vocabularies, due to their convolution and unnecessary visual complexity. The domain of a property has to be defined explicitly in an RDF vocabulary (with an rdfs:domain property statement), as opposed to a UML class diagram where it is defined implicitly. While it is natural to define properties in the context of a class, as in UML, RDF allows defining properties independently of any class. The RDF/XML syntax allows publishing an RDF vocabulary on the Web. For instance, the simple Book vocabulary defined in the RDF graph above, can be represented by the following RDF/XML document:
Notice that the values of the rdf:resource attribute must be URIs. If an attribute value is a fragment identifier like #Book, it represents a relative URI and is resolved into a full URI by appending the fragment identifier to the in-scope base URI, which may be defined with the xml:base attribute. If an attribute value is an absolute URI like “http://www.w3.org/2001/XMLSche ma#string”, it contains a full namespace URI (like “http://www.w3.org/2001/ XMLSchema”), even if a namespace prefix (like “xsd” or “xs”) is defined for it. This is because namespace prefixes can only be used for XML element and attribute names, but not for attribute values, which unfortunately makes RDF/XML hard to read for human users. Notice that the RDF formalization of our simple UML class model above has several shortcomings: 1. It does not express the constraints that all three properties are mandatory and single-valued, which they are by default in UML. 2. It does not express the constraints that the ISBN property, as a standard identifier (or primary key) attribute, is mandatory and unique. We show how to solve these two issues with the greater expressivity of OWL below.
5.4.2 RDF fact statements The propositional information items, or fact statements, expressible with RDF are 1. classification statements like “ex:Book is a rdfs:Class” or “urn: isbn:006251587X is a ex:Book”, and 2. property statements of the sort “the ex:isbn property value of urn: isbn:006251587X is ’006251587X’”.
5.4 Excursion: Formalizing Information Models with RDF and OWL
101
Consequently, for a UML object definition like
we obtain several RDF fact statements: 1. the classification statement
which can alternatively be expressed in a more concise way as
2. the three property statements
006251587X Weaving the Web 2000
which can also be merged into one rdf:Description element:
006251587X Weaving the Web 2000
5.4.3 Expressing structured data in web documents There are many use cases for machine-readable data (e. g., about people, events, products, etc.) embedded in web documents. For instance, search engines like Google can use such structured data⁴ for providing more meaningful search results. Structured data, or meta-data, can be embedded in a web document by either adding a JSON-LD⁵ script element containing it, or by annotating the document’s content, e. g., the HTML elements of a web page, with RDFa⁶.
Very limited annotation approaches, called “microformats” (proposed around 2005), are the historic predecessors of the general annotation language RDFa, which is derived from RDF. Some microfomats, like vCard and vEvent, are still being used today, but they are increasingly replaced with one of the two general formats RDFa and JSON-LD. The main author of HTML5, Ian Hickson, has proposed an alternative general annotation language, called microdata⁷, with the goal to simplify RDFa and remedy its usability issues (in particular, by dropping its use of XML namespaces). Despite the (rather unfortunate) choice of using different names for the same annotation concepts (like “itemprop” instead of “property”), Hickson’s microdata proposal succeeded to show 1. how to get essentially the same annotation functionality at lower usability costs, and 2. how to integrate annotations with the DOM. Since Hickson ended his collaboration with the W3C, the microdata proposal did not succeed to get an official W3C status, and web browsers have discontinued their support for it. However, it triggered a W3C proposal to use the RDFa Lite subset of RDFa, which “can be applied to most simple to moderate structured data markup tasks, without burdening the authors with additional complexities”. We present a simple example for using structured data in a web page. Consider the following HTML fragment:
My name is Carly Rae Jepsen. Call me maybe at 1 – 800 – 2437715.
For this content, we may want to code the information that 1. the available information is about an entity of type Person, which has been defined as a class by the search engine standard vocabulary schema.org⁸; 2. the name of the person is “Carly Rae Jepsen”; 3. the telephone number of the person is “1– 800 – 2437715”. Using the RDFa attributes typeof, vocab and property, we can code this information by adding the following annotations to the HTML content:
My name is Carly Rae Jepsen. Call me maybe at 1 – 800 – 2437715.
5.4 Excursion: Formalizing Information Models with RDF and OWL
103
Using JSON-LD, as recommended by Google, we need to add a script element of type “application/ld+json” containing the meta-data:
The propositional information expressed with RDFa annotations and JSON-LD corresponds to the following RDF/XML code:
Carly Rae Jepsen 1 – 800 – 2437715
5.4.4 OWL vocabularies and constraints OWL extends RDF by adding many additional language elements for expressing constraints, equalities and derived classes and properties in the context of defining vocabularies. Facts are expressed as in RDF (e. g., with rdf:Description). OWL provides its own predefined language elements for defining classes and properties: 1. The predefined class owl:Class is a subclass of rdfs:Class. 2. The predefined class owl:DatatypeProperty is a subclass of rdf:Property. It classifies attributes. Therefore, the values of an owl:DatatypeProperty are data literals. 3. The predefined class owl:ObjectProperty is a subclass of rdf:Property. It classifies reference properties corresponding to unidirectional binary associations. Since the values of a reference property are object references, the values of an owl:ObjectProperty are object references in the form of resource URIs. We only show with the help of an example that an OWL vocabulary can represent a class diagram more faithfully than the corresponding RDF vocabulary by allowing to express certain constraints. Consider the standard identifier attribute isbn defined in the Book class. In an RDF vocabulary, this attribute is defined in the following way:
104
5 Information Modeling
There are two issues with this RDF definition of an attribute: 1. It doesn’t make it explicit that the property defined is an attribute, and not a reference property. This can only be inferred by finding out that the range class is a datatype, and not an object type. 2. It does not constrain the attribute to have exactly one value, as implied by the defaults of UML class diagram semantics. Using OWL, we can remedy these shortcomings of RDF. The following OWL property definition makes it explicit that the property http://example.org/ex1#isbn is an attribute, while the added OWL restriction defines an “exactly one” cardinality constraint for it:
1
Since the ISBN attribute of the Book class has been designated as the standard identifier attribute in the UML class diagram above, we should define a uniqueness constraint for it. We can do this by including an owl:hasKey element within the class definition:
5.4.5 Usability issues of RDF and OWL Both RDF and OWL have many usability issues. Especially OWL is so difficult to use that most potential users will be discouraged by it. Because OWL was created by a community that is more concerned with formal logic than with information modeling and is not familiar with the concepts and terminology established in information modeling, they have introduced many new unfamiliar terms for concepts that had already been established and named in information modeling. They have even introduced duplicate names within OWL: an attribute
5.5 Summary
105
is in most places called “data property”, but in some places it is called “datatype property” (specifically in OWL/RDF). Usability issues of RDF are: 1. For historical reasons, RDF comes with a strange jargon. Especially, its “subject”“predicate”-“object” terminology sucks. 2. For historical reasons, RDF comes with two different XML namespaces, typically in the from of the two namespace prefixes “rdf” and “rdfs”. The history of a language should not be imposed on its syntax. Users shouldn’t have to bother about which prefix to use. 3. RDF is using the uncommon term “IRI” (as an abbreviation of “International Resource Identifier”), following the unfortunate naming history from “URL” via “URI” to “IRI”, while the What Working Group’s URL Living Standard⁹ has reverted this naming history. 4. For practical purposes, RDF is incomplete: a. it does not make an explicit syntactic distinction between attributes (having a datatype as range) and reference properties (having an object type as range); b. it does not allow expressing simple class definitions, which include mandatory value and single-value constraints, in an RDF vocabulary. OWL is needed for getting these fundamental features. Usability issues of OWL are: 1. it uses an uncommon terminology: e. g., “data property” instead of attribute, “restriction” instead of constraint; 2. some of its elements have confusing names: e. g., “ObjectIntersectionOf” does not denote an intersection of objects, but of object types, and “DataSomeValuesFrom” actually refers to “some data values from”; 3. many of its language elements are kind of unnatural and hard to grasp (much less to remember): e. g., an exactly-one-value property constraint cannot be expressed in the definition of a class along with the property declaration, but requires a separate Restriction element (as shown above).
5.5 Summary 1.
The inception phase of a development project includes problem analysis and requirements engineering. The main goal in this phase is to make a conceptual information model that lays the foundations for the app’s information architecture, which is defined by an implementation-agnostic information design model made in the design phase. Then, in the implementation phase, a platform-specific data
https://url.spec.whatwg.org/
106
2.
3. 4.
5 Information Modeling
model is derived from the information design model and coded in the platform’s programming language. In the case of an OOP platform, platform-specific data models take the form of class models that are coded as a set of model classes. UML class diagrams provide a visual language for defining an information architecture. Their main building blocks are class rectangles and association lines (connecting class rectangles). A class rectangle has one, two or three compartments, containing the name of the class, its properties, and its methods. An association line connects two classes. The purpose of an association is to classify relationships (links) between objects. In the special case of a unidirectional functional association there is a direct OOP counterpart: a reference property for referencing the objects that are linked to a given object by the association.
5.6 Exercises Consider the problem of managing information about movies, like the Internet Movie Database¹⁰. 1. Describe an illustrative information fragment with the help of some sample data about 3 movies in the form of itemized lists. 2. Turn the itemized lists of sample data into corresponding data tables with table names and suitable column headings. 3. Make a conceptual information model describing movies, then derive an information design model from it and turn it into a JavaScript class model. 4. Finally, code the JavaScript class model in the form of JavaScript model classes.
http://www.imdb.com/
6 Application Architecture Web application systems, like any other complex engineering artifacts, need an architecture that helps master their complexity by breaking them down into smaller parts with manageable inter-dependencies. Software application architecture is concerned with the following issues: 1. The state structure of the application system to be designed is defined in the form of information models describing its information architecture. 2. The goal of a code base architecture is to structure and partition the overall program code of an application in such a way that the fundamental software quality goal of maintainability is achieved. 3. The distribution of the application system’s runtime components within a computer network is defined by a deployment architecture. A web app is distributed on the Web, which is a world-wide client-server network system with HTTP-based communication. As in all cases of designing a complex system, no matter if a new building, a new space shuttle, a new computer or a new software application is to be designed, an architecture provides a kind of master plan for defining the structure of the system. Any good architecture is based on the following three principles: 1. separation of concerns, which helps managing complexity by breaking a system down into smaller, functionally defined parts such that their 2. interdependencies are minimized, 3. keeping the more fundamental parts independent of the less fundamental parts (the onion principle). Complying with these principles makes it easier to develop, or source, and update certain system parts independently of others. The three most important parts of the code base of an app are: 1. the model, which implements the app’s data model in the form of model classes, defining suitable data structures and constraints; 2. the data storage management procedures, which set up a connection to a storage management system (typically, but not necessarily, an SQL database system) and use it for storing and retrieving persistent data; 3. the user interface (UI) code, taking care of both information provision (or output) to the user, e. g., on the computer screen, and user input provided by user actions in the form of UI events, e. g., keyboard or mouse events, such that all required user interactions are supported. The onion principle requires to keep the app’s model classes independent of – the UI code because it should be possible to re-use the same model classes with different UI technologies; https://doi.org/10.1515/9783110499957-006
108
–
6 Application Architecture
the storage management code because it should be possible to re-use the same model classes with different storage technologies.
However, in many web application frameworks (e. g., in Java EE) the onion principle is violated.
6.1 The Model-View-Controller (MVC) Architecture Metaphor The most popular and most widely used code base architecture approach is the Model-View-Controller code base partitioning pattern. Although it has not been precisely defined, and has been implemented in many different ways, especially in web application frameworks, it is based on the principle of separation of concerns and on the fundamental insight that the model is the foundation for all other parts of an application, in particular for the user interface. Consequently, even if the MVC approach doesn’t provide a precise definition of what a ’model’ is, we can consider it to be a model-based approach.
Figure 6.1 The MVC code base architecture with the model as the foundation.
According to Wikipedia, the first MVC architecture was introduced to application programming with Smalltalk-76 by Trygve Reenskaug¹ in the 1970s. In a later article about Smalltalk-80², MVC is explained as a “three-way division of an application” that entails “separating (1) the parts that represent the model of the underlying application domain from (2) the way the model is presented to the user and from (3) the way the user interacts with it”. The authors, who also use the term “MVC metaphor”, point out that their approach would allow programmers to “write an application model by first defining new classes that would embody the special application domain-specific information”. Notice that the model is defined to consist of classes that capture the required domain information. We call them model classes. In this original MVC approach, there is no well-defined concept of a user interface (UI). The ’view’ is defined as comprising the output side of a UI, only, while the user input side is separated from it and subsumed under the term ’controller’. This does not reflect how a UI is really organized: by combining certain forms of application output with certain forms of user input like two sides of the same coin. A general
6.1 The Model-View-Controller (MVC) Architecture Metaphor
109
UI concept includes both the output (the information output provided to the user, as well as system actions) and the input (including information input provided, as well as actions performed, by the user). The Smalltalk MVC metaphor was developed for (monochromatic) text-screenbased user interfaces with no general notion of UI events. This may explain why they did not consider an integral concept of a UI. While they distinguished between the state of objects in the model and their state in the UI, which are both in the scope of a user session, they did not consider the distinction between the model state and the database state. In his web essay GUI Architectures³ (2006), Martin Fowler summarizes the main principles of the original MVC approach in the following way: 1. Separation between UI and model. 2. Divide UI into a ’controller’ and ’view’. 3. Views are synchronized with the model (by means of a data binding mechanism). While the first and third principles are fundamental for the architecture of software applications, the second principle has just a historic meaning and was soon abandoned by the developers of Smalltalk. Compared to the 1980s, computers, human-computer interaction and software application architecture have evolved. In particular, the establishment of the Web as the predominant computing platform has made web browsers to be the most important infrastructure for user interfaces. The MVC terminology is still widely used today, especially by web application frameworks, but with different meanings ascribed to “M”, “V” and “C”. Typically, the “view” denotes the app’s code for the HTML-forms-based user interface, and the “controller” denotes the code that is in charge of mediating between the “view” and the “model”. In many MVC approaches, the “model” is tightly coupled with the underlying database technology, often via an object-relational mapping (ORM) approach mapping classes to tables and objects to table rows. This tight coupling between model classes and database tables violates both the fundamental principle of minimizing interdependencies (because there is no need that model classes depend on database tables) and the onion principle discussed below. For instance, in the Active Record⁴ paradigm of the influential Ruby-on-Rails framework, which has been adopted by many other web application frameworks (such as by CakePHP), the “model” is a direct representation of the schema of the underlying database system, where each entity table of the database is represented by a “model” class that inherits data manipulation methods for performing Create/
Retrieve/Update/Delete (CRUD) operations. In this table-to-model-class mapping approach, the “model” depends on the schema of the underlying database and is therefore tightly coupled with the underlying ORM technology. While this may be a suitable approach for a database-first development methodology, where an SQL database is the foundation of an application, it is certainly not a general approach and it turns the model into a secondary asset. Also in frameworks based on ORM annotations, such as Java EE with JPA annotations, the C# framework ASP.NET MVC with Entity Framework and Data Annotations, or the PHP framework Symfony with Doctrine annotations, the “model” is coupled with the underlying ORM technology through the ORM annotations woven into the model class code, thus making the model dependent on the ORM technology used. All these frameworks use the Data Mapper⁵ approach for performing CRUD operations based on ORM annotations.
6.2 The Onion Architecture Metaphor The term “onion architecture” was coined by Jeffry Palermo in a series of blog posts⁶ in 2008. The main principles of this architecture metaphor are (1) to use a hierarchy of dependencies, where less fundamental (or central) parts depend on more fundamental parts, but never the other way around, and (2) the most fundamental part is the model, which implements the application’s data model in the form of model classes while data storage is a separate and less fundamental part that must not be coupled with the model. In fact, Palermo and his followers put a lot more into this architecture metaphor, such as using “repository interfaces” and “service interfaces”, but this is not really essential for the onion metaphor. Also, they are using a different terminology. When they are using the term “domain model” instead of simply model, they are confusing the term “domain model” with “implementation of data model”, which is what model classes do. A data model is derived from an information design model, which may itself be derived from a domain information model. This is the basic development chain in model-based software engineering. In principle, a Data Mapper approach, if it is not based on a platform-specific ORM (annotation) technology, but rather on some form of platform-independent mapping logic, can be used for storage management in an onion architecture.
6.3 “Logical” versus “Physical” User Interface The idea of a logical UI model, also called view model, was first proposed (under a different name) by Martin Fowler in his post on the Presentation Model⁷ in 2004, where he stated that the view model “pulls the state and behavior of the view out into a [view] model class”. This means that the logical content of a UI is abstracted out from a concrete “physical” UI, which has specific renderings of the logical UI fields and “commands” (user actions). Logical UI fields are rendered in the form of UI widgets that may have their own state, while logical UI commands are rendered in the form of suitable UI events. Later, in 2005, the view model concept was adopted by John Gossman (from Microsoft) in his blog post Introduction to Model/View/ViewModel pattern for building WPF Apps⁸ and popularized under the architecture pattern acronym “MVVM”. In a user interface for a Create/Retrieve/Update/Delete (CRUD) data management operation, a view model class would be bound to exactly one model class, but could support more than one view (class). A view model class would have properties that are bound to the widgets of the supported view(s), using either one-way or two-way data binding, and methods that are bound to corresponding commands (command binding). Typically, most view fields directly correspond to properties of the underlying model class, although they may have a different name. For these fields (or view model properties), a data binding to the corresponding model properties is needed. But a view model class may also have additional properties, some of them may represent view fields that are not bound to a model class property, while others may represent auxiliary fields that are not shown in the UI. The methods of a view model class are invoked when a corresponding command has been issued by the user through creating a UI event, to which the command has been bound. Dividing up the overall UI code into a view model part and a view part creates a certain overhead that may not be justified in certain cases. While the use of a view model is justified for all apps with CRUD data management operations, it may, for instance, not be justified for visualization user interfaces. The main benefits of view models are that they facilitate: (1) UI design, (2) the testing of the UI logic, and (3) the maintenance of the UI.
6.4 MVC Web Applications The MVC terminology is still widely used today, especially by web application frameworks, but with different meanings ascribed to “M”, “V” and “C”. In some approaches it is extended by adding another layer, called “view model” abbreviated by “VM”, which we will discuss later in the course. In the original MVC proposal, the “model” consists of model classes implementing the app’s data model, while today many MV(VM)(C) frameworks do not include any concept of model classes, but rather refer to data objects (or records) as “models”. Also the term “controller” is ambiguous today. In the original MVC proposal, the term “controller” referred to the handling of UI events including user inputs. Today, it typically refers to all the glue code needed for mediating between the model and the view, including the code for routing. In summary, in today’s frameworks, the term “model” vaguely refers to the data sources of an app, while the “view” denotes the app’s code for the user interface, which is based on CSS-styled HTML forms and DOM events, and the “controller” typically denotes the (glue) code that is in charge of mediating between the view and the model. There is widespread agreement that 1. the model should be independent of the view and the controller, implying that the model must neither invoke the view nor the controller (in the model code, there should be no calls of any view or controller procedures); 2. the controller may invoke both the model and the view; 3. the view may invoke the controller. However, as shown in the following diagram, it is not clear if the view may also invoke the model, or if it needs to route any calls of model procedures via the controller. Both approaches are being used in practice. The first approach, where model procedures can be called from the view, is simpler and leads to a leaner controller.
The second approach, where the view is decoupled from the model by routing all calls to it via the controller, is called the mediating-controller MVC approach, shown in the following diagram. It has the advantage of fewer dependencies and providing better support for model-view synchronization based on data-bindings.
6.5 Deployment Architectures
113
In many MVC web application frameworks, the model is tightly coupled with the underlying data storage technology, often via an object-relational mapping (ORM) approach mapping classes to tables and objects to table rows. This tight coupling makes model classes dependent on a specific database technology or even on corresponding database tables. It therefore violates the onion principle, since the model is the most fundamental part of an app, so it must not depend on any other part.
6.5 Deployment Architectures In addition to an information architecture and a code base organization architecture (like MVC), we also have the cross-cutting issue of how to distribute the different parts of an app when it is deployed, so we also need a deployment architecture. Traditionally, most parts of a web app have been executed on back-end computers, while only its HTML/CSS user interface code has been executed on the front-end (or client) side, within a web browser. Today, with the rise of modern JavaScript, we have the option to replace traditional back-end web apps with JavaScript front-end apps, where most parts of the app are executed within a web browser on a frontend device. Such a front-end web app may use either local storage or a (remote) cloud storage service, as illustrated by the two architecture diagrams shown in Figure 6.2.
Figure 6.2 Architectures for front-end web apps
114
6 Application Architecture
In both cases, the JavaScript code of the front-end app is loaded together with its start web page. While using local data storage (with JavaScript’s localStorage or indexedDB APIs) does not require any connection to the Internet after the initial loading of the app, using cloud storage via HTTP messaging with the XMLHttpRequest (XHR) API does require an Internet connection. Typically, but not necessarily, a front-end web app is a single-user application, which is not shared with other users. A back-end web app is a web app where essentially all work is performed by the back-end component, including data validation and user interface page creation. It does not have any front-end component, except the HTML-forms-based, and possibly JS-enriched, user interface pages sent to the user’s front-end computer for being rendered by a web browser. One important task of the controller, especially in back-end apps, is routing: mapping incoming HTTP request messages to a use case of the app, typically represented by a method of a class, such that either the corresponding user interface is provided in the form of a web page or the request message represents the corresponding form submission, which can be directly processed. Today, with NodeJS, we have the option to deploy the JavaScript components of our app either on the back-end or on the front-end, or on both. In the future, due to WebAssembly, the same options may also be available to other programming languages. Web apps that run the same JavaScript code both on the back-end and front-end have been called “universal” (or “isomorphic”). This is especially useful for data validation code because in a responsive constraint validation approach, as discussed in Chapter 7, we validate user data immediately on input in the user interface, but we also validate it on the back-end before save. Modern JavaScript allows truly distributed apps. The two diagrams shown in Figure 6.3 illustrate the distinction between the traditional back-end web app architecture and the new architecture of a (truly) distributed web app.
Figure 6.3 Architectures for back-end and distributed web apps
Part II Constraint Validation For catching various cases of flawed data, we need to define suitable integrity constraints that can be used by the application’s data validation mechanisms. Integrity constraints may take many different forms. The most important type of integrity constraints are property constraints, which define conditions on the admissible property values of an object.
7 Integrity Constraints and Data Validation 7.1 Introduction For detecting non-admissible and inconsistent data and for preventing such data to be added to an application’s database, we need to define suitable integrity constraints that can be used by the application’s data validation mechanisms for catching these cases of flawed data. Integrity constraints are logical conditions that must be satisfied by the data entered by a user and stored in the application’s database. For instance, if an application is managing data about persons including their birth dates and their death dates, then we must make sure that for any person record with a death date, this date is not before that person’s birth date. Since integrity maintenance is fundamental in database management, the data definition language part of the relational database language SQL supports the definition of integrity constraints in various forms. On the other hand, however, there is hardly any support for integrity constraints and data validation in common programming languages such as PHP, Java, C# or JavaScript. It is therefore important to take a systematic approach to constraint validation in web application engineering, like choosing an application development framework that provides sufficient support for it. Unfortunately, many web application development frameworks do not provide sufficient support for defining integrity constraints and performing data validation. Integrity constraints should be defined in one (central) place in an app, and then be used for configuring the user interface and for validating data in different parts of the app, such as in the user interface and in the database. In terms of usability, the goals should be: 1. To prevent the user from entering invalid data in the user interface (UI) by limiting the input options, if possible. 2. To detect and reject invalid user input as early as possible by performing constraint validation in the UI for those UI widgets where invalid user input cannot be prevented by limiting the input options. 3. To prevent that invalid data pollutes the app’s main memory state and persistent database state by performing constraint validation also in the model layer and in the database. HTML5 provides support for validating user input in an HTML-forms-based user interface (UI). Here, the goal is to provide immediate feedback to the user whenever invalid data has been entered into a form field. This UI mechanism of responsive validation is an important feature of modern web applications. In traditional web applications, the back-end component validates the data and returns the validation results in the form of a set of error messages to the front-end. Only then, often several seconds later, and in the hard-to-digest form of a bulk message, does the user get the validation feedback. https://doi.org/10.1515/9783110499957-007
118
7 Integrity Constraints and Data Validation
7.2 Integrity Constraints Integrity constraints (or simply constraints) are logical conditions on the data of an app. They may take many different forms. The most important type of constraints, property constraints, define conditions on the admissible property values of an object. They are defined for an object type (or class) such that they apply to all objects of that type. We concentrate on the most important cases of property constraints:
String Length Constraints
require that the length of a string value for an attribute is less than a certain maximum number, or greater than a minimum number.
Mandatory Value Constraints
require that a property must have a value. For instance, a person must have a name, so the name attribute must not be empty.
Range Constraints
require that an attribute must have a value from the value space of the type that has been defined as its range. For instance, an integer attribute must not have the value “aaa”
Interval Constraints
require that the value of a numeric attribute must be in a specific inteval.
Pattern Constraints
require that a string attribute’s value must match a certain pattern defined by a regular expression.
Cardinality Constraints
apply to multi-valued properties, only, and require that the cardinality of a multi-valued property’s value set is not less than a given minimum cardinality or not greater than a given maximum cardinality.
Uniqueness Constraints require that a property’s value is unique among all instances of the given object type. Referential Integrity Constraints
require that the values of a reference property refer to an existing object in the range of the reference property.
Frozen Value Constraints
require that the value of a property must not be changed after it has been assigned initially.
The visual language of UML class diagrams supports defining integrity constraints either in a special way for special cases (like with predefined keywords), or, in the general case, with the help of invariants, which are conditions expressed either in plain English or in the Object Constraint Language (OCL) and shown in a special type of rectangle attached to the model element concerned. We use UML class diagrams for modeling constraints in design models that are independent of a specific programming language or technology platform. UML class diagrams provide special support for expressing multiplicity (or cardinality) constraints. This type of constraint allows to specify a lower multiplicity (minimum cardinality) or an upper multiplicity (maximum cardinality), or both, for a property or an association end. In UML, this takes the form of a multiplicity expression l..u where the lower multiplicity l is a non-negative integer and the upper multiplicity u is either a positive integer not smaller than l or the special value *
7.2 Integrity Constraints
119
standing for unbounded. For showing property multiplicity (or cardinality) constrains in a class diagram, multiplicity expressions are enclosed in brackets and appended to the property name, as shown in the Person class rectangle below. In the following sections, we discuss the different types of property constraints listed above in more detail. We also show how to express some of them in computational languages such as UML class diagrams, SQL table creation statements, JavaScript model class definitions, or the annotation-based languages Java Bean Validation annotations and ASP.NET Data Annotations. Any systematic approach to constraint validation also requires to define a set of error (or ’exception’) classes, including one for each of the standard property constraints listed above.
7.2.1 String Length Constraints The length of a string value for a property such as the title of a book may have to be constrained, typically rather by a maximum length, but possibly also by a minimum length. In an SQL table definition, a maximum string length can be specified in parenthesis appended to the SQL datatype CHAR or VARCHAR, as in VARCHAR(50). UML does not define any special way of expressing string length constraints in class diagrams. Of course, we always have the option to use an invariant for expressing any kind of constraint, but it seems preferable to use a simpler form of expressing these property constraints. One option is to append a maximum length, or both a minimum and a maximum length, in parenthesis to the datatype name, like so
Another option is to use min/max constraint keywords in the property modifier list:
7.2.2 Mandatory Value Constraints A mandatory value constraint requires that a property must have a value. This can be expressed in a UML class diagram with the help of a multiplicity constraint expression where the lower multiplicity is 1. For a single-valued property, this would result in the multiplicity expression 1..1, or the simplified expression 1, appended to the
120
7 Integrity Constraints and Data Validation
property name in brackets. For example, the following class diagram defines a mandatory value constraint for the property name:
Whenever a class rectangle does not show a multiplicity expression for a property, the property is mandatory (and single-valued), that is, the multiplicity expression 1 is the default for properties. In an SQL table creation statement, a mandatory value constraint is expressed in a table column definition by appending the key phrase NOT NULL to the column definition as in the following example: CREATE TABLE persons( name VARCHAR(30) NOT NULL, age INTEGER )
According to this table definition, any row of the persons table must have a value in the column name, but not necessarily in the column age. In JavaScript, we can code a mandatory value constraint by a class-level check function that tests if the provided argument evaluates to a value, as illustrated in the following example: Person.checkName = function (n) { if (n === undefined) { return "A name must be provided!"; // constraint violation error message } else return ""; // no constraint violation };
With Java Bean Validation, a mandatory property like name is annotated with NotNull in the following way: @Entity public class Person { @NotNull private String name; private int age; }
The equivalent ASP.NET Data Annotation is Required as shown in
7.2 Integrity Constraints
121
public class Person{ [Required] public string name { get; set; } public int age { get; set; } }
7.2.3 Range Constraints A range constraint requires that a property must have a value from the value space of the type that has been defined as its range. This is implicitly expressed by defining a type for a property as its range. For instance, the attribute age defined for the object type Person in the class diagram above has the range Integer, so it must not have a value like “aaa”, which does not denote an integer. However, it may have values like -13 or 321, which also do not make sense as the age of a person. In a similar way, since its range is String, the attribute name may have the value “” (the empty string), which is a valid string that does not make sense as a name. We can avoid allowing negative integers like -13 as age values, and the empty string as a name, by assigning more specific datatypes as range to these attributes, such as NonNegativeInteger to age, and NonEmptyString to name. Notice that such more specific datatypes are neither predefined in SQL nor in common programming languages, so we have to implement them either in the form of user-defined types, as supported in SQL-99 database management systems such as PostgreSQL, or by using suitable additional constraints such as interval constraints, which are discussed in the next section. In a UML class diagram, we can simply define NonNegativeInteger and NonEmptyString as custom datatypes and then use them in the definition of a property, as illustrated in the following diagram:
In JavaScript, we can code a range constraint by a check function, as illustrated in the following example: Person.checkName = function (n) { if (typeof(n) !== "string" || n.trim() === "") { return "Name must be a non-empty string!"; } else return ""; };
This check function detects and reports a constraint violation if the given value for the name property is not of type “string” or is an empty string. In a Java EE web app, for declaring empty strings as non-admissible user input we must set the context parameter
to true in the web deployment descriptor file web.xml. In ASP.NET, empty strings are non-admissible by default.
7.2.4 Interval Constraints An interval constraint requires that an attribute’s value must be in a specific interval, which is specified by a minimum value or a maximum value, or both. Such a constraint can be defined for any attribute having an ordered type, but normally we define them only for numeric datatypes or calendar datatypes. For instance, we may want to define an interval constraint requiring that the age attribute value must be in the interval [25,70]. In a class diagram, we can define such a constraint by using the property modifiers min and max, as shown for the age attribute of the Driver class in the following diagram.
In an SQL table creation statement, an interval constraint is expressed in a table column definition by appending a suitable CHECK clause to the column definition as in the following example: CREATE TABLE drivers( name VARCHAR NOT NULL, age INTEGER CHECK (age >= 25 AND age 3) { return "There must be no more than 3 nicknames!"; } else return ""; };
With Java Bean Validation annotations, we can specify @Size( max=3) List nickNames @Size( min=3, max=5) List members
7.2.7 Uniqueness Constraints A uniqueness constraint (or key constraint) requires that a property’s value (or the value list of a list of properties in the case of a composite key constraint) is unique among all instances of the given object type. For instance, in a UML class diagram with the object type Book we can define the isbn attribute to be unique, or, in other words, a key, by appending the (user-defined) property modifier keyword key in curly braces to the attribute’s definition in the Book class rectangle shown in the following diagram.
In an SQL table creation statement, a uniqueness constraint is expressed by appending the keyword UNIQUE to the column definition as in the following example:
126
7 Integrity Constraints and Data Validation
CREATE TABLE books( isbn VARCHAR(10) NOT NULL UNIQUE, title VARCHAR(50) NOT NULL )
In JavaScript, we can code this uniqueness constraint by a check function that tests if there is already a book with the given isbn value in the books table of the app’s database.
7.2.8 Standard Identifiers (Primary Keys) An unique attribute (or a composite key) can be declared to be the standard identifier for objects of a given type, if it is mandatory (or if all attributes of the composite key are mandatory). We can indicate this in a UML class diagram with the help of the property modifier id appended to the declaration of the attribute isbn as shown in the following diagram.
Notice that such a standard identifier declaration implies both a mandatory value and a uniqueness constraint on the attribute concerned. Standard identifiers are called primary keys in relational databases. We can declare an attribute to be the primary key in an SQL table creation statement by appending the phrase PRIMARY KEY to the column definition as in the following example: CREATE TABLE books( isbn VARCHAR(10) PRIMARY KEY, title VARCHAR(50) NOT NULL )
In JavaScript, we cannot easily code a standard identifier declaration, because this would have to be part of the metadata of the class definition, and there is no standard support for such metadata in JavaScript. However, we should at least check if the given argument violates the implied mandatory value or uniqueness constraints by invoking the corresponding check functions discussed above.
7.2.9 Referential Integrity Constraints A referential integrity constraint requires that the values of a reference property refer to an object that exists in the population of the property’s range class. Since we do
7.2 Integrity Constraints
127
not deal with reference properties in this chapter, we postpone the discussion of referential integrity constraints to Volume 2.
7.2.10 Frozen and Read-Only Value Constraints A frozen value constraint defined for a property requires that the value of this property must not be changed after it has been assigned. This includes the special case of read-only value constraints on mandatory properties that are initialized at object creation time. Typical examples of properties with a frozen value constraint are standard identifier attributes and event properties. In the case of events, the semantic principle that the past cannot be changed prohibits that the property values of events can be changed. In the case of a standard identifier attribute we may want to prevent users from changing the ID of an object since this requires that all references to this object using the old ID value are changed as well, which may be difficult to achieve (even though SQL provides special support for such ID changes by means of its ON UPDATE CASCADE clause for the change management of foreign keys). The following diagram shows how to define a frozen value constraint for the isbn attribute:
In Java, a read-only value constraint can be enforced by declaring the property to be final. In JavaScript, a read-only property slot can be implemented as in the following example: Object.defineProperty( obj, "teamSize", {value: 5, writable: false, enumerable: true})
where the property slot obj.teamSize is made unwritable. An entire object obj can be frozen with Object.freeze( obj). We can implement a frozen value constraint for a property in the property’s setter method like so: Book.prototype.setIsbn = function (i) { if (this.isbn === undefined) this.isbn = i; else console.log("Attempt to re-assign a frozen property!"); }
128
7 Integrity Constraints and Data Validation
7.2.11 Beyond property constraints So far, we have only discussed how to define and check property constraints. However, in certain cases there may be also integrity constraints that do not just depend on the value of a particular property, but rather on 1. the values of several properties of a particular object (object-level constraints), 2. the value of a property before and its value after a change attempt (dynamic constraints), 3. the set of all instances of a particular object type (type-level constraints), 4. the set of all instances of several object types. OCL The Object Constraint Language (OCL) was defined in 1997 as a formal logic language for expressing integrity constraints in UML version 1.1. Later, it was extended for allowing to define also (1) derivation expressions for defining derived properties, and (2) preconditions and postconditions for operations, in a class model.
In a class model, property constraints can be expressed within the property declaration line in a class rectangle (typically with keywords, such as id, max, etc.). For expressing more complex constraints, such as object-level or type-level constraints, we can attach an invariant declaration box to the class rectangle(s) concerned and express the constraint either in (unambiguous) English or in the Object Constraint Language (OCL). A simple example of an object-level constraint expressed as an OCL invariant is shown in Figure 7.1.
Figure 7.1 An example of an object-level constraint
A general approach for implementing object-level constraint validation consists of taking the following steps: 1. Choose a fixed name for an object-level constraint validation function, such as validate. 2. For any class that needs object-level constraint validation, define a validate function returning either a ConstraintViolation or a NoConstraintViolation object. 3. Call this function, if it exists, for the given model class,
7.3 Responsive Validation
129
a. in the UI/view, on form submission; b. in the model class, before save, both in the create and in the update method. Constraints affecting two or more model classes could be defined in the form of static methods (in a model layer method library) that are invoked from the object-level validation methods of the affected model classes.
7.3 Responsive Validation This problem is well-known from classical web applications where the front-end component submits the user input data via HTML form submission to a back-end component running on a remote web server. Only this back-end component validates the data and returns the validation results in the form of a set of error messages to the front-end. Only then, often several seconds later, and in the hard-to-digest form of a bulk message, does the user get the validation feedback. This approach is no longer considered acceptable today. Rather, in a responsive validation approach, the user should get immediate validation feedback on each single data input. Technically, this can be achieved with the help of event handlers for the user interface events input or change. Responsive validation requires a data validation mechanism in the user interface (UI), such as the HTML5 form validation API². Alternatively, the jQuery Validation Plugin³ can be used as a (non-HTML5-based) form validation API. The HTML5 form validation API essentially provides new types of input fields (such as number or date) and a set of new attributes for form control elements for the purpose of supporting responsive validation performed by the browser. Since using the new validation attributes (like required, min, max and pattern) implies defining constraints in the UI, they are not really useful in a general approach where constraints are only checked, but not defined, in the UI. Consequently, we only use two methods of the HTML5 form validation API for validating constraints in the HTML-forms-based user interface of our app. The first of them, setCustomValidity, allows to mark a form field as either valid or invalid by assigning either an empty string or a non-empty (constraint violation) message string. The second method, checkValidity, is invoked on a form before user input data is committed or saved (for instance with a form submission). It tests, if all fields have a valid value. For having the browser automatically displaying any constraint
violation messages, we need to have a submit event, even if we don’t really submit the form, but just use a save button. See this Mozilla tutorial⁴ or this HTML5Rocks tutorial⁵ for more about the HTML5 form validation API.
7.4 Constraint Validation in MVC Applications Integrity constraints should be defined in the model classes of an MVC app since they are part of the business semantics of a model class (representing a business object type). However, a more difficult question is where to perform data validation? In the database? In the model classes? In the controller? Or in the user interface (“view”)? Or in all of them? A relational database management system (DBMS) performs data validation whenever there is an attempt to change data in the database, provided that all relevant integrity constraints have been defined in the database. This is essential since we want to avoid, under all circumstances, that invalid data enters the database. However, it requires that we somehow duplicate the code of each integrity constraint, because we want to have it also in the model class to which the constraint belongs. Also, if the DBMS would be the only application component that validates the data, this would create a latency, and hence usability, problem in distributed applications because the user would not get immediate feedback on invalid input data. Consequently, data validation needs to start in the user interface (UI). However, it is not sufficient to perform data validation in the UI. We also need to do it in the model classes, and in the database, for making sure that no flawed data enters the application’s persistent data store. This creates the problem of how to maintain the constraint definitions in one place (the model), but use them in two or three other places (at least in the model classes and in the UI code, and possibly also in the database).We call this the multiple validation problem. This problem can be solved in different ways. For instance: 1. Define the constraints in a declarative language (such as Java Bean Validation Annotations or ASP.NET Data Annotations) and generate the back-end/model and front-end/UI validation code both in a back-end application programming language such as Java or C#, and in JavaScript. 2. Keep your validation functions in the (PHP, Java, C# etc.) model classes on the back-end, and invoke them from the JavaScript UI code via XHR. This approach can only be used for specific validations, since it implies the penalty of an additional HTTP communication latency for each validation invoked in this way.
Use JavaScript as your back-end application programming language (such as with NodeJS), then you can code your validation functions in your JavaScript model classes on the back-end and execute them both before committing changes on the back-end and on user input and form submission in the UI on the front-end side.
The simplest, and most responsive, solution is the third one, using only JavaScript both for the back-end and front-end components of a web app.
7.5 Adding Constraints to a Design Model We again consider the book data management problem that was considered in Part 1. But now we also consider the data integrity rules (or ’business rules’) that govern the management of book data. These integrity rules, or constraints, can be expressed in a UML class diagram as shown in Figure 7.2 below.
Figure 7.2 A design model defining the object type Book with two invariants
1. 2. 3.
4.
In this model, the following constraints have been expressed: Due to the fact that the isbn attribute is declared to be the standard identifier of Book, it is mandatory and unique. The isbn attribute has a pattern constraint requiring its values to match the ISBN-10 format that admits only 10-digit strings or 9-digit strings followed by “X”. The title attribute is mandatory, as indicated by its multiplicity expression [1], and has a string length constraint requiring its values to have at most 50 characters. The year attribute is mandatory and has an interval constraint, however, of a special form since the maximum is not fixed, but provided by the calendar function nextYear(), which we implement as a utility function.
Notice that the edition attribute is not mandatory, but optional, as indicated by its multiplicity expression [0..1]. In addition to the constraints described in this list, there are the implicit range constraints defined by assigning the datatype NonEmptyString as range to isbn and title, Integer to year, and PositiveIn-
132
7 Integrity Constraints and Data Validation
teger to edition. In our plain JavaScript approach, all these property constraints are coded in the model class within property-specific check functions. The meaning of the design model can be illustrated by a sample data population respecting all constraints: Table 7.1 Sample data for Book ISBN
Title
Year
Edition
X
Weaving the Web Gödel, Escher, Bach I Am A Strange Loop
7.6 Summary 1.
2. 3. 4.
5. 6.
7.
Constraints are logical conditions on the data of an app. The simplest, and most important, types of constraints are property constraints and object-level constraints. Constraints should be defined in the model classes of an MVC app, since they are part of their business semantics. Constraints should be checked in various places of an MVC app: in the UI/view code, in model classes, and possibly in the database. Software applications that include CRUD data management need to perform two kinds of bi-directional object-to-string type conversions: a. Between the model and the UI: converting model object property values to UI widget values, and, the other way around, converting input widget values to property values. Typically, widgets are form fields that have string values. b. Between the model and the datastore: converting model objects to storage data sets (called serialization), and, the other way around, converting storage data sets to model objects (called de-serialization). This involves converting property values to storage data values, and, the other way around, converting storage data values to property values. Typically, datastores are either JavaScript’s local storage or IndexedDB, or SQL databases, and objects have to be mapped to some form of table rows. In the case of an SQL database, this is called “Object-Relational Mapping” (ORM). Do not perform any string-to-property-value conversion in the UI code. Rather, this is the business of the model code. For being able to observe how an app works, or, if it does not work, where it fails, it is essential to log all critical application events, such as data retrieval, save and delete events, at least in the JavaScript console. Responsive validation means that the user, while typing, gets immediate validation feedback on each input (keystroke), and when requesting to save the new data.
7.8 Quiz Questions
133
7.7 Criteria for Evaluating the Validation Support of Frameworks The support of MVC frameworks for constraint validation can be evaluated according to the following criteria. Does the framework support 1. the declaration of all important kinds of property constraints as defined above (String Length Constraints, Mandatory Value Constraints, Range Constraints, etc.) in model classes? 2. the provision of an object validation function to be invoked in the UI on form submission, and in the model layer before save? 3. validation in the model class on assign and before save? 4. informative generic validation error messages referring to the object and property concerned? 5. custom validation error messages with parameters? 6. responsive validation in the user interface on input and on submit based on the constraints defined in the model classes, preferably using the HTML5 constraint validation API, or at least a general front-end API like the jQuery Validation Plugin? 7. two-fold validation with defining constraints in the model and checking them in the model and in the view? 8. three-fold validation with defining constraints in the model and checking them in the model, the view and in the database system? 9. reporting database validation errors that are passed from the database system to the app?
7.8 Quiz Questions If you would like to look up the answers for the following quiz questions, you can check our discussion forum⁶. If you don’t find an answer in the forum, you may create a post asking for an answer to a particular question.
7.8.1 Question 1: Where to check constraints Where in the application code should the constraints be checked? In the … ☐ controller code ☐ model classes ☐ user interface code ☐ underlying DBMS (if it supports constraint validation)
http://web-engineering.info/forum/12
134
7 Integrity Constraints and Data Validation
7.8.2 Question 2: Where to define constraints Where in the application code should the constraints be defined? In the … ☐ controller code ☐ model classes ☐ user interface HTML5 code ☐ user interface JavaScript code
7.8.3 Question 3: Counting constraints
How many constraints are specified by the class model shown in the diagram? Enter a number: _____
7.8.4 Question 4: Constraint names
Which of the following constraints are specified by the class model? ☐ A range constraint for the property name. ☐ A uniqueness constraint for the property name. ☐ A referential integrity constraint for the property name. ☐ A uniqueness constraint for the property age. ☐ A mandatory value constraint for the property name. ☐ A mandatory value constraint for the property age.
7.8 Quiz Questions
135
7.8.5 Question 5: Compliant objects
Which of the following objects do not represent admissible instances (or, in other words, violate a constraint) of the object type Person? ☐ {name: "Susan", age: 8} ☐ {name: " ", age: 0} ☐ {name: "Susan"} ☐ {name: "Peter", age: 122}
8 Implementing Constraint Validation in a Plain JS Web App 8.1 Introduction The minimal JavaScript front-end web application that we have discussed in Chapter 3 has been limited to support the minimum functionality of a data management app only. For instance, it did not take care of preventing the user from entering invalid data into the app’s database. In this chapter, we show how to express integrity constraints in a JavaScript model class, and how to perform constraint validation both in the model part of the app and in the user interface built with HTML5. We show how to perform responsive validation with the HTML5 form validation API¹. Since using the new HTML5 input field types and validation attributes (like required, min, max and pattern) implies defining constraints in the UI, they are not really useful in a best-practice approach where constraints are only checked, but not defined, in the UI. Consequently, we will not use the new HTML5 features for defining constraints in the UI, but only use two methods of the HTML5 form validation API: 1. setCustomValidity, which allows to mark a form field as either valid or invalid by assigning either an empty string or a non-empty (constraint violation) message string; 2. checkValidity, which is invoked on a form before user input data is committed or saved (for instance with a form submission); it tests, if all fields have a valid value. In the case of two special kinds of attributes, having calendar dates or colors as values, it is desirable to use the new UI widgets defined by HTML5 for picking a date or picking a color (with corresponding input field types). Unfortunately, in 2017, the HTML5 date picker widget is still not supported by all major browsers.
8.2 New Issues Compared to the Minimal App² discussed in Chapter 3 we have to deal with a number of new issues: 1. In the model code we have to add for every property of a class a. a check function that can be invoked for validating the constraints defined for the property, and
b. a setter method that invokes the check function and is to be used for setting the value of the property. In the user interface (“view”) code we have to take care of a. responsive validation on user input for providing immediate feedback to the user, b. validation on form submission for preventing the submission of flawed data to the model layer.
For improving the break-down of the view code, we introduce a utility method (in lib/util.js) that fills a select form control with option elements the contents of which is retrieved from an entity table such as Book.instances. This method is used in the setupUserInterface method of both the updateBook and the deleteBook use cases. Checking the constraints in the user interface (UI) on user input is important for providing immediate feedback to the user. But it is not safe enough to perform constraint validation only in the UI, because this could be circumvented in a distributed web application where the UI runs in the web browser of a front-end device while the application’s data is managed by a back-end component on a remote web server. Consequently, we need multiple constraint validation, first in the UI on input (or on change) and on form submission, and subsequently in the model layer before saving/sending data to the persistent data store. And in an application based on a DBMS we may also use a third round of validation before persistent storage by using the validation mechanisms of the DBMS. This is a must, when the application’s database is shared with other apps. Our proposed solution to this multiple validation problem is to keep the constraint validation code in special check functions in the model classes and invoke these functions both in the UI on user input and on form submission, as well as in the create and update data management methods of the model class via invoking the setters. Notice that referential integrity constraints (and other relationship constraints) may also be violated through a delete operation, but in our single-class example we don’t have to consider this.
8.3 Make a JavaScript Class Model Using the information design model shown in Figure 7.2 above as the starting point, we make a JavaScript class model by performing the following steps: 1. Create a check operation for each (non-derived) property in order to have a central place for implementing all the constraints that have been defined for a property in the design model. For a standard identifier attribute, such as Book:: isbn, two check operations are needed:
138
8 Implementing Constraint Validation in a Plain JS Web App
A basic check operation, such as checkIsbn, for checking all basic constraints of the attribute, except the mandatory value and the uniqueness constraints. b. An extended check operation, such as checkIsbnAsId, for checking, in addition to the basic constraints, the mandatory value and uniqueness constraints that are required for a standard identifier attribute. The checkIsbnAsId operation is invoked on user input for the isbn form field in the create book form, and also in the setIsbn method, while the checkIsbn operation can be used for testing if a value satisfies the syntactic constraints defined for an ISBN. Create a setter operation for each (non-derived) single-valued property. In the setter, the corresponding check operation is invoked and the property is only set, if the check does not detect any constraint violation. a.
2.
This leads to the JavaScript class model shown on the right-hand side of the mapping arrow in the following figure.
Figure 8.1 From an information design model to a JS class model
Essentially, the JavaScript class model extends the design model by adding checks and setters for each property. The attached invariants have been dropped since they are taken care of in the checks. Property ranges have been turned into JavaScript datatypes (with a reminder to their real range in curly braces). Notice that the names of check functions are underlined, since this is the convention in UML for class-level (as opposed to instance-level) operations.
8.4 Set up the Folder Structure Adding Some Library Files
139
8.4 Set up the Folder Structure Adding Some Library Files The MVC folder structure of our validation app extends the structure of the minimal app by adding a lib folder containing the generic code libraries browserShims. js, errorTypes.js and util.js. Thus, we get the following folder structure: publicLibrary css main.css normalize.min.css lib browserShims.js errorTypes.js util.js src c m v index.html
We discuss the contents of the added files in the following sub-sections.
8.4.1 Provide general utility functions and JavaScript fixes in library files We add three library files to the lib folder: 1. browserShims.js contains a definition of the string trim function for older browsers that don’t support this function (which was only added to JavaScript in ES5, defined in 2009). More browser shims for other recently defined functions, such as querySelector and classList, could also be added to browserShims.js. 2. util.js contains the definitions of a few utility functions such as isNonEmptyString(x) for testing if x is a non-empty string. 3. errorTypes.js defines classes for error (or exception) types corresponding to the basic types of property constraints discussed above: StringLengthConstraintViolation, MandatoryValueConstraintViolation, RangeConstraintViolation, IntervalConstraintViolation, PatternConstraintViolation, UniquenessConstraintViolation. In addition, a class NoConstraintViolation is defined for being able to return a validation result object in the case of no constraint violation.
8.4.2 Create a start page The start page index.html takes care of loading CSS page styling files with the help of the following two link elements:
140
8 Implementing Constraint Validation in a Plain JS Web App
Then it loads the following JavaScript files: 1. browserShims.js and util.js from the lib folder, discussed in Section 4.1, 2. errorTypes.js from the lib folder, defining exception classes. 3. initialize.js from the src/c folder, defining the app’s MVC namespaces, as discussed in Chapter 3 4. Book.js from the src/m folder, a model class file that provides data management and other functions discussed in Section 5.
8.5 Write the Model Code The JavaScript class model shown on the right hand side in Figure 8.1 can be coded step by step for getting the code of the model layer of our JavaScript front-end app. These steps are summarized in the following section.
8.5.1 Summary 1. 2.
3.
4.
Code the model class as a JavaScript constructor function. Code the check functions, such as checkIsbn or checkTitle, in the form of class-level (’static’) methods. Take care that all constraints, as specified in the JavaScript class model, are properly coded in the check functions. Code the setter operations, such as setIsbn or setTitle, as (instance-level) methods. In the setter, the corresponding check operation is invoked and the property is only set, if the check does not detect any constraint violation. Code the add and remove operations, if there are any, as instance-level methods.
These steps are discussed in more detail in the following sections.
8.5.2 Code the model class as a constructor function The class Book is coded as a corresponding constructor function with the same name Book such that all its (non-derived) properties are supplied with values from corresponding key-value slots of a slots parameter. function Book( slots) { // assign default values this.isbn = ""; // string this.title = ""; // string
8.5 Write the Model Code
141
this.year = 0; // number (int) // set properties only if constructor is invoked with an argument if (arguments.length > 0) { this.setIsbn( slots.isbn); this.setTitle( slots.title); this.setYear( slots.year); // optional property if (slots.edition) this.setEdition( slots.edition); } };
In the constructor body, we first assign default values to the class properties. These values will be used when the constructor is invoked as a default constructor (without arguments), or when it is invoked with only some arguments. It is helpful to indicate the range of a property in a comment. This requires to map the platform-independent datatypes of the information design model to the corresponding implicit JavaScript datatypes according to the following table. Table 8.1 Datatype mapping Platform-independent datatype
JavaScript datatype
SQL
String Integer Decimal Boolean Date
string number (int) number (float) boolean Date
CHAR(n) or VARCHAR(n) INTEGER REAL, DOUBLE PRECISION or DECIMAL(p,s) BOOLEAN DATE
Since the setters may throw constraint violation errors, the constructor function, and any setter, should be called in a try-catch block where the catch clause takes care of processing errors (at least logging suitable error messages). As in the minimal app, we add a class-level property Book.instances representing the collection of all Book instances managed by the app in the form of an entity table: Book.instances = {};
8.5.3 Code the property checks Code the property check functions in the form of class-level (’static’) methods. In JavaScript, this means to define them as method slots of the constructor, as in Book.checkIsbn (recall that a constructor is a JS object, since in JavaScript, functions are objects, and as an object, it can have slots). Take care that all constraints of a property as specified in the class model are properly coded in its check function. This concerns, in particular, the mandatory
142
8 Implementing Constraint Validation in a Plain JS Web App
value and uniqueness constraints implied by the standard identifier declaration (with {id}), and the mandatory value constraints for all properties with multiplicity 1, which is the default when no multiplicity is shown. If any constraint is violated, an error object instantiating one of the error classes listed above in Section 4.1 and defined in the file errorTypes.js is returned. For instance, for the checkIsbn operation we obtain the following code: Book.checkIsbn = function (id) { if (!id) { return new NoConstraintViolation(); } else if (typeof id !== "string" || id.trim() === "") { return new RangeConstraintViolation( "The ISBN must be a non-empty string!"); } else if (!/\b\d{9}(\d|X)\b/.test( id)) { return new PatternConstraintViolation( "The ISBN must be a 10-digit string or "+ " a 9-digit string followed by 'X'!"); } else { return new NoConstraintViolation(); } };
Notice that, since isbn is the standard identifier attribute of Book, we only check the syntactic constraints in checkIsbn, but we check the mandatory value and uniqueness constraints in checkIsbnAsId, which itself first invokes checkIsbn: Book.checkIsbnAsId = function (id) { var constraintViolation = Book.checkIsbn( id); if ((constraintViolation instanceof NoConstraintViolation)) { if (!id) { constraintViolation = new MandatoryValueConstraintViolation( "A value for the ISBN must be provided!"); } else if (Book.instances[id]) { constraintViolation = new UniquenessConstraintViolation( "There is already a book record with this ISBN!"); } else { constraintViolation = new NoConstraintViolation(); } } return constraintViolation; };
We assume that all check functions and setters can deal both with proper data values (that are of the attribute’s range type) and also with string values that are supposed to represent proper data values, but have not yet been converted to the attribute’s range type. We take this approach for avoiding datatype conversions in the user interface (“view”) code. Notice that all data entered by a user in an HTML form field is of type String and must be converted (or de-serialized) before its validity can be
8.5 Write the Model Code
143
checked and it can be assigned to the corresponding property. It is preferable to perform these type conversions in the model code, and not in the user interface code.. For instance, in our example app, we have the integer-valued attribute year. When the user has entered a value for this attribute in a corresponding form field, in the Create or Update user interface, the form field holds a string value. This value is passed to the Book.add or Book.update method, which invokes the setYear and checkYear methods. Only after being validated, this string value is converted to an integer and assigned to the year attribute.
8.5.4 Code the property setters Code the setter operations as (instance-level) methods. In the setter, the corresponding check function is invoked and the property is only set, if the check does not detect any constraint violation. Otherwise, the constraint violation error object returned by the check function is thrown. For instance, the setIsbn operation is coded in the following way: Book.prototype.setIsbn = function (id) { var validationResult = Book.checkIsbnAsId( id); if (validationResult instanceof NoConstraintViolation) { this.isbn = id; } else { throw validationResult; } };
There are similar setters for the other properties (title, year and edition).
8.5.5 Add a serialization function It is helpful to have an object serialization function tailored to the structure of an object (as defined by its class) such that the result of serializing an object is a humanreadable string representation of the object showing all relevant information items of it. By convention, these functions are called toString(). In the case of the Book class, we use the following code: Book.prototype.toString = function () { return "Book{ ISBN:" + this.isbn + ", title:" + this.title + ", year:" + this.year + (this.edition || "") +"}"; };
144
8 Implementing Constraint Validation in a Plain JS Web App
8.5.6 Data management operations In addition to defining the model class in the form of a constructor function with property definitions, checks and setters, as well as a toString() serialization function, we also need to define the following data management operations as class-level methods of the model class: 1. Book.convertRec2Obj and Book.retrieveAll for loading all managed Book instances from the persistent data store. 2. Book.saveAll for saving all managed Book instances to the persistent data store. 3. Book.add for creating a new Book instance and adding it to the collection of all Book instances. 4. Book.update for updating an existing Book instance. 5. Book.destroy for deleting a Book instance. 6. Book.createTestData for creating a few sample book records to be used as test data. 7. Book.clearData for clearing the book data store. All of these methods essentially have the same code as in our minimal app discussed in Part 1, except that now 1. we may have to catch constraint violations in suitable try-catch blocks in the procedures Book.convertRec2Obj, Book.add, Book.update and Book. createTestData; 2. we create more informative status and error log messages for better observing what’s going on; and 3. we can use the toString() function for serializing an object in status and error messages. Notice that for the change operations add (create) and update, we need to implement an all-or-nothing policy: whenever there is a constraint violation for a property, no new object must be created and no (partial) update of the affected object must be performed. When a constraint violation is detected in one of the setters called when new Book(…) is invoked in Book.add, the object creation attempt fails, and instead a constraint violation error message is created. Otherwise, the new book object is added to Book.instances and a status message is created, as shown in the following program listing: Book.add = function (slots) { var book = null; try { book = new Book( slots); } catch (e) { console.log( e.constructor.name +": "+ e.message);
8.5 Write the Model Code
145
book = null; } if (book) { Book.instances[book.isbn] = book; console.log( book.toString() + " created!"); } };
When an object of a model class is to be updated, we first create a clone of it for being able to restore it if the update attempt fails. In the object update attempt, we only assign those properties of the object the value of which has changed, and we report this in a status log. Normally, all properties defined by a model class, except the standard identifier attribute, can be updated. It is, however, possible to also allow updating the standard identifier attribute. This requires special care for making sure that all references to the given object via its old standard identifier are updated as well. When a constraint violation is detected in one of the setters invoked in Book. update, the object update attempt fails, and instead the error message of the constraint violation object thrown by the setter and caught in the update method is shown, and the previous state of the object is restored. Otherwise, a status message is created, as shown in the following program listing: Book.update = function (slots) { var book = Book.instances[slots.isbn], noConstraintViolated = true, updatedProperties = [], objectBeforeUpdate = util.cloneObject( book); try { if (book.title !== slots.title) { book.setTitle( slots.title); updatedProperties.push("title"); } if (book.year !== parseInt( slots.year)) { book.setYear( slots.year); updatedProperties.push("year"); } if (slots.edition && slots.edition !== book.edition) { // slots.edition has a non-empty value that is new book.setEdition( slots.edition); updatedProperties.push("edition"); } else if (!slots.edition && book.edition !== undefined) { // slots.edition has an empty value that is new delete book.edition; updatedProperties.push("edition"); } } catch (e) {…} … };
146
8 Implementing Constraint Validation in a Plain JS Web App
Notice that optional properties, like edition, need to be treated in a special way. If the user doesn’t enter any value for them in a Create or Update user interface, the form field’s value is the empty string "". In the case of an optional property, this means that the property is not assigned a value in the add use case, or that it is unset if it has had a value in the update use case. This is different from the case of a mandatory property, where the empty string value obtained from an empty form field may or may not be an admissible value. If there is a constraint violation exception, an error message is written to the log and the object concerned is reset to its previous state: Book.update = function (slots) { … try { … } catch (e) { console.log( e.constructor.name +": "+ e.message); noConstraintViolated = false; // restore object to its state before updating Book.instances[slots.isbn] = objectBeforeUpdate; } if (noConstraintViolated) { if (updatedProperties.length > 0) { console.log("Properties " + updatedProperties.toString() + " modified for book " + slots.isbn); } else { console.log("No property value changed for book " + slots.isbn + " !"); } } };
8.6 Write the View Code The user interface (UI) consists of a start page index.html that allows the user choosing one of the data management operations by navigating to the corresponding UI page such as retrieveAndListAllBooks.html or createBook.html in the app folder. The start page index.html has been discussed in Section 4.2. We render the data management menu items in the form of buttons. For simplicity, we invoke the Book.clearData() and Book.createTestData() methods directly from the buttons’ onclick event handler attribute. Notice, however, that it is generally preferable to register such event handling functions with addEventListener(…), as we do in all other cases.
8.6 Write the View Code
147
8.6.1 The data management UI pages Each data management UI page loads the same basic CSS and JavaScript files like the start page index.html discussed above. In addition, it loads a use-case-specific view code file src/v/useCase.js and then sets the setupUserInterface procedure of the use-case as an event handler for the page load event, which takes care of initializing the use case when the UI page has been loaded.
8.6.2 Initialize the app For initializing the app, its namespace and MVC sub-namespaces have to be defined. For our example app, the main namespace is defined to be pl, standing for “Public Library”, with the three sub-namespaces m, v and c being initially empty objects: var pl = { m:{}, v:{}, c:{} };
We put this code in the file initialize.js in the c folder.
8.6.3 Set up the user interface For setting up the user interfaces of the data management use cases, we have to distinguish the case of “Retrieve/List All” from the other ones (Create, Update, Delete). While the latter ones require using an HTML form and attaching event handlers to form controls, in the case of “Retrieve/List All” we only have to render a table displaying all books, as in the case of the Minimal App³ discussed in Chapter 3. For the Create, Update and Delete use cases, we need to add event listeners for: 1. responsive validation on form field input events, 2. handling the event when the user clicks (or pushes) the save (or delete) button, 3. making sure the main memory data is saved when a beforeunload event occurs, that is, when the browser window/tab is closed. For the use case Create, we obtain the following code (in v/createBook.js):: pl.v.createBook = { setupUserInterface: function () { var formEl = document.forms['Book'], saveButton = formEl.commit; // load all book records Book.retrieveAll(); // add event listeners for responsive validation formEl.isbn.addEventListener("input", function () {
Notice that for each input field we add a listener for input events, such that on any user input a validation check is performed because input events are created by user input actions such as typing. We use the predefined function setCustomValidity from the HTML5 form validation API for having our property check functions invoked on the current value of the form field and returning an error message in the case of a constraint violation. So, whenever the string represented by the expression Book. checkIsbn( formEl.isbn.value).message is empty, everything is fine. Otherwise, if it represents an error message, the browser indicates the constraint violation to the user by rendering a red outline for the form field concerned (due to our CSS rule for the :invalid pseudo class). In addition to the event handlers for responsive constraint validation, we need two more event handlers: pl.v.createBook = { setupUserInterface: function () { … // Set an event handler for the submit/save button saveButton.addEventListener("click", pl.v.createBook.handleSaveButtonClickEvent); // neutralize the submit event formEl.addEventListener( 'submit', function (e) { e.preventDefault(); formEl.reset(); }); // when the browser window/tab is closed window.addEventListener("beforeunload", Book.saveAll); }, handleSaveButtonClickEvent: function () {…} };
8.6 Write the View Code
149
While the validation on user input enhances the usability of the UI by providing immediate feedback to the user, validation on form data submission is even more important for catching invalid data. Therefore, the event handler handleSaveButton ClickEvent() performs the property checks again with the help of setCustomValidity, as shown in the following program listing: handleSaveButtonClickEvent: function () { var formEl = document.forms['Book']; var slots = {isbn: formEl.isbn.value, title: formEl.title.value, year: formEl.year.value}; // set error messages in case of constraint violations formEl.isbn.setCustomValidity( Book.checkIsbnAsId( slots.isbn).message); formEl.title.setCustomValidity( Book.checkTitle( slots.title).message); formEl.year.setCustomValidity( Book.checkYear( slots.year).message); if (formEl.edition.value) { slots.edition = formEl.edition.value; formEl.edition.setCustomValidity( Book.checkEdition( slots.edition).message); } // save the input data only if all input fields are valid if (formEl.checkValidity()) Book.add( slots); }
By invoking checkValidity() on the form element, we make sure that the form data is only saved (by Book.add), if there is no constraint violation. After this handleSaveButtonClickEvent handler has been executed on an invalid form, the browser takes control and tests if the predefined property validity has an error flag for any form field. In our approach, since we use setCustomValidity, the validity.customError would be true. If this is the case, the custom constraint violation message will be displayed (in a bubble) and the submit event will be suppressed. In the UI of the use case Update, which is handled in v/updateBook.js, we do not have an input, but rather an output field for the standard identifier attribute isbn, since it is not supposed to be modifiable. Consequently, we don’t need to validate any user input for it. However, we need to set up a selection list (in the form of an HTML select element) allowing the user to select a learning unit in the first step, before its data can be modified. This requires to add a change event listener on the select element such that the fields of the UI can be filled with the data of the selected object. pl.v.updateBook = { setupUserInterface: function () { var formEl = document.forms['Book'],
150
8 Implementing Constraint Validation in a Plain JS Web App
submitButton = formEl.commit, selectBookEl = formEl.selectBook; // set up the book selection list util.fillSelectWithOptions( Book.instances, selectBookEl, "isbn", "title"); // when a book is selected, fill the form with its data selectBookEl.addEventListener("change", function () { var book=null, bookKey = selectBookEl.value; if (bookKey) { // set form fields and reset CustomValidity book = Book.instances[bookKey]; ["isbn","title","year","edition"].forEach( function (p) { formEl[p].value = book[p] !== undefined ? book[p] : ""; formEl[p].setCustomValidity(""); // no error }); } else formEl.reset(); }); // add event listeners for responsive validation … // Set an event handler for the submit/save button … // neutralize the submit event … // Set a handler for the event when the browser window/tab is closed … }, handleSaveButtonClickEvent: function () {…} };
There is no need to set up responsive validation for the standard identifier attribute isbn, but for all other form fields, as shown above for the Create use case. The logic of the setupUserInterface method for the Delete use case is similar. We only need to take care that the object to be deleted can be selected by providing a selection list, like in the Update use case. No validation is needed for the Delete use case.
8.7 Run the App and Get the Code You can run the validation app⁴ from our server or download the code⁵ as a ZIP archive file.
8.8 Possible Variations and Extensions 8.8.1 Adding an object-level validation function When object-level validation (across two or more properties) is required for a model class, we can add a custom validation function validate to it, such that objectlevel validation can be performed before save by invoking validate on the object concerned. For instance, for expressing the constraint defined in the class model shown in Figure 7.1, we define the following validation function: Author.prototype.validate = function () { if (this.dateOfDeath && this.dateOfDeath < this.dateOfBirth) { throw new ConstraintViolation( "The dateOfDeath must be after the dateOfBirth!"); } };
When a validate function has been defined for a model class, it can be invoked in the create and update methods. For instance, Author.add = function (slots) { var author = null; try { author = new Author( slots); author.validate(); } catch (e) { console.log( e.constructor.name +": "+ e.message); } };
8.8.2 Using implicit JS setters Since ES5, JavaScript has its own form of setters, which are implicit and allow having the same semantics as explicit setter methods, but with the simple syntax of direct access. In addition to having the advantage of a simpler syntax, implicit JS setters are also safer than explicit setters because they decrease the likelihood of a programmer circumventing a setter by using a direct property assignment when instead a setter should be used. In other OOP languages, like Java, this is prevented by declaring properties to be ’private’. But JavaScript does not have this option. The following code defines implicit setter and getter methods for the property title: Object.defineProperty( Book.prototype, "title", { set: function(t) { var validationResult = Book.checkTitle( t);
152
8 Implementing Constraint Validation in a Plain JS Web App
Notice that, also in the constructor definition, the internal property _title, used for storing the property value, is not used for setting/getting it, but rather the virtual property title: Book = function (slots) { this.learnUnitNo = 0; this.title = ""; if (arguments.length > 0) { this.learnUnitNo = slots.learnUnitNo; this.title = slots.title; // optional property if (slots.subjectArea) this.subjectArea = slots.subjectArea; } });
We will start using implicit setter and getter methods, along with ES6 class definitions, in Chapter 11.
8.9 Points of Attention 8.9.1 Boilerplate code An issue with the do-it-yourself code of this example app is the boilerplate code needed 1. per model class for the storage management methods add, update, destroy, etc.; 2. per model class and property for getters, setters and validation checks. While it is good to write this code a few times for learning app development, you don’t want to write it again and again later when you work on real projects. In Volume 2, we present an approach how to put these methods in a generic form in a meta-class, such that they can be reused in all model classes of an app.
8.10 Practice Projects
153
8.9.2 Configuring the UI for preventing invalid user input Some of the new HTML5 input field types (like number, date or color) are intended to allow web browsers rendering corresponding input elements in the form of UI widgets (like date or color pickers) that limit the user’s input options such that only valid input is possible. In terms of usability, it’s preferable to prevent users from entering invalid data instead of allowing to enter it and only then checking its validity and reporting errors. Unfortunately, and this is quite disappointing, many browsers do, in 2017, still not have reasonable widget implementations for these HTML5 input field types yet.
8.10 Practice Projects For the following projects, you have to take care of 1. adding, for every property, a check function that validates the constraints defined for the property, and a setter method that invokes the check function and is to be used for setting the value of the property, 2. performing validation before any data is saved in the add and update methods. in the model code of your app, while In the user interface (“view”) code you have to take care of 1. styling the user interface with CSS rules (for instance, by integrating a CSS library such as Yahoo’s Pure CSS), 2. validation on user input for providing immediate feedback to the user, 3. validation on form submission for preventing the submission of invalid data. Make sure that your pages comply with the XML syntax of HTML5 by means of XHTML5 validation⁶ (setting the validator field Preset to “HTML5 + SVG 1.1 + MathML 3.0”), and that your JavaScript code complies with our Coding Guidelines⁷ and its style is checked with JSHint⁸. If you have any questions about how to carry out the following projects, you can ask them on our discussion forum⁹.
8.10.1 Project 1 – Validate movie data The purpose of the app to be built is managing information about movies. Like in the book data management app discussed in the tutorial, you can make the simplifying
8 Implementing Constraint Validation in a Plain JS Web App
assumption that all the data can be kept in main memory. Persistent data storage is implemented with JavaScript’s Local Storage API. The app deals with just one object type: Movie, as depicted in the following class diagram.
In this model, the following constraints have been expressed: 1. Due to the fact that the movieId attribute is declared to be the standard identifier of Movie, it is mandatory and unique. 2. The title attribute is mandatory, as indicated by its multiplicity expression [1], and has a string length constraint requiring its values to have at most 120 characters. 3. The releaseDate attribute has an interval constraint: it must be greater than or equal to 1895 – 12 – 28. Notice that the releaseDate attribute is not mandatory, but optional, as indicated by its multiplicity expression [0..1]. In addition to the constraints described in this list, there are the implicit range constraints defined by assigning the datatype PositiveInteger to movieId, NonEmptyString to title, and Date to releaseDate. In our plain JavaScript approach, all these property constraints are coded in the model class within property-specific check functions. You can use the following sample data for testing your app: Table 8.2 Sample data about movies Movie ID
Title
Release date
Pulp Fiction Star Wars Casablanca The Godfather
– – – – – –
8.10 Practice Projects
155
More movie data can be found on the IMDb website¹⁰.
8.10.2 Project 2 – Validate country data The purpose of the app to be built is managing information about countries. The app deals with just one object type: Country, as depicted in the following class diagram.
In this model, the following constraints have been expressed: 1. Due to the fact that the name attribute is declared to be the standard identifier of Country, it is mandatory and unique. 2. The name attribute has a string length constraint requiring its values to have at least 3 and at most 50 characters. 3. The population attribute is mandatory, as indicated by the multiplicity expression [1] appended to the attribute name. 4. The lifeExpectancy attribute is also mandatory and has an interval constraint: its values must be less than or equal to 100. Notice that the militaryExpenditure attribute is not mandatory, but optional, as indicated by its multiplicity expression [0..1]. In addition to the constraints described in this list, there are the implicit range constraints defined by assigning the datatypes NonEmptyString to name, PositiveInteger to population, PositiveDecimal to lifeExpectancy and Percentage to militaryExpenditure (hint: a percentage value is a decimal number between 0 and 100). In our plain JavaScript approach, all these property constraints are coded in the model class within property-specific check functions. You can use the sample data shown in the following table for testing your app.
http://www.imdb.com
156
8 Implementing Constraint Validation in a Plain JS Web App
Table 8.3 Sample data about countries Name
Population
Life expectancy
Military expend. (% GDP)
Germany France Russia Monaco
,, ,, ,, ,
. . . .
. . .
More data about countries can be found in the CIA World Factbook¹¹.
8.11 Quiz Questions If you would like to look up the answers for the following quiz questions, you can check our discussion forum¹². If you don’t find an answer in the forum, you may create a post asking for an answer to a particular question.
8.11.1 Question 1: Validation in setter (1) Complete the following setter code fragment: Book.prototype.setIsbn = function (isbn) { var validationResult = ______________________________; if (validationResult instanceof NoConstraintViolation) { this.isbn = isbn; } else { … ; } };
8.11.2 Question 2: Validation in setter (2) Complete the following setter code fragment: Book.prototype.setIsbn = function (isbn) { var validationResult = …; if (validationResult instanceof NoConstraintViolation) { this.isbn = isbn; } else {
8.11.3 Question 3: Methods to add in JS class model
Consider the simple information design model shown in the class diagram. Which of the following methods have to be added to the Publisher class in a corresponding JavaScript class model? Select one or many: ☐ checkName( n: String): ConstraintViolation ☐ getName(): String ☐ setName( n: String) ☐ checkNameAsId( n: String): ConstraintViolation ☐ checkNameAsId( n: String): ConstraintViolation ☐ checkName( n: String): ConstraintViolation ☐ setName( n: String) Notice that an underlined method name denotes a class-level (“static”) method.
8.11.4 Question 4: Implementing constraints
Consider the constraints specified for the object type Person in the class diagram. Which of the following JavaScript code fragments implements these constraints? Select one: 1. ○ if (name === undefined) { return "A name is required!"; } else if (typeof name !== "string" || name.trim() === "") { return "Name must be a non-empty string!"; } else if (age !== undefined) { if (!Number.isInteger( age) || age < 0) { return "Age must be a non-negative integer!" } } else return "";
// no error
158
2.
8 Implementing Constraint Validation in a Plain JS Web App
○ if (name === undefined) { return "A name is required!"; } else if (typeof name !== "string") { return "Name must be a string!"; } else if (age !== undefined) { if (!Number.isInteger( age) || age < 0) { return "Age must be a non-negative integer!" } } else return "";
3.
// no error
○ if (name === undefined) { return "A name is required!"; } else if (typeof name !== "string" || name.trim() === "") { return "Name must be a non-empty string!"; } else if (age < 0) { return "Age must be a non-negative integer!" } else return "";
4.
// no error
○ if (name === undefined) { return "A name is required!"; } else if (typeof name !== "string" || name.trim() === "") { return "Name must be a non-empty string!"; } else if (age === undefined || !Number.isInteger( age) || age < 0) { return "Age must be a non-negative integer!" } else return "";
// no error
9 Implementing Constraint Validation in a Java EE Web App The minimal web app that we have discussed in Part 1 has been limited to support the minimum functionality of a data management app only. For instance, it did not take care of preventing the user from entering invalid data into the app’s database. In this chapter, we show how to express integrity constraints in a Java EE entity class, and how to have constraints automatically validated on critical life cycle events of entity objects with JPA, and on form submission with JSF.
9.1 Java Annotations for Persistent Data Management and Constraint Validation The integrity constraints of a distributed app have to be checked both in model classes and in the underlying database, and possibly also in the UI. However, this requirement for three-fold validation should not imply having to define the same constraints three times in three different languages: in Java, in SQL and in HTML5/JavaScript. Rather, the preferred approach is to define the constraints only once, in the model classes, and then reuse these constraint definitions also in the underlying database and in the UI. Java EE apps support this goal to some degree. There are two types of constraint annotations: 1. JPA constraint annotations, specify constraints to be used for generating the database schema (with CREATE TABLE statements) such that they are then checked by the DBMS, and not by the Java runtime environment; 2. Bean Validation annotations specify constraints to be checked by the Java runtime environment In this section we discuss how to use some of the predefined constraint annotations and how to define a custom constraint annotation for the year property of the Book class, since its value has an upper bound defined by an expression (’next year’).
9.1.1 JPA constraint annotations The JPA constraint annotations specify constraints to be used by the underlying database management system after generating the database schema, but not for Java validation. Consider the @Id annotation in the following definition of an entity class Item:
https://doi.org/10.1515/9783110499957-009
160
9 Implementing Constraint Validation in a Java EE Web App
@Entity @Table( name="items") public class Item { @Id private String itemCode; private int quantity; … // define constructors, setters and getters }
The @Id annotation of the itemCode attribute is mapped to a SQL primary key declaration for this attribute in the corresponding database table schema. As a consequence, the itemCode column of the generated items table must have a value in each row and these values have to be unique. However, these conditions are not checked in the Java runtime environment. JPA generates the following CREATE TABLE statement: CREATE TABLE IF NOT EXISTS items ( itemCode varchar(255) NOT NULL PRIMARY KEY, quantity int(11) DEFAULT NULL )
Since nothing has been specified about the length of itemCode strings, the length is set to 255 by default. However, in our case we know that itemCode has a fixed length of 10, which can be enforced by using the @Column annotation, which has the following parameters: – name allows to specify a name for the column to be used when the table is created (by default, the attribute name of the entity class is used); – nullable is a boolean parameter that defines if the column allows NULL values (by default, it is true); – length is a positive integer, specifying the maximum number of characters allowed for string values of that column (by default, this is 255); – unique is a boolean parameter that defines if the values of the column must be unique (by default, it is false). Using the @Column annotation, the improved Java/JPA code of the model class is: @Entity @Table( name="items") public class Item { @Id @Column( length=10) private String itemCode; @Column( nullable=false) private int quantity; … // define constructors, setters and getters }
As a result, the generated CREATE TABLE statement now contains the additional constraints expressed for the columns itemCode and quantity:
9.1 Java Annotations for Persistent Data Management and Constraint Validation
161
CREATE TABLE IF NOT EXISTS items ( itemCode varchar(10) NOT NULL PRIMARY KEY, quantity int(11) NOT NULL )
9.1.2 Bean Validation annotations In the Java EE Bean Validation¹ approach, Java runtime validation can be defined in the form of bean validation annotations placed on a property, method, or class. Table 9.1 Bean Validation annotations for properties Constraint Type
Annotations
Examples
String Length Constraints
@Size
@Size( min=8, max=80) String message;
Cardinality Constraints (for arrays, @Size collections and maps)
In addition, there are annotations that require a date value to be in the future (@Future) or in the past (@Past). All Bean Validation annotations have an optional message attribute for defining a custom error message. In the following example, we add two @NotNull annotations with messages, a @Size and a @Min annotation to the JPA constraint annotations. The @NotNull annotations constrain the itemCode and the quantity attributes to be mandatory, while the @Min annotation constrains the quantity attribute to have a minimum value of 0: @Entity @Table( name="items") public class Item { @Id @Column( length=8) @NotNull( message="An item code is required!")
9 Implementing Constraint Validation in a Java EE Web App
@Size( min=8, max=8) private String itemCode; @Column( nullable=false) @NotNull( message="A quantity is required!") @Min(0) private int quantity; }
Notice that that we need some duplicate logic in this example because the same constraints may have to be defined twice: as a JPA constraint and as a Bean Validation constraint. For instance, for a mandatory attribute like quantity we have both a @Column( nullable=false) JPA constraint annotation and a @NotNull Bean Validation annotation.
9.2 New Issues Compared to the Minimal App² discussed in Chapter 4 we have to deal with a number of new issues: 1. In the model layer we have to take care of adding for every property the constraints that must be checked before allowing a record to be saved to the database 2. In the user interface (view) we have to take care of form validation providing feedback to the user whenever data entered in the form is not valid. Checking the constraints in the user interface on user input is important for providing immediate feedback to the user. Using JSF and Bean Validation requires to submit the form before the validation checks are performed. It would be preferable to define the validation checks in the model classes only and use them in the user interface before form submission, without having to duplicate the validation logic in the JSF facelets. However, at this point in time, JSF does not support this, and the validation is performed only after the form is submitted. Using HTML5 validation attributes in the JSF facelets to enforce HTML5 validation before submitting the form requires an undesirable duplication of validation logic. The effect of such a duplication would be duplicate maintenance of the validation code, once in the model classes and once more in the user interface. In a simple application like our example app, this is doable, but in a larger application this quickly becomes a maintenance nightmare.
9.3 Make an Entity Class Model Using the information design model shown in Figure 7.2 above as the starting point, we make an Entity class model with getters/setters and corresponding Java datatypes.
Figure 9.1 Deriving an Entity class model from an information design model
The Entity class model shown on the right hand side in Figure 9.1 defines getters and setters for all properties and the following property constraint annotations: 1. Using @Id and @NotNull, the isbn attribute is declared to be a standard identifier, implying that it is mandatory and unique. 2. Using @Pattern(“\\b\\d{10}\\b”), the isbn attribute has a pattern constraint requiring its values to match the ISBN-10 format (simplified to the case of 10digit strings). 3. Using @NotNull and @Size( max=50), the title attribute is mandatory and has a string length maximum constraint of at most 50 characters. 4. Using @NotNull, @Min( 1459) and the custom annotation @UpToNextYear, the year attribute is mandatory and has an interval constraint, of a special form where the minimum is 1459 and the maximum is not fixed, but provided by a custom annotation implementing the calendar arithmetic function nextYear(). Since there is no predefined Bean Validation annotation for checking the uniqueness of an ID value provided when creating a new entity object, we define a static method checkIsbnAsId that can be invoked in a corresponding controller method when creating a new entity object. In addition, the entity class model defines the static CRUD data management methods retrieveAll, create, update and delete.
164
9 Implementing Constraint Validation in a Java EE Web App
9.4 Write the Model Code The Entity class model shown on the right hand side in Figure 9.1 can be coded step by step for getting the code of the entity classes of our Java EE web app.
9.4.1 Type mapping When defining the properties, we first need to map the platform-independent datatypes of the information design model to the corresponding implicit Java supported datatypes according to the following table. Table 9.2 Datatype mapping to Java Platform-independent datatype
Notice that for precise computations with decimal numbers, the special datatype java.math.BigDecimal³ is needed. A second datatype mapping is needed for obtaining the corresponding MySQL datatypes: Table 9.3 Datatype mapping to MySQL Platform-independent datatype
MySQL datatype
String Integer Decimal Boolean Date
VARCHAR INT DECIMAL BOOL DATETIME or TIMESTAMP
9.4.2 Code the constraints as annotations In this section we add JPA constraint annotations and Bean Validation annotations for implementing the property constraints defined for the Book class in the Java En http://www.opentaps.org/docs/index.php/How_to_Use_Java_BigDecimal:_A_Tutorial
9.4 Write the Model Code
165
tity class model. For the standard identifier attribute isbn, we add the JPA constraint annotations @Id and @Column( length=10), as well as the Bean Validation annotations @NotNull and @Pattern( regexp="\\b\\d{10}\\b"). Notice that, for readability, we have simplified the ISBN pattern constraint. For the attribute title, we add the JPA constraint annotation @Column( nullable=false), as well as the Bean Validation annotations @NotNull and @Size( max=50). For the attribute year, we add the JPA constraint annotation @Column( nullable=false), as well as the Bean Validation annotations @NotNull and @Min( value=1459). Notice that we cannot express the constraint that year must not be greater than next year with a standard validation annotation. Therefore, we’ll define a custom annotation for this constraint in Section 6 below. Coding the integrity constraints with JPA constraint annotations and Bean Validation annotations results in the following annotated bean class: @Entity @Table( name="books") @ManagedBean( name="book") @ViewScoped public class Book { @Id @Column( length=10) @NotNull( message="An ISBN value is required!") @Pattern( regexp="\\b\\d{10}\\b", message="The ISBN must be a 10-digit string!") private String isbn; @Column( nullable=false) @NotNull( message="A title is required!") @Size( max=255) private String title; @Column( nullable=false) @NotNull( message="A year is required!") @Min( value=1459, message="The year must not be before 1459!") private Integer year; …
Notice that for the year property, the Java Integer wrapper class is used instead of the primitive int datatype. This is required for the combined use of JSF and JPA, because if the value of an empty year input field is submitted in the create or update forms, the value which is passed to the year property by JSF via the setYear method is null (more details on Section 4.5, “Requiring non-empty strings”), which is not admitted for primitive datatypes by Java.
166
9 Implementing Constraint Validation in a Java EE Web App
We only provide an overview of the methods. For more details, see Chapter 4.
9.4.3 Checking uniqueness constraints For avoiding duplicate Book records we have to check that the isbn values are unique. At the level of the database, this is already checked since the isbn column is the primary key, and the DBMS makes sure that its values are unique. However, we would like to check this in our Java app before the data is passed to the DBMS. Unfortunately, there is no predefined Bean Validation annotation for this purpose, and it is not clear how to do this with a custom validation annotation. Therefore we need to write a static method, Book.checkIsbnAsId, for checking if a value for the isbn attribute is unique. This check method can then be called by the controller for validating any isbn attribute value before trying to create a new Book record. The Book.checkIsbnAsId method code is shown below: public static void checkIsbnAsId( EntityManager em, String isbn) throws UniquenessConstraintViolation, MandatoryValueConstraintViolation { if (isbn == null) { throw new MandatoryValueConstraintViolation( "An ISBN value is required!"); } else { Book book = Book.retrieve( em, isbn); // book was found, uniqueness constraint validation failed if ( book != null) { throw new UniquenessConstraintViolation( "There is already a book record with this ISBN!"); } } }
The method throws a UniquenessConstraintViolation exception in case that a Book record was found for the given ISBN value. The exception can then be caught and a corresponding error message displayed in the UI. In the sequel of this chapter we show how to define the controller validation method and inform JSF facelets that it must be used to validate the isbn form input field. Notice that in this case we also need to check the isbn value and reject null values, because the @NotNull validation triggers only later, when the isbn property of the Book is set, thus at this point we could get NullPointerException, from the Book.retrieve method.
9.4 Write the Model Code
167
9.4.4 Dealing with model-related exceptions The Book.checkIsbnAsId method discussed in the previous sub-section is designed to be used in combination with a controller so the user gets an error message when trying to duplicate a Book record (i. e., if the provided isbn value is already used in an existing record). However, if the Book.create method is used directly (i. e. by another piece of code, where the uniqueness constraint is not performed by calling Book.checkIsbnAsId), then uniqueness constraint validation may fail. Lets have a look on the Book.create code: public static void create( EntityManager em, UserTransaction ut, String isbn, String title, int year) throws NotSupportedException, SystemException, IllegalStateException, SecurityException, HeuristicMixedException, HeuristicRollbackException, RollbackException, EntityExistsException { ut.begin(); Book book = new Book( isbn, title, year); em.persist( book); ut.commit(); }
The method may throw a number of exceptions when trying to run the persist or the commit method. One of the exceptions (i. e. EntityExistsException) is thrown by the ut.commit call. The method which calls Book.create may catch this exception and perform specific actions, such as rolling back the transaction. In our case, the Book.create is called by the create action method of the BookController class, and the action performed is to show the exception stack trace in the console, as well as calling the ut.rollback which takes care of cancelling any database change performed by the current transaction. The rest of the exceptions are caught by using their super class (i. e. Exception) and the exception stack trace is displayed in the console. public String create( String isbn, String title, int year) { try { Book.create( em, ut, isbn, title, year); } catch ( EntityExistsException e) { try { ut.rollback(); } catch ( Exception e1) { e1.printStackTrace(); } e.printStackTrace(); } catch ( Exception e) { e.printStackTrace(); } return "create"; }
168
9 Implementing Constraint Validation in a Java EE Web App
Note: the EntityExistsException is part of the javax.persistence package (i. e. javax.persistence.EntityExistsException). TomEE uses the Apache OpenJPA⁴ implementation of the JPA API, which means that the EntityExistsException class (and other exceptions classes too) are part of the org. apache.openjpa.persistence package. Therefore, using this exception with our code, requires to import org.apache.openjpa.persistence.EntityExistsException instead of import javax.persistence.EntityExistsException as well as adding the openjpa-xxx.jar (located in the lib subfolder of the TomEE installation folder) to the Java application class path for being able to have the code compiled with Eclipse or other IDE tools.
9.4.5 Requiring non-empty strings Normally a mandatory string-valued attribute, such as title, requires a non-empty string, which is expressed in our model above by the range NonEmptyString. For treating empty strings as no value, the context parameter javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL must be set to true in web.xml:
9.5 Write the View Code After we have defined the constraints in the Java EE model layer and the database layer, we need to take care of validation in the user interface. In particular, we need to make sure that the user gets informed about issues by rendering visual indicators and informative validation error messages.
9.5.1 Validation in the Create use case The WebContent/views/books/create.xhtml file contains the JSF facelet code for creating a new Book record. We now use the JSF validator attribute for performing the uniqueness validation and JSF message elements for displaying validation error messages. http://openjpa.apache.org/
9.5 Write the View Code
169
Create a new book record
…
There are only a few changes compared to the same view used for the minimal app, where no validation was performed. The first change is the new h:message element which is bound to a specific form element by the for attribute. We create such an element for each of our form input elements. Notice that we don’t have to do anything else for seeing the validation errors for all integrity constraint checks which are performed by using the (built-in and custom) Bean Validation annotations. As soon as a constraint validation fails, the message set by using the message property of the integrity constraint annotation (e. g. @Pattern, @NotNull, etc) is displayed in an HTML span element generated by JSF as a result of using the h:message element. For all the integrity constraints we have used Bean Validation annotations, but for the uniqueness constraint we have used custom code, therefore no error message will be shown for it. In the view code we can see that a new attribute, validator in h:inputText, was used for the isbn input field. It specifies which custom method is used to perform validation of the provided value in this form field. In our case, we use the checkIsbnAsId method defined in the BookController as shown below: public void checkIsbnAsId( FacesContext context, UIComponent component, Object value) throws ValidatorException { String isbn = (String) value; try { Book.checkIsbnAsId( em, isbn);
170
9 Implementing Constraint Validation in a Java EE Web App
} catch ( UniquenessConstraintViolation e) { throw new ValidatorException( new FacesMessage( FacesMessage.SEVERITY_ERROR, e.getMessage(), e.getMessage())); } catch ( MandatoryValueConstraintViolation e) { throw new ValidatorException( new FacesMessage( FacesMessage.SEVERITY_ERROR, e.getMessage(), e.getMessage())); } }
The controller’s check method throws a ValidatorException which is also used to deliver the error message (the third parameter of the ValidatorException constructor) to the corresponding JSF facelet for being displayed in the UI. Methods used as JSF validators must have a specific syntax. The first two parameters of type FacesContext, respectively UIComponent are used by the container to invoke the method with references to the right view component and context, and they can be used in more complex validation methods. The last one, of type Object, represents the value to be validated by the method. This value has to be casted to the expected type (to String, in our example). It is important to know that, if a cast to a non-compatible type is performed, the validation method fails and an exception is thrown.
9.5.2 Validation in the Update use case In the Update use case, the facelet file update.xhtml in WebContent/views/ books was updated so it uses the h:message elements for being able to display validation errors:
Update a book record
…
9.6 Defining a Custom Validation Annotation
171
…
Since we do not allow to change the ISBN of a book, we create an output field for the isbn attribute with the JSF element h:outputText. This implies that no validation is performed. Using an h:outputText element for showing the value of an entity attribute results in an HTML span element. This implies that the HTTP form submission message contains no information about that attribute. If the validation fails, we expect to see the form content together with the error messages. To get the expected result, we need to use the annotation @ViewScoped for the entity class pl.m.Book instead of @RequestScoped, otherwise our bean instance referenced by the book variable is initialized with a new value on every request, implying that the expression #{book. isbn} evaluates to null and the ISBN value is not displayed. The @ViewScoped annotation specifies that the entity bean is alive as long as the associated view is alive, so the ISBN value stored by the book is available during this time and it can be displayed in the view. By contrast, h:inputText elements result in HTML input elements which are part of the form submission content, so the response contains the already existing values because these values are known in this case. This consideration shows that it is important to choose the right bean scope.
9.6 Defining a Custom Validation Annotation One other integrity constraint we have to consider is about the allowed values of the year property, which must be in the interval [1459, nextYear()] where nextYear() is a function invocation expression. We may have the idea to use @Min and @Max to specify the interval constraint, but this is not possible because the @Max annotation (as well as any other annotation) does not allow expressions, but only data literals. So, while we can express the interval’s lower bound with @Min( value=1459), we need another solution for expressing the upper bound. Fortunately, the Bean Validation API allows to define custom validation annotations with custom code performing the constraint checks. This means that we are free to express any kind of validation logic in this way. Creating and using a custom validation annotation requires the following steps: 1. Create the annotation interface UpToNextYear with the following code: @Target( {ElementType.FIELD, ElementType.METHOD}) @Retention( RetentionPolicy.RUNTIME) @Constraint( validatedBy = UpToNextYearImpl.class) public @interface UpToNextYear {
172
2.
9 Implementing Constraint Validation in a Java EE Web App
String message() default "The value of year must be between 1459 and next year!"; Class[] groups() default {}; Class[] groups() default {}; Class