spring rest

53
Craig Walls [email protected] Twitter: @habuma @springsocial http://github.com/habuma Giving Spring some REST

Upload: rubesh

Post on 14-Dec-2015

40 views

Category:

Documents


7 download

DESCRIPTION

Restful API's in Spring Framework by Craig Walls

TRANSCRIPT

Page 1: Spring Rest

Craig [email protected]: @habuma @springsocialhttp://github.com/habuma

Giving Spring some REST

Page 2: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

REST in One Slide

Resources (aka, the “things”)Representations

HTTP Methods (aka, the “verbs”)URIs and URLs

Page 3: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Why REST?

Key piece of the Modern Application puzzleMore APIs / Fewer “pages”

Humans and browsers consume pagesEverything can consume APIs

(incl. browsers, JS, mobile apps, other apps,etc)

Page 4: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Spring’s REST Story

Spring MVC 3.0+Spring HATEOAS

Spring Security for OAuth (S2OAuth)Spring RestTemplate

Spring SocialSpring Data RESTSpring REST Shell

WebSocket/STOMP Support (Spring 4)Spring Boot

Page 5: Spring Rest

Creating REST APIs

Page 6: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Let’s write a simple REST controller...

@Controller@RequestMapping("/books")public class BooksController {!! private BookRepository bookRepository;

! @Inject! public BooksController(BookRepository bookRepository) {! ! this.bookRepository = bookRepository;! !! }

! @RequestMapping(method=RequestMethod.GET)! public @ResponseBody List<Book> allBooks() {! ! return bookRepository.findAll();! }!}

GET http://host/app/books

Page 7: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

How Does @ResponseBody Work?

How does it know how to write it to the response?

Page 8: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Message ConvertersStringHttpMessageConverter

Reads “text/*” into String; writes String into “text/plain”

FormHttpMessageConverterReads/writes “application/x-www-form-urlencoded” from/to MultiValueMap<String,String>

ByteArrayMessageConverterReads “*/*” into byte[]; writes Object as “application/octet-stream”

Jaxb2RootElementHttpMessageConverterReads/writes “text/xml” or “application/xml” from/to JAXB-annotated objects

MappingJacksonHttpMessageConverter / MappingJackson2HttpMessageConverter

Reads/writes “application/json” from/to Objects

SourceHttpMessageConverterReads/writes “text/xml”/”application/xml” from/to javax.xml.transform.Source

ResourceHttpMessageConverterReads/writes org.springframework.core.io.Resource objects

[AtomFeed|RssChannel]HttpMessageConverterReads/writes Rome Feed and RssChannels (application/atom+xml | rss+xml)

Page 9: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Let’s Add Another Endpoint

@Controller@RequestMapping("/books")public class BooksController {!...

! @RequestMapping(value="/{id}", method=RequestMethod.GET)! public @ResponseBody Book bookById(@PathVariable("id") long id) {! ! return bookRepository.findOne(id);! }!}

GET http://host/app/books/{id}

Page 10: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

What About POSTing Resources?

@Controller@RequestMapping("/books")public class BooksController {!...

! @RequestMapping(method=RequestMethod.POST)! public @ResponseBody Book postBook(@RequestBody Book book) {! ! return bookRepository.save(book);! }!

}

POST http://host/app/books

Page 11: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Can We PUT a Resource?

@Controller@RequestMapping("/books")public class BooksController {!...

! @RequestMapping(value="/{id}" method=RequestMethod.PUT)! public void updateBook(@PathVariable("id") long id, @RequestBody Book book) {! ! bookRepository.save(book);! }

}

PUT http://host/app/books/{id}

Page 12: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Deleting a Resource

@Controller@RequestMapping("/books")public class BooksController {!...

! @RequestMapping(value="{id}", method=RequestMethod.DELETE)! public void deleteBook(@PathVariable("id") long id) {! ! bookRepository.delete(id);! }

}

DELETE http://host/app/books/{id}

Page 13: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

There’s More Than the Resource...

@Controller@RequestMapping("/books")public class BooksController {!...

! @RequestMapping(value="/{id}", method=RequestMethod.GET)! public @ResponseBody Book bookById(@PathVariable("id") long id) {! ! return bookRepository.findOne(id);! }!}

What will happen if findById() returns null?

What should happen?

Page 14: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

There’s More Than the Resource...

@Controller@RequestMapping("/books")public class BooksController {!...

! @RequestMapping(method=RequestMethod.POST)! public @ResponseBody Book postBook(@RequestBody Book book) {! ! return bookRepository.save(book);! }!

}

What will the HTTP status code be?

What should it be?

Page 15: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Returning a ResponseEntity

@Controller@RequestMapping("/books")public class BooksController {!...

! @RequestMapping(value="/{id}", method=RequestMethod.GET)! public ResponseEntity<?> bookById(@PathVariable("id") long id) {

Book book = bookRepository.findOne(id);if (book != null) {

return new ResponseEntity<Book>(book, HttpStatus.OK);} else {

Error error = new Error("Book with ID " + id + " not found");return new ResponseEntity<Error>(error, HttpStatus.NOT_FOUND);

}! }!}

Page 16: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Returning a ResponseEntity

@Controller@RequestMapping("/books")public class BooksController {!...

! @RequestMapping(value="/{id}", method=RequestMethod.GET)! public ResponseEntity<Book> bookById(@PathVariable("id") long id) {

Book book = bookRepository.findOne(id);if (book != null) {

return new ResponseEntity<Book>(book, HttpStatus.OK);}throw new BookNotFoundException(id);

! }!

@ExceptionHandler(BookNotFoundException.class)public ResponseEntity<Error> bookNotFound(BookNotFoundException e) {

Error error = new Error("Book with ID " + id + " not found");return new ResponseEntity<Error>(error, HttpStatus.NOT_FOUND);

}}

Page 17: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Returning a ResponseEntity

@Controller@RequestMapping("/books")public class BooksController {!...

! @RequestMapping(method=RequestMethod.POST)! public ResponseEntity<Book> postBook(@RequestBody Book book) {! ! Book newBook = bookRepository.save(book);! ! ResponseEntity<Book> bookEntity =

new ResponseEntity<Book>(newBook, HttpStatus.CREATED);! ! String locationUrl =

ServletUriComponentsBuilder.fromCurrentContextPath().! ! ! ! path("/books/" + newBook.getId()).build().toUriString();! ! bookEntity.getHeaders().setLocation(URI.create(locationUrl));! ! return bookEntity;! }

}

Page 18: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Linking Resources

HATEOAS

Hypermedia As The Engine Of Application State

Responses carry links to related endpoints

API is self-descriptive

Client can “learn” about the API

Page 19: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Self-Describing API

{ "links" : [ { "rel" : "self", "href" : "http://localhost:8080/BookApp/books/5" }, { "rel" : "all", "href" : "http://localhost:8080/BookApp/books/" },

{"rel" : "author","href" : "http://localhost:8080/BookApp/authors/2"

} ],

"id" : 5, "title" : "Spring in Action", ...}

Page 20: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Defining a Resource

public class BookResource extends ResourceSupport { ...}

Page 21: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Adding Links to a Resource

@RequestMapping(value="/{id}", method=RequestMethod.GET)public ResponseEntity<BookResource> bookById(@PathVariable("id") long id) { Book book = bookRepository.findOne(id); if (book != null) {

BookResource resource = bookResourceAssembler.toResource(book); resource.add(ControllerLinkerBuilder.linkTo(BooksController.class) .withRel("all"));

resource.add(ControllerLinkerBuilder.linkTo(AuthorsController.class) .slash(book.getAuthor().getId());

.withRel("author");

return new ResponseEntity<BookResource>(resource, HttpStatus.OK); }

throw new BookNotFoundException(id);}

Page 22: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Assembling a Resource

public class BookResourceAssembler extends ResourceAssemblerSupport<Book, BookResource> {

public BookResourceAssembler() { super(BooksController.class, BookResource.class); }

public BookResource toResource(Book book) { return createResource(book); }

public BookResource instantiateResource(Book book) { return new BookResource(book); }

}

Page 23: Spring Rest

Asynchronous Controllers

Page 24: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Spring + WebSocket + STOMP

Spring 4.0.0.M1: Low-level WebSocket supportSpring 4.0.0.M2: Higher-level, STOMP support

Messaging, not request-handlingSTOMP: Simple Text Oriented Messaging Protocol

Page 25: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Handling messages

@Controllerpublic class PortfolioController {

...

! @MessageMapping(value="/app/trade")! public void executeTrade(Trade trade, Principal principal) {! ! trade.setUsername(principal.getName());! ! this.tradeService.executeTrade(trade);! }

...}

Page 26: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Handling subscriptions

@Controllerpublic class PortfolioController {

...

! @SubscribeEvent("/app/positions")! public List<PortfolioPosition> getPortfolios(Principal principal) throws Exception {

! ! Portfolio portfolio = this.portfolioService.findPortfolio(principal.getName());

! ! return portfolio.getPositions();! }

...}

Page 27: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Handling message exceptions

@Controllerpublic class PortfolioController {

...

! @MessageExceptionHandler! @ReplyToUser(value="/queue/errors")! public String handleException(Throwable exception) {! ! return exception.getMessage();! }

...

}

Page 28: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

On the client side: Receiving messages

var socket = new SockJS('/spring-websocket-portfolio/portfolio');var stompClient = Stomp.over(socket);

...

stompClient.subscribe("/app/positions", function(message) { self.portfolio().loadPositions(JSON.parse(message.body));});

stompClient.subscribe("/topic/price.stock.*", function(message) { self.portfolio().processQuote(JSON.parse(message.body));});

stompClient.subscribe("/queue/errors" + queueSuffix, function(message) { self.pushNotification("Error " + message.body);});

Page 29: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

On the client side: Sending messages

var socket = new SockJS('/spring-websocket-portfolio/portfolio');var stompClient = Stomp.over(socket);

...

var trade = { "action" : self.action(), "ticker" : self.currentRow().ticker, "shares" : self.sharesToTrade()};

stompClient.send("/app/trade", {}, JSON.stringify(trade));

Page 30: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Configuring STOMP in Spring

Well...there are a lot of beans...

It gets better in 4.0.0.RC1 (see SPR-10835)...

But for now...

Page 31: Spring Rest

Securing REST APIs

Page 32: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

OAuth

• An open standard for authorization

• Supported by Facebook, Twitter, LinkedIn, TripIt, Salesforce, and dozens more

• Puts the user in control of what resources are shared

http://oauth.net

Page 33: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

The Many Versions of OAuth

OAuth 1.0

TripIt, NetFlix, DropBox, Gliffy, MySpace, ...

OAuth 1.0a

Twitter, LinkedIn, Evernote, Flickr, Yammer, Yelp!, ...

OAuth 2

Facebook, Foursquare, Google, GitHub, Instagram, ...

Page 34: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

OAuth 2

• Much simpler than OAuth 1.0(a)

• No more request token

• Leverages HTTPS instead of encrypting the token

• No signature or canonicalization of the request

• Much simpler Authorization header

• Scoped authorization

• Short-lived tokens, long-lived authorization

• Separate roles of authorization server/resource server

• Multiple grant types

Page 35: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Authorization Code Grant

• Like OAuth 1.0 flow

• Starts with redirect to provider for authorization

• After authorization, redirects back to client with code query parameter

• Code is exchanged for access token

• Client must be able to keep tokens confidential

• Commonly used for web apps

Page 36: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Implicit Grant

• Simplified authorization flow

• After authorization, redirects back to client with access token in fragment parameter

• Reduced round-trips

• No refresh token support

• Commonly used by in-browser JavaScript apps or widgets

Page 37: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Resource Owner Credentials Grant

• Directly exchanges user’s credentials for an access token

• Useful where the client is well-trusted by the user and where a browser redirect would be awkward

• Commonly used with mobile apps

Page 38: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Client Credentials Grant

• Directly exchanges the client’s credentials for an access token

• For accessing client-owned resources (with no user involvement)

Page 39: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

The OAuth 2 Authorization Header

Authorization: Bearer e139a950-2fc5-4822-9266-8a2b572108c5

Much simpler than OAuth 1.0(a)Differs across OAuth 2 drafts...

Authorization: BEARER e139a950-2fc5-4822-9266-8a2b572108c5

Authorization: OAuth e139a950-2fc5-4822-9266-8a2b572108c5

Authorization: Token token=”e139a950-2fc5-4822-9266-8a2b572108c5”

Drafts 14-current

Drafts 12-13

Draft 10

Drafts 1-9

Page 40: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

OAuth Provider Responsibilities

• Authorization server

• If supporting authorization code and/or implicit grant, must serve an authorization page

• Support an authorization endpoint for all supported grant types

• Not obligated to support all grant types

• Produce and manage tokens

• Resource server

• Validate access tokens on requests to resource endpoints

Page 41: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Spring Security OAuth (S2OAuth)

• Based on Spring Security

• Declarative model for OAuth

• Provider-side support for authorization endpoints, token management, and resource-level security

• Also offers client-side OAuth

• Implemented for both OAuth 1 and OAuth 2

• http://www.springsource.org/spring-security-oauth

Page 42: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Key Pieces of S2OAuth

• Authorization Server

• Implemented as Spring MVC controller

• Handles /oauth/authorize and /oauth/token endpoints

• Resource Server

• Implemented as servlet filters

Page 43: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Configuring the Authorization Server<oauth:authorization-server client-details-service-ref="clientDetails" token-services-ref="tokenServices"> <oauth:authorization-code /> <oauth:implicit /> <oauth:refresh-token /> <oauth:client-credentials /> <oauth:password /></oauth:authorization-server>

<oauth:client-details-service id="clientDetails"> <oauth:client client-id="tonr" secret="secret" resource-ids="sparklr" authorized-grant-types="authorization_code,implicit" authorities="ROLE_CLIENT" scope="read,write" /></oauth:client-details-service>

<bean id="tokenServices" class="o.sf.security.oauth2.provider.token.DefaultTokenServices"> <property name="tokenStore" ref="tokenStore" /> <property name="supportRefreshToken" value="true" /> <property name="clientDetailsService" ref="clientDetails"/></bean>

<bean id="tokenStore" class="o.sf.security.oauth2.provider.token.InMemoryTokenStore" />

Page 44: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Configuring the Authorization Server

<http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="clientAuthenticationManager" entry-point-ref="oauthAuthenticationEntryPoint" xmlns="http://www.springframework.org/schema/security"> <intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />! <anonymous enabled="false" />! <http-basic entry-point-ref="oauthAuthenticationEntryPoint" /></http>

<authentication-manager id="clientAuthenticationManager" xmlns="http://www.springframework.org/schema/security">! <authentication-provider user-service-ref="clientDetailsUserService" /></authentication-manager>

<bean id="clientDetailsUserService" class="o.sf.security.oauth2.provider.client.ClientDetailsUserDetailsService"> <constructor-arg ref="clientDetails" /></bean>

<bean id="oauthAuthenticationEntryPoint" class="o.sf.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"> <property name="realmName" value="sparklr2" /></bean>

Page 45: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Configuring the Resource Server

<http pattern="/photos/**" create-session="never" entry-point-ref="oauthAuthenticationEntryPoint"! access-decision-manager-ref="accessDecisionManager" ! xmlns="http://www.springframework.org/schema/security">! <anonymous enabled="false" />

! <intercept-url pattern="/photos" access="ROLE_USER,SCOPE_READ" />! <intercept-url pattern="/photos/trusted/**" access="ROLE_CLIENT,SCOPE_TRUST" />! <intercept-url pattern="/photos/user/**" access="ROLE_USER,SCOPE_TRUST" />! <intercept-url pattern="/photos/**" access="ROLE_USER,SCOPE_READ" />

! <custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" /></http>

<bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased" xmlns="http://www.springframework.org/schema/beans"> <constructor-arg> <list> <bean class="o.sf.security.oauth2.provider.vote.ScopeVoter" /> <bean class="o.sf.security.access.vote.RoleVoter" /> <bean class="o.sf.security.access.vote.AuthenticatedVoter" /> </list> </constructor-arg></bean>

Page 46: Spring Rest

Consuming REST APIs

Page 47: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

RestTemplate

Handles boilerplate HTTP connection codeKeeps your focus on the resources

Page 48: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Using RestTemplate

RestTemplate restTemplate = new RestTemplate();Book book = new Book("Spring in Action", "Gregg Walls");

Book newBook = restTemplate.postForObject( "http://host/app/books", book, Book.class);

book = restTemplate.getForObject( "http://host/app/books/{id}", Book.class, newBook.getId());

book.setAuthor("Craig Walls");restTemplate.put("http://host/app/books/{id}", Book.class, book.getId());

restTemplate.delete("http://host/app/books/{id}", Book.class, book.getId());

Page 49: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Tweeting with RestTemplate

RestTemplate rest = new RestTemplate();MultiValueMap<String, Object> params = new LinkedMultiValueMap<String, Object>();params.add("status", "Hello Twitter!");rest.postForObject("https://api.twitter.com/1/statuses/update.json", params, String.class);

WARNING: POST request for "https://api.twitter.com/1/statuses/update.json" resulted in 401 (Unauthorized); invoking error handlerorg.springframework.web.client.HttpClientErrorException: 401 Unauthorized! at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:75)! at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:486)! at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:443)! at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)! at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:279)

Oh no!

Page 50: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Configuring Spring Social

@Configuration@EnableJdbcConnectionRepository@EnableTwitter(appId="${twitter.consumerKey}", appSecret="${twitter.consumerSecret}")@EnableFacebook(appId="${facebook.clientId}", appSecret="${facebook.clientSecret}")public class SocialConfig {!! @Inject! private ConnectionFactoryLocator connectionFactoryLocator;!! @Inject ! private ConnectionRepository connectionRepository;

! @Bean! public ConnectController connectController() {! ! return new ConnectController(connectionFactoryLocator, connectionRepository);! }

}

Page 51: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Configuring Spring Social

<facebook:config app-id="${facebook.clientId}" app-secret="${facebook.clientSecret}" app-namespace="socialshowcase" /><twitter:config app-id="${twitter.consumerKey}" app-secret="${twitter.consumerSecret}"/>

<social:jdbc-connection-repository/>!

<bean id="connectController" class="org.springframework.social.connect.web.ConnectController" autowire="constructor" />

Page 52: Spring Rest

Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma

Tweeting with Spring Social

public class TwitterTimelineController {

! private final Twitter twitter;!! @Inject! public TwitterTimelineController(Twitter twitter) {! ! this.twitter = twitter;! }

! @RequestMapping(value="/twitter/tweet", method=RequestMethod.POST)! public String postTweet(String message) {! ! twitter.timelineOperations().updateStatus(message);! ! return "redirect:/twitter";! }

}

Page 53: Spring Rest

Thank you!REST Books Sample (a work in progress)

https://github.com/habuma/rest-books

Spring HATEOAShttps://github.com/SpringSource/spring-hateoas

Spring Security for OAuth (S2OAuth)http://www.springsource.org/spring-security-oauth

Spring Socialhttp://www.springsource.org/spring-security-oauth

Spring Data RESThttp://www.springsource.org/spring-data/rest

Spring REST Shellhttps://github.com/SpringSource/rest-shell