You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2018/10/19 20:44:16 UTC

[juneau] branch master updated: PetStore enhancements.

This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 5e5494b  PetStore enhancements.
5e5494b is described below

commit 5e5494bc66757b602724c9a4530a716d37754739
Author: JamesBognar <ja...@apache.org>
AuthorDate: Fri Oct 19 16:44:04 2018 -0400

    PetStore enhancements.
---
 juneau-doc/docs.txt                                |   1 +
 juneau-doc/src/main/javadoc/overview.html          | 105 +++++++++
 .../src/main/resources/ReleaseNotes/7.2.1.html     |   4 +
 .../01.RestProxies/10.DualPurposeInterfaces.html   | 188 +++++++--------
 .../rest/petstore/AbstractPersistenceService.java  | 256 +++++++++++++++++++++
 .../examples/rest/petstore/AddPetMenuItem.java     |   2 +-
 .../examples/rest/petstore/PetStoreService.java    |  95 ++++----
 .../examples/rest/petstore/dto/CreateOrder.java    |   1 -
 8 files changed, 496 insertions(+), 156 deletions(-)

diff --git a/juneau-doc/docs.txt b/juneau-doc/docs.txt
index db309de..fc3b2d0 100644
--- a/juneau-doc/docs.txt
+++ b/juneau-doc/docs.txt
@@ -228,6 +228,7 @@ juneau-rest-client.PipingOutput = #juneau-rest-client.PipingOutput, Overview > j
 juneau-rest-client.ResponsePatterns = #juneau-rest-client.ResponsePatterns, Overview > juneau-rest-client > Using Response Patterns
 juneau-rest-client.RestProxies = #juneau-rest-client.RestProxies, Overview > juneau-rest-client > REST Proxies
 juneau-rest-client.RestProxies.Body = #juneau-rest-client.RestProxies.Body, Overview > juneau-rest-client > REST Proxies > @Body
+juneau-rest-client.RestProxies.DualPurposeInterfaces = #juneau-rest-client.RestProxies.DualPurposeInterfaces, Overview > juneau-rest-client > REST Proxies > Dual-purpose (end-to-end) interfaces
 juneau-rest-client.RestProxies.FormData = #juneau-rest-client.RestProxies.FormData, Overview > juneau-rest-client > REST Proxies > @FormData
 juneau-rest-client.RestProxies.Header = #juneau-rest-client.RestProxies.Header, Overview > juneau-rest-client > REST Proxies > @Header
 juneau-rest-client.RestProxies.Path = #juneau-rest-client.RestProxies.Path, Overview > juneau-rest-client > REST Proxies > @Path
diff --git a/juneau-doc/src/main/javadoc/overview.html b/juneau-doc/src/main/javadoc/overview.html
index ded7e4e..976bc6b 100644
--- a/juneau-doc/src/main/javadoc/overview.html
+++ b/juneau-doc/src/main/javadoc/overview.html
@@ -384,6 +384,7 @@
 			<li><p class=''><a class='doclink' href='#juneau-rest-client.RestProxies.Path'>@Path</a></p>
 			<li><p class=''><a class='doclink' href='#juneau-rest-client.RestProxies.Request'>@Request</a></p>
 			<li><p class=''><a class='doclink' href='#juneau-rest-client.RestProxies.Response'>@Response</a></p>
+			<li><p class='new'><a class='doclink' href='#juneau-rest-client.RestProxies.DualPurposeInterfaces'>Dual-purpose (end-to-end) interfaces</a></p>
 		</ol>
 		<li><p class=''><a class='doclink' href='#juneau-rest-client.SSL'>SSL Support</a></p>
 		<li><p class=''><a class='doclink' href='#juneau-rest-client.Authentication'>Authentication</a></p>
@@ -22452,6 +22453,106 @@
 	The behavior and functionality of all of the annotations are the same as if they were used on method arguments directly. This means full support for OpenAPI serialization and validation.
 </p>
 </div><!-- END: 9.1.9 - juneau-rest-client.RestProxies.Response -->
+
+<!-- ==================================================================================================== -->
+
+<h4 class='topic new' onclick='toggle(this)'><a href='#juneau-rest-client.RestProxies.DualPurposeInterfaces' id='juneau-rest-client.RestProxies.DualPurposeInterfaces'>9.1.10 - Dual-purpose (end-to-end) interfaces</a></h4>
+<div class='topic'><!-- START: 9.1.10 - juneau-rest-client.RestProxies.DualPurposeInterfaces -->
+<p>
+	A common coding practice is to use the same Java interface to define both your server and client side REST interfaces.
+	The advantage to this approach is that changes that you make to your REST interface can be reflected in both places
+	at the same time, reducing the chances for compatibility mistakes.
+</p>
+<p>
+	What makes this possible is that method-level annotations such as <ja>@RestMethod</ja> and parameter-level annotations
+	such as <ja>@Query</ja> are inherited from parent classes.  
+	This normally isn't possible, but the framework will spider up the parent hierarchy of classes to find method and parameter level
+	annotations defined on overridden methods.
+</p>
+<p>
+	The general approach is to define your {@link org.apache.juneau.rest.client.remote.RemoteResource @RemoteResource}-annotated interface first.
+	The following example is pulled from the PetStore app:
+</p>
+<p class='bpcode w800'>
+	<ja>@RemoteResource</ja>(path=<js>"/petstore"</js>)
+	<jk>public interface</jk> PetStore {
+	
+		<ja>@RemoteMethod</ja>(method=<jsf>GET</jsf>, path=<js>"/pet"</js>)
+		<jk>public</jk> Collection&lt;Pet&gt; getPets() <jk>throws</jk> NotAcceptable;
+	
+		<ja>@RemoteMethod</ja>(method=<jsf>DELETE</jsf>, path=<js>"/pet/{petId}"</js>)
+		<jk>public</jk> Ok deletePet(
+			<ja>@Header</ja>(
+				name=<js>"api_key"</js>,
+				description=<js>"Security API key"</js>,
+				required=<jk>true</jk>,
+				example=<js>"foobar"</js>
+			)
+			String apiKey,
+			<ja>@Path</ja>(
+				name=<js>"petId"</js>,
+				description=<js>"Pet id to delete"</js>,
+				example=<js>"123"</js>
+			)
+			<jk>long</jk> petId
+		) <jk>throws</jk> IdNotFound, NotAcceptable;
+		
+		...
+</p>
+<p>
+	Next you define the implementation of your interface as a normal Juneau REST resource:
+</p>
+<p class='bpcode w800'>
+	<ja>@RestResource</ja>(
+		path=<js>"/petstore"</js>,
+		title=<js>"Petstore application"</js>,
+		...
+	)
+	<jk>public class</jk> PetStoreResource <jk>extends</jk> BasicRestServletJena <jk>implements</jk> PetStore {
+	
+		...
+	
+		<ja>@Override</ja> <jc>/* PetStore */</jc>
+		<ja>@RestMethod</ja>(
+			name=<jsm>GET</jsm>,
+			path=<js>"/pet"</js>,
+			summary=<js>"All pets in the store"</js>,
+			...
+		)
+		<jk>public</jk> Collection&lt;Pet&gt; getPets() <jk>throws</jk> NotAcceptable {
+			<jk>return</jk> <jsf>store</jsf>.getPets();
+		}
+	
+		<ja>@Override</ja> <jc>/* PetStore */</jc>
+		<ja>@RestMethod</ja>(
+			name=<jsf>DELETE</jsf>,
+			path=<js>"/pet/{petId}"</js>,
+			summary=<js>"Deletes a pet"</js>,
+			...
+		)
+		<jk>public</jk> Ok deletePet(String apiKey, long petId) <jk>throws</jk> IdNotFound, NotAcceptable {
+			<jsf>store</jsf>.removePet(petId);
+			<jk>return</jk> <jsf>OK</jsf>;
+		}
+</p>
+<p>
+	Then use the interface as a remote resource like so:
+</p>
+<p class='bpcode w800'>
+	<jk>try</jk> (RestClient rc = RestClient.<jsm>create</jsm>().json().rootUrl(<js>"http://localhost:10000"</js>).build()) {
+		PetStore ps = rc.getRemoteResource(PetStore.<jk>class</jk>);
+
+		<jk>for</jk> (Pet x : ps.getPets()) {
+			ps.deletePet(<js>"my-special-key"</js>, x.getId());
+			System.<jsf>err</jsf>.println(<js>"Deleted pet:  id="</js> + x.getId());
+		}
+	}
+</p>	
+<p>
+	In the example above, we chose to add the <ja>@RestMethod</ja> annotation to the implementation class.  
+	However, they could have been added to the interface instead.
+</p>
+</div><!-- END: 9.1.10 - juneau-rest-client.RestProxies.DualPurposeInterfaces -->
 </div><!-- END: 9.1 - juneau-rest-client.RestProxies -->
 
 <!-- ==================================================================================================== -->
@@ -34309,6 +34410,10 @@
 <h5 class='topic w800'>juneau-rest-server</h5>
 <ul class='spaced-list'>
 	<li>
+		Method-level annotations (e.g. <ja>@RestMethod</ja>) and parameter-level annotations (e.g. <ja>@Query</ja>) are now inheritable
+		from parent classes and interfaces. 
+		<br>This allows you to define {@doc juneau-rest-client.RestProxies.DualPurposeInterfaces}.
+	<li>
 		The <code>ReaderResource</code> and <code>StreamResource</code> classes have been moved to the <code>org.apache.juneau.http</code>
 		package in <code>juneau-marshall</code>.  This allows them to be used as return types in remote REST interfaces.
 		<br>A new {@link org.apache.juneau.rest.helper.ResolvingReaderResource} class has been added that includes the variable-resolving support since
diff --git a/juneau-doc/src/main/resources/ReleaseNotes/7.2.1.html b/juneau-doc/src/main/resources/ReleaseNotes/7.2.1.html
index 6c2f248..d2916b2 100644
--- a/juneau-doc/src/main/resources/ReleaseNotes/7.2.1.html
+++ b/juneau-doc/src/main/resources/ReleaseNotes/7.2.1.html
@@ -32,6 +32,10 @@
 <h5 class='topic w800'>juneau-rest-server</h5>
 <ul class='spaced-list'>
 	<li>
+		Method-level annotations (e.g. <ja>@RestMethod</ja>) and parameter-level annotations (e.g. <ja>@Query</ja>) are now inheritable
+		from parent classes and interfaces. 
+		<br>This allows you to define {@doc juneau-rest-client.RestProxies.DualPurposeInterfaces}.
+	<li>
 		The <code>ReaderResource</code> and <code>StreamResource</code> classes have been moved to the <code>org.apache.juneau.http</code>
 		package in <code>juneau-marshall</code>.  This allows them to be used as return types in remote REST interfaces.
 		<br>A new {@link oajr.helper.ResolvingReaderResource} class has been added that includes the variable-resolving support since
diff --git a/juneau-doc/src/main/resources/Topics/09.juneau-rest-client/01.RestProxies/10.DualPurposeInterfaces.html b/juneau-doc/src/main/resources/Topics/09.juneau-rest-client/01.RestProxies/10.DualPurposeInterfaces.html
index ac0c021..7ee18c0 100644
--- a/juneau-doc/src/main/resources/Topics/09.juneau-rest-client/01.RestProxies/10.DualPurposeInterfaces.html
+++ b/juneau-doc/src/main/resources/Topics/09.juneau-rest-client/01.RestProxies/10.DualPurposeInterfaces.html
@@ -16,110 +16,100 @@
 {new} Dual-purpose (end-to-end) interfaces
 
 <p>
-	
+	A common coding practice is to use the same Java interface to define both your server and client side REST interfaces.
+	The advantage to this approach is that changes that you make to your REST interface can be reflected in both places
+	at the same time, reducing the chances for compatibility mistakes.
 </p>
-
-
-@RemoteResource(path="/petstore")
-public interface PetStore {
-
-	@RemoteMethod(method=GET, path="/pet")
-	public Collection<Pet> getPets() throws NotAcceptable;
-
-	@RemoteMethod(path="/pet/{petId}") /* method inferred from method name */
-	public Pet getPet(
-		@Path(
-			name="petId",
-			description="ID of pet to return",
-			example="123"
-		)
-		long petId
-	) throws IdNotFound, NotAcceptable;
-
-	
+<p>
+	What makes this possible is that method-level annotations such as <ja>@RestMethod</ja> and parameter-level annotations
+	such as <ja>@Query</ja> are inherited from parent classes.  
+	This normally isn't possible, but the framework will spider up the parent hierarchy of classes to find method and parameter level
+	annotations defined on overridden methods.
+</p>
+<p>
+	The general approach is to define your {@link oajr.client.remote.RemoteResource @RemoteResource}-annotated interface first.
+	The following example is pulled from the PetStore app:
+</p>
+<p class='bpcode w800'>
+	<ja>@RemoteResource</ja>(path=<js>"/petstore"</js>)
+	<jk>public interface</jk> PetStore {
 	
+		<ja>@RemoteMethod</ja>(method=<jsf>GET</jsf>, path=<js>"/pet"</js>)
+		<jk>public</jk> Collection&lt;Pet&gt; getPets() <jk>throws</jk> NotAcceptable;
 	
-public class PetStoreResource extends BasicRestServletJena implements PetStore {
-	private static final long serialVersionUID = 1L;
-
-	private PetStoreService store;
-
-
-	@Override /* PetStore */
-	@RestMethod(
-		name=GET,
-		path="/pet",
-		summary="All pets in the store",
-		swagger=@MethodSwagger(
-			tags="pet",
-			parameters={
-				Queryable.SWAGGER_PARAMS
-			}
-		),
-		bpx="Pet: tags",
-		htmldoc=@HtmlDoc(
-			widgets={
-				QueryMenuItem.class,
-				AddPetMenuItem.class
-			},
-			navlinks={
-				"INHERIT",                // Inherit links from class.
-				"[2]:$W{QueryMenuItem}",  // Insert QUERY link in position 2.
-				"[3]:$W{AddPetMenuItem}"  // Insert ADD link in position 3.
-			}
-		),
-		converters={Queryable.class}
-	)
-	public Collection<Pet> getPets() throws NotAcceptable {
-		return store.getPets();
-	}
-
-	@Override /* PetStore */
-	@RestMethod(
-		name=GET,
-		path="/pet/{petId}",
-		summary="Find pet by ID",
-		description="Returns a single pet",
-		swagger=@MethodSwagger(
-			tags="pet",
-			value={
-				"security:[ { api_key:[] } ]"
-			}
-		)
+		<ja>@RemoteMethod</ja>(method=<jsf>DELETE</jsf>, path=<js>"/pet/{petId}"</js>)
+		<jk>public</jk> Ok deletePet(
+			<ja>@Header</ja>(
+				name=<js>"api_key"</js>,
+				description=<js>"Security API key"</js>,
+				required=<jk>true</jk>,
+				example=<js>"foobar"</js>
+			)
+			String apiKey,
+			<ja>@Path</ja>(
+				name=<js>"petId"</js>,
+				description=<js>"Pet id to delete"</js>,
+				example=<js>"123"</js>
+			)
+			<jk>long</jk> petId
+		) <jk>throws</jk> IdNotFound, NotAcceptable;
+		
+		...
+</p>
+<p>
+	Next you define the implementation of your interface as a normal Juneau REST resource:
+</p>
+<p class='bpcode w800'>
+	<ja>@RestResource</ja>(
+		path=<js>"/petstore"</js>,
+		title=<js>"Petstore application"</js>,
+		...
 	)
-	public Pet getPet(long petId) throws IdNotFound, NotAcceptable {
-		return store.getPet(petId);
-	}
+	<jk>public class</jk> PetStoreResource <jk>extends</jk> BasicRestServletJena <jk>implements</jk> PetStore {
 	
+		...
 	
-		try (RestClient rc = RestClient.create().json().rootUrl("http://localhost:10000").build()) {
-			PetStore ps = rc.getRemoteResource(PetStore.class);
-
-			for (Pet x : ps.getPets()) {
-				ps.deletePet("apiKey", x.getId());
-				w.println(format("Deleted pet:  id={0}", x.getId()));
-			}
-			for (Order x : ps.getOrders()) {
-				ps.deleteOrder(x.getId());
-				w.println(format("Deleted order:  id={0}", x.getId()));
-			}
-			for (User x : ps.getUsers()) {
-				ps.deleteUser(x.getUsername());
-				w.println(format("Deleted user:  username={0}", x.getUsername()));
-			}
-			for (CreatePet x : parser.parse(getStream("init/Pets.json"), CreatePet[].class)) {
-				long id = ps.postPet(x);
-				w.println(format("Created pet:  id={0}, name={1}", id, x.getName()));
-			}
-			for (Order x : parser.parse(getStream("init/Orders.json"), Order[].class)) {
-				long id = ps.placeOrder(x.getPetId(), x.getUsername());
-				w.println(format("Created order:  id={0}", id));
-			}
-			for (User x: parser.parse(getStream("init/Users.json"), User[].class)) {
-				ps.postUser(x);
-				w.println(format("Created user:  username={0}", x.getUsername()));
-			}
+		<ja>@Override</ja> <jc>/* PetStore */</jc>
+		<ja>@RestMethod</ja>(
+			name=<jsm>GET</jsm>,
+			path=<js>"/pet"</js>,
+			summary=<js>"All pets in the store"</js>,
+			...
+		)
+		<jk>public</jk> Collection&lt;Pet&gt; getPets() <jk>throws</jk> NotAcceptable {
+			<jk>return</jk> <jsf>store</jsf>.getPets();
 		}
 	
-	
-	
\ No newline at end of file
+		<ja>@Override</ja> <jc>/* PetStore */</jc>
+		<ja>@RestMethod</ja>(
+			name=<jsf>DELETE</jsf>,
+			path=<js>"/pet/{petId}"</js>,
+			summary=<js>"Deletes a pet"</js>,
+			...
+		)
+		<jk>public</jk> Ok deletePet(String apiKey, long petId) <jk>throws</jk> IdNotFound, NotAcceptable {
+			<jsf>store</jsf>.removePet(petId);
+			<jk>return</jk> <jsf>OK</jsf>;
+		}
+</p>
+<p>
+	Then use the interface as a remote resource like so:
+</p>
+<p class='bpcode w800'>
+	<jk>try</jk> (RestClient rc = RestClient.<jsm>create</jsm>().json().rootUrl(<js>"http://localhost:10000"</js>).build()) {
+		PetStore ps = rc.getRemoteResource(PetStore.<jk>class</jk>);
+
+		<jk>for</jk> (Pet x : ps.getPets()) {
+			ps.deletePet(<js>"my-special-key"</js>, x.getId());
+			System.<jsf>err</jsf>.println(<js>"Deleted pet:  id="</js> + x.getId());
+		}
+	}
+</p>	
+<p>
+	In the example above, we chose to add the <ja>@RestMethod</ja> annotation to the implementation class.  
+	However, they could have been added to the interface instead.
+</p>
+<p>
+	Note how we didn't need to use the <ja>@Header</ja> and <ja>@Path</ja> annotations in our implementation since
+	the annotations were inherited from the interface.
+</p>
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AbstractPersistenceService.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AbstractPersistenceService.java
new file mode 100644
index 0000000..92b178c
--- /dev/null
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AbstractPersistenceService.java
@@ -0,0 +1,256 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.examples.rest.petstore;
+
+import java.util.*;
+
+import javax.persistence.*;
+
+import org.apache.juneau.utils.*;
+
+/**
+ * Superclass for DAOs that use the JPA entity manager.
+ */
+public class AbstractPersistenceService {
+
+	private final EntityManagerFactory entityManagerFactory;
+
+	public AbstractPersistenceService() {
+		entityManagerFactory = Persistence.createEntityManagerFactory("test");
+	}
+
+	/**
+	 * Retrieves an entity manager session.
+	 */
+	protected EntityManager getEntityManager() {
+		return entityManagerFactory.createEntityManager();
+	}
+
+	/**
+	 * Retrieves the specified JPA bean from the repository.
+	 *
+	 * @param em The entity manager to use to retrieve the bean.
+	 * @param t The bean type to retrieve.
+	 * @param id The primary key value.
+	 * @return The JPA bean, or null if not found.
+	 */
+	protected <T> T find(EntityManager em, Class<T> t, Object id) {
+		return em.find(t, id);
+	}
+
+	/**
+	 * Same as {@link #find(EntityManager, Class, Object)} but uses a new entity manager.
+	 *
+	 * @param t The bean type to retrieve.
+	 * @param id The primary key value.
+	 * @return The JPA bean, or null if not found.
+	 */
+	protected <T> T find(Class<T> t, Object id) {
+		return find(getEntityManager(), t, id);
+	}
+
+	/**
+	 * Store the specified JPA bean in the repository.
+	 *
+	 * @param em The entity manager to use to store and merge the bean.
+	 * @param t The bean to store.
+	 * @return The merged JPA bean returned by the {@link EntityManager#merge(Object)} method, or null if the bean was null.
+	 */
+	protected <T> T merge(EntityManager em, T t) {
+		if (t == null)
+			return null;
+		try {
+			EntityTransaction et = em.getTransaction();
+			et.begin();
+			t = em.merge(t);
+			et.commit();
+			return t;
+		} finally {
+			em.close();
+		}
+	}
+
+	/**
+	 * Same as {@link #merge(EntityManager, Object)} but uses a new entity manager.
+	 *
+	 * @param t The bean to store.
+	 * @return The merged JPA bean returned by the {@link EntityManager#merge(Object)} method, or null if the bean was null.
+	 */
+	protected <T> T merge(T t) {
+		return merge(getEntityManager(), t);
+	}
+
+	/**
+	 * Store the specified JPA beans in the repository.
+	 *
+	 * All values are persisted in the same transaction.
+	 *
+	 * @param em The entity manager to use to store and merge the beans.
+	 * @param c The collection of beans to store.
+	 * @return The merged JPA beans returned by the {@link EntityManager#merge(Object)} method.
+	 */
+	protected <T> Collection<T> merge(EntityManager em, Collection<T> c) {
+		Collection<T> c2 = new ArrayList<>();
+		try {
+			EntityTransaction et = em.getTransaction();
+			et.begin();
+			for (T t : c)
+				c2.add(em.merge(t));
+			et.commit();
+			return c2;
+		} finally {
+			em.close();
+		}
+	}
+
+	/**
+	 * Same as {@link #merge(EntityManager, Collection)} but uses a new entity manager.
+	 *
+	 * @param c The collection of beans to store.
+	 * @return The merged JPA beans returned by the {@link EntityManager#merge(Object)} method.
+	 */
+	protected <T> Collection<T> merge(Collection<T> c) {
+		return merge(getEntityManager(), c);
+	}
+
+	/**
+	 * Remove the specified JPA bean from the repository.
+	 *
+	 * @param t The bean type to remove.
+	 * @param id The primary key value.
+	 */
+	protected <T> void remove(Class<T> t, Object id) {
+		EntityManager em = getEntityManager();
+		remove(em, find(em, t, id));
+	}
+
+	/**
+	 * Remove the specified JPA bean from the repository.
+	 *
+	 * @param em The entity manager used to retrieve the bean.
+	 * @param t The bean to remove.  Can be null.
+	 */
+	protected <T> void remove(EntityManager em, T t) {
+		if (t == null)
+			return;
+		try {
+			EntityTransaction et = em.getTransaction();
+			et.begin();
+			em.remove(t);
+			et.commit();
+		} finally {
+			em.close();
+		}
+	}
+
+	/**
+	 * Runs a JPA query and returns the results.
+	 *
+	 * @param em The entity manager to use to retrieve the beans.
+	 * @param query The JPA query.
+	 * @param t The bean type.
+	 */
+	protected <T> List<T> query(EntityManager em, String query, Class<T> t, SearchArgs searchArgs) {
+		TypedQuery<T> q = em.createQuery(query, t);
+		if (searchArgs != null) {
+			q.setMaxResults(searchArgs.getLimit() == 0 ? 100 : searchArgs.getLimit());
+			q.setFirstResult(searchArgs.getPosition());
+		}
+		return em.createQuery(query, t).getResultList();
+	}
+
+	/**
+	 * Same as {@link #query(EntityManager,String,Class,SearchArgs)} but uses a new entity manager.
+	 *
+	 * @param query The JPA query.
+	 * @param t The bean type.
+	 */
+	protected <T> List<T> query(String query, Class<T> t, SearchArgs searchArgs) {
+		return query(getEntityManager(), query, t, searchArgs);
+	}
+
+	/**
+	 * Runs a JPA parameterized query and returns the results.
+	 *
+	 * @param em The entity manager to use to retrieve the beans.
+	 * @param query The JPA query.
+	 * @param t The bean type.
+	 * @param params The query parameter values.
+	 */
+	protected <T> List<T> query(EntityManager em, String query, Class<T> t, Map<String,Object> params) {
+		TypedQuery<T> tq = em.createQuery(query, t);
+		for (Map.Entry<String,Object> e : params.entrySet()) {
+			tq.setParameter(e.getKey(), e.getValue());
+		}
+		return tq.getResultList();
+	}
+
+	/**
+	 * Same as {@link #query(EntityManager,String,Class,Map)} but uses a new entity manager.
+	 *
+	 * @param query The JPA query.
+	 * @param t The bean type.
+	 * @param params The query parameter values.
+	 */
+	protected <T> List<T> query(String query, Class<T> t, Map<String,Object> params) {
+		return query(getEntityManager(), query, t, params);
+	}
+
+	/**
+	 * Runs a JPA update statement.
+	 *
+	 * @param em The entity manager to use to run the statement.
+	 * @param query The JPA update statement.
+	 * @return The number of rows modified.
+	 */
+	protected int update(EntityManager em, String query) {
+		return em.createQuery(query).executeUpdate();
+	}
+
+	/**
+	 * Same as {@link #update(EntityManager,String)} but uses a new entity manager.
+	 *
+	 * @param query The JPA update statement.
+	 * @return The number of rows modified.
+	 */
+	protected int update(String query) {
+		return update(getEntityManager(), query);
+	}
+
+	/**
+	 * Runs a JPA parameterized update statement.
+	 *
+	 * @param em The entity manager to use to run the statement.
+	 * @param query The JPA update statement.
+	 * @param params The query parameter values.
+	 * @return The number of rows modified.
+	 */
+	protected int update(EntityManager em, String query, Map<String,Object> params) {
+		Query q = em.createQuery(query);
+		for (Map.Entry<String,Object> e : params.entrySet()) {
+			q.setParameter(e.getKey(), e.getValue());
+		}
+		return q.executeUpdate();
+	}
+
+	/**
+	 * Same as {@link #update(EntityManager,String,Map)} but uses a new entity manager.
+	 *
+	 * @param query The JPA update statement.
+	 * @param params The query parameter values.
+	 * @return The number of rows modified.
+	 */
+	protected int update(String query, Map<String,Object> params) {
+		return update(getEntityManager(), query, params);
+	}
+}
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AddPetMenuItem.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AddPetMenuItem.java
index 10aba70..3e5eb88 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AddPetMenuItem.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AddPetMenuItem.java
@@ -42,7 +42,7 @@ public class AddPetMenuItem extends MenuItemWidget {
 						th("Species:"),
 						td(
 							select().name("species").children(
-								option("cat"), option("dog"), option("bird"), option("fish"), option("mouse"), option("rabbit"), option("snake")
+								option("CAT"), option("DOG"), option("BIRD"), option("FISH"), option("MOUSE"), option("RABBIT"), option("SNAKE")
 							)
 						),
 						td(new Tooltip("&#x2753;", "The kind of animal."))
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetStoreService.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetStoreService.java
index 817271d..063e1bd 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetStoreService.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetStoreService.java
@@ -22,18 +22,27 @@ import javax.persistence.*;
 import org.apache.juneau.examples.rest.petstore.dto.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.rest.client.*;
+import org.apache.juneau.utils.*;
 
 /**
  * Pet store database application.
+ * <p>
+ * Uses JPA persistence to store and retrieve PetStore DTOs.
+ * JPA beans are defined in <code>META-INF/persistence.xml</code>.
  */
-public class PetStoreService {
+public class PetStoreService extends AbstractPersistenceService {
 
-	private final EntityManagerFactory entityManagerFactory;
-
-	public PetStoreService() {
-		entityManagerFactory = Persistence.createEntityManagerFactory("test");
-	}
+	//-----------------------------------------------------------------------------------------------------------------
+	// Initialization methods.
+	//-----------------------------------------------------------------------------------------------------------------
 
+	/**
+	 * Initialize the petstore database using JPA.
+	 *
+	 * @param w Console output.
+	 * @return This object (for method chaining).
+	 * @throws Exception
+	 */
 	public PetStoreService initDirect(PrintWriter w) throws Exception {
 
 		EntityManager em = getEntityManager();
@@ -76,6 +85,13 @@ public class PetStoreService {
 		return this;
 	}
 
+	/**
+	 * Initialize the petstore database by using a remote resource interface against our REST.
+	 *
+	 * @param w Console output.
+	 * @return This object (for method chaining).
+	 * @throws Exception
+	 */
 	public PetStoreService initViaRest(PrintWriter w) throws Exception {
 		JsonParser parser = JsonParser.create().ignoreUnknownBeanProperties().build();
 
@@ -111,6 +127,9 @@ public class PetStoreService {
 		return this;
 	}
 
+	//-----------------------------------------------------------------------------------------------------------------
+	// Service methods.
+	//-----------------------------------------------------------------------------------------------------------------
 
 	public Pet getPet(long id) throws IdNotFound {
 		return find(Pet.class, id);
@@ -126,15 +145,15 @@ public class PetStoreService {
 	}
 
 	public List<Pet> getPets() {
-		return query("select X from Pet X", Pet.class);
+		return query("select X from Pet X", Pet.class, (SearchArgs)null);
 	}
 
 	public List<Order> getOrders() {
-		return query("select X from PetstoreOrder X", Order.class);
+		return query("select X from PetstoreOrder X", Order.class, (SearchArgs)null);
 	}
 
 	public List<User> getUsers() {
-		return query("select X from PetstoreUser X", User.class);
+		return query("select X from PetstoreUser X", User.class, (SearchArgs)null);
 	}
 
 	public Pet create(CreatePet c) {
@@ -150,28 +169,34 @@ public class PetStoreService {
 	}
 
 	public Pet update(UpdatePet u) throws IdNotFound {
-		return merge(getPet(u.getId()).apply(u));
+		EntityManager em = getEntityManager();
+		return merge(em, find(em, Pet.class, u.getId()).apply(u));
 	}
 
 	public Order update(Order o) throws IdNotFound {
-		return merge(getOrder(o.getId()).apply(o));
+		EntityManager em = getEntityManager();
+		return merge(em, find(em, Order.class, o.getId()).apply(o));
 	}
 
 	public User update(User u) throws IdNotFound, InvalidUsername {
 		assertValidUsername(u.getUsername());
-		return merge(getUser(u.getUsername()).apply(u));
+		EntityManager em = getEntityManager();
+		return merge(em, find(em, User.class, u.getUsername()).apply(u));
 	}
 
 	public void removePet(long id) throws IdNotFound {
-		remove(getPet(id));
+		EntityManager em = getEntityManager();
+		remove(em, find(em, Pet.class, id));
 	}
 
 	public void removeOrder(long id) throws IdNotFound {
-		remove(getOrder(id));
+		EntityManager em = getEntityManager();
+		remove(em, find(em, Order.class, id));
 	}
 
 	public void removeUser(String username) throws IdNotFound {
-		remove(getUser(username));
+		EntityManager em = getEntityManager();
+		remove(em, find(em, User.class, username));
 	}
 
 	public Collection<Pet> getPetsByStatus(PetStatus[] status) {
@@ -213,46 +238,6 @@ public class PetStoreService {
 			throw new InvalidUsername();
 	}
 
-	private EntityManager getEntityManager() {
-		return entityManagerFactory.createEntityManager();
-	}
-
-	private <T> T merge(T t) {
-		EntityManager em = getEntityManager();
-		try {
-			EntityTransaction et = em.getTransaction();
-			et.begin();
-			t = em.merge(t);
-			et.commit();
-			return t;
-		} finally {
-			em.close();
-		}
-	}
-
-	private <T> void remove(T t) {
-		EntityManager em = getEntityManager();
-		try {
-			EntityTransaction et = em.getTransaction();
-			et.begin();
-			em.remove(t);
-			et.commit();
-		} finally {
-			em.close();
-		}
-	}
-
-	private <T> List<T> query(String query, Class<T> t) {
-		return getEntityManager().createQuery(query, t).getResultList();
-	}
-
-	private <T> T find(Class<T> t, Object id) throws IdNotFound {
-		T o = getEntityManager().find(t, id);
-		if (o == null)
-			throw new IdNotFound(id, t);
-		return o;
-	}
-
 	private InputStream getStream(String fileName) {
 		return getClass().getResourceAsStream(fileName);
 	}
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/dto/CreateOrder.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/dto/CreateOrder.java
index 2b42888..d26a270 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/dto/CreateOrder.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/dto/CreateOrder.java
@@ -16,7 +16,6 @@ import org.apache.juneau.annotation.*;
 
 /**
  * Bean for creating {@link Order} objects.
- * Also serves as the superclass for the {@link Order} object.
  */
 @Bean(fluentSetters=true, properties="petId,username")
 public class CreateOrder {