Difference between revisions of "GxRest/GxJRS/Responses"

From Gcube Wiki
Jump to: navigation, search
(Inbound Responses)
(Inbound Responses)
Line 254: Line 254:
 
</source>
 
</source>
  
... or by wrapping a <source lang="Java"> javax.ws.rs.core.Response</source> object:
+
... or by wrapping a <code> javax.ws.rs.core.Response</code> object:
  
 
<source lang="Java">
 
<source lang="Java">

Revision as of 04:13, 16 February 2018

There are two classes of responses in gxRest:

  • outbound responses, that model messages, statuses, errors or exceptions returned from the web application.
  • inbound responses, that provide facilities to interpret the response and access to its content at the client side.


Outbound Responses

Outbound responses are the information returned from a webapp to its client. They could model a success status or an error condition.

Success Responses

Success responses can be created in fluent API manner. All the parameters are optional except the ones requested by the method that constructs the new response instance.

The following code shows how to return a CREATE (201) response from a resource method (in the RESTful meaning) that creates a new resource.

import org.gcube.common.gxrest.response.outbound.GXOutboundSuccessResponse;
 
@Path("context")
public class RMContext {
 
  @POST
  public Response create(...) {
 
    //Resource successfully created 
 
    URI location = ... //URI of the new resource
    return GXOutboundSuccessResponse.newCREATEResponse(location)
              .withMessage("Context successfully created.")
	      .ofType(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8)
              .build();
  }
}

The following code shows how to return an OK (200) response from a resource method that deletes resources.

import org.gcube.common.gxrest.response.outbound.GXOutboundSuccessResponse;
 
@Path("context")
public class RMContext {
 
  @DELETE
  public Response delete(...) {
 
    //Resource successfully deleted
 
    return GXOutboundSuccessResponse.newOKResponse()
              .withMessage("Context successfully deleted.")
	      .ofType(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8)
              .build();
  }
}

[TBD from here]

Error Responses

gxRest offers two approaches to return an error from the webapp to its client: Plain Old Java Exceptions (or POJEs) and Code Exceptions.

Outbound error responses share the following advantages:

  • abstract over an error response returned by a REST resource method
  • throw an error response wherever the developer feels it is safe to terminate the execution
  • simplify the method declaration (they do not need to be declared).

Plain Old Java Exceptions

Any Java programmer is used to manage error conditions by throwing and catching Exceptions. POJE brings this behavior into web applications.

Let's revise the create method previously shown to demonstrate how to send to a client an Exception (MyException in this case) instance with an associated error message.

import javax.ws.rs.core.Response;
 
import org.gcube.common.gxrest.response.outbound.GXOutboundSuccessResponse;
import org.gcube.common.gxrest.response.outbound.GXOutboundErrorResponse;
 
@Path("context")
public class RMContext {
 
  @POST
  public Response create(...) {
 
    //Oh no, something failed here
    GXOutboundErrorResponse.throwException(new MyException("Context was not created."));
 
 
    //Resource successfully created 
 
    URI location = ... //URI of the new resource
    return GXOutboundSuccessResponse.newCREATEResponse(uri)
              .withMessage("Context successfully created.")
	      .ofType(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8).build();
 
  }
}

Code Exceptions

Code Exceptions model an approach to simplify and uniform the handling of exceptions (in the Java sense) within RESTful or REST-like services. Instead of creating separate classes for each exception type, the idea behind Code Exceptions is to use a single, system-wide exception class. And make it extend WebApplicationException (from javax.ws.rs) that in turn extends RuntimeException.

A Code Exception is capable to hold an error code and an associated message, wrap them into a REST response and return them to the caller.

Compared to POJEs, Code Exceptions have these additional advantages:

  • provide a single (and self-documented) point where to declare error codes and messages returned by the webapp
  • reduce the class count in a project (not to mention in a system) avoiding the proliferation of custom Exception classes
  • simplify the client code that manages only the error codes it is capable to handle
  • remove the need to declare exceptions that sometimes aren’t going to be handled anyway.

On the other hand, Code Exceptions require more coding than POJEs.

Declare your Error Codes

The first step is to declare an enumeration of the error codes and associated messages. The enum must extend the ErrorCode interface.

import org.gcube.common.gxrest.response.outbound.ErrorCode;
 
public enum RMCode implements ErrorCode {
 
	INVALID_METHOD_REQUEST(0, "The request is invalid."), 
	MISSING_PARAMETER(1,"Required query parameter is missing."), 
	MISSING_HEADER(2, "Required header is missing."), 
	CONTEXT_ALREADY_EXISTS(3, "Context already exists at the same level of the hierarchy."),
	CONTEXT_PARENT_DOES_NOT_EXIST(4, "Failed to validate the request. The request was not submitted to the Resource Registry."),
	INVALID_REQUEST_FOR_RR(5, "Failed to validate the request. The request was not submitted to the Resource Registry."),
	GENERIC_ERROR_FROM_RR(6, "The Resource Registry returned an error.");
 
	private int id;
	private String msg;
 
	private RMCode(int id, String msg) {
		this.id = id;
		this.msg = msg;
	}
 
	public int getId() {
		return this.id;
	}
 
	public String getMessage() {
		return this.msg;
	}
}

Different enums can be created for different resource methods or per REST resource. The granularity is up to the developer.

Throw Code Exceptions

The following method tries to create a Context given certain parameters (not shown).

import javax.ws.rs.core.Response;
 
import org.gcube.resourcemanagement.manager.io.rs.RMCode;
import org.gcube.common.gxrest.response.outbound.GXOutboundErrorResponse;
 
@Path("context")
public class RMContext {
 
  @POST
  public Response create(...) {
 
    //An error condition occurs
    GXOutboundErrorResponse.throwErrorCode(RMCode.CONTEXT_ALREADY_EXISTS);
 
    //Another error condition occurs
    GXOutboundErrorResponse.throwErrorCode(RMCode.CONTEXT_PARENT_DOES_NOT_EXIST);
  }
}

Do note that GXOutboundErrorResponse can be thrown by any object invoked within the create method without the need of declaring them (this is because they are RuntimeExceptions too).

By default, a GXOutboundErrorResponse has the NOT ACCEPTABLE(406) status. It is possible to assign a different status to the response by using overloaded methods of the class as shown below.

import javax.ws.rs.core.Response;
import org.gcube.resourcemanagement.manager.io.rs.RMCode;
import org.gcube.common.gxrest.response.outbound.GXOutboundErrorResponse;
 
@Path("context")
public class RMContext {
 
  @POST
  public Response create(...) {
 
    // ops, the parent context does not exist
    GXOutboundErrorResponse.throwErrorCode(Response.Status.CONFLICT, RMCode.CONTEXT_PARENT_DOES_NOT_EXIST);
 
  }
}

Local Code Exceptions

gxRest provides another type of exception called LocalCodeException. A local code exception holds an error code and a message and it is intended to be used internally in the webapp to propagate such information whenever the developer feels it's not the right time to terminate the execution of the REST request (thus using a GXOutboundErrorResponse). The same enums extending the ErrorCode used for the web exceptions can be used to create a local code exception.

Intentionally, they do not extend RuntimeException because of their declared scope: the error code inside the local exception is intended to be consumed by the webapp, not by the caller (at least, not yet). The method that throws them must declare this behavior in the signature and the method caller must explicitly catch it (just as any other standard Java exception).

At any time, a local code exception can be converted into a web code exception and therefore it is immediately returned to the caller.

The conversion is very simple.

import org.gcube.common.gxrest.response.outbound.LocalCodeException;
import org.gcube.common.gxrest.response.outbound.GXOutboundErrorResponse;
import org.gcube.resourcemanagement.manager.io.rs.RMCode;
 
public class Test {
 
 private void doSomething() throws LocalCodeException {
   throw new LocalCodeException(RMCode.INVALID_METHOD_REQUEST);
 } 
 
 private void callDoSomething() {
   try {
     doSomething();
   } catch (LocalCodeException lce) {
 
     //do something with lce 
     switch (lce.getId()) {
	case 3:
	  //manage the error with code 3 (CONTEXT_ALREADY_EXISTS, see RMCode)
	  break;
	case 4:
	  //manage the error with code 4 (CONTEXT_PARENT_DOES_NOT_EXIST, see RMCode)
	  break;	
	default:
          //can't manage the other cases
	  break;
     }
     //and then return it to the webapp's client
     GXOutboundErrorResponse.throwErrorCode(lce);
   }
 }
 
}

It's not mandatory to convert a local exception into a web exception. Local exceptions can also just be used as a mean to propagate and handle errors within the webapp. For instance the switch statement in the example above could convert and return the local exception only in the default case (i.e. when the method doesn't know how to recover from a case not managed before).

Inbound Responses

Inbound responses are the information received by a client following up the invocation of a resource method on a web application.

They can be obtained as returned object from a GXRequest:

import org.gcube.common.gxrest.response.inbound.GXInboundResponse;
import org.gcube.common.gxrest.request.GXRequest;
 
GXInboundResponse response = GXRequest.newRequest(...).submit();

... or by wrapping a javax.ws.rs.core.Response object:

import javax.ws.rs.core.Response;
import org.gcube.common.gxrest.response.inbound.GXInboundResponse;
 
// invoke the create method and get a response.
Response create = target("context").queryParam(...).request()
		.post(Entity.entity(ISMapper.marshal(newContext), MediaType.APPLICATION_JSON + ";charset=UTF-8"));
 
//wrap the response
GXInboundResponse response = new GXInboundResponse(create);

The code above makes usage of the Jersey testing framework to invoke the create method.

As enums are not POJOs, they cannot be properly serialized in the Response's entity. Because of that, the enum value is converted and returned as a SerializableErrorCode instance.

Success Response

Once the client fetched the SerializableErrorCode instance, there are two ways to handle the error codes:

  • Use the SerializableErrorCode methods:
System.err.println(String.format("The service says %d, %s",code.getId(), code.getMessage()));
 
switch (code.getId()) {
	case 3:
	  //manage the error with code 3 (CONTEXT_ALREADY_EXISTS, see RMCode)
	  break;
	case 4:
	  //manage the error with code 4 (CONTEXT_PARENT_DOES_NOT_EXIST, see RMCode)
	  break;	
	default:
          //can't manage the other cases
	  break;
}


  • Convert the SerializableErrorCode object back into the original enum value:
import org.gcube.core.gxrest.exceptions.CodeFinder;
import org.gcube.resourcemanagement.manager.io.rs.RMCode;
 
RMCode realCode = CodeFinder.findAndConvert(code, RMCode.values());
 
switch (realCode) {
	case CONTEXT_ALREADY_EXISTS:
	   //manage the error 
	   break;
	case CONTEXT_PARENT_DOES_NOT_EXIST:
	  //manage the error
	  break;	
	default:
          //can't manage the other cases
	  break;
}

CodeFinder is an utility provided by the CodeException package. This case is possible only if the enum with the ErrorCodes is available in a component shared between the Service and the client.