Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update tutorial to RESTful application. #372

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,188 +15,104 @@
This chapter shows how to work with Cayenne in a web application.

=== Converting Tutorial to a Web Application
The web part of the web application tutorial is done in JSP, which is the least common
denominator of the Java web technologies, and is intentionally simplistic from the UI
perspective, to concentrate on Cayenne integration aspect, rather than the interface. A
typical Cayenne web application works like this:

- Cayenne configuration is loaded when an application context is started, using a special servlet filter.
- User requests are intercepted by the filter, and the DataContext is bound to
the request thread, so the application can access it easily from anywhere.
- The same DataContext instance is reused within a single user session;
different sessions use different DataContexts (and therefore different sets of
objects). _The context can be scoped differently
depending on the app specifics. For the tutorial we'll be using a
session-scoped context._
The web part of the web application tutorial is done as RESTful application.
A typical Cayenne web application works like this:

- Cayenne configuration is loaded when an application context is started, using Jersey DI container.
- New ObjectContext is created for each request.

So let's convert the tutorial that we created to a web application:

- First you need to create `CayenneServerRuntimeFactory` for injecting `ServerRuntime` to your service:

.CayenneServerRuntimeFactory.java
[source,java]
----
include::{java-include-dir}/config/CayenneServerRuntimeFactory.java[tag=content]
----

- Then create `CayenneConfig` what is the basis of the application:

.CayenneConfig.java
[source,java]
----
include::{java-include-dir}/config/CayenneConfig.java[tag=content]
----

- In IDEA under "tutorial" project folder create a new folder `src/main/webapp/WEB-INF`.
- Under `WEB-INF` create a new file `web.xml` (a standard web app descriptor):

.web.xml
[source,xml]
----
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Cayenne Tutorial</display-name>

<!-- This filter bootstraps ServerRuntime and then provides each request thread
with a session-bound DataContext. Note that the name of the filter is important,
as it points it to the right named configuration file.
-->
<filter>
<filter-name>cayenne-project</filter-name>
<filter-class>org.apache.cayenne.configuration.web.CayenneFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cayenne-project</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>Cayenne Rest Demo</display-name>

<servlet>
<servlet-name>Cayenne Rest Demo</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>org.apache.cayenne.tutorial.config.CayenneConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Cayenne Rest Demo</servlet-name>
<url-pattern>/cayenne/*</url-pattern>
</servlet-mapping>

</web-app>
----

- Create the artist browser page `src/main/webapp/index.jsp` file with the following contents:
- Next step is to create POJO classes for your entities. It will allow us to return JSON objects as responses.
For example POJO object for `Artist` might look like:

.webapp/index.jsp
[source,jsp]
.ArtistPojo.java
[source,java]
----
<%@ page language="java" contentType="text/html" %>
<%@ page import="org.example.cayenne.persistent.*" %>
<%@ page import="org.apache.cayenne.*" %>
<%@ page import="org.apache.cayenne.query.*" %>
<%@ page import="org.apache.cayenne.exp.*" %>
<%@ page import="java.util.*" %>

<%
ObjectContext context = BaseContext.getThreadObjectContext();
List<Artist> artists = ObjectSelect.query(Artist.class)
.orderBy(Artist.NAME.asc())
.select(context);
%>

<html>
<head>
<title>Main</title>
</head>
<body>
<h2>Artists:</h2>

<% if(artists.isEmpty()) {%>
<p>No artists found</p>
<% } else {
for(Artist a : artists) {
%>
<p><a href="detail.jsp?id=<%=Cayenne.intPKForObject(a)%>"> <%=a.getName()%> </a></p>
<%
}
} %>
<hr>
<p><a href="detail.jsp">Create new artist...</a></p>
</body>
</html>
include::{java-include-dir}/pojo/ArtistPojo.java[tag=content]
----

- Create the artist editor page `src/main/webapp/detail.jsp` with the following content:
- Create POJOs for other entities.

.webapp/detail.jsp
[source,jsp]
- Finally you need to create resource class.

.CayenneResource.java
[source,java]
----
<%@ page language="java" contentType="text/html" %>
<%@ page import="org.example.cayenne.persistent.*" %>
<%@ page import="org.apache.cayenne.*" %>
<%@ page import="org.apache.cayenne.query.*" %>
<%@ page import="java.util.*" %>
<%@ page import="java.text.*" %>
<%@ page import="java.time.format.DateTimeFormatter" %>

<%
ObjectContext context = BaseContext.getThreadObjectContext();
String id = request.getParameter("id");

// find artist for id
Artist artist = null;
if(id != null &amp;&amp; id.trim().length() > 0) {
artist = SelectById.query(Artist.class, Integer.parseInt(id)).selectOne(context);
}

if("POST".equals(request.getMethod())) {
// if no id is saved in the hidden field, we are dealing with
// create new artist request
if(artist == null) {
artist = context.newObject(Artist.class);
}

// note that in a real application we would so dome validation ...
// here we just hope the input is correct
artist.setName(request.getParameter("name"));
artist.setDateOfBirthString(request.getParameter("dateOfBirth"));

context.commitChanges();

response.sendRedirect("index.jsp");
}

if(artist == null) {
// create transient artist for the form response rendering
artist = new Artist();
}

String name = artist.getName() == null ? "" : artist.getName();

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
String dob = artist.getDateOfBirth() == null
? "" :artist.getDateOfBirth().format(formatter);
%>
<html>
<head>
<title>Artist Details</title>
</head>
<body>
<h2>Artists Details</h2>
<form name="EditArtist" action="detail.jsp" method="POST">
<input type="hidden" name="id" value="<%= id != null ? id : "" %>" />
<table border="0">
<tr>
<td>Name:</td>
<td><input type="text" name="name" value="<%= name %>"/></td>
</tr>
<tr>
<td>Date of Birth (yyyyMMdd):</td>
<td><input type="text" name="dateOfBirth" value="<%= dob %>"/></td>
</tr>
<tr>
<td></td>
<td align="right"><input type="submit" value="Save" /></td>
</tr>
</table>
</form>
</body>
</html>
include::{java-include-dir}/resource/CayenneResource.java[tag=content]
----

==== Running Web Application

We need to add cayenne-web module and javax servlet-api for our application.
We need to add jersey-container-servlet, jersey-hk2, jersey-media-json-jackson and
javax servlet-api for our application.

.pom.xml
[source,xml]
----
<dependency>
<groupId>org.apache.cayenne</groupId>
<artifactId>cayenne-web</artifactId>
<version>${cayenne.version}</version>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.28</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>2.28</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.28</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
----
Expand All @@ -213,7 +129,7 @@ section and save the POM:
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.8.v20171121</version>
<version>9.4.15.v20190215</version>
</plugin>
</plugins>
</build>
Expand Down Expand Up @@ -255,32 +171,10 @@ like this:

- So the Jetty container just started.

- Now go to http://localhost:8080/ URL. You should see "No artists found message" in the web browser and
the following output in the IDEA console:

INFO: Loading XML configuration resource from file:/.../tutorial/target/classes/cayenne-project.xml
INFO: loading user name and password.
INFO: Connecting to 'jdbc:derby:memory:testdb;create=true' as 'null'
INFO: +++ Connecting: SUCCESS.
INFO: setting DataNode 'datanode' as default, used by all unlinked DataMaps
INFO: Detected and installed adapter: org.apache.cayenne.dba.derby.DerbyAdapter
INFO: --- transaction started.
INFO: No schema detected, will create mapped tables
INFO: CREATE TABLE GALLERY (ID INTEGER NOT NULL, NAME VARCHAR (200), PRIMARY KEY (ID))
INFO: CREATE TABLE ARTIST (DATE_OF_BIRTH DATE, ID INTEGER NOT NULL, NAME VARCHAR (200), PRIMARY KEY (ID))
INFO: CREATE TABLE PAINTING (ARTIST_ID INTEGER, GALLERY_ID INTEGER, ID INTEGER NOT NULL,
NAME VARCHAR (200), PRIMARY KEY (ID))
INFO: ALTER TABLE PAINTING ADD FOREIGN KEY (ARTIST_ID) REFERENCES ARTIST (ID)
INFO: ALTER TABLE PAINTING ADD FOREIGN KEY (GALLERY_ID) REFERENCES GALLERY (ID)
INFO: CREATE TABLE AUTO_PK_SUPPORT (
TABLE_NAME CHAR(100) NOT NULL, NEXT_ID BIGINT NOT NULL, PRIMARY KEY(TABLE_NAME))
...
INFO: SELECT t0.DATE_OF_BIRTH, t0.NAME, t0.ID FROM ARTIST t0 ORDER BY t0.NAME
INFO: === returned 0 rows. - took 17 ms.
INFO: +++ transaction committed.</screen>
- Now you can create new artist using `curl -X POST 'http://localhost:8080/cayenne/artist?name=Picasso&date=18811125'`

- You can click on "Create new artist" link to create artists. Existing artists can be edited by clicking on their name:
- Get all artists using `curl -X GET 'http://localhost:8080/cayenne/artists'`

image::chrome-webapp.png[align="center"]
- Modify artist by id using `curl -X PUT 'http://localhost:8080/cayenne/artist/200?name=NewName'`

You are done with the tutorial!
3 changes: 2 additions & 1 deletion tutorials/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
</modules>

<properties>
<jetty.version>9.4.8.v20171121</jetty.version>
<jetty.version>9.4.15.v20190215</jetty.version>
<jersey.version>2.28</jersey.version>
</properties>

<build>
Expand Down
22 changes: 16 additions & 6 deletions tutorials/tutorial/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,27 @@
<artifactId>cayenne-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.cayenne</groupId>
<artifactId>cayenne-web</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
Expand Down
Loading