Enterprise Java Development@TOPIC@
Use Web API resource classes as HTTP facades
Inject EJB/business logic components to perform details of work
Report obvious status from EJB/business logic and error messages
Report accurate status and error messages from Web API
Define an EJB to handle the (transactional and non-transactional) business logic
# GreetingEJB.java
import javax.ejb.Stateless;
# GreetingEJB.java
@Stateless
public class GreetingEJB {
}
This EJB happens to be a No Interface EJB
The class is the interface
Inject EJB into JAX-RS Resource class
#GreetingsResource.java
import javax.ejb.EJB;
#GreetingsResource.java
@Path("greetings")
public class GreetingsResource {
@EJB
private GreetingEJB greetingEJB;
Container locates and injects component that matches declared interface type
Web API needs to report accurate status to caller -- especially when there is an error
EJB interfaces need to make determining that status obvious to detect and easy to report
Business and lower-levels are in best situation to know the specifics of the error
Web API should just be in the role of being the facade that reports it
Use distinct exceptions that map to specific statuses to be returned by the Web API
Built-in JAX-RS Exceptions not valid for use in @Remote interfaces - embedded Response not Serializable
Deliberatly used candidate exception names here -- similar but not identical to JAX-RS exceptions names
Request failed because of a client request error
Informs caller that "it is them and not the service" that has the problem
If they keep making the same request -- they will get the same failure result
public class ClientErrorException extends Exception {
public ClientErrorException(String msg) {
super(msg);
}
}
Extending a checked exception because these errors generally represent business errors
Thrown checked exceptions do not automatically rollback transactions
Client identified resource that cannot be found
public class ResourceNotFoundException extends Exception {
public ResourceNotFoundException(String format, Object...args) {
super(String.format(format, args));
}
public ResourceNotFoundException(String msg) {
super(msg);
}
}
Make it easy for EJB/business logic to report cause ready to return to caller
throw new ResourceNotFoundException("listName[%s] not found", listName);
throw new ResourceNotFoundException("todoList[%s], todoItem[%s] not found", listName, itemName);
Caller targeting a resource that cannot be found
public class InvalidRequestException extends ClientErrorException {
public InvalidRequestException(String format, Object...args) {
super(String.format(format, args));
}
public InvalidRequestException(String msg) {
super(msg);
}
}
throw new InvalidRequestException("Unable to greet, name not supplied");
Request failed because of internal or back-end problem
public class InternalErrorException extends RuntimeException {
public InternalErrorException(String format, Object...args) {
super(String.format(format, args));
}
public InternalErrorException(String msg) {
super(msg);
}
public InternalErrorException(Throwable ex, String msg) {
super(msg, ex);
}
public InternalErrorException(Throwable ex, String format, Object...args) {
super(String.format(format, args), ex);
}
}
Extends un-checked exception since these errors are generally infrastructure-related
Unchecked exceptions automatically rollback current transaction
Exception of value to identify the source of error within the server code
throw new InternalErrorException("Internal error greeting name[%s]: %s", name, ex);
Define basic EJB method
# GreetingEJB.java
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
# GreetingEJB.java
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public String greet(String name) {
return String.format("hello %s", name); //core business code
}
Implements greeting
No transactional behavior -- default for EJB is REQUIRED
Define Resource method in terms of calling EJB method
# GreetingsResource.java
@GET
@Path("greet")
@Produces(MediaType.TEXT_PLAIN)
public Response greet(@QueryParam("name") String name) {
ResponseBuilder rb=null;
String entity = greetingEJB.greet(name);
rb = Response.ok(entity);
return rb.build();
}
Method is accessible at URI /api/greetings/greet
# client
URI uri = UriBuilder.fromUri(baseTodosUrl)
.path("greetings")
.path("greet")
.build();
WebTarget target = client.target(uri);
Method is accessible using GET request and Accepts "text/plain"
# client
Response response = target.request(MediaType.TEXT_PLAIN_TYPE)
.get();
logger.info("GET {} => {}/{}", target.getUri(), response.getStatus(), response.getStatusInfo());
Resource method parameter mapped from HTTP query parameter -- not yet passed
Missing name not yet reported in status
# client GET http://localhost:8080/ejavaTodos/api/greetings/greet => 200/OK hello null
The above should have been reported a detected BAD_REQUEST
We need to fix/implement this
Add error logic to EJB/business method
# GreetingEJB.java
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public String greet(String name) throws InvalidRequestException {
try {
if (name==null || name.isEmpty()) {
throw new InvalidRequestException("Unable to greet, name not supplied");
}
return String.format("hello %s", name); //core business code
} catch (RuntimeException ex) {
throw new InternalErrorException("Internal error greeting name[%s]", name);
}
}
Validates name was provided - reports bad request if missing
Implements greeting
Catches any infrastructure exceptions and reports internal server error with text message for caller
Add minimal exception handling in resource method
# GreetingsResource.java
@GET
@Path("greet")
@Produces(MediaType.TEXT_PLAIN)
public Response greet(@QueryParam("name") String name) {
ResponseBuilder rb=null;
try {
String entity = greetingEJB.greet(name);
rb = Response.ok(entity);
} catch (Exception ex) {
rb=Response.serverError()
.entity(String.format("unexpected error greeting name[%s]", name));
}
return rb.build();
}
We are purposely generalizing our exception logic here as part of incremental example
Resource method currently treating all exceptions as Internal Server Error
# client
WebTarget target = client.target(uri);
Response response = target.request(MediaType.TEXT_PLAIN_TYPE)
.get();
logger.info("GET {} => {}/{}", target.getUri(), response.getStatus(), response.getStatusInfo());
GET http://localhost:8080/ejavaTodos/api/greetings/greet => 500/Internal Server Error unexpected error greeting name[null]
This needs improvement/correction to report correct error type
Add complete error reporting logic to resource method
# GreetingsResource.java
@GET
@Path("greet")
@Produces(MediaType.TEXT_PLAIN)
public Response greet(@QueryParam("name") String name) {
ResponseBuilder rb=null;
try {
String entity = greetingEJB.greet(name);
rb = Response.ok(entity);
} catch (InvalidRequestException ex) {
rb = Response.status(Status.BAD_REQUEST)
.entity(ex.getMessage());
} catch (InternalErrorException ex) {
rb = Response.status(Status.INTERNAL_SERVER_ERROR)
.entity(ex.getMessage());
} catch (Exception ex) {
rb=Response.serverError()
.entity(String.format("unexpected error greeting name[%s]", name));
}
return rb.build();
}
BAD_REQUEST returned for InvalidRequestException -- passing informative message to caller
GET http://localhost:8080/ejavaTodos/api/greetings/greet => 400/Bad Request 19:37:49,825 INFO (GreetingsIT.java:111) -Unable to greet, name not supplied
Client can correct by adding name query parameter
WebTarget target = client.target(uri)
.queryParam("name", "ejava");
Response response = target.request(MediaType.TEXT_PLAIN_TYPE)
.get();
logger.info("GET {} => {}", target.getUri(), response.getStatusInfo());
String greeting = response.readEntity(String.class);
logger.info("{}", greeting);
GET http://localhost:8080/ejavaTodos/api/greetings/greet?name=ejava => OK hello ejava