This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/juneau-website.git
The following commit(s) were added to refs/heads/asf-site by this push:
new 78aa278 Petstore documentation.
78aa278 is described below
commit 78aa2782a5925ca283511e1cb168353ddb2d6a7c
Author: JamesBognar <[email protected]>
AuthorDate: Mon Sep 9 20:13:07 2019 -0400
Petstore documentation.
---
content/petstore.html | 951 ++++++++++++++++++++++++++++++++++++++++++++++++
content/petstore/1.png | Bin 0 -> 1878139 bytes
content/petstore/2a.png | Bin 0 -> 20939 bytes
content/petstore/2b.png | Bin 0 -> 21099 bytes
content/petstore/2c.png | Bin 0 -> 18792 bytes
content/petstore/2d.png | Bin 0 -> 29067 bytes
content/petstore/3a.png | Bin 0 -> 196893 bytes
content/petstore/3b.png | Bin 0 -> 376985 bytes
content/petstore/4a.png | Bin 0 -> 274461 bytes
content/petstore/4b.png | Bin 0 -> 165332 bytes
content/petstore/5a.png | Bin 0 -> 195096 bytes
content/petstore/5b.png | Bin 0 -> 20719 bytes
content/petstore/6a.png | Bin 0 -> 172281 bytes
content/petstore/6b.png | Bin 0 -> 185001 bytes
content/petstore/7a.png | Bin 0 -> 157815 bytes
content/petstore/7b.png | Bin 0 -> 166928 bytes
content/petstore/8a.png | Bin 0 -> 199619 bytes
content/petstore/9a.png | Bin 0 -> 221670 bytes
content/petstore/9b.png | Bin 0 -> 146792 bytes
content/petstore/9c.png | Bin 0 -> 226731 bytes
content/petstore/9d.png | Bin 0 -> 234305 bytes
content/petstore/9e.png | Bin 0 -> 110349 bytes
content/petstore/9f.png | Bin 0 -> 185344 bytes
content/petstore/9g.png | Bin 0 -> 140337 bytes
content/petstore/9h.png | Bin 0 -> 116711 bytes
templates/about.html | 18 +
26 files changed, 969 insertions(+)
diff --git a/content/petstore.html b/content/petstore.html
new file mode 100644
index 0000000..c103e4d
--- /dev/null
+++ b/content/petstore.html
@@ -0,0 +1,951 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <!-- Google Tag Manager -->
+ <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
+ new Date().getTime(),event:'gtm.js'});var
f=d.getElementsByTagName(s)[0],
+ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
+
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
+ })(window,document,'script','dataLayer','GTM-MT3PBTF');</script>
+ <!-- End Google Tag Manager -->
+
+ <style>
+ @import url("styles/juneau-code.css");
+ @import url("styles/juneau-doc.css");
+ @import url("../content/styles/juneau-code.css");
+ @import url("../content/styles/juneau-doc.css");
+ </style>
+</head>
+<body>
+ <!-- Google Tag Manager (noscript) -->
+ <noscript><iframe
src="https://www.googletagmanager.com/ns.html?id=GTM-MT3PBTF"
+ height="0" width="0"
style="display:none;visibility:hidden"></iframe></noscript>
+ <!-- End Google Tag Manager (noscript) -->
+
+<p>
+ The <c>PetStore</c> application is an functional application meant to
demonstrate the following:
+</p>
+<ul class='spaced-list'>
+ <li>
+ Creating a Juneau-based REST interface on top of JPA beans.
+ <li>
+ Creating sophisticated Swagger UI using only annotations.
+</ul>
+<p>
+ When you click the <l>petstore</l> link on the home page of the
examples app, you should see this page:
+</p>
+<p class='bpcode w800'>
+ http://localhost:10000/petstore
+</p>
+<img class='bordered w800' src='petstore/1.png'>
+
+<p>
+ The contents of this page is primarily defined via annotations defined
on the <l>PetStoreResource</l> class:
+</p>
+
+<h5 class='figure'>PetStoreResource.java</h5>
+<p class='bpcode w800'>
+ <ja>@RestResource</ja>(
+ path=<js><js>"/petstore"</js></js>,
+ title=<js><js>"Petstore application"</js></js>,
+ description= {
+ <js><js>"This is a sample server Petstore server based
on the Petstore sample at Swagger.io."</js></js>,
+ <js>"You can find out more about Swagger at <a
class='link' href='http://swagger.io'>http://swagger.io</a>."</js>,
+ },
+ htmldoc=<ja>@HtmlDoc</ja>(
+ widgets={
+ ContentTypeMenuItem.<jk>class</jk>,
+ ThemeMenuItem.<jk>class</jk>,
+ },
+ navlinks={
+ <js>"up: request:/.."</js>,
+ <js>"options: servlet:/?method=OPTIONS"</js>,
+ <js>"$W{ContentTypeMenuItem}"</js>,
+ <js>"$W{ThemeMenuItem}"</js>,
+ <js>"source:
$C{Source/gitHub}/org/apache/juneau/examples/rest/petstore/$R{servletClassSimple}.java"</js>
+ },
+ head={
+ <js>"<link rel='icon'
href='$U{servlet:/htdocs/cat.png}'/>"</js> <jc>// Add a cat icon to the
page.</jc>
+ },
+ header={
+
<js>"<h1>$R{resourceTitle}</h1>"</js>,
+
<js>"<h2>$R{methodSummary}</h2>"</js>,
+ <js>"$C{PetStore/headerImage}"</js>
+ },
+ aside={
+ <js>"<div style='max-width:400px'
class='text'>"</js>,
+ <js>" <p>This page shows a standard
nested REST resource.</p>"</js>,
+ <js>" <p>It shows how different
properties can be rendered on the same bean in different views.</p>"</js>,
+ <js>" <p>It also shows examples of
HtmlRender classes and @BeanProperty(format) annotations.</p>"</js>,
+ <js>" <p>It also shows how the
Queryable converter and query widget can be used to create searchable
interfaces.</p>"</js>,
+ <js>"</div>"</js>
+ },
+ stylesheet=<js>"servlet:/htdocs/themes/dark.css"</js>
<jc>// Use dark theme by default.</jc>
+ ),
+ ...
+ staticFiles={<js>"htdocs:htdocs"</js>}, <jc>// Expose static
files in htdocs subpackage.</jc>
+ children={
+ SqlQueryResource.<jk>class</jk>,
+ PhotosResource.<jk>class</jk>
+ }
+ )
+ <jk>public class</jk> PetStoreResource <jk>extends</jk> BasicRestJena {
+</p>
+<p>
+ The inner contents of the page are generated from this method which is
used to define a jumping-off
+ page for the application:
+</p>
+<h5 class='figure'>PetStoreResource.java</h5>
+<p class='bpcode w800'>
+ <ja>@RestMethod</ja>(
+ name=<jsf>GET</jsf>,
+ path=<js>"/"</js>,
+ summary=<js>"Navigation page"</js>,
+ htmldoc=<ja>@HtmlDoc</ja>(
+ style={
+ <js>"INHERIT"</js>, <jc>// Flag for inheriting
resource-level CSS.</jc>
+ <js>"body { "</js>,
+ <js>"background-image:
url('petstore/htdocs/background.jpg'); "</js>,
+ <js>"background-color: black; "</js>,
+ <js>"background-size: cover; "</js>,
+ <js>"background-attachment: fixed;
"</js>,
+ <js>"}"</js>
+ }
+ )
+ )
+ <jk>public</jk> ResourceDescriptions getTopPage() {
+ <jk>return new</jk> ResourceDescriptions()
+ .append(<js>"pet"</js>, <js>"All pets in the
store"</js>)
+ .append(<js>"store"</js>, <js>"Orders and
inventory"</js>)
+ .append(<js>"user"</js>, <js>"Petstore users"</js>)
+ .append(<js>"photos"</js>, <js>"Photos service"</js>)
+ .append(<js>"sql"</js>, <js>"SQL query service"</js>)
+ ;
+ }
+</p>
+<p>
+ Note how we used the <ja>@HtmlDoc</ja> annotation to add some CSS to
our page
+ that renders our background image.
+</p>
+<p>
+ The {@link oajr.helper.ResourceDescriptions} class used above is a
convenience class for creating
+ hyperlinks to child resources.
+</p>
+<hr>
+<p>
+ The application itself is defined in 3 packages:
+</p>
+<ul class='javatree'>
+ <li class='jp'><c>org.apache.juneau.rest.examples.rest.petstore</c>
+ <br>Defines the service for storing and retrieving Petstore
data.
+ <br><img class='bordered' style='width:250px'
src='petstore/2a.png'>
+ <li class='jp'><c>org.apache.juneau.rest.examples.rest.petstore.dto</c>
+ <br>Data transfer objects.
+ <br>These are JPA beans that are used both to store data in our
database and are serialized
+ directly by our REST interface.
+ <br><img class='bordered' style='width:250px'
src='petstore/2b.png'>
+ <li class='jp'><c>org.apache.juneau.rest.examples.rest.petstore.rest</c>
+ <br>The classes used for our REST interface.
+ <br><img class='bordered' style='width:250px'
src='petstore/2c.png'>
+</ul>
+<p>
+ We also define some static files in the
<c>org.apache.juneau.rest.examples.rest.petstore</c> package:
+</p>
+<img class='bordered' style='width:250px' src='petstore/2d.png'>
+<hr>
+<p>
+ The <l>PetStoreService</l> class is a pretty-straightforward service
for storing and retrieving JPA beans:
+</p>
+<h5 class='figure'>PetStoreService.java</h5>
+<p class='bpcode w800'>
+ <jk>public class</jk> PetStoreService <jk>extends</jk>
AbstractPersistenceService {
+
+ <jk>public</jk> Pet getPet(<jk>long</jk> id) <jk>throws</jk>
IdNotFound {
+ <jk>return</jk> find(Pet.<jk>class</jk>, id);
+ }
+
+ <jk>public</jk> List<Pet> getPets() {
+ <jk>return</jk> query(<js>"select X from PetstorePet
X"</js>, Pet.<jk>class</jk>, (SearchArgs)<jk>null</jk>);
+ }
+
+ <jk>public</jk> Pet create(CreatePet c) {
+ <jk>return</jk> merge(new
Pet().status(PetStatus.<jsf>AVAILABLE</jsf>).apply(c));
+ }
+
+ <jk>public</jk> Pet update(UpdatePet u) <jk>throws</jk>
IdNotFound {
+ EntityManager em = getEntityManager();
+ <jk>return</jk> merge(em, find(em, Pet.<jk>class</jk>,
u.getId()).apply(u));
+ }
+
+ <jk>public void</jk> removePet(<jk>long</jk> id)
<jk>throws</jk> IdNotFound {
+ EntityManager em = getEntityManager();
+ remove(em, find(em, Pet.<jk>class</jk>, id));
+ }
+
+ ...
+ }
+</p>
+<p>
+ The DTOs are simply beans that combine both JPA and Juneau bean
annotations:
+</p>
+<h5 class='figure'>Pet.java</h5>
+<p class='bpcode w800'>
+ <ja>@Bean</ja>(typeName=<js>"Pet"</js>, fluentSetters=<jk>true</jk>,
properties=<js>"id,species,name,tags,price,status,photo"</js>)
+ <ja>@Entity</ja>(name=<js>"PetstorePet"</js>)
+ <jk>public class</jk> Pet {
+
+ <ja>@Column @Id @GeneratedValue</ja>
+ <ja>@Schema</ja>(description=<js>"Unique identifier for this
pet."</js>)
+ <ja>@Html</ja>(link=<js>"servlet:/pet/{id}"</js>)
+ <jk>private long</jk> <jf>id</jf>;
+
+ <ja>@Column</ja>(length=50)
+ <ja>@Schema</ja>(description=<js>"Pet name."</js>, minLength=3,
maxLength=50)
+ <jk>private</jk> String <jf>name</jf>;
+
+ ...
+
+ <jk>public long</jk> getId() {
+ <jk>return</jk> <jf>id</jf>;
+ }
+
+ <jk>public</jk> Pet id(<jk>long</jk> id) {
+ <jk>this</jk>.<jf>id</jf> = id;
+ <jk>return this</jk>;
+ }
+
+ <jk>public</jk> String getName() {
+ <jk>return</jk> <jf>name</jf>;
+ }
+
+ <jk>public</jk> Pet name(String name) {
+ <jk>this</jk>.<jf>name</jf> = name;
+ <jk>return this</jk>;
+ }
+
+ ...
+ }
+</p>
+<p>
+ The beans are found by JPA by adding them to the JPA persistence file.
+</p>
+<h5 class='figure'>META-INF/persistence.xml</h5>
+<p class='bpcode w800'>
+ <xt><persistence></xt>
+ <xt><persistence-unit</xt> <xa>name</xa>=<xs>"test"</xs>
<xa>transaction-type</xa>=<xs>"RESOURCE_LOCAL"</xs>></xt>
+
<xt><class></xt>org.apache.juneau.examples.rest.petstore.dto.Pet<xt></class></xt>
+
<xt><class></xt>org.apache.juneau.examples.rest.petstore.dto.Order<xt></class></xt>
+
<xt><class></xt>org.apache.juneau.examples.rest.petstore.dto.User<xt></class></xt>
+ <xt><properties></xt>
+ <xt><property</xt>
<xa>name</xa>=<xs>"javax.persistence.jdbc.driver"</xs>
<xa>value</xa>=<xs>"org.apache.derby.jdbc.EmbeddedDriver"</xs> <xt>/></xt>
+ <xt><property</xt>
<xa>name</xa>=<xs>"javax.persistence.jdbc.url"</xs>
<xa>value</xa>=<xs>"jdbc:derby:target/derby/testDB;create=true"</xs>
<xt>/></xt>
+ <xt><property</xt>
<xa>name</xa>=<xs>"javax.persistence.jdbc.user"</xs> <xa>value</xa>=<xs>""</xs>
<xt>/></xt>
+ <xt><property</xt>
<xa>name</xa>=<xs>"javax.persistence.jdbc.password"</xs>
<xa>value</xa>=<xs>""</xs> <xt>/></xt>
+ <xt><property</xt>
<xa>name</xa>=<xs>"hibernate.dialect"</xs>
<xa>value</xa>=<xs>"org.hibernate.dialect.DerbyDialect"</xs> <xt>/></xt>
+ <xt><property</xt>
<xa>name</xa>=<xs>"hibernate.hbm2ddl.auto"</xs>
<xa>value</xa>=<xs>"create-drop"</xs> <xt>/></xt>
+ <xt><property</xt>
<xa>name</xa>=<xs>"show_sql"</xs> <xa>value</xa>=<xs>"true"</xs> <xt>/></xt>
+ <xt><property</xt>
<xa>name</xa>=<xs>"hibernate.temp.use_jdbc_metadata_defaults"</xs>
<xa>value</xa>=<xs>"false"</xs> <xt>/></xt>
+ <xt></properties></xt>
+ <xt></persistence-unit></xt>
+ <xt></persistence></xt>
+</p>
+<p>
+ The Petstore service is instantiated in our REST interface using a hook.
+ Note that a real-world scenario would likely use some other means such
as injection.
+</p>
+<h5 class='figure'>PetStoreResource.java</h5>
+<p class='bpcode w800'>
+ <jk>public class</jk> PetStoreResource <jk>extends</jk> BasicRestJena
<jk>implements</jk> PetStore {
+
+ <jk>private</jk> PetStoreService <jf>store</jf>;
+
+ <ja>@RestHook</ja>(<jsf>INIT</jsf>)
+ <jk>public void</jk> startup(RestContextBuilder builder)
<jk>throws</jk> Exception {
+ <jf>store</jf> = <jk>new</jk> PetStoreService();
+ }
+
+ ...
+</p>
+<hr>
+<p>
+ The Petstore database is empty by default.
+ The <l>INIT</l> link brings you to a page to initialize the contents of
the database
+ from sample data:
+</p>
+<p class='bpcode w800'>
+ http://localhost:10000/petstore/init
+</p>
+<img class='bordered w800' src='petstore/3a.png'>
+<p>
+ You can try loading the Petstore database using direct JPA or via REST
calls through a client-side proxy.
+</p>
+<img class='bordered w800' src='petstore/3b.png'>
+<p>
+ The initialize page is rendered using the following methods in our
<l>PetStoreResource</l> class.
+ Note that we're using HTML5 beans to render the contents of the page,
and
+ the use of a direct unbuffered writer allowing for the creation
+ of a console-like window in an iframe.
+</p>
+<h5 class='figure'>PetStoreResource.java</h5>
+<p class='bpcode w800'>
+ <ja>@RestMethod</ja>(
+ summary=<js>"Initialize database form entry page"</js>
+ )
+ <jk>public</jk> Div getInit() {
+ <jk>return</jk> <jsm>div</jsm>(
+
<jsm>form</jsm>(<js>"servlet:/init"</js>).method(<jsf>POST</jsf>).target(<js>"buf"</js>).children(
+ <jsm>table</jsm>(
+ <jsm>tr</jsm>(
+ <jsm>th</jsm>(<js>"Initialize
petstore database:"</js>),
+
<jsm>td</jsm>(<jsm>input</jsm>(<js>"radio"</js>).name(<js>"init-method"</js>).value(<js>"direct"</js>).checked(<jk>true</jk>),
<js>"direct"</js>,
<jsm>input</jsm>(<js>"radio"</js>).name(<js>"init-method"</js>).value(<js>"rest"</js>),
<js>"rest"</js>),
+
<jsm>td</jsm>(<jsm>button</jsm>(<js>"submit"</js>,
<js>"Submit"</js>).style(<js>"float:right"</js>).onclick(<js>"scrolling=true"</js>))
+ )
+ )
+ ),
+ <jsm>br</jsm>(),
+
<jsm>iframe</jsm>().id(<js>"buf"</js>).name(<js>"buf"</js>).style(<js>"width:800px;height:600px;"</js>).onload(<js>"window.parent.scrolling=false;"</js>),
+ <jsm>script</jsm>(<js>"text/javascript"</js>,
+ <js>"var scrolling = false;"</js>,
+ <js>"function scroll() { if (scrolling) {
document.getElementById('buf').contentWindow.scrollBy(0,50); }
setTimeout('scroll()',200); } "</js>,
+ <js>"scroll();"</js>
+ )
+ );
+ }
+
+ <ja>@RestMethod</ja>(
+ summary=<js>"Initialize database"</js>
+ )
+ <jk>public void</jk> postInit(
+ <ja>@FormData</ja>(<js>"init-method"</js>) String initMethod,
+ RestResponse res
+ ) <jk>throws</jk> Exception {
+ res.setHeader(<js>"Content-Encoding"</js>, <js>"identity"</js>);
+ <jk>if</jk> (<js>"direct"</js>.equals(initMethod))
+
<jf>store</jf>.initDirect(res.getDirectWriter(<js>"text/plain"</js>));
+ <jk>else</jk>
+
<jf>store</jf>.initViaRest(res.getDirectWriter(<js>"text/plain"</js>));
+ }
+</p>
+<p>
+ The direct initialization uses direct JPA to store beans in the
database.
+ The following code in <l>PetStoreService</l> reads and parses DTOs from
our init JSON files (e.g. <js>"init/Pets.json"</js>)
+ and stores them using the JPA <l>EntityManager</l> class.
+</p>
+<h5 class='figure'>PetStoreService.java</h5>
+<p class='bpcode w800'>
+ <jk>public</jk> PetStoreService initDirect(PrintWriter w)
<jk>throws</jk> Exception {
+
+ EntityManager em = getEntityManager();
+ EntityTransaction et = em.getTransaction();
+ JsonParser parser = JsonParser.<jsm>create</jsm>().build();
+
+ et.begin();
+
+ <jk>for</jk> (Pet x : em.createQuery(<js>"select X from
PetstorePet X"</js>, Pet.<jk>class</jk>).getResultList()) {
+ em.remove(x);
+ w.println(<jsm>format</jsm>(<js>"Deleted pet:
id={0}"</js>, x.getId()));
+ }
+ ...
+
+ et.commit();
+ et.begin();
+
+ <jk>for</jk> (Pet x :
parser.parse(getStream(<js>"init/Pets.json"</js>), Pet[].<jk>class</jk>)) {
+ x = em.merge(x);
+ w.println(<jsm>format</jsm>(<js>"Created pet: id={0},
name={1}"</js>, x.getId(), x.getName()));
+ }
+ ...
+
+ et.commit();
+
+ <jk>return this</jk>;
+ }
+
+ <jk>private</jk> InputStream getStream(String fileName) {
+ <jk>return</jk> getClass().getResourceAsStream(fileName);
+ }
+</p>
+<p>
+ The REST initialization uses a REST proxy interface to delete and store
values in the database:
+
+</p>
+<h5 class='figure'>PetStoreService.java</h5>
+<p class='bpcode w800'>
+ <jk>public</jk> PetStoreService initViaRest(PrintWriter w)
<jk>throws</jk> Exception {
+ JsonParser parser =
JsonParser.<jsm>create</jsm>().ignoreUnknownBeanProperties().build();
+
+ <jk>try</jk> (RestClient rc =
RestClient.<jsm>create</jsm>().json().rootUrl(<js>"http://localhost:10000"</js>).build())
{
+ PetStore ps =
rc.getRemoteResource(PetStore.<jk>class</jk>);
+
+ <jk>for</jk> (Pet x : ps.getPets()) {
+ ps.deletePet(<js>"apiKey"</js>, x.getId());
+ w.println(<jsm>format</jsm>(<js>"Deleted pet:
id={0}"</js>, x.getId()));
+ }
+ ...
+ <jk>for</jk> (CreatePet x :
parser.parse(getStream(<js>"init/Pets.json"</js>), CreatePet[].<jk>class</jk>))
{
+ <jk>long</jk> id = ps.postPet(x);
+ w.println(<jsm>format</jsm>(<js>"Created pet:
id={0}, name={1}"</js>, id, x.getName()));
+ }
+ ...
+ }
+
+ <jk>return this</jk>;
+ }
+</p>
+<p>
+ The <l>PetStore</l> class is an interface annotated with
<ja>@RemoteResource</ja> and <ja>@RemoteMethod</ja>
+ annotations defining how to communicate with our REST interface:
+</p>
+<h5 class='figure'>PetStore.java</h5>
+<p class='bpcode w800'>
+ <ja>@RemoteResource</ja>(path=<js>"/petstore"</js>)
+ <jk>public interface</jk> PetStore {
+
+ <ja>@RemoteMethod</ja>(method=<jsf>GET</jsf>,
path=<js>"/pet"</js>)
+ <jk>public</jk> Collection<Pet> getPets() <jk>throws</jk>
NotAcceptable;
+
+ <ja>@RemoteMethod</ja>(path=<js>"/pet/{petId}"</js>) <jc>/*
method inferred from method name */</jc>
+ <jk>public</jk> Pet getPet(
+ <ja>@Path</ja>(
+ name=<js>"petId"</js>,
+ description=<js>"ID of pet to return"</js>,
+ example=<js>"123"</js>
+ )
+ <jk>long</jk> petId
+ ) <jk>throws</jk> IdNotFound, NotAcceptable;
+
+ <ja>@RemoteMethod</ja> <jc>/* method and path inferred from
method name */</jc>
+ <jk>public long</jk> postPet(
+ <ja>@Body</ja>(
+ description=<js>"Pet object to add to the
store"</js>
+ ) CreatePet pet
+ ) <jk>throws</jk> IdConflict, NotAcceptable,
UnsupportedMediaType;
+
+ <ja>@RemoteMethod</ja>(method=<jsf>PUT</jsf>,
path=<js>"/pet/{petId}"</js>)
+ <jk>public</jk> Ok updatePet(
+ <ja>@Body</ja>(
+ description=<js>"Pet object that needs to be
added to the store"</js>
+ ) UpdatePet pet
+ ) <jk>throws</jk> IdNotFound, NotAcceptable,
UnsupportedMediaType;
+
+ <ja>@RemoteMethod</ja>(method=<jsf>DELETE</jsf>,
path=<js>"/pet/{petId}"</js>)
+ <jk>public</jk> Ok deletePet(
+ <ja>@Header</ja>(
+ name=<js>"api_key"</js>,
+ description=<js>"Security API key"</js>,
+ required=<jk>true</jk>,
+ example=<js>"foobar"</js>
+ )
+ String apiKey,
+ <ja>@Path</ja>(
+ name=<js>"petId"</js>,
+ description=<js>"Pet id to delete"</js>,
+ example=<js>"123"</js>
+ )
+ <jk>long</jk> petId
+ ) <jk>throws</jk> IdNotFound, NotAcceptable;
+
+ ...
+ }
+</p>
+<p>
+ Note that this is the same interface used to define our server-side
REST implementation!
+ The annotations defined on the method parameters used for client-side
proxies are also
+ inherited by and used for our server-side implementation class.
+</p>
+<h5 class='figure'>PetStoreResource.java</h5>
+<p class='bpcode w800'>
+ <jk>public class</jk> PetStoreResource <jk>extends</jk> BasicRestJena
<jk>implements</jk> PetStore {
+
+ <ja>@Override</ja> <jc>/* PetStore */</jc>
+ <ja>@RestMethod</ja>(
+ name=<jsf>GET</jsf>,
+ path=<js>"/pet"</js>,
+ summary=<js>"All pets in the store"</js>,
+ swagger=<ja>@MethodSwagger</ja>(
+ tags=<js>"pet"</js>,
+ parameters={
+ Queryable.<jsf>SWAGGER_PARAMS</jsf>
+ }
+ ),
+ bpx=<js>"Pet: tags,photo"</js>,
+ htmldoc=<ja>@HtmlDoc</ja>(
+ widgets={
+ QueryMenuItem.<jk>class</jk>,
+ AddPetMenuItem.<jk>class</jk>
+ },
+ navlinks={
+ <js>"INHERIT"</js>,
<jc>// Inherit links from class.</jc>
+ <js>"[2]:$W{QueryMenuItem}"</js>,
<jc>// Insert QUERY link in position 2.</jc>
+ <js>"[3]:$W{AddPetMenuItem}"</js>
<jc>// Insert ADD link in position 3.</jc>
+ }
+ ),
+ converters={Queryable.<jk>class</jk>}
+ )
+ <jk>public</jk> Collection<Pet> getPets() <jk>throws</jk>
NotAcceptable {
+ <jk>return</jk> <jf>store</jf>.getPets();
+ }
+
+ <ja>@Override</ja> <jc>/* PetStore */</jc>
+ <ja>@RestMethod</ja>(
+ name=<jsf>GET</jsf>,
+ path=<js>"/pet/{petId}"</js>,
+ summary=<js>"Find pet by ID"</js>,
+ description=<js>"Returns a single pet"</js>,
+ swagger=<ja>@MethodSwagger</ja>(
+ tags=<js>"pet"</js>,
+ value={
+ <js>"security:[ { api_key:[] } ]"</js>
+ }
+ )
+ )
+ <jk>public</jk> Pet getPet(<jk>long</jk> petId) <jk>throws</jk>
IdNotFound, NotAcceptable {
+ <jk>return</jk> <jf>store</jf>.getPet(petId);
+ }
+
+ <ja>@Override</ja> <jc>/* PetStore */</jc>
+ <ja>@RestMethod</ja>(
+ summary=<js>"Add a new pet to the store"</js>,
+ swagger=<ja>@MethodSwagger</ja>(
+ tags=<js>"pet"</js>,
+ value={
+ <js>"security:[ {
petstore_auth:['write:pets','read:pets'] } ]"</js>
+ }
+ )
+ )
+ <jk>public long</jk> postPet(CreatePet pet) <jk>throws</jk>
IdConflict, NotAcceptable, UnsupportedMediaType {
+ <jk>return</jk> <jf>store</jf>.create(pet).getId();
+ }
+
+ <ja>@Override</ja> <jc>/* PetStore */</jc>
+ <ja>@RestMethod</ja>(
+ name=<jsf>PUT</jsf>,
+ path=<js>"/pet/{petId}"</js>,
+ summary=<js>"Update an existing pet"</js>,
+ swagger=<ja>@MethodSwagger</ja>(
+ tags=<js>"pet"</js>,
+ value={
+ <js>"security:[ { petstore_auth:
['write:pets','read:pets'] } ]"</js>
+ }
+ )
+ )
+ <jk>public</jk> Ok updatePet(UpdatePet pet) <jk>throws</jk>
IdNotFound, NotAcceptable, UnsupportedMediaType {
+ <jf>store</jf>.update(pet);
+ <jk>return</jk> <jsf>OK</jsf>;
+ }
+
+ <ja>@Override</ja> <jc>/* PetStore */</jc>
+ <ja>@RestMethod</ja>(
+ name=<jsf>DELETE</jsf>,
+ path=<js>"/pet/{petId}"</js>,
+ summary=<js>"Deletes a pet"</js>,
+ swagger=<ja>@MethodSwagger</ja>(
+ tags=<js>"pet"</js>,
+ value={
+ <js>"security:[ { petstore_auth:[
'write:pets','read:pets' ] } ]"</js>
+ }
+ )
+ )
+ <jk>public</jk> Ok deletePet(String apiKey, <jk>long</jk>
petId) <jk>throws</jk> IdNotFound, NotAcceptable {
+ <jf>store</jf>.removePet(petId);
+ <jk>return</jk> <jsf>OK</jsf>;
+ }
+ ...
+ }
+</p>
+<p>
+ The advantage to using a common interface for both your server-side and
client-side APIs is that you
+ have less of a chance of a mismatch between the server and client side
definitions.
+</p>
+<hr>
+<p>
+ Now that we've initialized the contents of our database, we can start
exploring the REST interface.
+ We can start by click the <l>pet</l> link on the home page which takes
you to a summary page of <l>Pet</l>
+ objects:
+</p>
+<p class='bpcode w800'>
+ http://localhost:10000/petstore/pet
+</p>
+<img class='bordered w800' src='petstore/4a.png'>
+
+<p>
+ Clicking on one of the ID links takes you to a details page:
+</p>
+<p class='bpcode w800'>
+ http://localhost:10000/petstore/pet/1
+</p>
+<img class='bordered w800' src='petstore/4b.png'>
+<p>
+ You'll notice the details page shows <l>tags</l> and <l>photo</l>
fields not shown on the summary page.
+ This was accomplished with the <c>bpx=<js>"Pet: tags,photo"</js></c>
annotation on the <l>getPets()</l>
+ method which excludes those two properties from the view.
+ This is a common way of defining summary and details views for POJOs.
+</p>
+<p>
+ The hyperlinks and special rendering for <l>Pet</l> objects is done
through <ja>@Html</ja> annotations
+ and {@link oaj.html.HtmlRender} objects.
+</p>
+<h5 class='figure'>Pet.java</h5>
+<p class='bpcode w800'>
+ <jk>public class</jk> Pet {
+
+ <ja>@Html</ja>(link=<js>"servlet:/pet/{id}"</js>)
+ <jk>private long</jk> <jf>id</jf>;
+
+ <ja>@Html</ja>(render=PriceRender.<jk>class</jk>)
+ <jk>private float</jk> <jf>price</jf>;
+
+ <jk>private</jk> Species <jf>species</jf>;
+
+ <jk>private</jk> PetStatus <jf>status</jf>;
+
+ ...
+
+ <jk>public static final class</jk> PriceRender <jk>extends</jk>
HtmlRender<Float> {
+ <ja>@Override</ja> <jc>/* HtmlRender */</jc>
+ <jk>public</jk> Object getContent(SerializerSession
session, Float value) {
+ <jk>return</jk> value == <jk>null</jk> ?
<jk>null</jk> : String.<jsm>format</jsm>(<js>"$%.2f"</js>, value);
+ }
+ }
+
+ ...
+</p>
+
+<h5 class='figure'>Species.java</h5>
+<p class='bpcode w800'>
+ <ja>@Html</ja>(render=Species.SpeciesRender.<jk>class</jk>)
+ <jk>public enum</jk> Species {
+
+ <jsf>BIRD</jsf>, <jsf>CAT</jsf>, <jsf>DOG</jsf>,
<jsf>FISH</jsf>, <jsf>MOUSE</jsf>, <jsf>RABBIT</jsf>, <jsf>SNAKE</jsf>;
+
+ <jk>public static class</jk> SpeciesRender <jk>extends</jk>
HtmlRender<Species> {
+ <ja>@Override</ja> <jc>/* HtmlRender */</jc>
+ <jk>public</jk> Object getContent(SerializerSession
session, Species value) {
+ <jk>return new</jk>
Img().src(<js>"servlet:/htdocs/"</js>+value.name().toLowerCase()+<js>".png"</js>);
+ }
+ <ja>@Override</ja> <jc>/* HtmlRender */</jc>
+ <jk>public</jk> String getStyle(SerializerSession
session, Species value) {
+ <jk>return</jk>
<js>"background-color:#FDF2E9"</js>;
+ }
+ }
+ }
+</p>
+
+<h5 class='figure'>PetStatus.java</h5>
+<p class='bpcode w800'>
+ <ja>@Html</ja>(render=PetStatus.PetStatusRender.<jk>class</jk>)
+ <jk>public enum</jk> PetStatus {
+
+ <jsf>AVAILABLE</jsf>, <jsf>PENDING</jsf>, <jsf>SOLD</jsf>,
<jsf>UNKNOWN</jsf>;
+
+ <jk>public static class</jk> PetStatusRender <jk>extends</jk>
HtmlRender<PetStatus> {
+ <ja>@Override</ja> <jc>/* HtmlRender */</jc>
+ <jk>public</jk> String getStyle(SerializerSession
session, PetStatus value) {
+ <jk>switch</jk>(value) {
+ <jk>case</jk> <jsf>AVAILABLE</jsf>:
<jk>return</jk>
<js>"background-color:#5cb85c;text-align:center;vertical-align:middle;"</js>;
+ <jk>case</jk> <jsf>PENDING</jsf>:
<jk>return</jk>
<js>"background-color:#f0ad4e;text-align:center;vertical-align:middle;"</js>;
+ <jk>case</jk> <jsf>SOLD</jsf>:
<jk>return</jk>
<js>"background-color:#888;text-align:center;vertical-align:middle;"</js>;
+ <jk>default</jk>: <jk>return</jk>
<js>"background-color:#777;text-align:center;vertical-align:middle;"</js>;
+ }
+ }
+ }
+ }
+</p>
+<p>
+ The <l>CONTENT-TYPE</l> menu items gives a shorthand way of showing our
POJOs in any of the supported
+ serialization languages:
+</p>
+<p class='bpcode w800'>
+ http://localhost:10000/petstore/pet/1
+</p>
+<img class='bordered w800' src='petstore/5a.png'>
+<p>
+ For example, selecting <l>APPLICATION/JSON+SIMPLE</l> shows us
simplified JSON:
+</p>
+<p class='bpcode w800'>
+
http://localhost:10000/petstore/pet?plainText=true&Accept=application%2Fjson%2Bsimple
+</p>
+<img class='bordered w800' src='petstore/5b.png'>
+<p>
+ Note that we're using the convenience feature for specifying an
<c>Accept</c> header via a query parameter.
+</p>
+<p>
+ The <l>THEME</l> menu items allows you to quickly change the stylesheet
used on the page:
+</p>
+<p class='bpcode w800'>
+ http://localhost:10000/petstore/pet/1
+</p>
+<img class='bordered w800' src='petstore/6a.png'>
+<p>
+ For example, selecting <l>LIGHT</l> shows us the page rendered using
the light look-and-feel:
+</p>
+<p class='bpcode w800'>
+
http://localhost:10000/petstore/pet?stylesheet=htdocs%2Fthemes%2Flight.css
+</p>
+<img class='bordered w800' src='petstore/6b.png'>
+<p>
+ Both the <l>CONTENT-TYPE</l> and <l>THEMES</l> menu items are
implemented as widgets and
+ associated with the page contents through the use of <l>$W</l>
variables in the navigation links:
+</p>
+<h5 class='figure'>PetStoreResource.java</h5>
+<p class='bpcode w800'>
+ <ja>@RestResource</ja>(
+ htmldoc=<ja>@HtmlDoc</ja>(
+ widgets={
+ ContentTypeMenuItem.<jk>class</jk>,
+ ThemeMenuItem.<jk>class</jk>,
+ },
+ navlinks={
+ <js>"up: request:/.."</js>,
+ <js>"options: servlet:/?method=OPTIONS"</js>,
+ <js>"init: servlet:/init"</js>,
+ <js>"$W{ContentTypeMenuItem}"</js>,
+ <js>"$W{ThemeMenuItem}"</js>,
+ <js>"source:
$C{Source/gitHub}/org/apache/juneau/examples/rest/petstore/$R{servletClassSimple}.java"</js>
+ },
+ ...
+ ),
+ ...
+</p>
+<p>
+ The implementation of a menu item contains methods for retrieving the
label and HTML5 content of the menu item.
+</p>
+<h5 class='figure'>ContentTypeMenuItem.java</h5>
+<p class='bpcode w800'>
+ <jk>public class</jk> ContentTypeMenuItem <jk>extends</jk>
MenuItemWidget {
+
+ <ja>@Override</ja> <jc>/* MenuItemWidget */</jc>
+ <jk>public</jk> String getLabel(RestRequest req) {
+ <jk>return</jk> <js>"content-type"</js>;
+ }
+
+ <ja>@Override</ja> <jc>/* MenuItemWidget */</jc>
+ <jk>public</jk> Div getContent(RestRequest req) {
+ Div div = <jsm>div</jsm>();
+ Set<MediaType> l = <jk>new</jk> TreeSet<>();
+ <jk>for</jk> (Serializer s :
req.getSerializers().getSerializers())
+ l.add(s.getPrimaryMediaType());
+ <jk>for</jk> (MediaType mt : l) {
+ URI uri = req.getUri(<jk>true</jk>,
<jk>new</jk>
AMap<String,String>().append(<js>"plainText"</js>,<js>"true"</js>).append(<js>"Accept"</js>,mt.toString()));
+ div.children(<jsm>a</jsm>(uri, mt),
<jsm>br</jsm>());
+ }
+ <jk>return</jk> div;
+ }
+ }
+</p>
+
+<h5 class='figure'>ThemeMenuItem.java</h5>
+<p class='bpcode w800'>
+ <jk>public class</jk> ThemeMenuItem <jk>extends</jk> MenuItemWidget {
+
+ <jk>private static final</jk> String[]
<jsf>BUILT_IN_STYLES</jsf> = {<js>"devops"</js>, <js>"light"</js>,
<js>"original"</js>, <js>"dark"</js>};
+
+ <ja>@Override</ja> <jc>/* Widget */</jc>
+ <jk>public</jk> String getLabel(RestRequest req) {
+ <jk>return</jk> "themes";
+ }
+
+ <ja>@Override</ja> <jc>/* MenuItemWidget */</jc>
+ <jk>public</jk> Div getContent(RestRequest req) <jk>throws</jk>
Exception {
+ Div div = <jsm>div</jsm>();
+ <jk>for</jk> (String s : <jsf>BUILT_IN_STYLES</jsf>) {
+ java.net.URI uri = req.getUri(<jk>true</jk>,
<jk>new</jk> AMap<String,String>().append(<js>"stylesheet"</js>,
<js>"htdocs/themes/"</js>+s+<js>".css"</js>));
+ div.children(<jsm>a</jsm>(uri, s),
<jsm>br</jsm>());
+ }
+ <jk>return</jk> div;
+ }
+ }
+</p>
+<p>
+ The <l>QUERY</l> menu item shows off the capabilities of the {@link
oajr.converters.Queryable} converter.
+</p>
+
+<p class='bpcode w800'>
+ http://localhost:10000/petstore/pet
+</p>
+<img class='bordered w800' src='petstore/7a.png'>
+<p>
+ The converter will take the POJOs to be serialized and filter them
based on the provided query/view/sort/paging attributes:
+</p>
+<p class='bpcode w800'>
+
http://localhost:10000/petstore/pet?s=name%3DHoppy*&v=species%2Cname&o=name&p=0&l=20
+</p>
+<img class='bordered w800' src='petstore/7b.png'>
+<p>
+ The <l>ADD</l> menu item is a custom menu item created for the petstore
app for adding pets through the
+ web interface.
+</p>
+
+<p class='bpcode w800'>
+ http://localhost:10000/petstore/pet
+</p>
+<img class='bordered w800' src='petstore/8a.png'>
+
+<p>
+ Both the <l>QUERY</l> and <l>ADD</l> menu items are only applicable for
this page, and so are defined
+ on the <l>getPets()</l> method:
+</p>
+
+<h5 class='figure'>PetStoreResource.java</h5>
+<p class='bpcode w800'>
+ <ja>@Override</ja> <jc>/* PetStore */</jc>
+ <ja>@RestMethod</ja>(
+ name=<jsf>GET</jsf>,
+ path=<js>"/pet"</js>,
+ summary=<js>"All pets in the store"</js>,
+ swagger=<ja>@MethodSwagger</ja>(
+ tags=<js>"pet"</js>,
+ parameters={
+ Queryable.<jsf>SWAGGER_PARAMS</jsf>
+ }
+ ),
+ bpx=<js>"Pet: tags,photo"</js>,
+ htmldoc=<ja>@HtmlDoc</ja>(
+ widgets={
+ QueryMenuItem.<jk>class</jk>,
+ AddPetMenuItem.<jk>class</jk>
+ },
+ navlinks={
+ <js>"INHERIT"</js>, <jc>//
Inherit links from class.</jc>
+ <js>"[2]:$W{QueryMenuItem}"</js>, <jc>//
Insert QUERY link in position 2.</jc>
+ <js>"[3]:$W{AddPetMenuItem}"</js> <jc>//
Insert ADD link in position 3.</jc>
+ }
+ ),
+ converters={Queryable.<jk>class</jk>}
+ )
+ <jk>public</jk> Collection<Pet> getPets() <jk>throws</jk>
NotAcceptable {
+ <jk>return</jk> <jf>store</jf>.getPets();
+ }
+</p>
+
+<h5 class='figure'>QueryMenuItem.java</h5>
+<p class='bpcode w800'>
+ <jk>public class</jk> QueryMenuItem <jk>extends</jk> MenuItemWidget {
+
+ <ja>@Override</ja> <jc>/* Widget */</jc>
+ <jk>public</jk> String getStyle(RestRequest req)
<jk>throws</jk> Exception {
+ <jk>return super</jk>.getStyle(req)
+ + <js>"\n"</js>
+ +
<jsm>loadStyle</jsm>(<js>"QueryMenuItem.css"</js>);
+ }
+
+ <ja>@Override</ja> <jc>/* MenuItemWidget */</jc>
+ <jk>public</jk> String getLabel(RestRequest req)
<jk>throws</jk> Exception {
+ <jk>return</jk> <js>"query"</js>;
+ }
+
+ <ja>@Override</ja> <jc>/* MenuItemWidget */</jc>
+ <jk>public</jk> String getContent(RestRequest req)
<jk>throws</jk> Exception {
+ <jk>return</jk>
<jsm>loadHtml</jsm>(<js>"QueryMenuItem.html"</js>);
+ }
+ }
+</p>
+
+<h5 class='figure'>AddPetMenuItem.java</h5>
+<p class='bpcode w800'>
+ <jk>public class</jk> AddPetMenuItem <jk>extends</jk> MenuItemWidget {
+
+ <ja>@Override</ja> <jc>/* MenuItemWidget */</jc>
+ <jk>public</jk> String getLabel(RestRequest req)
<jk>throws</jk> Exception {
+ <jk>return</jk> <js>"add"</js>;
+ }
+
+ <ja>@Override</ja> <jc>/* Widget */</jc>
+ <jk>public</jk> Object getContent(RestRequest req)
<jk>throws</jk> Exception {
+ <jk>return</jk> <jsm>div</jsm>(
+
<jsm>form</jsm>().id(<js>"form"</js>).action(<js>"servlet:/pet"</js>).method(<jsf>POST</jsf>).children(
+ <jsm>table</jsm>(
+ <jsm>tr</jsm>(
+
<jsm>th</jsm>(<js>"Name:"</js>),
+
<jsm>td</jsm>(<jsm>input</jsm>().name(<js>"name"</js>).type(<js>"text"</js>)),
+
<jsm>td</jsm>(<jk>new</jk> Tooltip(<js>"&#x2753;"</js>, <js>"The name of
the pet."</js>, <jsm>br</jsm>(), <js>"e.g. 'Fluffy'"</js>))
+ ),
+ <jsm>tr</jsm>(
+
<jsm>th</jsm>(<js>"Species:"</js>),
+ <jsm>td</jsm>(
+
<jsm>select</jsm>().name(<js>"species"</js>).children(
+
<jsm>option</jsm>(<js>"CAT"</js>), <jsm>option</jsm>(<js>"DOG"</js>),
<jsm>option</jsm>(<js>"BIRD"</js>), <jsm>option</jsm>(<js>"FISH"</js>),
<jsm>option</jsm>(<js>"MOUSE"</js>), <jsm>option</jsm>(<js>"RABBIT"</js>),
<jsm>option</jsm>(<js>"SNAKE"</js>)
+ )
+ ),
+
<jsm>td</jsm>(<jk>new</jk> Tooltip(<js>"&#x2753;"</js>, <js>"The kind of
animal."</js>))
+ ),
+ <jsm>tr</jsm>(
+
<jsm>th</jsm>(<js>"Price:"</js>),
+
<jsm>td</jsm>(<jsm>input</jsm>().name(<js>"price"</js>).type(<js>"number"</js>).placeholder(<js>"1.0"</js>).step(<js>"0.01"</js>).min(1).max(100).value(9.99)),
+
<jsm>td</jsm>(<jk>new</jk> Tooltip(<js>"&#x2753;"</js>, <js>"The price to
charge for this pet."</js>))
+ ),
+ <jsm>tr</jsm>(
+
<jsm>th</jsm>(<js>"Tags:"</js>),
+
<jsm>td</jsm>(<jsm>input</jsm>().name(<js>"tags"</js>).type(<js>"text"</js>)),
+
<jsm>td</jsm>(<jk>new</jk> Tooltip(<js>"&#x2753;"</js>, <js>"Arbitrary
textual tags (comma-delimited)."</js>, <jsm>br</jsm>(), <js>"e.g.
'fluffy,friendly'"</js>))
+ ),
+ <jsm>tr</jsm>(
+
<jsm>td</jsm>().colspan(2).style(<js>"text-align:right"</js>).children(
+
<jsm>button</jsm>(<js>"reset"</js>, <js>"Reset"</js>),
+
<jsm>button</jsm>(<js>"button"</js>,<js>"Cancel"</js>).onclick(<js>"window.location.href='/'"</js>),
+
<jsm>button</jsm>(<js>"submit"</js>, <js>"Submit"</js>)
+ )
+ )
+ ).style(<js>"white-space:nowrap"</js>)
+ )
+ );
+ }
+ }
+</p>
+<hr>
+<p>
+ The <l>OPTIONS</l> menu items takes you to the auto-generated Swagger
UI for the application:
+</p>
+<p class='bpcode w900'>
+ http://localhost:10000/petstore/pet?method=OPTIONS
+</p>
+<img class='bordered w900' src='petstore/9a.png'>
+<p>
+ Since we've defined tags on our annotations, the pet-related operations
are all grouped under the <l>pet</l> tag:
+</p>
+<img class='bordered w900' src='petstore/9b.png'>
+<p>
+ Information for all HTTP parts is automatically generated:
+</p>
+<img class='bordered w900' src='petstore/9h.png'>
+<p>
+ The schema models for POJO models is available in the <l>Responses</l>
section of an operation:
+</p>
+<img class='bordered w900' src='petstore/9c.png'>
+<p>
+ Auto-generated examples are available for all supported languages:
+</p>
+<img class='bordered w900' src='petstore/9d.png'>
+<p>
+ For example, <l>application/json+simple</l>:
+</p>
+<img class='bordered w900' src='petstore/9e.png'>
+<p>
+ Examples can be derived in a number of ways. In our case, we've
defined a static method on our <l>Pet</l>
+ class annotated with <ja>@Example</ja>:
+</p>
+<h5 class='figure'>Pet.java</h5>
+<p class='bpcode w800'>
+ <ja>@Example</ja>
+ <jk>public static</jk> Pet example() {
+ <jk>return new</jk> Pet()
+ .id(123)
+ .species(Species.<jsf>DOG</jsf>)
+ .name(<js>"Doggie"</js>)
+ .tags(<js>"friendly"</js>,<js>"smart"</js>)
+ .status(PetStatus.<jsf>AVAILABLE</jsf>);
+ }
+</p>
+<p>
+ Similar functionality exists for request bodies as well:
+</p>
+<img class='bordered w900' src='petstore/9f.png'>
+<p>
+ At the bottom of the page is a listing of the POJO models in the app:
+</p>
+<img class='bordered w900' src='petstore/9g.png'>
+
+</body>
+
+
+
diff --git a/content/petstore/1.png b/content/petstore/1.png
new file mode 100644
index 0000000..fc65f06
Binary files /dev/null and b/content/petstore/1.png differ
diff --git a/content/petstore/2a.png b/content/petstore/2a.png
new file mode 100644
index 0000000..9515c5c
Binary files /dev/null and b/content/petstore/2a.png differ
diff --git a/content/petstore/2b.png b/content/petstore/2b.png
new file mode 100644
index 0000000..2afcbcf
Binary files /dev/null and b/content/petstore/2b.png differ
diff --git a/content/petstore/2c.png b/content/petstore/2c.png
new file mode 100644
index 0000000..7e27708
Binary files /dev/null and b/content/petstore/2c.png differ
diff --git a/content/petstore/2d.png b/content/petstore/2d.png
new file mode 100644
index 0000000..014fcf6
Binary files /dev/null and b/content/petstore/2d.png differ
diff --git a/content/petstore/3a.png b/content/petstore/3a.png
new file mode 100644
index 0000000..ae029d8
Binary files /dev/null and b/content/petstore/3a.png differ
diff --git a/content/petstore/3b.png b/content/petstore/3b.png
new file mode 100644
index 0000000..9193b1d
Binary files /dev/null and b/content/petstore/3b.png differ
diff --git a/content/petstore/4a.png b/content/petstore/4a.png
new file mode 100644
index 0000000..2f47259
Binary files /dev/null and b/content/petstore/4a.png differ
diff --git a/content/petstore/4b.png b/content/petstore/4b.png
new file mode 100644
index 0000000..5a91391
Binary files /dev/null and b/content/petstore/4b.png differ
diff --git a/content/petstore/5a.png b/content/petstore/5a.png
new file mode 100644
index 0000000..5c88c5a
Binary files /dev/null and b/content/petstore/5a.png differ
diff --git a/content/petstore/5b.png b/content/petstore/5b.png
new file mode 100644
index 0000000..46cd848
Binary files /dev/null and b/content/petstore/5b.png differ
diff --git a/content/petstore/6a.png b/content/petstore/6a.png
new file mode 100644
index 0000000..ddbee32
Binary files /dev/null and b/content/petstore/6a.png differ
diff --git a/content/petstore/6b.png b/content/petstore/6b.png
new file mode 100644
index 0000000..9c04552
Binary files /dev/null and b/content/petstore/6b.png differ
diff --git a/content/petstore/7a.png b/content/petstore/7a.png
new file mode 100644
index 0000000..ffd41ce
Binary files /dev/null and b/content/petstore/7a.png differ
diff --git a/content/petstore/7b.png b/content/petstore/7b.png
new file mode 100644
index 0000000..e65d56c
Binary files /dev/null and b/content/petstore/7b.png differ
diff --git a/content/petstore/8a.png b/content/petstore/8a.png
new file mode 100644
index 0000000..3cc3f6f
Binary files /dev/null and b/content/petstore/8a.png differ
diff --git a/content/petstore/9a.png b/content/petstore/9a.png
new file mode 100644
index 0000000..33a1791
Binary files /dev/null and b/content/petstore/9a.png differ
diff --git a/content/petstore/9b.png b/content/petstore/9b.png
new file mode 100644
index 0000000..85720f2
Binary files /dev/null and b/content/petstore/9b.png differ
diff --git a/content/petstore/9c.png b/content/petstore/9c.png
new file mode 100644
index 0000000..23a3e7f
Binary files /dev/null and b/content/petstore/9c.png differ
diff --git a/content/petstore/9d.png b/content/petstore/9d.png
new file mode 100644
index 0000000..f183863
Binary files /dev/null and b/content/petstore/9d.png differ
diff --git a/content/petstore/9e.png b/content/petstore/9e.png
new file mode 100644
index 0000000..286a78f
Binary files /dev/null and b/content/petstore/9e.png differ
diff --git a/content/petstore/9f.png b/content/petstore/9f.png
new file mode 100644
index 0000000..dd0013c
Binary files /dev/null and b/content/petstore/9f.png differ
diff --git a/content/petstore/9g.png b/content/petstore/9g.png
new file mode 100644
index 0000000..a5c0b6c
Binary files /dev/null and b/content/petstore/9g.png differ
diff --git a/content/petstore/9h.png b/content/petstore/9h.png
new file mode 100644
index 0000000..084e7ce
Binary files /dev/null and b/content/petstore/9h.png differ
diff --git a/templates/about.html b/templates/about.html
index 98df900..23f3d6f 100644
--- a/templates/about.html
+++ b/templates/about.html
@@ -42,11 +42,29 @@
</td>
</tr>
<tr class='dark bb'>
+ <td class='code'><a class='doclink'
href='http://juneau.apache.org/site/apidocs-{@property
juneauVersion}/overview-summary.html##juneau-marshall-rdf'>juneau-marshall-rdf</a></td>
+ <td>
+ Extended marshalling support for RDF
languages.
+ </td>
+ </tr>
+ <tr class='dark bb'>
<td class='code'><a class='doclink'
href='http://juneau.apache.org/site/apidocs-{@property
juneauVersion}/overview-summary.html##juneau-dto'>juneau-dto</a></td>
<td>
A variety of predefined DTOs for
serializing and parsing languages such as HTML5, Swagger and ATOM.
</td>
</tr>
+ <tr class='dark bb'>
+ <td class='code'><a class='doclink'
href='http://juneau.apache.org/site/apidocs-{@property
juneauVersion}/overview-summary.html##juneau-svl'>juneau-svl</a></td>
+ <td>
+ A simple yet powerful variable
replacement language API.
+ </td>
+ </tr>
+ <tr class='dark bb'>
+ <td class='code'><a class='doclink'
href='http://juneau.apache.org/site/apidocs-{@property
juneauVersion}/overview-summary.html##juneau-config'>juneau-config</a></td>
+ <td>
+ A sophisticated configuration file API.
+ </td>
+ </tr>
<tr class='light bb'>
<td rowspan="4"
style='text-align:center;font-weight:bold;padding:20px;'
class='code'>juneau-rest</td>
<td class='code'><a class='doclink'
href='http://juneau.apache.org/site/apidocs-{@property
juneauVersion}/overview-summary.html##juneau-rest-server'>juneau-rest-server</a></td>