XPages to Java EE, Part 12: Container Authentication

Wed Feb 20 12:24:56 EST 2019

Tags: javaee
  1. XPages to Java EE, Part 1: Overview
  2. XPages to Java EE, Part 2: Terminology
  3. XPages to Java EE, Part 3: Hello, World
  4. XPages to Java EE, Part 4: Application Servers
  5. XPages to Java EE, Part 5: Web Pages
  6. XPages to Java EE, Part 6: Dependencies
  7. XPages to Java EE, Part 7: MVC
  8. XPages to Java EE, Part 8: IDE Server Integration
  9. XPages to Java EE, Part 9: IDE Features Grab Bag
  10. XPages to Java EE, Part 10: Data Storage
  11. XPages to Java EE, Part 11: Mixing MVC and an API
  12. XPages to Java EE, Part 12: Container Authentication
  13. XPages to Java EE, Part 13: Why Do This, Anyway?

Coming from Domino, we're both spoiled by the way it handles users and authentication. The server comes with an implicit directory far removed from the app, and so we just use that. Moreover, the HTTP "container" handles authentication for us: we configure whether we want HTTP Basic auth, simple session auth, LTPA, or SAML (I guess) authentication at the server/web site level, also far from the app. Then, in the application, we just set up an ACL in the DB or XPage and reader/author fields on notes and let the server take care of it.

We're also pretty constrained by this, as anyone who has wanted to set up a custom in-app login page, in-app user registry, or even server-level specialized user repository knows. The convenience comes with a cost.

Java EE's Routes

Authentication in Java EE has a complicated history, but the general idea is that there are three main current ways to handle user registries and authentication:

  1. Container authentication, which is roughly equivalent to Domino. In this case, you configure your server with knowledge about the user registry (say, a static set of users or an LDAP server) and how those users map to "roles" in the JEE sense (more on this in a bit), and then your app just tells the server what parts need login.
  2. Per-app custom authentication. This is roughly equivalent to doing a special in-app session cookie in a Domino app for authentication, but the hooks provided by Java EE make it easier to manage. You could do this entirely homebrew or use a project like Apache Shiro to do the heavy lifting for you.
  3. The Java EE Security API 1.0. This is a new standard, introduced in Java EE 8, meant to give apps the fine-grained custom control of the second option while offloading as many particulars as desired to the servlet container - essentially, a "best of both worlds" approach. In practice, I've found it a bit under-documented and fiddly in its current incarnation, but I'll aim to cover it in a future post.

Container Authentication

For this post, I'll cover setting up container authentication, since it's the easiest, is well-documented, and can even serve the "per-app" role if you follow the "one app per server" model that's in vogue for microservice/"cloud native" applications.

For testing and development cases, web servers generally provide a mechanism for writing out a simple user/password list. Open Liberty does this via server.xml and Tomcat/TomEE does it via tomcat-users.xml. However, since we're Domino people, that means we have access to an LDAP server we're already comfortable with running, so we can jump right into a "real" setup.

Configuring Domino for LDAP

This topic is a bit out of the bailiwick of this series, but usually it just means creating an LDAP Internet Site and running load ldap. Domino's pretty convenient sometimes. I like to test a freshly-configured LDAP server with Apache Directory Studio.

Configuring Liberty

Once you have LDAP running and working, open Liberty's "server.xml" file (dubbed "Server Configuration" in Eclipse's Servers view) and add the ldapRegistry-3.0 feature and an ldapRegistry element within the top-level server element, customized for your setup:

<featureManager>
    <feature>javaee-8.0</feature>
    <feature>localConnector-1.0</feature>
    <feature>mpOpenAPI-1.0</feature>
    <feature>ldapRegistry-3.0</feature>
</featureManager>

<ldapRegistry host="some.domino.server" port="389" sslEnabled="false"
    	bindDN="someuser" bindPassword="somepassword" 
    	ldapType="IBM Lotus Domino" baseDN=""/>

There are ways to prevent putting an unencrypted password in the "server.xml" file, but this will do for now.

As a nice bonus, Liberty's IBM provenance means we get built-in knowledge of Domino and so don't have to jump through the common hoops of setting up query filters and whatnot.

The other bit of configuration in this file is to map users and groups to roles. This is an area where Domino and JEE diverge a bit. "Roles" in JEE mean more or less the same thing as in Domino, but they're configured outside of the application. They can be configured per-app within the server configuration, accomplishing the same end result, but there's a different level of indirection. For our users, we'll configure them server-wide. So, add another element as a peer to the ldapRegistry:

<authorization-roles>
  <security-role name="admin">
    <group name="LocalDomainAdmins"/>
  </security-role>
</authorization-roles>

You can feel free to customize this at will - there's also a user type and the concept lines up pretty much with what you'd expect.

Configuring the App

The server configuration on its own doesn't yet change anything - we can still visit any page in the app without a login prompt. What we should do now is to clamp down certain aspects. To demonstrate this, we'll add a UI operation for deleting people and restrict it to our freshly-minted "admin" role. Go to the PersonController class and add the method:

@POST
@Path("{id}/delete")
@RolesAllowed("admin")
@Controller
@Operation(hidden=true)
public String deletePerson(@PathParam("id") String id) {
  personRepository.deleteById(id);
  return "redirect:people"; //$NON-NLS-1$
}

Note that we're using POST for this instead of DELETE as a nod to web browsers' historical limitations. There is a way to intercept and remap incoming requests so that we can use @DELETE annotations that I may cover later, but for now this should work fine.

Modify the "personList.tag" file within "WEB-INF/tags" in "Deployed Resources" to include a button to point to this action:

<%@tag description="Displays List&kt;model.Person&gt;" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
<%@attribute name="value" required="true" type="java.util.List" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<h1>People</h1>
<table>
	<thead>
		<tr>
			<th>ID</th>
			<th>Name</th>
			<th>Email Address</th>
			<th></th>
		</tr>
	</thead>
	<tbody>
		<c:forEach items="${pageScope.value}" var="person">
		    <tr>
		    	<td><c:out value="${person.id}"/></td>
		    	<td><c:out value="${person.name}"/></td>
		    	<td><c:out value="${person.emailAddress}"/></td>
		    	<td>
		    		<form action="${pageContext.request.contextPath}/resources/people/${person.id}/delete" method="POST">
		    			<input type="submit" value="X"/>
		    		</form>
		    	</td>
		    </tr>
		</c:forEach>
	</tbody>
</table>

If you visit http://localhost:9091/javaeetutorial/resources/people and click one of the deletion buttons, you should be greeted with an HTTP Basic authentication prompt that should block you until you enter in valid credentials for an admin.

Next Steps

At this point, we've really covered the main aspects that you'd need to know when making the move to Java EE. After this point, I have some ideas for various miscellaneous posts - other authentication options, app fit and finish, JSF, app configuration, deployment, and so forth - but, if you've been following along, I suggest you take the opportunity now to explore for yourself.

It would also be worth your time to look up some other existing educational resources. I hear that Java Brains is quite good on a lot of these topics, and Adam Bien is one of the leading sources for Java EE examples (and is the source of the template we used at the very start). When looking around, be wary of the age of the content: though pretty much anything related to Java EE will still work, there was a big move away from the Bad Old Days in recent versions, particularly EE 8, and so older examples may have you jump through uglier hoops than necessary. There are also whole technologies that are in common use that are not of immediate interest to us, such as Enterprise JavaBeans, so you can learn or not learn about those at will.

XPages to Java EE, Part 11: Mixing MVC and an API

Sat Feb 16 12:49:13 EST 2019

Tags: javaee
  1. XPages to Java EE, Part 1: Overview
  2. XPages to Java EE, Part 2: Terminology
  3. XPages to Java EE, Part 3: Hello, World
  4. XPages to Java EE, Part 4: Application Servers
  5. XPages to Java EE, Part 5: Web Pages
  6. XPages to Java EE, Part 6: Dependencies
  7. XPages to Java EE, Part 7: MVC
  8. XPages to Java EE, Part 8: IDE Server Integration
  9. XPages to Java EE, Part 9: IDE Features Grab Bag
  10. XPages to Java EE, Part 10: Data Storage
  11. XPages to Java EE, Part 11: Mixing MVC and an API
  12. XPages to Java EE, Part 12: Container Authentication
  13. XPages to Java EE, Part 13: Why Do This, Anyway?

When we set up our MVC controller classes, we put the @Controller annotation at the class level, which tells the environment that the entire class is dedicated to running the UI. However, we don't necessarily always want to do that - JAX-RS is the way to build REST APIs, after all, and so we should also add JSON versions of our Person methods.

Person Model Modification

Before we get to the meat of the code, go back to the Person class and modify it to remove the parameter to the @Id annotation and switch it to a String type:

package model;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.ws.rs.FormParam;

import org.jnosql.artemis.Column;
import org.jnosql.artemis.Entity;
import org.jnosql.artemis.Id;

@Entity
public class Person {
	@Id
	private String id;
	@Column @FormParam("name") @NotBlank
	private String name;
	@Column @FormParam("emailAddress") @NotBlank @Email
	private String emailAddress;
	
	public String getId() { return id; }
	public void setId(String id) { this.id = id; }
	public String getName() { return name; }
	public void setName(String name) { this.name = name; }
	public String getEmailAddress() { return emailAddress; }
	public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; }
}

The reason for the change is that my original example was based on code from an older version of JNoSQL, and long IDs end up causing trouble when updating existing documents.

Also go to PersonRepository and modify it to use a String for the key:

package model;

import java.util.List;

import org.jnosql.artemis.Repository;

public interface PersonRepository extends Repository<Person, String> {
	List<Person> findAll();
}

 

Tweaking the Controller

The first step to adding in API methods doing this is to move the @Controller annotation down to just the methods that emit JSP responses (and adjust for the changed ID field while we're here):

package com.example;

import java.util.Random;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.validation.Valid;
import javax.ws.rs.BeanParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;

import org.bson.types.ObjectId;
import org.jnosql.artemis.Database;
import org.jnosql.artemis.DatabaseType;

import model.Person;
import model.PersonRepository;

@Path("/people")
@RequestScoped
public class PersonController {
	@Inject
	Models models;
	
	@Inject
	@Database(DatabaseType.DOCUMENT)
	PersonRepository personRepository;
	
	@GET
	@Controller
	public String home() {
		models.put("people", personRepository.findAll()); //$NON-NLS-1$
		return "person-new.jsp"; //$NON-NLS-1$
	}
	
	@POST
	@Controller
	public String createPerson(@BeanParam @Valid Person person) {
		if(person.getId() == null || person.getId().isEmpty()) {
			person.setId(new ObjectId().toHexString());
		}
		
		models.put("person", personRepository.save(person)); //$NON-NLS-1$
		models.put("people", personRepository.findAll()); //$NON-NLS-1$
		return "person-created.jsp"; //$NON-NLS-1$
	}
}

Doing this shouldn't change the behavior of the app, and that's what we want.

Add Some API methods

Now, to be a proper REST API, we'll want a suite of Create-Read-Update-Delete methods using standard HTTP verbs. Add these methods to the class:

@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Person> list() {
  return personRepository.findAll();
}

@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Person getPerson(@PathParam("id") String id) {
  return personRepository.findById(id).orElseThrow(() -> new javax.ws.rs.NotFoundException("Could not find person for ID " + id)); //$NON-NLS-1$
}

@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Person createPersonApi(@Valid Person person) {
  if(person.getId() == null) {
    person.setId(new ObjectId().toHexString());
  }
  return personRepository.save(person);
}

@DELETE
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public JsonObject deletePersonApi(@PathParam("id") String id) {
  personRepository.findById(id).orElseThrow(() -> new javax.ws.rs.NotFoundException("Could not find person for ID " + id)); //$NON-NLS-1$
  personRepository.deleteById(id);
  return Json.createObjectBuilder()
      .add("success", true) //$NON-NLS-1$
      .build();
}

@PUT
@Path("{id}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Person updatePersonApi(@PathParam("id") String id, @Valid Person person) {
  person.setId(id);
  return personRepository.save(person);
}

Here, we're taking advantage of JAX-RS's MIME-type-based routing: because our @Controller methods deal with HTML but these new methods declare that they're working with JSON, JAX-RS will route incoming browser visits to the controller and incoming API requests to the others.

Testing It Out

We can see this in action by trying out the API from the command line (or with a REST client app like Postman):

$ curl -i http://localhost:9091/javaeetutorial/resources/people
HTTP/1.1 200 OK
X-Powered-By: Servlet/4.0
Content-Type: application/json
Date: Sat, 16 Feb 2019 17:05:16 GMT
Content-Language: en-US
Content-Length: 246

[{"emailAddress":"api@test.com","id":"5c683fd40048ce11b5f6aee8","name":"API Test"},{"emailAddress":"foo@foo.com","id":"5c6841520048cea7c9f6c2d5","name":"Foo Fooson"},{"emailAddress":"foo@foo.com","id":"5c6841690048cea7c9f6c2d7","name":"API mod"}]

$ curl -i -X POST -H"Content-Type: application/json" http://localhost:9091/javaeetutorial/resources/people -d "{\"emailAddress\":\"foo@foo.com\",\"name\":\"Created with cURL\"}"
HTTP/1.1 200 OK
X-Powered-By: Servlet/4.0
Content-Type: application/json
Date: Sat, 16 Feb 2019 17:11:55 GMT
Content-Language: en-US
Content-Length: 89

{"emailAddress":"foo@foo.com","id":"5c68445b0048cea7c9402b85","name":"Created with cURL"}

$ curl -i -X PUT -H"Content-Type: application/json" http://localhost:9091/javaeetutorial/resources/people/5c68445b0048cea7c9402b85 -d "{\"emailAddress\":\"foo_mod@foo.com\",\"name\":\"Modified with cURL\"}"
HTTP/1.1 200 OK
X-Powered-By: Servlet/4.0
Content-Type: application/json
Date: Sat, 16 Feb 2019 17:12:30 GMT
Content-Language: en-US
Content-Length: 94

{"emailAddress":"foo_mod@foo.com","id":"5c68445b0048cea7c9402b85","name":"Modified with cURL"}

$ curl -i -X DELETE http://localhost:9091/javaeetutorial/resources/people/5c68445b0048cea7c9402b85
HTTP/1.1 200 OK
X-Powered-By: Servlet/4.0
Content-Type: application/json
Date: Sat, 16 Feb 2019 17:12:54 GMT
Content-Language: en-US
Content-Length: 16

{"success":true}

OpenAPI Documentation

OpenAPI is the boring-ified name of the standardized spec of Swagger, a mechanism for documenting REST APIs - kind of like what WSDL is for SOAP web services. This spec has become an important part of MicroProfile, the set of Java server technologies geared towards writing microservices that shares a lot of core technologies with Java EE. One of the niceties that MicroProfile includes is an automatic OpenAPI generator for JAX-RS services. There are a few things to add to our environment to enable this, but not too much.

To begin with, we'll have to enable the OpenAPI generator feature in Open Liberty (TomEE may have something like this; I don't know). To do that, open up the "server.xml" file (labeled "Server Configuration" in Eclipse's Servers view) and add "mpOpenAPI-1.0" to the feature list:

<featureManager>
    <feature>javaee-8.0</feature>
    <feature>localConnector-1.0</feature>
    <feature>mpOpenAPI-1.0</feature>
</featureManager>

Doing that alone will enable the API documentation, available at http://localhost:9091/openapi. However, if you look closely at the output, you'll see it's not exactly what we'd want: the GET operation for /resources/people points to our MVC home method, which it considers an unstructured string. It also lists the "helloworld" and "markdown" resources, and you can feel free to delete those classes outright - we won't be returning to them.

To fix this, first go to the project's "pom.xml" and add a dependency on the MicroProfile OpenAPI spec:

<dependency>
    <groupId>org.eclipse.microprofile.openapi</groupId>
    <artifactId>microprofile-openapi-api</artifactId>
    <version>1.1</version>
    <scope>provided</scope>
</dependency>

This is another one we can mark as "provided" since the implementation is included with the server.

Now, go back to the PersonController class, add an import line for org.eclipse.microprofile.openapi.annotations.Operation, and annotate the two MVC methods to mark them hidden from OpenAPI:

@GET
@Controller
@Operation(hidden=true)
public String home() {
  models.put("people", personRepository.findAll()); //$NON-NLS-1$
  return "person-new.jsp"; //$NON-NLS-1$
}

@POST
@Controller
@Operation(hidden=true)
public String createPerson(@BeanParam @Valid Person person) {
  if(person.getId() == null) {
    person.setId(new ObjectId().toHexString());
  }
  personRepository.save(person);

  models.put("person", person); //$NON-NLS-1$
  models.put("people", personRepository.findAll()); //$NON-NLS-1$
  return "person-created.jsp"; //$NON-NLS-1$
}

Now, if you refresh the /openapi output, you can see that it switched to the list method, and it knows that it outputs JSON, and includes a reference to the Person object structure at the bottom of the file.

There's a good deal more you can do with these annotations to customize the output, but it's nice to know that you can get an immediately-useful file that could be used to generate structured client libraries "for free".

Next Steps

Next, I think we'll dive into the world of Java EE authentication, which will be a very-different experience from what we're used to with Domino, for better and for worse.

XPages to Java EE, Part 10: Data Storage

Fri Feb 15 12:48:50 EST 2019

Tags: javaee
  1. XPages to Java EE, Part 1: Overview
  2. XPages to Java EE, Part 2: Terminology
  3. XPages to Java EE, Part 3: Hello, World
  4. XPages to Java EE, Part 4: Application Servers
  5. XPages to Java EE, Part 5: Web Pages
  6. XPages to Java EE, Part 6: Dependencies
  7. XPages to Java EE, Part 7: MVC
  8. XPages to Java EE, Part 8: IDE Server Integration
  9. XPages to Java EE, Part 9: IDE Features Grab Bag
  10. XPages to Java EE, Part 10: Data Storage
  11. XPages to Java EE, Part 11: Mixing MVC and an API
  12. XPages to Java EE, Part 12: Container Authentication
  13. XPages to Java EE, Part 13: Why Do This, Anyway?

How you store your data in an application is a potentially-huge topic, which is one of the reasons I've pushed it off for a while.

Designer's Curse

This is particularly the case because of the habits we learned over the years as Domino developers. One of the most grievous wounds Domino inflicted on us was an encouragement to always write directly to the data-storage implementation objects - forms and views for Notes client design or the lsxbe/lotus.domino classes for LotusScript and Java. They work, sure - fetching a document, setting fields, and storing it will get the job done - but it's an extremely-bad habit to work without a model framework and some level of indirection. Various people, including me, have made valiant efforts to add a model/DAO layer into XPages development in particular, but they've met with little uptake outside the individual developers who wrote them.

Fortunately, Java EE does not suffer from this specific brain poison, and it has a long history of abstracted data access, primarily via the Java Persistence API, traditionally backed by a JDBC driver for a SQL database. The point of an API like that is to let you write your model objects with just some annotations to explain to JPA bits about how it should be stored, and JPA will take care of the dirty work of actually mapping data types, writing queries, fetching data, and so forth.

JNoSQL

We won't be using JPA for this example, though. Instead, we'll be adding our second incubating spec: JNoSQL. JNoSQL is intended to be essentially "JPA for NoSQL", a largely-rethought API that won't crash into the hackiness of Hibernate's valiant attempt of re-using JPA directly. JNoSQL is currently slated for standardization as part of Jakarta EE and is under active development, but reached a point a while ago where it's good for use.

However, while there's technically a Domino JNoSQL driver that I put together last year, it's more of a POC than a real thing, and we'll skip it for this. For my uses, I've been using Darwino, which does have a splendid JNoSQL driver, but this series isn't the place to go through getting set up with that. For simplicity's sake, we'll be using MongoDB, which is quick to set up and is probably the furthest-developed driver in core JNoSQL.

MongoDB

So, to start out with, install MongoDB somewhere locally. This differs system-by-system - on Linux and macOS, I think it's available with package managers, or for any OS you can download an installer from their site.

Once it's installed, create a database named "exampledb" and a collection within it named "Person", as seen here with Compass, the standard admin app.

MongoDB collection configuration

Add the Driver

In your project's "pom.xml", add the JNoSQL document DB packages and MongoDB driver to your dependencies block:

		<!-- JNoSQL -->
		<dependency>
			<groupId>org.jnosql.artemis</groupId>
			<artifactId>artemis-core</artifactId>
			<version>0.0.7</version>
		</dependency>
		<dependency>
			<groupId>org.jnosql.artemis</groupId>
			<artifactId>artemis-document</artifactId>
			<version>0.0.7</version>
		</dependency>
		<dependency>
			<groupId>org.jnosql.artemis</groupId>
			<artifactId>artemis-validation</artifactId>
			<version>0.0.7</version>
		</dependency>
		<dependency>
			<groupId>org.jnosql.diana</groupId>
			<artifactId>mongodb-driver</artifactId>
			<version>0.0.7</version>
		</dependency>

For reference, "artemis" in JNoSQL terms refers to the mapping API - the annotations we're going to use - while "diana" refers to the driver portion.

Create the Configuration Class

Create a new class in the model package called DocumentCollectionManagerProducer:

package model;

import java.util.Collections;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;

import org.jnosql.diana.api.Settings;
import org.jnosql.diana.api.document.DocumentCollectionManager;
import org.jnosql.diana.api.document.DocumentCollectionManagerFactory;
import org.jnosql.diana.api.document.DocumentConfiguration;
import org.jnosql.diana.mongodb.document.MongoDBDocumentCollectionManager;
import org.jnosql.diana.mongodb.document.MongoDBDocumentCollectionManagerFactory;
import org.jnosql.diana.mongodb.document.MongoDBDocumentConfiguration;

@ApplicationScoped
public class DocumentCollectionManagerProducer {
	private DocumentConfiguration<MongoDBDocumentCollectionManagerFactory> configuration;
	private DocumentCollectionManagerFactory<MongoDBDocumentCollectionManager> managerFactory;
	
	@PostConstruct
	public void init() {
		configuration = new MongoDBDocumentConfiguration();
    // Modify this if MongoDB is not on localhost
		Map<String, Object> settings = Collections.singletonMap("mongodb-server-host-1", "localhost:27017"); //$NON-NLS-1$ //$NON-NLS-2$
		managerFactory = configuration.get(Settings.of(settings));
	}
	
	@Produces
	public DocumentCollectionManager getManager() {
		return managerFactory.get("exampledb"); //$NON-NLS-1$
	}
}

There's a lot there, but fortunately some of it builds on the CDI producer/scope functionality we encountered earlier. What we're doing here is setting up an application-wide bean that produces a configuration object for JNoSQL to use - specifically, using MongoDB. In a real situation, you'd want to externalize the settings in some way, but putting it into the code will do for now. The getManager() method will be used behind the scenes when JNoSQL asks the environment for a document-database manager.

Create the Model

Create another new class in the model package, this time named Person:

package model;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.ws.rs.FormParam;

import org.jnosql.artemis.Column;
import org.jnosql.artemis.Entity;
import org.jnosql.artemis.Id;

@Entity
public class Person {
	@Id("id")
	private long id;
	@Column @FormParam("name") @NotBlank
	private String name;
	@Column @FormParam("emailAddress") @NotBlank @Email
	private String emailAddress;
	
	public long getId() { return id; }
	public void setId(long id) { this.id = id; }
	public String getName() { return name; }
	public void setName(String name) { this.name = name; }
	public String getEmailAddress() { return emailAddress; }
	public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; }
}

This class uses JNoSQL's annotations to define an object that can be stored (@Entity), its unique ID field (@Id), and the fields contained in it (@Column, a name that matches JPA's SQL-based view of the world). You can also see that it includes the JAX-RS and validation annotations from the class we set up when learning about MVC. With the artemis-validation dependency we included, JNoSQL will, like JAX-RS, automatically enforce bean property constraints like this when saving, meaning we don't have to spent so much time dealing with validation logic ourselves.

Whether or not it's a good idea mix the JAX-RS and persistence annotations like this is something I'm not entirely sure about, but it'll work for our purposes.

Create the Repository

Create a new interface (not a class) in the model package named PersonRepository:

package model;

import java.util.List;

import org.jnosql.artemis.Repository;

public interface PersonRepository extends Repository<Person, Long> {
	List<Person> findAll();
}

You may be thinking at this point, as I originally did, that the next step will be to create an implementation class to do the work for this. Nope! This is where some real CDI voodoo comes into play: inside JNoSQL is a bean that produces "proxy" classes on the fly for Repository interfaces and figures out implementations of the methods based on their names, return types, and parameters. It's not magic - there are limits - but in cases like this it'll do what we'd otherwise expect to have to do ourselves.

Back to the PersonController

Return to the PersonController class we created before and rework it to use our newly-minted JNoSQL objects:

package com.example;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.validation.Valid;
import javax.ws.rs.BeanParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;

import org.jnosql.artemis.Database;
import org.jnosql.artemis.DatabaseType;

import model.Person;
import model.PersonRepository;

@Path("/people")
@Controller
@RequestScoped
public class PersonController {
	@Inject
	Models models;
	
	@Inject
	@Database(DatabaseType.DOCUMENT)
	PersonRepository personRepository;
	
	@GET
	public String home() {
		models.put("people", personRepository.findAll()); //$NON-NLS-1$
		return "person-new.jsp"; //$NON-NLS-1$
	}
	
	@POST
	public String createPerson(@BeanParam @Valid Person person) {
		if(person.getId() == 0) {
			person.setId(new Random().nextLong());
		}
		personRepository.save(person);
		
		models.put("person", person); //$NON-NLS-1$
		models.put("people", personRepository.findAll()); //$NON-NLS-1$
		return "person-created.jsp"; //$NON-NLS-1$
	}
}

Add a Person List Tag

Back in the "Deployed Resources" section of the project, create a new directory beneath "WEB-INF" called "tags", and within that create a new file named "personList.tag":

WEB-INF/tags directory

Set the file contents to:

<%@tag description="Displays List&kt;model.Person&gt;" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
<%@attribute name="value" required="true" type="java.util.List" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<h1>People</h1>
<table>
	<thead>
		<tr>
			<th>ID</th>
			<th>Name</th>
			<th>Email Address</th>
		</tr>
	</thead>
	<tbody>
		<c:forEach items="${pageScope.value}" var="person">
		    <tr>
		    	<td><c:out value="${person.id}"/></td>
		    	<td><c:out value="${person.name}"/></td>
		    	<td><c:out value="${person.emailAddress}"/></td>
		    </tr>
		</c:forEach>
	</tbody>
</table>

This is our JSP equivalent of an XPages custom control, though all of the configuration is done inline instead of via XPages's auto-maintained .xsp-config side file.

Update the Person Views

Modify "person-new.jsp":

<%@page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>
<!DOCTYPE html>
<html lang="${translation._lang}">
	<head>
		<title>${translation.appTitle}</title>
	</head>
	<body>
		<h1>Create Person</h1>
		<form method="post" action="people">
			<dl>
				<dt>Name</dt>
				<dd><input name="name" required/></dd>
			</dl>
			<dl>
				<dt>Email Address</dt>
				<dd><input type="email" name="emailAddress" required/></dd>
			</dl>
			<input type="submit" value="Submit"/>
		</form>
		
		<t:personList value="${people}"/>
	</body>
</html>

Do similarly to "person-created.jsp":

<%@page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>
<!DOCTYPE html>
<html lang="${translation._lang}">
	<head>
		<title>${translation.appTitle}</title>
	</head>
	<body>
		<h1>Created Person</h1>
		<dl>
			<dt>Name</dt>
			<dd><c:out value="${person.name}"/></dd>
		</dl>
		<dl>
			<dt>Email Address</dt>
			<dd><c:out value="${person.emailAddress}"/></dd>
		</dl>
		
		<t:personList value="${people}"/>
	</body>
</html>

Take It For a Spin

Launch the Liberty server and visit http://localhost:9091/javaeetutorial/resources/people. You should be able to add new entries, with the browser taking care of the client-side validation for you and JNoSQL and JAX-RS handling it on the server side. Best of all, the data should persist!

Updated UI with person listing

If you look at the database in Compass, you'll see entries there as well. JNoSQL mapped the Person class name to the "Person" collection in the database:

Data stored in MongoDB

Next Steps

In the next post, I plan to touch a bit on mixing MVC controller methods with JSON-based REST APIs, to bring these parts together into something that starts to approach a real application.

Update: Troubleshooting Note

One thing I encountered in my fiddling was an intermittent case where the server wouldn't load the app, instead complaining about an unsatisfied provider for the PersonRepository class. If you run into this, make sure you have a "beans.xml" file inside your "webapp/WEB-INF" directory in "Deployed Resources", and set its contents to:

<?xml version="1.0" encoding="UTF-8"?>
<beans bean-discovery-mode="all"
  xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"/>

This is the CDI configuration file. Though it's mostly empty, the critical part is bean-discovery-mode="all", which causes it to check all available providers in the classpath.

XPages to Java EE, Part 9: IDE Features Grab Bag

Wed Feb 13 11:50:26 EST 2019

Tags: javaee
  1. XPages to Java EE, Part 1: Overview
  2. XPages to Java EE, Part 2: Terminology
  3. XPages to Java EE, Part 3: Hello, World
  4. XPages to Java EE, Part 4: Application Servers
  5. XPages to Java EE, Part 5: Web Pages
  6. XPages to Java EE, Part 6: Dependencies
  7. XPages to Java EE, Part 7: MVC
  8. XPages to Java EE, Part 8: IDE Server Integration
  9. XPages to Java EE, Part 9: IDE Features Grab Bag
  10. XPages to Java EE, Part 10: Data Storage
  11. XPages to Java EE, Part 11: Mixing MVC and an API
  12. XPages to Java EE, Part 12: Container Authentication
  13. XPages to Java EE, Part 13: Why Do This, Anyway?

In today's post, I'm going to go over a handful of features that IDEs, particularly Eclipse, bring to the table to improve the development experience. Some of these aren't unique to EE development, but our use of Maven and standardized technology makes them better than their equivalents in XPages development.

Open Declaration

In Eclipse, if you right click on most anything in a Java file, you can choose "Open Declaration" (or press F3 with the text cursor inside it), you can go to the source of whatever it is, if available:

Open Declaration menu item

This also works in Designer, but it's often much more useful here: because Designer ships without source for its JVM or really any of the classes that make up the XPage stack, you tend to be greeted by the white "here's a bunch of bytecode" screen, which is rarely particularly useful.

Since we're working with open-source components and we told Eclipse to download source and Javadoc for Maven artifacts, though, almost everything will have available source, letting you explore what's happening much more easily.

This will also work for your own code, making F3 an extremely-useful method of navigation. While you're there, try out the "Open Type Hierarchy" and "Open Call Hierarchy" options too.

Open Implementation

CDI's technique of using @Inject to inject implementations of interfaces automatically is a great way to abstract away the business of doing a new SomethingImpl() or finding the managed bean. However, sometimes it's good to find out where the objects you're getting are actually coming from, and Eclipse lets you do this by holding Command (probably Control on Windows, if I had to guess) and hovering over an interface name:

Open Implementation menu option

In this menu, "Open Declaration" will take you to the Models interface class file, while "Open Implementation" will take you to the ModelsImpl implementation class.

Note, though, that Eclipse isn't really following CDI logic here - instead, it's just trying to find implementing classes and either opening a single implementation if there's only one or showing a menu to select from multiple. It happens that that's usually the same thing effectively, but it's an important distinction.

IntelliJ is a bit smarter on this point: it will try to figure out all the CDI logic and show a little bean icon on a line with an injection to take you to the object or method providing it:

IntelliJ bean detection

JBoss Tools

To be fair to Eclipse, there is a project to provide a great deal of improved Java EE behavior: JBoss Tools. It comes with a bunch of addins, editors, and configurators meant to make Eclipse much more aware of things like CDI's real logic. I personally have found it pretty janky in practice, but it's been a while since I gave it a proper shot. Your mileage may vary.

JAX-RS Resources

Since JAX-RS is such a critical part of modern Java development, it comes in handy that Eclipse has some specialized knowledge of it. If you expand the "Services" folder in your project, you'll have a "REST" folder that contains all of your declared JAX-RS endpoint classes and their methods:

Eclipse JAX-RS resource listing

You can use this as a general overview of your app's external API (and, when using MVC, its full URL layout) and you can also double-click any of the entries to go to the declaration.

If you get into a situation where you're writing a paired set of a server module and a client module, you can also let Eclipse generate a resource client class for you.

Deployment Descriptor

Within the "Deployment Descriptor" node in your project, Eclipse lists a bunch of EE/Servlet stuff:

Deployment Descriptor node

Historically, this referred specifically to the contents of the web.xml, but it also shows annotated classes as applicable - such as the Listener coming from the Ozark MVC implementation. Depending on how you're constructing your app, this may be more or less useful, but it's definitely good to know it's there.

Up Next

Next time, maybe I'll finally get to talking about users and data. Or maybe I won't! We'll see.

 

XPages to Java EE, Part 8: IDE Server Integration

Tue Feb 12 10:52:21 EST 2019

Tags: javaee
  1. XPages to Java EE, Part 1: Overview
  2. XPages to Java EE, Part 2: Terminology
  3. XPages to Java EE, Part 3: Hello, World
  4. XPages to Java EE, Part 4: Application Servers
  5. XPages to Java EE, Part 5: Web Pages
  6. XPages to Java EE, Part 6: Dependencies
  7. XPages to Java EE, Part 7: MVC
  8. XPages to Java EE, Part 8: IDE Server Integration
  9. XPages to Java EE, Part 9: IDE Features Grab Bag
  10. XPages to Java EE, Part 10: Data Storage
  11. XPages to Java EE, Part 11: Mixing MVC and an API
  12. XPages to Java EE, Part 12: Container Authentication
  13. XPages to Java EE, Part 13: Why Do This, Anyway?

I said that the next post was going to be about authentication or databases, but I'm turning that into a filthy lie. Instead, we're going to take a bit of a detour to talk about a couple ways to let the IDE help you out in development. I'll be talking about Eclipse specifically, but I know that at least the paid version of IntelliJ IDEA has similar features. The first feature on the docket is having the IDE manage an app server for you.

Servers

Up until this point, we've been using the TomEE Maven plugin to create and launch a server for us, which is convenient and portable across whatever environment you're working with. However, once you have a couple applications or want to make persistent server configurations outside of the individual app's pom.xml, it makes sense to run a server and deploy the app to it.

This is where Eclipse's "Servers" view comes into play. If the panel isn't currently in your workspace, go to Window -> Show View -> Other... and choose "Servers" under the "Server" category. By default, it'll be stacked with some other tabs, but I like to position this pane in the bottom-right of my IDE:

Eclipse Servers view

The way this view tends to work is that it points either at a local server installation or to a running remote server. We'll want the former, but, sadly, this is where we part ways with TomEE. Though the server is more than capable for our needs, the Eclipse integration (based on base Tomcat) is not, and it hinders its use here. Instead, we'll be switching to my current favorite: Open Liberty. Download the latest build from that page (19.0.0.1 as of this writing) and extract the archive to some location on your system (I like to keep a directory of various app servers).

Next, we'll need to install the Liberty support plugin in Eclipse, which is a fortunately-simple matter. Visit the Liberty in Eclipse download page and drag the "Install" button onto your Eclipse toolbar. Once it loads, click Next and Finish a couple times until it's done, and then let it restart Eclipse.

Once Eclipse restarts, click that "Click this link to create a new server..." link. In the resultant dialog, choose "Liberty Server" under the "IBM" category and give it a descriptive name:

Eclipse's New Server dialog

Click Next > and enter the path where you extracted Open Liberty in the "Path" field:

Server path

Click Next >, leave everything at its default, and click Finish.

Click the twistie next to the newly-created server and double-click the "Server Configuration [server.xml] new server" entry to open the server config editor. If it opens on the "Design" tab, click the "Source" tab to see the XML configuration. Set it to:

<server description="new server">
    <!-- Enable features -->
    <featureManager>
        <feature>javaee-8.0</feature>
        <feature>localConnector-1.0</feature>
    </featureManager>

    <!-- To access this server from a remote client add a host attribute to the following element, e.g. host="*" -->
    <httpEndpoint httpPort="9091" httpsPort="9443" id="defaultHttpEndpoint"/>

    <!-- Automatically expand WAR files and EAR files -->
    <applicationManager autoExpand="true"/>
  
    <keyStore id="defaultKeyStore" password="testKeystore"/>
</server>

 

The first difference here from the default is to swap out the jsp-2.3 feature for javaee-8.0. Liberty organizes its capabilities by "features" in order to let you really trim down the runtime if you don't need specific capabilities. For our sake, we want the whole EE shebang available, though. localConnector-1.0 remains, and is what allows Eclipse to control the server.

We also changed the httpPort of the httpEndpoint element to 9091 to match what we've been using via the Maven config. We also added a basic not-exactly-secure keyStore configuration. This would be filled in more if you enable SSL.

Deploying the App

There are several ways to actually get our app onto this server, but the one I've built up a habit of using is to right-click the project, and then select Run As -> Run On Server:

Run on Server

The newly-created server should be selected by default in the resultant dialog, so just click Finish immediately.

The server will churn for a bit and eventually output [AUDIT ] CWWKT0016I: Web application available (default_host): http://localhost:9091/javaeetutorial/. It will also, I note with chagrin, output this:

[ERROR   ] SRVE0283E: Exception caught while initializing context: java.lang.IllegalArgumentException: The controller com.example.HomeController is not a managed CDI bean. Maybe the controller class is missing a scope annotation (e.g. @RequestScoped).
	at org.mvcspec.ozark.servlet.OzarkServletContextListener.failIfNoCdiBean(OzarkServletContextListener.java:76)
	at org.mvcspec.ozark.servlet.OzarkServletContextListener.contextInitialized(OzarkServletContextListener.java:60)
	at com.ibm.ws.webcontainer.webapp.WebApp.notifyServletContextCreated(WebApp.java:2377)
	at [internal classes]

That's because I forgot a step in the setup of MVC controllers yesterday, which is that you're supposed to add one of those annotations to the class too. Fortunately, the examples still work, and we'll fix that omission next time we edit the code.

Most likely, Eclipse will automatically open the app's default page in whatever browser it's configured to use - by default, a browser embedded in Eclipse itself.

Updating the App

In future posts, rather than specifically saying to run the clean install tomee:run Maven goal, I'll just say to run the app - either way should still work. By default, Eclipse will re-deploy the application to Liberty after changes, so you probably won't need to do anything other than refresh the page to see future changes.

Next Up

Next, I think I'm going to cover a grab bag of other features Eclipse has for working with Java EE apps before returning to the dirty business of writing code.

 

XPages to Java EE, Part 7: MVC

Mon Feb 11 15:04:22 EST 2019

Tags: javaee
  1. XPages to Java EE, Part 1: Overview
  2. XPages to Java EE, Part 2: Terminology
  3. XPages to Java EE, Part 3: Hello, World
  4. XPages to Java EE, Part 4: Application Servers
  5. XPages to Java EE, Part 5: Web Pages
  6. XPages to Java EE, Part 6: Dependencies
  7. XPages to Java EE, Part 7: MVC
  8. XPages to Java EE, Part 8: IDE Server Integration
  9. XPages to Java EE, Part 9: IDE Features Grab Bag
  10. XPages to Java EE, Part 10: Data Storage
  11. XPages to Java EE, Part 11: Mixing MVC and an API
  12. XPages to Java EE, Part 12: Container Authentication
  13. XPages to Java EE, Part 13: Why Do This, Anyway?

I mentioned in the last post that the rest of this tutorial is going to involve making some technology choices that are less universal than the ones up until this point. While pretty much every new EE app is going to involve CDI and JAX-RS, things will start to diverge once you decide on how you're going to handle authentication, your UI, and your data storage. We're going to dip into the the second one of those today.

Specifically, we'll be choosing the characteristically-dryly-named MVC Spec as the foundation for our interface.

MVC

If you're not familiar with the term "MVC" in a general sense, it stands for Model-View-Controller, and it represents one of the common ways to structure your application to keep the code clean and growable without it turning into a nightmare. There are other ways, and there are some flaws in the design that require band-aid solutions, but in general it remains a very-useful way to write your app. The general idea is that you have three components: the "model" (your data), the "view" (what your user sees), and the "controller" (what connects the two). On the web, this often takes the shape of having controllers pointed to by the "routing" within your application (e.g. /posts/new), which then do the work of fetching the data and binding it to the view.

XPages is ostensibly MVC-based, but that doesn't really play out in reality. The stack and IDE encourage direct mingling of the view and data (the <xp:dominoDocument/> data source and SSJS are the primary culprits here), and the lack of a model framework and the stultifying strictures of the built-in NHTTP routing make it very difficult to do it right even if you try (and lord knows we tried).

The MVC Spec

I've talked a bit about the spec before, and the idea of it is to build a simple-to-write-and-read MVC standard for Java EE, using existing EE technologies for the "view" part. I believe that the spec is heavily based on Spring MVC, though I haven't written any Spring apps.

The main reason I'm such a big fan of this spec is that it builds cleanly on top of JAX-RS, which already provides an excellent skeleton for cleanly-organized apps. JAX-RS already encourages clean, RESTful design for accessing data objects, and MVC builds on that to provide a natural way to display a web UI for non-API clients (which is to say, humans).

One thing to bear in mind with MVC 1.0 is that it's not quite a true Java EE component. It was slated for inclusion in Java EE 8, but Oracle cut it from the list before release. However, the spec has support within the Jakarta EE community and remains a likely candidate for future inclusion. Moreover, because the spec is so small and strongly encourages clean design, I feel that it's worth building upon - even if it disappeared tomorrow, almost all of your code would still work.

Adding to the Project

Since the spec isn't included in the default Java EE 8 spec or with EE servers, we'll need to add explicit dependencies to the pom.xml for the project, for both the spec itself and the reference implementation (currently named "Ozark", but it's going through a rename to "Krazo" for trademark purposes):

<!-- MVC 1.0 -->
<dependency>
  <groupId>javax.mvc</groupId>
  <artifactId>javax.mvc-api</artifactId>
  <version>1.0-pfd</version>
</dependency>
<dependency>
  <groupId>org.mvc-spec.ozark</groupId>
  <artifactId>ozark-core</artifactId>
  <version>1.0.0-m04</version>
</dependency>

Creating the Controller

MVC Controllers are implemented as normal JAX-RS resources with the extra @Controller annotation, and whose methods return an indicator of what view to use. Create a class in the com.example package called HomeController:

package com.example;

import java.time.LocalDateTime;

import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("/")
@Controller
public class HomeController {
	@Inject
	Models models;
	
	@GET
	public String hello() {
		models.put("hello", "Hello, MVC!"); //$NON-NLS-1$
		models.put("now", LocalDateTime.now()); //$NON-NLS-1$
		
		return "hello.jsp"; //$NON-NLS-1$
	}
}

The "Models" object is a MVC-provided object that acts sort of like a viewScope: you toss whatever objects you'd like into it and they're available as variables on your page. Despite having an important-sounding name, it's really just a simplified Map.

Creating the View

MVC supports several "templating" technologies, among them JSP and JSF Facelets. However, though it uses JSF technology, it's not meant to be used for a full JSF app with server-side state. From what I can tell, the JSF support is more for those who already have JSF apps to use. Despite XPages's heritage, that doesn't really apply to us. Create a new folder named "views" within the "webapp/WEB-INF" directory in "Deployed Resources" (which is "src/main/webapp" in the filesystem). Then, create a new file and call it "hello.jsp":

hello.xhtml location

Set its contents to:

<%@page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
<!DOCTYPE html>
<html lang="${translation._lang}">
	<head>
		<title>${translation.appTitle}</title>
	</head>
	<body>
		<h1>${hello}</h1>
		<p>I was loaded at ${now}.</p>
	</body>
</html>

Since this still uses CDI, we're still able to use the translation bean we set up earlier, but now we have access to the extra variables the controller set up.

If you do another Maven build with goals "clean install tomee:run" and visit http://localhost:8081/javaeetutorial/resources now, you should be greeted with the expected basic page:

Basic MVC output

Working With a Model

So that's two out of three down; time to simulate some data access. Using a real backing database will be its own large topic, so for now we'll create a simple in-memory object.

Create a new class in the "com.example" package named "PersonController":

package com.example;

import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.ws.rs.BeanParam;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;

@Path("/people")
@Controller
public class PersonController {
	public static class FormPerson {
		@FormParam("firstName") @NotEmpty
		private String firstName;
		@FormParam("lastName") @NotEmpty
		private String lastName;
		
		public String getFirstName() { return firstName; }
		public String getLastName() { return lastName; }
	}

	
	@Inject
	Models models;
	
	@GET
	public String home() {
		return "person-new.jsp"; //$NON-NLS-1$
	}
	
	@POST
	public String createPerson(@BeanParam @Valid FormPerson person) {
		models.put("person", person); //$NON-NLS-1$
		return "person-created.jsp"; //$NON-NLS-1$
	}
}

Next, create a new file in the "WEB-INF/views" directory named "person-new.jsp":

<%@page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
<!DOCTYPE html>
<html lang="${translation._lang}">
	<head>
		<title>${translation.appTitle}</title>
	</head>
	<body>
		<h1>Create Person</h1>
		<form method="post" action="people">
			<dl>
				<dt>First Name</dt>
				<dd><input name="firstName" required/></dd>
			</dl>
			<dl>
				<dt>Last Name</dt>
				<dd><input name="lastName"/></dd>
			</dl>
			<input type="submit" value="Submit"/>
		</form>
	</body>
</html>

Finally, create a second JSP file in the same directory named "person-created.jsp":

<%@page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="${translation._lang}">
	<head>
		<title>${translation.appTitle}</title>
	</head>
	<body>
		<h1>Created Person</h1>
		<dl>
			<dt>First Name</dt>
			<dd><c:out value="${person.firstName}"/></dd>
		</dl>
		<dl>
			<dt>Last Name</dt>
			<dd><c:out value="${person.lastName}"/></dd>
		</dl>
	</body>
</html>

Now, do another Maven build and visit http://localhost:9091/javaeetutorial/resources/people:

Create Person page

In this screenshot, you can see that the "First Name" field got a red outline because it's marked as required and I entered and then deleted a first name value. Neat!

Anyway, fill in something for "First Name" but not "Last Name", and hit "Submit". You'll be greeted with... nothing! Well, visibly nothing, anyway. You'll have a 400 response in your browser with no content, and you'll see a line like this in your server console:

11-Feb-2019 14:52:55.942 WARNING [http-nio-9091-exec-2] org.apache.cxf.jaxrs.validation.ValidationExceptionMapper.toResponse Value '' of PersonController.createPerson.arg0.lastName: {javax.validation.constraints.NotEmpty.message}

Hey, the server-side validation worked! Down the line, we'll probably add an exception handler to display this type of thing to the user, but, for now, the important part is that invalid data was blocked before it even got to our code.

Go back and enter something for each of the name fields, and then hit "Submit" again. While doing so, bask in the pleasant fact that, because there's no server-side state, you don't need to worry about mangled JSF view state or anything. Once you submit, you should be greeted with the "Created Person" page with your data:

Created Person page

As a nice bonus, because this output page uses the JSTL <c:out/> tag, the values are nicely HTML-escaped, making it a bit more secure than our original Hello World page.

Next Steps

In the next couple posts, we'll cover the ominous topics of authentication and data storage.