Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Easy REST APIs with Jersey and RestyGWT

3,404 views

Published on

Presentation from GWT.create 2015 on how to easily build a REST API that can be consumed via GWT or native mobile clients. Presentation video here: http://gwtcreate.com/videos/#rpc-jersey-resty-gwt

Published in: Software
  • Be the first to comment

Easy REST APIs with Jersey and RestyGWT

  1. 1. Easy REST APIs with Jersey and RestyGWT David Chandler turbomanage.wordpress.com
  2. 2. why RestyGWT? Ease of GWT-RPC Power of Command pattern Less boilerplate Easier testing
  3. 3. 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/
  4. 4. 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
  5. 5. 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);
 }

  6. 6. invoke the service private static final QuoteService svc = GWT.create(QuoteService.class);
 
 @Override
 public 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));
 }
 });
 }

  7. 7. simple CRUD API public 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);
 
 }

  8. 8. 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));
 }
 });
 }

  9. 9. 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
  10. 10. 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);
 } . . . }
  11. 11. 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);
 } . . . }

  12. 12. getting Jersey pom.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>

  13. 13. Jersey config web.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>

  14. 14. what about auth? AuthFilter.java // if an API call, return JSON response
 if (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>

  15. 15. exception handling @Override
 public 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);
 }
 }

  16. 16. application error handling service.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();
 } . . . }
  17. 17. finer points Text, JSON, XML via direct API — res.get()… CachingRetryingDispatcher ModelChangeEvent Objects with final fields (@JsonCreator) Polymorphism (@JsonSubTypes)
  18. 18. 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)
  19. 19. Please rate this session at gwtcreate.com/agenda src github.com/turbomanage/listmaker David Chandler turbomanage.wordpress.com resty-gwt.github.io

×