PetStoreResource
The PetStoreResource class provides examples for creating a Swagger-based interface.
@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 <a class='link' href='http://swagger.io'>http://swagger.io</a>.",
},
htmldoc=@HtmlDoc(
widgets={
ContentTypeMenuItem.class,
ThemeMenuItem.class,
},
navlinks={
"up: request:/..",
"options: servlet:/?method=OPTIONS",
"$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>"
}
),
properties= {
// Resolve recursive references when showing schema info in the swagger.
@Property(name=SWAGGERUI_resolveRefsMaxDepth, value="99")
},
swagger=@ResourceSwagger("$F{PetStoreResource.json}"),
staticFiles={"htdocs:htdocs"}
)
public class PetStoreResource extends BasicRestServletJena {
private PetStore store;
@RestHook(INIT)
public void initDatabase(RestContextBuilder builder) throws Exception {
store = new PetStore().init();
}
@RestMethod(
name=GET,
path="/",
summary="Navigation page"
)
public ResourceDescriptions getTopPage() {
return new ResourceDescriptions()
.append("pet", "All pets in the store")
.append("store", "Orders and inventory")
.append("user", "Petstore users")
;
}
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Pets
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@RestMethod(
name=GET,
path="/pet",
summary="All pets in the store",
swagger=@MethodSwagger(
tags="pet",
parameters={
Queryable.SWAGGER_PARAMS
}
),
bpx="Pet: tags",
htmldoc=@HtmlDoc(
widgets={
QueryMenuItem.class,
AddPetMenuItem.class
},
navlinks={
"INHERIT", // Inherit links from class.
"[2]:$W{QueryMenuItem}", // Insert QUERY link in position 2.
"[3]:$W{AddPetMenuItem}" // Insert ADD link in position 3.
}
),
converters={Queryable.class}
)
public Collection<Pet> getPets() throws NotAcceptable {
return store.getPets();
}
@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(
@Path(
name="petId",
description="ID of pet to return",
example="123"
)
long petId
) throws IdNotFound, NotAcceptable {
return store.getPet(petId);
}
@RestMethod(
summary="Add a new pet to the store",
swagger=@MethodSwagger(
tags="pet",
value={
"security:[ { petstore_auth:['write:pets','read:pets'] } ]"
}
)
)
public Ok postPet(
@Body(description="Pet object to add to the store") PetCreate pet
) throws IdConflict, NotAcceptable, UnsupportedMediaType {
store.create(pet);
return OK;
}
@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(
@Body(description="Pet object that needs to be added to the store") PetUpdate pet
) throws IdNotFound, NotAcceptable, UnsupportedMediaType {
store.update(pet);
return OK;
}
@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 return",
example="123"
)
long petId
) throws IdConflict, 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("(?)", "The name of the pet.", br(), "e.g. 'Fluffy'"))
),
tr(
th("Name:"),
td(input().name("name").type("text").value(pet.getName())),
td(new Tooltip("(?)", "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("(?)", "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("(?)", "The price to charge for this pet."))
),
tr(
th("Tags:"),
td(input().name("tags").type("text").value(Tag.asString(pet.getTags()))),
td(new Tooltip("(?)", "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("(?)", "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")
)
);
}
@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(
@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 {
return store.getPetsByStatus(status);
}
@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(
@Query(
name="tags",
description="Tags to filter by",
required=true,
example="['tag1','tag2']"
)
String[] tags
) throws InvalidTag, NotAcceptable {
return store.getPetsByTags(tags);
}
@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(
@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 {
store.removePet(petId);
return OK;
}
@RestMethod(
name=POST,
path="/pet/{petId}/uploadImage",
summary="Uploads an image",
swagger=@MethodSwagger(
tags="pet",
value={
"security:[ { petstore_auth:[ 'write:pets','read:pets' ] } ]"
}
)
)
public Ok uploadImage(
@Path(
name="petId",
description="ID of pet to update",
example="123"
)
long petId,
@FormData(
name="additionalMetadata",
description="Additional data to pass to server",
example="Foobar"
)
String additionalMetadata,
@FormData(
name="file",
description="file to upload",
required=true,
type="file"
)
byte[] file
) throws NotAcceptable, UnsupportedMediaType {
return OK;
}
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Orders
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@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")
;
}
@RestMethod(
name=GET,
path="/store/order",
summary="Petstore orders",
swagger=@MethodSwagger(
tags="store"
),
htmldoc=@HtmlDoc(
widgets={
QueryMenuItem.class,
AddOrderMenuItem.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();
}
@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(
@Path(
name="orderId",
description="ID of order to fetch",
maximum="1000",
minimum="101",
example="123"
)
long orderId
) throws InvalidId, IdNotFound, NotAcceptable {
if (orderId < 101 || orderId > 1000)
throw new InvalidId();
return store.getOrder(orderId);
}
@RestMethod(
name=POST,
path="/store/order",
summary="Place an order for a pet",
swagger=@MethodSwagger(
tags="store"
),
pojoSwaps={
DateSwap.ISO8601D.class
}
)
public Order placeOrder(
@FormData(
name="petId",
description="Pet ID"
)
long petId,
@FormData(
name="shipDate",
description="Ship date"
)
Date shipDate
) throws IdConflict, NotAcceptable, UnsupportedMediaType {
CreateOrder co = new CreateOrder(petId, shipDate);
return store.create(co);
}
@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 deletePurchaseOrder(
@Path(
name="orderId",
description="ID of the order that needs to be deleted",
minimum="1",
example="5"
)
long orderId
) throws InvalidId, IdNotFound, NotAcceptable {
if (orderId < 0)
throw new InvalidId();
store.removeOrder(orderId);
return OK;
}
@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
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@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();
}
@RestMethod(
name=GET,
path="/user/{username}",
summary="Get user by user name",
swagger=@MethodSwagger(
tags="user"
)
)
public User getUser(
@Path(
name="username",
description="The name that needs to be fetched. Use user1 for testing."
)
String username
) throws InvalidUsername, IdNotFound, NotAcceptable {
return store.getUser(username);
}
@RestMethod(
summary="Create user",
description="This can only be done by the logged in user.",
swagger=@MethodSwagger(
tags="user"
)
)
public Ok postUser(
@Body(description="Created user object") User user
) throws InvalidUsername, IdConflict, NotAcceptable, UnsupportedMediaType {
store.add(user);
return OK;
}
@RestMethod(
name=POST,
path="/user/createWithArray",
summary="Creates list of users with given input array",
swagger=@MethodSwagger(
tags="user"
)
)
public Ok createUsers(
@Body(description="List of user objects") User[] users
) throws InvalidUsername, IdConflict, NotAcceptable, UnsupportedMediaType {
for (User user : users)
store.add(user);
return OK;
}
@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(
@Path(
name="username",
description="Name that need to be updated"
)
String username,
@Body(
description="Updated user object"
)
User user
) throws InvalidUsername, IdNotFound, NotAcceptable, UnsupportedMediaType {
store.update(user);
return OK;
}
@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(
@Path(
name="username",
description="The name that needs to be deleted"
)
String username
) throws InvalidUsername, IdNotFound, NotAcceptable {
store.removeUser(username);
return OK;
}
@RestMethod(
name=GET,
path="/user/login",
summary="Logs user into the system",
swagger=@MethodSwagger(
tags="user"
)
)
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,
RestRequest req,
RestResponse res
) throws InvalidLogin, NotAcceptable {
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;
}
@ResponseHeader(
name="X-Expires-After",
type="string",
format="date-time",
description="Date in UTC when token expires",
example="2012-10-21"
)
public static class ExpiresAfter {
private final Calendar c;
public ExpiresAfter(Date d) {
this.c = new GregorianCalendar();
c.setTime(d);
}
public Calendar toCalendar() {
return c;
}
}
@RestMethod(
name=GET,
path="/user/logout",
summary="Logs out current logged in user session",
swagger=@MethodSwagger(
tags="user"
)
)
public Ok logout(RestRequest req) throws NotAcceptable {
req.getSession().removeAttribute("login-expires");
return OK;
}
}
Pointing a browser to the resource shows the following:
http://localhost:10000/petstore
Clicking the QUERY link renders the following menu pop-up complete with tooltips:
The STYLES menu item allows you to try out the other default look-and-feels: