Rest interfaces using JAX-RS

Rest interfaces are currently being implemented in a manual way. This guide describes the best practices from ongoing NSX Projects, and will serve as a backbone when this functionality is automatically expanded.

As an additional reference, be sure to read up on the correct http codes.

Libraries, exclude filters and Struts2 interaction

Including JAX-RS can be done in a straightforward way, by setting the correct library. Struts needs to be instructed however, to exclude the endpoint path, otherwise this will lead to conflicts.

API definition

@Api("A descriptive name for the API")
@Path("/<<path>>")
public class <<API>>Connector {
    //calls go here
}

Handling a Rest call

Below is an example of a REST call.

@PATCH // or @GET, @POST and so on
@Path(ApiManagerEndPoints.ACTION)
@ApiOperation(value = "A descriptive name for the action")
@ApiResponses(value = {@ApiResponse(code = 200, message = ResponseCodes.r200, response = PatchResponse.class),
        @ApiResponse(code = 500, message = ResponseCodes.r500, response = ServerErrorResponse.class),
        @ApiResponse(code = 400, message = ResponseCodes.r400, response = BadRequestResponse.class),
        @ApiResponse(code = 401, message = ResponseCodes.r401, response = UnauthorizedResponse.class)
})
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response linkDeliverable(/return type can be general, or the 200 response class
        @PathParam("apiId") Long id,
        @PathParam("deliverableId") String delivId,
        @Context ContainerRequestContext requestContext)
        throws CrudResultException, UnknownIdException, ModificationException, ValidationException, SearchResultException {
    UserContext userContext = new ManagerRequestContext(requestContext).getUserContext();

   new DeliverableDataValidator(userContext,delivId).validate();

    ApiDefinitionDetails details = findApiDefinition(userContext, id);
    addDeliverable(delivId, details, userContext);

    return PatchResponse.withLocation("url");
}

Error Handling

The above example can throw several types of errors. JAX-RS can handle these calls by intercepting them with a @Provider class. This will be an exception handler:

@Provider
public class CreateExceptionHandler implements ExceptionMapper<CreateException> {
    String errorTypeStatus = "Error in CreateResult";

    @Override
    public Response toResponse(CreateException e) {
        Response.ResponseBuilder response = ResultError.resultErrorHandler(e, errorTypeStatus);

        return response.build();
    }

}

This handler will intercept the exception, and return the appropriate response with the correct code.

Authentication

In order to authenticate a request, a TokenFilter is used. This token filter is a provider, and will pre-match any request. In order to avoid an authentication failure when logging in, excludedPaths need to be set. This is also the case for the swagger documentation, which will always be available.

@Provider
@PreMatching
public class TokenFilter implements ContainerRequestFilter {

    private Logger logger = LoggerFactory.getLogger(this.getClass().getCanonicalName());

    public static final String AUTHENTICATION_HEADER = "Authorization";
    private CertificateRequestContext certificateRequestContext = new CertificateRequestContext();
    private List<String> excludedPaths;

    /**
     *
     */
    public TokenFilter() {
        excludedPaths  = new ArrayList<>();
        excludedPaths.add(EndPoints.SWAGGERJSON);
        excludedPaths.add(EndPoints.SWAGGERYAML);
        excludedPaths.add(EndPoints.RECOGNITION);
        excludedPaths.add(EndPoints.AUTHORIZE);
    }

    private boolean validateToken(String token) {
        if(token.startsWith("test"))
            return true;
        else
            return false;
    }
    private String parseToken(String authHeader) {
        if(!(authHeader.startsWith("Bearer")||authHeader.startsWith("bearer"))) {
            return "";
        }
        return authHeader.replaceAll("[Bb]earer ","");
    }

    /**
     * jersey 2 --> ContainerRequestContext
     * @param containerRequestContext
     * @throws IOException
     */
    @Override
    public void filter(ContainerRequestContext containerRequestContext) throws IOException {
        if(!excludedPaths.contains(containerRequestContext.getUriInfo().getPath())) {
            String authHeader = containerRequestContext.getHeaderString(AUTHENTICATION_HEADER);
            logger.info("Authorizing request");
            try {
                if (authHeader == null) {
                    logger.error("No authorization header");
                    throw new NotAuthorizedException("Bearer");
                }
                String token = parseToken(authHeader);
                if (!validateToken(token)) {
                    logger.error("Authorization header not valid");
                    throw new NotAuthorizedException("Bearer error=\"invalid_token\"");
                }
                CertificateUserContext certificateUserContext = new CertificateUserContext();
                certificateUserContext.setToken(token);
                certificateUserContext.setUserName(token);
                certificateUserContext.setMobileNumber(token);

                try {
                    certificateRequestContext.addUserContext(containerRequestContext,  certificateUserContext);
                } catch (Exception e) {
                    logger.error("Could not add authorization context to request properties");
                    throw new NotAuthorizedException("Could not add property: " + e.getMessage());
                }
                logger.info("Request authorized, please continue");
            } catch (NotAuthorizedException e) {
                containerRequestContext.abortWith( new NotAuthorizedExceptionHandler().toResponse(e));
            }
        }
    }

    /**
     * jersey 1 --> ContainerRequest
     * @param containerRequestContext
     * @throws IOException
     */
    /*@Override
    public void filter(ContainerRequest containerRequest) throws IOException {
        logger.info("filter works... [jersey2]");
    }*/
}