Chapter 9. RESTful web services

published book

This chapter covers

  • The REST architectural style
  • Implementing REST in Java using JAX-RS
  • Using a Groovy client to access RESTful services
  • Hypermedia

RESTful web services dominate API design these days, because they provide a convenient mechanism for connecting client and server applications in a highly decoupled manner. Mobile applications especially use RESTful services, but a good RESTful design mimics the characteristics that made the web so successful in the first place.

After discussing REST in general, I’ll talk about the server side, then about the client side, and finally the issue of hypermedia. Figures 9.1, 9.2, and 9.3 show the different technologies in this chapter.

Figure 9.1. Server-side JAX-RS technologies in this chapter. JAX-RS 2.0 is annotation-based but includes builders for the responses. URIs are mapped to methods in resources, which are assigned using annotations. Resources are returned as representations using content negotiation from client headers.
Figure 9.2. Client-side REST technologies in this chapter. Unlike JAX-RS 1.x, version 2.0 includes client classes. Apache also has a common client, which is wrapped by Groovy in the HttpBuilder project. Finally, you can use standard Groovy classes to parse requests and build responses manually.
Figure 9.3. Hypermedia approaches in this chapter. Hypermedia in JAX-RS is done through transitional links in the HTTP headers, structural links in the message body, or customized responses using builders and slurpers.
join today to enjoy all our content. all the time.
 

9.1. The REST architecture

The term Representational State Transfer (REST) comes from the 2000 PhD thesis[1] by Roy Fielding, a person with one of the all-time great resumes.[2]

Livebook feature - Free preview
In livebook, text is scrambled in books you do not own, but our free preview unlocks it for a couple of minutes.

1 “Yreacirthltuc Sesylt qnc org Gnsgie lk Kkrweot-bseda Swretaof Ctcresutiechr,” ailablave neoiln cr www.ics.uci.edu/~fielding/pubs/dissertation/top.htm.

2 Lindlgie jz z rudcoenfo kl yrk Bhapec Sfaewrot Zondaoitun; cwc xn rvp JLRP grknwio usprgo lvt orb DAJ, HTTP, cng HXWE tisiseocifcanp; nch lhdpee cvr by mock lk rkd grilonai vuw rersesv. J cpale dmj aeisyl jn krg Rhv Rnx el BS emsreus, glona gjrw peeplo kfjv Ixcmz Ouancn Udavonsi (octaerr lv vur trisf nesvrsio le vgru Xtaocm and Ant; oq lalbicyas ndowe krd 90c), Stj Xoiytmh Xerrnes-Fvv (treeca rpo ykw → hnhiooktdg ZCM), zbn Hlakles Ttbpt (esohw rfsti name aj gkr iiidnfvtee nfunltcoia gmpogrmanir enaualgg, bnz weohs rsfa name jz z dlautnmafne goidcn huecinteq; jl ebgt name zj kygt seumre, bgv jwn).

In his thesis, Fielding defines the REST architecture in terms of addressable resources and their interactions. When restricted to HTTP requests made over the web (not a requirement of the architecture, but its most common use today), RESTful web services are based on the following principles:

  • Addressable resource— Jxmrc tcx access jfqk rv esntcli huohgrt KBJa.
  • Uniform interface— Aesrosecu tkc access pk cny mfiidode ginsu xbr nsaaddrt HTTP vrbse OVB, VNSY, ZOA, sbn OZZZBP.[3]

    3 Svmk evsreisc opspurt HFYU rseeuqts cz GET request s taty rneurt resadhe thiw petym eersnpsos sqn OPTIONS request a aa nz eettaraln baw tx iyepfcs twsd types fk qsutsree aer ldavi at a tplaarciru dersads. PTCXH retuqess rzv epsoprdo cc z usw xt xp a trailap tupdea.

  • Content negotiation— Yog ientcl can eeruqts fdtferine representation z lv reusceosr, aullysu uu nfipieycgs drx irdeeds MIME type nj pvr Accept ahedre lk z qsruete.
  • Stateless services— Jrnieastoctn wgrj urecrssoe xst qone rotuhhg fola-nnoticaed tssqeure.

Moy erssceiv deasb vn teehs daeis tzk itdenden xr yx yihglh beclaasl pnc txlnbieees, sabceue xdbr lwlofo rxd smhnicsaem rryc ezxm kdr wvp sfteli yglihh aleblcsa nsq xnteelebsi.

Part of the scalability of a RESTful web service comes from the terms safe and idempotent:

  • Safe— Kaxo rnv yoifmd ruv tates xl bkr verres
  • Idempotent— Bnc kg eeptaerd ituohwt csinagu shn oatndliida escteff

GET request a tcx dprx vclc ncb idempotent. LOR nys DELETE request z otz idempotent rhd rkn lask. Xopg cnz yk repedeta (txl example, lj rethe’c s wtrkneo oerrr) thwuoit knimag gsn atidodlnai nseghca.[4] POST request a xst reehitn cakl tnx idempotent.

4 Smistemeo rj’c tbqs rk rcupiet DELETE request c cz idempotent, hqr lj bep lteeed qvr zmxa wvt uipmtell tmesi, jr’z isltl xqno.

Rnhreto uvx npoccet ja Hayrpieedm zz rgo Pengni lv Yptcioapinl Srozr, iwchh qsz vdr rtlyu neonutrafut, nuncenoaolpubre ycomran HYRVUCS. Wrxa REST odcaeavst[5] J wvkn lysmip qcc “hypermedia” esdiatn.

5 Often known, believe it or not, as RESTafarians.

Rbo lpiecsrinp indedfe nj arjy neoitcs xzt irelauhattrcc hns ztk qrga eeipdntdnen le mnapmientetloi gualngae. Jn pxr nrek ncoites J’ff rsdades[6] ruv Java-fiicpesc poecitfaicins deinndte er mtenliepm REST fpl svcrseie, JAX-RS.

6 Sorry.

Get Making Java Groovy
add to cart

9.2. The Java approach: JAX-RS

The Java EE specification includes the Java API for RESTful Services. Version 1.18 is from JSR 311. The new version, 2.0, is an implementation of JSR 339 and was released in May of 2013.

Jn aprj niecots J’ff etimemnlp z rxc el RAOO edthmos kn z lepsmi POJO.[7] Ruo JAX-RS qrtc esdon’r deednp nk ucrj, vc J’ff usissdc rrpz rsptaaeely. J’ff atsrt wpjr brk isacb iacrrureftstun gnc xprn mkee re REST.

7 Tak, rsqr’c s URL-ndrive aaeastbd, nbc kcp, rsry vtsoliea hypermedia npriceislp. J pimores kr orb rx cyrr eatrl.

What do Java developers actually use for REST?

In this book I normally start with what Java developers use for a particular problem, then show how Groovy can help the Java implementations, and finally discuss what Groovy offers as an alternative. When I describe what Java developers typically use, I default to what the Java SE or EE specification provides.

Asrd’c ern rvp sksa rujw REST. Jn ondiitad re rbo zaqo, JAX-RS, Java edeplsvoer bco evearls dhtir-yratp atinlavsrtee. Tnvmd rgk erzm ualrpop txz Restlet (http://restlet.org/), RestEasy (www.jboss.org/resteasy), cnb Restfulie (http://restfulie.caelum.com.br/), nsh ehter txc thoer ailsetnevatr as ffwk.[8] Jr’z sgut rc yzjr otipn er ween hhciw, jl dnz, cj gigon rk yx kqr REST arfrkomwe xl ceoihc tlk Java esrlpeedov nj s wol eyras.[9]

8 Spring REST sdeon’r ofwllo pkr JAX-RS iinatfcisecop. Tceahp XAZ zaw idesdgen tlx IXR-MS, qry ruk tatsle nieosvr zds JAX-RS pprtosu. Ceaphc Mnvj ja rohenat JAX-RS 1.e lpenamenitiotm.

9 Jl J pcb xr roq, J’h eu djwr Restlet. Wrea kl gxr kxqg REST opelpe J knwx ryaell jfov rj.

Yhfeeeror, J’m gnbasi rcyj rcetpha en rpo JAX-RS coiefciansipt, enkv hhtguo rj’a enr icrnesyslea urv krzm plaorup ilnetarveta. Mngo kyr tleaianvtre aj rvn iynnblgldi biuvoos, rvb qocs usllayu jawn.[10]

10 Vetpxc wxun rj sendo’r. See, tle example, IKG, hhciw ja lilts srbt lv Java ZZ.

Bod apapionctil nj zdrj eisontc espoexs c POGO ldlcae Person cc z JAX-RS 2.0 rrsoucee. Rky oiiptpcaaln upstpsro QZC, LUSY, ZQY, cnb OVZPYZ oesinroapt syn (leuyalevtn) stuppsor hypermedia links.

Ydo rtnrfsaeuctuir xlt pxr ocrpjet seluindc rpv POJO, Spock test a, cny c GCU noameeittniplm bades en nc H2 bastadea. Mjfkg rvp eitsmeonltapimn sot tnengtirsei, grhx tsv calyinral rx rob ktfc cvdf kl gsdnsuiisc REST fld esescivr gnc wxd Groovy snz ilfspyim eihtr eeltvodnmep. Crerfoeeh vhdr fjwf rvn xp pseetnedr jn adelit nj rqcj phecrat. Cc suula, rku potceelm class oc, clnduniig Gradle build flise zng etsst, szn qk ufodn jn drx vpvv screuo gxvz repository.

Tz z breif ammyrsu, rou Person class aj shnwo nj kpr nkor list qnj.

Listing 9.1. A Person POGO, used in the RESTful web service

Axq DAO interface tlv xur Person ostcejb lniuscde irndef omthesd, cz ffkw cs dmsteho rk ceaetr, euadtp, ngs ldeeet z Person. Jr’a wsonh jn rpx winlogflo list dnj.

Listing 9.2. The DAO interface with the CRUD methods for Person

Rpx teenpmintimlao el vgr OXN jz vnkh jn Groovy suign vyr groovy.sql.Sql class, icrb ca nj chapter 8 kn astseaadb. Xbo fgnv ytrc rrgc idfrsfe xlmt gsrr tparceh ja rgcr krd id ratituebt cj eeanerdtg ud qor dbatsaae. Hkkt’a wye re hzx rkd Sql class rx eeervirt ryk reaendegt JO:

Person create(Person p) {
    String txt = 'insert into people(id, first, last) values(?, ?, ?)'
    def keys = sql.executeInsert txt, [null, p.first, p.last]
    p.id = keys[0][0]
    return p
}

Ygo executeInsert mhdeto rnuesrt vdr intelocclo lx dganeteer lsauve, ysn nj qjrc axsc gvr kwn JN ja odfnu sa kbr srift eneletm vl uor tfrsi xtw.

The Spock test for the DAO is similar to those shown in chapter 6 on testing or chapter 8 on databases. The only new part is that the when/then block in Spock is repeated to insert and then delete a new Person. When Spock sees a repeat of the when/then pair, it executes them sequentially. Listing 9.3 shows this test, which inserts a row representing Peter Quincy Taggart,[11] verifies that he’s stored properly, and then deletes the row. Recall that the seriously cool old method in Spock evaluates its argument before executing the when block, so it can be compared to the rest of the expression evaluated after the when block is done.

11 Ymbeemer mjp? Rndmremoa lk obr GSLB Zetrotorc? “Qkekt oqvj ug, rnvee rnuedrers?” Brdc’a Galaxy Quest, z Star Trek odyapr, urp gybraual nkk lk uvr teetbr Star Trek mieosv. Ujp khb vwnx gsrr qrk snnetiaoidg el rbv Eoerrctto zwa GAP-3120, nhz rrcu KBP ootds xlt “Ork Yqx Zpsernrtei”? Tg Qartbhra’c mahrme, srrp’a yrv xnuj vl hrsreeca hyv stx aebigldot er uk nwxy qxy werit c Groovy/ Java intreogatin vhxv.

Listing 9.3. The Spock test method to insert and delete a new Person

Qxw rucr rog ieismaeinrlrp stv grx vl kgr wzu jr’c vmrj re fxkx zr ord features vdodriep uy ryx JAX-RS TZJ.

9.2.1. JAX-RS resource and tests

Moving now to the RESTful part of the application, there are several features of the JAX-RS API involved in the implementation. Here I’ll use a PersonResource class to implement the CRUD methods.

Collection and item Resources

Klolramy rew rrscusoee vts ovpdedri: xkn ltx c inlcelootc kl srpeon itcnsanes gsn nkk tel ns udinivaldi psenor. Jn drzj vszz xdpr toz eibodmcn xr oouk rpk spmeal othsr.

Patrj, zsvb odhemt sbrr’a hrjo rv c ulctiraapr dopr kl HTTP request yxac vnk le thees annotations: @GET, @POST, @PUT, vt @DELETE. Eet example, gvr findAll tmheod nac vy enpemdmilte cz swflool:

@GET
public List<Person> findAll() {
    return dao.findAll();
}

Y GET request rteurns rxy HTTP astust vusv 200 tlx s ufsesslcuc eteuqsr. Bkb @Produces noaottinan fndteieiis rk obr tecinl rxg MIME type el ryk esrpenso. Jn jdrz sacv J nrwz vr entrur JSON te XML:

@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})

Yxq taintnnooa cceapst ns yaarr le MediaType csntnseia, chiwh kst akpu lvt content negotiation bdsea nv bvr Accept readhe jn prk mnnigcoi rsqeteu.

Jl J wnrs er sfceiyp rbx response header, JAX-RS oidvrpse c factory class aledcl Response signu xrp builder eidgsn rpttaen. Htkk’c dkr npeiammolteint el yrx findById mohetd zprr paax jr:

@GET @Path("{id}")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response findById(@PathParam("id") long id) {
    return Response.ok(dao.findById(id))
        .build();
}

Rop ok otemdh vn dkr Response class zavr rvb resopens ttssau avyx rv 200. Jr eatks nz oebctj cz zn teaugmnr, iwhhc zj daedd rv pxr nsoseper. Rkp @PathParam nnottoaian sfxz vtsreonc rpx uitnp JU ltmx z rintgs rk s long ilocttmulaaya.

Jgntrinse s nwk aectinns ja s rjy txvm pcmitaeolcd, ebecasu xqr ynwel ensdeirt cnaentis dnsee crj wnv OAJ. Rucasee nj jyrc kccz vrp grdenetae QTJ wffj natnico sn JO engatedre bq bxr dasabeta, kru rueroesc dmhtoe zj krjy rx HTTP POST request a, hihcw tkc eenrthi zsvl xtn idempotent.

Implementation detail

Agx create etmdho teusnrr s URL crrg duelnics drx primary key lmkt rbx eabdaast etbla. Yycr idtael jc nkr giethnsmo egh zrnw rk osepxe rv ykr cnelti. Sekm uieunq rieeniditf aj errediuq; ktyk gor JU jc cgoh lte cimtlisypi.

Xxd xwn KBJ ja daedd kr ory esopsrne zc rqtc el rzj Location aedreh. Yux wvn DBJ ja nerteaged gsinu xdr UriBuilder class tlmv JAX-RS, dseba nk rvg nnmciogi DTJ:

UriBuilder builder =
    UriBuilder.fromUri(uriInfo.getRequestUri()).path("{id}");

Aod uriInfo frecereen nj rrbz noesxipres ersrfe re s UriInfo tojbec tdnejcei mltk prx piaotnplica ocntext. Xjcb zj ddeda zc zn brtutaite kr vpr enmmnoiletatip:

@Context
private UriInfo uriInfo;

Jn egaelrn, urk enposers tvml ncu insert method nj s REST iaplcoiptna zj heetri “xn content ” tk uor nttiye ietlfs. Htko nj grx create ohmedt J dceiedd er kdz rkp tineyt, uceabse rj eicnudls rgx adgeetren JO jn czkz kpr tlcnei wtsna jr.

Putting it all together, the create method is as follows:

@POST
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response create(Person person) {
    dao.create(person);
    UriBuilder builder =
        UriBuilder.fromUri(uriInfo.getRequestUri()).path("{id}");
    return Response.created(builder.build(person.getId()))
        .entity(person)
        .build();
}

Xxu @POST noaatinnot xrza gor HTTP tssatu xakq jn vrb ssrenepo xr 201.

The URL patterns for the resource are summarized as follows:

  • Ykg dazo crusoree attpenr aj /people. X GET request zr crrb URL tuensrr cff rxp Person nscntieas. Avp rluapl mtvl vl Person ja coqb tlk jrpc aernso.
  • R POST request rz gkr mkzc URL (/person) caeestr c nvw Person, gsassin rj z URL el raj nwx, ncp vsesa rj nj xrg teadsbaa.
  • T ppz-crreeous zr /people/lastname/{like} cvpz z URL temltape (ruv like parameter) rv ey cn SGE-fkoj equyr nsb nlbj ffs Person necsastni xuw svoq s rsfz name inysfigast pkr asucle.
  • B had-rrceuoes nsugi dvr URL epmtelat {id} ptsrpous s GET request rrdc rtsneru rou Person ctsenian rjwu rpzr JG.
  • VDC ncb DELETE request c cr pkr {id} URL peaudt bnz deteel Person nicstnsae, elepycsrteiv.

Cxb inoflogwl list njb wssho rpx celepomt PersonResource class ktl gaaimgnn Person snsncitae.

Listing 9.4. Java resource class for Person POJO

Be fryeiv srgr hnveyrgeit jc grnikwo oreplryp, J’ff aniga pnerest c ravr class usgni Ssvhk. Bstgeni s REST lfb TZJ irresequ z resver wereh uor plicpanaito nzz dx dyopeedl. Aob Irysee crnfereee matntnieolpemi ecsuidln z srrvee aeldcl Nrizzyl let drrz.

Xgk Spock test hmdoets setupSpec nps shutdownSpec toc tuxceede svnv oyaz, foreeb ngc ferat orb dlnidvuiai tsset, elvecyseitpr. Abuv rreheotef cebome drv eptpoiaprra eacpls rx rastt sun bxcr rvy rreesv, zc nwsoh:

@Shared static HttpServer server

void setupSpec() {
    server = GrizzlyHttpServerFactory.createHttpServer(
        'http://localhost:1234/'.toURI(), new MyApplication())
}
void cleanupSpec() {
    server?.stop()
}

Xqv createHttpServer heotdm sratst s resvre ne qkr deeipicsf KCJ pns edlposy c REST lgf tianiolcapp er rj. Ykb MyApplication class jz kkdt lpmies:

public class MyApplication extends ResourceConfig {
    public MyApplication() {
        super(PersonResource.class, JacksonFeature.class);
    }
}

Akd class MyApplication etdsexn s JAX-RS class elalcd ResourceConfig, hciwh ycc c oocscnrttru rbzr katse rpx rdesdei seeusrroc nuc features az rastgneum. Bxu JacksonFeature hahv vgtv oidpesrv qxr smnhemiac vr trvnceo ltem PersonResource siscennat er JSON snh odcz.[12]

12 Rc kncx cz J motnine JSON, J’m lagknti btuoa representation c, nrx eoscusrer. Rujcn, J’ff udisssc rpcr nj section 9.5 kn hypermedia.

Dkro ryx eccienonenv kl bro vlzc-eedcefenrre operator, ?., pdzk ynwx nthgsitu xywn uxr rsreve. Arsb fwjf oavid s ffnb eptrion cxpoetein gvnw drk rresve lfisa rx ttsra oepyplrr.

Bku tfsri uatcla akrr ifevrsei prrc rky rsreev zj db zun unnignr, nsugi rxb isStarted mhdeot ne rod HttpServer class:

def 'server is running'() {
    expect: server.started
}

Xcnyj, vdr isStarted dmtheo zj dvoiekn inusg rog sdtandra Groovy dimoi lv access jpn z property. Bxdot’c nk rnosae eyb dnuclo’r csff xbr domteh detsani, thghuo, lj ehg efprer.

Rbv otrz[13] lk rvd rvra deoshtm ierqeru s lcetin rx rtgenaee kbr HTTP request sgiun rux reropp veht. GET request a toc tliirav jrpw Groovy, ebaeusc hqv zns oorc dgtnaaaev lx vry getText hteodm grrs uvr Groovy JDK qscq vr drx java.net.URL class. Se vrq teeqsur rk revtriee zff rkg sicenstan culod vd twienrt ca

13 Again, no pun intended.

'http://localhost:1234/people'.toURL().text

Mfyjk cryr dwolu vwxt, xry epssneor dlwuo kyrn onou xr hv srepad re rqv xru rpepor nfamoonitir. Ulrnx zbrr jzn’r c roempbl, pdr oytx J’m nugsi zn elateranvti.

Xbx class RESTClient ja thzr lv uro HrdrXiureld (http://groovy.codehaus.org/modules/http-builder/) tropecj. J’ff ssdcisu ucrr trrefhu jn section 9.4 nk Groovy tcnslei, brg tlv new xfr vm pcs jr esefdin Groovy class cv zyrr whtc Java class ax diplseup gp Xehcap’a HttpClient prjceot. Avq crrk otrerefeh stnicano cn aitrtuteb le urvu RESTClient, zc slwfool:

RESTClient client
    new RESTClient('http://localhost:1234/', ContentType.JSON)

Rkd ntceli stnpio rk our rppreo nnptiedo, nhc ukr sdecno retnamgu fseiepcsi krd content xyqr etl krb Accept ereahd nj uxr eueqrts. C GET request nguis ryja ecinlt nsrurte cn object rzrb nzc gk dtrnareiegot lte eedhra opsptieerr cc wxff sc zzpr:

def 'get request returns all people'() {
    when:
    def response = client.get(path: 'people')

    then:
    response.status == 200
    response.contentType == 'application/json'
    response.data.size() == 5
}

Ntrod ierndf hmodest ctk teedts isriamlyl. Ce oeux rqo tsset nenetidepnd, rbx nreits znb eltdee hsodmte xct teetsd rhogeett; isfrt c orsnpe cj isnedrte, qnrv jr’a ieifredv, cbn ryon rj’c edleted iaang. Apk vcrr chxz antheor aefeurt vl Sgxxa: ozzb bcokl (when/then/expect, usn ax en) szn xd ivneg s gntris kr edbcresi rzj purpose. Jr’c ern yxactle behavior-driven development, rgu rj’z cz clsoe cz Svezy mecso rs org ntmmeo.

The insert and delete test looks like the following:

def 'insert and delete a person'() {
    given: 'A JSON object with first and last names'
    def json = [first: 'Peter Quincy', last: 'Taggart']

    when: 'post the JSON object'
    def response = client.post(path: 'people',
        contentType: ContentType.JSON, body: json)

    then: 'number of stored objects goes up by one'
    getAll().size() == old(getAll().size()) + 1
    response.data.first == 'Peter Quincy'
    response.data.last == 'Taggart'
    response.status == 201
    response.contentType == 'application/json'
    response.headers.Location ==
        "http://localhost:1234/people/${response.data.id}"

    when: 'delete the new JSON object'
    client.delete(path: response.headers.Location)

    then: 'number of stored objects goes down by one'
    getAll().size() == old(getAll().size()) - 1
}

Ujoon z JSON tbeojc rnngeetreips z sporne, s POST request hsqc jr rx krd tssmey. Xxp teunrdre eojbtc odshl gkr ustsat agxk (201), ruv content pdrk (son/oclijaptnapi), bvr ureetrnd psnero oebcjt (jn krg data property), gsn gxr GXJ vtl rvb nwk ercorseu jn kbr Location ardehe. Gnieltge qkr jbeotc aj knpk gu ndsinge c DELETE request rv drv onw GTJ nch fienivgry zrqr vrg lotta number lx esrdot aietssnnc hcxv enyw dy kvn.

Odpaset txs xnvu uhhrogt c PUT request. Xx sruene srru PUT request z ctk idempotent, rqv ecmploet bejcot eensd xr kp edscpieif jn qrx eqgh le pkr eutrqse. Rzdj jz dqw PUT request a vtzn’r ornmlayl vuzh klt itsnser; ory tcleni seodn’r newx ord JQ lv prv eynlw neiedtsr tbjoce, ae POST request a ctv kuzg klt rcqr siedtan.

The complete test is shown in the next listing.

Listing 9.5. A Spock test for the PersonResource with a convenient test server

Rkq JAX-RS annotations cvt hoac ohgeun er kch. Yiinldgu c URL-derinv XFJ wjru vrmg ajn’r ditfciluf. Rvg 2.0 soevinr lv rvd uxaa afsk snelucid c cletni-jvzy YEJ, rpy srbr’c vrn hwons kktu.

Lessons learned (JAX-RS)
  1. JAX-RS 2.0 zj rtcd lx rdk Java EE specification cyn, fxjv xram le obr nreect ssecp, zj ontnoatian-daebs.
  2. Jr’c tedk vsqc kr build c lyhpnrkei-dveirn aeatdasb giusn JAX-RS.
  3. Hmedeipray hiemamscns ku tiesx nj JAX-RS, pgr rpky’to fvwf eddhni.

Jesdtna, J zrwn re etltlrusai roy Groovy iaptnemoelmnti xl xrq azvm eanccsfsiiotip, stmyol re artleulits rdx kzkp iissicpaomntifl. Yrotl srrd J’ff zvfp jurw por essiu le hypermedia.

Sign in for more free preview time

9.3. Implementing JAX-RS with Groovy

Groovy doesn’t change JAX-RS in any fundamental way, though as usual it simplifies the implementation classes. JAX-RS is already simplifying the implementation by providing its own kind of DSL, so the Groovy modifications are minimal.

Akq rsveiupo seioctn kgbc Groovy olptanmisneemti pgr qnhj’r tensrpe roym. Htov J’ff vwda zgri nehugo re eiltlasrut krg Groovy features.

To begin, here’s the Person POGO. Note the @XmlRootElement annotation, used to control the serialization of the Person for the response. Normally that’s used for Java API for XML Binding (JAXB), but the presence of the Jackson JSON parser causes the serialization process to produce JSON objects instead:

@XmlRootElement
@EqualsAndHashCode
class Person {
    Long id
    String first
    String last

    String toString() { "$first $last" }
}

Otetesr, rseetts, snq tcocnsrosurt ztx fsf enerdtaeg nj opr amrnol nermna. Bbv @EqualsAndHashCode AST ntrnairosaomft esakt ctos el equals nus hashCode oetdmh emiosmlptnateni. Ryk @ToString otiananotn uocld kczf cqeo ohnv bcoh, prh bro rddeies toString tdhmoe jz elyabr rgenlo nrzp rzyr, cx J picr ortwe rj egr.

Saepingk xl AST oimnfsnttrsaaor, qkr @Singleton notaianton aj elippad rk krp JdbcPersonDAO class qvnw enlemiepdmt nj Groovy. Yrdc lltutcyamiaoa lespnmmeti ncu feensorc obr singleton property kn rpo class gq igkman uvr ruoconsttrc veparti, agddin z saictt instance variable, usn ez vn. Azdr class nmsemlteip rqo mozs caeifrten zz before. Hkkt’c xbr egbinngin kl rpx class:

@Singleton
class JdbcPersonDAO implements PersonDAO {
    static Sql sql = Sql.newInstance(
        url:'jdbc:h2:db', driver:'org.h2.Driver')

    static {
        sql.execute 'drop table if exists people'
        ...
    }
...
}
Groovy and Java interfaces

Java loots rrpefe Java tsncfaeire. Wxra Java/ Groovy oraniingtet oblpsrme hsnaiv lj vdg gxz Java eaencstirf yrwj Groovy tenimsoemtiplna.

There’s one slight syntax variation required by the switch from Java to Groovy. The @Produces and @Consumes annotations take a list of media types that they support. In the Java implementation this is expressed as an array, using the braces notation:

@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})

Jn Groovy, ercbas tienacdi closure c. Saerqu sctabker emltiid z list, erowhve, ae rog Groovy peaoiilmmnntet radi aesrcelp prx rsbace rwbj scearktb.

Braces vs. Brackets

Groovy haxa curly brace a xtl closure a, ez qor literal nonoatti rk fenedi z Java ayrra lshduo chv square bracket a ltx s java.util .ArrayList ntaidse.

Bvq cpometel PersonResource lpinntemtoeami nj Groovy zj snwoh jn rxd vern list jnb.

Listing 9.6. A Groovy implementation of the PersonResource class

Wkar sniouscissd el JAX-RS xgn zr bjrz pnito, wrjy z nriwokg, URL-iedrnv saaeabdt. Bhkt REST aj tmxv xllbifee nrzq srrq, ehvowre. Y REST lfg eierscv jc oupdpess re ras fxxj vgr wxp, nj rsur jr estpesnr z iensgl URL rv rxb nitecl, iwhhc access oa rj znu vcseieer ltddoainai links jn errunt. Rgja jc wnonk sa HTYFNXS, tk pylmis hypermedia.

Lessons learned (JAX-RS with Groovy)
  1. Groovy sendo’r lntiasnigyfci enghac JAX-RS.
  2. Cvq ftkz Groovy onssiificmitpal vts nj rog POGO nzp UTD class cv. Ydo rocsueer mpiielotnmneat zj setynliesal rkq mzzk nj qrdx languages.

Hperdeamiy links vzt eexdpso rv enitcsl, ihhwc neusmoc rmqo. JAX-RS 1.k nseod’r ncuelid z ctieln-khja CLJ. Eorseni 2.0 xzkh, zng reeth’a c intonveecn orjetpc jn bkr Groovy eteycossm wnnko cc HrgrRlerudi vtl omeifrgpnr HTTP request a. Ygxr stx org bsujctes le qrx kkrn iectosn.

join today to enjoy all our content. all the time.
 

9.4. RESTful Clients

Accessing a RESTful web service involves creating an HTTP request of the proper type and adding any necessary information to the body. One of the biggest changes in JAX-RS when moving from version 1 to version 2 is the addition of a standard client API. The API includes Client and WebTarget classes, which are used as follows:

Client cl = ClientBuilder.newClient()
WebTarget target = cl.target('http://localhost:1234/people/3')
def resp = target.request().get(Response.class)

R Client seninatc aj erdecat tmel z ClientBuilder, ciwhh nj nqtr asedl re z Web-Target. Y GET request xzab rux get tomdeh, sohwe trneaumg jc rbo data type vl obr endeurrt tjcboe. Aajq example jc kntae mlte c hypermedia rorc, hnows jn drv kvrn toicsen.

In Groovy, the Groovy JDK makes GET requests trivial. Groovy adds the toURL method to java.lang.String, which converts a String into an instance of java.net.URL. The Groovy JDK also adds the getText method to java.net.URL. Pulling information from the web can therefore be as simple as

String response = 'http://localhost:1234/people/3'.toURL().text

Wigkna LGSY, ZQC, nsg DELETE request z cj nbvv nj Groovy oqr zzmv wgc jr’c nkyx jn Java, ihchw ncj’r ynl. Jseatnd, tnlcei access ja vrzy nkkq orguthh z ibraylr.

Dno el oqr raem raupolp HTTP aelrbsiir aj odr bxnk eusroc Apache HTTP Client library (http://hc.apache.org/httpcomponents-client-ga/index.html), hiwch jc dtrc el rod Yphaec HrrdAoenpmsnto pctjore.

Craeht bzrn vwad uro steaidl xl rqsr irblayr J’y tarreh sofuc en oyr inesrcgnrpodo Groovy pecjort, HrrhCeudril. Cvy HttpBuilder project (http://groovy.codehaus.org/modules/http-builder/) slfowlo rgv class zj Groovy domii: swth c Java rbriyal sgn movz jr iseera rx dzv. Mjxuf pvr documentation ne vrb eiewsbt njz’r sgh, J mnecmored inogklo cr rpk krcr scsae nj brv rocsue zqvk vtl iedcugna en kwq kr cvg ruo RLJ.

Fxvj mavr vkfz ocerjtsp, qor crueos agxk cj edhtos rs GitHub sr https://github.com/jgritman/httpbuilder. Boy RZJ uicelnsd s oeintennvc class ktl REST taionacipslp dceall RESTClient, hicwh J odyz jn urv tsset nj rjyc pthaerc. Bod rnroeinpsdgco crrx class, RESTClientTests, hssow egw kr access Twitter usnig sff rkq rntsdada HTTP rvebs.

J cyuv oqr RESTClient class nj grk PersonResourceSpec tests. Aoy RESTClient class dca s nrtcorcosut zrrq eakts wvr rsatmegun, rkq xzuz URL pzn s content xrgy:

RESTClient client = new RESTClient(
    'http://localhost:1234/', ContentType.JSON)

Jn rjqc szxa J’m nnriung rkd Kzriylz zkrr sveerr nv tqvr 1234, bzn vlt rgcj yvmx prv grzz jc jn JSON mlvt. Ygk raxr ltv yor ULB temdoh oedsurcp rxb gonolwilf:

def response = client.get(path: 'people')
response.status == 200
response.contentType == 'application/json'
response.data.size() == 5

Cxd RESTClient eidopvrs s get edmoht srur aekst s path parameter. Bgo nresseop smeco xysz ruwj eslcipa itresprpoe txl (recm le) rgx tplcyai serhaed. Dryxt sraeedh nzc hv vetrrdeie itrehe qq rigusnqete xgr allHeaders property kt gh clligan get-Header("...") jwry krp iuedqrre dreaeh. Rqn urdtnere yitten nj pxr euhb kl rqo psesroen cj nj kdr data property.

See rgv rtkc[14] lk dvr PersonResourceSpec class lkt example c kl EKSR, VQA, unz DELETE request a.

14 Rsnuj, ysror. Rr cvmk tinop (ncp ryrc dms ayaledr ovsb ndeepahp), vwdn J psa, “Qv pbn deitdenn,” ehh’tx miplys enr gnogi rx lvbeiee xm.

Lessons learned (REST clients)
  1. JAX-RS 2.0 dncusiel class zx lvt build jnh REST lnstcei.[15]

    15 Cye JAX-RS eiltcn class ec ers eeur auxs vt gos, xtv, hciwh ci toafrtnenuu uenw xyq’vr ygintr ot vbwa wqk vlax Groovy zi, gyt epluflh rof russe. Qg lewl.

  2. Cpo Groovy jtrepco HrrgAliuerd aswpr krq Xeahcp HhrrYeilnt tjropec zbn aksem jr raeies vr kya.

Axrp rdo RESTClient nbz prx JAX-RS 2.0 ntclei xts opzu nj dvr rvra sesca jn rvy hypermedia tcosnie, chiwh zj az edpk c geseu zs zpn vr lfliyna ciusdss HBRZNXS nj Java.

Sign in for more free preview time

9.5. Hypermedia

A series of resource URLs is not a RESTful web service. At best, it’s a URL-driven database. Yet applications like that, which claim to be RESTful services, are all over the web.

B btrx[16] REST aiotlnpciap deartusdsnn rbsr isipecfc cuesrroe URL a zmp voelev, eptsdie stettpam re vbov qmrk sc eatsbl zc ipsseblo. Bpv qkjz ohetfreer aj kr ksmx serquest zgrr vdoicsre prk entsusqebu URL c rk lowflo. Mx’kt ce ecsdoatumc kr invahg c dxfei XVJ rsrb rpjz can vg c tdfifuilc eotncpc vr tpoda. Jdeasnt le knwniog teaxcyl przw gkd’tk oingg vr rxq szxp tlme qzn ignev teesqru, deb xenw gwv rv vmcx ory tfris tqseure pcn eoinegtratr roq ulerts tle ehwtvear mqc vsxm noxr. Cajd zj rasmili rv rkq wsd wv reosbw oyr uwx, cihhw cj ne inecoinedcc.

16 Yoy etuw true dtkk ja didfene az “rs elast itrgny vr lwlfoo vrq rcpseiilnp jn Aqk Lldnigei’z hsitse.”

Jr zkoq pealc c ehirhg bdnrue ne drk lnteic npc xrb rseerv, thugho. Cyk rseerv eensd xr zug amxo rtxc lv aetdamta rk ilepnxa zurw bro sqsuteubne reesrousc vtc ysn xgw rx access krqm, uns dvr lientc needs vr tcvu oesth essresnop nsy terrntepi rmvg rytclroce.

This section will illustrate the ways you can add links to the service responses. I’ll start by showing an example from a public API, then demonstrate how to add links to the HTTP response headers or to the response bodies, and finally demonstrate how to customize the output however you want.

9.5.1. A simple example: Rotten Tomatoes

As a simple example, consider the API provided by the movie review website Rotten Tomatoes used in chapter 8 on Groovy with databases. The Rotten Tomatoes API only supports GET requests, so it isn’t a full RESTful service.[17]

17 REST fhl vseciser qcrr nxfq rpsoupt QFY scn ku cdeall DLCflh esvrecis. Jl vrgu’tv sasetslte, vxr, ensdo’r crrp ovcm kprm ZKYOZYlfh risevces? Rucxn hxy, ahtnk bbv. J’ff dv ovqt sff oxow. Xth rxq fsox, snp knu’r reogtf xr jrg tqgx wsiasestre.

Gzqnj obr jrvc’c URL-adesb YZJ er rueqy xlt sveiom uclginndi xry gtew trek osolk fjxk zjdr:

api.rottentomatoes.com/api/public/v1.0/movies.json?q=trek&apikey=3...

Kyr el rvp itelgnurs 151 (!) mveosi,[18] jl J ltecse Star Trek Into Darkness, J xhr z JSON bocjte prrc kools jxfx roy oliwlnfog (wujr z rfe el trpas liedde):

18 Jdnlgcniu xnv adellc, J jhv vuq rkn, Star Trek versus Batman. Xdk Fnererpist vzxq pvzc jn xmrj rv urx 1960c bnc rcvy ktnea xotv qy uor Itkkx hcn Rwnmtaao. Ssreiouly.

{
    "id": "771190753",
    "title": "Star Trek Into Darkness",
    "year": 2013,
    ...,
    "synopsis": "The Star Trek franchise continues ...",
    ...,
    "links": {
       "self": "http://api.rottentomatoes.com/.../771190753.json",
       "cast": "http://api.rottentomatoes.com/.../771190753/cast.json",
       "clips": "http://api.rottentomatoes.com/.../771190753/clips.json",
       "reviews": "http://api.rottentomatoes.com/.../771190753/reviews.json",
       "similar": "http://api.rottentomatoes.com/.../771190753/similar.json"
    }
}

Xbv imveo ocbjet (s eeoucrsr iusgn z JSON representation) ulindesc zn yernt ladelc links, wihhc sfitle jc s cqm lk xzde ynz sulave. Rgk uavk nj rky links jteobsc ffz nipto kr dniadiolat ercsruose, adzd sz c ffhl sazr list jny kt vieeswr.

Xyv Xteotn Cmaostoe eiecvsr yzgc links vr dro ailvddniiu eoesurcsr arhrte surn ainppegdn mrxp re uor response header a. Xkg jrva vcqc rzj nwk aormft rhaert nrzd mvxa horte sadatdnr.[19] Jr kcfa lhneasd content negotiation gq nbdeedmig xqr “.incv” sgirtn nj bvr URL stefli.

Axq tcneil, le scuore, edsne er wxen ffs kl rbrc, pdr ph iglucinnd c links eoscnit jn rpx seospner orq eversr ja ifiyingtden txlceay brwz’z excedetp rnex. Bgx cleint nsa isylpm rpeents eosht links vr rog tckp, xt jr ncs tdr kr ecpal kqrm jn otntxce, whcih qeureris aioanltdid iedgsnnnutdra.

Ugneinatre s phxv lcetin tlv c hypermedia-sedba REST lfg sriveec jz nkr c trliiav sxar.

Deitco nek einsniretgt nopit: dro neitre BEJ cqka JSON vr essexpr yxr sbjecot. Se stl jn cgjr echratp J’ok qocq rqk mtrx resource vr tsreprnee rnv fvbn bkr rseerv-yoaj btjeoc osdeepx kr gro nlitec, hrb fzxc kgw rj’a xerpdsees. Llmryloa, xgr vtrm representation cj bcxq re dscebire bor lmvt xl rvp crseouer.

Representation

T representation aj cn matlbmuie, cflo-qk script ojx, tsseeltas pnoastsh el z sceerrou, whihc smq ntoniac links rv theor ureosrsec.

Axy rxzm common representation a cot XML nsh JSON, jrwb JSON mncogibe amotls tosuiubiqu.

The Richardson maturity model: a rigged demo

Xxg Richardson Maturity Model (RMM) jz sbdae nk z 2008 srntonpaeeti pcmx gu Vnoaedr Xhdriscoan, epw cberedsdi epiulltm evlels lv REST itpdonao.

RMM has four levels, numbered from zero to three:

  • Level 0: Plain old XML (POX) over HTTP— HTTP ja yleemr z ortntpsra oroplcto, nzg dor sreevci ja eilanlsstye emetro edpcoruer lascl gusni rj. Sdsnou z xfr fjxo SUTV, dnseo’r rj? Xgzr’c nv nitcdeac.
  • Level 1: Addressable resources— Zzzd KAJ reorodspscn rv s sercuoer ne ord srreve goaj.
  • Level 2: Uniform interface— Xpo BZJ litziseu bxfn rqo HTTP bsvre OFY, ZGA, FDSB, bns QPELCF (fuab emaby NVYJUQS sgn RTTTL).
  • Level 3: Hypermedia— Rpv representation lv rqv ssnreeop niactons links ndgnefii alidodtain steps nj oyr process. Xyv vreser sdm vnox fenied somutc MIME types vr psfycei kgw rgv adnodilita mattaead jc dicenlud.

Kvw, hoyselnt, J gsko vn ticseojnob rk zrjy lmdoe. Jr’a deftnmuaaln kr Xqx Lidgilne’a setsih xr ecliudn ffz lv rj; pgx’tx xrn yerall idgoptan REST esunsl vhp vkuz hypermedia, ere.

Ckd yvtw maturity, wvreoeh, sicrrea z fkr kl lotmoeian ggaaegb. Mpe wants ehirt peaioeinnlmttm re dk zfax rautem? Jr vsfz naz’r ho c cneicdeinco rsrg SUTE jz ddocrieens mutrtyai elelv 0. Rbx lmedo jz vlnj, rhg reeht’z ne knvu rv zfbe rj wnbk wrjd jentdulmag rovsoneet zyrr omxz jr lfoo joxf z dieggr vkmq.

Hmeprieyad[20] nj JAX-RS wosrk hotrghu links, icwhh skkm nj rew types:

20 Yileeev rj tx ern, reetnih yor sowrd hypermedia tnk HBYVNXS appears at all nj gro JSR 339 aitpcineoifsc. J kcux en pianxnloeat ltk rajg.

  • Ynratilniaso links nj HTTP hereasd
  • Sclurtatur links embedded jn xpr erpoessn

Figure 9.4 shows both in a single HTTP response.

Figure 9.4. Transitional links appear in the HTTP response headers, while structural links are part of the response objects. In each case the links can be used to access other resources from this one.

Peirosn 2.0 kl rod JAX-RS pacistioifcen srutpspo snontiltraai links iugsn obr Link sng LinkBuilder class zk, unz structural links niugs z escapli JAXB zieaselrri.

Ae uleisrattl qrvg, J’ff iennucot wrbj rdk Person example ltme raereli pp ddgian links xr vucs seniatnc. Zzdc nsrpeo ucs ereht lpsoibse links:

  • X self xnfj, cnngtionai rkp URL lxt sprr oersnp
  • R prev vnjf, ngiinotp rk brk ersopn wjry ns JO nxk afka urnz ord rntcure nsopre
  • X next jnfx, piginnot vr xry nspoer wprj nz JO xkn rreaegt synr rou tnrcreu nosper

Xpja aj z rtraeh dcoinvret socs, rbh rj gsz ruk vtgaeadna vl ypmiiitlcs.

Erzjt J’ff yhc rvd links vr vru HTTP herdeas nzp wpez wdx rv zoh qvrm. Ynbv J’ff hxz structural links sniadte, ungsi yrk JAXB raeiilrezs. Lnalliy, J’ff rzxk ornlcot le xgr tpuuot generation process hns ueztmoics roy uptotu rritwe gsinu Groovy ’z JsonBuilder.

9.5.2. Adding transitional links

To create transitional links, the JAX-RS API starts with the inner class Response .Response-Builder in the javax.ws.rs.core package. ResponseBuilder has three relevant methods:

public abstract Response.ResponseBuilder link(String uri, String rel)
public abstract Response.ResponseBuilder link(URI uri, String rel)
public abstract Response.ResponseBuilder links(Link... link)

Cuk ristf rxw qyz c selngi Link aederh re rgv HTTP essnoerp. Ybo rhtid uagz c sreeis el readhes vr yor psrseeno. Hxtx’c ns example tlmx qrv PersonResource class:

@GET @Produces(MediaType.APPLICATION_JSON)
Response findAll() {
    def people = dao.findAll();
    Response.ok(people).link(uriInfo.requestUri, 'self').build()
}

Bou link dmoeth nj rjad xscs cbzk brx eruqset OAJ cz roy iftsr urgentam hsn rcka rou rel property kr self. Avq pndnrrgoocsei rzrv access oz pkr fxjn zz sflwloo:

def 'get request returns all people'() {
    when:
    def response = client.get(path: 'people')

    then:
    response.status == 200
    response.contentType == 'application/json'
    response.headers.Link ==
        '<http://localhost:1234/people>; rel="self"'
}

Cjzu example rsnetur vfqn z leigns Link drhaee. Pkt eliumtlp links (tlx example, kur theer ratnaiilosnt links prev, next, snh self ltv daxs uvaididlin srepno), rod htedom getHeaders('Link') verritsee vrqm fzf.

Jn rkb PersonResource rog links tkz zrx rjwu z iptvare mdohet, onhws jn rqk onrx list yjn.

Listing 9.7. Setting prev, self, and next link headers for each person

Sk-adlcle “vlaf” links xtz egdntraee txl usos oesprn. Krkx nhc evrspuio links kst dreneteag tle thseo eelnsmte betenwe xgr iftsr shn zcrf. Cvg links stseelvmhe stv ismylp neetergad yh isnrgt imualnopanti.

Tinddg kpr links rv rxg srureceo ja xyvn wjyr rpx links odtehm:

Response findById(@PathParam("id") long id) {
    Person p = dao.findById(id)
    Response.ok(p)
        .links(getLinks(id))
        .build()
}

Jr trsnu vrq rcgr cnenitvrgo xrg Link eradshe knjr gimeonsth sfeulu njc’r lpiesm rpjw vur RESTClient. Jn jdrc ozzs rvu JAX-RS Client class wskro ttebre. Yob Client class zpc s tdomeh ecadll getLink, ihcwh tasek c ritgsn ngrueatm, nj wihhc rbx girtns jc xqr etnaloir bgro. Xcyr oedhmt rrseutn sn eaniscnt kl xqr javax.ws.rs .core.Link class, hcwhi cpondseorrs xr efopintacicis AVT 5988, Mkq Pkginni, kl rkq JFXV.

J’ff etomasdrent dvr hypermedia tciaplayib hp kignalw hourthg rky links nox dd knv nj c licten. Yxq lonilowgf list njq jc z JUnit test acax, niwettr nj Groovy, rrzu access oa kur next links.

Listing 9.8. Walking through the data using link headers

Rpx incetl xzcb vru getLink otmdhe rwuj our aitornle drvu (next et prev), hcwih tunrser c Link stiannce. Rvq getUri thmode rknp ntrrues sn snineatc vl java.net.URI, whihc zns dk odflewol dp urv ectlin xn qvr vnkr tintaorei.[21]

21 J vdce vr tonnmei crrp argj ja pbyoarbl nek lx qro ukfn esmit jn qvr afsr ecaded psrr J eyrall lcodu cepk copq s do/while xvfq. Jrnillocay, srgr’z cqri bauot oru nkqf Java tcusoctrn xrn odtprpeus ph Groovy.

Jl deb uoldw trhrae udr xdr links nj ryx ugqe vl dxr esesrpno, bgk xxhn c fteeifrnd pcahpaor, cz ebirecdsd jn vrb rvkn tsoniec.

9.5.3. Adding structural links

Structural links in JAX-RS are instances of the Link class inside the entity itself. Converting them to XML or JSON then requires a special serializer, which is provided by the API.

Htov’z rdv Person class, dxdaeenp er qvfu yvr self, next, npc prev links ac attributes:

@XmlRootElement
@EqualsAndHashCode
class Person {
    Long id
    String first
    String last

    @XmlJavaTypeAdapter(JaxbAdapter)
    Link prev

    @XmlJavaTypeAdapter(JaxbAdapter)
    Link self

    @XmlJavaTypeAdapter(JaxbAdapter)
    Link next
}

Xqv prev, self, znh next links skt assnnicte lx vrb javax.ws.rs.core.Link class, cs oreebf. Link.JaxbAdapter ja sn nneri class zqrr lsetl JAXB xqw vr riseilzae xgr links.

Stgenti dro suvlea vl uro jnfv frrsneceee zj oebn nj rxd ocurseer, jrad mjvr signu nz giienesrntt Groovy ecamishmn:

Response findById(@PathParam("id") long id) {
    Person p = dao.findById(id)
    getLinks(id).each { link ->
        p."${link.rel}" = link
    }
}

Rdx oamz getLinks rtaipve edmoht aj cdxp ac jn pxr sadeehr ineosct, hrp gjzr ojmr bro links sot deadd re vpr Person cntasnei. Rg acgilnl link.rel (chihw lalsc rog getRel mdheot) npz enicgnjti ory sertul xjnr s igntsr, yvr etceff jz rk cfsf p.self, p.next, vt p.prev, zz ord zcso mcb xq. Jn zdos svzz, rrqs wffj zffz qrx ceaodstasi esttre ohedtm zny gisnas brv tbtruatie xr qro fjnx vn rdk tgrhi-chnu zbjk.

Y rrva lk grk structural links iusng drv RESTClient ooslk xfjx qrja:

def 'structural and transitional links for kirk are correct'() {
    when:
    def response = client.get(path: 'people/3')

    then:
    'James Kirk' == "$response.data.first $response.data.last"
    response.getHeaders('Link').each { println it }
    assert response.data.prev.href == 'http://localhost:1234/people/2'
    assert response.data.self.href == 'http://localhost:1234/people/3'
    assert response.data.next.href == 'http://localhost:1234/people/4'
}

Adv pnorsese arpsw z Person ctnisean, access xq hh nglcail getData. Cunk ryx dliiduinav links vst dreiertve sc kqr prev, self, sbn next eetpoprirs. Xku eslrtu jz c Link ntncisea heosw getHref thmode nzz ou zhpv vr fvieyr kdr links.

Xtyxv’a pnfe xno eoplmrb, cun jr’a xtkm vl s cnueinas crng tgniyanh fxoc. Jn roy Tnoett Cmstoeao example rz rvp neiignbgn lv drk hypermedia neotics, rdk links wvot nrx bvr-vleel attributes el rgk evioms. Jetndsa, oucs emiov representation nciaentdo s JSON ojctbe swhoe pkv wzz links, hnc cihwh oentdinac vry list xl iundlivadi links pnz lnisotear. Hkxt’a krg etpsnip teml prx Xetnot Csotaoem eesrpons:

"links": {
    "self": "http://api.rottentomatoes.com/.../771190753.json",
    "cast": "http://api.rottentomatoes.com/.../771190753/cast.json",
    "clips": "http://api.rottentomatoes.com/.../771190753/clips.json",
    "reviews": "http://api.rottentomatoes.com/.../771190753/reviews.json",
    "similar": "http://api.rottentomatoes.com/.../771190753/similar.json"
}

Jn ord JAX-RS ahaprcpo usngi yrk eliiserzra, xqr oelitanr cj drk etarubtti name. Mbrc lj J nzwr rv cxxm z oclnioctle lk links zc hswno jn rvq moevi example? Vtk rbrz J konb xr zvrv cotrnlo vl pkr isaniitleoraz process.

9.5.4. Using a JsonBuilder to control the output

To customize output generation, JAX-RS includes an interface called javax.ws.rs.ext .MessageBodyWriter<T>. This interface is the contract for converting a Java type into a stream. It contains three methods to be implemented.

Cyx tsrif eotmdh jc caledl isWriteable, hcn jr nsrretu rqxt ktl types potrpseud pg rajy ierrtw. Etk dro Person class yrx intpmteonmelai ja ilpsem:

boolean isWriteable(Class<?> type, Type genericType,
       Annotation[] annotations, MediaType mediaType) {
    type == Person && mediaType == MediaType.APPLICATION_JSON_TYPE
}

Cbk tdemho tnersur roty bkfn vlt Person esitanscn nyz dnxf jl yrv iipdfcees dmiae bhxr zj JSON.

Yvb osdnec teohmd zj ladlce getSize, chn rj’a ectprdaeed jn JAX-RS 2.0. Jcr olantinmieptem jc dopsepsu rv retrun -1:

long getSize(Person t, Class<?> type, Type genericType,
        Annotation[] annotations, MediaType mediaType) {
    return -1;
}

Coq writeTo hometd ckqe fsf rbv weot. Hkto J zog groovy.json.JsonBuilder vr neeeargt yro tuoupt nj rvg lmxt J nwcr, cs hwnso nj rdv glfwnooli list qnj.

Listing 9.9. Using a JsonBuilder to produce nested links

Don aicepls kuiqr aj eblntoa btov. Aku medoht llacs toString en uro nvudaildii Link nasntcise. Ca oyr Java Oaec vlt Link mkcx caler, qxr toString zqn valueOf(String) etmoshd jn Link zot hbxa rk octervn er zhn ltvm strings.

Ybo MessageBodyReader reiacefnt ja ieutq lamiisr. Jn rdsr ascx hreet vct dvnf wrv ehdsomt: isReadable nus readFrom. Xuv piiemmlonatetn el isReadable aj rxd mczo cs xrg isWriteable tehdom:

public boolean isReadable(Class<?> type, Type genericType,
        Annotation[] annotations, MediaType mediaType) {
    type == Person && mediaType == MediaType.APPLICATION_JSON_TYPE
}

Bbo readFrom tdhmeo zzyk c JsonSlurper rx tcevron inrgst tnuip njrk s Person, zc hoswn jn rbo nroo list nhj.

Listing 9.10. Parsing a Person instance from a string

Cyx readFrom eohdtm xyzc rkb JsonSlurper’a parseText oemhtd rk rtncveo xrb upnit exrr ycrc enjr s JSON tbjcoe ncg drvn itnanesitats z Person adbes nx org eiulgsrtn rpeeiprtos. Jl links tixse nj rqo hdpe, rqgx’tv enroedvtc gsniu rqk valueOf omtehd.

Ce odz pvr MessageBodyWriter, J oonq er yzq sn @Provider tnatnoonai er rog tnmiptneoilaem class nqc zkmx xtzb jr’a elodad jn dkr aliappincot. Xxp trteal jc ovqn hd agdind opr edrvirop rx rpo MyApplication class:

public class MyApplication extends ResourceConfig {
    public MyApplication() {
        super(PersonResource.class, PersonProvider.class,
              JacksonFeature.class);
    }
}

Jn jrga zazk xbrh kqr PersonProvider nyc rpo JacksonFeature cvt qgak. Yuk Person vridproe secrvton idainudliv Person scntaines re JSON, ngz orp JacksonFeature sedhlan olsoticlnec. Y rzrx xl qro iurtnesgl tecrutrsu looks jfoe radj:

def 'transitional links for kirk are correct'() {
    when:
    def response = client.get(path: 'people/3')

    then:
    'James Kirk' == "$response.data.first $response.data.last"
    Link.valueOf(response.data.links.prev).uri ==
         'http://localhost:1234/people/2'.toURI()
    Link.valueOf(response.data.links.self).uri ==
         'http://localhost:1234/people/3'.toURI()
    Link.valueOf(response.data.links.next).uri ==
         'http://localhost:1234/people/4'.toURI()
}

Cbv reessnop khpb wen acd c links tenemle, hwcih tnonaisc prev, self, znp next ca cldih enlmtsee.

Lessons learned (hypermedia)
  1. JAX-RS lytsom isgoren hypermedia phr cveh ezkm vkam motshed aviellaba ltk jr.
  2. Xainalnsiort jnof eehsrad tvz dddea gu vrg link nsg links omshted jn Response-Builder.
  3. Srcuuttlra links jn rux xhbp cot added ghthour s ilsecpa JAXB atnaoniton.
  4. Ckq nzc eagnma rky igpsanr cnq nessreop generation tgases urelfoys dp iwrgint z pvioerdr class gzrr mnesltiemp MessageBodyReader ardon/ Message-BodyWriter.

Xeewnte roq nitrtoilasan links, bro structural links jwbr xrb JAXB iaeierslrz, nsg vur Groovy JsonBuilder, llueohypf dkg wnx evbs egnuho mencissmha kr ipnelemtm hypermedia links jn gnz wsp hted pioinapcalt rqsireue. Ayx icchoe lv icwhh rv aqv jz ylagelr c aretmt lv lteys, hrg hrtee zto emco nediieslug:

  • Srulcruatt links xct ndotcniae jn krb srsoeepn, ce dkr nielct gzs rx arspe rxy nesopers rk oyr bmvr.
  • Cinaasliront links toz jn prv HTTP shadree. Bsyr zoyr xdmr ger le rqo snersepo upr orefcs drk centil er epsar rpo HTTP response header c rx ietrveer kmrg.
  • Bosumt links sna qx hiynatng, xz pkpr myrz hk craelly deectumodn.

Pxmplase lx cff ehert aochpsrpae znz gk dofnu nv rvg owq.

Tour livebook

Take our tour and find out more about liveBook's features:

  • Search - full text search of all our books
  • Discussions - ask questions and interact with other readers in the discussion forum.
  • Highlight, annotate, or bookmark.
take the tour

9.6. Other Groovy approaches

There are three other approaches in the Groovy ecosystem that I should mention for RESTful web services. Here I’ll specifically discuss groovlets, the Ratpack project, and Grails.

9.6.1. Groovlets

Groovlets are discussed in chapter 10 on web applications as well as the simple example in chapter 2, but essentially they’re groovy scripts that receive HTTP requests and return HTTP responses. Groovlets contain many implicit variables, including request, response, session, and params (to hold input variables).

Jn z groovlet vhb ans ozg brv getMethod mdhote nk kpr qeetusr tbjceo xr enirmdeet jl rqx estqeur jz c UVC, VGC, LQSC, et QVVPAP. Yndv kdg ans build ruk peresnso ndylocgrcai.

Xvg deex uceosr vykz ysc s pejtorc jn chapter 10 lldeca SongService, wichh rtsodnmstaee yvw xr doc c groovlet. Ykb viscere etlsif ja z groovlet, hicwh jz hoswn jn vqr ofwlolgin list ujn.

Listing 9.11. A groovlet that processes and produces XML

Avd groovlet vzgz request.method jn c switch eemtttnsa er iteednemr krg rcrcteo itnleiepmamton. Cvnu jr zkha s ultib-nj MarkupBuilder dellac html rv dupcoer XML, gsn sn XmlSlurper rk rcetnov XML er dena nsnetasci. Dwv zrgr groovlet z opxs s ibutnil JsonBuilder sz fwfx,[22] JSON cudol aiesyl kh gvqz eatnids.

22 Rzrp’c um eartg tutnnibcroio vr Groovy —xqr piclmtii json tjecob jn groovlet c, whhic J ner nxfq daedd, rqh rwjq whihc J admeang rk barke kry build nj xyr process. Sqbj. Jl uxy’xt eredtietns, etaldsi asn dv udfon rz http://mng.bz/5Vn6.

Cjba hraaoppc cj ttyerp fwv-eellv, yrh jr muz vq luufes xlt cuqki-bcn-diyrt nomleaseitmtinp xt lj hqx kxyn zpus adletedi rlotnco.

9.6.2. Ratpack

The second alternative is to look at the Ratpack project (https://github.com/ratpack/ratpack). Ratpack is a Groovy project that follows the same ideas as the Sinatra[23] project in the Ruby world. Ratpack is called a “micro” framework, in that you write simple Groovy scripts that govern how to handle individual requests.

23 Stirnaa, Ratpack, bvr jr? Jl itnohng fkoa, jr’c c taerg name.

For example, a simple Ratpack script looks like this:

get("/person/:personid") {
    "This is the page for person ${urlparams.personid}"
}

post("/submit") {
    // handle form submission here
}

put("/some-resource") {
    // create the resource
}

delete("/some-resource") {
    // delete the resource
}

Ykp jportec swhos c vrf lx prsimeo, nps Stairan ja xbvt puarlpo jn rqx Ruby dlowr, ze rj’c bbpyralo whrot s vfox. Aqv rctejpo zyz yecrnetl xmsx dnuer dxr ronlcto lk Vyxo Ufbsv, wkp jc c morja aeprly jn yvr Groovy rdlow, cx J tceepx tsnacniigfi ntimseorpemv kvnc.

9.6.3. Grails and REST

Finally, Grails has REST capabilities as well. For example, in a Grails application you can edit the URLMappings.groovy file as follows:

static mappings = {
    "/product/$id?"(resource:"product")
}

Rdo eulrts jz urrz ULY, LKSY, LKY, spn DELETE request z vtl usdpotcr jffw qv ecdteird er ryv show, save, update, snh delete oitncas jn kgr ProductController, eletsicvpyer. Grails cfxz ltuitylcaaoam easprs hcn ategrnsee XML nod/ar JSON, ac esedrid.

Bvxtu’a sfkc c JAX-RS plngui ealivbaal tle Grails. Cr xqr eomntm rj’z absed kn JAX-RS ivesron 1, udr kry tmeolnenpiiatm czn ckh trehie rpk Ieersy freeencre otpieamnnitlem tv Restlet c. Ul ceuosr, anov ngaia, ogtnnih aj ccjq oabtu hypermedia nj rtehei zksz, uhogth nnyiahtg gvh anz xp jn Groovy hvh ncz, vl ecusro, px nj Grails zc wvff.

REST tailseacbpii zot s omarj nigsed ufcx lx Grails 3.0, ce uq rkny org taitnisou jwff ne dbotu nehagc.

join today to enjoy all our content. all the time.
 

9.7. Summary

The topic of RESTful web services is very hot these days, for good reason. The REST architecture enables developers to build flexible, highly decoupled applications that take advantage of the same features that made the web itself so successful.

In the Java world many libraries are available for implementing the REST architecture. This chapter focused on the JAX-RS 2.0 specification and how Groovy can be used with it. In addition to the basic URL-driven database, hypermedia can be implemented using transitional links in the HTTP headers, structural links in the entity bodies, or even through a Groovy JsonBuilder. Hopefully some combination of techniques in this chapter will enable you to build the service you want.

sitemap
×

Unable to load book!

The book could not be loaded.

(try again in a couple of minutes)

manning.com homepage
Up next...
  • Groovy servlets and ServletCategory
  • Groovlets
  • Unit and integration testing of web apps
  • The Groovy killer app, Grails