Simple JAX-RS resource to match Can.JS defaults


Can.JS is a nice client side (javascript) library providing a MVC solution out of the box. The model is often backed by some JSON server and in Java(EE) case by JAX-RS.

Can.JS has a shortcut for CRUD models called ‘resource’ based on a default mapping. Let see how to implement it on a Java server side easily.

If you go to can.Model.resource you’ll see that you can define a resource as simply as:

var User = can.Model.extend({ resource: "/api/user" }, {});

And then you can use it to find a user, find all users etc..

User.findOne({id: 473629}, function (userFound) { /* do something */});
User.findAll().then(function (users) { /* do something */});
new User({name: 'Can.JS', first: 'Can', last: 'last'}).create().then(function (createdUser) { /*...*/});
// ...

However, no explicit mapping of operations means your server mapping matches conventions of Can.JS.

To do it with JAX-RS, use this kind of resource:

import javax.transaction.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.Collection;
import java.util.Locale;
import java.util.Optional;

import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;

@Transactional // this sample uses JPA
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("user")
public class UserResource {
    @PersistenceContext
    private EntityManager em;

    @POST
    public UserView post(final UserView view) {
        em.persist(mergeView(view, new User()));
        em.flush();
        return view;
    }

    @PUT
    @Path("{id}")
    public UserView put(@PathParam("id") final String id, final UserView view) {
        return toView(mergeView(view, em.find(User.class, id)));
    }

    @DELETE
    @Path("{id}")
    public void delete(@PathParam("id") final String id) {
        em.remove(em.getReference(User.class, id));
    }

    @GET
    @Path("{id}")
    public UserView get(@PathParam("id") final String id) {
        return toView(em.find(User.class, id));
    }

    @GET
    public Collection getAll() {
        return em.createNamedQuery("User.findAll", User.class)
                .getResultList().stream()
                .map(this::toView)
                .collect(toList());
    }

    private User mergeView(final UserView view, final User user) {
        user.setName(requireNonNull(view.getName())); // all setters actually or a mapper
        return user;
    }

    private UserView toView(final User user) {
        final UserView view = new UserView();
        view.setName(user.getName());
        view.setId(user.getId());
        return view;
    }
}

As you already guessed, this can easily be abstracted requiring you to simply implement the mapping functions…but this is another story 😉

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s