easy rest apis with jersey and restygwt

19
Easy REST APIs with Jersey and RestyGWT David Chandler [email protected] turbomanage.wordpress.com

Upload: david-chandler

Post on 17-Jul-2015

604 views

Category:

Software


5 download

TRANSCRIPT

Easy REST APIs with Jersey and RestyGWT

David [email protected]

why RestyGWT?

Ease of GWT-RPC

Power of Command pattern

Less boilerplate

Easier testing

get started

pom.xml

<dependency> <groupId>org.fusesource.restygwt</groupId> <artifactId>restygwt</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>javax.ws.rs</groupId> <artifactId>jsr311-api</artifactId> <version>1.1.1</version> </dependency>

*.gwt.xml

<inherits name="org.fusesource.restygwt.RestyGWT"/>

https://resty-gwt.github.io/

map JSON

public class YqlResponse { public YqlQuery query; public static class YqlQuery { public YqlResults results; public static class YqlResults { public List<Quote> quote; } }}

no annotations needed

create a service API

@Path("http://query.yahooapis.com/v1/public") public interface QuoteService extends RestService { @GET @Path("yql") public void get( @QueryParam("q") String query, @QueryParam("env") String env, @QueryParam("format") String format, MethodCallback<YqlResponse> callback);}

invoke the serviceprivate static final QuoteService svc = GWT.create(QuoteService.class);@Overridepublic void load(LoaderDemo.QuoteRequest input, final Callback<ListLoadResult<Quote>, Throwable> callback) { svc.get(input.getYql(), ENV, FORMAT, new MethodCallback<YqlResponse>() { @Override public void onFailure(Method method, Throwable throwable) { Info.display("QuoteProxy", "failure"); callback.onFailure(throwable); } @Override public void onSuccess(Method method, YqlResponse yqlResponse) { List<Quote> quotes = yqlResponse.query.results.quote; Info.display("QuoteProxy", "success"); callback.onSuccess(new ListLoadResultBean<Quote>(quotes)); } });}

simple CRUD APIpublic interface RestApi<T> extends RestService { @GET @Path("get") public void get(@QueryParam("id")Long id, MethodCallback<T> callback); @GET @Path("all") public void listAll(MethodCallback<ListResponse<T>> callback); @POST @Path("save") public void save(T obj, MethodCallback<T> callback); @POST @Path("saveMany") public void saveMany(List<T> obj, MethodCallback<Integer> callback); @POST @Path("delete") public void delete(Long id, MethodCallback<Integer> callback);}

not much here!

minimal boilerplate@Path("/api/note") public interface NoteItemRestService extends RestApi<Note> { }private static final NoteItemRestService service = GWT.create(NoteItemRestService.class); public void addNote(Display display, long listId, Note item){ NoteList noteList = App.getNoteListService().getNoteList(listId); // All are 0-based for consistency with GWT constants item.listId = listId; service.save(item, new AppCallback<Note>(display) { @Override public void handleSuccess(Note result) { App.getAppModel().getAllNotes().add(0, result); App.getEventBus().fireEvent(new ShowMessageEvent("Note saved.")); App.getEventBus().fireEvent(new NotesLoadedEvent(App.getAppModel().getAllNotes())); App.getEventBus().fireEvent(new NoteAddedEvent(result)); } });}

public class ProjectEntryPoint implements EntryPoint { private static DispatcherFactory dispatcherFactory = new DispatcherFactory(); private static FilterawareDispatcher dispatcher = dispatcherFactory.cachingDispatcher(); @Override public void onModuleLoad() { // Configure RestyGWT dispatcher.addFilter(new CORSFilter()); Defaults.setDispatcher(dispatcher); LoaderDemo loaderDemo = new LoaderDemo(); RootPanel.get().add(loaderDemo); } }

ListAllNotesAction, ListAllNotesResult, AddNoteAction, AddNoteResult, …

Command pattern

server side@Path("api/note") public class NoteDao extends RestServiceDao<Note>{ private static final Logger LOG = Logger.getLogger(NoteDao.class.getName()); @Override @Path("all") @GET public ListWrapper<Note> findAll() { User user = AuthFilter.getUser(); List<Note> notes = this.listByOwner(user); return new ListWrapper<Note>(notes); }

. . .

}

Objectify + Jersey@Produces(MediaType.APPLICATION_JSON) public class RestServiceDao<T extends Owned> extends ObjectifyDao<T> { public T getForOwner() { User user = AuthFilter.getUser(); T obj = null; try { obj = this.getByOwner(user); return obj; } catch (TooManyResultsException e) { throw new WebApplicationException(e); } } public ListWrapper<T> findAll() { User user = AuthFilter.getUser(); List<T> userAll = this.listByOwner(user); return new ListWrapper<T>(userAll); }

. . .

}

getting Jerseypom.xml

<dependency> <groupId>org.glassfish.jersey.containers</groupId> <!-- if your container implements Servlet API older than 3.0, use "jersey-container-servlet-core" --> <artifactId>jersey-container-servlet-core</artifactId> <version>2.7</version> </dependency> <!-- workaround for https://java.net/jira/browse/JERSEY-2459 --><dependency> <groupId>org.glassfish.hk2</groupId> <artifactId>hk2-api</artifactId> <version>2.3.0-b09</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-json-provider</artifactId> <version>2.3.2</version> </dependency>

Jersey configweb.xml

<servlet> <servlet-name>jerseyServlet</servlet-name> <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class> <init-param> <param-name>jersey.config.server.provider.packages</param-name> <param-value>com.example.listmaker.server</param-value> </init-param> <init-param> <!-- speed up initial Jersey loading by deactivating WADL --> <param-name>jersey.config.server.wadl.disableWadl</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>jersey.config.server.provider.classnames</param-name> <param-value>org.glassfish.jersey.filter.LoggingFilter</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>jerseyServlet</servlet-name> <url-pattern>/listmaker/*</url-pattern> </servlet-mapping>

what about auth?

AuthFilter.java

// if an API call, return JSON responseif (path.startsWith("/listmaker/api")) { ((HttpServletResponse) resp).setStatus(401); resp.setContentType(MediaType.TEXT_PLAIN); resp.getWriter().write("User must log in"); } else { // otherwise redirect httpRes.sendRedirect(LOGIN_FORM);}

<filter> <filter-name>AuthFilter</filter-name> <filter-class>com.turbomanage.gwt.server.servlet.AuthFilter</filter-class> </filter> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/listmaker/*</url-pattern> </filter-mapping>

exception handling@Overridepublic void onFailure(Method method, Throwable throwable) { String url = method.builder.getUrl(); App.getLogger().log(Level.SEVERE, "Error calling service " + url, throwable); try { // Decode the exception if (throwable instanceof FailedStatusCodeException) { FailedStatusCodeException sce = (FailedStatusCodeException) throwable; App.getLogger().log(Level.SEVERE, "Service returned " + sce.getStatusCode() + sce.getMessage()); if (401 == sce.getStatusCode()) { Window.Location.replace(LOGIN_FORM); } else if (500 == sce.getStatusCode()) { if ("UserNotRegisteredException".equals(sce.getMessage())) { Window.Location.replace(SIGNUP_URL); } } } handleFailure(throwable); } finally { reset(null); }}

application error handlingservice.save(item, new AppCallback<Note>(display) { @Override public void handleSuccess(Note result) { . . . } });

public abstract class AppCallback<R> implements MethodCallback<R> { private final Display display; public AppCallback() { this.display = null; } public AppCallback(Display display) { this.display = display; display.startProcessing(); }

. . .

}

finer points

Text, JSON, XML via direct API — res.get()…

CachingRetryingDispatcher

ModelChangeEvent

Objects with final fields (@JsonCreator)

Polymorphism (@JsonSubTypes)

same obj on client / server?

older versions of RestyGWT used Jackson 1.7

RestyGWT 2.0 uses Jackson 2.3

so you can now use the same annotations on client and server

and therefore the same POJOs (yeah)!

use @JsonIgnore for server-only fields (like Ref)

Please rate this session atgwtcreate.com/agenda

src

github.com/turbomanage/listmaker

David [email protected]

resty-gwt.github.io