blob: 344bf221af485b29b072bbb1d52ba33bb4ccf800 [file] [log] [blame]
Title: Tutorial Webapp
<P>This sections shows how to work with Cayenne in a web application.</P>
<H3><A name="TutorialWebapp-ConvertingTutorialtoaWebApplication"></A>Converting Tutorial to a Web Application</H3>
<P>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:</P>
<UL>
<LI>Cayenne configuiration is loaded when an application context is started, using a special servlet filter.</LI>
<LI>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.</LI>
<LI>The same DataContext instance is reused within a single user session; different sessions use different DataContexts (and therefore different sets of objects). <EM>However see &quot;ObjectContext Scope&quot; section of the <A href="web-applications.html" title="Web Applications">Web Applications</A> page. The context can be scoped differently depending on the app specifics. For the tutorial we'll be using a session-scoped context.</EM></LI>
</UL>
<P>For more information on the web application deployment, check <A href="web-applications.html" title="Web Applications">Web Applications</A> page.</P>
<P>So let's convert the tutorial that we created to a web application:</P>
<UL>
<LI>In Eclipse under &quot;tutorial&quot; project folder create a new folder &quot;src/main/webapp/WEB-INF&quot;.</LI>
<LI>Under &quot;WEB-INF&quot; create a new file &quot;web.xml&quot; (a standard web app descriptor):</LI>
</UL>
<DIV class="code panel" style="border-width: 1px;"><DIV class="codeHeader panelHeader" style="border-bottom-width: 1px;"><B>web.xml</B></DIV><DIV class="codeContent panelContent">
<PRE class="code-java">&lt;?xml version=<SPAN class="code-quote">&quot;1.0&quot;</SPAN> encoding=<SPAN class="code-quote">&quot;utf-8&quot;</SPAN>?&gt;
&lt;!DOCTYPE web-app
PUBLIC <SPAN class="code-quote">&quot;-<SPAN class="code-comment">//Sun Microsystems, Inc.//DTD Web Application 2.3//EN&quot;</SPAN>
</SPAN> <SPAN class="code-quote">&quot;http:<SPAN class="code-comment">//java.sun.com/dtd/web-app_2_3.dtd&quot;</SPAN>&gt;
</SPAN>&lt;web-app&gt;
&lt;display-name&gt;Cayenne Tutorial&lt;/display-name&gt;
&lt;!-- This filter provides each request thread with a session-bound DataContext --&gt;
&lt;filter&gt;
&lt;filter-name&gt;CayenneFilter&lt;/filter-name&gt;
&lt;filter-class&gt;org.apache.cayenne.conf.WebApplicationContextFilter&lt;/filter-class&gt;
&lt;/filter&gt;
&lt;filter-mapping&gt;
&lt;filter-name&gt;CayenneFilter&lt;/filter-name&gt;
&lt;url-pattern&gt;/*&lt;/url-pattern&gt;
&lt;/filter-mapping&gt;
&lt;welcome-file-list&gt;
&lt;welcome-file&gt;index.jsp&lt;/welcome-file&gt;
&lt;/welcome-file-list&gt;
&lt;/web-app&gt;</PRE>
</DIV></DIV>
<UL>
<LI>Create the artist browser page <TT>src/main/webapp/index.jsp</TT> file with the following contents:</LI>
</UL>
<DIV class="code panel" style="border-width: 1px;"><DIV class="codeHeader panelHeader" style="border-bottom-width: 1px;"><B>webapp/index.jsp</B></DIV><DIV class="codeContent panelContent">
<PRE class="code-java">&lt;%@ page language=<SPAN class="code-quote">&quot;java&quot;</SPAN> contentType=<SPAN class="code-quote">&quot;text/html&quot;</SPAN> %&gt;
&lt;%@ page <SPAN class="code-keyword">import</SPAN>=<SPAN class="code-quote">&quot;org.example.cayenne.persistent.*&quot;</SPAN> %&gt;
&lt;%@ page <SPAN class="code-keyword">import</SPAN>=<SPAN class="code-quote">&quot;org.apache.cayenne.*&quot;</SPAN> %&gt;
&lt;%@ page <SPAN class="code-keyword">import</SPAN>=<SPAN class="code-quote">&quot;org.apache.cayenne.query.*&quot;</SPAN> %&gt;
&lt;%@ page <SPAN class="code-keyword">import</SPAN>=<SPAN class="code-quote">&quot;org.apache.cayenne.exp.*&quot;</SPAN> %&gt;
&lt;%@ page <SPAN class="code-keyword">import</SPAN>=<SPAN class="code-quote">&quot;java.util.*&quot;</SPAN> %&gt;
&lt;%
SelectQuery query = <SPAN class="code-keyword">new</SPAN> SelectQuery(Artist.class);
query.addOrdering(Artist.NAME_PROPERTY, SortOrder.ASCENDING);
ObjectContext context = BaseContext.getThreadObjectContext();
List&lt;Artist&gt; artists = context.performQuery(query);
%&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Main&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h2&gt;Artists:&lt;/h2&gt;
&lt;% <SPAN class="code-keyword">if</SPAN>(artists.isEmpty()) {%&gt;
&lt;p&gt;No artists found&lt;/p&gt;
&lt;% } <SPAN class="code-keyword">else</SPAN> {
<SPAN class="code-keyword">for</SPAN>(Artist a : artists) {
%&gt;
&lt;p&gt;&lt;a href=<SPAN class="code-quote">&quot;detail.jsp?id=&lt;%=DataObjectUtils.intPKForObject(a)%&gt;&quot;</SPAN>&gt; &lt;%=a.getName()%&gt; &lt;/a&gt;&lt;/p&gt;
&lt;%
}
} %&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=<SPAN class="code-quote">&quot;detail.jsp&quot;</SPAN>&gt;Create <SPAN class="code-keyword">new</SPAN> artist...&lt;/a&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</PRE>
</DIV></DIV>
<UL>
<LI>Create the artist editor page <TT>src/main/webapp/detail.jsp</TT> with the following content:</LI>
</UL>
<DIV class="code panel" style="border-width: 1px;"><DIV class="codeHeader panelHeader" style="border-bottom-width: 1px;"><B>webapp/detail.jsp</B></DIV><DIV class="codeContent panelContent">
<PRE class="code-java">&lt;%@ page language=<SPAN class="code-quote">&quot;java&quot;</SPAN> contentType=<SPAN class="code-quote">&quot;text/html&quot;</SPAN> %&gt;
&lt;%@ page <SPAN class="code-keyword">import</SPAN>=<SPAN class="code-quote">&quot;org.example.cayenne.persistent.*&quot;</SPAN> %&gt;
&lt;%@ page <SPAN class="code-keyword">import</SPAN>=<SPAN class="code-quote">&quot;org.apache.cayenne.*&quot;</SPAN> %&gt;
&lt;%@ page <SPAN class="code-keyword">import</SPAN>=<SPAN class="code-quote">&quot;java.util.*&quot;</SPAN> %&gt;
&lt;%@ page <SPAN class="code-keyword">import</SPAN>=<SPAN class="code-quote">&quot;java.text.*&quot;</SPAN> %&gt;
&lt;%
ObjectContext context = BaseContext.getThreadObjectContext();
<SPAN class="code-object">String</SPAN> id = request.getParameter(<SPAN class="code-quote">&quot;id&quot;</SPAN>);
<SPAN class="code-comment">// find artist <SPAN class="code-keyword">for</SPAN> id
</SPAN> Artist artist = <SPAN class="code-keyword">null</SPAN>;
<SPAN class="code-keyword">if</SPAN>(id != <SPAN class="code-keyword">null</SPAN> &amp;&amp; id.trim().length() &gt; 0) {
artist = DataObjectUtils.objectForPK(context, Artist.class, <SPAN class="code-object">Integer</SPAN>.parseInt(id));
}
<SPAN class="code-keyword">if</SPAN>(<SPAN class="code-quote">&quot;POST&quot;</SPAN>.equals(request.getMethod())) {
<SPAN class="code-comment">// <SPAN class="code-keyword">if</SPAN> no id is saved in the hidden field, we are dealing with
</SPAN> <SPAN class="code-comment">// create <SPAN class="code-keyword">new</SPAN> artist request
</SPAN> <SPAN class="code-keyword">if</SPAN>(artist == <SPAN class="code-keyword">null</SPAN>) {
artist = context.newObject(Artist.class);
}
<SPAN class="code-comment">// note that in a real application we would so dome validation ...
</SPAN> <SPAN class="code-comment">// here we just hope the input is correct
</SPAN> artist.setName(request.getParameter(<SPAN class="code-quote">&quot;name&quot;</SPAN>));
artist.setDateOfBirthString(request.getParameter(<SPAN class="code-quote">&quot;dateOfBirth&quot;</SPAN>));
context.commitChanges();
response.sendRedirect(<SPAN class="code-quote">&quot;index.jsp&quot;</SPAN>);
}
<SPAN class="code-keyword">if</SPAN>(artist == <SPAN class="code-keyword">null</SPAN>) {
<SPAN class="code-comment">// create <SPAN class="code-keyword">transient</SPAN> artist <SPAN class="code-keyword">for</SPAN> the form response rendering
</SPAN> artist = <SPAN class="code-keyword">new</SPAN> Artist();
}
<SPAN class="code-object">String</SPAN> name = artist.getName() == <SPAN class="code-keyword">null</SPAN> ? &quot;&quot; : artist.getName();
<SPAN class="code-object">String</SPAN> dob = artist.getDateOfBirth() == <SPAN class="code-keyword">null</SPAN>
? <SPAN class="code-quote">&quot;&quot; : <SPAN class="code-keyword">new</SPAN> SimpleDateFormat(&quot;</SPAN>yyyyMMdd&quot;).format(artist.getDateOfBirth());
%&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Artist Details&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h2&gt;Artists Details&lt;/h2&gt;
&lt;form name=<SPAN class="code-quote">&quot;EditArtist&quot;</SPAN> action=<SPAN class="code-quote">&quot;detail.jsp&quot;</SPAN> method=<SPAN class="code-quote">&quot;POST&quot;</SPAN>&gt;
&lt;input type=<SPAN class="code-quote">&quot;hidden&quot;</SPAN> name=<SPAN class="code-quote">&quot;id&quot;</SPAN> value=<SPAN class="code-quote">&quot;&lt;%= id != <SPAN class="code-keyword">null</SPAN> ? id : &quot;</SPAN><SPAN class="code-quote">&quot; %&gt;&quot;</SPAN> /&gt;
&lt;table border=<SPAN class="code-quote">&quot;0&quot;</SPAN>&gt;
&lt;tr&gt;
&lt;td&gt;Name:&lt;/td&gt;
&lt;td&gt;&lt;input type=<SPAN class="code-quote">&quot;text&quot;</SPAN> name=<SPAN class="code-quote">&quot;name&quot;</SPAN> value=<SPAN class="code-quote">&quot;&lt;%= name %&gt;&quot;</SPAN>/&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Date of Birth (yyyyMMdd):&lt;/td&gt;
&lt;td&gt;&lt;input type=<SPAN class="code-quote">&quot;text&quot;</SPAN> name=<SPAN class="code-quote">&quot;dateOfBirth&quot;</SPAN> value=<SPAN class="code-quote">&quot;&lt;%= dob %&gt;&quot;</SPAN>/&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td align=<SPAN class="code-quote">&quot;right&quot;</SPAN>&gt;&lt;input type=<SPAN class="code-quote">&quot;submit&quot;</SPAN> value=<SPAN class="code-quote">&quot;Save&quot;</SPAN> /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;</PRE>
</DIV></DIV>
<H3><A name="TutorialWebapp-RunningWebApplication"></A>Running Web Application</H3>
<P>To run the web application we'll use &quot;maven-jetty-plugin&quot;. To activate it, let's add the following piece of code to the &quot;pom.xml&quot; file, following the &quot;dependencies&quot; section and save the POM:</P>
<DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent">
<PRE class="code-java">&lt;build&gt;
&lt;plugins&gt;
&lt;plugin&gt;
&lt;groupId&gt;org.mortbay.jetty&lt;/groupId&gt;
&lt;artifactId&gt;maven-jetty-plugin&lt;/artifactId&gt;
&lt;version&gt;6.1.22&lt;/version&gt;
&lt;/plugin&gt;
&lt;/plugins&gt;
&lt;/build&gt;</PRE>
</DIV></DIV>
<UL>
<LI>Go to &quot;Run &gt; Run Configurations...&quot; menu, select &quot;Maven Build&quot;, right click and select &quot;New&quot;</LI>
<LI>Make sure you fill &quot;Name&quot;, &quot;Base directory&quot; and &quot;Goals&quot; fields as shown on the screenshot:</LI>
</UL>
<P><SPAN class="image-wrap" style=""><IMG src="tutorial-webapp.data/eclipse-mvnrun.png" style="border: 0px solid black"></SPAN></P>
<UL>
<LI>Click &quot;Apply&quot; and &quot;Run&quot;. On the first execution it may take a few minutes for Jetty plugin to download all dependencies, but eventually you'll see the logs like this:</LI>
</UL>
<DIV class="preformatted panel" style="border-width: 1px;"><DIV class="preformattedContent panelContent">
<PRE>[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Unnamed - org.example.cayenne:tutorial:jar:0.0.1-SNAPSHOT
[INFO]
[INFO] Id: org.example.cayenne:tutorial:jar:0.0.1-SNAPSHOT
[INFO] task-segment: [jetty:run]
[INFO] ------------------------------------------------------------------------
...
[INFO] [jetty:run]
[INFO] Configuring Jetty for project: Unnamed - org.example.cayenne:tutorial:jar:0.0.1-SNAPSHOT
[INFO] Webapp source directory = /.../tutorial/Desktop/work/tutorial/src/main/webapp
...
[INFO] Starting jetty 6.1.22 ...
2009-12-22 14:08:06.301::INFO: jetty-6.1.22
2009-12-22 14:08:06.474::INFO: No Transaction manager found - if your webapp requires one, please configure one.
INFO: started configuration loading.
INFO: loaded domain: UntitledDomain
INFO: loaded &lt;map name='UntitledDomainMap' location='UntitledDomainMap.map.xml'&gt;.
INFO: loading &lt;node name='UntitledDomainNode' datasource='UntitledDomainNode.driver.xml'
factory='org.apache.cayenne.conf.DriverDataSourceFactory' schema-update-
strategy='org.apache.cayenne.access.dbsync.CreateIfNoSchemaStrategy'&gt;.
INFO: using factory: org.apache.cayenne.conf.DriverDataSourceFactory
INFO: loading driver information from 'UntitledDomainNode.driver.xml'.
INFO: loading driver org.apache.derby.jdbc.EmbeddedDriver
INFO: loading user name and password.
INFO: Created connection pool: jdbc:derby:memory:testdb;create=true
Driver class: org.apache.derby.jdbc.EmbeddedDriver
Min. connections in the pool: 1
Max. connections in the pool: 1
INFO: loaded datasource.
INFO: no adapter set, using automatic adapter.
INFO: loaded map-ref: UntitledDomainMap.
INFO: finished configuration loading in 355 ms.
2009-12-22 14:08:07.081::INFO: Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server</PRE>
</DIV></DIV>
<UL>
<LI>So the Jetty container just started and loaded Cayenne.</LI>
<LI>Now go to <A href="http://localhost:8080/tutorial/" class="external-link" rel="nofollow">http://localhost:8080/tutorial/</A> URL. You should see &quot;No artists found message&quot; in the web browser and the following output in the Eclipse console:</LI>
</UL>
<DIV class="preformatted panel" style="border-width: 1px;"><DIV class="preformattedContent panelContent">
<PRE>INFO: --- will run 1 query.
INFO: Opening connection: jdbc:derby:memory:testdb;create=true
Login: null
Password: *******
INFO: +++ Connecting: SUCCESS.
INFO: --- transaction started.
INFO: Detected and installed adapter: org.apache.cayenne.dba.derby.DerbyAdapter
INFO: No schema detected, will create mapped tables
INFO: CREATE TABLE GALLERY (ID INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY,
NAME VARCHAR (200), PRIMARY KEY (ID))
INFO: CREATE TABLE ARTIST (DATE_OF_BIRTH DATE, ID INTEGER NOT NULL GENERATED
BY DEFAULT AS IDENTITY, NAME VARCHAR (200), PRIMARY KEY (ID))
INFO: CREATE TABLE PAINTING (ARTIST_ID INTEGER, GALLERY_ID INTEGER,
ID INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY, 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: DELETE FROM AUTO_PK_SUPPORT WHERE TABLE_NAME IN ('ARTIST', 'GALLERY', 'PAINTING')
INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('ARTIST', 200)
INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('GALLERY', 200)
INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('PAINTING', 200)
INFO: SELECT t0.DATE_OF_BIRTH, t0.ID, t0.NAME FROM ARTIST t0 ORDER BY t0.NAME - prepared in 35 ms.
INFO: === returned 0 rows. - took 53 ms.
INFO: +++ transaction committed.</PRE>
</DIV></DIV>
<UL>
<LI>You can click on &quot;Create new artist&quot; link to create artists. Existing artists can be edited by clicking on their name:</LI>
</UL>
<P><SPAN class="image-wrap" style=""><IMG src="tutorial-webapp.data/firefox-webapp.png" style="border: 0px solid black"></SPAN></P>
<P>You are done with the tutorial!</P>