dimanche 29 janvier 2012

Jersey custom parameter, annotation and exception mapping

Jersey is the JSR-311 reference implementation for JAX-RS (Java API for RESTful Web Services). One of the drawback of this API is its lack of documentation when you want to go deeper into some complex or recurrent issues. Because we all like KISS code, let see how to keep Jersey simple, stupid!

Let's take the following example where we want to :
  1. Map Java Exception to specific HTTP response with localized messages
  2. Inject parameters of any type with custom validation
  3. Define a specialized annotation for specific injectable parameters
A first shot with no optimization will look like this :
@Path("/test")
@Produces("application/json")
public class TestEndpoint {
    
    private static final Logger log = Logger.getLogger(TestEndpoint.class.getName());
    
    private static final ResourceBundle resource = 
        ResourceBundle.getBundle("com.blogspot.avianey");
    
    @Inject TestService service;
    
    @POST
    @Path("{pathParam: \\d+}")
    public String testMethod(
            @PathParam("pathParam") Long id,
            @FormParam("date") String dateStr,
            @Context HttpServletRequest request) {

        // verifying that the user is logged in
        User user = (User) request.getSession().getAttribute("user");
        if (user == null) {
            throw new WebApplicationException(Status.UNAUTHORIZED);
        }
        
        // verifying the format of the sent date
        Date date = null;
        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
        try {
            date = sdf.parse(dateStr);
        } catch (ParseException pe) {
            throw new WebApplicationException(Status.BAD_REQUEST);
        }
        
        try {
            // calling the business logic
            return service.testOperation(id, date, user);
        } catch (BusinessException boe) {
            log.log(Level.FINER, "Business problem while executing testMethod", boe);
            throw new WebApplicationException(
                    Response.status(Status.BAD_REQUEST)
                            .type(MediaType.TEXT_PLAIN)
                            .entity(resource.getString(boe.getMessage()))
                            .build());
        } catch (TechnicalException te) {
            log.log(Level.FINER, "Technical problem while executing testMethod", te);
            throw new WebApplicationException(
                    Response.status(Status.INTERNAL_SERVER_ERROR)
                            .type(MediaType.TEXT_PLAIN)
                            .entity(resource.getString("error.internal"))
                            .build());
        }
        
    }
    
}
It's easy to think about the number of duplicated lines of code we would have if such a solution is applied for all our exposed methods... Now it's time to go deeper into Jersey and JAX-RS.

Custom Exception mapping with Jersey

In a Three-Tier architecture, the Logic-Tier may throw an application specific BusinessException, the Data-Tier may throw a TechnicalException, and so on... As BusinessException are relative to business rule violations it might be interresting to alert the user with a comprehensive message in the Presentation-Tier. On the contrary, a synthetic message will be displayed to the user for TechnicalException that refers to problems that are not likely to happen.

Jersey makes it possible to bind Java Exception with specialized HTTP response. All we have to do is to register an ExceptionMapper for each Java Exception we want to handle in a generic manner.
@Provider
public class BusinessExceptionMapper implements ExceptionMapper<BusinessException> {

    private static final Logger log = Logger.getLogger(TestEndpoint.class.getName());
    
    private static final ResourceBundle resource = 
        ResourceBundle.getBundle("com.blogspot.avianey");

    @Override
    public Response toResponse(BusinessException e) {
        log.log(Level.FINER, "Business problem while executing testMethod", e);
        return Response.status(Status.BAD_REQUEST)
            .type(MediaType.TEXT_PLAIN)
            .entity(resource.getString(e.getMessage()))
            .build();
    }

}
@Provider
public class TechnicalExceptionMapper implements ExceptionMapper<TechnicalException> {

    private static final Logger log = Logger.getLogger(TestEndpoint.class.getName());
    
    private static final ResourceBundle resource = 
        ResourceBundle.getBundle("com.blogspot.avianey");

    @Override
    public Response toResponse(TechnicalException e) {
        log.log(Level.FINER, "Technical problem while executing testMethod", e);
        return Response.status(Status.INTERNAL_SERVER_ERROR)
            .type(MediaType.TEXT_PLAIN)
            .entity(resource.getString("error.internal"))
            .build();
    }

}
Just as service classes, @Provider classes should be placed in a package that is scanned by the Jersey Servlet at startup so they can be used at runtime.
<servlet>
    <servlet-name>Jersey</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>com.sun.jersey.config.property.packages</param-name>
        <param-value>pkg.provider; pkg.endpoint</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
We no longer have to handle explicitly BusinessException and TechnicalException in our exposed method :
@Path("/test")
@Produces("application/json")
public class TestEndpoint {
    
    private static final Logger log = Logger.getLogger(TestEndpoint.class.getName());
    
    @Inject TestService service;
    
    @POST
    @Path("{pathParam: \\d+}")
    public String testMethod(
            @PathParam("pathParam") Long id,
            @FormParam("date") String dateStr,
            @Context HttpServletRequest request) {

        // verifying that the user is logged in
        User user = (User) request.getSession().getAttribute("user");
        if (user == null) {
            throw new WebApplicationException(Status.UNAUTHORIZED);
        }
        
        // verifying the format of the sent date
        Date date = null;
        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
        try {
            date = sdf.parse(dateStr);
        } catch (ParseException pe) {
            throw new WebApplicationException(Status.BAD_REQUEST);
        }
        
        // calling the business logic
        // no need to catch exception here anymore
        return service.testOperation(id, date, user);
        
    }
    
}

Custom Jersey parameters

JAX-RS Param annotations like QueryParam, FormParam and PathParam can be apply to any Java Object that have a constructor with a single String argument. When calling a method with such an annotated parameter, Jersey instantiate a new object with the param value and pass it to the method.

We can easily use this feature to centralize the validation of date parameters accross all of our exposed methods :
/**
 * A DateParam to validate the format of date parameters received by Jersey
 */
public class DateParam {

    private Date date;
    
    public DateParam(String dateStr) {
        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
        try {
            this.date = sdf.parse(dateStr);
        } catch (ParseException pe) {
            throw new WebApplicationException(Status.BAD_REQUEST);
        }
    }
    
    public Date value() {
        return this.date;
    }
    
}
Once again, our original code gained in maintainability :
@Path("/test")
@Produces("application/json")
public class TestEndpoint {
    
    private static final Logger log = Logger.getLogger(TestEndpoint.class.getName());
    
    @Inject TestService service;
    
    @POST
    @Path("{pathParam: \\d+}")
    public String testMethod(
            @PathParam("pathParam") Long id,
            @FormParam("date") DateParam date,
            @Context HttpServletRequest request) {

        // verifying that the user is logged in
        User user = (User) request.getSession().getAttribute("user");
        if (user == null) {
            throw new WebApplicationException(Status.UNAUTHORIZED);
        }
        
        // calling the business logic
        return service.testOperation(id, date.value(), user);
        
    }
    
}

Contextual object injection

Now we will see how it possible to directly inject the logged User into our exposed method. There is two diferent approaches doing this :
  1. Use the @Context annotation with a custom provider to inject the desired Object
  2. Create a custom annotation and its Injectable and associated InjectableProvider
In the first approach, we define a new Injectable implementation for the User Type and associate it with the @Context annotation.
@Provider
public class LoggedUserProvider extends AbstractHttpContextInjectable<User>
        implements InjectableProvider<Context, Type> {

    private final HttpServletRequest r;
    
    public LoggedUserProvider(@Context HttpServletRequest r) {
        this.r = r;
    }

    /**
     * From interface InjectableProvider
     */
    @Override
    public Injectable<user> getInjectable(ComponentContext ic, Context a, Type c) {
        if (c.equals(User.class)) {
            return this;
        }
        return null;
    }

    /**
     * From interface InjectableProvider
     * A new Injectable is instanciated per request
     */
    @Override
    public ComponentScope getScope() {
        return ComponentScope.PerRequest;
    }

    /**
     * From interface Injectable
     * Get the logged User associated with the request
     * Or throw an Unauthorized HTTP status code
     */
    @Override
    public User getValue(HttpContext c) {
        final User user = Contestr.getSessionUser(r);
        if (user == null) {
            throw new WebApplicationException(Response.Status.UNAUTHORIZED);
        }
        return user;
    }
}
The exposed method consist now of one line of code only !
@Path("/test")
@Produces("application/json")
public class TestEndpoint {
    
    @Inject TestService service;
    
    @POST
    @Path("{pathParam: \\d+}")
    public String testMethod(
            @PathParam("pathParam") Long id,
            @FormParam("date") DateParam date,
            @Context User user) {
        // calling the business logic
        return service.testOperation(id, date.value(), user);
    }
    
}
In the second approach, we need to define a new annotation :
@Target({ ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface LoggedUser {}
Just as seen before, we need to associate the @LoggedUser with a new Injectable for the User type :
@Provider
public class LoggedUserProvider  
    implements Injectable<User>, InjectableProvider<LoggedUser, Type> {

    private final HttpServletRequest r;
    
    public LoggedUserProvider(@Context HttpServletRequest r) {
        this.r = r;
    }

    @Override
    public Injectable<user> getInjectable(ComponentContext cc, LoggedUser a, Type c) {
        if (c.equals(User.class)) {
            return this;
        }
        return null;
    }

    /**
     * From interface InjectableProvider
     * A new Injectable is instanciated per request
     */
    @Override
    public ComponentScope getScope() {
        return ComponentScope.PerRequest;
    }

    /**
     * From interface Injectable
     * Get the logged User associated with the request
     * Or throw an Unauthorized HTTP status code
     */
    @Override
    public User getValue() {
        final User user = (User) r.getSession().getAttribute("user");
        if (user == null) {
            throw new WebApplicationException(Response.Status.UNAUTHORIZED);
        }
        return user;
    }
    
}
This solution is much more flexible than the previous one as we can build more than one annotation for the same Type :
  • @LoggedUser : retrieve the logged in User and throw an HTTP 401 unauthorized status code if no logged in User is associated with the current request.
  • @AdminLoggedUser : retrieve the logged in User and throw an HTTP 401 unauthorized status code if no logged in User is associated with the current request or if the User is not an administrator.
@Path("/test")
@Produces("application/json")
public class TestEndpoint {
    
    @Inject TestService service;
    
    @POST
    @Path("{pathParam: \\d+}")
    public String testMethod(
            @PathParam("pathParam") Long id,
            @FormParam("date") DateParam date,
            @LoggedUser User user) {
        // calling the business logic
        return service.testOperation(id, date.value(), user);
    }
    
}
In a next post, I will cover the integration of Jersey with Guice or any other JSR-330 compliant IOC framework.

dimanche 8 janvier 2012

Generic JPA sharded counter for Google App Engine

One of the drawback of The Google App Engine datastore is the rate at which it can handle update for a single entity or an entity group. The datastore documentation indicates that the maximum rates is arround 5 write operations per second for the same entity or entity group. To overpass this limitation, Google recommend to use horizontal partitioning by using sharded counter. The App Engine documentation provides a simple (an non transactional) JDO implementation of sharded counters. Let see how we can build an reusable and transactional JPA-based sharded counter for the Google App Engine.

The Counter Class

The Entity bellow allow us to implement sharded counters for almost anything :
count :
this attribute will handle the counter value for this sharded counter
refId :
the ID of the Entity this sharded counter is used for
entityClass :
the Entity class this sharded counter is used for
type :
what this counter is counting
@Entity
@SuppressWarnings("serial")
public class Counter implements Serializable {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long count;
    private Long refId;
    private int type;
    private String entityClass;
    
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Long getCount() {
        return count;
    }
    public void setCount(Long count) {
        this.count = count;
    }
    public Long getRefId() {
        return refId;
    }
    public void setRefId(Long refId) {
        this.refId = refId;
    }
    public String getEntityClass() {
        return entityClass;
    }
    public void setEntityClass(String entityClass) {
        this.entityClass = entityClass;
    }
    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }

}

The service and how to increment a counter

In order to increment the number of time a web page is viewed, we would like to do something like :
incrementCounter(WebPage.class, webPageId, WebPage.VIEWS)
Where webPageId is the id of the WebPage Entity and WebPage.VIEW a constant.

The incrementCounter method will work as follow :
  1. Defines a MAX_TRIES values to store the maximum number of time we will try to update an existing sharded counter
  2. Retrives the list of the sharded counter already persisted for the given type
  3. If none exists, a new sharded counter with a value of 1 is persisted for the given type, the method returns
  4. Else, one sharded counter is picked up at random and its value is incremented
  5. If the update fails, the number of remaining tries is decremented
  6. If there is no try left, a new sharded counter with a value of 1 is persisted for the given type, the method returns
  7. Else, start again at step 2
/**
 * Sum all the counter for the given type and entity
 * @param c
 * @param refId
 * @param type
 * @return
 */
protected long counterSum(Class<?> c, Long refId, int type) {
    long sum = 0;
    List<Counter> counters = getCounters(c, refId, type);
    for (Counter counter : counters) {
        sum += counter.getCount();
    }
    return sum;
}

/**
 * Get all the counter for the given type and entity
 * @param c
 * @param refId
 * @param type
 * @return
 */
private List<Counter> getCounters(Class<?> c, Long refId, int type) {
    Map<String, Object> params = new HashMap<String, Object>();
    params.put("refId", refId);
    params.put("type", type);
    params.put("entityClass", c.getSimpleName());
    return list(Counter.class, 
        "SELECT c FROM Counter c WHERE refId = :refId AND type = :type AND entityClass = :entityClass", 
        params);
}

protected void incrementCounter(Class<?> c, Long refId, int type) {
    modifyCounter(c, refId, type, 1);
}

protected void decrementCounter(Class<?> c, Long refId, int type) {
    modifyCounter(c, refId, type, -1);
}

/**
 * Modify the counter value for the given type and entity
 * @param c
 * @param refId
 * @param type
 * @param step
 */
protected void modifyCounter(Class<?> c, Long refId, int type, int step) {
    int tries = MAX_TRIES;
    EntityManager em = getEntityManager();
    while (true) {
        try {
            List<Counter> counters = getCounters(c, refId, type);
            if (counters.size() == 0) {
                newCounter(c, refId, type, step);
                break;
            }
            try {
                em.getTransaction().begin();
                Random generator = new Random();
                int counterNum = generator.nextInt(counters.size());
                Counter counter = counters.get(counterNum);
                counter.setCount(counter.getCount() + step);
                em.merge(counter);
                em.getTransaction().commit();
                break;
            } finally {
                if (em != null) {
                    if (em.getTransaction().isActive()) {
                        em.getTransaction().rollback();
                    }
                }
            }
        } catch (ConcurrentModificationException cme) {
            if (--tries == 0) {
                newCounter(c, refId, type, step);
                break;
            }
        }
    }
}

private void newCounter(Class<?> c, Long refId, int type, int step) {
    EntityManager em = null;
    try {
        em = getEntityManager();
        Counter counter = new Counter();
        counter.setCount(Long.valueOf(step));
        counter.setEntityClass(c.getSimpleName());
        counter.setRefId(refId);
        counter.setType(type);
        em.getTransaction().begin();
        em.persist(counter);
        em.getTransaction().commit();
    } finally {
        if (em != null) {
            if (em.getTransaction().isActive()) {
                em.getTransaction().rollback();
            }
        }
    }
}


protected final <R> List<R> list(Class<R> c, String query, Map<String, Object> parameters) {
    EntityManager em = getEntityManager();
    Query select = em.createQuery(query);
    for (String key : parameters.keySet()) {
        select.setParameter(key, parameters.get(key));
    }
    List<R> list = select.getResultList();
    return list;
}
Enjoy!
Fork me on GitHub