JUNEAU-140 Provide initial contents of PetStore modules.
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/pojotools/PageArgs.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/pojotools/PageArgs.java
new file mode 100644
index 0000000..2487098
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/pojotools/PageArgs.java
@@ -0,0 +1,37 @@
+// ***************************************************************************************************************************
+// * 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.pojotools;
+
+/**
+ * TODO
+ */
+public class PageArgs {
+
+	/**
+	 * TODO
+	 *
+	 * @return TODO
+	 */
+	public int getLimit() {
+		return 0;
+	}
+
+	/**
+	 * TODO
+	 *
+	 * @return TODO
+	 */
+	public int getPosition() {
+		return 0;
+	}
+}
diff --git a/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-api/src/main/java/org/apache/juneau/petstore/PetStore.java b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-api/src/main/java/org/apache/juneau/petstore/PetStore.java
new file mode 100644
index 0000000..ef2add7
--- /dev/null
+++ b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-api/src/main/java/org/apache/juneau/petstore/PetStore.java
@@ -0,0 +1,421 @@
+// ***************************************************************************************************************************
+// * 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.petstore;
+
+import static org.apache.juneau.http.HttpMethodName.*;
+
+import java.util.*;
+
+import org.apache.juneau.jsonschema.annotation.Items;
+import org.apache.juneau.petstore.dto.*;
+import org.apache.juneau.*;
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.http.remote.*;
+import org.apache.juneau.http.exception.*;
+import org.apache.juneau.http.response.*;
+
+/**
+ * Defines the interface for both the server-side and client-side pet store application.
+ *
+ * <p>
+ * On the server side, this interface is implemented by the <c>PetStoreResource</c> class.
+ *
+ * <p>
+ * On the client side, this interface is instantiated as a proxy using the <c>RestClient.getRemoteProxy()</c> method.
+ *
+ * <ul class='seealso'>
+ * 	<li class='extlink'>{@source}
+ * </ul>
+ */
+@RemoteResource(path="/petstore")
+public interface PetStore {
+
+	//------------------------------------------------------------------------------------------------------------------
+	// Pets
+	//------------------------------------------------------------------------------------------------------------------
+
+	/**
+	 * Returns all pets in the database.
+	 *
+	 * @return All pets in the database.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 */
+	@RemoteMethod(method=GET, path="/pet")
+	public Collection<Pet> getPets() throws NotAcceptable;
+
+	/**
+	 * Returns a pet from the database.
+	 *
+	 * @param petId The ID of the pet to retrieve.
+	 * @return The pet.
+	 * @throws IdNotFound Pet was not found.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 */
+	@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;
+
+	/**
+	 * Adds a pet to the database.
+	 *
+	 * @param pet The pet data to add to the database.
+	 * @return {@link Ok} if successful.
+	 * @throws IdConflict ID already in use.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 * @throws UnsupportedMediaType Unsupported <c>Content-Type</c> header specified.
+	 */
+	@RemoteMethod /* method and path inferred from method name */
+	public long postPet(
+		@Body(
+			description="Pet object to add to the store"
+		) CreatePet pet
+	) throws IdConflict, NotAcceptable, UnsupportedMediaType;
+
+	/**
+	 * Updates a pet in the database.
+	 *
+	 * @param pet The pet data to add to the database.
+	 * @return {@link Ok} if successful.
+	 * @throws IdNotFound ID not found.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 * @throws UnsupportedMediaType Unsupported <c>Content-Type</c> header specified.
+	 */
+	@RemoteMethod(method=PUT, path="/pet/{petId}")
+	public Ok updatePet(
+		@Body(
+			description="Pet object that needs to be added to the store"
+		) UpdatePet pet
+	) throws IdNotFound, NotAcceptable, UnsupportedMediaType;
+
+	/**
+	 * Find all pets with the matching statuses.
+	 *
+	 * @param status The statuses to match against.
+	 * @return The pets that match the specified statuses.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 */
+	@RemoteMethod(method=GET, path="/pet/findByStatus")
+	public Collection<Pet> findPetsByStatus(
+		@Query(
+			name="status",
+			description="Status values that need to be considered for filter.",
+			required=true,
+			type="array",
+			collectionFormat="csv",
+			items=@Items(
+				type="string",
+				_enum="AVAILABLE,PENDING,SOLD",
+				_default="AVAILABLE"
+			),
+			example="AVALIABLE,PENDING"
+		)
+		PetStatus[] status
+	) throws NotAcceptable;
+
+	/**
+	 * Find all pets with the specified tags.
+	 *
+	 * @param tags The tags to match against.
+	 * @return The pets that match the specified tags.
+	 * @throws InvalidTag Invalid tag was specified.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 */
+	@RemoteMethod(method=GET, path="/pet/findByTags")
+	@Deprecated
+	public Collection<Pet> findPetsByTags(
+		@Query(
+			name="tags",
+			description="Tags to filter by",
+			required=true,
+			example="['tag1','tag2']"
+		)
+		String[] tags
+	) throws InvalidTag, NotAcceptable;
+
+	/**
+	 * Deletes the specified pet.
+	 *
+	 * @param apiKey Security key.
+	 * @param petId ID of pet to delete.
+	 * @return {@link Ok} if successful.
+	 * @throws IdNotFound Pet not found.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 */
+	@RemoteMethod(method=DELETE, path="/pet/{petId}")
+	public Ok deletePet(
+		@Header(
+			name="api_key",
+			description="Security API key",
+			required=true,
+			example="foobar"
+		)
+		String apiKey,
+		@Path(
+			name="petId",
+			description="Pet id to delete",
+			example="123"
+		)
+		long petId
+	) throws IdNotFound, NotAcceptable;
+
+	//------------------------------------------------------------------------------------------------------------------
+	// Orders
+	//------------------------------------------------------------------------------------------------------------------
+
+	/**
+	 * Returns all orders in the database.
+	 *
+	 * @return All orders in the database.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 */
+	@RemoteMethod(method=GET, path="/store/order")
+	public Collection<Order> getOrders() throws NotAcceptable;
+
+	/**
+	 * Returns an order from the database.
+	 *
+	 * @param orderId The ID of the order to retreieve.
+	 * @return The retrieved order.
+	 * @throws InvalidId ID was invalid.
+	 * @throws IdNotFound Order was not found.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 */
+	@RemoteMethod(method=GET, path="/store/order/{orderId}")
+	public Order getOrder(
+		@Path(
+			name="orderId",
+			description="ID of order to fetch",
+			maximum="1000",
+			minimum="1",
+			example="123"
+		)
+		long orderId
+	) throws InvalidId, IdNotFound, NotAcceptable;
+
+	/**
+	 * Adds an order to the database.
+	 *
+	 * @param petId Id of pet to order.
+	 * @param username The username of the user placing the order.
+	 * @return The ID of the order.
+	 * @throws IdConflict ID was already in use.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 * @throws UnsupportedMediaType Unsupported <c>Content-Type</c> header specified.
+	 */
+	@RemoteMethod(method=POST, path="/store/order")
+	public long placeOrder(
+		@FormData(
+			name="petId",
+			description="Pet ID"
+		)
+		long petId,
+		@FormData(
+			name="username",
+			description="The username of the user creating the order"
+		)
+		String username
+	) throws IdConflict, NotAcceptable, UnsupportedMediaType;
+
+	/**
+	 * Deletes an order from the database.
+	 *
+	 * @param orderId The order ID.
+	 * @return {@link Ok} if successful.
+	 * @throws InvalidId ID not valid.
+	 * @throws IdNotFound Order not found.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 */
+	@RemoteMethod(method=DELETE, path="/store/order/{orderId}")
+	public Ok deleteOrder(
+		@Path(
+			name="orderId",
+			description="ID of the order that needs to be deleted",
+			minimum="1",
+			example="5"
+		)
+		long orderId
+	) throws InvalidId, IdNotFound, NotAcceptable;
+
+	/**
+	 * Returns an inventory of pet statuses and counts.
+	 *
+	 * @return An inventory of pet statuses and counts.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 */
+	@RemoteMethod(method=GET, path="/store/inventory")
+	public Map<PetStatus,Integer> getStoreInventory() throws NotAcceptable;
+
+	//------------------------------------------------------------------------------------------------------------------
+	// Users
+	//------------------------------------------------------------------------------------------------------------------
+
+	/**
+	 * Returns all users in the database.
+	 *
+	 * @return All users in the database.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 */
+	@RemoteMethod(method=GET, path="/user")
+	public Collection<User> getUsers() throws NotAcceptable;
+
+	/**
+	 * Returns a user from the database.
+	 *
+	 * @param username The username.
+	 * @return The user.
+	 * @throws InvalidUsername Invalid username.
+	 * @throws IdNotFound username not found.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 */
+	@RemoteMethod(method=GET, path="/user/{username}")
+	public User getUser(
+		@Path(
+			name="username",
+			description="The name that needs to be fetched. Use user1 for testing."
+		)
+		String username
+	) throws InvalidUsername, IdNotFound, NotAcceptable;
+
+	/**
+	 * Adds a new user to the database.
+	 *
+	 * @param user The user to add to the database.
+	 * @return {@link Ok} if successful.
+	 * @throws InvalidUsername Username was invalid.
+	 * @throws IdConflict Username already in use.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 * @throws UnsupportedMediaType Unsupported <c>Content-Type</c> header specified.
+	 */
+	@RemoteMethod
+	public Ok postUser(
+		@Body(
+			description="Created user object"
+		)
+		User user
+	) throws InvalidUsername, IdConflict, NotAcceptable, UnsupportedMediaType;
+
+	/**
+	 * Bulk creates users.
+	 *
+	 * @param users The users to add to the database.
+	 * @return {@link Ok} if successful.
+	 * @throws InvalidUsername Username was invalid.
+	 * @throws IdConflict Username already in use.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 * @throws UnsupportedMediaType Unsupported <c>Content-Type</c> header specified.
+	 */
+	@RemoteMethod(method=POST, path="/user/createWithArray")
+	public Ok createUsers(
+		@Body(
+			description="List of user objects"
+		)
+		User[] users
+	) throws InvalidUsername, IdConflict, NotAcceptable, UnsupportedMediaType;
+
+	/**
+	 * Updates a user in the database.
+	 *
+	 * @param username The username.
+	 * @param user The updated information.
+	 * @return {@link Ok} if successful.
+	 * @throws InvalidUsername Username was invalid.
+	 * @throws IdNotFound User was not found.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 * @throws UnsupportedMediaType Unsupported <c>Content-Type</c> header specified.
+	 */
+	@RemoteMethod(method=PUT, path="/user/{username}")
+	public Ok updateUser(
+		@Path(
+			name="username",
+			description="Name that need to be updated"
+		)
+		String username,
+		@Body(
+			description="Updated user object"
+		)
+		User user
+	) throws InvalidUsername, IdNotFound, NotAcceptable, UnsupportedMediaType;
+
+	/**
+	 * Deletes a user from the database.
+	 *
+	 * @param username The username.
+	 * @return {@link Ok} if successful.
+	 * @throws InvalidUsername Username was not valid.
+	 * @throws IdNotFound User was not found.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 */
+	@RemoteMethod(method=DELETE, path="/user/{username}")
+	public Ok deleteUser(
+		@Path(
+			name="username",
+			description="The name that needs to be deleted"
+		)
+		String username
+	) throws InvalidUsername, IdNotFound, NotAcceptable;
+
+	/**
+	 * User login.
+	 *
+	 * @param username The username for login.
+	 * @param password The password for login in clear text.
+	 * @param rateLimit Calls per hour allowed by the user.
+	 * @param expiresAfter The <bc>Expires-After</bc> response header.
+	 * @param req The servlet request.
+	 * @param res The servlet response.
+	 * @return {@link Ok} if successful.
+	 * @throws InvalidLogin Login was unsuccessful.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 */
+	@RemoteMethod(method=GET, path="/user/login")
+	public Ok login(
+		@Query(
+			name="username",
+			description="The username for login.",
+			required=true,
+			example="myuser"
+		)
+		String username,
+		@Query(
+			name="password",
+			description="The password for login in clear text.",
+			required=true,
+			example="abc123"
+		)
+		String password,
+		@ResponseHeader(
+			name="X-Rate-Limit",
+			type="integer",
+			format="int32",
+			description="Calls per hour allowed by the user.",
+			example="123"
+		)
+		Value<Integer> rateLimit,
+		Value<ExpiresAfter> expiresAfter
+	) throws InvalidLogin, NotAcceptable;
+
+	/**
+	 * User logout.
+	 *
+	 * @return {@link Ok} if successful.
+	 * @throws NotAcceptable Unsupported <c>Accept</c> header specified.
+	 */
+	@RemoteMethod(method=GET, path="/user/logout")
+	public Ok logout() throws NotAcceptable;
+}
diff --git a/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-client/pom.xml b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-client/pom.xml
index ee7f019..dd5aa42 100644
--- a/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-client/pom.xml
+++ b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-client/pom.xml
@@ -43,6 +43,7 @@
 
     <dependencies>
 
+
          <dependency>
             <groupId>org.apache.juneau</groupId>
             <artifactId>juneau-examples-petstore-api</artifactId>
diff --git a/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-client/src/main/java/org/apache/juneau/petstore/Main.java b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-client/src/main/java/org/apache/juneau/petstore/Main.java
new file mode 100644
index 0000000..ecc9c4e
--- /dev/null
+++ b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-client/src/main/java/org/apache/juneau/petstore/Main.java
@@ -0,0 +1,105 @@
+// ***************************************************************************************************************************
+// * 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.petstore;
+
+import static java.text.MessageFormat.*;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.juneau.json.*;
+import org.apache.juneau.marshall.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.petstore.dto.*;
+import org.apache.juneau.rest.client.*;
+
+/**
+ * Example code showing how to connect to the PetStore application using a remote proxy.
+ *
+ * <p>
+ * The remote proxy allows you to make REST calls against our REST interface through Java interface method calls.
+ */
+public class Main {
+
+	private static final JsonParser JSON_PARSER = JsonParser.create().ignoreUnknownBeanProperties().build();
+
+	public static void main(String[] args) {
+
+		// Create a RestClient with JSON serialization support.
+		try (RestClient rc = RestClient.create(SimpleJsonSerializer.class, JsonParser.class).build()) {
+
+			// Instantiate our proxy.
+			PetStore petStore = rc.getRemoteResource(PetStore.class, "http://localhost:5000");
+
+			// Print out the pets in the store.
+			Collection<Pet> pets = petStore.getPets();
+
+			// Pretty-print them to SYSOUT.
+			SimpleJson.DEFAULT_READABLE.println(pets);
+
+			// Initialize the application through REST calls.
+			init(new PrintWriter(System.err), petStore);
+
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * Initialize the petstore database by using a remote resource interface against our REST.
+	 *
+	 * @param w Console output.
+	 * @return This object (for method chaining).
+	 * @throws ParseException Malformed input encountered.
+	 * @throws IOException Thrown by client stream.
+	 */
+	public static void init(PrintWriter w, PetStore ps) throws ParseException, IOException {
+
+		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 : load("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 : load("init/Orders.json", Order[].class)) {
+			long id = ps.placeOrder(x.getPetId(), x.getUsername());
+			w.println(format("Created order:  id={0}", id));
+		}
+		for (User x : load("init/Users.json", User[].class)) {
+			ps.postUser(x);
+			w.println(format("Created user:  username={0}", x.getUsername()));
+		}
+	}
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// Helper methods
+	//-----------------------------------------------------------------------------------------------------------------
+
+	private static <T> T load(String fileName, Class<T> c) throws ParseException, IOException {
+		return JSON_PARSER.parse(getStream(fileName), c);
+	}
+
+	private static InputStream getStream(String fileName) {
+		return Main.class.getResourceAsStream(fileName);
+	}
+}
diff --git a/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/pom.xml b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/pom.xml
index 5f70636..504140d 100644
--- a/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/pom.xml
+++ b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/pom.xml
@@ -42,6 +42,37 @@
 
 
     <dependencies>
+        <!-- Juneau dependencies -->
+        <dependency>
+            <groupId>org.apache.juneau</groupId>
+            <artifactId>juneau-examples-petstore-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.juneau</groupId>
+            <artifactId>juneau-rest-server-springboot</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        
+        <!-- Spring Boot dependencies-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <version>${springframework.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+		<!-- Needed for @Inject support -->
+		<dependency>
+			<groupId>javax.inject</groupId>
+			<artifactId>javax.inject</artifactId>
+			<version>${javax.inject.version}</version>
+		</dependency>
 
     </dependencies>
 
diff --git a/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/App.java b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/App.java
new file mode 100644
index 0000000..062b75d
--- /dev/null
+++ b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/App.java
@@ -0,0 +1,34 @@
+// ***************************************************************************************************************************
+// * 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.petstore;
+
+import org.apache.juneau.rest.springboot.JuneauRestInitializer;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * Entry point for PetStore application.
+ */
+@SpringBootApplication
+public class App {
+
+    public static void main(String[] args) {
+        new App().start(args);
+    }
+
+    protected void start(String[] args) {
+        ConfigurableApplicationContext context = new SpringApplicationBuilder(App.class).initializers(new JuneauRestInitializer(App.class)).run(args);
+        AppConfiguration.setAppContext(context);
+    }
+}
diff --git a/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/AppConfiguration.java b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/AppConfiguration.java
new file mode 100644
index 0000000..462b274
--- /dev/null
+++ b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/AppConfiguration.java
@@ -0,0 +1,56 @@
+// ***************************************************************************************************************************
+// * 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.petstore;
+
+import org.apache.juneau.petstore.rest.*;
+import org.apache.juneau.rest.springboot.annotation.JuneauRestRoot;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.*;
+
+@Configuration
+public class AppConfiguration {
+
+    public static String DEFAULT_JDBC_URL = "jdbc:h2:mem:testdb;MODE=PostgreSQL";
+    public static String DEFAULT_JDBC_USERNAME = "sa";
+    public static String DEFAULT_JDBC_PASSWORD = "";
+
+    @Autowired
+    private static volatile ApplicationContext appContext;
+
+    public static ApplicationContext getAppContext() {
+        return appContext;
+    }
+
+    public static void setAppContext(ApplicationContext appContext) {
+        AppConfiguration.appContext = appContext;
+    }
+
+    //-----------------------------------------------------------------------------------------------------------------
+    // Services
+    //-----------------------------------------------------------------------------------------------------------------
+
+    //-----------------------------------------------------------------------------------------------------------------
+    // REST
+    //-----------------------------------------------------------------------------------------------------------------
+
+    @Bean @JuneauRestRoot
+    public RootResources rootResources() {
+        return new RootResources();
+    }
+
+    @Bean
+    public PetStoreResource petStoreResource() {
+        return new PetStoreResource();
+    }
+}
diff --git a/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/rest/PetStoreResource.java b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/rest/PetStoreResource.java
new file mode 100644
index 0000000..49807b3
--- /dev/null
+++ b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/rest/PetStoreResource.java
@@ -0,0 +1,685 @@
+// ***************************************************************************************************************************

+// * 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.petstore.rest;

+

+import static org.apache.juneau.dto.html5.HtmlBuilder.*;

+import static org.apache.juneau.dto.swagger.ui.SwaggerUI.*;

+import static org.apache.juneau.http.HttpMethodName.*;

+import static org.apache.juneau.http.response.Ok.*;

+

+import java.util.*;

+import java.util.Map;

+

+import javax.inject.*;

+

+import org.apache.juneau.jsonschema.annotation.*;

+import org.apache.juneau.petstore.*;

+import org.apache.juneau.petstore.dto.*;

+import org.apache.juneau.petstore.service.*;

+import org.apache.juneau.*;

+import org.apache.juneau.annotation.*;

+import org.apache.juneau.dto.html5.*;

+import org.apache.juneau.html.annotation.*;

+import org.apache.juneau.http.annotation.*;

+import org.apache.juneau.http.annotation.Path;

+import org.apache.juneau.internal.*;

+import org.apache.juneau.rest.*;

+import org.apache.juneau.rest.annotation.*;

+import org.apache.juneau.http.exception.*;

+import org.apache.juneau.rest.helper.*;

+import org.apache.juneau.http.response.*;

+import org.apache.juneau.rest.widget.*;

+import org.apache.juneau.transforms.*;

+import org.apache.juneau.rest.converters.*;

+

+/**

+ * Sample Petstore application.

+ *

+ * <ul class='seealso'>

+ * 	<li class='extlink'>{@source}

+ * </ul>

+ */

+@RestResource(

+	path="/petstore",

+	title="Petstore application",

+	description={

+		"This is a sample server Petstore server based on the Petstore sample at Swagger.io.",

+		"You can find out more about Swagger at http://swagger.io.",

+	},

+	properties= {

+		// Resolve recursive references when showing schema info in the swagger.

+		@Property(name=SWAGGERUI_resolveRefsMaxDepth, value="99")

+	},

+	swagger=@ResourceSwagger(

+		version="1.0.0",

+		title="Swagger Petstore",

+		termsOfService="You are on your own.",

+		contact=@Contact(

+			name="Juneau Development Team",

+			email="dev@juneau.apache.org",

+			url="http://juneau.apache.org"

+		),

+		license=@License(

+			name="Apache 2.0",

+			url="http://www.apache.org/licenses/LICENSE-2.0.html"

+		),

+		externalDocs=@ExternalDocs(

+			description="Find out more about Juneau",

+			url="http://juneau.apache.org"

+		),

+		tags={

+			@Tag(

+				name="pet",

+				description="Everything about your Pets",

+				externalDocs=@ExternalDocs(

+					description="Find out more",

+					url="http://juneau.apache.org"

+				)

+			),

+			@Tag(

+				name="store",

+				description="Access to Petstore orders"

+			),

+			@Tag(

+				name="user",

+				description="Operations about user",

+				externalDocs=@ExternalDocs(

+					description="Find out more about our store",

+					url="http://juneau.apache.org"

+				)

+			)

+		}

+	),

+	staticFiles={"htdocs:htdocs"},  // Expose static files in htdocs subpackage.

+	children={

+		SqlQueryResource.class,

+		PhotosResource.class

+	}

+)

+@HtmlDocConfig(

+	widgets={

+		ContentTypeMenuItem.class,

+		ThemeMenuItem.class,

+	},

+	navlinks={

+		"up: request:/..",

+		"options: servlet:/?method=OPTIONS",

+		"init: servlet:/init",

+		"$W{ContentTypeMenuItem}",

+		"$W{ThemeMenuItem}",

+		"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/petstore/$R{servletClassSimple}.java"

+	},

+	head={

+		"<link rel='icon' href='$U{servlet:/htdocs/cat.png}'/>"  // Add a cat icon to the page.

+	},

+	header={

+		"<h1>$R{resourceTitle}</h1>",

+		"<h2>$R{methodSummary}</h2>",

+		"$C{PetStore/headerImage}"

+	},

+	aside={

+		"<div style='max-width:400px' class='text'>",

+		"	<p>This page shows a standard nested REST resource.</p>",

+		"	<p>It shows how different properties can be rendered on the same bean in different views.</p>",

+		"	<p>It also shows examples of HtmlRender classes and @BeanProperty(format) annotations.</p>",

+		"	<p>It also shows how the Queryable converter and query widget can be used to create searchable interfaces.</p>",

+		"</div>"

+	},

+	stylesheet="servlet:/htdocs/themes/dark.css"  // Use dark theme by default.

+)

+public class PetStoreResource extends BasicRest implements PetStore {

+

+	@Inject

+	private PetStoreService store;

+

+	/**

+	 * Navigation page

+	 *

+	 * @return Navigation page contents.

+	 */

+	@RestMethod(

+		name=GET,

+		path="/",

+		summary="Navigation page"

+	)

+	@HtmlDocConfig(

+		style={

+			"INHERIT",  // Flag for inheriting resource-level CSS.

+			"body { ",

+				"background-image: url('petstore/htdocs/background.jpg'); ",

+				"background-color: black; ",

+				"background-size: cover; ",

+				"background-attachment: fixed; ",

+			"}"

+		}

+	)

+	public ResourceDescriptions getTopPage() {

+		return new ResourceDescriptions()

+			.append("pet", "All pets in the store")

+			.append("store", "Orders and inventory")

+			.append("user", "Petstore users")

+			.append("photos", "Photos service")

+			.append("sql", "SQL query service")

+		;

+	}

+

+	//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

+	// Initialization

+	//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

+

+	/**

+	 * Initialize database form entry page.

+	 *

+	 * @return Initialize database form entry page contents.

+	 */

+	@RestMethod(

+		summary="Initialize database form entry page"

+	)

+	public Div getInit() {

+		return div(

+			form("servlet:/init").method(POST).target("buf").children(

+				table(

+					tr(

+						th("Initialize petstore database:"),

+						td(input("radio").name("init-method").value("direct").checked(true), "direct", input("radio").name("init-method").value("rest"), "rest"),

+						td(button("submit", "Submit").style("float:right").onclick("scrolling=true"))

+					)

+				)

+			),

+			br(),

+			iframe().id("buf").name("buf").style("width:800px;height:600px;").onload("window.parent.scrolling=false;"),

+			script("text/javascript",

+				"var scrolling = false;",

+				"function scroll() { if (scrolling) { document.getElementById('buf').contentWindow.scrollBy(0,50); } setTimeout('scroll()',200); } ",

+				"scroll();"

+			)

+		);

+	}

+

+	/**

+	 * Initialize database.

+	 *

+	 * @param initMethod The method used to initialize the database.

+	 * @param res HTTP request.

+	 * @throws Exception Error occurred.

+	 */

+	@RestMethod(

+		summary="Initialize database"

+	)

+	public void postInit(

+		RestResponse res

+	) throws Exception {

+		res.setHeader("Content-Encoding", "identity");

+		store.initDirect(res.getDirectWriter("text/plain"));

+	}

+

+	//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

+	// Pets

+	//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

+

+	@Override /* PetStore */

+	@RestMethod(

+		name=GET,

+		path="/pet",

+		summary="All pets in the store",

+		swagger=@MethodSwagger(

+			tags="pet",

+			parameters={

+				Queryable.SWAGGER_PARAMS

+			}

+		),

+		converters={Queryable.class}

+	)

+	@BeanConfig(

+		bpx="Pet: tags,photo"

+	)

+	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:[] } ]"

+			}

+		)

+	)

+	public Pet getPet(long petId) throws IdNotFound, NotAcceptable {

+		return store.getPet(petId);

+	}

+

+	@Override /* PetStore */

+	@RestMethod(

+		summary="Add a new pet to the store",

+		swagger=@MethodSwagger(

+			tags="pet",

+			value={

+				"security:[ { petstore_auth:['write:pets','read:pets'] } ]"

+			}

+		)

+	)

+	public long postPet(CreatePet pet) throws IdConflict, NotAcceptable, UnsupportedMediaType {

+		return store.create(pet).getId();

+	}

+

+	@Override /* PetStore */

+	@RestMethod(

+		name=PUT,

+		path="/pet/{petId}",

+		summary="Update an existing pet",

+		swagger=@MethodSwagger(

+			tags="pet",

+			value={

+				"security:[ { petstore_auth: ['write:pets','read:pets'] } ]"

+			}

+		)

+	)

+	public Ok updatePet(UpdatePet pet) throws IdNotFound, NotAcceptable, UnsupportedMediaType {

+		store.update(pet);

+		return OK;

+	}

+

+	@Override /* PetStore */

+	@RestMethod(

+		name=GET,

+		path="/pet/findByStatus",

+		summary="Finds Pets by status",

+		description="Multiple status values can be provided with comma separated strings.",

+		swagger=@MethodSwagger(

+			tags="pet",

+			value={

+				"security:[{petstore_auth:['write:pets','read:pets']}]"

+			}

+		)

+	)

+	public Collection<Pet> findPetsByStatus(PetStatus[] status) throws NotAcceptable {

+		return store.getPetsByStatus(status);

+	}

+

+	@Override /* PetStore */

+	@RestMethod(

+		name=GET,

+		path="/pet/findByTags",

+		summary="Finds Pets by tags",

+		description="Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.",

+		swagger=@MethodSwagger(

+			tags="pet",

+			value={

+				"security:[ { petstore_auth:[ 'write:pets','read:pets' ] } ]"

+			}

+		)

+	)

+	@Deprecated

+	public Collection<Pet> findPetsByTags(String[] tags) throws InvalidTag, NotAcceptable {

+		return store.getPetsByTags(tags);

+	}

+

+	@Override /* PetStore */

+	@RestMethod(

+		name=DELETE,

+		path="/pet/{petId}",

+		summary="Deletes a pet",

+		swagger=@MethodSwagger(

+			tags="pet",

+			value={

+				"security:[ { petstore_auth:[ 'write:pets','read:pets' ] } ]"

+			}

+		)

+	)

+	public Ok deletePet(String apiKey, long petId) throws IdNotFound, NotAcceptable {

+		store.removePet(petId);

+		return OK;

+	}

+

+	//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

+	// Pets - extra methods

+	//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

+

+	/**

+	 * Displays the pet edit page.

+	 *

+	 * @param petId ID of pet to edit

+	 * @return Edit page contents.

+	 * @throws NotAcceptable Unsupported <bc>Accept</bc> header value specified.

+	 * @throws UnsupportedMediaType Unsupported <bc>Content-Type</bc> header value specified.

+	 */

+	@RestMethod(

+		name=GET,

+		path="/pet/{petId}/edit",

+		summary="Pet edit page",

+		swagger=@MethodSwagger(

+			tags="pet",

+			value={

+				"security:[ { petstore_auth:['write:pets','read:pets'] } ]"

+			}

+		)

+	)

+	public Div editPetPage(

+			@Path(

+				name="petId",

+				description="ID of pet to edit",

+				example="123"

+			)

+			long petId

+		) throws NotAcceptable, UnsupportedMediaType {

+

+		Pet pet = getPet(petId);

+

+		return div(

+			form().id("form").action("servlet:/pet/" + petId).method(POST).children(

+				table(

+					tr(

+						th("Id:"),

+						td(input().name("id").type("text").value(petId).readonly(true)),

+						td(new Tooltip("&#x2753;", "The name of the pet.", br(), "e.g. 'Fluffy'"))

+					),

+					tr(

+						th("Name:"),

+						td(input().name("name").type("text").value(pet.getName())),

+						td(new Tooltip("&#x2753;", "The name of the pet.", br(), "e.g. 'Fluffy'"))

+					),

+					tr(

+						th("Species:"),

+						td(

+							select().name("species").children(

+								option("cat"), option("dog"), option("bird"), option("fish"), option("mouse"), option("rabbit"), option("snake")

+							).choose(pet.getSpecies())

+						),

+						td(new Tooltip("&#x2753;", "The kind of animal."))

+					),

+					tr(

+						th("Price:"),

+						td(input().name("price").type("number").placeholder("1.0").step("0.01").min(1).max(100).value(pet.getPrice())),

+						td(new Tooltip("&#x2753;", "The price to charge for this pet."))

+					),

+					tr(

+						th("Tags:"),

+						td(input().name("tags").type("text").value(StringUtils.join(pet.getTags(), ','))),

+						td(new Tooltip("&#x2753;", "Arbitrary textual tags (comma-delimited).", br(), "e.g. 'fluffy,friendly'"))

+					),

+					tr(

+						th("Status:"),

+						td(

+							select().name("status").children(

+								option("AVAILABLE"), option("PENDING"), option("SOLD")

+							).choose(pet.getStatus())

+						),

+						td(new Tooltip("&#x2753;", "The current status of the animal."))

+					),

+					tr(

+						td().colspan(2).style("text-align:right").children(

+							button("reset", "Reset"),

+							button("button","Cancel").onclick("window.location.href='/'"),

+							button("submit", "Submit")

+						)

+					)

+				).style("white-space:nowrap")

+			)

+		);

+	}

+

+	//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

+	// Orders

+	//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

+

+	/**

+	 * Store navigation page.

+	 *

+	 * @return Store navigation page contents.

+	 */

+	@RestMethod(

+		summary="Store navigation page",

+		swagger=@MethodSwagger(

+			tags="store"

+		)

+	)

+	public ResourceDescriptions getStore() {

+		return new ResourceDescriptions()

+			.append("store/order", "Petstore orders")

+			.append("store/inventory", "Petstore inventory")

+		;

+	}

+

+	@Override

+	@RestMethod(

+		name=GET,

+		path="/store/order",

+		summary="Petstore orders",

+		swagger=@MethodSwagger(

+			tags="store"

+		)

+	)

+	@HtmlDocConfig(

+		widgets={

+			QueryMenuItem.class

+		},

+		navlinks={

+			"INHERIT",                // Inherit links from class.

+			"[2]:$W{QueryMenuItem}",  // Insert QUERY link in position 2.

+			"[3]:$W{AddOrderMenuItem}"  // Insert ADD link in position 3.

+		}

+	)

+	public Collection<Order> getOrders() throws NotAcceptable {

+		return store.getOrders();

+	}

+

+	@Override

+	@RestMethod(

+		name=GET,

+		path="/store/order/{orderId}",

+		summary="Find purchase order by ID",

+		description="Returns a purchase order by ID.",

+		swagger=@MethodSwagger(

+			tags="store"

+		)

+	)

+	public Order getOrder(long orderId) throws InvalidId, IdNotFound, NotAcceptable {

+		if (orderId < 1 || orderId > 1000)

+			throw new InvalidId();

+		return store.getOrder(orderId);

+	}

+

+	@Override

+	@RestMethod(

+		name=POST,

+		path="/store/order",

+		summary="Place an order for a pet",

+		swagger=@MethodSwagger(

+			tags="store"

+		),

+		pojoSwaps={

+			TemporalDateSwap.IsoLocalDate.class

+		}

+	)

+	public long placeOrder(long petId, String username) throws IdConflict, NotAcceptable, UnsupportedMediaType {

+		CreateOrder co = new CreateOrder(petId, username);

+		return store.create(co).getId();

+	}

+

+	@Override

+	@RestMethod(

+		name=DELETE,

+		path="/store/order/{orderId}",

+		summary="Delete purchase order by ID",

+		description= {

+			"For valid response try integer IDs with positive integer value.",

+			"Negative or non-integer values will generate API errors."

+		},

+		swagger=@MethodSwagger(

+			tags="store"

+		)

+	)

+	public Ok deleteOrder(long orderId) throws InvalidId, IdNotFound, NotAcceptable {

+		if (orderId < 0)

+			throw new InvalidId();

+		store.removeOrder(orderId);

+		return OK;

+	}

+

+	@Override

+	@RestMethod(

+		name=GET,

+		path="/store/inventory",

+		summary="Returns pet inventories by status",

+		description="Returns a map of status codes to quantities",

+		swagger=@MethodSwagger(

+			tags="store",

+			responses={

+				"200:{ 'x-example':{AVAILABLE:123} }",

+			},

+			value={

+				"security:[ { api_key:[] } ]"

+			}

+		)

+	)

+	public Map<PetStatus,Integer> getStoreInventory() throws NotAcceptable {

+		return store.getInventory();

+	}

+

+	//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

+	// Users

+	//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

+

+	@Override

+	@RestMethod(

+		name=GET,

+		path="/user",

+		summary="Petstore users",

+		bpx="User: email,password,phone",

+		swagger=@MethodSwagger(

+			tags="user"

+		)

+	)

+	public Collection<User> getUsers() throws NotAcceptable {

+		return store.getUsers();

+	}

+

+	@Override

+	@RestMethod(

+		name=GET,

+		path="/user/{username}",

+		summary="Get user by user name",

+		swagger=@MethodSwagger(

+			tags="user"

+		)

+	)

+	public User getUser(String username) throws InvalidUsername, IdNotFound, NotAcceptable {

+		return store.getUser(username);

+	}

+

+	@Override

+	@RestMethod(

+		summary="Create user",

+		description="This can only be done by the logged in user.",

+		swagger=@MethodSwagger(

+			tags="user"

+		)

+	)

+	public Ok postUser(User user) throws InvalidUsername, IdConflict, NotAcceptable, UnsupportedMediaType {

+		store.create(user);

+		return OK;

+	}

+

+	@Override

+	@RestMethod(

+		name=POST,

+		path="/user/createWithArray",

+		summary="Creates list of users with given input array",

+		swagger=@MethodSwagger(

+			tags="user"

+		)

+	)

+	public Ok createUsers(User[] users) throws InvalidUsername, IdConflict, NotAcceptable, UnsupportedMediaType {

+		for (User user : users)

+			store.create(user);

+		return OK;

+	}

+

+	@Override

+	@RestMethod(

+		name=PUT,

+		path="/user/{username}",

+		summary="Update user",

+		description="This can only be done by the logged in user.",

+		swagger=@MethodSwagger(

+			tags="user"

+		)

+	)

+	public Ok updateUser(String username, User user) throws InvalidUsername, IdNotFound, NotAcceptable, UnsupportedMediaType {

+		store.update(user);

+		return OK;

+	}

+

+	@Override

+	@RestMethod(

+		name=DELETE,

+		path="/user/{username}",

+		summary="Delete user",

+		description="This can only be done by the logged in user.",

+		swagger=@MethodSwagger(

+			tags="user"

+		)

+	)

+	public Ok deleteUser(String username) throws InvalidUsername, IdNotFound, NotAcceptable {

+		store.removeUser(username);

+		return OK;

+	}

+

+	@Override

+	@RestMethod(

+		name=GET,

+		path="/user/login",

+		summary="Logs user into the system",

+		swagger=@MethodSwagger(

+			tags="user"

+		)

+	)

+	public Ok login(

+			String username,

+			String password,

+			Value<Integer> rateLimit,

+			Value<ExpiresAfter> expiresAfter

+		) throws InvalidLogin, NotAcceptable {

+

+		RestRequest req = getRequest();

+

+		if (! store.isValid(username, password))

+			throw new InvalidLogin();

+

+		Date d = new Date(System.currentTimeMillis() + 30 * 60 * 1000);

+		req.getSession().setAttribute("login-expires", d);

+		rateLimit.set(1000);

+		expiresAfter.set(new ExpiresAfter(d));

+		return OK;

+	}

+

+	@Override

+	@RestMethod(

+		name=GET,

+		path="/user/logout",

+		summary="Logs out current logged in user session",

+		swagger=@MethodSwagger(

+			tags="user"

+		)

+	)

+	public Ok logout() throws NotAcceptable {

+		getRequest().getSession().removeAttribute("login-expires");

+		return OK;

+	}

+}
\ No newline at end of file
diff --git a/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/rest/PhotosResource.java b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/rest/PhotosResource.java
new file mode 100644
index 0000000..1eeb058
--- /dev/null
+++ b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/rest/PhotosResource.java
@@ -0,0 +1,327 @@
+// ***************************************************************************************************************************

+// * 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.petstore.rest;

+

+import static org.apache.juneau.http.HttpMethodName.*;

+

+import org.apache.juneau.jsonschema.annotation.ExternalDocs;

+import org.apache.juneau.jsonschema.annotation.Schema;

+import java.awt.image.*;

+import java.io.*;

+import java.net.*;

+import java.util.*;

+import java.util.Map;

+

+import javax.imageio.*;

+import javax.servlet.*;

+import javax.servlet.http.*;

+

+import org.apache.juneau.*;

+import org.apache.juneau.html.annotation.*;

+import org.apache.juneau.http.annotation.*;

+import org.apache.juneau.http.annotation.Body;

+import org.apache.juneau.http.annotation.Path;

+import org.apache.juneau.http.annotation.Response;

+import org.apache.juneau.internal.*;

+import org.apache.juneau.parser.*;

+import org.apache.juneau.rest.*;

+import org.apache.juneau.rest.annotation.*;

+import org.apache.juneau.http.exception.*;

+import org.apache.juneau.rest.helper.*;

+import org.apache.juneau.rest.matchers.*;

+import org.apache.juneau.serializer.*;

+

+/**

+ * Sample resource that allows images to be uploaded and retrieved.

+ *

+ * <ul class='seealso'>

+ * 	<li class='extlink'>{@source}

+ * </ul>

+ */

+@RestResource(

+	path="/photos",

+	messages="nls/PhotosResource",

+	title="Photo REST service",

+	description="Sample resource that allows images to be uploaded and retrieved.",

+	swagger=@ResourceSwagger(

+		contact=@Contact(name="Juneau Developer",email="dev@juneau.apache.org"),

+		license=@License(name="Apache 2.0",url="http://www.apache.org/licenses/LICENSE-2.0.html"),

+		version="2.0",

+		termsOfService="You are on your own.",

+		externalDocs=@ExternalDocs(description="Apache Juneau",url="http://juneau.apache.org")

+	)

+)

+@HtmlDocConfig(

+	navlinks={

+		"up: request:/..",

+		"options: servlet:/?method=OPTIONS",

+		"$W{UploadPhotoMenuItem}",

+		"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java"

+	},

+	aside={

+		"<div style='max-width:400px;min-width:200px' class='text'>",

+		"	<p>Shows an example of using custom serializers and parsers to create REST interfaces over binary resources.</p>",

+		"	<p>In this case, our resources are marshalled jpeg and png binary streams and are stored in an in-memory 'database' (also known as a <c>TreeMap</c>).</p>",

+		"</div>"

+	},

+	widgets={

+		UploadPhotoMenuItem.class

+	},

+	stylesheet="servlet:/htdocs/themes/dark.css"

+)

+@HtmlConfig(

+	// Make the anchor text on URLs be just the path relative to the servlet.

+	uriAnchorText="SERVLET_RELATIVE"

+)

+public class PhotosResource extends BasicRestServlet {

+	private static final long serialVersionUID = 1L;

+

+	// Our cache of photos

+	private Map<String,Photo> photos = new TreeMap<>();

+

+	@Override /* Servlet */

+	public void init() {

+		try (InputStream is = getClass().getResourceAsStream("photos/cat.jpg")) {

+			BufferedImage image = ImageIO.read(is);

+			Photo photo = new Photo("cat", image);

+			photos.put(photo.id, photo);

+		} catch (IOException e) {

+			throw new RuntimeException(e);

+		}

+	}

+

+	/** Our bean class for storing photos */

+	public static class Photo {

+		String id;

+		BufferedImage image;

+

+		Photo(String id, BufferedImage image) {

+			this.id = id;

+			this.image = image;

+		}

+

+		/**

+		 * @return The URI of this photo.

+		 * @throws URISyntaxException ID could not be converted to a URI.

+		 */

+		public URI getURI() throws URISyntaxException {

+			return new URI("servlet:/" + id);

+		}

+	}

+

+	//-----------------------------------------------------------------------------------------------------------------

+	// REST methods

+	//-----------------------------------------------------------------------------------------------------------------

+

+	/**

+	 * Show the list of all currently loaded photos

+	 *

+	 * @return The list of all currently loaded photos.

+	 * @throws Exception Error occurred.

+	 */

+	@RestMethod(

+		name=GET,

+		path="/",

+		summary="Show the list of all currently loaded photos"

+	)

+	public Collection<Photo> getAllPhotos() throws Exception {

+		return photos.values();

+	}

+

+	/**

+	 * Shows how to use a custom serializer to serialize a BufferedImage object to a stream.

+	 *

+	 * @param id The photo ID.

+	 * @return The image.

+	 * @throws NotFound Image was not found.

+	 */

+	@RestMethod(

+		name=GET,

+		path="/{id}",

+		serializers=ImageSerializer.class,

+		summary="Get a photo by ID",

+		description="Shows how to use a custom serializer to serialize a BufferedImage object to a stream."

+	)

+	@Response(

+		schema=@Schema(type="file")

+	)

+	public BufferedImage getPhoto(@Path("id") String id) throws NotFound {

+		Photo p = photos.get(id);

+		if (p == null)

+			throw new NotFound("Photo not found");

+		return p.image;

+	}

+

+	/**

+	 * Shows how to use a custom parser to parse a stream into a BufferedImage object.

+	 *

+	 * @param id The photo ID.

+	 * @param image Binary contents of image.

+	 * @return <js>"OK"</jk> if successful.

+	 * @throws Exception Error occurred.

+	 */

+	@RestMethod(

+		name=PUT,

+		path="/{id}",

+		parsers=ImageParser.class,

+		summary="Add or overwrite a photo",

+		description="Shows how to use a custom parser to parse a stream into a BufferedImage object."

+	)

+	public String addPhoto(

+			@Path("id") String id,

+			@Body(

+				description="Binary contents of image.",

+				schema=@Schema(type="file")

+			)

+			BufferedImage image

+		) throws Exception {

+		photos.put(id, new Photo(id, image));

+		return "OK";

+	}

+

+	/**

+	 * Shows how to use a custom parser to parse a stream into a BufferedImage object.

+	 *

+	 * @param image Binary contents of image.

+	 * @return The Photo bean.

+	 * @throws Exception Error occurred.

+	 */

+	@RestMethod(

+		name=POST,

+		path="/",

+		parsers=ImageParser.class,

+		summary="Add a photo",

+		description="Shows how to use a custom parser to parse a stream into a BufferedImage object."

+	)

+	public Photo setPhoto(

+			@Body(

+				description="Binary contents of image.",

+				schema=@Schema(type="file")

+			)

+			BufferedImage image

+		) throws Exception {

+		Photo p = new Photo(UUID.randomUUID().toString(), image);

+		photos.put(p.id, p);

+		return p;

+	}

+

+	/**

+	 * Upload a photo from a multipart form post.

+	 *

+	 * @param req HTTP request.

+	 * @return Redirect to servlet root.

+	 * @throws Exception Error occurred.

+	 */

+	@RestMethod(

+		name=POST,

+		path="/upload",

+		matchers=MultipartFormDataMatcher.class,

+		summary="Upload a photo from a multipart form post",

+		description="Shows how to parse a multipart form post containing a binary field.",

+		swagger=@MethodSwagger(

+			parameters={

+				"{in:'formData', name:'id', description:'Unique identifier to assign to image.', type:'string', required:false},",

+				"{in:'formData', name:'file', description:'The binary contents of the image file.', type:'file', required:true}"

+			}

+		)

+	)

+    public SeeOtherRoot uploadFile(RestRequest req) throws Exception {

+       MultipartConfigElement multipartConfigElement = new MultipartConfigElement((String)null);

+       req.setAttribute("org.eclipse.jetty.multipartConfig", multipartConfigElement);

+       String id = UUID.randomUUID().toString();

+       BufferedImage img = null;

+       for (Part part : req.getParts()) {

+    	   switch (part.getName()) {

+    		   case "id":

+    			   id = IOUtils.read(part.getInputStream());

+    			   break;

+    		   case "file":

+    			   img = ImageIO.read(part.getInputStream());

+    	   }

+       }

+       addPhoto(id, img);

+       return new SeeOtherRoot(); // Redirect to the servlet root.

+    }

+

+	/**

+	 * Removes a photo from the database.

+	 *

+	 * @param id ID of photo to remove.

+	 * @return <js>"OK"</jk> if successful.

+	 * @throws NotFound Photo was not found.

+	 */

+	@RestMethod(

+		name=DELETE,

+		path="/{id}",

+		summary="Delete a photo by ID"

+	)

+	public String deletePhoto(@Path("id") String id) throws NotFound {

+		Photo p = photos.remove(id);

+		if (p == null)

+			throw new NotFound("Photo not found");

+		return "OK";

+	}

+

+	//-----------------------------------------------------------------------------------------------------------------

+	// Custom serializers and parsers.

+	//-----------------------------------------------------------------------------------------------------------------

+

+	/** Serializer for converting images to byte streams */

+	public static class ImageSerializer extends OutputStreamSerializer {

+

+		/**

+		 * Constructor.

+		 * @param ps The property store containing all the settings for this object.

+		 */

+		public ImageSerializer(PropertyStore ps) {

+			super(ps, null, "image/png,image/jpeg");

+		}

+

+		@Override /* Serializer */

+		public OutputStreamSerializerSession createSession(SerializerSessionArgs args) {

+			return new OutputStreamSerializerSession(args) {

+

+				@Override /* SerializerSession */

+				protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException {

+					RenderedImage image = (RenderedImage)o;

+					String mediaType = getProperty("mediaType", String.class, (String)null);

+					ImageIO.write(image, mediaType.substring(mediaType.indexOf('/')+1), out.getOutputStream());

+				}

+			};

+		}

+	}

+

+	/** Parser for converting byte streams to images */

+	public static class ImageParser extends InputStreamParser {

+

+		/**

+		 * Constructor.

+		 * @param ps The property store containing all the settings for this object.

+		 */

+		public ImageParser(PropertyStore ps) {

+			super(ps, "image/png", "image/jpeg");

+		}

+

+		@Override /* Parser */

+		public InputStreamParserSession createSession(final ParserSessionArgs args) {

+			return new InputStreamParserSession(args) {

+

+				@Override /* ParserSession */

+				@SuppressWarnings("unchecked")

+				protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws IOException, ParseException, ExecutableException {

+					return (T)ImageIO.read(pipe.getInputStream());

+				}

+			};

+		}

+	}

+}
\ No newline at end of file
diff --git a/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/rest/RootResources.java b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/rest/RootResources.java
new file mode 100644
index 0000000..424b8f3
--- /dev/null
+++ b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/rest/RootResources.java
@@ -0,0 +1,66 @@
+// ***************************************************************************************************************************

+// * 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.petstore.rest;

+

+import org.apache.juneau.html.annotation.*;

+import org.apache.juneau.rest.*;

+import org.apache.juneau.rest.annotation.*;

+import org.apache.juneau.rest.widget.*;

+import org.apache.juneau.serializer.annotation.*;

+

+/**

+ * Sample REST resource showing how to implement a "router" resource page.

+ *

+ * <ul class='seealso'>

+ * 	<li class='extlink'>{@source}

+ * </ul>

+ */

+@RestResource(

+	path="/*",

+	title="Root resources",

+	description="Example of a router resource page.",

+	children={

+		PetStoreResource.class

+	}

+)

+@HtmlDocConfig(

+	widgets={

+		ContentTypeMenuItem.class,

+		ThemeMenuItem.class

+	},

+	navlinks={

+		"options: ?method=OPTIONS",

+		"$W{ContentTypeMenuItem}",

+		"$W{ThemeMenuItem}",

+		"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java"

+	},

+	aside={

+		"<div style='max-width:400px' class='text'>",

+		"	<p>This is an example of a 'router' page that serves as a jumping-off point to child resources.</p>",

+		"	<p>Resources can be nested arbitrarily deep through router pages.</p>",

+		"	<p>Note the <span class='link'>options</span> link provided that lets you see the generated swagger doc for this page.</p>",

+		"	<p>Also note the <span class='link'>sources</span> link on these pages to view the source code for the page.</p>",

+		"	<p>All content on pages in the UI are serialized POJOs.  In this case, it's a serialized array of beans with 2 properties, 'name' and 'description'.</p>",

+		"	<p>Other features (such as this aside) are added through annotations.</p>",

+		"</div>"

+	}

+)

+@SerializerConfig(

+	// For testing purposes, we want to use single quotes in all the serializers so it's easier to do simple

+	// String comparisons.

+	// You can apply any of the Serializer/Parser/BeanContext settings this way.

+	quoteChar="'"

+)

+public class RootResources extends BasicRestServletGroup {

+	private static final long serialVersionUID = 1L;

+}

diff --git a/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/rest/SqlQueryResource.java b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/rest/SqlQueryResource.java
new file mode 100644
index 0000000..de015b0
--- /dev/null
+++ b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/rest/SqlQueryResource.java
@@ -0,0 +1,249 @@
+// ***************************************************************************************************************************

+// * 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.petstore.rest;

+

+import static org.apache.juneau.dto.html5.HtmlBuilder.*;

+import static org.apache.juneau.http.HttpMethodName.*;

+import static org.apache.juneau.internal.StringUtils.*;

+import static org.apache.juneau.rest.annotation.HookEvent.*;

+

+import java.sql.*;

+import java.util.*;

+

+import org.apache.juneau.jsonschema.annotation.ExternalDocs;

+import org.apache.juneau.config.*;

+import org.apache.juneau.dto.*;

+import org.apache.juneau.dto.html5.*;

+import org.apache.juneau.html.annotation.*;

+import org.apache.juneau.http.annotation.*;

+import org.apache.juneau.http.annotation.Body;

+import org.apache.juneau.http.annotation.Query;

+import org.apache.juneau.http.annotation.Response;

+import org.apache.juneau.rest.*;

+import org.apache.juneau.rest.annotation.*;

+import org.apache.juneau.http.exception.*;

+import org.apache.juneau.rest.widget.*;

+

+/**

+ * Sample resource that shows how Juneau can serialize ResultSets.

+ *

+ * <ul class='seealso'>

+ * 	<li class='extlink'>{@source}

+ * </ul>

+ */

+@RestResource(

+	path="/sql",

+	title="SQL query service",

+	description="Executes queries against the local derby '$C{SqlQueryResource/connectionUrl}' database",

+	swagger=@ResourceSwagger(

+		contact=@Contact(name="Juneau Developer",email="dev@juneau.apache.org"),

+		license=@License(name="Apache 2.0",url="http://www.apache.org/licenses/LICENSE-2.0.html"),

+		version="2.0",

+		termsOfService="You are on your own.",

+		externalDocs=@ExternalDocs(description="Apache Juneau",url="http://juneau.apache.org")

+	)

+)

+@HtmlDocConfig(

+	widgets={

+		ThemeMenuItem.class

+	},

+	navlinks={

+		"up: request:/..",

+		"options: servlet:/?method=OPTIONS",

+		"$W{ThemeMenuItem}",

+		"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java"

+	},

+	aside={

+		"<div style='min-width:200px' class='text'>",

+		"	<p>An example of a REST interface over a relational database that serializes ResultSet objects.</p>",

+		"	<p>Specify one or more queries delimited by semicolons.</p>",

+		"	<h5>Examples:</h5>",

+		"	<ul>",

+		"		<li><a class='link' href='?sql=select+*+from+sys.systables'>Tables</a>",

+		"		<li><a class='link' href='?sql=select+*+from+PetstorePet'>Pets</a>",

+		"		<li><a class='link' href='?sql=select+*+from+PetstoreOrder'>Orders</a>",

+		"		<li><a class='link' href='?sql=select+*+from+PetstoreUser'>Users</a>",

+		"	</ul>",

+		"</div>"

+	},

+	stylesheet="servlet:/htdocs/themes/dark.css"

+)

+public class SqlQueryResource extends BasicRestServlet {

+	private static final long serialVersionUID = 1L;

+

+	private String driver, connectionUrl;

+	private boolean allowUpdates, allowTempUpdates, includeRowNums;

+

+	/**

+	 * Initializes the registry URL and rest client.

+	 *

+	 * @param builder The resource config.

+	 */

+	@RestHook(INIT)

+	public void initConnection(RestContextBuilder builder) {

+		Config cf = builder.getConfig();

+

+		driver = cf.getString("SqlQueryResource/driver");

+		connectionUrl = cf.getString("SqlQueryResource/connectionUrl");

+		allowUpdates = cf.getBoolean("SqlQueryResource/allowUpdates", false);

+		allowTempUpdates = cf.getBoolean("SqlQueryResource/allowTempUpdates", false);

+		includeRowNums = cf.getBoolean("SqlQueryResource/includeRowNums", false);

+

+		try {

+			Class.forName(driver).newInstance();

+		} catch (Exception e) {

+			e.printStackTrace(System.err);

+			throw new RuntimeException(e);

+		}

+	}

+

+	/**

+	 * Displays the query entry page.

+	 *

+	 * @param sql Text to prepopulate the SQL query field with.

+	 * @return The HTML div tag to serialize.

+	 */

+	@RestMethod(

+		summary="Display the query entry page"

+	)

+	public Div get(

+			@Query(

+				name="sql",

+				description="Text to prepopulate the SQL query field with.",

+				example="select * from sys.systables"

+			)

+			String sql

+		) {

+

+		return div(

+			script("text/javascript",

+				"// Quick and dirty function to allow tabs in textarea.",

+				"function checkTab(e) {",

+				"	if (e.keyCode == 9) {",

+				"		var t = e.target;",

+				"		var ss = t.selectionStart, se = t.selectionEnd;",

+				"		t.value = t.value.slice(0,ss).concat('\\t').concat(t.value.slice(ss,t.value.length));",

+				"		e.preventDefault();",

+				"	}",

+				"}",

+				"// Load results from IFrame into this document.",

+				"function loadResults(b) {",

+				"	var doc = b.contentDocument || b.contentWindow.document;",

+				"	var data = doc.getElementById('data') || doc.getElementsByTagName('body')[0];",

+				"	document.getElementById('results').innerHTML = data.innerHTML;",

+				"}"

+			),

+			form("servlet:/").method(POST).target("buf").children(

+				table(

+					tr(

+						th("Position (1-10000):").style("white-space:nowrap"),

+						td(input().name("pos").type("number").value(1)),

+						th("Limit (1-10000):").style("white-space:nowrap"),

+						td(input().name("limit").type("number").value(100)),

+						td(button("submit", "Submit"), button("reset", "Reset"))

+					),

+					tr(

+						td().colspan(5).children(

+							textarea().name("sql").text(sql == null ? " " : sql).style("width:100%;height:200px;font-family:Courier;font-size:9pt;").onkeydown("checkTab(event)")

+						)

+					)

+				)

+			),

+			br(),

+			div().id("results"),

+			iframe().name("buf").style("display:none").onload("parent.loadResults(this)")

+		);

+	}

+

+	/**

+	 * Execute one or more queries.

+	 *

+	 * @param in

+	 * 	Query input

+	 * @return

+	 * 	Query results.

+	 * 	<br>Each entry in the array is a result of one query.

+	 * 	<b>Each result can be a result set (for queries) or update count (for updates).

+	 * @throws BadRequest Invalid SQL detected.

+	 */

+	@RestMethod(

+		summary="Execute one or more queries"

+	)

+	@Response(

+		description="Query results.\nEach entry in the array is a result of one query.\nEach result can be a result set (for queries) or update count (for updates)."

+	)

+	public List<Object> post(

+			@Body(

+				description="Query input",

+				example="{sql:'select * from sys.systables',pos:1,limit:100}"

+			)

+			PostInput in

+		) throws BadRequest {

+

+		List<Object> results = new LinkedList<>();

+

+		// Don't try to submit empty input.

+		if (isEmpty(in.sql))

+			return results;

+

+		if (in.pos < 1 || in.pos > 10000)

+			throw new BadRequest("Invalid value for position.  Must be between 1-10000");

+		if (in.limit < 1 || in.limit > 10000)

+			throw new BadRequest("Invalid value for limit.  Must be between 1-10000");

+

+		String sql = null;

+

+		// Create a connection and statement.

+		// If these fais, let the exception filter up as a 500 error.

+		try (Connection c = DriverManager.getConnection(connectionUrl)) {

+			c.setAutoCommit(false);

+			try (Statement st = c.createStatement()) {

+				for (String s : in.sql.split(";")) {

+					sql = s.trim();

+					if (! sql.isEmpty()) {

+						Object o = null;

+						if (allowUpdates || (allowTempUpdates && ! sql.matches("(?:i)commit.*"))) {

+							if (st.execute(sql)) {

+								try (ResultSet rs = st.getResultSet()) {

+									o = new ResultSetList(rs, in.pos, in.limit, includeRowNums);

+								}

+							} else {

+								o = st.getUpdateCount();

+							}

+						} else {

+							try (ResultSet rs = st.executeQuery(sql)) {

+								o = new ResultSetList(rs, in.pos, in.limit, includeRowNums);

+							}

+						}

+						results.add(o);

+					}

+				}

+			}

+			if (allowUpdates)

+				c.commit();

+			else if (allowTempUpdates)

+				c.rollback();

+		} catch (SQLException e) {

+			throw new BadRequest(e, "Invalid query:  {0}", sql);

+		}

+

+		return results;

+	}

+

+	/** The parsed form post */

+	@SuppressWarnings("javadoc")

+	public static class PostInput {

+		public String sql = "";

+		public int pos = 1, limit = 100;

+	}

+}

diff --git a/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/rest/UploadPhotoMenuItem.java b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/rest/UploadPhotoMenuItem.java
new file mode 100644
index 0000000..3847890
--- /dev/null
+++ b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/rest/UploadPhotoMenuItem.java
@@ -0,0 +1,61 @@
+// ***************************************************************************************************************************
+// * 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.petstore.rest;
+
+import static org.apache.juneau.dto.html5.HtmlBuilder.*;
+import static org.apache.juneau.http.HttpMethodName.*;
+
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.widget.*;
+
+/**
+ * Menu item for uploading a Photo.
+ *
+ * <ul class='seealso'>
+ * 	<li class='extlink'>{@source}
+ * </ul>
+ */
+public class UploadPhotoMenuItem extends MenuItemWidget {
+
+	@Override /* MenuItemWidget */
+	public String getLabel(RestRequest req, RestResponse res) throws Exception {
+		return "upload";
+	}
+
+	@Override /* Widget */
+	public Object getContent(RestRequest req, RestResponse res) throws Exception {
+		return div(
+			form().id("form").action("servlet:/upload").method(POST).enctype("multipart/form-data").children(
+				table(
+					tr(
+						th("ID:"),
+						td(input().name("id").type("text")),
+						td(new Tooltip("&#x2753;", "The unique identifier of the photo.", br(), "e.g. 'Fluffy'"))
+					),
+					tr(
+						th("File:"),
+						td(input().name("file").type("file").accept("image/*")),
+						td(new Tooltip("&#x2753;", "The image file."))
+					),
+					tr(
+						td().colspan(2).style("text-align:right").children(
+							button("reset", "Reset"),
+							button("button","Cancel").onclick("window.location.href='/'"),
+							button("submit", "Submit")
+						)
+					)
+				).style("white-space:nowrap")
+			)
+		);
+	}
+}
diff --git a/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/service/AbstractPersistenceService.java b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/service/AbstractPersistenceService.java
new file mode 100644
index 0000000..4e2aedf
--- /dev/null
+++ b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/service/AbstractPersistenceService.java
@@ -0,0 +1,273 @@
+// ***************************************************************************************************************************
+// * 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.petstore.service;
+
+import java.util.*;
+
+import javax.persistence.*;
+
+import org.apache.juneau.pojotools.*;
+
+/**
+ * Superclass for DAOs that use the JPA entity manager.
+ *
+ * <ul class='seealso'>
+ * 	<li class='extlink'>{@source}
+ * </ul>
+ */
+public class AbstractPersistenceService {
+
+	private final EntityManagerFactory entityManagerFactory;
+
+	/**
+	 * Constructor.
+	 */
+	public AbstractPersistenceService() {
+		entityManagerFactory = Persistence.createEntityManagerFactory("test");
+	}
+
+	/**
+	 * Retrieves an entity manager session.
+	 *
+	 * @return The 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 <T> The bean type.
+	 * @param em The entity manager to use to retrieve the beans.
+	 * @param query The JPA query.
+	 * @param t The bean type.
+	 * @param searchArgs Optional search arguments.
+	 * @return The results.
+	 */
+	protected <T> List<T> query(EntityManager em, String query, Class<T> t, SearchArgs searchArgs, PageArgs pageArgs) {
+		TypedQuery<T> q = em.createQuery(query, t);
+		if (pageArgs != null) {
+			q.setMaxResults(pageArgs.getLimit() == 0 ? 100 : pageArgs.getLimit());
+			q.setFirstResult(pageArgs.getPosition());
+		}
+		return em.createQuery(query, t).getResultList();
+	}
+
+	/**
+	 * Same as {@link #query(EntityManager,String,Class,SearchArgs)} but uses a new entity manager.
+	 *
+	 * @param <T> The bean type.
+	 * @param query The JPA query.
+	 * @param t The bean type.
+	 * @param searchArgs Optional search arguments.
+	 * @return The results.
+	 */
+	protected <T> List<T> query(String query, Class<T> t, SearchArgs searchArgs, PageArgs pageArgs) {
+		return query(getEntityManager(), query, t, searchArgs, pageArgs);
+	}
+
+	/**
+	 * 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.
+	 * @return The results.
+	 */
+	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.
+	 * @return The results.
+	 */
+	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-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/service/PetStoreService.java b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/service/PetStoreService.java
new file mode 100644
index 0000000..ac83c4c
--- /dev/null
+++ b/juneau-examples/juneau-examples-petstore/juneau-examples-petstore-server/src/main/java/org/apache/juneau/petstore/service/PetStoreService.java
@@ -0,0 +1,329 @@
+// ***************************************************************************************************************************
+// * 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.petstore.service;
+
+import static java.text.MessageFormat.*;
+
+import java.io.*;
+import java.util.*;
+
+import javax.persistence.*;
+
+import org.apache.juneau.json.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.petstore.dto.*;
+import org.apache.juneau.pojotools.*;
+import org.apache.juneau.pojotools.SearchArgs;
+
+/**
+ * Pet store database application.
+ * <p>
+ * Uses JPA persistence to store and retrieve PetStore DTOs.
+ * JPA beans are defined in <c>META-INF/persistence.xml</c>.
+ *
+ * <ul class='seealso'>
+ * 	<li class='extlink'>{@source}
+ * </ul>
+ */
+public class PetStoreService extends AbstractPersistenceService {
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// Initialization methods.
+	//-----------------------------------------------------------------------------------------------------------------
+
+	/**
+	 * Initialize the petstore database using JPA.
+	 *
+	 * @param w Console output.
+	 * @return This object (for method chaining).
+	 * @throws ParseException Malformed input encountered.
+	 * @throws IOException File could not be read from file system.
+	 */
+	public PetStoreService initDirect(PrintWriter w) throws ParseException, IOException {
+
+		EntityManager em = getEntityManager();
+		EntityTransaction et = em.getTransaction();
+		JsonParser parser = JsonParser.create().build();
+
+		et.begin();
+
+		for (Pet x : em.createQuery("select X from PetstorePet X", Pet.class).getResultList()) {
+			em.remove(x);
+			w.println(format("Deleted pet:  id={0}", x.getId()));
+		}
+		for (Order x : em.createQuery("select X from PetstoreOrder X", Order.class).getResultList()) {
+			em.remove(x);
+			w.println(format("Deleted order:  id={0}", x.getId()));
+		}
+		for (User x : em.createQuery("select X from PetstoreUser X", User.class).getResultList()) {
+			em.remove(x);
+			w.println(format("Deleted user:  username={0}", x.getUsername()));
+		}
+
+		et.commit();
+		et.begin();
+
+		for (Pet x : parser.parse(getStream("init/Pets.json"), Pet[].class)) {
+			x = em.merge(x);
+			w.println(format("Created pet:  id={0}, name={1}", x.getId(), x.getName()));
+		}
+		for (Order x : parser.parse(getStream("init/Orders.json"), Order[].class)) {
+			x = em.merge(x);
+			w.println(format("Created order:  id={0}", x.getId()));
+		}
+		for (User x: parser.parse(getStream("init/Users.json"), User[].class)) {
+			x = em.merge(x);
+			w.println(format("Created user:  username={0}", x.getUsername()));
+		}
+
+		et.commit();
+
+		return this;
+	}
+
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// Service methods.
+	//-----------------------------------------------------------------------------------------------------------------
+
+	/**
+	 * Returns the pet with the specified ID.
+	 *
+	 * @param id The pet ID.
+	 * @return The pet with the specified ID.  Never <jk>null</jk>.
+	 * @throws IdNotFound If pet was not found.
+	 */
+	public Pet getPet(long id) throws IdNotFound {
+		return find(Pet.class, id);
+	}
+
+	/**
+	 * Returns the order with the specified ID.
+	 *
+	 * @param id The order ID.
+	 * @return The order with the specified ID.  Never <jk>null</jk>.
+	 * @throws IdNotFound If order was not found.
+	 */
+	public Order getOrder(long id) throws IdNotFound {
+		return find(Order.class, id);
+	}
+
+	/**
+	 * Returns the user with the specified username.
+	 *
+	 * @param username The username.
+	 * @return The user with the specified username.  Never <jk>null</jk>.
+	 * @throws InvalidUsername Username was not valid.
+	 * @throws IdNotFound If order was not found.
+	 */
+	public User getUser(String username) throws InvalidUsername, IdNotFound  {
+		assertValidUsername(username);
+		return find(User.class, username);
+	}
+
+	/**
+	 * Returns all pets in the database.
+	 *
+	 * @return All pets in the database.
+	 */
+	public List<Pet> getPets() {
+		return query("select X from PetstorePet X", Pet.class, (SearchArgs)null, (PageArgs)null);
+	}
+
+	/**
+	 * Returns all orders in the database.
+	 *
+	 * @return All orders in the database.
+	 */
+	public List<Order> getOrders() {
+		return query("select X from PetstoreOrder X", Order.class, (SearchArgs)null, (PageArgs)null);
+	}
+
+	/**
+	 * Returns all users in the database.
+	 *
+	 * @return All users in the database.
+	 */
+	public List<User> getUsers() {
+		return query("select X from PetstoreUser X", User.class, (SearchArgs)null, (PageArgs)null);
+	}
+
+	/**
+	 * Creates a new pet in the database.
+	 *
+	 * @param c The pet input data.
+	 * @return a new {@link Pet} object.
+	 */
+	public Pet create(CreatePet c) {
+		return merge(new Pet().status(PetStatus.AVAILABLE).apply(c));
+	}
+
+	/**
+	 * Creates a new order in the database.
+	 *
+	 * @param c The order input data.
+	 * @return a new {@link Order} object.
+	 */
+	public Order create(CreateOrder c) {
+		return merge(new Order().status(OrderStatus.PLACED).apply(c));
+	}
+
+	/**
+	 * Creates a new user in the database.
+	 *
+	 * @param c The user input data.
+	 * @return a new {@link User} object.
+	 */
+	public User create(User c) {
+		return merge(new User().apply(c));
+	}
+
+	/**
+	 * Updates a pet in the database.
+	 *
+	 * @param u The update information.
+	 * @return The updated {@link Pet} object.
+	 * @throws IdNotFound Pet was not found.
+	 */
+	public Pet update(UpdatePet u) throws IdNotFound {
+		EntityManager em = getEntityManager();
+		return merge(em, find(em, Pet.class, u.getId()).apply(u));
+	}
+
+	/**
+	 * Updates an order in the database.
+	 *
+	 * @param o The update information.
+	 * @return The updated {@link Order} object.
+	 * @throws IdNotFound Order was not found.
+	 */
+	public Order update(Order o) throws IdNotFound {
+		EntityManager em = getEntityManager();
+		return merge(em, find(em, Order.class, o.getId()).apply(o));
+	}
+
+	/**
+	 * Updates a user in the database.
+	 *
+	 * @param u The update information.
+	 * @return The updated {@link User} object.
+	 * @throws IdNotFound User was not found.
+	 * @throws InvalidUsername The username was not valid.
+	 */
+	public User update(User u) throws IdNotFound, InvalidUsername {
+		assertValidUsername(u.getUsername());
+		EntityManager em = getEntityManager();
+		return merge(em, find(em, User.class, u.getUsername()).apply(u));
+	}
+
+	/**
+	 * Removes a pet from the database.
+	 *
+	 * @param id The pet ID.
+	 * @throws IdNotFound Pet was not found.
+	 */
+	public void removePet(long id) throws IdNotFound {
+		EntityManager em = getEntityManager();
+		remove(em, find(em, Pet.class, id));
+	}
+
+	/**
+	 * Removes an order from the database.
+	 *
+	 * @param id The order ID.
+	 * @throws IdNotFound Order was not found.
+	 */
+	public void removeOrder(long id) throws IdNotFound {
+		EntityManager em = getEntityManager();
+		remove(em, find(em, Order.class, id));
+	}
+
+	/**
+	 * Removes a user from the database.
+	 *
+	 * @param username The username.
+	 * @throws IdNotFound User was not found.
+	 */
+	public void removeUser(String username) throws IdNotFound {
+		EntityManager em = getEntityManager();
+		remove(em, find(em, User.class, username));
+	}
+
+	/**
+	 * Returns all pets with the specified statuses.
+	 *
+	 * @param status Pet statuses.
+	 * @return Pets with the specified statuses.
+	 */
+	public Collection<Pet> getPetsByStatus(PetStatus[] status) {
+		return getEntityManager()
+			.createQuery("select X from PetstorePet X where X.status in :status", Pet.class)
+			.setParameter("status", status)
+			.getResultList();
+	}
+
+	/**
+	 * Returns all pets with the specified tags.
+	 *
+	 * @param tags Pet tags.
+	 * @return Pets with the specified tags.
+	 * @throws InvalidTag Tag name was invalid.
+	 */
+	public Collection<Pet> getPetsByTags(String[] tags) throws InvalidTag {
+		return getEntityManager()
+			.createQuery("select X from PetstorePet X where X.tags in :tags", Pet.class)
+			.setParameter("tags", tags)
+			.getResultList();
+	}
+
+	/**
+	 * Returns a summary of pet statuses and counts.
+	 *
+	 * @return A summary of pet statuses and counts.
+	 */
+	public Map<PetStatus,Integer> getInventory() {
+		Map<PetStatus,Integer> m = new LinkedHashMap<>();
+		for (Pet p : getPets()) {
+			PetStatus ps = p.getStatus();
+			if (! m.containsKey(ps))
+				m.put(ps, 1);
+			else
+				m.put(ps, m.get(ps) + 1);
+		}
+		return m;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if the specified username and password is valid.
+	 *
+	 * @param username The username.
+	 * @param password The password.
+	 * @return <jk>true</jk> if the specified username and password is valid.
+	 */
+	public boolean isValid(String username, String password) {
+		return getUser(username).getPassword().equals(password);
+	}
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// Helper methods
+	//-----------------------------------------------------------------------------------------------------------------
+
+	private void assertValidUsername(String username) throws InvalidUsername {
+		if (username == null || ! username.matches("[\\w\\d]{3,8}"))
+			throw new InvalidUsername();
+	}
+
+	private InputStream getStream(String fileName) {
+		return getClass().getResourceAsStream(fileName);
+	}
+}
\ No newline at end of file