Enterprise Java Development@TOPIC@
Annotated with @Path
Injected with implementation details and call context
TodoList Example
import javax.ejb.EJB;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import info.ejava.examples.jaxrs.todos.ejb.TodosMgmtRemote;
import javax.ejb.EJB;
@Path("todo_lists")
public class TodoListsResource {
@EJB
private TodosMgmtRemote todosMgmt;
@Context
private UriInfo uriInfo;
...
}
Products Example
@Path("products")
public class ProductsResource {
@EJB
private InventoryMgmtEJB ejb;
@Context
private Request request;
@Context
private UriInfo uriInfo;
...
}
Get members of a resource
Could add query parameters
Common to add paging
@GET @Path("")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response getTodoLists(
@QueryParam("offset") @DefaultValue("0") Integer offset,
@QueryParam("limit") @DefaultValue("10") Integer limit) {
ResponseBuilder rb = null;
try {
TodoListListDTO entity = todosMgmt.getTodoLists(offset, limit);
rb = Response.ok(entity)
.contentLocation(uriInfo.getAbsolutePath());
} catch (InternalErrorException ex) {
rb = Response.serverError().entity(ex.getMessage());
} catch (Exception ex) {
logger.info("Unexpected exception getting TodoLists", ex);
String msg = String.format("Unexpected error getting TodoLists: %s", ex.toString());
rb = Response.serverError()
.entity(new MessageDTO(msg));
}
return rb.build();
}
@Path("") - method applied to URI path of overall JAX-RS class
@Produces - indicates Content-Type results are available for
@QueryParam - maps query parameter to input method argument
@DefaultValue - assigns a value to be applied when query parameter not supplied by caller
contentLocation() - returns Content-Location header indicating URI for payload
uriInfo - injected resource that knows the calling URI context for method
# client
private UriBuilder getBaseUrl(String...path) {
UriBuilder builder = UriBuilder.fromUri(baseUrl);
if (path!=null) {
for (String p:path) {
builder = builder.path(p);
}
}
return builder;
}
UriBuilder - used to form a URL to resource
# client
static String TODO_LISTS_PATH = "todo_lists";
# client
public Response getTodoLists(Integer offset, Integer limit) {
URI uri = getBaseUrl(TODO_LISTS_PATH).build();
WebTarget target = client.target(uri);
if (offset!=null) {
target=target.queryParam(OFFSET, offset);
}
if (limit!=null) {
target=target.queryParam(LIMIT, limit);
}
return target.request(mediaType)
.buildGet()
.invoke();
}
queryParam - used to pass named query parameters to method
mediaType - used to indicate which media types willing to accept
Response - directly returned for inspection
# client
static <T> T getEntity(Response response, Class<T> type) {
if (Response.Status.Family.SUCCESSFUL.equals(response.getStatusInfo().getFamily())) {
return response.readEntity(type, type.getAnnotations());
} else {
Helper method to inspect Response and return instance if success
Inspection may need to look at headers
Content-Type dictated by API return payload
# client
Response response = todosClient.getTodoLists(null, null);
TodoListListDTO todoLists = getEntity(response, TodoListListDTO.class);
Caller indicates Java type for payload returned
Create new resource or tunnel service
Returns CREATED and URI of created resource
private ResponseBuilder getBadRequestResponse(Exception ex) {
logger.debug(ex.getMessage());
return Response.status(Status.BAD_REQUEST)
.entity(new MessageDTO(ex.getMessage()));
}
Simple helper method to build status-specific response
MessageDTO used to report error text -- still targeted mediaType
@POST
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response createTodoList(TodoListDTO todoList) {
ResponseBuilder rb = null;
try {
TodoListDTO entity = todosMgmt.createTodoList(todoList);
URI location = uriInfo.getBaseUriBuilder()
.path(TodoListsResource.class)
.path(TodoListsResource.class, "getTodoList")
.build(entity.getName());
rb = Response.created(location)
.contentLocation(location)
.entity(entity);
} catch (InvalidRequestException ex) {
rb = getBadRequestResponse(ex);
} catch (InternalErrorException ex) {
rb = getInternalErrorResponse(ex);
} catch (Exception ex) {
rb = getUndexpectedErrorResponse("Unexpected error creating TodoList", ex);
}
return rb.build();
}
@POST - makes the method available for the resource
created() - helper method to indicate a 201/CREATED status and a Location header
uriInfo.getBaseUriBuilder() - access to a URIBuilder to build locationj URI
# client
public Response createTodoList(TodoListDTO todoList) {
URI uri = getBaseUrl(TODO_LISTS_PATH).build();
WebTarget target = client.target(uri);
return target.request(mediaType)
.buildPost(Entity.entity(todoList, mediaType, todoList.getClass().getAnnotations()))
.invoke();
}
Entity.entity() - used to define request content
buildPost() - accepts entity definition or null if no payload content
# client
Response response = todosClient.createTodoList(todoList);
TodoListDTO createdList = getEntity(response,TodoListDTO.class);
Non-destructive read
Returns 200/OK with payload
@GET @Path("{listName}")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response getTodoList(@PathParam("listName") String listName) {
ResponseBuilder rb = null;
try {
TodoListDTO entity = todosMgmt.getTodoList(listName);
rb = Response.ok(entity)
.contentLocation(uriInfo.getAbsolutePath());
} catch (ResourceNotFoundException ex) {
rb = getNotFoundResponse(ex);
} catch (InternalErrorException ex) {
rb = getInternalErrorResponse(ex);
} catch (Exception ex) {
rb = getUndexpectedErrorResponse("Unexpected error creating TodoList", ex);
}
return rb.build();
}
@Path defines required parameter(s) using {variable} syntax
@PathParam maps "listName" from URI path to String listName argument to method
@DefaultValue("value") can be applied -- otherwise null if not supplied
Since this is the GET for a single resource -- the contentLocation will be the exact URI that called this endpoint
# client
static String TODO_LIST_PATH = "todo_lists/{listName}";
String URI is defined with {variable} syntax
{variable} - in this case - identifies which todo_list resource within the collection
# client
public Response getTodoList(String listName) {
URI uri = getBaseUrl(TODO_LIST_PATH).build(listName);
WebTarget target = client.target(uri);
return target.request(mediaType)
.buildGet()
.invoke();
}
build() - accepts {variable} values in sequence order
# client
//request a resource that does exist
Response response = todosClient.getTodoList(todoList.getName());
TodoListDTO resultList = getEntity(response, TodoListDTO.class);
Specific Resource will be marshaled back to client using DTO view
# client
//request a resource that does not exist
Response response = todosClient.getTodoList("foobar_not_exist");
assertEquals("unexpected error family",
Status.Family.CLIENT_ERROR,
response.getStatusInfo().getFamily());
assertEquals("unexpected status",
Status.NOT_FOUND,
response.getStatusInfo());
NOT_FOUND CLIENT_ERROR will be returned to client for unknown resource ID
Update existing or create well-known URI
Optional results
In this case we will work with nested resource
@PUT @Path("{listName}/todo_items/{itemName}")
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response updateTodoItem(
@PathParam("listName") String listName,
@PathParam("itemName") String itemName,
TodoItemDTO item) {
ResponseBuilder rb = null;
try {
TodoItemDTO entity = todosMgmt.updateTodoListItem(listName, itemName, item);
rb = Response.ok(entity);
} catch (ResourceNotFoundException ex) {
rb = getNotFoundResponse(ex);
} catch (InvalidRequestException ex) {
rb = getBadRequestResponse(ex);
} catch (InternalErrorException ex) {
rb = getInternalErrorResponse(ex);
} catch (Exception ex) {
rb = getUndexpectedErrorResponse("Unexpected error updating TodoItems", ex);
}
return rb.build();
}
Our todoItem is below a specific todoList
Two {variables} exist in path to represent the parent and child IDs
We are using a PUT since we know the exact URI for the resource
# client
static String TODO_LIST_PATH = "todo_lists/{listName}";
static String TODO_ITEM_PATH = "todo_items/{itemName}";
# client
public Response updateTodoItem(String listName, TodoItemDTO item) {
URI uri = getBaseUrl(TODO_LIST_PATH, TODO_ITEM_PATH).build(listName, item.getName());
WebTarget target = client.target(uri);
return target.request(mediaType)
.buildPut(Entity.entity(item, mediaType, item.getClass().getAnnotations()))
.invoke();
}
Our URI has two {variables} and build() requires two sequential values
buildPut() - accepts payload definition same as earlier buildPost()
# client
Response response = todosClient.updateTodoItem(todoList.getName(), item);
TodoItemDTO updated = getEntity(response, item),TodoItemDTO.class);
Caller must supply context information to fill in nested resource path
Deletes specified resource
Optional results
@DELETE @Path("{listName}")
public Response deleteTodoList(@PathParam("listName") String listName) {
ResponseBuilder rb = null;
try {
todosMgmt.deleteTodoList(listName);
rb = Response.noContent();
} catch (ResourceNotFoundException ex) {
rb = getNotFoundResponse(ex);
} catch (InternalErrorException ex) {
rb = getInternalErrorResponse(ex);
} catch (Exception ex) {
rb = getUndexpectedErrorResponse("Unexpected error deleting TodoList", ex);
}
return rb.build();
}
DELETE method signals action to take with identified resource
noContent() - a variant of 200/OK with Content-Length=0; statusCode=204
# client
static String TODO_LIST_PATH = "todo_lists/{listName}";
# client
public Response deleteTodoList(String listName) {
URI uri = getBaseUrl(TODO_LIST_PATH).build(listName);
WebTarget target = client.target(uri);
return target.request(mediaType)
.buildDelete()
.invoke();
}
buildDelete() - builds request similar to others except with DELETE as the verb
# client
Response response = todosClient.deleteTodoList(todoList.getName());
assertSuccess("error deleting todoList", response);
Caller inspects Response for successful status code
# client
static <T> void assertSuccess(String message, Response response) {
if (!Response.Status.Family.SUCCESSFUL.equals(response.getStatusInfo().getFamily())) {
throw new IllegalStateException(String.format(message + ", error response[%d %s]: %s",
response.getStatus(),
response.getStatusInfo(),
response.readEntity(String.class))
);
} else {
response.close();
}
}
SUCCESSFUL family check avoids 200/OK versus 204/NO_CONTENT confusion