SpringREST
BalajiVaranasi
SudhaBelida
SpringREST
Copyright©2015byBalajiVaranasiandSudhaBelida
Thisworkissubjecttocopyright.AllrightsarereservedbythePublisher,whetherthewholeorpartofthe
materialisconcerned,specificallytherightsoftranslation,reprinting,reuseofillustrations,recitation,
broadcasting,reproductiononmicrofilmsorinanyotherphysicalway,andtransmissionorinformationstorage
andretrieval,electronicadaptation,computersoftware,orbysimilarordissimilarmethodologynowknownor
hereafterdeveloped.Exemptedfromthislegalreservationarebriefexcerptsinconnectionwithreviewsor
scholarlyanalysisormaterialsuppliedspecificallyforthepurposeofbeingenteredandexecutedonacomputer
system,forexclusiveusebythepurchaserofthework.Duplicationofthispublicationorpartsthereofispermitted
onlyundertheprovisionsoftheCopyrightLawofthePublisher’slocation,initscurrentversion,andpermission
forusemustalwaysbeobtainedfromSpringer.PermissionsforusemaybeobtainedthroughRightsLinkatthe
CopyrightClearanceCenter.ViolationsareliabletoprosecutionundertherespectiveCopyrightLaw.
ISBN-13(pbk):978-1-4842-0824-3
ISBN-13(electronic):978-1-4842-0823-6
Trademarkednames,logos,andimagesmayappearinthisbook.Ratherthanuseatrademarksymbolwithevery
occurrenceofatrademarkedname,logo,orimageweusethenames,logos,andimagesonlyinaneditorial
fashionandtothebenefitofthetrademarkowner,withnointentionofinfringementofthetrademark.
Theuseinthispublicationoftradenames,trademarks,servicemarks,andsimilarterms,eveniftheyarenot
identifiedassuch,isnottobetakenasanexpressionofopinionastowhetherornottheyaresubjecttoproprietary
rights.
Whiletheadviceandinformationinthisbookarebelievedtobetrueandaccurateatthedateofpublication,
neithertheauthorsnortheeditorsnorthepublishercanacceptanylegalresponsibilityforanyerrorsoromissions
thatmaybemade.Thepublishermakesnowarranty,expressorimplied,withrespecttothematerialcontained
herein.
ManagingDirector:WelmoedSpahr
LeadEditor:SteveAnglin
TechnicalReviewer:DeepakVohra
EditorialBoard:SteveAnglin,LouiseCorrigan,JonathanGennick,RobertHutchinson,Michelle
Lowman,JamesMarkham,SusanMcDermott,MatthewMoodie,JeffreyPepper,DouglasPundick,
BenRenow-Clarke,GwenanSpearing,SteveWeiss
CoordinatingEditor:MarkPowers
CopyEditor:LauraLawrie
Compositor:SPiGlobal
Indexer:SPiGlobal
Artist:SPiGlobal
DistributedtothebooktradeworldwidebySpringerScience+BusinessMediaNewYork,233SpringStreet,6th
Floor,NewYork,NY10013.Phone1-800-SPRINGER,fax(201)348-4505,e-mailorders-ny@springersbm.com,orvisitwww.springeronline.com.ApressMedia,LLCisaCaliforniaLLCandthesolemember
(owner)isSpringerScience+BusinessMediaFinanceInc(SSBMFinanceInc).SSBMFinanceIncisaDelaware
corporation.
Forinformationontranslations,pleasee-mailrights@apress.com,orvisitwww.apress.com.
ApressandfriendsofEDbooksmaybepurchasedinbulkforacademic,corporate,orpromotionaluse.eBook
versionsandlicensesarealsoavailableformosttitles.Formoreinformation,referenceourSpecialBulkSales–
eBookLicensingwebpageatwww.apress.com/bulk-sales.
Anysourcecodeorothersupplementarymaterialreferencedbytheauthorinthistextisavailabletoreadersat
www.apress.com/9781484208243.Fordetailedinformationabouthowtolocateyourbook’ssourcecode,
gotowww.apress.com/source-code/.
ToOurFamily
ContentsataGlance
AbouttheAuthors
AbouttheTechincalreviewer
Acknowledgments
Introduction
Chapter1:IntroductiontoREST
Chapter2:SpringWebMVCPrimer
Chapter3:RESTfulSpring
Chapter4:BeginningQuickPollApplication
Chapter5:ErrorHandling
Chapter6:DocumentingRESTServices
Chapter7:Versioning,Paging,andSorting
Chapter8:Security
Chapter9:ClientsandTesting
Chapter10:HATEOAS
AppendixA:InstallingcURLonWindows
Index
Contents
AbouttheAuthors
AbouttheTechincalreviewer
Acknowledgments
Introduction
Chapter1:IntroductiontoREST
WhatisREST?
UnderstandingResources
IdentifyingResources
URITemplates
Representation
HTTPMethods
Safety
Idempotency
GET
HEAD
DELETE
PUT
POST
PATCH
HTTPStatusCodes
Richardson’sMaturityModel
LevelZero
LevelOne
LevelTwo
LevelThree
BuildingaRESTfulAPI
Summary
Chapter2:SpringWebMVCPrimer
SpringOverview
DependencyInjection
AspectOrientedProgramming
SpringWebMVCOverview
ModelViewControllerPattern
SpringWebMVCArchitecture
SpringWebMVCComponents
Summary
Chapter3:RESTfulSpring
GeneratingaSpringBootProject
InstallingaBuildTool
GeneratingaProjectusingstart.spring.io
GeneratingaProjectusingSTS
GeneratingaProjectUsingtheCLI
AccessingRESTApplications
Postman
RESTClient
Summary
Chapter4:BeginningQuickPollApplication
IntroducingQuickPoll
DesigningQuickPoll
ResourceIdentification
ResourceRepresentation
EndpointIdentification
ActionIdentification
QuickPollArchitecture
ImplementingQuickPoll
DomainImplementation
RepositoryImplementation
EmbeddedDatabase
APIImplementation
Summary
Chapter5:ErrorHandling
QuickPollErrorHandling
ErrorResponses
InputFieldValidation
ExternalizingErrorMessages
ImprovingRestExceptionHandler
Summary
Chapter6:DocumentingRESTServices
Swagger
IntegratingSwagger
SwaggerUI
CustomizingSwagger
ConfiguringControllers
ConfiguringUI
Summary
Chapter7:Versioning,Paging,andSorting
Versioning
VersioningApproaches
DeprecatinganAPI
QuickPollVersioning
Swaggerconfig
Pagination
PageNumberPagination
LimitOffsetPagination
Cursor-BasedPagination
Time-BasedPagination
PaginationData
QuickPollPagination
ChangingDefaultPageSize
Sorting
SortAscendingorSortDescending
QuickPollSorting
Summary
Chapter8:Security
SecuringRESTServices
Session-basedSecurity
HTTPBasicAuthentication
DigestAuthentication
Certificate-BasedSecurity
XAuth
OAuth2.0
SpringSecurityOverview
SecuringQuickPoll
cURL
UserInfrastructureSetup
UserDetailsServiceImplementation
CustomizingSpringSecurity
SecuringURI
QuickPollOAuth2.0ProviderImplementation
TestingQuickPollOAuth2.0Implementation
Summary
Chapter9:ClientsandTesting
QuickPollJavaClient
RestTemplate
GettingPolls
CreatingaPoll
PUTMethod
DELETEMethod
HandlingPagination
HandlingBasicAuthentication
HandlingOAuth2
TestingRESTServices
SpringTest
UnitTestingRESTControllers
IntegrationTestingRESTControllers
Summary
Chapter10:HATEOAS
HATEOAS
JSONHypermediaTypes
JSONHypermediaTypes
HAL
HATEOASinQuickPoll
Summary
AppendixA:InstallingcURLonWindows
Index
AbouttheAuthors
BalajiVaranasiisasoftwaredevelopmentmanager,author,speaker,andtechnology
entrepreneur.Hehasover14years’experiencedesigninganddevelopinghighperformance,scalableJavaand.NETmobileapplications.Hehasworkedintheareasof
security,Webaccessibility,search,andenterpriseportals.HehasaMaster’sdegreein
computersciencefromUtahStateUniversityandservesasadjunctfacultyatthe
UniversityofPhoenix,teachingprogrammingandinformationsystemcourses.Hehas
authoredApress’sPracticalSpringLDAPandhascoauthoredIntroducingMaven.
SudhaBelidaisaseniorsoftwareengineerandtechnologyenthusiast.Shehasmorethan
sevenyears’experienceworkingwithJavaandJEEtechnologiesandframeworkssuchas
Spring,Hibernate,Struts,andAngularJS.Herinterestslieinentrepreneurshipandagile
methodologiesforsoftwaredesignanddevelopment.ShehasaMaster’sdegreein
computationalsciencefromtheUniversityofUtah.ShehascoauthoredApress’s
IntroducingMavenbook.
AbouttheTechincalreviewer
DeepakVohraisaconsultantandaprincipalmemberoftheNuBean.comsoftware
company.DeepakisaSun-certifiedJavaprogrammerandWebcomponentdeveloper.He
hasworkedinthefieldsofXML,Javaprogramming,andJavaEEforoverfiveyears.
DeepakisthecoauthorofProXMLDevelopmentwithJavaTechnology(Apress,2006).
DeepakisalsotheauthoroftheJDBC4.0andOracleJDeveloperforJ2EEDevelopment,
ProcessingXMLDocumentswithOracleJDeveloper11g,EJB3.0DatabasePersistence
withOracleFusionMiddleware11g,andJavaEEDevelopmentinEclipseIDE(Packt
Publishing).HealsoservedasthetechnicalrevieweronWebLogic:TheDefinitiveGuide
(O’ReillyMedia,2004)andRubyProgrammingfortheAbsoluteBeginner(Cengage
LearningPTR,2007).
Acknowledgments
Thisbookwouldnothavebeenpossiblewithoutthesupportofseveralpeople,andwe
wouldliketotakethisopportunitytosincerelythankthem.
ThankstotheamazingfolksatApress;withoutyou,thisbookwouldnothaveseenthe
lightofday.ThankstoMarkPowersforbeingpatientandkeepingusfocused.Thanksto
MatthewMoodieandLauraLawriefortheirsuggestionsinmakingthisbookbetter.
ThankstoSteveAnglinforhisconstantsupportandtherestoftheApressteaminvolved
inthisproject.
HugethankstoourtechnicalreviewerDeepakVohraforhiseffortsandattentionto
detail.Hisvaluablefeedbackhasledtomanyimprovementsinthebook.
Finally,wewouldliketothankourfriendsandfamilyfortheirconstantsupportand
encouragement.
Introduction
SpringRESTservesasapracticalguidefordesigninganddevelopingRESTfulAPIsusing
thepopularSpringFramework.ThisbookbeginswithabriefintroductiontoREST,
HTTP,andWebinfrastructure.ItthenprovidesdetailedcoverageofseveralSpring
portfolioprojectssuchasSpringBoot,SpringMVC,SpringDataJPA,andSpring
Security.ThebookwalksthroughtheprocessofdesigningandbuildingaREST
applicationwhiletakingadeeperlookintodesignprinciplesandbestpracticesfor
versioning,security,documentation,errorhandling,paging,andsorting.Italsodiscusses
techniquesforbuildingclientsthatconsumeRESTservices.Finally,itcoversSpring
MVCtestframeworksforcreatingunitandintegrationtestsforRESTAPI.
Afterreadingthebook,youwillhavelearned:
AboutRESTfundamentalsandWebinfrastructure
AboutSpringtechnologiessuchasSpringBootandSpringDataJPA
HowtobuildRESTapplicationswithSpringtechnologies
HowtoidentifyRESTresourcesanddesigntheirrepresentations
DesignprinciplesforversioningRESTservices
HowtodocumentRESTservicesusingSwagger
Strategiesforhandlingerrorsandcommunicatingmeaningful
messages
Techniquesforhandlinglargedatasetsusingpagination
SecuringRESTservicesusing“BasicAuth”and“OAuth2.0”
HowtobuildRESTclientsusingRestTemplate
HowtotestRESTservicesusingtheSpringMVCTestframework
HowIsThisBookStructured?
Chapter1startswithanoverviewofREST.WecoverRESTfundamentalsand
abstractionssuchasresourcesandrepresentations.WethendiscussWebinfrastructure
suchasURIs,HTTPmethods,andHTTPresponsecodes.WealsocoverRichardson’s
MaturityModel,whichprovidesaclassificationofRESTservices.
Chapter2providesdetailedcoverageofSpringWebMVC.Webeginwithagentle
introductiontotheSpringFrameworkandcoveritstwoimportantconcepts—Dependency
InjectionandAspectOrientedProgramming.Thenwetakeadeeperlookatthedifferent
componentsthatmakeupSpringWebMVC.
Chapter3introducesSpringBoot,aSpringprojectthatsimplifiesthebootstrappingof
Springapplications.WethenuseSpringBoottobuildaHelloWorldRESTapplication.
Finally,welookatsometoolsthatcanbeusedtoaccessRESTapplications.
Chapter4discussesthebeginningsofaRESTfulapplicationnamedQuickPoll.We
analyzetherequirementsanddesignresourcesandtheirrepresentations.UsingSpring
MVCcomponents,weimplementasetofRESTfulservices.
Chapter5coverserrorhandlinginRESTservices.Well-designed,meaningfulerror
responsesplayanimportantroleintheadoptionofRESTservices.Wedesignacustom
errorresponseforQuickPollandimplementthedesign.Wealsoaddvalidation
capabilitiestotheinputsprovidedbyusers.Finally,welookattechniquesfor
externalizingtheerrormessagestopropertyfiles.
Chapter6beginswithanoverviewoftheSwaggerspecificationanditsassociated
tools/frameworks.WethenimplementSwaggerinQuickPolltogenerateinteractive
documentation.WealsocustomizeSwaggerandSwaggerUItomeetourapplication
requirements.
Chapter7coversthedifferentstrategiesforversioningaRESTAPI.Wethenlookat
implementingversioninginQuickPollusingtheURIversioningapproach.Wealsoreview
thedifferentapproachesfordealingwithlargedatasetsusingpaginationandsorting.
Chapter8beginswithadiscussionofdifferentstrategiesforsecuringRESTservices.
WeprovideadetailedtreatmentofOAuth2andreviewitsdifferentcomponents.Wethen
usetheSpringSecurityframeworktoimplementBasicAuthenticationandOAuth2inthe
QuickPollapplication.
Chapter9coversbuildingRESTclientsandtestingRESTAPIs.WeuseSpring’s
RestTemplatefeaturestobuildaRESTclientthatworkswithdifferentversionsofthe
QuickPollAPI.WethentakeadeeperlookintotheSpringMVCTestframeworkand
examineitscoreclasses.Finally,wewriteunitandintegrationteststotesttheRESTAPI.
Chapter10discussestheHATEOASconstraintthatallowsdevelopersbuildflexible
andlooselycoupledRESTservices.ItalsocoverstheHALhypermediaformat.Wethen
modifytheQuickPollapplicationsuchthatthePollrepresentationsaregenerated
followingHATEOASprinciples.
AppendixAprovidesstep-by-stepinstructionsfordownloadingandinstallingcURL
onaWindowsmachine.Chapter8makesuseofcURLfortestingRESTservices.
TargetAudience
SpringRESTisintendedforenterpriseandWebdevelopersusingJavaandSpringwho
wanttobuildRESTapplications.ThebookrequiresabasicknowledgeofJava,Spring,
andtheWebbutnopriorexposuretoREST.
DownloadingtheSourceCode
Thesourcecodefortheexamplesinthisbookcanbedownloadedfrom
www.apress.com.Detailedinformationregardingthesourcecodewithexamplesfor
thisbookcanbedownloadedfromwww.apress.com/9781484208243.Thesource
codeisalsoavailableonGitHubathttps://github.com/bava/springrestbook.
ThedownloadedsourcecodecontainsanumberoffoldersnamedChapterX,in
whichXrepresentsthecorrespondingchapternumber.EachChapterXfoldercontains
twosubfolders:astarterfolderandafinalfolder.Thestarterfolderhousesa
QuickPollprojectthatyoucanuseasabasistofollowalongthesolutiondescribedinthe
correspondingchapter.Eventhougheachchapterbuildsonthepreviousone,thestarter
projectallowsyoutoskiparoundthebook.Forexample,ifyouareinterestedinlearning
aboutsecurity,youcansimplyloadtheQuickPollapplicationunderthe
Chapter8\starterfolderandfollowthesolutiondescribedinChapter8.Asthe
namesuggests,thefinalfoldercontainstheexpectedendstateforthatchapter.
Chapters1and2don’thaveanyassociatedcode.Therefore,thecorresponding
ChapterXfoldersforthosechapterscontainemptystarterandfinalfolders.In
Chapter3,webuildaHelloWorldapplication,soChapter3’sstarterandfinal
folderscontainthehello-restapplication.StartingfromChapter4,thestarterand
finalfolderscontainQuickPollprojectsourcecode.
ContactingtheAuthors
Wealwayswelcomefeedbackfromourreaders.Ifyouhaveanyquestionsorsuggestions
regardingthecontentsofthisbook,youcancontacttheauthorsat
Balaji@inflinx.comorSudha@inflinx.com.
CHAPTER1
IntroductiontoREST
Inthischapter,wewilllearn:
RESTfundamentals
RESTresourcesandtheirrepresentations
HTTPmethodsandstatuscodes
Richardson’smaturitymodel
Today,theWebhasbecomeanintegralpartofourlives—checkingstatuseson
Facebooktoorderingproductsonlinetocommunicatingviaemail.Thesuccessand
ubiquityoftheWebhasresultedinorganizationsapplyingtheWeb’sarchitectural
principlestobuildingdistributedapplications.Inthischapter,wewilltakeadeepdiveinto
REST,anarchitecturalstylethatformalizestheseprinciples.
WhatisREST?
RESTstandsforREpresentationalStateTransferandisanarchitecturalstylefor
designingdistributednetworkapplications.RoyFieldingcoinedthetermRESTinhis
PhDdissertation1andproposedthefollowingsixconstraintsorprinciplesasitsbasis:
Client-Server—Concernsshouldbeseparatedbetweenclientsand
servers.Thisenablesclientandservercomponentstoevolve
independentlyandinturnallowsthesystemtoscale.
Stateless—Thecommunicationbetweenclientandservershouldbe
stateless.Theserverneednotrememberthestateoftheclient.Instead,
clientsmustincludeallofthenecessaryinformationintherequestso
thatservercanunderstandandprocessit.
LayeredSystem—Multiplehierarchicallayerssuchasgateways,
firewalls,andproxiescanexistbetweenclientandserver.Layerscan
beadded,modified,reordered,orremovedtransparentlytoimprove
scalability.
Cache—Responsesfromtheservermustbedeclaredascacheableor
noncacheable.Thiswouldallowtheclientoritsintermediary
componentstocacheresponsesandreusethemforlaterrequests.This
reducestheloadontheserverandhelpsimprovetheperformance.
UniformInterface—Allinteractionsbetweenclient,server,and
intermediarycomponentsarebasedontheuniformityoftheir
interfaces.Thissimplifiestheoverallarchitectureascomponentscan
evolveindependentlyaslongastheyimplementtheagreed-on
contract.Theuniforminterfaceconstraintisfurtherbrokendowninto
foursubconstraints—resourceidentification,resourcerepresentations,
self-descriptivemessages,andhypermediaastheengineofapplication
stateorHATEOAS.Wewillexaminesomeoftheseguidingprinciples
inthelatersectionsofthischapter.
Codeondemand—Clientscanextendtheirfunctionalityby
downloadingandexecutingcodeondemand.Examplesinclude
JavaScriptscripts,Javaapplets,Silverlight,andsoon.Thisisan
optionalconstraint.
ApplicationsthatadheretotheseconstraintsareconsideredtobeRESTful.Asyou
mighthavenoticed,theseconstraintsdon’tdictatetheactualtechnologytobeusedfor
developingapplications.Instead,adherencetotheseguidelinesandbestpracticeswould
makeanapplicationscalable,visible,portable,reliable,andabletoperformbetter.In
theory,itispossibleforaRESTfulapplicationtobebuiltusinganynetworking
infrastructureortransportprotocol.Inpractice,RESTfulapplicationsleveragefeatures
andcapabilitiesoftheWebanduseHTTPasthetransportprotocol.
TheUniformInterfaceconstraintisakeyfeaturethatdistinguishesRESTapplications
fromothernetwork-basedapplications.UniformInterfaceinaRESTapplicationis
achievedthroughabstractionssuchasresources,representations,URIs,andHTTP
methods.Inthenextsections,wewilllookattheseimportantRESTabstractions.
UnderstandingResources
“ThekeyabstractionofinformationinRESTisaresource.”
—RoyFielding
FundamentaltoRESTistheconceptofresource.Aresourceisanythingthatcanbe
accessedormanipulated.Examplesofresourcesinclude“videos,”“blogentries,”“user
profiles,”“images,”andeventangiblethingssuchaspersonsordevices.Resourcesare
typicallyrelatedtootherresources.Forexample,inanecommerceapplication,acustomer
canplaceanorderforanynumberofproducts.Inthisscenario,theproductresourcesare
relatedtothecorrespondingorderresource.Itisalsopossibleforaresourcetobegrouped
intocollections.Usingthesameecommerceexample,“orders”isacollectionof
individual“order”resources.
IdentifyingResources
Beforewecaninteractandusearesource,wemustbeabletoidentifyit.TheWeb
providestheUniformResourceIdentifier,orURI,foruniquelyidentifyingresources.The
syntaxofaURIis:
scheme:scheme-specific-part
Theschemeandthescheme-specific-partareseparatedusingasemicolon.
Examplesofaschemeincludehttporftpormailtoandareusedtodefinethe
semanticsandinterpretationoftherestoftheURI.TaketheexampleoftheURI
—http://www.apress.com/9781484208427.Thehttpportionoftheexample
isthescheme;itindicatesthataHTTPschemeshouldbeusedforinterpretingtherestof
theURI.TheHTTPscheme,definedaspartofRFC7230,2indicatesthattheresource
identifiedbyourexampleURIislocatedonamachinewithhostnameapress.com.
Table1-1showsexamplesofURIsandthedifferentresourcestheyrepresent.
Table1-1.URIandresourcedescription
URI
ResourceDescription
http://blog.example.com/posts
Representsacollectionofblogpost
resources
http://blog.example.com/posts/1
Representsablogpostresourcewith
identifier“1”;suchresourcesarecalled
singletonresources
http://blog.example.com/posts/1/comments
Representsacollectionofcomments
associatedwiththeblogentryidentified
by“1”;collectionssuchasthesethat
resideunderaresourcearereferredtoas
subcollections
http://blog.example.com/posts/1/comments/245
Representsthecommentresource
identifiedby“245”
EventhoughaURIuniquelyidentifiesaresource,itispossibleforaresourcetohave
morethanoneURI.Forexample,FacebookcanbeaccessedusingURIs
https://www.facebook.comandhttps://www.fb.com.ThetermURIaliases
isusedtorefertosuchURIsthatidentifythesameresources.URIaliasesprovide
flexibilityandaddedconveniencesuchashavingtotypefewercharacterstogettothe
resource.
URITemplates
WhenworkingwithRESTandaRESTAPI,therewillbetimeswhereyouneedto
representthestructureofaURIratherthantheURIitself.Forexample,inablog
application,theURIhttp://blog.example.com/2014/postswouldretrieveall
theblogpostscreatedintheyear2014.Similarly,theURIs
http://blog.example.com/2013/posts,
http://blog.example.com/2012/posts,andsoforthwouldreturnblogposts
correspondingtotheyears2013,2012,andsoon.Inthisscenario,itwouldbeconvenient
foraconsumingclienttoknowtheURIstructure
http://blog.example.com/year/poststhatdescribestherangeofURIsrather
thanindividualURIs.
URItemplates,definedinRFC6570
(http://tools.ietf.org/html/rfc6570),provideastandardizedmechanism
fordescribingURIstructure.ThestandardizedURItemplateforthisscenariocouldbe:
http://blog.example.com/{year}/posts
Thecurlybraces{}indicatethattheyearportionofthetemplateisavariable,often
referredtoasapathvariable.ConsumingclientscantakethisURItemplateasinput,
substitutetheyearvariablewiththerightvalue,andretrievethecorrespondingyear’sblog
posts.Ontheserverside,URLtemplatesallowtheservercodetoparseandretrievethe
valuesofthevariablesorselectedportionsofURIeasily.
Representation
RESTfulresourcesareabstractentities.ThedataandmetadatathatmakeaRESTful
resourceneedstobeserializedintoarepresentationbeforeitgetssenttoaclient.This
representationcanbeviewedasasnapshotofaresource’sstateatagivenpointintime.
Consideradatabasetableinanecommerceapplicationthatstoresinformationaboutall
theavailableproducts.Whenanonlineshopperusestheirbrowsertobuyaproductand
requestsitsdetails,theapplicationwouldprovidetheproductdetailsasaWebpagein
HTML.Now,whenadeveloperwritinganativemobileapplicationrequestsproduct
details,theecommerceapplicationmightreturnthosedetailsinXMLorJSONformat.In
bothscenarios,theclientsdidn’tinteractwiththeactualresource—thedatabaserecordholdingproductdetails.Instead,theydealtwithitsrepresentation.
NoteRESTcomponentsinteractwitharesourcebytransferringitsrepresentations
backandforth.Theyneverdirectlyinteractwiththeresource.
Asnotedinthisproductexample,thesameresourcecanhaveseveralrepresentations.
Theserepresentationscanrangefromtext-basedHTML,XML,andJSONformatsto
binaryformatssuchasPDFs,JPEGs,andMP4s.Itispossiblefortheclienttorequesta
particularrepresentationandthisprocessistermedascontentnegotiation.Herearethe
twopossiblecontentnegotiationstrategies:
PostfixingtheURIwiththedesiredrepresentation—Inthisstrategy,a
clientrequestingproductdetailsinJSONformatwouldusetheURI
http://www.example.com/products/143.json.A
differentclientmightusetheURI
http://www.example.com/products/143.xmltoget
productdetailsinXMLformat.
UsingtheAcceptheader—ClientscanpopulatetheHTTPAccept
headerwiththedesiredrepresentationandsenditalongwiththe
request.TheapplicationhandlingtheresourcewouldusetheAccept
headervaluetoserializetherequestedrepresentation.TheRFC26163
providesadetailedsetofrulesforspecifyingoneormoreformatsand
theirpriorities.
NoteJSONhasbecomethedefactostandardforRESTservices.Alloftheexamplesin
thisbookuseJSONasthedataformatforrequestsandresponses.
HTTPMethods
The“UniformInterface”constraintrestrictstheinteractionsbetweenclientandserver
throughahandfulofstandardizedoperationsorverbs.OntheWeb,theHTTPstandard4
provideseightHTTPmethodsthatallowclientstointeractandmanipulateresources.
SomeofthecommonlyusedmethodsareGET,POST,PUT,andDELETE.Beforewe
delvedeepintoHTTPmethods,let’sreviewtheirtwoimportantcharacteristics—safety
andidempotency.
NoteTheHTTPspecificationusesthetermmethodtodenoteHTTPactionssuchas
GET,PUT,andPOST.However,thetermHTTPverbisalsousedinterchangeably.
Safety
AHTTPmethodissaidtobesafeifitdoesn’tcauseanychangestotheserverstate.
ConsidermethodssuchasGETorHEAD,whichareusedtoretrieve
information/resourcesfromtheserver.Theserequestsaretypicallyimplementedasreadonlyoperationswithoutcausinganychangestotheserver’sstateand,hence,considered
safe.
Safemethodsareusedtoretrieveresources.However,safetydoesn’tmeanthatthe
methodmustreturnthesamevalueeverytime.Forexample,aGETrequesttoretrieve
Googlestockmightresultinadifferentvalueforeachcall.Butaslongasitdidn’talter
anystate,itisstillconsideredsafe.
Inreal-worldimplementations,theremaystillbesideeffectswithasafeoperation.
Considertheimplementationinwhicheachrequestforstockpricesgetsloggedina
database.Fromapuristperspectivewearechangingthestateoftheentiresystem.
However,fromapracticalstandpoint,becausethesesideeffectswerethesole
responsibilityoftheserverimplementation,theoperationisstillconsideredtobesafe.
Idempotency
Anoperationisconsideredtobeidempotentifitproducesthesameserverstatewhether
weapplyitonceoranynumberoftimes.HTTPmethodssuchasGET,HEAD(whichare
alsosafe),PUT,andDELETEareconsideredtobeidempotent,guaranteeingthatclients
canrepeatarequestandexpectthesameeffectasmakingtherequestonce.Thesecond
andsubsequentrequestsleavetheresourcestateinexactlythesamestateasthefirst
requestdid.
Considerthescenarioinwhichyouaredeletinganorderinanecommerceapplication.
Onsuccessfulcompletionoftherequest,theordernolongerexistsontheserver.Hence,
anyfuturerequeststodeletethatorderwouldstillresultinthesameserverstate.By
contrast,considerthescenarioinwhichyouarecreatinganorderusingaPOSTrequest.
Onsuccessfulcompletionoftherequest,anewordergetscreated.Ifyouwereto
re“POST”thesamerequest,theserversimplyhonorstherequestandcreatesaneworder.
BecausearepeatedPOSTrequestcanresultinunforeseensideeffects,POSTisnot
consideredtobeidempotent.
GET
TheGETmethodisusedtoretrievearesource’srepresentation.Forexample,aGETon
theURIhttp://blog.example.com/posts/1returnstherepresentationofthe
blogpostidentifiedby1.Bycontrast,aGETontheURI
http://blog.example.com/postsretrievesacollectionofblogposts.Because
GETrequestsdon’tmodifyserverstate,theyareconsideredtobesafeandidempotent.
AhypotheticalGETrequesttohttp://blog.example.com/posts/1andthe
correspondingresponseareshownhere.
GET/posts/1HTTP/1.1
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/
Accept-Encoding:gzip,deflate
Accept-Language:en-US,en;q=0.5
Connection:keep-alive
Host:blog.example.com
Content-Type:text/html;charset=UTF-8
Date:Sat,10Jan201520:16:58GMT
Server:Apache
<!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<htmlxmlns="http://www.w3.org/1999/xhtml">
<head>
<title>FirstPost</title>
</head>
<body>
<h3>HelloWorld!!</h3>
</body>
</html>
Inadditiontotherepresentation,theresponsetoGETrequestsincludesmetadata
associatedwiththeresource.Thismetadataisrepresentedasasequenceofkeyvaluepairs
calledHTTPheaders.Content-TypeandServerareexamplesoftheheadersthat
youseeinthisresponse.BecausetheGETmethodissafe,responsestoGETrequestscan
becached.
ThesimplicityoftheGETmethodisoftenabusedanditisusedtoperformoperations
suchasdeletingorupdatingaresource’srepresentation.Suchusageviolatesstandard
HTTPsemanticsandishighlydiscouraged.
HEAD
Onoccasions,aclientwouldliketocheckifaparticularresourceexistsanddoesn’treally
careabouttheactualrepresentation.Inanotherscenario,theclientwouldliketoknowifa
newerversionoftheresourceisavailablebeforeitdownloadsit.Inbothcases,aGET
requestcouldbe“heavyweight”intermsofbandwidthandresources.Instead,aHEAD
methodismoreappropriate.
TheHEADmethodallowsaclienttoonlyretrievethemetadataassociatedwitha
resource.Noresourcerepresentationgetssenttotheclient.Thismetadatarepresentedas
HTTPheaderswillbeidenticaltotheinformationsentinresponsetoaGETrequest.The
clientusesthismetadatatodetermineresourceaccessibilityandrecentmodifications.
HereisahypotheticalHEADrequestandtheresponse.
HEAD/posts/1HTTP/1.1
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/
Accept-Encoding:gzip,deflate
Accept-Language:en-US,en;q=0.5
Connection:keep-alive
Host:blog.example.com
Connection:Keep-Alive
Content-Type:text/html;charset=UTF-8
Date:Sat,10Jan201520:16:58GMT
Server:Apache
LikeGET,theHEADmethodisalsosafeandidempotentandresponsescanbecached
ontheclient.
DELETE
TheDELETEmethod,asthenamesuggests,requestsaresourcetobedeleted.On
receivingtherequest,aserverdeletestheresource.Forresourcesthatmighttakealong
timetodelete,theservertypicallysendsaconfirmationthatithasreceivedtherequestand
willworkonit.Dependingontheserviceimplementation,theresourcemayormaynotbe
physicallydeleted.
Onsuccessfuldeletion,futureGETrequestsonthatresourcewouldyielda“Resource
NotFound”errorviaHTTPstatuscode404.Wewillbecoveringstatuscodesinjusta
minute.
Inthisexample,theclientrequestsapostidentifiedby1tobedeleted.Oncompletion,
theservercouldreturnastatuscode200(OK)or204(NoContent),indicatingthatthe
requestwassuccessfullyprocessed.
Delete/posts/1HTTP/1.1
Content-Length:0
Content-Type:application/json
Host:blog.example.com
Similarly,inthisexample,allcommentsassociatedwithpost#2getdeleted.
Delete/posts/2/commentsHTTP/1.1
Content-Length:0
Content-Type:application/json
Host:blog.example.com
BecauseDELETEmethodmodifiesthestateofthesystem,itisnotconsideredtobe
safe.However,theDELETEmethodisconsideredidempotent;subsequentDELETE
requestswouldstillleavetheresourceandthesysteminthesamestate.
PUT
ThePUTmethodallowsaclienttomodifyaresourcestate.Aclientmodifiesthestateofa
resourceandsendstheupdatedrepresentationtotheserverusingaPUTmethod.On
receivingtherequest,theserverreplacestheresource’sstatewiththenewstate.
Inthisexample,wearesendingaPUTrequesttoupdateapostidentifiedby1.The
requestcontainsanupdatedblogpost’sbodyalongwithalloftheotherfieldsthatmake
uptheblogpost.Theserver,onsuccessfulprocessing,wouldreturnastatuscode200,
indicatingthattherequestwasprocessedsuccessfully.
PUT/posts/1HTTP/1.1
Accept:*/*
Content-Type:application/json
Content-Length:65
Host:blog.example.com
BODY
{"title":"FirstPost","body":"UpdatedHelloWorld!!"}
Considerthecaseinwhichwejustwantedtoupdatetheblogposttitle.TheHTTP
semanticsdictatethataspartofthePUTrequestwesendthefullresourcerepresentation,
whichincludestheupdatedtitleaswellasotherattributessuchasblogpostbodyandso
onthatdidn’tchange.However,thisapproachwouldrequirethattheclienthasthe
completeresourcerepresentation,whichmightnotbepossibleiftheresourceisverybig
orhasalotofrelationships.Additionally,thiswouldrequirehigherbandwidthfordata
transfers.So,forpracticalreasons,itisacceptabletodesignyourAPIthattendstoaccept
partialrepresentationsaspartofaPUTrequest.
NoteTosupportpartialupdates,anewmethodcalledPATCHhasbeenaddedaspartof
RFC5789(http://www.ietf.org/rfc/rfc5789.txt).Wewillbelookingat
thePATCHmethodlaterinthischapter.
ClientscanalsousePUTmethodtocreateanewresource.However,itwillonlybe
possiblewhentheclientknowstheURIofthenewresource.Inabloggingapplication,for
example,aclientcanuploadanimageassociatedwithablogpost.Inthatscenario,the
clientdecidestheURLfortheimageasshowninthisexample:
PUThttp://blog.example.com/posts/1/images/author.jpg
PUTisnotasafeoperation,asitchangesthesystemstate.However,itisconsidered
idempotent,asputtingthesameresourceonceormorethanoncewouldproducethesame
result.
POST
ThePOSTmethodisusedtocreateresources.Typically,itisusedtocreateresources
undersubcollections—resourcecollectionsthatexistunderaparentresource.For
example,thePOSTmethodcanbeusedtocreateanewblogentryinablogging
application.Here,“posts”isasubcollectionofblogpostresourcesthatresideunderablog
parentresource.
POST/postsHTTP/1.1
Accept:*/*
Content-Type:application/json
Content-Length:63
Host:blog.example.com
BODY
{"title":"SecondPost","body":"AnotherBlogPost."}
Content-Type:application/json
Location:posts/12345
Server:Apache
UnlikePUT,aPOSTrequestdoesn’tneedtoknowtheURIoftheresource.Theserver
isresponsibleforassigninganIDtotheresourceanddecidingtheURIwheretheresource
isgoingtoreside.Inthepreviousexample,thebloggingapplicationwillprocessthe
POSTrequestandcreateanewresourceunder
http://blog.example.com/posts/12345,where“12345”istheserver
generatedid.TheLocationheaderintheresponsecontainstheURLofthenewly
createdresource.
ThePOSTmethodisveryflexibleandisoftenusedwhennootherHTTPmethod
seemsappropriate.Considerthescenarioinwhichyouwouldliketogenerateathumbnail
foraJPEGorPNGimage.Hereweasktheservertoperformanactionontheimage
binarydatathatwearesubmitting.HTTPmethodssuchasGETandPUTdon’treallyfit
here,aswearedealingwithanRPC-styleoperation.Suchscenariosarehandledusingthe
POSTmethod.
NoteTheterm“controllerresource”hasbeenusedtodescribeexecutableresources
thattakeinputs,performsomeaction,andreturnoutputs.Althoughthesetypesof
resourcesdon’tfitthetrueRESTresourcedefinition,theyareveryconvenienttoexpose
complexoperations.
ThePOSTmethodisnotconsideredsafe,asitchangessystemstate.Also,multiple
POSTinvocationswouldresultinmultipleresourcesbeinggenerated,makingit
nonidempotent.
PATCH
Aswediscussedearlier,theHTTPspecificationrequirestheclienttosendtheentire
resourcerepresentationaspartofaPUTrequest.ThePATCHmethodproposedaspartof
RFC5789(http://tools.ietf.org/html/rfc5789)isusedtoperformpartial
resourceupdates.Itisneithersafenoridempotent.HereisanexamplethatusesPATCH
methodtoupdateablogposttitle.
PATCH/posts/1HTTP/1.1
Accept:*/*
Content-Type:application/json
Content-Length:59
Host:blog.example.com
BODY
{"replace":"title","value":"NewAwesometitle"}
Therequestbodycontainsadescriptionofchangesthatneedtobeperformedonthe
resource.Intheexample,therequestbodyusesthe“replace”commandtoindicate
thatthevalueofthe“title”fieldneedstobereplaced.
Thereisnostandardizedformatfordescribingthechangestotheserveraspartofa
PATCHrequest.Adifferentimplementationmightusethefollowingformattodescribethe
samechange:
{"change":"name","from":"PostTitle","to":"New
AwesomeTitle"}
Currently,thereisaworkinprogress
(http://tools.ietf.org/html/draft-ietf-appsawg-json-patch)for
definingaPATCHformatforJSON.Thislackofstandardhasresultedinimplementations
thatdescribechangesetsinasimplerformat,asshownhere:
{"name":"NewAwesomeTitle"}
CRUDANDHTTPVERBS
Data-drivenapplicationstypicallyusethetermCRUDtoindicatefourbasic
persistencefunctions—Create,Read,Update,andDelete.Somedevelopersbuilding
RESTapplicationshavemistakenlyassociatedthefourpopularHTTPverbsGET,
POST,PUT,andDELETEwithCRUDsemantics.Thetypicalassociationoftenseen
is:
Create->POST
Update->PUT
Read->GET
Delete->DELETE
ThesecorrelationsaretrueforReadandDeleteoperations.However,itisnotas
straightforwardforCreate/UpdateandPOST/PUT.Asyouhaveseenearlierinthis
chapter,PUTcanbeusedtocreatearesourceaslongasidempotencyconstraintis
met.Inthesamewayitwasneverconsiderednon-RESTfulifPOSTisusedfor
update(http://roy.gbiv.com/untangled/2009/it-is-okay-touse-post).ItisalsopossibleforaclienttousePATCHforupdatingaresource.
Therefore,itisimportantforAPIdesignerstousetherightverbsforagiven
operationthansimplyusinga1-1mappingwithCRUD.
HTTPStatusCodes
TheHTTPStatuscodesallowaservertocommunicatetheresultsofprocessingaclient’s
request.Thesestatuscodesaregroupedintothefollowingcategories:
InformationalCodes—Statuscodesindicatingthattheserverhas
receivedtherequestbuthasn’tcompletedprocessingit.These
intermediateresponsecodesareinthe100series.
SuccessCodes—Statuscodesindicatingthattherequesthasbeen
successfullyreceivedandprocessed.Thesecodesareinthe200series.
RedirectionCodes—Statuscodesindicatingthattherequesthasbeen
processed,buttheclientmustperformanadditionalactionto
completetherequest.Theseactionstypicallyinvolveredirectingtoa
differentlocationtogettheresource.Thesecodesareinthe300
series.
ClientErrorCodes—Statuscodesindicatingthattherewasanerroror
aproblemwithclient’srequest.Thesecodesareinthe400series.
ServerErrorCodes—Statuscodesindicatingthattherewasanerror
ontheserverwhileprocessingtheclient’srequest.Thesecodesarein
the500series.
TheHTTPStatuscodesplayanimportantroleinRESTAPIdesignasmeaningful
codeshelpcommunicatetherightstatus,enablingtheclienttoreactappropriately.Table
1-2showssomeoftheimportantstatuscodesintowhichyoutypicallyrun.
Table1-2.HTTPstatuscodesandtheirdescriptions
StatusCode
Description
100(Continue)
Indicatesthattheserverhasreceivedthefirstpartoftherequestandtherestoftherequest
shouldbesent.
200(OK)
Indicatesthatallwentwellwiththerequest.
201(Created)
Indicatesthatrequestwascompletedandanewresourcegotcreated.
202
(Accepted)
Indicatesthatrequesthasbeenacceptedbutisstillbeingprocessed.
204(No
Content)
Indicatesthattheserverhascompletedtherequestandhasnoentitybodytosendtothe
client.
301(Moved
Permanently)
IndicatesthattherequestedresourcehasbeenmovedtoanewlocationandanewURI
needstobeusedtoaccesstheresource.
400(Bad
Request)
Indicatesthattherequestismalformedandtheserverisnotabletounderstandtherequest.
401
(Unauthorized)
Indicatesthattheclientneedstoauthenticatebeforeaccessingtheresource.Iftherequest
alreadycontainsclient’scredentials,thena401indicatesinvalidcredentials(e.g.,bad
password).
403
(Forbidden)
Indicatesthattheserverunderstoodtherequestbutisrefusingtofulfillit.Thiscouldbe
becausetheresourceisbeingaccessedfromablacklistedIPaddressoroutsidethe
approvedtimewindow.
404(Not
Found)
IndicatesthattheresourceattherequestedURIdoesn’texist.
406(Not
Acceptable)
Indicatesthattheserveriscapableofprocessingtherequest;however,thegenerated
responsemaynotbeacceptabletotheclient.Thishappenswhentheclientbecomestoo
pickywithitsacceptheaders.
500(Internal
ServerError)
Indicatesthattherewasanerrorontheserverwhileprocessingtherequestandthatthe
requestcan’tbecompleted.
503(Service
Unavailable)
Indicatesthattherequestcan’tbecompleted,astheserverisoverloadedorgoingthrough
scheduledmaintenance.
Richardson’sMaturityModel
TheRichardson’sMaturityModel(RMM),developedbyLeonardRichardson,classifies
REST-basedWebservicesonhowwelltheyadheretoRESTprinciples.Figure1-1shows
thefourlevelsofthisclassification.
Figure1-1.RMMlevels
RMMcanbevaluableinunderstandingthedifferentstylesofWebservice,their
designs,benefits,andtradeoffs.
LevelZero
Thisisthemostrudimentarymaturitylevelforaservice.ServicesinthisleveluseHTTP
asthetransportmechanismandperformremoteprocedurecallsonasingleURI.
Typically,POSTorGETHTTPmethodsareemployedforservicecalls.SOAP-andXMLRPC-basedWebservicesfallunderthislevel.
LevelOne
ThenextleveladherestotheRESTprinciplesmorecloselyandintroducesmultipleURIs,
oneperresource.Complexfunctionalityofalargeserviceendpointisbrokendowninto
multipleresources.However,servicesinthislayeruseoneHTTPverb,typicallyPOST,to
performalloftheoperations.
LevelTwo
ServicesinthislevelleverageHTTPprotocolandmaketherightuseofHTTPverbsand
statuscodesavailableintheprotocol.WebservicesimplementingCRUDoperationsare
goodexamplesofLevel2services.
LevelThree
ThisisthemostmaturelevelforaserviceandisbuiltaroundthenotionofHypermediaas
theEngineofApplicationState,orHATEOAS.Servicesinthislevelallowdiscoverability
byprovidingresponsesthatcontainlinkstootherrelatedresourcesandcontrolsthattell
theclientwhattodonext.
BuildingaRESTfulAPI
DesigningandimplementingabeautifulRESTfulAPIisnolessthananart.Ittakestime,
effort,andseveraliterations.Awell-designedRESTfulAPIallowsyourendusersto
consumetheAPIeasilyandmakesitsadoptioneasier.Atahighlevel,herearethesteps
involvedinbuildingaRESTfulAPI:
1. IdentifyResources—CentraltoRESTareresources.Westart
modelingdifferentresourcesthatareofinteresttoourconsumers.
Often,theseresourcescanbetheapplication’sdomainorentities.
However,aone-to-onemappingisnotalwaysrequired.
2. IdentifyEndpoints—ThenextstepistodesignURIsthatmap
resourcestoendpoints.InChapter4,wewilllookatbestpractices
fordesigningandnamingendpoints.
3. IdentifyActions—IdentifytheHTTPmethodsthatcanbeusedto
performoperationsontheresources.
4. IdentifyResponses—Identifythesupportedresourcerepresentation
fortherequestandresponsealongwiththerightstatuscodestobe
returned.
Intherestofthebook,wewilllookatbestpracticesfordesigningaRESTfulAPIand
implementingitusingSpringtechnologies.
Summary
RESThasbecomethedefactostandardforbuildingservicestoday.Inthischapter,we
coveredthefundamentalsofRESTandabstractionssuchasresources,representations,
URIs,andHTTPmethodsthatmakeupREST’sUniformInterface.Wealsolookedat
RMM,whichprovidesaclassificationofRESTservices.
Inthenextchapter,wewilltakeadeepdiveintoSpringanditsrelatedtechnologies
thatsimplifyRESTservicedevelopment.
_____________________
1https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm.
2http://tools.ietf.org/html/rfc7230.
3http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1.
4https://www.ietf.org/rfc/rfc2616.txt.
CHAPTER2
SpringWebMVCPrimer
Inthischapter,wewilldiscuss:
Springanditsfeatures
TheModelViewControllerPattern
SpringWebMVCanditscomponents
TheJavaecosystemisfilledwithframeworkssuchasJerseyandRestEasy,which
allowyoutodevelopRESTapplications.SpringWebMVCisonesuchpopularweb
frameworkthatsimplifiesWebandRESTapplicationdevelopment.Webeginthischapter
withanoverviewoftheSpringframeworkandtakeadeepdiveintoSpringWebMVC
anditscomponents.
NoteThisbookdoesn’tgiveacomprehensiveoverviewofSpringandSpringWeb
MVC.RefertoProSpringandProSpringMVCandWebFlow(bothpublishedbyApress)
fordetailedtreatmentoftheseconcepts.
SpringOverview
TheSpringFrameworkhasbecomethedefactostandardforbuildingJava/JavaEE–based
enterpriseapplications.OriginallywrittenbyRodJohnsonin2002,theSpringFramework
isoneofthesuiteofprojectsownedandmaintainedbyPivotalSoftwareInc.
(http://spring.io).Amongmanyotherthings,theSpringFrameworkprovidesa
dependencyinjectionmodel1thatreducesplumbingcodeforapplicationdevelopment,
supportsaspectorientedprogramming(AOP)forimplementingcrosscuttingconcerns,and
makesiteasytointegratewithotherframeworksandtechnologies.TheSpringFramework
ismadeupofdifferentmodulesthatofferservicessuchasdataaccess,instrumentation,
messaging,testing,andWebintegration.ThedifferentSpringFrameworkmodulesand
theirgroupingsareshowninFigure2-1.
Figure2-1.SpringFrameworkmodules
Asadeveloper,youarenotforcedtouseeverythingthattheSpringFrameworkhasto
offer.ThemodularityoftheSpringFrameworkallowsyoutopickandchoosethemodules
basedonyourapplicationneeds.Inthisbook,wewillbefocusingontheWebmodulefor
developingRESTservices.Additionally,wewillbeusingafewotherSpringportfolio
projectssuchasSpringData,SpringSecurity,andSpringBoot.Theseprojectsarebuilton
topoftheinfrastructureprovidedbytheSpringFrameworkmodulesandareintendedto
simplifydataaccess,authentication/authorization,andSpringapplicationcreation.
DevelopingSpring-basedapplicationsrequiresathoroughunderstandingoftwocore
concepts—DependencyInjectionandAspectOrientedProgramming.
DependencyInjection
AttheheartoftheSpringFrameworkliesDependencyInjection(DI).Asthename
suggests,DependencyInjectionallowsdependenciestobeinjectedintocomponentsthat
needthem.Thisrelievesthosecomponentsfromhavingtocreateorlocatetheir
dependencies,allowingthemtobelooselycoupled.
TobetterunderstandDI,considerthescenarioofpurchasingaproductinanonline
retailstore.Completingapurchaseistypicallyimplementedusingacomponentsuchasan
OrderService.TheOrderServiceitselfwouldinteractwithanOrderRepositorythatwould
createorderdetailsinadatabaseandaNotificationComponentthatwouldsendoutthe
orderconfirmationtothecustomer.Inatraditionalimplementation,theOrderService
creates(typicallyinitsconstructor)instancesofOrderRepositoryand
NotificationComponentandusesthem.Eventhoughthereisnothingwrongwiththis
approach,itcanleadtohard-to-maintain,hard-to-test,andhighlycoupledcode.
DI,bycontrast,allowsustotakeadifferentapproachwhendealingwith
dependencies.WithDI,youletanexternalprocesssuchasSpringcreatedependencies,
managedependencies,andinjectthosedependenciesintotheobjectsthatneedthem.So,
withDI,SpringwouldcreatetheOrderRepositoryandNotificationComponentandthen
handoverthosedependenciestotheOrderService.ThisdecouplesOrderServicefrom
havingtodealwithOrderRepository/NotificationComponentcreation,makingiteasierto
test.Itallowseachcomponenttoevolveindependently,makingdevelopmentand
maintenanceeasier.Also,itmakesiteasiertoswapthesedependencieswithdifferent
implementationsorusethesecomponentsinadifferentcontext.
AspectOrientedProgramming
AspectOrientedProgramming(AOP)isaprogrammingmodelthatimplements
crosscuttinglogicorconcerns.Logging,transactions,metrics,andsecurityaresome
examplesofconcernsthatspan(crosscut)differentpartsofanapplication.Theseconcerns
don'tdealwithbusinesslogicandareoftenduplicatedacrosstheapplication.AOP
providesastandardizedmechanismcalledanaspectforencapsulatingsuchconcernsina
singlelocation.Theaspectsarethenweavedintootherobjectssothatthecrosscutting
logicisautomaticallyappliedacrosstheentireapplication.
SpringprovidesapureJava-basedAOPimplementationthroughitsSpringAOP
module.SpringAOPdoesnotrequireanyspecialcompilationnorchangestotheclass
loaderhierarchy.Instead,SpringAOPusesproxiesforweavingaspectsintoSpringbeans
atruntime.Figure2-2providesarepresentationofthisbehavior.Whenamethodonthe
targetbeangetscalled,theproxyinterceptsthecall.Itthenappliestheaspectlogicand
invokesthetargetbeanmethod.
Figure2-2.SpringAOPProxy
Springprovidestwo-proxyimplementations—JDKdynamicproxyandCGLIBproxy.
Ifthetargetbeanimplementsaninterface,SpringwilluseJDKdynamicproxytocreate
theAOPproxy.Iftheclassdoesn'timplementaninterface,SpringusesCGLIBtocreatea
proxy.
SpringWebMVCOverview
SpringWebMVC,partoftheSpringFramework’sWebmodule,isapopulartechnology
forbuildingWeb-basedapplications.Itisbasedonthemodel-view-controllerarchitecture
andprovidesarichsetofannotationsandcomponents.Overtheyears,theframeworkhas
evolved;itcurrentlyprovidesarichsetofconfigurationannotationsandfeaturessuchas
flexibleviewresolutionandpowerfuldatabinding.
ModelViewControllerPattern
TheModelViewController,orMVC,isanarchitecturalpatternforbuildingdecoupled
Webapplications.ThispatterndecomposestheUIlayerintothefollowingthree
components:
Model—Themodelrepresentsdataorstate.InaWeb-based
bankingapplication,informationrepresentingaccounts,
transactions,andstatementsareexamplesofthemodel.
View—Providesavisualrepresentationofthemodel.Thisiswhat
theuserinteractswithbyprovidinginputsandviewingtheoutput.
Inourbankingapplication,Webpagesshowingaccountsand
transactionsareexamplesofviews.
Controller—Thecontrollerisresponsibleforhandlinguseractions
suchasbuttonclicks.Ittheninteractswithservicesorrepositories
topreparethemodelandhandsthepreparedmodelovertoan
appropriateview.
Eachcomponenthasspecificresponsibility.Theinteractionbetweenthemisshownin
Figure2-3.TheinteractionbeginswiththeControllerpreparingthemodelandselecting
anappropriateviewtoberendered.TheViewusesthedatafromthemodelforrendering.
FurtherinteractionswiththeViewaresenttotheController,whichstartstheprocessall
overagain.
Figure2-3.ModelViewControllerinteraction
SpringWebMVCArchitecture
Spring’sWebMVCimplementationrevolvesaroundtheDispatcherServlet—an
implementationoftheFrontControllerPattern2thatactsasanentrypointforhandling
requests.SpringWebMVC’sarchitectureisshowninFigure2-4.
Figure2-4.SpringWebMVC'sarchitecture
ThedifferentcomponentsinFigure2-4andtheirinteractionsinclude:
1. TheinteractionbeginswiththeDispatcherServletreceivingthe
requestfromtheclient.
2. DispatcherServletqueriesoneormoreHandlerMappingtofigure
outaHandlerthatcanservicetherequest.AHandlerisageneric
wayofaddressingaControllerandotherHTTP-basedendpoints
thatSpringWebMVCsupports.
3. TheHandlerMappingcomponentusestherequestpathtodetermine
therightHandlerandpassesittotheDispatcherServlet.The
HandlerMappingalsodeterminesalistofInterceptorsthatneedto
getexecutedbefore(Pre-)andafter(Post-)Handlerexecution.
4. TheDispatcherServletthenexecutesthePre-ProcessInterceptorsif
anyareappropriateandpassesthecontroltotheHandler.
5. TheHandlerinteractswithanyService(s)neededandpreparesthe
model.
6. TheHandleralsodeterminesthenameoftheviewthatneedstoget
renderedintheoutputandsendsittoDispatcherServlet.ThePostProcessInterceptorsthengetexecuted.
7. ThisisfollowedbytheDispatcherServletpassingthelogicalView
nametoaViewResolver,whichdeterminesandpassestheactual
Viewimplementation.
8. TheDispatcherServletthenpassesthecontrolandmodeltothe
View,whichgeneratesresponse.ThisViewResolverandView
abstractionallowstheDispatcherServlettobedecoupledfroma
particularViewimplementation.
9. TheDispatcherServletreturnsthegeneratedresponseovertothe
client.
SpringWebMVCComponents
Intheprevioussection,youwereintroducedtoSpringWebMVCcomponentssuchas
HandlerMappingandViewResolver.Inthissection,wewilltakeadeeperlookatthoseas
wellasadditionalSpringWebMVCcomponents.
NoteInthisbookwewillbeusingJavaConfigurationforcreatingSpringbeans.
ContrarytoXML-basedconfiguration,Javaconfigurationprovidescompiletimesafety,
flexibility,andaddedpower/control.
Controller
ControllersinSpringWebMVCaredeclaredusingthestereotype
org.springframework.stereotype.Controller.AstereotypeinSpring
designatesrolesorresponsibilitiesofaclassoraninterface.Listing2-1showsabasic
controller.
Listing2-1.HomeControllerimplementation
@Controller
publicclassHomeController{
@RequestMapping("/home.html")
publicStringshowHomePage(){
return"home";
}
}
The@ControllerannotationdesignatestheHomeControllerclassasaMVC
controller.The@RequestMappingannotationmapsWebrequeststohandlerclasses
andhandlermethods.Inthiscase,the@RequestMappingindicatesthatwhenarequest
forhome.htmlismade,theshowHomePagemethodshouldgetexecuted.The
showHomePagemethodhasatinyimplementationandsimplyreturnsthelogicalview
namehome.Thiscontrollerdidnotprepareanymodelinthisexample.
Model
Springprovidestheorg.springframework.ui.Modelinterfacethatservesas
holderformodelattributes.Listing2-2showstheModelinterfacewiththeavailable
methods.Asthenamessuggest,theaddAttributeandaddAttributesmethods
canbeusedtoaddattributestothemodelobject.
Listing2-2.Modelinterface
publicinterfaceModel{
ModeladdAttribute(StringattributeName,Object
attributeValue);
ModeladdAttribute(ObjectattributeValue);
ModeladdAllAttributes(Collection<?>attributeValues);
ModeladdAllAttributes(Map<String,?>attributes);
ModelmergeAttributes(Map<String,?>attributes);
booleancontainsAttribute(StringattributeName);
Map<String,Object>asMap();
}
Theeasiestwayforacontrollertoworkwithamodelobjectisbydeclaringitasa
methodparameter.Listing2-3showstheshowHomePagemethodwiththeModel
parameter.Inthemethodimplementation,weareaddingthecurrentDateattributeto
themodelobject.
Listing2-3.showHomePagewithModelattribute
@RequestMapping("/home.html")
publicStringshowHomePage(Modelmodel){
model.addAttribute("currentDate",newDate());
return"home";
}
TheSpringFrameworkstrivestodecoupleourapplicationsfromtheframework’s
classes.So,apopularapproachforworkingwithmodelobjectsistousea
java.util.MapinstanceasshowninListing2-4.SpringwouldusethepassedinMap
parameterinstancetoenrichthemodelthatgetsexposedtotheview.
Listing2-4.showHomePagewithMapattribute
@RequestMapping("/home.html")
publicStringshowHomePage(Mapmodel){
model.put("currentDate",newDate());
return"home";
}
View
SpringWebMVCsupportsavarietyofviewtechnologiessuchasJSP,Velocity,
Freemarker,andXSLT.SpringWebMVCusesthe
org.springframework.web.servlet.Viewinterfacetoaccomplishthis.The
Viewinterfacehastwomethods,asshowninListing2-5.
Listing2-5.ViewInterfaceAPI
publicinterfaceView
{
StringgetContentType();
voidrender(Map<String,?>model,HttpServletRequest
request,HttpServletResponseresponse)throwsException;
}
ConcreteimplementationsoftheViewinterfaceareresponsibleforrenderingthe
response.Thisisaccomplishedbyoverridingtherendermethod.The
getContentTypemethodreturnsthegeneratedview'scontenttype.Table2-1shows
importantViewimplementationsthatSpringWebMVCprovidesoutofthebox.Youwill
noticethatalloftheseimplementationsresideinsidethe
org.springframework.web.servlet.viewpackage.
Table2-1.SpringWebMVCViewImplementations
ClassName
Description
org.springframework.web.servlet.view.json.
MappingJackson2JsonView
Viewimplementationthat
encodesmodelattributesand
returnsJSON.
org.springframework.web.servlet.view.xslt.XsltView
Viewimplementationthat
performsXSLT
transformationandreturns
theresponse.
org.springframework.web.servlet.view.
InternalResourceView
Viewimplementationthat
delegatestherequesttoaJSP
pageinsidetheweb
application.
org.springframework.web.servlet.view.tiles2.TilesView
Viewimplementationthat
usesApacheTiles
configurationforTile
definitionandrendering.
org.springframework.web.servlet.view.JstlView
Specializedimplementation
of
InternalResourceView
thatsupportsJSPpagesusing
JSTL.
Viewimplementationthat
org.springframework.web.servlet.view.RedirectView
redirectstoadifferent
(absoluteorrelative)URL.
Listing2-6showsthereimplementationoftheHomeControllerthatwelookedat
earlier.HerewearecreatinganinstanceofJstlViewandsettingtheJSPpagethatwe
needtoberendered.
Listing2-6.HomeControllerViewimplementation
@Controller
publicclassHomeController{
@RequestMapping("/home.html")
publicViewshowHomePage(){
JstlViewview=newJstlView();
view.setUrl("/WEB-INF/pages/home.jsp");
returnview;
}
}
Controllerimplementationstypicallydon'tdealwithviewinstances.Instead,they
returnlogicalviewnames,asshowninListing2-1,andletviewresolversdetermineand
createviewinstances.Thisdecouplesthecontrollersfromtyingtoaspecificview
implementationandmakesiteasytoswapviewimplementations.Also,thecontrollersno
longerneedtoknowintricaciessuchasthelocationoftheviews.
@RequestParam
The@RequestParamannotationisusedtobindServletrequestparametersto
handler/controllermethodparameters.Therequestparametervaluesareautomatically
convertedtothespecifiedmethodparametertypeusingtypeconversion.Listing2-7
showstwousagesof@RequestParam.Inthefirstusage,Springlooksforarequest
parameternamedqueryandmapsitsvaluetothemethodparameterquery.Inthe
secondusage,Springlooksforarequestparameternamedpage,convertsitsvaluetoan
integer,andmapsittothepageNumbermethodparameter.
Listing2-7.RequestParamUsage
@RequestMapping("/search.html")
publicStringsearch(@RequestParamStringquery,
@RequestParam("page")intpageNumber){
model.put("currentDate",newDate());
return"home";
}
Whenamethodparameterisannotatedusing@RequestParam,thespecifiedrequest
parametermustbeavailableintheclientrequest.Iftheparameterismissing,Springwill
throwaMissingServletRequestParameterExceptionexception.Onewayto
addressthisistosettherequiredattributetofalse,asshowninListing2-8.The
otheroptionistousethedefaultValueattributetospecifyadefaultvalue.
Listing2-8.Makingarequestparameternotrequired
@RequestMapping("/search.html")
publicStringsearch(@RequestParamStringquery,
@RequestParam(value="page",required=false)intpageNumber)
{
model.put("currentDate",newDate());
return"home";
}
@RequestMapping
Aswelearnedinthe“Controller”section,the@RequestMappingannotationisusedto
mapaWebrequesttoahandlerclassorhandlermethod.@RequestMappingprovides
severalattributesthatcanbeusedtonarrowdownthesemappings.Table2-2showsthe
differentelementsalongwiththeirdescriptions.
Table2-2.RequestMappingElements
Element
Name
Description
Method
RestrictsamappingtoaspecificHTTPmethodsuchasGET,POST,HEAD,OPTIONS,PUT,
PATCH,DELETE,TRACE
Produces
Narrowsmappingtomediatypethatisproducedbythemethod
Consumes
Narrowsmappingtomediatypethatthemethodconsumes
Headers
Narrowsmappingtotheheadersthatshouldbepresent
name
Allowsyoutoassignanametothemapping
params
Restrictsamappingtothesuppliedparameternameandvalue
ThedefaultHTTPmethodmappedby@RequestMappingisGET.Thisbehavior
canbechangedusingthemethodelementshowninListing2-9.Springinvokesthe
saveUsermethodonlywhenaPOSToperationisperformed.AGETrequeston
saveUserwillresultinanexceptionthrown.Springprovidesahandy
RequestMethodenumerationwiththelistofHTTPmethodsavailable.
Listing2-9.POSTmethodexample
@RequestMapping(value="/saveuser.html",
method=RequestMethod.POST)
publicStringsaveUser(@RequestParamStringusername,
@RequestParamStringpassword){
//SaveUserlogic
return"success";
}
Theproduceselementindicatesthemediatype,suchasJSONorXMLorHTML,
producedbythemappedmethod.Theproduceselementcantakeasinglemediatypeor
multiplemediatypesasitsvalue.Listing2-10showsthesearchmethodwiththe
produceselementadded.TheMediaType.TEXT_HTMLvalueindicatesthatwhena
GETrequestisperformedonsearch.html,themethodreturnsanHTMLresponse.
Listing2-10.Produceselementexample
@RequestMapping(value="/search.html",
method=RequestMethod.GET,produces="MediaType.TEXT_HTML")
publicStringsearch(@RequestParamStringquery,
@RequestParam(value="page",required=false)intpageNumber)
{
model.put("currentDate",newDate());
return"home";
}
ItispossiblefortheclienttoperformaGETrequeston/search.htmlbutsendan
Acceptheaderwithvalueapplication/JSON.Inthatscenario,Springwillnot
invokethesearchmethod.Instead,itwillreturna404error.Theproduceselement
providesaconvenientwaytorestrictmappingstocontenttypesthatthecontrollercan
serve.Inthesamefashion,theconsumeselementisusedtoindicatethemediatypethat
theannotatedmethodconsumes.
ACCEPTANDCONTENT-TYPEHEADER
AsdiscussedinChapter1,RESTresourcescanhavemultiplerepresentations.REST
clientstypicallyusetheAcceptandContent-Typeheaderstoworkwiththese
representations.
RESTclientsusetheAcceptheadertoindicatetherepresentationsthattheyaccept.
TheHTTPspecificationallowsaclienttosendaprioritizedlistofdifferentmedia
typesthatitwillacceptasresponses.Onreceivingtherequest,theserverwillsend
therepresentationwiththehighestpriority.Tounderstandthis,considerthedefault
AcceptheaderforFirefoxbrowser:
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Theqparameter,alsoknownasrelativequalityparameter,indicatesthedegreeof
preferenceandhasvaluesrangingfrom0to1.Fromthestring,wecaninferthatthe
HTMLandXHTMLwillhaveapriorityof1becausetheydon'thaveanassociatedq
value.TheXMLmediatypehaspriority0.9andtherestoftherepresentationshavea
priorityof0.8.Onreceivingthisrequest,theserverwouldtrytosenda
HTML/XHTMLrepresentationbecauseithasthehighestpriority.
Inasimilarfashion,RESTclientsusetheContent-typeheadertoindicatethe
mediatypeoftherequestbeingsenttotheserver.Thisallowstheservertoproperly
interprettherequestandparsethecontentscorrectly.Iftheserverisunabletoparse
thecontent,itwillsenda415UnsupportedMediaTypeerrorstatuscode.
SpringWebMVCallowsflexiblesignaturesformethodsannotatedwith
@RequestMapping.Thisincludesvariablemethodparametersandmethodreturn
types.Table2-3liststheimportantargumentsallowed.Foradetailedlistofallowed
arguments,refertoSpring'sJavadocsat
http://docs.spring.io/spring/docs/current/javadocapi/org/springframework/web/bind/annotation/RequestMapping.html
Table2-3.Methodargumentsanddescriptions
MethodArgument
Description
HttpServletRequest/HttpServletResponse
HTTPServletrequestandresponseobjects.
Allowsrawaccesstoclient’sdata,suchasrequest
parametersandheaders.
HttpSession
Instancerepresentingauser’sHTTPsession.
CommandObject
APOJOormodelobjectthatSpring
populates/bindswiththeusersubmitteddata.The
commandobjectcanbeannotatedwith
@ModelAttribute.
BindingResult
Instancerepresentingacommandobject’s
validationandbinding.Thisparametermust
immediatelyprecedethecommandobject.
HttpEntity<?>
InstancerepresentingaHTTPrequest.Each
HttpEntityiscomposedofrequestbodyanda
setofheaders.
Principal
Ajava.security.Principalinstancethat
representstheauthenticateduser.
Thedifferentreturntypessupportedinmethodsannotatedwith@RequestMapping
areshowninTable2-4.
Table2-4.Returntypesanddescriptions
ReturnType
Description
String
Representsthelogicalviewname.Registeredviewresolversareemployedtoresolvethe
physicalviewandaresponseisgenerated.
View
Instancerepresentingaview.Inthiscase,noviewresolutionisperformedandtheview
objectisresponsibleforgeneratingtheresponse.ExamplesincludeJstlView,
VelocityView,RedirectView,andsoon.
HttpEntity<?
>
InstancerepresentingaHTTPresponse.EachHttpEntityiscomposedofresponse
bodyandasetofheaders.
HttpHeaders
Instancecapturingtheheaderstobereturned.Responsewillhaveanemptybody.
Pojo
Javaobjectthatisconsideredtobeamodelattribute.Aspecialized
RequestToViewNameTranslatorisusedtodeterminetheappropriatelogical
viewname.
PathVariables
The@RequestMappingannotationsupportsdynamicURIsviaURItemplates.As
discussedinChapter1,URItemplatesareURIswithplaceholdersorvariables.The
@PathVariableannotationallowsyoutoaccessandusetheseplaceholdersviamethod
parameters.Listing2-11givesanexampleof@PathVariable.Inthisscenario,the
getUsermethodisdesignedtoserveuserinformationassociatedwiththepathvariable
{username}.TheclientwouldperformaGETontheURL/users/jdoetoretrieve
userinformationassociatedwithusernamejdoe.
Listing2-11.PathVariableexample
@RequestMapping("/users/{username}")
publicUsergetUser(@PathVariable("username")String
username){
Useruser=null;
//Codetoconstructuserobjectusingusername
returnuser;
}
ViewResolver
Asdiscussedintheprevioussections,aSpringWebMVCcontrollercanreturnan
org.springframework.web.servlet.Viewinstanceoralogicalviewname.
Whenalogicalviewnameisreturned,aViewResolverisemployedtoresolvethe
viewtoaViewimplementation.Ifthisprocessfailsforsomereason,a
javax.servlet.ServletExceptionisthrown.TheViewResolverinterface
hasasinglemethodandisshowninListing2-12.
Listing2-12.ViewResolverInterface
publicinterfaceViewResolver
{
ViewresolveViewName(StringviewName,Localelocale)
throwsException;
}
Table2-5listssomeoftheViewResolverimplementationsprovidedbySpring
WebMVC.
Table2-5.ViewResolverimplementationsanddescriptions
ReturnType
Description
ViewResolverimplementationthatlooksforabeanwith
BeanNameViewResolver
anidthatmatchesthelogicalviewnameinthe
ApplicationContext.Ifitdoesn'tfindthebeaninthe
ApplicationContext,anullisreturned.
InternalResourceViewResolver
ViewResolverthatlooksforaninternalresourcethathas
thelogicalviewname.Thelocationoftheinternalresourceis
typicallycomputedbyprefixingandsuffixingthelogical
namewithpathandextensioninformation.
ContentNegotiatingViewResolver
ViewResolverthatdelegatestheviewresolutiontoother
viewresolvers.Thechoiceoftheviewresolverisbasedon
therequestedmediatype,whichitselfisdeterminedusingan
AcceptheaderorfileextensionorURLparameter.
TilesViewResolver
ViewResolverthatlooksforatemplateintheTiles
configurationthatmatchesthelogicalviewname.
Asyoumighthavenoticed,thedifferentviewresolversinTable2-5mimicthe
differenttypesofviewswelookedatearlier.Listing2-13showsthecoderequiredfor
creatinganInternalViewResolver.
Listing2-13.InternalViewResolverexample
@Bean
publicViewResolverviewResolver(){
InternalResourceViewResolverviewResolver=new
InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
returnviewResolver;
}
ExceptionHandler
ExceptionsarepartofanyapplicationandSpringprovidesthe
HandlerExceptionResolvermechanismforhandlingthoseunexpectedexceptions.
TheHandlerExceptionResolverabstractionissimilartotheViewResolver
andisusedtoresolveexceptionstoerrorviews.Listing2-14showsthe
HandlerExceptionResolverAPI.
Listing2-14.HandlerExceptionResolverAPI
publicinterfaceHandlerExceptionResolver{
ModelAndViewresolveException(HttpServletRequestrequest,
HttpServletResponseresponse,
Objecthandler,Exceptionex);
}
Springprovidesseveralout-of-the-boximplementationsof
HandlerExceptionResolver,asshowninTable2-6.
Table2-6.HandlerExceptionResolverimplementationsanddescriptions
ResolverImplementation
Description
org.springframework.web.servlet.handlerS.
impleMappingExceptionResolver
Exceptionresolverimplementationthat
mapsexceptionclassnamestoview
names.
org.springframework.web.servlet.mvc.support.
DefaultHandlerExceptionResolver
Exceptionresolverimplementationthat
translatesstandardSpringexceptionsto
HTTPstatuscodes.
org.springframework.web.servlet.mvc.
annotation.ResponseStatusExceptionResolver
CustomexceptionsinSpring
applicationscanbeannotatedwith
@ResponseStatus,whichtakesa
HTTPstatuscodeasitsvalue.This
exceptionresolvertranslatesthe
exceptionstoitsmappedHTTPstatus
codes.
org.springframework.web.servlet.mvc.method.
annotation.ExceptionHandlerExceptionResolver
Exceptionresolverimplementationthat
resolvesexceptionsusingannotated
@ExceptionHandlermethods.
TheSimpleMappingExceptionResolverhasbeenaroundforareallylong
time.Spring3introducedanewwayofhandlingexceptionsusingthe
@ExceptionHandlerstrategy.Thisprovidesamechanismforhandlingerrorsin
REST-basedserviceswherethereisreallynoviewtoshowbut,rather,returndata.Listing
2-15showsacontrollerwithanexceptionhandler.Anymethodsthatnowthrowa
SQLExceptionintheHomeControllerwillgethandledinthe
handleSQLExceptionmethod.ThehandleSQLExceptionsimplycreatesa
ResponseEntityinstanceandreturnsit.However,additionaloperationssuchas
logging,returningadditionaldiagnosticdata,andsooncanbeperformed.
Listing2-15.ExceptionHandlerexample
@Controller
publicclassHomeController{
@ExceptionHandler(SQLException.class)
publicResponseEntityhandleSQLException(){
ResponseEntityresponseEntity=new
ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR);
returnresponseEntity;
}
@RequestMapping("/stream")
publicvoidstreamMovie(HttpServletResponseresponse)
throwsSQLException{
}
}
The@ExceptionHandlerannotatedmethodscanonlyhandleexceptionsthat
occurinthecontrolleroritssubclasses.So,ifweneedtohandleSQLexceptionsinother
controllers,thenweneedtocopyandpastethehandleSQLExceptionmethodinall
ofthosecontrollers.Thisapproachcanposeseverelimitations,asexceptionhandlingis
trulyacrosscuttingconcernandshouldbecentralized.
Toaddressthis,Springprovidesthe@ControllerAdviceannotation.Methodsin
classesannotatedwith@ControllerAdvicegetappliedtoallthe
@RequestMappingmethods.Listing2-16showstheGlobalExceptionHandler
withthehandleSQLExceptionmethod.Asyoucansee,the
GlobalExceptionHandlerextendsSpring's
ResponseEntityExceptionHandler,whichconvertsdefaultSpringWebMVC
exceptionstoaResponseEntitywithHTTPstatuscodes.
Listing2-16.GlobalExceptionHandlerexample
@ControllerAdvice
publicclassGlobalExceptionHandlerextends
ResponseEntityExceptionHandler{
@ExceptionHandler(SQLException.class)
publicResponseEntityhandleSQLException(){
ResponseEntityresponseEntity=new
ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR);
returnresponseEntity;
}
}
Interceptors
SpringWebMVCprovidesthenotionofinterceptorstoimplementconcernsthatcrosscut
acrossdifferenthandlers.Considerthescenarioinwhichyouwanttoprevent
unauthenticatedaccesstoasetofcontrollers.Aninterceptorallowsyoutocentralizethis
accesslogicwithoutyouhavingtocopyandpastethecodeineverycontroller.Asthe
namesuggests,interceptorsinterceptarequest;theydosoatthefollowingthreepoints:
Beforethecontrollergetsexecuted.Thisallowstheinterceptorto
decideifitneedstocontinuetheexecutionchainorreturnwithan
exceptionorcustomresponse.
Afterthecontrollergetsexecutedbutbeforetheresponseissentout.
Thisallowstheinterceptortoprovideanyadditionalmodelobjectsto
theview.
Aftertheresponseissentoutallowinganyresourcecleanup.
NoteSpringWebMVCinterceptorsaresimilartoHTTPservletfilters.Bothcanbe
usedtointerceptarequestandexecutecommonconcerns.However,thereareafew
differencesbetweenthemthatareworthnoting.Filtershavethecapabilitytowraporeven
swaptheHttpServletRequestandHttpServletResponseobjects.Interceptors
can’tdecorateorswapthoseobjects.InterceptorsareSpring-managedbeans,andwecan
easilyinjectotherspringbeansinthem.Filtersarecontainer-managedinstances;they
don'tprovideastraightforwardmechanismforinjectingSpring-managedbeans.
SpringWebMVCprovidestheHandlerInterceptorinterfaceforimplementing
interceptors.Listing2-17givestheHandlerInterceptorinterface.Asyoucansee,
thethreemethodscorrespondtothethreeinterceptorfeaturesthatwejustdiscussed.
Listing2-17.HandlerInterceptorAPI
publicinterfaceHandlerInterceptor{
voidafterCompletion(HttpServletRequestrequest,
HttpServletResponseresponse,Objecthandler,Exceptionex);
voidpostHandle(HttpServletRequestrequest,
HttpServletResponseresponse,Objecthandler,ModelAndView
modelAndView);
booleanpreHandle(HttpServletRequestrequest,
HttpServletResponseresponse,Objecthandler);
}
Listing2-18givesasimpleinterceptorimplementation.Asyoucansee,the
SimpleInterceptorclassextendsHandlerInterceptorAdapter.The
HandlerInterceptorAdapterisaconvenientabstractclassthatimplementsthe
HandlerInterceptorinterfaceandprovidesdefaultimplementationsofitsmethods.
Listing2-18.SpringWebMVCInterceptorexample
publicclassSimpleInterceptorextends
HandlerInterceptorAdapter{
privatestaticfinalLoggerlogger
=Logger.getLogger(SimpleInterceptor.class);
publicbooleanpreHandle(HttpServletRequestrequest,
HttpServletResponseresponse,Objecthandler)throws
Exception{
logger.info("Insidetheprehandle");
returnfalse;
}
}
InterceptorscanberegisteredinaSpringWebapplicationusingthe
InterceptorRegistrystrategy.WhenusingJavaConfiguration,thisistypically
achievedbycreatingaconfigurationclassthatextendsWebMvcConfigurerAdapter.
SpringWebMVC’sWebMvcConfigurerAdapterclassprovidesthe
addInterceptorsmethodthatcanbeusedtoaccesstheInterceptorRegistry.
Listing2-19showsthecoderegisteringtwointerceptors:LocalInterceptorthat
comesoutoftheboxwithSpringandourSimpleInterceptor.
Listing2-19.Exampleregisteringinterceptors
@Configuration
@EnableWebMvc
@ComponentScan(basePackages={"com.apress.springrest.web"
})
publicclassWebConfigextendsWebMvcConfigurerAdapter{
@Override
publicvoidaddInterceptors(InterceptorRegistryregistry)
{
registry.addInterceptor(newLocaleChangeInterceptor());
registry.addInterceptor(new
SimpleInterceptor()).addPathPatterns("/auth/**");
}
}
Whenaninterceptorisaddedtotheinterceptorregistry,theinterceptorgetsappliedto
allofthehandlermappings.So,theLocaleChangeInterceptorinListing2-19gets
appliedtoallthehandlermappings.However,itisalsopossibletorestricttheinterceptor
tocertainURLs.ThisisdemonstratedinListing2-19usingtheaddPathPatterns
method.HereweareindicatingthattheSimpleInterceptorshouldbeappliedto
onlytheURLsthatareundertheauthpath.
Summary
Inthischapter,wehavelookedatthebasicsoftheSpringFrameworkanddifferent
componentsofaSpringWebMVC.Inthenextchapter,wewillbringthingstogetherand
lookatbuildingourfirstRESTfulapplicationusingSpringBoot.
_____________________
1http://martinfowler.com/articles/injection.html.
2http://www.oracle.com/technetwork/java/frontcontroller-135648.html.
CHAPTER3
RESTfulSpring
Inthischapter,wewilldiscuss:
ThebasicsofSpringBoot
BuildingaHelloWorldRESTapplication
ToolsforaccessingRESTapplications
OneoftheSpringFramework’sgoalsistoreduceplumbingcodesothatdevelopers
canfocustheireffortsonimplementingcorebusinesslogic.However,astheSpring
Frameworkevolvedandaddedseveralsubprojectstoitsportfolio,developersendedup
spendingaconsiderableamountoftimesettingupprojects,findingprojectdependencies,
andwritingboilerplatecodeandconfiguration.
SpringBoot,aSpringportfolioprojectaimsatsimplifyingSpringapplication
bootstrappingbyprovidingasetofstarterprojecttemplates.Thesewouldpullallthe
properdependenciesthatareneededbasedonprojectcapabilities.Forexample,ifyou
enableJPAcapability,itautomaticallyincludesallthedependentJPA,Hibernate,and
SpringJARfiles.
SpringBootalsotakesanopinionatedapproachandprovidesdefaultconfiguration
thatsimplifiesapplicationdevelopmentquiteabit.Forexample,ifSpringBootfindsJPA
andMySQLJARsintheclasspath,itwouldautomaticallyconfigureaJPAPersistence
Unit.ItalsoenablescreationofstandaloneSpringapplicationswithembedded
Jetty/Tomcatservers,makingthemeasytodeployonanymachinewithjustJavainstalled.
Additionally,itprovidesproduction-readyfeaturessuchasmetricsandhealthchecks.
Throughoutthisbook,wewillbeexploringandlearningtheseandadditionalfeaturesof
SpringBoot.
NoteSpringRooisanotherSpringportfolioprojectthatattemptstoproviderapid
Springapplicationdevelopment.Itprovidesacommand-linetoolthatenableseasyproject
bootstrappingandgeneratescodeforcomponentssuchasJPAentities,Webcontrollers,
testscripts,andnecessaryconfiguration.Althoughtherewasalotofinitialinterestinthe
project,SpringRooneverreallybecamemainstream.AspectJCodegenerationandasteep
learningcurvecoupledwithitsattempttotakeoveryourprojectaresomereasonsforlack
ofitsadoption.SpringBoot,bycontrast,takesadifferentapproach;itfocusesonjump
startingtheprojectandprovidingclever,sensible,defaultconfiguration.Itdoesn’t
generateanycodeandcaneasilyberemoved.
GeneratingaSpringBootProject
ItispossibletocreateaSpringBootprojectfromscratch.However,SpringBootprovides
thefollowingoptionstogenerateanewproject:
UseSpringBoot’sstarterwebsite(http://start.spring.io)
UsetheSpringToolSuite(STS)IDE
UsetheBootcommandlineinterface(CLI)
Wewillexploreallthreealternativesinthischapter.However,fortherestofthebook
wewillbeoptingfortheBootCLItogeneratenewprojects.Beforewestartwithproject
generation,itisimportantthatJavaisinstalledonyourmachine.SpringBootrequiresthat
youhaveJavaSDK1.6orhigherinstalled.InthisbookwewillbeusingJava1.7.
InstallingaBuildTool
SpringBootsupportsthetwomostpopularbuildsystems:MavenandGradle.Inthisbook
wewillbeusingMavenasourbuildtool.SpringBootrequiresMavenversion3.2or
higher.ThestepstodownloadandconfigureMavenonyourWindowsmachinearegiven
here.SimilarinstructionsforMacandotheroperatingsystemscanbefoundonMaven’s
downloadpage(http://maven.apache.com/download.cgi):
1. DownloadthelatestMavenbinaryfrom
http://maven.apache.org/download.cgi.Atthetime
ofwritingthisbook,thecurrentversionofMavenwas3.2.5.For
Windows,downloadtheapache-maven-3.2.5-bin.zip
file.
2. UnzipthecontentsofthezipfileunderC:\tools\maven.
3. AddanEnvironmentvariableM2_HOMEwithvalue
C:\tools\maven\apache-maven-3.2.5-bin\apachemaven-3.2.5.ThistellsMavenandothertoolswhereMavenis
installed.AlsomakesurethattheJAVA_HOMEvariableispointing
totheinstalledJDK.
4. Appendthevalue%M2_HOME%\bintothePathenvironment
variable.ThisallowsyoutorunMavencommandsfromthe
commandline.
5. Openanewcommandlineandtypethefollowing:
mvn-v
YoushouldseeanoutputsimilartoFigure3-1,indicatingthatMavenwassuccessfully
installed.
Figure3-1.Maveninstallationverification
NoteTolearnmoreaboutMaven,refertoIntroducingMaven,publishedbyApress
(http://www.apress.com/9781484208427).
GeneratingaProjectusingstart.spring.io
SpringBoothostsanInitializrapplicationathttp://start.spring.io.The
InitializrprovidesaWebinterfacethatallowsyoutoenterprojectinformation,pickthe
capabilitiesneededforyourproject,andvoilà—itgeneratestheprojectasazipfile.
FollowthesestepstogenerateourHelloWorldRESTapplication:
1. Launchthehttp://start.spring.iowebsiteinyour
browserandentertheinformationshowninFigure3-2.
Figure3-2.start.spring.iowebsite
2. UnderProjectdependencies Web,selecttheoption“Web”and
indicatethatyouwouldlikeSpringBoottoincludeWebproject
infrastructureanddependencies.
3. Thenhitthe“GenerateProject”button.Thiswillbeginthehellorest.zipfiledownload.
Oncompletionofthedownload,extractthecontentsofthezipfile.Youwillseethe
hello-restfoldergenerated.Figure3-3showsthecontentsofthegeneratedfolder.
Figure3-3.hello-restapplicationcontents
Aquicklookatthehello-restcontentsshowsthatwehaveastandardMavenbasedJavaprojectlayout.Wehavethesrc\main\javafolder,whichhousesJava
sourcecode;src\main\resources,whichcontainspropertyfiles;staticcontent,
suchasHTML\CSS\JSfiles;andthesrc\test\javafolder,whichcontainsthetest
cases.OnrunningaMavenbuild,thisprojectgeneratesaJARartifact.Now,thismightbe
littleconfusingforthefirst-timerwhoisusedtoWARartifactsfordeployingWeb
applications.Bydefault,SpringBootcreatesstandaloneapplicationsinwhicheverything
getspackagedintoaJARfile.Theseapplicationswillhaveembeddedservletcontainers
suchasTomcatandareexecutedusingagoodoldmain()method.
NoteSpringBootalsoallowsyoutoworkwithWARartifactsthatcanbedeployedto
externalWebandapplicationcontainers.
Listing3-1givesthecontentsofthehello-restapplication’spom.xmlfile.
Listing3-1.hello-restpom.xmlfilecontents
<?xmlversion="1.0"encoding="UTF-8"?>
<projectxmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.apress</groupId>
<artifactId>hello-rest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>HelloWorldREST</name>
<description>HelloWorldRESTApplicationUsingSpring
Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-
parent</artifactId>
<version>1.2.1.RELEASE</version>
<relativePath/><!--lookupparentfrom
repository-->
</parent>
<properties>
<project.build.sourceEncoding>UTF-
8</project.build.sourceEncoding>
<startclass>com.apress.hellorest.HelloWorldRestApplication</startclass>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starterweb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-startertest</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-mavenplugin</artifactId>
</plugin>
</plugins>
</build>
</project>
ThegroupId,artifactId,andversionelementsinthepom.xmlfile
correspondtoMaven’sstandardGAVcoordinatesdescribingourproject.Theparenttag
indicatesthatwewillbeinheritingfromthespring-boot-starter-parentPOM.
ThisensuresthatourprojectinheritsSpringBoot’sdefaultdependenciesandversions.
ThedependencieselementliststwoPOMfiledependencies:spring-bootstarter-webandspring-boot-starter-test.SpringBootusestheterm
starterPOMstodescribesuchPOMfiles.
ThesestarterPOMsareusedtopullotherdependenciesanddon’tactuallycontainany
codeoftheirown.Forexample,thespring-boot-starter-webpullsSpringMVC
dependencies,Tomcat-embeddedcontainerdependencies,andaJacksondependencyfor
JSONprocessing.Thesestartermodulesplayanimportantroleinprovidingneeded
dependenciesandsimplifyingtheapplication’sPOMfiletojustafewlines.Table3-1lists
someofthecommonlyusedstartermodules.
Table3-1.SpringBootStarterModules
StarterPOM
Dependency
Use
spring-bootstarter
Starterthatbringsincoredependenciesnecessaryforfunctionssuchasautoconfigurationsupportandlogging
spring-bootstarter-aop
Starterthatbringsinsupportforaspect-orientedprogrammingandAspectJ
spring-bootstarter-test
StarterthatbringsindependenciessuchasJUnit,Mockito,andspring-test
necessaryfortesting
spring-bootstarter-web
StarterthatbringsinMVCdependencies(spring-webmvc)andembedded
servletcontainersupport
spring-bootstarter-data-jpa
StarterthataddsJavaPersistenceAPIsupportbybringinginspring-datajpa,spring-ormandHibernatedependencies
spring-bootstarter-data-rest
Starterthatbringsinspring-data-rest-webmvctoexposerepositoriesas
RESTAPI
spring-bootstarter-hateoas
Starterthatbringsinspring-hateoasdependenciesforHATEOASREST
services
spring-bootstarter-jdbc
StarterforsupportingJDBCdatabases
Finally,thespring-boot-maven-plugincontainsgoalsforpackagingthe
applicationasanexecutableJAR/WARandrunningit.
TheHelloWorldRestApplication.javaclassservesasthemainclassforour
applicationandcontainsthemain()method.Listing3-2showsthecontentsofthe
HelloWorldRestApplication.javaclass.The@SpringBootApplication
annotationisaconvenientannotationandisequivalenttodeclaringthefollowingthree
annotations:
@Configuration—Markstheannotatedclassascontainingoneor
moreSpringbeandeclarations.Springprocessestheseclassesto
createbeandefinitionsandinstances.
@ComponentScan—ThisclasstellsSpringtoscanandlookfor
classesannotatedwith@Configuration,@Service,
@Repository,andsoon.Bydefault,Springscansalltheclassesin
thepackagewherethe@ComponentScanannotatedclassresides.
@EnableAutoConfiguration—EnablesSpringBoot’sautoconfigurationbehavior.Basedonthedependenciesandconfiguration
foundintheclasspath,SpringBootintelligentlyguessesandcreates
beanconfigurations.
TypicalSpringBootapplicationsalwaysusethesethreeannotations.Inadditionto
providinganicealternativeinthosescenarios,the@SpringBootApplication
annotationcorrectlydenotestheclass’sintent.
Listing3-2.HelloWorldRestApplicationcontents
packagecom.apress.hellorest;
importorg.springframework.boot.SpringApplication;
import
org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
publicclassHelloWorldRestApplication{
publicstaticvoidmain(String[]args){
SpringApplication.run(HelloWorldRestApplication.class,
args);
}
}
Themain()methodsimplydelegatestheapplicationbootstrappingto
SpringApplication’srun()method.run()takesa
HelloWorldRestApplication.classasitsargumentandinstructsSpringtoread
annotationmetadatafromHelloWorldRestApplicationandpopulate
ApplicationContextfromit.
Nowthatwehavelookedatthegeneratedproject,let’screateaRESTendpointthat
simplyreturns“HelloREST”.Ideally,wewouldcreatethisendpointinaseparate
controllerJavaclass.However,tokeepthingssimple,wewillcreatetheendpointin
HelloWorldRestApplication,asshowninListing3-3.Westartbyaddingthe
@RestController,indicatingthatHelloWorldRestApplicationhaspossible
RESTendpoints.WethencreatethehelloGreeting()method,whichsimplyreturns
thegreeting“HelloREST”.Finally,weusetheRequestMappingannotationtomap
Webrequestsfor“/greet”pathtohelloGreeting()handlermethod.
Listing3-3.HelloRESTEndpoint
packagecom.apress.hellorest;
importorg.springframework.boot.SpringApplication;
import
org.springframework.boot.autoconfigure.SpringBootApplication;
import
org.springframework.web.bind.annotation.RestController;
import
org.springframework.web.bind.annotation.RequestMapping;
@SpringBootApplication
@RestController
publicclassHelloWorldRestApplication{
publicstaticvoidmain(String[]args){
SpringApplication.run(HelloWorldRestApplication.class,
args);
}
@RequestMapping("/greet")
publicStringhelloGreeting(){
return"HelloREST";
}
}
Thenextstepistolaunchandrunourapplication.Todothis,openacommandline,
navigatetothehello-restfolder,andrunthefollowingcommand:
mvnspring-boot:run
YouwillseeMavendownloadingthenecessarypluginsanddependencies,andthenit
willlaunchtheapplication,asshownhere:
._________
/\/___’_____(_)______\\\\
(()\___|‘_|‘_||‘_\/_`|\\\\
\/___)||_)|||||||(_||))))
‘|____|.__|_||_|_||_\__,|////
=========|_|==============|___/=/_/_/_/
::SpringBoot::(v1.2.1.RELEASE)
2015-01-2522:13:19.094INFO1468–[lication.main()]
ationConfigEmbeddedWebApplicationContext:Refreshing
org.springframework.boot.context.embedded.AnnotationConfigEmbedde
startupdate[SunJan2522:13:19MST2015];rootofcontext
hierarchy
2015-01-2522:13:20.315INFO1468–[lication.main()]
s.b.c.e.t.TomcatEmbeddedServletContainer:Tomcat
initializedwithport(s):8080(http)
2015-01-2522:13:20.672INFO1468–[lication.main()]
o.apache.catalina.core.StandardService:Startingservice
Tomcat
2015-01-2522:13:20.673INFO1468–[lication.main()]
org.apache.catalina.core.StandardEngine:StartingServlet
Engine:ApacheTomcat/8.0.15
2015-01-2522:13:20.753INFO1468–[ost-startStop-1]
o.a.c.c.C.[Tomcat].[localhost].[/]:Initializing
SpringembeddedWebApplicationContext
2015-01-2522:13:20.754INFO1468–[ost-startStop-1]
o.s.web.context.ContextLoader:Root
WebApplicationContext:initializationcompletedin1660ms
2015-01-2522:13:21.895INFO1468–[lication.main()]
c.a.hellorest.HelloWorldRestApplication:Started
HelloWorldRestApplicationin3.081seconds(JVMrunningfor
11.783)
Totestourrunningapplication,launchabrowserandnavigateto
http://localhost:8080/greet.NoticethatSpringBootlaunchestheapplication
astheROOTcontextandnotthehello-worldcontext.Youshouldseeascreensimilar
tothatinFigure3-4.
Figure3-4.HelloRESTgreeting
SPRINGINITIALIZR
TheSpringInitializrapplicationhostedathttp://start.spring.ioitselfis
builtusingSpringBoot.YoucanfindthesourcecodeofthisapplicationonGitHub
athttps://github.com/spring-io/initializr.Itisalsopossiblefor
youtobuildandhostyourowninstancesoftheInitializrapplication.
InadditiontoprovidingaWebinterface,theInitializrprovidesaHTTPendpointthat
providessimilarprojectgenerationcapability.Infact,SpringBoot’sCLIandIDEs
suchasSTSusethisHTTPendpointbehindthescenesforgeneratingprojects.
TheHTTPendpointcanalsobeinvokedfromthecommandlineusingcurl.For
example,thefollowingcommandwouldgeneratethehello-restprojectzipfile
usingcurl.The–doptionsareusedtoprovidedatathatgetspassedasrequest
parameters:
curlhttps://start.spring.io/starter.zip-dstyle=web-d
name=hello-rest
GeneratingaProjectusingSTS
SpringToolSuiteorSTSisafreeEclipse-baseddevelopmentenvironmentthatprovides
greattoolingsupportfordevelopingSpring-basedapplications.Youcandownloadand
installthelatestversionofSTSfromPivotal’swebsiteat:
https://spring.io/tools/sts/all.Atthetimeofwritingthisbook,the
currentversionofSTSwas3.6.3.
STSprovidesauserinterfacesimilartoInitializr’swebinterfaceforgeneratingBoot
starterprojects.HerearethestepsforgeneratingaSpringBootproject:
1. LaunchSTSifyouhaven’talreadydoneso.GotoFile Newand
clickonSpringStarterProject,asshowninFigure3-5.
Figure3-5.STSSpringstarterproject
2. Infollowingscreen,entertheinformationasshowninFigure3-6.
InadditiontoenteringMaven’sGAVinformation,selecttheWeb
starteroption.HitNext.
Figure3-6.Starterprojectoptions
3. Onthefollowingscreen,changethelocationwhereyouwouldlike
tostoretheproject.The“FullUrl”areashowstheHTTPREST
endpointalongwiththeoptionsthatyouselected(seeFigure3-7).
Figure3-7.Starterprojectlocation
4. HittheFinishbuttonandyouwillseethenewprojectcreatedin
STS.Thecontentsoftheprojectaresimilartotheprojectthatwe
createdearlier(seeFigure3-8).
Figure3-8.STSSpringstarterprojectresources
STS’sstarterprojectwizardprovidesaconvenientwaytogeneratenewSpringBoot
projects.ThenewlycreatedprojectautomaticallygetsimportedintotheIDEandis
immediatelyavailablefordevelopment.
GeneratingaProjectUsingtheCLI
SpringBootprovidesacommandlineinterface(CLI)forgeneratingprojects,prototyping,
andrunningGroovyscripts.BeforewecanstartusingtheCLI,weneedtoinstallit.Here
arethestepsforinstallingtheBootCLIonaWindowsmachine:
1. DownloadthelatestversionoftheCLIZIPdistributionfrom
Spring’swebsiteat
http://repo.spring.io/release/org/springframework/boot/
boot-cli.Atthetimeofwritingthisbook,thecurrentversionof
CLIwas1.2.1.Thisversioncanbedownloadeddirectlyfrom
http://repo.spring.io/release/org/springframework/boot/
boot-cli/1.2.1.RELEASE/spring-boot-cli1.2.1.RELEASE-bin.zip.
2. Extractthezipfileandplaceitscontents(folderssuchasbinand
lib)underC:\tools\springbootcli,asshowninFigure39.
Figure3-9.SpringBootCLIcontents
3. AddanewenvironmentvariableSPRING_HOMEwithvalue
c:\tools\springbootcli.
4. EditthePathenvironmentvariableandaddthe
%SPRING_HOME%/binvaluetoitsend.
5. Openanewcommandlineandverifytheinstallationrunningthe
followingcommand:
spring--version
YoushouldseeanoutputsimilartothatshowninFigure3-10.
Figure3-10.SpringBootCLIinstallation
NowthatwehavetheBootCLIinstalled,generatinganewprojectsimplyinvolves
runningthefollowingcommandatthecommandline:
springinit--dependencieswebrest-cli
Thecommandcreatesanewrest-cliprojectwithWebcapability.Theoutputof
runningthecommandisshowninListing3-4.
Listing3-4.BootCLIOutput
C:\test>springinit--dependencieswebrest-cli
Usingserviceathttps://start.spring.io
Projectextractedto'C:\test\rest-cli'
AccessingRESTApplications
Thereareseveralfreeandcommercialtoolsthatallowyoutoaccessandexperimentwith
RESTAPI/applications.Inthissectionwewilllookatsomeofthepopulartoolsthatallow
youtoquicklytestarequestandinspecttheresponse.
Postman
PostmanisaChromebrowserextensionformakingHTTPrequests.Itoffersaplethoraof
featuresthatmakesiteasytodevelop,test,anddocumentaRESTAPI.AChromeapp
versionofPostmanisalsoavailablethatprovidesadditionalfeaturessuchasbulk
uploadingthatarenotavailableinthebrowserextension.
PostmancanbedownloadedandinstalledfromtheChromeWebStore.Toinstall
Postman,simplylaunchtheChromebrowserandnavigateto
https://chrome.google.com/webstore/detail/postman-restclient/fdmmgilgnpjigdojojpjoooidkmcomcm.Youmightbeaskedtologin
toyourGoogleChromeaccountandconfirmusingthe“Newapp”installationdialog.On
completionoftheinstallation,youshouldbeabletolocateandlaunchPostmanusingthe
“Appsicon”intheBookmarksbarorbytypingchrome://apps/shortcut.Figure
3-10showsPostmanlaunchedintheChromebrowser.
PostmanprovidesacleanintuitiveuserinterfaceforcomposinganHTTPrequest,
sendingittoaserver,andviewingtheHTTPresponse.Italsoautomaticallysavesthe
requests,whicharereadilyavailableforfutureruns.Figure3-11showsaHTTPGET
requestmadetoourGreetserviceanditsresponse.Youcanalsoseetherequestsavedin
theHistorysectionoftheleftsidebar.
Figure3-11.Postmanbrowserextension
PostmanmakesiteasytologicallygrouprelatedAPIcallsintocollections,asshown
inFigure3-12.Itispossibletohavesubcollectionsofrequestsunderacollection.
Figure3-12.Postmancollections
RESTClient
RESTClientisaFirefoxextensionforaccessingRESTAPIsandapplications.Unlike
Postman,RESTClientdoesn’thavealotofbellsandwhistles,butitprovidesbasic
functionalitytoquicklytestaRESTAPI.ToinstallRESTClient,launchtheFirefox
browserandnavigatetotheURLhttps://addons.mozilla.org/enUS/firefox/addon/restclient/.Thenclickthe“+AddtoFirefox”buttonand
inthefollowing“SoftwareInstallation”dialogclickthe“InstallNow”button.
Oncompletionoftheinstallation,youcanlaunchRESTClientusingtheRESTClient
icon onthetoprightcornerofthebrowser.Figure3-13showstheRESTClient
applicationwitharequesttoourGreetserviceandthecorrespondingresponse.
Figure3-13.RESTClient
Summary
SpringBootprovidesanopinionatedapproachtobuildingSpring-basedapplications.In
thischapter,welookedatSpringBoot’sfeaturesandusedittobuildaHelloWorldREST
application.WealsolookedatthePostmanandRESTClienttoolsfortestingandexploring
theRESTAPI.
Inthenextchapter,wewillbeginworkonamorecomplexRESTapplicationand
discusstheprocessofidentifyinganddesigningresources.
CHAPTER4
BeginningQuickPollApplication
Inthischapterwewilldiscuss:
AnalyzingtherequirementsforQuickPoll
IdentifyingQuickPollresources
Designingrepresentations
ImplementingQuickPoll
Uptothispoint,wehavelookedatthefundamentalsofRESTandreviewedour
technologychoiceofimplementation—SpringMVC.Nowit’stimetodevelopamore
complexapplication.Inthischapter,wewillintroduceyoutothebeginningsofan
applicationthatwewillbeworkingonthroughoutthisbook.WewillcallitQuickPoll.We
willgothroughtheprocessofanalyzingtherequirements,identifyingresources,designing
theirrepresentation,and,finally,provideanimplementationtoasubsetoffeatures.In
upcomingchapters,wewillcontinueourdesignandimplementationbyaddingnew
features,documentation,security,andversioning.
IntroducingQuickPoll
Pollshavebecomeapopularoptionforsolicitingviewsandopinionsfromthecommunity
onmanywebsitesthesedays.Thereareacoupleofvariationsbetweenonlinepolls,buta
polltypicallyhasaquestionandalistofanswers,asshowninFigure4-1.
Figure4-1.Webpollexample
Participantsvoteandcommunicatetheiropinionbyselectingoneormoreresponses.
Manypollsalsoallowparticipantstoviewthepollresults,asshowninFigure4-2.
Figure4-2.Webpollresults
ImaginebeingpartofQuickPollInc.,abuddingSoftwareasaService,orSaaS,
providerthatallowsuserstocreate,manipulate,andvoteonpolls.Weplantolaunchour
servicestoasmallaudience,butweintendtobecomeaglobalenterprise.Inadditionto
theWeb,QuickPollwouldalsoliketotargetnativeiOSandAndroidplatforms.To
achievetheseloftygoals,wehavechosentoimplementourapplicationusingREST
principlesandWebtechnologies.
Webeginthedevelopmentprocessbyanalyzingandunderstandingrequirements.Our
QuickPollapplicationhasthefollowingrequirements:
UsersinteractwithQuickPollservicestocreatenewpolls
Eachpollcontainsasetofoptionsthatareprovidedduringpoll
creation
Optionsinsideapollcanbeupdatedatalaterpoint
Tokeepthingssimple,QuickPollrestrictsvotingonasingleoption
Participantscancastanynumberofvotes
Resultsofapollcanbeviewedbyanyone
WehavestartedwithasimplesetofrequirementsforQuickPoll.Aswithanyother
application,theserequirementswillevolveandchange.Wewilladdressthosechangesin
upcomingchapters.
DesigningQuickPoll
AsdiscussedinChapter1,designingaRESTfulapplicationtypicallyinvolvesthe
followingsteps:
1. ResourceIdentification
2. ResourceRepresentation
3. EndpointIdentification
4. Verb/ActionIdentification
ResourceIdentification
Webegintheresourceidentificationprocessbyanalyzingrequirementsandextracting
nouns.Atahighlevel,theQuickPollapplicationhasusersthatcreateandinteractwith
polls.Fromthepreviousstatement,youcanidentifyUserandPollasnounsandclassify
themasresources.Similarly,userscanvoteonpollsandviewthevotingresults,making
Voteanotherresource.Thisresourcemodelingprocessissimilartodatabasemodelingin
thatitisusedtoidentifyentitiesorobject-orienteddesignthatidentifiesdomainobjects.
Itisimportanttorememberthatallnounsidentifiedneednotbeexposedasresources.
Forexample,apollcontainsseveraloptions,makingOptionanothercandidatefor
resource.MakingPollOptionaresourcewouldrequireaclienttomaketwoGETrequests.
ThefirstrequestwillobtainaPollrepresentation;thesecondrequestwillobtainan
associatedOptionsrepresentation.However,thisapproachmakestheAPIchattyand
mightoverloadservers.AnalternativeapproachistoincludetheoptionsinsideaPoll
representation,therebyhidingOptionasaresource.ThiswouldmakePollacoarsegrainedresource,butclientswouldgetpoll-relateddatainonecall.Additionally,the
secondapproachcanenforcebusinessrulessuchasrequiringatleasttwooptionsfora
polltobecreated.
Thisnounapproachallowsustoidentifycollectionresources.Now,considerthe
scenarioinwhichyouwanttoretrieveallofthevotesforagivenpoll.Tohandlethis,you
needa“votes”collectionresource.YoucanperformaGETrequestandobtaintheentire
collection.Similarly,weneeda“polls”collectionresource,whichallowsustoquery
groupsofpollsandcreatenewones.
Finally,weneedtoaddressthescenarioinwhichwecountallofthevotesforapoll
andreturnthecomputedresultstotheclient.Thisinvolvesloopingthroughallthevotes
forapoll,groupingthosevotesbasedonoptions,andthencountingthem.Such
processingoperationsaretypicallyimplementedusinga“controller”resource,whichwe
introducedinChapter1.Inthiscase,wemodelaComputeResultresource,which
performsthiscountingoperation.Table4-1showstheidentifiedresourcesandtheir
collectionresourcecounterparts.
Table4-1.ResourcesforQuickPollapplication
Resource
Description
User
SingletonUserResource
Users
CollectionUserResource
Poll
SingletonPollResource
Polls
CollectionPollResource
Vote
SingletonVoteResource
Votes
CollectionVoteResource
ComputeResult
CountProcessingResource
ResourceRepresentation
ThenextstepintheRESTAPIdesignprocessisdefiningresourcerepresentationsand
representationformats.RESTAPIstypicallysupportmultipleformatssuchasHTML,
JSON,andXML.ThechoiceoftheformatlargelydependsontheAPIaudience.For
example,aRESTservicethatisinternaltothecompanymightonlysupportJSONformat,
whereasapublicRESTAPImightspeakXMLandJSONformats.Inthischapterandin
therestofthebook,JSONwillbethepreferredformatforouroperations.
JSONFORMAT
TheJavaScriptObjectNotation,orJSON,isalightweightformatforexchanging
information.InformationinJSONisorganizedaroundtwostructures:objectsand
arrays.
AJSONobjectisacollectionofname/valuepairs.Eachname/valuepairconsistsofa
fieldnameindoublequotesfollowedbyacolon(:),followedbyafieldvalue.JSON
supportsseveraltypesofvaluessuchasBoolean(trueorfalse),number(intorfloat),
String,null,arrays,andobject.Examplesofname/valuepairsinclude:
“country”:“US”
“age”:31
“isRequired”:true
“email”:null
JSONobjectsaresurroundedbycurlybraces({}),andeachname/valuepairis
separatedusingacomma(,).HereisanexampleofapersonJSONobject:
{“firstName”:“John”,“lastName”:“Doe”,“age”:26,
“active”:true}
TheotherJSONstructure,anarray,isanorderedcollectionofvalues.Eacharrayis
surroundedbysquarebrackets([]),withvaluesseparatedbyacomma.Hereisan
exampleofanarrayoflocations:
[“SaltLakeCity”,“NewYork”,“LasVegas”,“Dallas”]
JSONarrayscanalsocontainobjectsastheirvalues:
[
{“firstName”:“Jojn”,“lastName”:“Doe”,“age”:
26,“active”:true},
{“firstName”:“Jane”,“lastName”:“Doe”,“age”:
22,“active”:true},
{“firstName”:“Jonnie”,“lastName”:“Doe”,“age”:
30,“active”:false}
]
Resourcesaremadeupofsetofattributesthatcanbeidentifiedusingprocesssimilar
toObjectOrienteddesign.APollresource,forexample,hasaquestionattribute,
containingaPollquestion,andanidattribute,whichuniquelyidentifiesthePoll.Italso
containsasetofoptions;eachoptionismadeupofavalueandanid.Listing4-1showsa
representationofaPollwithsampledata.
Listing4-1.Pollrepresentation
{
"id":2,
"question":"HowwillwinSuperBowlthisyear?",
"options":[{"id":45,"value":"NewEngland
Patriots"},{"id":49,
"value":"SeattleSeahawks"},{"id":51,"value":
"GreenBayPackers"},
{"id":54,"value":"DenverBroncos"}]
}
NoteWeareintentionallyexcludingauserfromPollrepresentationinthischapter.In
Chapter8,wewilldiscussUserrepresentationalongwithitsassociationstoPollandVote
resources.
TherepresentationofaPollcollectionresourcecontainsacollectionofindividual
polls.Listing4-2givestherepresentationofaPollscollectionresourcewithdummydata.
Listing4-2.ListofPollsrepresentation
[
{
"id":5,
"question":"q1",
"options":[{"id":6,"value":"X"},{"id":9,
"value":"Y"},{"id":10,"value":"Z"}]
},
{
"id":2,
"question":"q10",
"options":[{"id":15,"value":"Yes"},{"id":16,
"value":"No"}]
}
.......
]
TheVoteresourcecontainstheoptionforwhichthevotewascastandaunique
identifier.Listing4-3showstheVoteresourcerepresentationwithdummydata.
Listing4-3.Voterepresentation
{
"id":245,
"option":{"id":45,"value":"NewEnglandPatriots"}
}
Listing4-4givestheVotescollectionresourcerepresentationwithdummydata.
Listing4-4.ListofVotesrepresentation
[
{
"id":245,
"option":{"id":5,"value":"X"}
},
{
"id":110,
"option":{"id":7,"value":"Y"}
},
............
TheComputeResultresourcerepresentationshouldincludethetotalnumberofvotes
andPolloptionsalongwiththevotecountassociatedwitheachoption.Listing4-5shows
thisrepresentationwithsampledata.WeusethetotalVotesattributetoholdthecastvotes
andtheresultsattributetoholdtheoptionidandtheassociatedvotes.
Listing4-5.ComputeResultrepresentation
{
totalVotes:100,
"results":[
{"id":1,"count":10},
{"id":2,"count":8},
{"id":3,"count":6},
{"id":4,"count":4}
]
}
Nowthatwehavedefinedourresourcerepresentation,wewillmoveontoidentifying
endpointsforthoseresources.
EndpointIdentification
RESTresourcesareidentifiedusingURIendpoints.Well-designedRESTAPIsshould
haveendpointsthatareunderstandable,intuitive,andeasytouse.Rememberthatwebuild
RESTAPIsforourconsumerstouse.Hence,thenamesandthehierarchythatwechoose
forourendpointsshouldbeunambiguoustoconsumers.
Wedesigntheendpointsforourserviceusingbestpracticesandconventionswidely
usedintheindustry.ThefirstconventionistouseabaseURIforourRESTservice.The
baseURIprovidesanentrypointforaccessingtheRESTAPI.PublicRESTAPI
providerstypicallyuseasubdomainsuchashttp://api.domain.comor
http://dev.domain.comastheirbaseURI.PopularexamplesincludeGitHub’s
https://api.github.comandTwitter’shttps://api.twitter.com.By
creatingaseparatesubdomain,youpreventanypossiblenamecollisionswithwebpages.
Italsoallowsyoutoenforcesecuritypoliciesthataredifferentfromtheregularwebsite.
Tokeepthingssimple,wewillbeusinghttp://localhost:8080asourbaseURI
inthisbook.
Thesecondconventionistonameresourceendpointsusingpluralnouns.Inour
QuickPollapplication,thiswouldresultinanendpoint
http://localhost:8080/pollsforaccessingthePollcollectionresource.
IndividualPollresourceswillbeaccessedusingaURIsuchas
http://localhost:8080/polls/1234and
http://localhost:8080/polls/3456.Wecangeneralizeaccesstoindividual
PollresourcesusingtheURItemplate
http://localhost:8080/polls/{pollId}.Similarly,theendpoints
http://localhost:8080/usersand
http://localhost:8080/users/{userId}areusedforaccessingcollection
andindividualUserresources.
ThethirdconventionadvisesusingaURIhierarchytorepresentresourcesthatare
relatedtoeachother.InourQuickPollapplication,eachVoteresourceisrelatedtoaPoll
resource.BecausewetypicallycastvotesforaPoll,ahierarchicalendpoint
http://localhost:8080/polls/{pollId}/votesisrecommendedfor
obtainingormanipulatingallthevotesassociatedwithagivenPoll.Inthesameway,the
endpointhttp://localhost:8080/polls/{pollId}/votes/{voteId}
wouldreturnanindividualvotethatwascastforthePoll.
Finally,theendpointhttp://localhost:8080/computeresultcanbeused
toaccesstheComputeResultresource.Forthisresourcetofunctionproperlyandcountthe
votes,apollidisrequired.BecausetheComputeResultworkswithVote,Poll,and
Optionresources,wecan’tusethethirdapproachfordesigningaURIthatishierarchal
innature.Forusecaseslikethesethatrequiredatatoperformcomputation,thefourth
conventionrecommendsusingaqueryparameter.Forexample,aclientcaninvokethe
endpointhttp://localhost:8080/computeresult?pollId=1234tocount
allofthevotesforthePollwithid1234.Queryparametersareanexcellentvehiclefor
providingadditionalinformationtoaresource.
Inthissection,wehaveidentifiedtheendpointsfortheresourcesinourQuickPoll
application.Thenextstepistoidentifytheactionsthatareallowedontheseresources,
alongwiththeexpectedresponses.
ActionIdentification
HTTPVerbsallowclientstointeractandaccessresourcesusingtheirendpoints.Inour
QuickPollapplication,theclientsmustbeabletoperformoneormoreCRUDoperations
onresourcessuchasPollandVote.Analyzingtheusecasesfromthe“Introducing
QuickPoll”section,Table4-2showstheoperationsallowedonPoll/Pollscollection
resourcesalongwiththesuccessanderrorresponses.NoticethatonthePollcollection
resourceweallowGETandPOSToperationsbutdenyPUTandDeleteoperations.A
POSTonthecollectionresourceallowstheclienttocreatenewpolls.Similarly,weallow
GET,PUT,andDeleteoperationsonagivenPollresourcebutdenyPOSToperation.The
servicereturnsa404statuscodeforanyGET,PUT,andDELETEoperationonaPoll
resourcethatdoesn’texist.Similarly,anyservererrorswouldresultinastatuscodeof500
senttotheclient.
Table4-2.AllowedoperationsonaPollresource
Inthesamefashion,Table4-3showstheoperationsallowedonVote/Votescollection
resources.
Table4-3.AllowedoperationsonVoteresource
Finally,Table4-4showstheoperationsallowedontheComputeResultresource.
Table4-4.AllowedoperationsonComputeResultresource
ThisconcludesthedesignfortheQuickPollRESTservice.Beforewestartour
implementation,wewillreviewQuickPoll’shigh-levelarchitecture.
QuickPollArchitecture
TheQuickPollapplicationwillbemadeofaWeborRESTAPIlayerandaRepository
layerwithadomainlayercrosscuttingthosetwo,asdepictedinFigure4-3.Alayered
approachprovidesaclearseparationofconcerns,makingapplicationseasytobuildand
maintain.Eachlayerinteractswiththelayerbelowusingawell-definedcontract.Aslong
asthecontractismaintained,itispossibletoswapoutunderlyingimplementations
withoutanyimpacttotheoverallsystem.
Figure4-3.QuickPollarchitecture
TheWebAPIlayerisresponsibleforreceivingclientrequests,validatinguserinput,
interactingwithaserviceorarepositorylayer,andgeneratingaresponse.UsingHTTP
protocol,resourcerepresentationsareexchangedbetweenclientsandtheWebAPIlayer.
ThislayercontainsControllers/Handlersandistypicallyverylightweightasitdelegates
mostoftheworktolayersbeneathit.
Thedomainlayerisconsideredtobethe“heart”ofanapplication.Domainobjectsin
thislayercontainbusinessrulesandbusinessdata.Theseobjectsaremodeledafterthe
nounsinthesystem.Forexample,aPollobjectinourQuickPollapplicationwouldbe
consideredadomainobject.
Therepositoryordataaccesslayerisresponsibleforinteractingwithadatastoresuch
asadatabaseorLDAPoralegacysystem.IttypicallyprovidesCRUDoperationsfor
storingandretrievingobjectsfrom/toadatastore.
NoteObservantreaderswillnoticethattheQuickPollarchitectureismissingaservice
layer.ServicelayertypicallysitsbetweentheAPI/PresentationlayerandRepositorylayer.
Itcontainscoarse-grainedAPIwithmethodsthatfulfilloneormoreusecases.Itisalso
responsibleformanagingtransactionsandothercrosscuttingconcernssuchassecurity.
BecausewearenotdealingwithanycomplexusecasesforQuickPollapplicationinthis
book,wewillnotbeintroducingservicelayersintoourarchitecture.
ImplementingQuickPoll
WebeginQuickPollimplementationbygeneratingaSpringBootprojectusingSTS.
Followthestepsdiscussedinthe“GeneratingProjectusingSTS”sectionofChapter3,
andcreateaprojectnamedquick-poll.Figure4-4givestheconfigurationinformationused
duringprojectgeneration.Noticethatwehaveselectedthe“JPA”and“Web”options.
Figure4-4.QuickPollprojectconfiguration
Alternatively,youcanimporttheQuickPollprojectintoyourSTSIDEfromthe
downloadedsourcecodeforthisbook.Thedownloadedsourcecodecontainsanumberof
foldersnamedChapterX,inwhichXrepresentsthecorrespondingchapternumber.Each
ChapterXfolderfurthercontainstwosubfolders:astarterfolderandafinal
folder.ThestarterfolderhousesaQuickPollprojectthatyoucanusetofollowalong
withthesolutiondescribedinthischapter.
Eventhougheachchapterbuildsonthepreviouschapter’swork,thestarterproject
allowsyoutoskiparoundinthebook.Forexample,ifyouareinterestedinlearningabout
security,youcansimplyloadtheQuickPollapplicationundertheChapter8\starter
folderandfollowthesolutionasdescribedinChapter8.
Asthenamesuggests,thefinalfoldercontainsthecompletedsolution/codeforeach
chapter.Tominimizecodeinthechaptertext,Ihaveomittedgetters/settersmethods,
importsandpackagedeclarationsinsomeofthecodelistings.Pleaserefertothe
QuickPollcodeunderthefinalfolderforcompletecodelistings.
Bydefault,SpringBootapplicationsrunonport8080.So,ifyouintendtoruntwo
versionsofQuickPoll,simplyusethecommandlineoption-Dserver.port:
mvnspring-boot:run-Dserver.port=8181
NoteJavaPersistenceAPI,orJPA,isastandards-basedAPIforaccessing,storing,and
managingdatabetweenJavaobjectsandrelationaldatabase.LikeJDBC,JPAisapurelya
specificationandmanycommercialandopensourceproductssuchasHibernateand
TopLinkprovideJPAimplementations.AformaloverviewofJPAisbeyondthescopeof
thisbook.PleaserefertoProJPA2
(http://www.apress.com/9781430219569/)tolearnmore.
DomainImplementation
Thedomainobjectstypicallyactasabackboneforanyapplication.So,thenextstepin
ourimplementationprocessistocreatedomainobjects.Figure4-5showsaUMLClass
diagramrepresentingthethreedomainobjectsinourQuickPollapplicationandtheir
relationships.
Figure4-5.QuickPolldomainobjects
Insidethequick-pollproject,createacom.apress.domainsubpackageunderthe
/src/main/javafolderandcreateJavaclassescorrespondingtothedomainobjects
thatweidentified.Listing4-6givestheimplementationoftheOptionclass.Asyoucan
see,theOptionclasshastwofields:id,toholdtheidentity,andvalue,corresponding
totheoptionvalue.Additionally,youwillseethatwehaveannotatedthisclasswithJPA
annotationssuchas@Entityand@Id.ThisallowsinstancesoftheOptionclasstobe
easilypersistedandretrievedusingJPAtechnology.
Listing4-6.Optionclass
packagecom.apress.domain;
importjavax.persistence.Column;
importjavax.persistence.Entity;
importjavax.persistence.GeneratedValue;
importjavax.persistence.Id;
@Entity
publicclassOption{
@Id
@GeneratedValue
@Column(name="OPTION_ID")
privateLongid;
@Column(name="OPTION_VALUE")
privateStringvalue;
//GettersandSettersomittedforbrevity
}
Next,wecreateaPollclass,asshowninListing4-7,alongwithcorrespondingJPA
annotations.ThePollclasshasaquestionfieldtostorethepollquestion.The
@OneToManyannotation,asthenamesuggests,indicatesthataPollinstancecan
containzeroormoreOptioninstances.TheCascadeType.Allindicatesthatany
databaseoperationssuchaspersist,remove,ormergeonaPollinstanceneedstobe
propagatedtoallrelatedOptioninstances.Forexample,whenaPollinstancegets
deleted,alloftherelatedOptioninstanceswillbedeletedfromthedatabase.
Listing4-7.Pollclass
packagecom.apress.domain;
importjava.util.Set;
importjavax.persistence.CascadeType;
importjavax.persistence.Column;
importjavax.persistence.Entity;
importjavax.persistence.GeneratedValue;
importjavax.persistence.Id;
importjavax.persistence.JoinColumn;
importjavax.persistence.OneToMany;
importjavax.persistence.OrderBy;
@Entity
publicclassPoll{
@Id
@GeneratedValue
@Column(name="POLL_ID")
privateLongid;
@Column(name="QUESTION")
privateStringquestion;
@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="POLL_ID")
@OrderBy
privateSet<Option>options;
//GettersandSettersomittedforbrevity
}
Finally,wecreatetheVoteclass,asshowninListing4-8.The@ManyToOne
annotationindicatesthatanOptioninstancecanhavezeroormoreVoteinstances
associatedwithit.
Listing4-8.Pollclass
packagecom.apress.domain;
importjavax.persistence.Column;
importjavax.persistence.Entity;
importjavax.persistence.GeneratedValue;
importjavax.persistence.Id;
importjavax.persistence.JoinColumn;
importjavax.persistence.ManyToOne;
@Entity
publicclassVote{
@Id
@GeneratedValue
@Column(name="VOTE_ID")
privateLongid;
@ManyToOne
@JoinColumn(name="OPTION_ID")
privateOptionoption;
//GettersandSettersomittedforbrevity
}
RepositoryImplementation
Repositories,orDataAccessObjects(DAO),provideanabstractionforinteractingwith
datastores.Repositoriestraditionallyincludeaninterfacethatprovidesasetoffinder
methodssuchasfindById,findAllforretrievingdata,andmethodstopersistand
deletedata.Repositoriesalsoincludeaclassthatimplementsthisinterfaceusing
datastore-specifictechnologies.Forexample,arepositorydealingwithadatabaseuses
technologysuchasJDBCorJPA,andarepositorydealingwithLDAPwoulduseJNDI.It
isalsocustomarytohaveoneRepositoryperdomainobject.
Althoughthishasbeenapopularapproach,thereisalotofboilerplatecodethatgets
duplicatedineachrepositoryimplementation.Developersattempttoabstractcommon
functionalityintoagenericinterfaceandgenericimplementation
(http://www.ibm.com/developerworks/library/j-genericdao/).
However,theyarestillrequiredtocreateapairofrepositoryinterfacesandclassesfor
eachdomainobject.Oftentheseinterfacesandclassesareemptyandjustresultinmore
maintenance.
TheSpringDataprojectaimsataddressingthisproblembycompletelyeliminatingthe
needtowriteanyrepositoryimplementations.WithSpringData,allyouneedisa
repositoryinterfacetoautomaticallygenerateitsimplementationatruntime.Theonly
requirementisthatapplicationrepositoryinterfacesshouldextendoneofthemany
availableSpringDatamarkerinterfaces.BecausewewillbepersistingourQuickPoll
domainobjectsintoarelationaldatabaseusingJPA,wewillbeusingSpringDataJPA
subproject’sorg.springframework.data.repository.CrudRepository
markerinterface.AsyoucanseefromListing4-9,theCrudRepositoryinterface
takesthetypeofdomainobjectthatitmanipulatesandthetypeofdomainobject’s
identifierfieldasitsgenericparametersTandID.
Listing4-9.CrudRepositoryAPI
publicinterfaceCrudRepository<T,IDextendsSerializable>
extendsRepository<T,ID>{
//CreateandUpdateMethods
<SextendsT>Ssave(Sentity);
<SextendsT>Iterable<S>save(Iterable<S>entities);
//FinderMethods
TfindOne(IDid);
Iterable<T>findAll();
Iterable<T>findAll(Iterable<ID>ids);
//DeleteMethods
voiddelete(IDid);
voiddelete(Tentity);
voiddelete(Iterable<?extendsT>entities);
voiddeleteAll();
//UtilityMethods
longcount();
booleanexists(IDid);
}
Webeginourrepositoryimplementationbycreatingacom.apress.repository
packageunderthesrc\main\javafolder.Then,wecreateanOptionRepository
interfaceasshowninListing4-10.Asdiscussedearlier,theOptionRepository
extendsSpringData’sCrudRepositoryandtherebyinheritsallofitsCRUDmethods.
BecausetheOptionRepositoryworkswiththeOptiondomainobject,itpasses
OptionandLongasgenericparametervalues.
Listing4-10.OptionRepositoryinterface
packagecom.apress.repository;
importorg.springframework.data.repository.CrudRepository;
importcom.apress.domain.Option;
publicinterfaceOptionRepositoryextends
CrudRepository<Option,Long>{
}
Takingthesameapproach,wethencreatePollRepositoryand
VoteRepositoryinterfaces,asshowninListings4-11and4-12.
Listing4-11.PollRepositoryinterface
publicinterfacePollRepositoryextendsCrudRepository<Poll,
Long>{
}
Listing4-12.OptionRepositoryinterface
publicinterfaceVoteRepositoryextendsCrudRepository<Vote,
Long>{
}
EmbeddedDatabase
Intheprevioussection,wecreatedrepositories,butweneedarelationaldatabasefor
persistingdata.Therelationaldatabasemarketisfullofoptionsrangingfromcommercial
databasessuchasOracle,SQLServertoopensourcedatabasessuchasMySQL,and
PostgreSQL.TospeedupourQuickPollapplicationdevelopment,wewillbeusing
HSQLDB,apopularin-memorydatabase.In-memory,akaembedded,databasesdon’t
requireanyadditionalinstallationsandcansimplyrunasaJVMprocess.Theirquick
startupandshutdowncapabilitiesmakethemidealcandidatesforprototypingand
integrationtesting.Atthesametime,theydon’tusuallyprovideapersistentstorageand
theapplicationneedstoseedthedatabaseeverytimeitbootstraps.
SpringBootprovidesexcellentsupportforHSQLDB-,H2-,andDerby-embedded
databases.Theonlyrequirementistoincludeabuilddependencyinthepom.xmlfile.
SpringBoottakescareofstartingthedatabaseduringdeploymentandstoppingitduring
applicationshutdown.ThereisnoneedtoprovideanydatabaseconnectionURLsor
usernameandpassword.Listing4-13showsthedependencyinformationthatneedstobe
addedtoQuickPoll’spom.xmlfile.
Listing4-13.HSQLDBPOM.XMLdependency
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
APIImplementation
Inthissection,wewillcreateSpringMVCcontrollersandimplementourRESTAPI
endpoints.Webeginbycreatingthecom.apress.controllerpackageunder
src\main\javatohouseallofthecontrollers.
PollControllerImplementation
ThePollControllerprovidesallofthenecessaryendpointstoaccessandmanipulate
thePollandPollsresources.Listing4-14showsabarebonesPollControllerclass.
Listing4-14.PollControllerclass
packagecom.apress.controller;
importjavax.inject.Inject;
import
org.springframework.web.bind.annotation.RestController;
importcom.apress.repository.PollRepository;
@RestController
publicclassPollController{
@Inject
privatePollRepositorypollRepository;
}
ThePollControllerclassisannotatedwithan@RestControllerannotation.
The@RestControllerisaconvenientyetmeaningfulannotationandhasthesame
effectasaddingboth@Controllerand@ResponseBodyannotations.Becausewe
needtoreadandstorePollinstances,weusethe@Injectannotationtoinjectan
instanceofPollRepositoryintoourcontroller.Thejavax.inject.Inject
annotationintroducedaspartofJavaEE6providesastandardmechanismfordeclaring
dependencies.WeusethisannotationinfavorofSpring’sproprietary@Autowired
annotationtobemorecompliant.Inordertousethe@Injectannotation,weneedtoadd
thedependencyshowninListing4-15tothepom.xmlfile.
Listing4-15.InjectdependencyinPOMfile
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
AGETrequestonthe/pollsendpointprovidesacollectionofallofthepolls
availableintheQuickPollsapplication.Listing4-16showsthenecessarycodefor
implementingthisfunctionality.The@RequestMappingannotationdeclarestheURI
andtheallowedHTTPmethod.ThegetAllPollsmethodusedResponseEntityas
itsreturntype,indicatingthatthereturnvalueisthecompleteHTTPresponse.
ResponseEntitygivesyoufullcontrolovertheHTTPresponse,includingthe
responsebodyandresponseheaders.Themethodimplementationbeginswithreadingall
ofthepollsusingthePollRepository.Wethencreateaninstanceof
ResponseEntityandpassinPolldataandtheHttpStatus.OKstatusvalue.The
PolldatabecomespartoftheresponsebodyandOK(code200)becomestheresponse
statuscode.
Listing4-16.GETverbimplementationfor/polls
@RequestMapping(value="/polls",method=RequestMethod.GET)
publicResponseEntity<Iterable<Poll>>getAllPolls(){
Iterable<Poll>allPolls=pollRepository.findAll();
returnnewResponseEntity<>(pollRepository.findAll(),
HttpStatus.OK);
}
Let’squicklytestourimplementationbyrunningtheQuickPollapplication.Ina
commandline,navigatetothequick-pollprojectdirectoryandrunthefollowing
command:
mvnspring-boot:run
LaunchthePostmanappinyourChromebrowserandentertheURL
http://localhost:8080/polls,asshowninFigure4-6,andhitSend.Because
wedon’thaveanypollscreatedyet,thiscommandwouldresultinanemptycollection.
Figure4-6.GetAllPollsrequest
NoteThedownloadedsourcecodecontainsanexportedPostmancollectionwith
requeststhatcanbeusedtoruntestsinthischapter.Simplyimportthiscollectioninto
yourPostmanapplicationandstartusingit.
Thenextstopforusistoimplementcapabilitytoaddnewpollstothe
PollController.WeaccomplishthisbyimplementingthePOSTverbfunctionality,
asshowninListing4-17.ThecreatePollmethodtakesaparameterofthetypePoll.
The@RequestBodyannotationtellsSpringthattheentirerequestbodyneedstobe
convertedtoaninstanceofPoll.SpringusestheincomingContent-Typeheaderto
identifyapropermessageconverteranddelegatestheactualconversiontoit.SpringBoot
comeswithmessageconvertersthatsupportJSONandXMLresourcerepresentations.
Insidethemethod,wesimplydelegatethePollpersistencetoPollRepository’s
savemethod.WethencreateanewResponseEntitywithstatusCREATED(201)
andreturnit.
Listing4-17.Implementationtocreatenewpoll
@RequestMapping(value="/polls",method=RequestMethod.POST)
publicResponseEntity<?>createPoll(@RequestBodyPollpoll)
{
poll=pollRepository.save(poll);
returnnewResponseEntity<>(null,HttpStatus.CREATED);
}
Althoughthisimplementationfulfillstherequest,theclienthasnowayofknowingthe
URIofthenewlycreatedPoll.Forexample,iftheclientwantstosharethenewly
createdPolltoasocialnetworkingsite,thecurrentimplementationwillnotsuffice.A
bestpracticeistoconveytheURItothenewlycreatedresourceusingtheLocation
HTTPheader.BuildingtheURIwouldrequireustoinspectthe
HttpServletRequestobjecttoobtaininformationsuchasRootURIandcontext.
SpringmakestheURIgenerationprocesseasyviaits
ServletUriComponentsBuilderutilityclass:
URInewPollUri=ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(poll.getId())
.toUri();
ThefromCurrentRequestmethodpreparesthebuilderbycopyinginformation
suchashost,schema,port,andsoonfromtheHttpServletRequest.Thepath
methodappendsthepassed-inpathparametertotheexistingpathinthebuilder.Inthe
caseofthecreatePollmethod,thiswouldresultin
http://localhost:8080/polls/{id}.ThebuildAndExpandmethodwould
buildanUriComponentsinstanceandreplacesanypathvariables({id}inourcase)
withpassed-invalue.Finally,weinvokethetoUrimethodontheUriComponents
classtogeneratethefinalURI.ThecompleteimplementationofthecreatePoll
methodisshowninListing4-18.
Listing4-18.CompleteimplementationofCreatePoll
@RequestMapping(value="/polls",method=RequestMethod.POST)
publicResponseEntity<?>createPoll(@RequestBodyPollpoll)
{
poll=pollRepository.save(poll);
//Setthelocationheaderforthenewlycreated
resource
HttpHeadersresponseHeaders=newHttpHeaders();
URInewPollUri=ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(poll.getId()
.toUri();
responseHeaders.setLocation(newPollUri);
returnnewResponseEntity<>(null,responseHeaders,
HttpStatus.CREATED);
}
Totestournewlyaddedfunctionality,starttheQuickPollapplication.Ifyouhavethe
applicationalreadyrunning,youneedtoterminatetheprocessandrestartit.Enterthe
informationinPostmanasshowninFigure4-7andhitSend.Makesurethatyouhave
addedtheContent-Typeheaderwithvalueapplication/json.TheJSONusedin
thebodyisshownhere:
{
"question":"WhowillwinSuperBowlthisyear?",
"options":[
{"value":"NewEnglandPatriots"},
{"value":"SeattleSeahawks"},
{"value":"GreenBayPackers"},
{"value":"DenverBroncos"}]
}
Figure4-7.CreatePollPostmanexample
Oncompletionoftherequest,youwillseeaStatus201Createdmessageandheaders:
Content-Length→0
Date→Mon,23Feb201500:05:11GMT
Location→http://localhost:8080/polls/1
Server→Apache-Coyote/1.1
Nowlet’sturnourattentiontoaccessinganindividualpoll.Listing4-19givesthe
necessarycode.ThevalueattributeintheRequestMappingtakesaURItemplate
/polls/{pollId}.Theplaceholder{pollId}alongwiththe@PathVarible
annotationallowsSpringtoexaminetherequestURIpathandextractthepollIdparameter
value.Insidethemethod,weusethePollRepository’sfindOnefindermethodto
readthepollandpassitaspartofaResponseEntity.
Listing4-19.Retreivinganindividualpoll
@RequestMapping(value="/polls/{pollId}",
method=RequestMethod.GET)
publicResponseEntity<?>getPoll(@PathVariableLongpollId)
{
Pollp=pollRepository.findOne(pollId);
returnnewResponseEntity<>(p,HttpStatus.OK);
}
Inthesamefashion,weimplementthefunctionalitytoupdateanddeleteaPoll,as
showninListing4-20.
Listing4-20.Updateanddeleteapoll
@RequestMapping(value="/polls/{pollId}",
method=RequestMethod.PUT)
publicResponseEntity<?>updatePoll(@RequestBodyPollpoll,
@PathVariableLongpollId){
//Savetheentity
Pollp=pollRepository.save(poll);
returnnewResponseEntity<>(HttpStatus.OK);
}
@RequestMapping(value="/polls/{pollId}",
method=RequestMethod.DELETE)
publicResponseEntity<?>deletePoll(@PathVariableLong
pollId){
pollRepository.delete(pollId);
returnnewResponseEntity<>(HttpStatus.OK);
}
OnceyouhavethiscodeaddedtothePollController,restarttheQuickPoll
applicationandexecutethePostmanrequestasshowninFigure4-7tocreateanewpoll.
TheninputtheinformationinFigure4-8tocreateanewPostmanrequestandupdatethe
poll.NoticethatthePUTrequestcontainstheentirePollrepresentationalongwithIDs.
Figure4-8.Updatepoll
ThisconcludestheimplementationofthePostController.
VoteControllerImplementation
FollowingtheprinciplesusedtocreatePollController,weimplementthe
VoteControllerclass.Listing4-21givesthecodefortheVoteControllerclass
alongwiththefunctionalitytocreateavote.TheVoteControllerusesaninjected
instanceofVoteRepositorytoperformCRUDoperationsonVoteinstances.
Listing4-21.VoteControllerimplementation
@RestController
publicclassVoteController{
@Inject
privateVoteRepositoryvoteRepository;
@RequestMapping(value="/polls/{pollId}/votes",
method=RequestMethod.POST)
publicResponseEntity<?>createVote(@PathVariableLong
pollId,@RequestBodyVotevote){
vote=voteRepository.save(vote);
//Settheheadersforthenewlycreated
resource
HttpHeadersresponseHeaders=new
HttpHeaders();
responseHeaders.setLocation(ServletUriComponentsBuil
returnnewResponseEntity<>(null,
responseHeaders,HttpStatus.CREATED);
}
}
Totestthevotingcapabilities,POSTanewVotetothe/polls/1/votesendpoint
withanoptionintherequestbody,asshowninFigure4-9.Onsuccessfulrequest
execution,youwillseeaLocationresponseheaderwithvalue
http://localhost:8080/polls/1/votes/1.
Figure4-9.Castanewvote
Next,welookatimplementingthecapabilitytoretrieveallvotesforagivenpoll.The
findAllmethodintheVoteRepositoryreturnsallvotesinthedatabase.Because
thiswouldnotmeetourneeds,weneedtoaddthisfunctionalitytothe
VoteRepositoryasshowninListing4-22.
Listing4-22.ModifiedVoteRepositoryImplementation
importorg.springframework.data.jpa.repository.Query;
publicinterfaceVoteRepositoryextendsCrudRepository<Vote,
Long>{
@Query(value="selectv.*fromOptiono,Votevwhere
o.POLL_ID=?1andv.OPTION_ID=o.OPTION_ID",nativeQuery
=true)
publicIterable<Vote>findByPoll(LongpollId);
}
ThecustomfindermethodfindVotesByPolltakestheIDofthePollasits
parameter.The@QueryannotationonthismethodtakesanativeSQLqueryalongwith
thenativeQueryflagsettotrue.Atruntime,SpringDataJPAreplacesthe?1
placeholderwiththepassed-inpollIdparametervalue.Next,weimplementthe
/polls/{pollId}/votesendpointintheVoteController,asshowninListing
4-23.
Listing4-23.GETAllVotesImplementation
@RequestMapping(value="/polls/{pollId}/votes",
method=RequestMethod.GET)
publicIterable<Vote>getAllVotes(@PathVariableLongpollId)
{
returnvoteRepository.findByPoll(pollId);
}
ComputeResultControllerImplementation
ThefinalpieceremainingforusistheimplementationoftheComputeResultresource.
Becausewedon’thaveanydomainobjectsthatcandirectlyhelpgeneratethisresource
representation,weimplementtwoDataTransferObjectsorDTOs—OptionCountand
VoteResult.TheOptionCountDTOcontainstheIDoftheoptionandacountof
votescastedforthatoption.TheVoteResultDTOcontainsthetotalvotescastanda
collectionofOptionCountinstances.ThesetwoDTOsarecreatedunderthe
com.apress.dtopackage,andtheirimplementationisgiveninListing4-24.
Listing4-24.DTOsforComputeResultresources
packagecom.apress.dto;
publicclassOptionCount{
privateLongoptionId;
privateintcount;
//GettersandSettersomittedforbrevity
}
packagecom.apress.dto;
importjava.util.Collection;
publicclassVoteResult{
privateinttotalVotes;
privateCollection<OptionCount>results;
//GettersandSettersomittedforbrevity
}
FollowingtheprinciplesusedincreatingthePollControllerand
VoteController,wecreateanewComputeResultControllerclass,asshown
inListing4-25.WeinjectaninstanceofVoteRepositoryintothecontroller,whichis
usedtoretrievevotesforagivenpoll.ThecomputeResultmethodtakespollIdas
itsparameter.The@RequestParamannotationinstructsSpringtoretrievethepollId
valuefromaHTTPqueryparameter.Thecomputedresultsaresenttotheclientusinga
newlycreatedinstanceofResponseEntity.
Listing4-25.ComputeResultControllerimplementation
packagecom.apress.controller;
@RestController
publicclassComputeResultController{
@Inject
privateVoteRepositoryvoteRepository;
@RequestMapping(value="/computeresult",
method=RequestMethod.GET)
publicResponseEntity<?>computeResult(@RequestParam
LongpollId){
VoteResultvoteResult=newVoteResult();
Iterable<Vote>allVotes
=voteRepository.findByPoll(pollId);
//Algorithmtocountvotes
returnnewResponseEntity<VoteResult>
(voteResult,HttpStatus.OK);
}
}
Thereareseveralwaystocountvotesassociatedwitheachoption.Thiscodeprovides
onesuchoption:
inttotalVotes=0;
Map<Long,OptionCount>tempMap=newHashMap<Long,
OptionCount>();
for(Votev:allVotes){
totalVotes++;
//GettheOptionCountcorrespondingtothisOption
OptionCountoptionCount
=tempMap.get(v.getOption().getId());
if(optionCount==null){
optionCount=newOptionCount();
optionCount.setOptionId(v.getOption().getId());
tempMap.put(v.getOption().getId(),
optionCount);
}
optionCount.setCount(optionCount.getCount()+1);
}
voteResult.setTotalVotes(totalVotes);
voteResult.setResults(tempMap.values());
ThisconcludestheComputeResultcontrollerimplementation.Start/restartthe
QuickPollapplication.UsingtheearlierPostmanrequests,createapollandcastvoteson
itsoptions.ThencreateanewPostmanrequestasshowninFigure4-10andsubmititto
testour/computeresultendpoint.
Figure4-10.ComputeResultEndpointtest
Onsuccessfulcompletion,youwillseeanoutputsimilartothis:
{
"totalVotes":7,
"results":[
{
"optionId":1,
"count":4
},
{
"optionId":2,
"count":3
}
]
}
Summary
Inthischapter,welookedatcreatingRESTfulservicesfortheQuickPollapplication.
Mostofourexamplesinthischapterassumeda“happypath”inwhicheverythinggoesas
planned.However,thisrarelyhappensintherealworld.Inthenextchapterwewilllook
athandlingerrors,validatinginputdata,andcommunicatingmeaningfulerrormessages.
CHAPTER5
ErrorHandling
Inthischapterwewilldiscuss:
HandlingerrorsinaRESTAPI
Designingmeaningfulerrorresponses
ValidatingAPIinputs
Externalizingerrormessages
Errorhandlingisoneofthemostimportantyetsomewhatoverlookedtopicsfor
programmers.Althoughwedevelopsoftwarewithgoodintent,thingsdogowrongandwe
mustbepreparedtohandleandcommunicatethoseerrorsgracefully.Thecommunication
aspectisespeciallyimportanttodevelopersconsumingaRESTAPI.Well-designederror
responsesallowconsumingdeveloperstounderstandtheissuesandhelpthemtousethe
APIcorrectly.Additionally,gooderrorhandlingallowsAPIdeveloperstologinformation
thatcanaidindebuggingissuesontheirend.
QuickPollErrorHandling
InourQuickPollapplication,considerthescenarioinwhichausertriestoretrieveapoll
thatdoesn’texist.Figure5-1showsthePostmanrequestforanonexistentpollwithid
100.
Figure5-1.Requestinganonexistentpoll
Onreceivingtherequest,thePollControllerinourQuickPollapplicationuses
PollRepositorytoretrievethepoll.Becauseapollwithid100doesn’texist,
PollRepository’sfindOnemethodreturnsanullandthePollController
sendsanemptybodytotheclient,asshowninFigure5-2.
Figure5-2.Responsetoanonexistentpoll
NoteInthischapter,wewillcontinueworkingontheQuickPollapplicationthatwe
builtinthepreviouschapter.ThecodeisalsoavailableundertheChapter5\starter
folderofthedownloadedsourcecode.Thecompletedsolutionisavailableunderthe
Chapter5\finalfolder.Aswehaveomittedgetter/settermethodsandimportsin
someofthelistingsinthischapter,pleaserefertothecodeunderthefinalfolderfor
completelistings.TheChapter5folderalsocontainsanexportedPostmancollection
containingRESTAPIrequestsassociatedwiththischapter.
Thiscurrentimplementationisdeceptive,astheclientreceivesastatuscode200.
Instead,astatuscode404shouldbereturned,indicatingthattherequestedresource
doesn’texist.Toachievethiscorrectbehavior,wewillvalidatethepollidinthe
com.apress.controller.PollController’sgetPollmethodand,for
nonexistentpolls,throwa
com.apress.exception.ResourceNotFoundExceptionexception.Listing51showsthemodifiedgetPollimplementation.
Listing5-1.getPollimplementation
@RequestMapping(value="/polls/{pollId}",
method=RequestMethod.GET)
publicResponseEntity<?>getPoll(@PathVariableLongpollId)
{
Pollp=pollRepository.findOne(pollId);
if(p==null){
thrownewResourceNotFoundException("Pollwith
id"+pollId+"notfound");
}
returnnewResponseEntity<>(p,HttpStatus.OK);
}
TheResourceNotFoundExceptionisacustomexceptionandits
implementationisshowninListing5-2.Noticethatan@ResponseStatusannotation
isdeclaredattheclasslevel.TheannotationinstructsSpringMVCthatanHttpStatus
NOT_FOUND(404code)shouldbeusedintheresponsewhena
ResourceNotFoundExceptionisthrown.
Listing5-2.ResourceNotFoundExceptionimplementation
packagecom.apress.exception;
importorg.springframework.http.HttpStatus;
import
org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
publicclassResourceNotFoundExceptionextends
RuntimeException{
privatestaticfinallongserialVersionUID=1L;
publicResourceNotFoundException(){}
publicResourceNotFoundException(Stringmessage){
super(message);
}
publicResourceNotFoundException(Stringmessage,
Throwablecause){
super(message,cause);
}
}
Withthesemodificationsinplace,starttheQuickPollapplicationandrunthePostman
requestforpollwithID100.ThePollControllerreturnstherightstatuscodeas
showninFigure5-3.
Figure5-3.Newresponseforanonexistentpoll
InadditiontoGET,otherHTTPmethodssuchasPUT,DELETE,andPATCHacton
existingPollresources.Hence,weneedtoperformthesamepollIDvalidationinthe
correspondingmethodssothatwereturntherightstatuscodetotheclient.Listing5-3
showsthepollidverificationlogicencapsulatedintoaPollController’s
verifyPollmethodalongwiththemodifiedgetPoll,updatePoll,and
deletePollmethods.
Listing5-3.UpdatedPollController
protectedvoidverifyPoll(LongpollId)throws
ResourceNotFoundException{
Pollpoll=pollRepository.findOne(pollId);
if(poll==null){
thrownewResourceNotFoundException("Pollwith
id"+pollId+"notfound");
}
}
@RequestMapping(value="/polls/{pollId}",
method=RequestMethod.GET)
publicResponseEntity<?>getPoll(@PathVariableLongpollId)
{
verifyPoll(pollId);
Pollp=pollRepository.findOne(pollId);
returnnewResponseEntity<>(p,HttpStatus.OK);
}
@RequestMapping(value="/polls/{pollId}",
method=RequestMethod.PUT)
publicResponseEntity<?>updatePoll(@RequestBodyPollpoll,
@PathVariableLongpollId){
verifyPoll(pollId);
pollRepository.save(poll);
returnnewResponseEntity<>(HttpStatus.OK);
}
@RequestMapping(value="/polls/{pollId}",
method=RequestMethod.DELETE)
publicResponseEntity<?>deletePoll(@PathVariableLong
pollId){
verifyPoll(pollId);
pollRepository.delete(pollId);
returnnewResponseEntity<>(HttpStatus.OK);
}
ErrorResponses
HTTPstatuscodesplayanimportantroleinRESTAPIs.APIdevelopersshouldstriveto
returntherightcodesindicatingtherequeststatus.Additionally,itisgoodpracticeto
providehelpful,fine-graineddetailsregardingtheerrorintheresponsebody.Thesedetails
willenableAPIconsumerstotroubleshootissueseasilyandhelpthemtorecover.Asyou
canseeinFigure5-3,SpringBootfollowsthispracticeandincludesthefollowingdetails
inerrorresponsebodies:
timestamp—Thetimeinmillisecondswhentheerrorhappened
status—HTTPstatuscodeassociatedwiththeerror;thisispartly
redundantasitissameastheresponsestatuscode
error—Thedescriptionassociatedwiththestatuscode
exception—Thefullyqualifiedpathtotheexceptionclassresultingin
thiserror
message—Themessageprovidingmoredetailsabouttheerror
path—TheURIthatresultedintheexception
ThesedetailsaregeneratedbytheSpringBootframework.Thisfeatureisnot
availableoutoftheboxinnon–BootSpringMVCapplications.Inthissection,wewill
implementasimilarerrorresponseforaQuickPollapplicationusinggenericSpringMVC
componentssothatitworksinbothBootandnon-Bootenvironments.Beforewediveinto
thisimplementation,let’slookattheerrorresponsedetailsoftwopopularapplications:
GitHubandTwilio.Figure5-4showsGitHub’serrorresponsedetailsforarequest
containinginvalidinputs.Themessageattributegivesaplaindescriptionoftheerrorand
theerrorsattributeliststhefieldswithinvalidinputs.Inthisexample,theclient’srequest
ismissingtheIssueresource’stitlefield.
Figure5-4.GitHuberrorresponse
TwilioprovidesanAPIthatallowsdevelopersprogrammaticallymakephonecalls,
sendtexts,andreceivetexts.Figure5-5showstheerrorresponseforaPOSTcallthatis
missinga“To”phonenumber.ThestatusandmessagefieldsaresimilartofieldsinSpring
Boot’sresponse.Thecodefieldcontainsanumericcodethatcanbeusedtofindmore
informationabouttheexception.Themore_infofieldcontainstheURLforerrorcode
documentation.Onreceivingthiserror,aTwilioAPIconsumercannavigateto
https://www.twilio.com/docs/errors/21201andgetmoreinformationto
troubleshoottheerror.
Figure5-5.Twilioerrorresponse
Itisclearthatthereisnotastandardresponseformatforerrors.ItisuptotheAPIand
frameworkimplementerstodecideonthedetailstobesenttotheclient.However,
attemptstostandardizetheresponseformathavebegunandanIETFspecificationknown
asProblemDetailsforHTTPAPIs(http://tools.ietf.org/html/draftnottingham-http-problem-06)isgainingtraction.Inspiredbythe“Problem
DetailsforHTTPAPIs”specification,Listing5-4showstheerrorresponseformatthatwe
willbeimplementinginourQuickPollapplication.
Listing5-4.QuickPollErrorResponseFormat
{
"title":"",
"status":"",
"detail":",
"timestamp":"",
"developerMessage:"",
"errors":{}
}
HereisabriefdescriptionofthefieldsintheQuickPollerrorresponse:
title—Thetitlefieldprovidesabrieftitlefortheerror
condition.Forexample,errorsresultingasaresultofinputvalidation
willhavethetitle“ValidationFailure”.Similarly,an“InternalServer
Error”willbeusedforinternalservererrors.
status—ThestatusfieldcontainstheHTTPstatuscodeforthe
currentrequest.Eventhoughitisredundanttoincludestatuscodein
theresponsebody,itallowsAPIclientstolookforalltheinformation
thatitneedstotroubleshootinoneplace.
detail—Thedetailfieldcontainsashortdescriptionoftheerror.
Theinformationinthisfieldistypicallyhuman-readableandcanbe
presentedtoanenduser.
timestamp—Thetimeinmillisecondswhentheerroroccurred.
developerMessage—ThedeveloperMessagecontains
informationsuchasexceptionclassnameorstacktracethatisrelevant
todevelopers.
errors—Theerrorsfieldisusedtoreportfieldvalidationerrors.
Nowthatwehaveourerrorresponsedefined,wearereadytomodifyQuickPoll
application.WebeginbycreatingaJavarepresentationoftheresponsedetails,asshown
inListing5-5.Asyoucansee,theErrorDetailclassismissingtheerrorsfield.We
willbeaddingthatfunctionalityintheupcomingsection.
Listing5-5.Errorresponsedetailsrepresentation
packagecom.apress.dto.error;
publicclassErrorDetail{
privateStringtitle;
privateintstatus;
privateStringdetail;
privatelongtimeStamp;
privateStringdeveloperMessage;
//GettersandSettersommitedforbrevity
}
Errorhandlingisacrosscuttingconcern.Weneedanapplication-widestrategythat
handlesalloftheerrorsinthesamewayandwritestheassociateddetailstotheresponse
body.AswediscussedinChapter2,classesannotatedwith@ControllerAdvicecan
beusedtoimplementsuchcrosscuttingconcerns.Listing5-6showsthe
RestExceptionHandlerclasswithanaptlynamed
handleResourceNotFoundExceptionmethod.Thankstothe
@ExceptionHandlerannotation,anytimeaResourceNotFoundExceptionis
thrownbyacontroller,SpringMVCwouldinvoketheRestExceptionHandler’s
handleResourceNotFoundExceptionmethod.Insidethismethod,wecreatean
instanceofErrorDetailandpopulateitwitherrorinformation.
Listing5-6.RestExceptionHandlerimplementation
packagecom.apress.handler;
importjava.util.Date;
importjavax.servlet.http.HttpServletRequest;
importorg.springframework.http.HttpStatus;
importorg.springframework.http.ResponseEntity;
import
org.springframework.web.bind.annotation.ControllerAdvice;
import
org.springframework.web.bind.annotation.ExceptionHandler;
importcom.apress.dto.error.ErrorDetail;
importcom.apress.exception.ResourceNotFoundException;
@ControllerAdvice
publicclassRestExceptionHandler{
@ExceptionHandler(ResourceNotFoundException.class)
publicResponseEntity<?>
handleResourceNotFoundException(ResourceNotFoundException
rnfe,HttpServletRequestrequest){
ErrorDetailerrorDetail=newErrorDetail();
errorDetail.setTimeStamp(newDate().getTime());
errorDetail.setStatus(HttpStatus.NOT_FOUND.value());
errorDetail.setTitle("ResourceNotFound");
errorDetail.setDetail(rnfe.getMessage());
errorDetail.setDeveloperMessage(rnfe.getClass().getN
returnnewResponseEntity<>(errorDetail,null,
HttpStatus.NOT_FOUND);
}
}
Toverifythatournewlycreatedhandlerworksasexpected,restarttheQuickPoll
applicationandsubmitaPostmanrequesttoanonexistentpollwithid100.Youshould
seeanerrorresponseasshowninFigure5-6.
Figure5-6.ResourceNotFoundExceptionerrorresponse
InputFieldValidation
Asafamousproverbgoes,“GarbageinGarbageOut”;inputfieldvalidationshouldbe
anotherareaofemphasisineveryapplication.Considerthescenarioinwhichaclient
requestsanewpolltobecreatedbutdoesn’tincludethepollquestionintherequest.
Figure5-7showsaPostmanrequestwithamissingquestionandthecorresponding
response.MakesurethatyousettheContent-Typeheaderto“application/json”before
firingthePostmanrequest.Fromtheresponse,youcanseethatthepollstillgetscreated.
Creatingapollwithamissingquestioncanresultindatainconsistenciesandotherbugs.
Figure5-7.Creatingapollwithamissingquestion
SpringMVCprovidestwooptionsforvalidatinguserinput.Inthefirstoption,we
createavalidatorthatimplementsthe
org.springframework.validation.Validatorinterface.Thenweinjectthis
validatorintoacontrollerandinvokevalidator’svalidatemethodmanuallytoperform
validation.ThesecondoptionistousetheJSR303validation,anAPIintendedtosimplify
fieldvalidationinanylayeroftheapplication.Consideringthesimplicityandthe
declarativenatureoftheframework,wewillbeusingJSR303validationframeworkin
thisbook.
TheJSR303andJSR349definespecificationsfortheBeanValidationAPI(version
1.0and1.1,respectively).TheyprovideametadatamodelforJavaBeanvalidationviaa
setofstandardizedvalidationconstraints.UsingthisAPI,youannotatedomainobject
propertieswithvalidationconstraintssuchas@NotNulland@Email.Implementing
frameworksenforcetheseconstraintsatruntime.Inthisbook,wewillbeusingHibernate
Validator,apopularJSR303/349implementationframework.Table5-1showssomeof
theout-of-the-boxvalidationconstraintsavailablewithBeanValidationAPI.Additionally,
itispossibletodefineyourowncustomconstraints.
Table5-1.BeanValidationAPIconstraints
Constraint
Description
NotNull
Annotatedfieldmustnothavenullvalue
Null
Annotatedfieldmustbenull
Max
Annotatedfieldvaluemustbeanintegervaluelowerthanorequaltothenumberspecifiedin
theannotation
Min
Annotatedfieldvaluemustbeanintegervaluegreaterthanorequaltothenumberspecifiedin
theannotation
Past
Annotatedfieldmustbeadateinthepast
Future
Annotatedfieldmustbeadateinthefuture
Size
Annotatedfieldmustmatchtheminandmaxboundariesspecifiedintheannotation.Forafield
thatisaCollection,thesizeoftheCollectionismatchedagainstboundaries.ForaStringfield,
thelengthofthestringisverifiedagainstboundaries
Pattern
Annotatedfieldmustmatchtheregularexpressionspecifiedintheannotation
ToaddvalidationcapabilitiestoQuickPoll,westartbyannotatingthePollclassas
showninListing5-7.BecausewewanttomakesurethateachPollhasaquestion,we
annotatedthequestionfieldwithan@NonEmptyannotation.The
org.hibernate.validator.constraints.NonEmptyannotationisnotpartof
JSR303/349API.Instead,itispartofHibernateValidator;itensuresthattheinputstring
isnotnullanditslengthisgreaterthanzero.Also,tomaketheexperienceoftakingapoll
simpler,wewillrestricteachpolltocontainnofewerthantwoandnomorethansix
options.
Listing5-7.PollclassannotatedwithJSR303annotations
@Entity
publicclassPoll{
@Id
@GeneratedValue
@Column(name="POLL_ID")
privateLongid;
@Column(name="QUESTION")
@NotEmpty
privateStringquestion;
@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="POLL_ID")
@OrderBy
@Size(min=2,max=6)
privateSet<Option>options;
//GettersandSettersremovedforbrevity
}
Wenowmoveourattentiontothe
com.apress.controller.PollControllerandaddan@Validannotationto
thecreatePollmethod’sPollparameter,asshowninListing5-8.The@Valid
annotationinstructsSpringtoperformdatavalidationafterbindingtheuser-submitted
data.SpringdelegatestheactualvalidationtoaregisteredValidator.WithSpringBoot
addingJSR303/JSR349andHibernatevalidatorjarstotheclasspath,theJSR303/JSR
349isenabledautomaticallyandwillbeusedtoperformthevalidation.
Listing5-8.PollControllerannotatedwith@Validannotations
@RequestMapping(value="/polls",method=RequestMethod.POST)
publicResponseEntity<?>createPoll(@Valid@RequestBodyPoll
poll){
poll=pollRepository.save(poll);
//Setthelocationheaderforthenewlycreated
resource
HttpHeadersresponseHeaders=newHttpHeaders();
URInewPollUri=ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}").buildAndExpand(poll.getId()).toUri();
responseHeaders.setLocation(newPollUri);
returnnewResponseEntity<>(null,responseHeaders,
HttpStatus.CREATED);
}
OnrepeatingthePostmanrequestwithamissingquestionaswedidinFigure5-7,you
willseetheoperationfailwithanerrorcode400,asshowninFigure5-8.Fromtheerror
response,noticethatSpringMVCcompletedvalidatingtheinput.Onnotfindingthe
requiredquestionfield,itthrewaMethodArgumentNotValidException
exception.
Figure5-8.Missingquestionresultinginerror
EventhoughSpringBoot’serrormessageishelpful,tobeconsistentwithour
QuickPollerrorresponsethatwedesignedinListing5-4,wewillmodifythe
RestExceptionHandlersothatwecanintercepta
MethodArgumentNotValidExceptionexceptionandreturnanappropriate
ErrorDetailinstance.WhileweweredesigningtheQuickPollerrorresponse,we
cameupwithanerrorsfieldthatcanholdourvalidationerrors.Itispossibleforafieldto
haveoneormorevalidationerrorsassociatedwithit.Forexample,amissingquestion
fieldinourPollexamplewouldresultina“Fieldmaynotbenull”validationerror.Inthe
sameway,anemptyemailaddresscouldresultin“Fieldmaynotbenull”and“Fieldis
notawellformedemail”validationerrors.Keepingthesevalidationconstraintsinmind,
Listing5-9showsacompleteerrorresponsewiththevalidationerrorexamples.The
errorsobjectcontainsanunorderedcollectionofkey-valueerrorinstances.Theerror
keyrepresentsthenameoftheresourcefeedthathasvalidationerrors.Theerrorvalueis
anarrayrepresentingthevalidationerrordetails.FromListing5-9,wecanseethatfield1
containsonevalidationerrorandfield2isassociatedwithtwovalidationerrors.Each
validationerroritselfismadeupofcodethatrepresentstheviolatedconstraintanda
messagecontainingahuman-readableerrorrepresentation.
Listing5-9.Validationerrorformat
{
"title":"",
"status":"",
"detail":",
"timestamp":"",
"path":"",
"developerMessage:"",
"errors":{
"field1":[{
"code":"NotNull",
message":"Field1maynotbenull"
}],
"field2":[{
"code":"NotNull",
"message":"Field2maynotbe
null"
},
{
"code":"Email",
"message":"Field2isnotawell
formedemail"
}]
}
}
TorepresentthenewlyaddedvalidationerrorfeatureintheJavacode,wecreateda
newcom.apress.dto.error.ValidationErrorclass.Listing5-10showsthe
ValidationErrorclassandupdatedErrorDetailclass.Inordertogeneratethe
errorresponseformatshownininListing5-9,theerrorsfieldinErrorDetailclassis
definedasaMapthatacceptsStringinstancesaskeysandListof
ValidationErrorinstancesasvalues.
Listing5-10.ValidationErrorandupdatedErrorDetailclasses
packagecom.apress.dto.error;
publicclassValidationError{
privateStringcode;
privateStringmessage;
//GettersandSettersremovedforbrevity
}
publicclassErrorDetail{
privateStringtitle;
privateintstatus;
privateStringdetail;
privatelongtimeStamp;
privateStringpath;
privateStringdeveloperMessage;
privateMap<String,List<ValidationError>>errors
=newHashMap<String,List<ValidationError>>();
//Gettersandsettersremovedforbrevity
}
ThenextstepistomodifytheRestExceptionHandlerbyaddingamethodthat
interceptsandprocessestheMethodArgumentNotValidExceptionexception.
Listing5-11showsthehandleValidationErrormethodimplementationin
RestExceptionHandler.Webeginthemethodimplementationbycreatingan
instanceofErrorDetailandpopulatingit.Thenweusethepassed-inexception
parametertoobtainallthefielderrorsandloopthroughthelist.Wecreatedaninstanceof
ValidationErrorforeachfielderrorandpopulateditwithcodeandmessage
information.
Listing5-11.handleValidationErrorimplementation
@ControllerAdvice
publicclassRestExceptionHandler{
@ExceptionHandler(MethodArgumentNotValidException.class)
publicResponseEntity<?>
handleValidationError(MethodArgumentNotValidExceptionmanve,
HttpServletRequestrequest){
ErrorDetailerrorDetail=newErrorDetail();
//PopulateerrorDetailinstance
errorDetail.setTimeStamp(newDate().getTime());
errorDetail.setStatus(HttpStatus.BAD_REQUEST.value()
StringrequestPath=(String)
request.getAttribute("javax.servlet.error.request_uri");
if(requestPath==null){
requestPath=request.getRequestURI();
}
errorDetail.setTitle("ValidationFailed");
errorDetail.setDetail("Inputvalidation
failed");
errorDetail.setDeveloperMessage(manve.getClass().get
//CreateValidationErrorinstances
List<FieldError>fieldErrors
=manve.getBindingResult().getFieldErrors();
for(FieldErrorfe:fieldErrors){
List<ValidationError>validationErrorList
=errorDetail.getErrors().get(fe.getField());
if(validationErrorList==null){
validationErrorList=new
ArrayList<ValidationError>();
errorDetail.getErrors().put(fe.getField(
validationErrorList);
}
ValidationErrorvalidationError=new
ValidationError();
validationError.setCode(fe.getCode());
validationError.setMessage(fe.getDefaultMessag
validationErrorList.add(validationError);
}
returnnewResponseEntity<>(errorDetail,null,
HttpStatus.BAD_REQUEST);
}
/**handleResourceNotFoundExceptionmethodremoved**/
}
Withthisimplementationinplace,restarttheQuickPollapplicationandsubmitaPoll
withmissingquestion.Thiswillresultinastatuscodeof400withourcustomerror
response,asshowninFigure5-9.
Figure5-9.Validationerrorreponse
ExternalizingErrorMessages
Wehavemadequiteabitofprogresswithourinputvalidationandprovidedtheclient
withdescriptiveerrormessagesthatcanhelpthemtroubleshootandrecoverfromthose
errors.However,theactualvalidationerrormessagemaynotbeverydescriptiveandAPI
developersmightwanttochangeit.Itwouldbeevenbetteriftheywereabletopullthis
messagefromanexternalpropertiesfile.Thepropertyfileapproachnotonlysimplifies
Javacodebutalsomakesiteasytoswapthemessageswithoutmakingcodechanges.It
alsosetsthestageforfutureinternationalization/localizationneeds.Toachievethis,create
amessages.propertiesfileunderthesrc\main\resourcesfolderandaddthe
followingtwomessages:
NotEmpty.poll.question=Questionisarequiredfield
Size.poll.options=Optionsmustbegreaterthan{2}andless
than{1}
Asyoucansee,wearefollowingtheconvention<<Constraint_Name>>.model_name.field_Nameforeachkeyofthemessage.
Themodel_namerepresentsnameoftheSpringMVC’smodelobjecttowhichuser
submitteddataisbeingbound.Thenameistypicallyprovidedusingthe
@ModelAttributeannotation.Inthescenariosinwhichthisannotationismissing,the
modelnameisderivedusingtheparameter’snonqualifiedclassname.The
PollController’screatePollmethodtakesacom.apress.domain.Poll
instanceasitsmodelobject.Hence,inthiscase,themodelnamewillbederivedaspoll.If
acontrollerweretotakeaninstanceofcom.apress.domain.SomeObjectasits
parameter,thederivedmodelnamewillbesomeObject.Itisimportanttorememberthat
Springwillnotusethenameofthemethodparameterasthemodelname.
Thenextstepistoreadthepropertiesfromthefileandusethemduringthe
ValidationErrorinstancecreation.Wedothatbyinjectinganinstanceof
MessageSourceintotheRestExceptionHandlerclass.Spring’s
MessageSourceprovidesanabstractiontoeasilyresolvemessages.Listing5-12shows
themodifiedsourcecodeforhandleValidationError.Noticethatweareusing
MessageResource’sgetMessagemethodtoretrievemessages.
Listing5-12.Readingmessagesfrompropertiesfile
@ControllerAdvice
publicclassRestExceptionHandler{
@Inject
privateMessageSourcemessageSource;
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public@ResponseBodyErrorDetail
handleValidationError(MethodArgumentNotValidExceptionmanve,
HttpServletRequestrequest){
ErrorDetailerrorDetail=newErrorDetail();
//PopulateerrorDetailinstance
errorDetail.setTimeStamp(newDate().getTime());
errorDetail.setStatus(HttpStatus.BAD_REQUEST.value()
StringrequestPath=(String)
request.getAttribute("javax.servlet.error.request_uri");
if(requestPath==null){
requestPath=request.getRequestURI();
}
errorDetail.setTitle("ValidationFailed");
errorDetail.setDetail("Inputvalidation
failed");
errorDetail.setDeveloperMessage(manve.getClass().get
//CreateValidationErrorinstances
List<FieldError>fieldErrors
=manve.getBindingResult().getFieldErrors();
for(FieldErrorfe:fieldErrors){
List<ValidationError>validationErrorList
=errorDetail.getErrors().get(fe.getField());
if(validationErrorList==null){
validationErrorList=new
ArrayList<ValidationError>();
errorDetail.getErrors().put(fe.getField(
validationErrorList);
}
ValidationErrorvalidationError=new
ValidationError();
validationError.setCode(fe.getCode());
validationError.setMessage(messageSource.getMe
null));
validationErrorList.add(validationError);
}
returnerrorDetail;
}
}
RestartingtheQuickPollapplicationandsubmittingapollwithamissingquestion
wouldresultinthenewvalidationerrormessageasshowninFigure5-10.
Figure5-10.Newvalidationerrormessage
ImprovingRestExceptionHandler
Bydefault,SpringMVChandleserrorscenariossuchasnotbeingabletoreada
malformedrequestornotfindingarequiredrequestparameterbythrowingasetof
standardexceptions.However,SpringMVCdoesn’twritethesestandardexceptiondetails
totheresponsebody.TokeepthingsconsistentforourQuickPollclients,itisimportant
thatSpringMVCstandardexceptionsarealsohandledinthesamewayandthatwereturn
thesameerrorresponseformat.Astraightforwardapproachistocreateahandlermethod
foreachexceptioninourRestExceptionHandler.Asimplerapproachistohave
RestExceptionHandlerclassextendSpring’s
ResponseEntityExceptionHandler.The
ResponseEntityExceptionHandlerclasscontainsasetofprotectedmethodsthat
handlestandardexceptionandreturnaResponseEntityinstancecontainingerror
details.
ExtendingtheResponseEntityExceptionHandlerclassallowsustooverride
theprotectedmethodassociatedwiththeexceptionandreturnanErrorDetail
instance.Listing5-13showsamodifiedRestExceptionHandlerthatoverrides
handleHttpMessageNotReadablemethod.Themethodimplementationfollows
thesamepatternthatweusedbefore—createandpopulateaninstanceofErrorDetail.
BecausetheResponseEntityExceptionHandleralreadycomeswithahandler
methodforMethodArgumentNotValidException,wehavemovedthe
handleValidationErrormethodcodetoanoverridden
handleMethodArgumentNotValidmethod.
Listing5-13.RestExceptionHandlerhandlingmalformedmessages
@ControllerAdvice
publicclassRestExceptionHandlerextends
ResponseEntityExceptionHandler{
@Override
protectedResponseEntity<Object>
handleHttpMessageNotReadable(
HttpMessageNotReadableExceptionex,
HttpHeadersheaders,
HttpStatusstatus,WebRequestrequest){
ErrorDetailerrorDetail=newErrorDetail();
errorDetail.setTimeStamp(newDate().getTime());
errorDetail.setStatus(status.value());
errorDetail.setTitle("MessageNotReadable");
errorDetail.setDetail(ex.getMessage());
errorDetail.setDeveloperMessage(ex.getClass().getNam
returnhandleExceptionInternal(ex,errorDetail,
headers,status,request);
}
@Override
publicResponseEntity<Object>
handleMethodArgumentNotValid(MethodArgumentNotValidException
manve,HttpHeadersheaders,HttpStatusstatus,WebRequest
request){
//implementationremovedforbrevity
returnhandleExceptionInternal(manve,
errorDetail,headers,status,request);
}
}
Let’squicklyverifyourimplementationbysubmittinganonreadablemessage(suchas
removinga’,’fromtheJSONrequestbody)usingPostman.Youshouldseearesponseas
showninFigure5-11.
Figure5-11.NotReadablemessageerror
Summary
Inthischapter,wedesignedandimplementedanerrorresponseformatforSpringMVC–
basedRESTapplications.Wealsolookedatvalidatinguserinputandreturningerror
messagesthataremeaningfultoAPIconsumers.Inthenextchapter,wewilllookat
strategiesfordocumentingRESTservicesusingtheSwaggerframework.
CHAPTER6
DocumentingRESTServices
Inthischapterwewilldiscuss:
ThebasicsofSwagger
UsingSwaggerforAPIdocumentation
CustomizingSwagger
Documentationisanimportantaspectofanyproject.Thisisespeciallytruefor
enterpriseandopensourceprojects,Swaggerwheremanypeoplecollaboratetobuildthe
project.Inthischapter,wewilllookatSwagger,atoolthatsimplifiesRESTAPI
documentation.
DocumentingaRESTAPIforconsumerstouseandinteractwithisadifficulttask
becausetherearenorealestablishedstandards.Organizationshavehistoricallyreliedon
manuallyediteddocumentstoexposeRESTcontractstoclients.WithSOAP-basedWeb
services,aWSDLservesasacontractfortheclientandprovidesadetaileddescriptionof
theoperationsandassociatedrequest/responsepayloads.TheWADL,orWebApplication
DescriptionLanguage,specificationtriedtofillthisgapintheRESTWebservicesworld,
butitdidn’tgetalotofadoption.Inrecentyears,therehasbeenagrowthinthenumberof
metadatastandardssuchasSwagger,Apiary,andiODocsfordescribingRESTservices.
MostofthemgrewoutoftheneedtodocumentAPIs,therebyexpandinganAPI’s
adoption.
Swagger
Swagger(http://swagger.io)isaspecificationandaframeworkforcreating
interactiveRESTAPIdocumentation.Itenablesdocumentationtobeinsyncwithany
changesmadetoRESTservices.ItalsoprovidesasetoftoolsandSDKgeneratorsfor
generatingAPIclientcode.SwaggerwasoriginallydevelopedbyWordnikinearly2010
andiscurrentlybackedbySmartBearsoftware.
Swaggerisalanguage-agnosticspecificationwithimplementationsavailablefora
varietyoflanguagessuchasJava,Scala,andPHP.Afulldescriptionofthe1.2
specificationcanbefoundathttps://github.com/swagger-api/swaggerspec/blob/master/versions/1.2.md.Thespecificationismadeupoftwofile
types—aresourcelistingfileandasetofAPIdeclarationfilesthatdescribetheRESTAPI
andtheavailableoperations.
Theresourcelistingfilereferredtobythename“api-docs”istherootdocumentfor
describingtheAPI.ItcontainsgeneralinformationabouttheAPIsuchastheAPIversion,
title,description,andlicense.Asthenamesuggests,theresourcelistingfilealsocontains
alloftheAPIresourcesavailableintheapplication.Listing6-1showsasampleresource
listingfileforahypotheticalRESTAPI.NoticethatSwaggerusesJSONasitsdescription
language.FromtheapisarrayinListing6-1,youcanseethattheresourcelistingfilehas
twoAPIresourcesdeclared,namely,productsandorders.TheURIs
/default/productsand/default/ordersallowyoutoaccesstheresource’s
APIdeclarationfile.Swaggerallowsgroupingofitsresources;bydefault,allresources
aregroupedunderthedefaultgroupand,hence,the“/default”intheURI.Theinfo
objectcontainsthecontactandlicensinginformationassociatedwiththeAPI.
Listing6-1.Sampleresourcefile
{
"apiVersion":"1.0",
"swaggerVersion":"1.2"
"apis":[
{
"description":"EndpointforProduct
management",
"path":"/default/products"
},
{
"description":"EndpointforOrder
management",
"path":"/default/orders"
}
],
"authorizations":{},
"info":{
"contact":"contact@test.com",
"description":"Apiforanecommerceapplication",
"license":"Apache2.0",
"licenseUrl":"http://www.apache.org/licenses/LICENSE-
2.0.html",
"termsOfServiceUrl":"Apitermsofservice",
"title":"ECommerceApp"
}
}
AnAPIdeclarationfiledescribesaresourcealongwiththeAPIoperationsand
request/responserepresentations.Listing6-2showsasampleAPIdeclarationfileforthe
productsresourceandwillbeservedattheURI/default/products.The
basePathfieldprovidestherootURIservingtheAPI.TheresourcePathspecifies
theresourcepathrelativetothebasePath.Inthiscase,wearespecifyingthatthe
product’sRESTAPIisaccessibleathttp://server:port/products.Theapis
fieldcontainsAPIobjectsthatdescribeanAPIoperation.Listing6-2describesoneAPI
operationcalledcreateProductanditsassociatedHTTPmethod,themediatypeof
themessagesconsumed/producedandAPIresponses.Themodelsfieldcontainsany
modelobjectsassociatedwiththeresource.Listing6-2showsaproductmodelobject
associatedwithaproductresource.
Listing6-2.SampleproductsAPIdeclarationfileat/default/products
{
"apiVersion":"1.0",
"swaggerVersion":"1.2"
"basePath":"/",
"resourcePath":"/products",
"apis":[
{
"description":"createProduct",
"operations":[
{
"method":"POST",
"produces":["application/json"],
"consumes":["application/json"],
"parameters":[{"allowMultiple":false}
],
"responseMessages":[
{
"code":200,
"message":null,
"responseModel":"object"
}
]
}
],
"path":"/products"
}
],
"models":{
"Product":{
"description":"",
"id":"Product",
"properties":{}
}
}
}
NoteInourhypotheticalexample,SwaggerexpectstheAPIdeclarationfileforthe
productsresourcetoresideatthe“/default/products”URI.Thisshouldnotbe
confusedwiththeactualRESTAPIlocationforaccessingtheproductsresource.Inthis
example,thedeclarationfileindicatesthattheproductsresourceisaccessibleat
http://server:port/productsURI.
IntegratingSwagger
IntegratingSwaggerinvolvescreatingthe“api-docs”resourcelistingfileandasetof
APIdeclarationfilesdescribingAPI’sresources.Insteadofhandcodingthesefiles,there
areseveralSwaggerandcommunity-ownedprojectsthatintegratewithexistingsource
codeandautomaticallygeneratethesefiles.Swagger-springmvcisonesuchframework
thatsimplifiesSwaggerintegrationwithSpringMVCbasedprojects.Webeginthe
SwaggerintegrationwithQuickPollapplicationbyaddingtheswagger-springmvc
MavendependencyshowninListing6-3inthepom.xmlfile.
NoteWecontinueourtraditionofbuildingontheworkwedidontheQuickPoll
applicationinpreviouschapters.Youcanalsousethestarterprojectavailableat
Chapter6\starterfolderofthedownloadedsourcecode.Thecompletedsolutionis
availableundertheChapter6\finalfolder.
Listing6-3.Swagger-springmvcdependency
<dependency>
<groupId>com.mangofactory</groupId>
<artifactId>swagger-springmvc</artifactId>
<version>1.0.2</version>
</dependency>
Thenextstepistoenableswagger-springmvc.Thisisdonebyaddingthe
@EnableSwaggerannotationtotheQuickPollApplicationclassasshownin
Listing6-4.
Listing6-4.Enableswagger-springmvc
packagecom.apress;
importcom.mangofactory.swagger.plugin.EnableSwagger;
@SpringBootApplication
@EnableSwagger
publicclassQuickPollApplication{
publicstaticvoidmain(String[]args){
SpringApplication.run(QuickPollApplication.class,
args);
}
}
Withthisminimalconfigurationinplace,runtheQuickPollapplicationandlaunchthe
URIhttp://localhost:8080/api-docs.Youshouldseetheresourcelistingfile
asshowninFigure6-1.NavigatetotheURIhttp://localhost:8080/apidocs/default/poll-controllertoviewthepollresource’sAPIdeclaration.
Figure6-1.QuickPollresourcelistingfile
SwaggerUI
TheresourcelistingandAPIdeclarationfilesactasavaluableresourceforunderstanding
aRESTAPI.SwaggerUIisasubprojectofSwaggerthattakesthesefilesand
automaticallygeneratespleasant,intuitiveinterfaceforinteractingwithAPI.Usingthis
interface,bothtechnicalandnontechnicalfolkscantestRESTservicesbysubmitting
requestsandseehowthoseservicesrespond.TheSwaggerUIisbuiltusingHTML,CSS,
andJavaScript,anddoesn’thaveanyotherexternaldependencies.Itcanbehostedinany
serverenvironmentorcanevenrunfromyourlocalmachine.
TointegrateSwaggerUIinourQuickPollapplication,wedownloadthestableversion
ofSwaggerUIfromtheproject’sGitHubsiteat:https://github.com/swaggerapi/swagger-ui.Inthisbookweareusingversion2.0.24,availableat
https://github.com/swagger-api/swagger-ui/tree/v2.0.24.Clone
ordownloadtherepoontoyourlocalmachine.Movethecontentsofthedistfolderthat
isinthecloned/downloadedcodetoanewlycreatedswagger-uifolderunderthe
quick-pollproject’ssrc\main\resources\staticfolder.TheQuickPoll
project,inthechapter6\finalfolder,hasthefullyconfiguredswagger-uiassets
availableforyoutousewithouthavingtodownloadthemfromGitHub.Outofthebox,
SpringBootwillautomaticallyserveanystaticcontentresidingunderthestaticfolder.
Hence,noconfigurationchangesarerequiredontheSpringBootend.However,by
default,theSwaggerUIcomeswithahardcodedreferencetoaSwaggerPetstoreservice.
TomaketheSwaggerUIuseourQuickPollservice,opentheindex.htmlfileand
modifytheurlvariabletopointtohttp://localhost:8080/api-docs,as
showninListing6-5.
Listing6-5.Modifiedindex.htmlfile
$(function(){
window.swaggerUi=newSwaggerUi({
url:“http://localhost:8080/api-docs“,
dom_id:“swagger-ui-container”,
//coderemovedforbrevity
}
Withthesemodifications,wearereadytolaunchSwaggerUI.Runthequick-poll
applicationandnavigatetotheURLhttp://localhost:8080/swaggerui/index.html.YoushouldseeQuickPollSwaggerUI,asshowninFigure6-2.
Figure6-2.QuickPollSwaggerUI
UsingtheUI,youshouldbeabletoperformoperationssuchascreatingpollsandread
allpolls.
CustomizingSwagger
Intheprevioussections,youhaveseenthatwithminimalconfigurationwewereableto
createinteractivedocumentationusingSwagger.Additionally,thisdocumentationwould
automaticallyupdateitselfwhenwemakechangestoourservices.However,youwill
noticethatoutofthebox,thetitle,andtheAPIdescriptionsarenotveryintuitive.Also,
theURLssuchas“TermsofService,”“ContacttheDeveloper,”andsoondon’twork.As
youexploretheUI,theResponseclassessuchasPollandVotearenotvisibleinthe
SwaggerUIandtheuserhastoendupguessingwhatthereturntypefortheoperationsare
goingtobe.
SwaggerSpringmvcprovidesaconvenientbuildernamed
SwaggerSpringMvcPluginforcustomizingandconfiguringSwagger.The
SwaggerSpringMvcPluginprovidesconvenientmethodsandsensibledefaults,but
itselfusestheSpringSwaggerConfigclasstoperformtheactualconfiguration.We
beginourSwaggercustomizationbycreatingaSwaggerConfigclassunderthe
com.apresspackageinourQuickPollapplication.Populatethenewlycreatedclass
withthecontentsofListing6-6.
Listing6-6.CustomSwaggerimplementation
packagecom.apress;
importjavax.inject.Inject;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
import
com.mangofactory.swagger.configuration.SpringSwaggerConfig;
importcom.mangofactory.swagger.models.dto.ApiInfo;
import
com.mangofactory.swagger.models.dto.builder.ApiInfoBuilder;
importcom.mangofactory.swagger.plugin.EnableSwagger;
import
com.mangofactory.swagger.plugin.SwaggerSpringMvcPlugin;
@Configuration
@EnableSwagger
publicclassSwaggerConfig{
@Inject
privateSpringSwaggerConfigspringSwaggerConfig;
@Bean
publicSwaggerSpringMvcPluginconfigureSwagger(){
SwaggerSpringMvcPluginswaggerSpringMvcPlugin
=newSwaggerSpringMvcPlugin(this.springSwaggerConfig);
ApiInfoapiInfo=newApiInfoBuilder()
.title("QuickPollRESTAPI")
.description("QuickPollApi
forcreatingand
managingpolls")
.termsOfServiceUrl("http://
of-service")
.contact("info@example.com")
.license("MITLicense")
.licenseUrl("http://opensource.org/
.build();
swaggerSpringMvcPlugin.apiInfo(apiInfo)
.apiVersion("1.0");
returnswaggerSpringMvcPlugin;
}
}
TheSwaggerConfigclassisannotatedwith@Configurationindicatingthatit
containsoneormoreSpringBeanconfigurations.Wealsomovedthe
@EnableSwaggerannotationfromtheQuickPollApplicationclassto
SwaggerConfiginordertocentralizeallSwagger-relatedconfigurationinone
location.BecausetheSwaggerSpringMvcPluginreliesontheframework’s
SpringSwaggerConfig,weinjectaninstanceofSpringSwaggerConfigfor
lateruse.TheSpringSwaggerConfigisaSpring-managedbeanthatgetsinstantiated
duringSpring’scomponentscanninginJARfiles.
TheconfigureSwaggermethodcontainsthemeatofourSwaggerconfiguration.
Themethodisannotatedwith@Bean,indicatingtoSpringthatthereturnvalueisa
SpringbeanandneedstoberegisteredwithinaBeanFactory.TheSwaggerSpringmvc
frameworkpicksupthisbeanandcustomizesSwagger.Webeginthemethod
implementationbycreatinganinstanceofSwaggerSpringMvcPlugin.Then,usingthe
ApiInfoBuilder,wecreateanApiInfoobjectcontainingthetitle,description,
contact,andlicenseinformationassociatedwiththeQuickPollapplication.Finally,we
passthecreatedapiInfoandapiVersioninformationtothe
SwaggerSpringMvcPlugininstanceandreturnit.
NoteItispossibletohavemultiplemethodsproducing
SwaggerSpringMvcPluginbeans.EachSwaggerSpringMvcPluginwould
resultinaseparateresourcelisting.Thisisusefulinsituationsinwhichyouhavethesame
SpringMVCapplicationthatservesmorethanoneAPIormultipleversionsofthesame
API.
WiththenewSwaggerConfigclassadded,runtheQuickPollapplicationand
navigatetohttp://localhost:8080/swagger-ui/index.html.Youwillsee
thechangesreflectedinourUIasshowninFigure6-3.
Figure6-3.UpdatedQuickPollSwaggerUI
FromFigure6-3,youwillnoticethatinadditiontothethreeQuickPollREST
endpoints,thereisaSpringBoot’s“/error”endpoint.Becausethisendpointreally
doesn’tserveanypurpose,let’shideitfromourAPIdocumentation.Toaccomplishthis,
wewillusetheSwaggerSpringMvcPluginclass’shandyincludePattern
method.TheincludePatternmethodallowsustospecifywhichrequestmappings
shouldbeincludedintheresourcelisting.Listing6-7showstheupdatedportionofthe
SwaggerConfig’sconfigureSwaggermethod.TheincludePatternmethod
bydefaulttakesregularexpressionsand,inourcase,weexplicitlylistedallthree
endpointswewouldliketoinclude.
Listing6-7.configureSwaggermethodwithincludePatterns
swaggerSpringMvcPlugin
.apiInfo(apiInfo)
.apiVersion("1.0")
.includePatterns("/polls/*.*","/votes/*.*",
"/computeresult/*.*");
ReruntheQuickPollapplicationandyouwillseetheSpringBoot’serrorcontrollerno
longerappearinginthedocumentation.
ConfiguringControllers
SwaggerCoreprovidesasetofannotationsthatmakesiteasytocustomizecontroller
documentation.InthissectionwewillcustomizethePollController,butthesame
principlesapplytootherRESTcontrollers.ThedownloadedcodeinChapter6\final
hasthecompletecustomizationofallcontrollers.
WebeginbyannotatingthePollContollerwiththe@Apiannotationasshownin
Listing6-8.The@ApiannotationmarksaclassasaSwaggerresource.Swaggerscans
classesannotatedwith@Apitoreadmetadatarequiredforgeneratingresourcelistingand
APIdeclarationfiles.Hereweareindicatingthatthedocumentationassociatedwiththe
PollControllerwillbehostedat/polls.Rememberthatoutofthebox,Swagger
usedtheClassnameandgeneratedURIpoll-controller
(http://localhost:8080/swagger-ui/index.html#!/pollcontroller)tohostthedocumentation.Withourchange,thePollControllerSwagger
documentationisaccessibleathttp://localhost:8080/swaggerui/index.html#!/polls.Usingthe@Apiannotation,wehavealsoprovidedthe
descriptionassociatedwithourPollAPI.
Listing6-8.@Apiannotationinaction
importcom.wordnik.swagger.annotations.Api;
@RestController
@Api(value="polls",description="PollAPI")
publicclassPollController{
//Implementationremovedforbrevity
}
RuntheQuickPollapplicationand,onnavigatingtoSwaggerUIat
http://localhost:8080/swagger-ui/index.html,youwillnoticethe
updatedURIpathanddescriptionasshowninFigure6-4.
Figure6-4.Updatedpollendpoint
NowwewillmoveontotheAPIoperationcustomizationusingthe
@ApiOperationannotation.Thisannotationallowsustocustomizetheoperation
informationsuchasname,description,andresponse.Listing6-9showsthe
@ApiOperationappliedtothecreatePoll,getPoll,andgetAllPolls
methods.Weusethevalueattributetoprovideabriefdescriptionoftheoperation.
Swaggerrecommendslimitingthisfieldto120characters.Thenotesfieldcanbeusedto
providemoredescriptiveinformationabouttheoperation.
Listing6-9.@ApiOperationannotatedmethods
importcom.wordnik.swagger.annotations.ApiOperation;
@RequestMapping(value="/polls",method=RequestMethod.POST)
@ApiOperation(value="CreatesanewPoll",notes="Thenewly
createdpollIdwillbesentinthelocationresponse
header",response=Void.class)
publicResponseEntity<Void>createPoll(@Valid@RequestBody
Pollpoll){
.......
}
@RequestMapping(value="/polls/{pollId}",
method=RequestMethod.GET)
@ApiOperation(value="RetrievesaPollassociatedwiththe
pollId",response=Poll.class)
publicResponseEntity<?>getPoll(@PathVariableLongpollId)
{
.........
}
@RequestMapping(value="/polls",method=RequestMethod.GET)
@ApiOperation(value="Retrievesallthepolls",
response=Poll.class,responseContainer="List")
publicResponseEntity<Iterable<Poll>>getAllPolls(){
..........
}
ThecreatePollmethodonsuccessfulcompletionsendsanemptybodyanda
statuscode201totheclient.However,becausewearereturningaResponseEntity,
Swaggerisnotabletofigureouttherightresponsemodel.Wefixthisusing
ApiOperation’sresponseattributeandsettingittoaVoid.class.Wealso
changedthemethodreturntypefromResponseEntity<?>to
ResponseEntity<Void>tomakeourintentmoreclear.
ThegetPollmethodreturnsapollassociatedwiththepassedinpollId
parameter.Hence,wesettheApiOperation’sresponseattributetoPoll.class.
BecausethegetAllPollsmethodreturnsacollectionofPollinstances,wehaveused
theresponseContainerattributeandsetitsvaluetoList.
Withtheseannotationsadded,rerunandlaunchQuickPollapplication’sSwaggerUIto
verifythatthedescriptions,responsemodel,andnotessectionsarechanged.Forexample,
clickthe“polls”linknextto“PollAPI”toexpandthePollController’soperations.Then
clickthe“/polls/{pollId}”linknexttoGETtoseetheresponsemodelassociatedwith
getPollmethod.Figure6-5showsthisupdatedresponsemodel.
Figure6-5.GetPollmethod’supdatedmodel
The@ApiOperationweusedearlierallowsustospecifyanoperation’sdefault
returntype.Aswehaveseenthroughoutthebook,awell-definedAPIusesadditional
statuscodesandSwaggerprovidesthe@ApiResponseannotationtoconfigurethe
codesandassociatedresponsebody.Listing6-10showsthecreatePollmethodannotated
with@ApiResponseforstatuscode201and500.Swaggerrequiresustoplaceallthe
@ApiResponseannotationsinsideawrapper@ApiResponseannotation.Withthe
statuscode201,wehaveaddednotesindicatinghowtoretrievethenewlycreatedpollID.
Withthestatuscode500,wehaveindicatedthattheresponsebodywillcontainan
ErrorDetailinstance.
Listing6-10.@ApiResponseannotations
importcom.wordnik.swagger.annotations.ApiResponse;
importcom.wordnik.swagger.annotations.ApiResponses;
@RequestMapping(value="/polls",method=RequestMethod.POST)
@ApiOperation(value="CreatesanewPoll",notes="The
newlycreatedpollIdwillbesentinthelocationresponse
header",response=Void.class)
@ApiResponses(value={@ApiResponse(code=201,
message="PollCreatedSuccessfully",response=Void.class),
@ApiResponse(code=500,message="Error
creatingPoll",response=ErrorDetail.class)})
publicResponseEntity<Void>createPoll(@Valid
@RequestBodyPollpoll){
//Contentremovedforbrevity
}
RuntheQuickPollapplicationandnavigatetoSwaggerUI.Clickthe“polls”linknext
to“PollAPI”toexpandthePollController’soperations.Thenclickthe“/polls”linknext
toPOSTtoseetheupdatednotesandErrorDetailmodelschema.Figure6-6shows
theexpectedoutput.
Figure6-6.Modifiedresponsemessages
AquickglanceatFigure6-6showsthatwehavemoreresponsemessagesthan
configured.ThisisbecauseSwaggeroutoftheboxaddsasetofdefaultresponse
messagesforeachHTTPmethod.Thisbehaviorcanbedisabledusingthe
useDefaultResponseMessagesmethodintheSwaggerSpringMvcPlugin
classasshowninListing6-11.
Listing6-11.Ignoredefaultreponsemessages
publicclassSwaggerConfig{
@Inject
privateSpringSwaggerConfigspringSwaggerConfig;
@Bean
publicSwaggerSpringMvcPluginconfigureSwagger(){
//Contentremoved
swaggerSpringMvcPlugin.useDefaultResponseMessages(false);
returnswaggerSpringMvcPlugin;
}
}
RuntheQuickPollapplicationandrepeatthesestepstoviewtheresponsemessages
associatedwiththePOSToperationon“/polls”URI.AsshowninFigure6-7,thedefault
responsemessagesarenolongerdisplayed.
Figure6-7.Updatedresponsemessages
Inadditiontotheconfigurationoptionswelookedat,Swaggerprovidesthefollowing
annotationstoconfiguremodelobjects:
@ApiModel—Annotationthatallowschangingthenameofthe
modelorprovidingadescriptiontotheassociatedmodel
@ApiModelProperty—Annotationthatcanbeusedtoprovide
propertydescription,listofallowedvaluesandtoindicateifitis
requiredornot
ConfiguringUI
BecausewearebundlingtheSwaggerUIHTML/CSS/JSassetswiththeapplication,any
neededlookandfeelchangescanbeperformedbydirectlyeditingthosefiles.Inthis
section,wewillchangethenameintheheaderfromSwaggertoQuickPoll.Wewillalso
changetheURLtowhichtheheadertextpointsandremovethe“Toolbox”and“Settings”
iconnexttothetext.Todothis,opentheindex.htmlfileunder
src\main\resources\static\swagger-uiandreplacetheswagger-uiwrapdivcontentswiththecontentsseeninListing6-12.
Listing6-12.Modifiedswagger-ui-wrapcontents
<aid="logo"href="http://localhost:8080">QuickPoll</a>
<formid='api_selector'>
<divclass='input'><input
placeholder="http://example.com/api"id="input_baseUrl"
name="baseUrl"type="text"/></div>
<divclass='input'><inputplaceholder="api_key"
id="input_apiKey"name="apiKey"type="text"/></div>
<divclass='input'><aid="explore"href="#">Explore</a>
</div>
</form>
RestarttheQuickPollapplicationandnavigatetoSwaggerUIinyourbrowser.TheUI
shouldreflectourchangesandshouldresembleFigure6-8.
Figure6-8.UpdatedSwaggerUI
Summary
DocumentationplaysanimportantroleinunderstandingandconsumingaRESTAPI.In
thischapter,wereviewedthebasicsofSwaggerandintegrateditwithaQuickPoll
applicationtogenerateinteractivedocumentation.WealsolookedatcustomizingSwagger
tomeetourapplication-specificneeds.
Inthenextchapter,wewilllookattechniquesforversioningRESTAPIand
implementingpagingandsortingcapabilities.
CHAPTER7
Versioning,Paging,andSorting
Inthischapterwewilldiscuss:
StrategiesforversioningRESTservices
Addingpaginationcapabilities
Addingsortingcapabilities
Weallarefamiliarwiththefamousproverb“theonlythingconstantinlifeischange.”
Thisappliestosoftwaredevelopment.InthischapterwewilllookatversioningourAPI
asawaytohandlesuchchanges.Additionally,dealingwithlargedatasetscanbe
problematicespeciallywhenmobileclientsareinvolved.Largedatasetscanalsoresultin
serveroverloadandperformanceissues.Tohandlethis,wewillemploypagingand
sortingtechniquesandsenddatainmanageablechunks.
Versioning
Asuserrequirementsandtechnologychange,nomatterhowplannedourdesign,wewill
endupchangingourcode.ThiswillinvolvemakingchangestoRESTresourcesby
adding,updating,andsometimesremovingattributes.AlthoughthecruxoftheAPI—
read,create,update,andremoveoneormoreresources—remainsthesame,thiscould
resultinsuchdrasticchangestotherepresentationthatitmaybreakanyexisting
consumers.Similarly,changestofunctionalitysuchassecuringourservicesandrequiring
authenticationorauthorizationcanbreakexistingconsumers.Suchmajorchanges
typicallycallfornewversionsoftheAPI.
Inthischapter,wewillbeaddingpagingandsortingfunctionalitytoourQuickPoll
API.Asyouwillseeinlatersections,thischangewillresultinchangestothe
representationsreturnedforsomeoftheGETHTTPmethods.Beforeweversionour
QuickPollAPItohandlepagingandsorting,let’slookatsomeapproachesforversioning.
VersioningApproaches
TherearefourpopularapproachestoversioningaRESTAPI:
URIversioning
URIparameterversioning
Acceptheaderversioning
Customheaderversioning
Noneoftheseapproachesaresilverbulletsandeachhasitsfairshareofadvantages
anddisadvantages.InthissectionwewilllookattheseapproachesalongwithsomerealworldpublicAPIsthatusethem.
URIVersioning
Inthisapproach,versioninformationbecomespartoftheURI.Forexample,
http://api.example.org/v1/usersand
http://api.example.org/v2/usersrepresenttwodifferentversionsofan
applicationAPI.Hereweusevnotationtodenoteversioningandthenumbers1and2
followingthevindicatethefirstandsecondAPIversions.
URIversioninghasbeenoneofthemostcommonlyusedapproachesandisusedby
majorpublicAPIssuchasTwitter,LinkedIn,Yahoo,andSalesForce.Herearesome
examples:
LinkedIn:https://api.linkedin.com/v1/people/~
Yahoo:
https://social.yahooapis.com/v1/user/12345/profile
SalesForce:
http://na1.salesforce.com/services/data/v26.0
Twitter:
https://api.twitter.com/1.1/statuses/user_timeline.json
Twilio:https://api.twilio.com/2010-0401/Accounts/{AccountSid}/Calls
Asyoucansee,LinkedIn,Yahoo,andSalesForceusethevnotation.Inadditiontoa
majorversion,SalesForceusesaminorversionaspartofitsURIversion.Twilio,by
contrast,takesauniqueapproachandusesatimestampintheURItodifferentiateits
versions.
MakingaversionpartoftheURIisveryappealingastheversioninformationisright
intheURI.ItalsosimplifiesAPIdevelopmentandtesting.Folkscaneasilybrowseand
usedifferentversionsofRESTservicesviaaWebbrowser.Onthecontrary,thismight
makeclient’slifedifficult.Forexample,consideraclientstoringreferencestouser
resourcesinitsdatabase.Onswitchingtoanewversion,thesereferencesgetoutdatedand
theclienthastodoamassdatabaseupdatetoupgradereferencestonewversion.
URIParameterVersioning
ThisissimilartotheURIversioningthatwejustlookedatexceptthattheversion
informationisspecifiedasaURIrequestparameter.Forexample,theURI
http://api.example.org/users?v=2usestheversionparametervtorepresent
thesecondversionoftheAPI.Theversionparameteristypicallyoptionalandadefault
versionoftheAPIwillcontinueworkingforrequestswithoutversionparameter.Most
often,thedefaultversionisthelatestversionoftheAPI.
Althoughasnotpopularasotherversioningstrategies,afewmajorpublicAPIssuch
asNetflixhaveusedthisstrategy.TheURIparameterversioningsharesthesame
disadvantagesofURIversioning.Anotherdisadvantageisthatsomeproxiesdon’tcache
resourceswithaURIparameter,resultinginadditionalnetworktraffic.
AcceptHeaderVersioning
ThisversioningapproachusestheAcceptheadertocommunicateversioninformation.
Becausetheheadercontainsversioninformation,therewillbeonlyoneURIformultiple
versionsofAPI.
Uptothispoint,wehaveusedstandardmediatypessuchas
“application/json”aspartoftheAcceptheadertoindicatethetypeofcontent
theclientexpects.Topassadditionalversioninformation,weneedacustommediatype.
Thefollowingconventionispopularwhencreatingacustommediatype:
vnd.product_name.version+suffix
Thevndisthestartingpointofthecustommediatypeandindicatesvendor.The
productorproducernameisthenameoftheproductanddistinguishesthismediatype
fromothercustomproductmediatypes.Theversionpartisrepresentedusingstringssuch
asv1orv2orv3.Finally,thesuffixisusedtospecifythestructureofthemediatype.
Forexample,the+jsonsuffixindicatesastructurethatfollowstheguidelinesestablished
formediatype“application/json”.RFC6389
(https://tools.ietf.org/html/rfc6839)givesafulllistofstandardized
prefixessuchas+xml,+json,and+zip.Usingthisapproach,aclient,forexample,can
sendanapplication/vnd.quickpoll.v2+jsonacceptheadertorequestthe
secondversionoftheAPI.
TheAcceptheaderversioningapproachisbecomingmoreandmorepopularasit
allowsfine-grainedversioningofindividualresourceswithoutimpactingtheentireAPI.
ThisapproachcanmakebrowsertestingharderaswehavetocarefullycrafttheAccept
header.GitHubisapopularpublicAPIthatusesthisAcceptheaderstrategy.For
requeststhatdon’tcontainanyAcceptheader,GitHubusesthelatestversionoftheAPI
tofulfilltherequest.
CustomHeaderVersioning
ThecustomheaderversioningapproachissimilartotheAcceptheaderversioning
approachexceptthat,insteadoftheAcceptheader,acustomheaderisused.Microsoft
Azuretakesthisapproachandusesthecustomheaderx-ms-version.Forexample,to
getthelatestversionofAzureatthetimeofwritingthisbook,yourrequestneedsto
includeacustomheader:
x-ms-version:2014-02-14
ThisapproachsharesthesameprosandconsasthatoftheAcceptheaderapproach.
BecausetheHTTPspecificationprovidesastandardwayofaccomplishingthisviathe
Acceptheader,thecustomheaderapproachhasn’tbeenwidelyadopted.
DeprecatinganAPI
AsyoureleasenewversionsofanAPI,maintainingolderversionsbecomescumbersome
andcanresultinmaintenancenightmares.Thenumberofversionstomaintainandtheir
longevitydependsontheAPIuserbase,butitisstronglyrecommendedtomaintainat
leastoneolderversion.
APIversionsthatwillnolongerbemaintainedneedtobedeprecatedandeventually
retired.Itisimportanttorememberthatdeprecationisintendedtocommunicatethatthe
APIisstillavailablebutwillceasetoexistinfuture.APIusersshouldbegivenplentyof
noticeaboutdeprecationsothattheycanmigratetonewerversions.
QuickPollVersioning
Inthisbook,wewillbeusingtheURIversioningapproachtoversiontheQuickPoll
RESTAPI.
ImplementingandmaintainingdifferentversionsofanAPIcanbedifficult,asit
generallycomplicatescode.Wewanttomakesurethatchangesinoneversionofcode
don’timpactotherversionsofthecode.Toimprovemaintainability,wewanttomakesure
thatweavoidcodeduplicationasmuchaspossible.Herearetwoapproachesfor
organizingcodetosupportmultipleAPIversions:
Completecodereplication—Inthisapproach,youreplicatetheentire
codebaseandmaintainparallelcodepathsforeachversion.Popular
APIbuilderApigilitytakesthisapproachandclonestheentirecode
baseforeachnewversion.Thisapproachmakesiteasytomakecode
changesthatwouldn’timpactotherversions.Italsomakesiteasyto
switchbackenddatastores.Thiswouldalsoalloweachversionto
becomeaseparatedeployableartifact.Althoughthisapproach
providesalotofflexibility,wewillbeduplicatingtheentirecode
base.
Versionspecificcodereplication—Inthisapproach,weonlyreplicate
thecodethatisspecifictoeachversion.Eachversioncanhaveitsown
setofcontrollersandrequest/responseDTOobjectsbutwillreuse
mostofthecommonserviceandbackendlayers.Forsmaller
applications,thisapproachcanworkwellasversion-specificcodecan
simplybeseparatedintodifferentpackages.Caremustbetakenwhen
makingchangestothereusedcode,asitmighthaveimpacton
multipleversions.
SpringMVCmakesiteasytoversionaQuickPollapplicationusingtheURI
versioningapproach.Consideringthatversioningplaysacrucialroleinmanaging
changes,itisimportantthatweversionasearlyaspossibleinthedevelopmentcycle.
Hence,wewillassignaversionv1toalloftheQuickPollservicesthatwehave
developedsofar.Tosupportmultipleversions,wewillfollowthesecondapproachand
createaseparatesetofcontrollers.
NoteInthischapterwewillcontinuebuildingontheworkthatwedidonthe
QuickPollapplicationinpreviouschapters.Alternatively,astarterprojectinsidethe
Chapter7\starterfolderofthedownloadedsourcecodeisavailableforyoutouse.
ThecompletedsolutionisavailableundertheChapter7\finalfolder.Pleasereferto
thissolutionforcompletelistingscontaininggetters/settersandadditionalimports.The
downloadedChapter7folderalsocontainsanexportedPostmancollectioncontaining
RESTAPIrequestsassociatedwiththischapter.
Webegintheversioningprocessbycreatingtwopackages
com.apress.v1.controllerandcom.apress.v2.controller.Moveallof
thecontrollersfromthecom.apress.controllerpackagetothe
com.apress.v1.controller.Toeachcontrollerinthenewv1package,adda
classlevel@RequestMapping(“/v1/”)annotation.Becausewewillhavemultiple
versionsofcontrollers,weneedtogiveuniquecomponentnamestoindividualcontrollers.
Wewillfollowtheconventionofappendingversionnumbertotheunqualifiedclassname
toderiveourcomponentname.Usingthisconvention,thev1PollControllerwill
haveacomponentnamepollControllerV1.
Listing7-1showstheportionofthePollControllerclasswiththese
modifications.Noticethatthecomponentnameisprovidedasavaluetothe
@RestControllerannotation.Similarly,assignthecomponentname
voteControllerV1tothev1VoteControllerand
computeResultControllerV1tothev1ComputeResultController.
Listing7-1.Versiononeofthepollcontroller
packagecom.apress.v1.controller;
import
org.springframework.web.bind.annotation.RequestMapping;
@RestController("pollControllerV1")
@RequestMapping("/v1/")
@Api(value="polls",description="PollAPI")
publicclassPollController{
}
NoteEventhoughthebehaviorandcodeofVoteControllerand
ComputeResultControlerdon’tchangeacrossversions,wearecopyingthecodeto
keepthingssimple.Inreal-worldscenarios,refactorcodeintoreusablemodulesoruse
inheritancetoavoidcodeduplication.
Withtheclasslevel@RequestMappingannotationinplace,alloftheURIsinthe
v1PollControllerbecomerelativeto“/v1/”.RestarttheQuickPollapplication
and,usingPostman,verifythatyoucancreateanewPollatthenew
http://localhost:8080/v1/pollsendpoint.
TocreatethesecondversionoftheAPI,copyallofthecontrollersfromthev1
packagetothev2package.Changetheclass-levelRequestMappingvaluefrom
“/v1/”to“/v2/”andthecomponentnamesuffixfrom“V1”to“V2”.Listing7-2
showsthemodifiedportionsoftheV2versionofthePollController.Becausethe
v2PollControllerisacopyofthev1PollController,wehaveomittedthe
PollControllerclassimplementationfromListing7-2.
Listing7-2.Versiontwoofthepollcontroller
@RestController("pollControllerV2")
@RequestMapping("/v2/")
@Api(value="polls",description="PollAPI")
publicclassPollController{
//Codecopiedfromthev1PollController
}
Onceyouhavecompletedmodificationsforthethreecontrollers,restarttheQuickPoll
applicationand,usingPostman,verifythatyoucancreateanewpollusingthe
http://localhost:8080/v2/pollsendpoint.Similarly,verifythatyoucan
accesstheVoteControllerandComputeResultControllerendpointsby
accessingthehttp://localhost:8080/v2/votesand
http://localhost:8080/v2/computeresultendpoints.
Swaggerconfig
TheversioningchangesthatwemaderequirechangestoourSwaggerconfigurationso
thatwecanusetheUItotestandinteractwithbothRESTAPIversions.Listing7-3shows
therefactoredcom.apress.SwaggerConfigclass.Asdiscussedintheprevious
chapter,a
com.mangofactory.swagger.plugin.SwaggerSpringMvcPlugininstance
representsaSwaggergroup.Hence,therefactoredSwaggerConfigclasscontainstwo
methods,eachreturningaSwaggerSpringMvcPlugininstancerepresentinganAPI
group.Also,noticethatwehaveextractedAPIinformationtoitsownmethodandusedit
toconfigurebothinstancesofSwaggerSpringMvcPlugin.
Listing7-3.RefactoredSwaggerConfigclass
packagecom.apress;
importjavax.inject.Inject;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
import
com.mangofactory.swagger.configuration.SpringSwaggerConfig;
importcom.mangofactory.swagger.models.dto.ApiInfo;
import
com.mangofactory.swagger.models.dto.builder.ApiInfoBuilder;
importcom.mangofactory.swagger.plugin.EnableSwagger;
import
com.mangofactory.swagger.plugin.SwaggerSpringMvcPlugin;
@Configuration
@EnableSwagger
publicclassSwaggerConfig{
@Inject
privateSpringSwaggerConfigspringSwaggerConfig;
privateApiInfogetApiInfo(){
ApiInfoapiInfo=newApiInfoBuilder()
.title("QuickPollRESTAPI")
.description("QuickPollApi
forcreatingandmanagingpolls")
.termsOfServiceUrl("http://example.com/terms-of-service")
.contact("info@example.com")
.license("MITLicense")
.licenseUrl("http://opensource.org/licenses/MIT")
.build();
returnapiInfo;
}
@Bean
publicSwaggerSpringMvcPluginv1APIConfiguration(){
SwaggerSpringMvcPluginswaggerSpringMvcPlugin
=newSwaggerSpringMvcPlugin(this.springSwaggerConfig);
swaggerSpringMvcPlugin
.apiInfo(getApiInfo()).apiVersion("1.0")
.includePatterns("/v1/*.*").swaggerGroup
swaggerSpringMvcPlugin.useDefaultResponseMessages(fa
returnswaggerSpringMvcPlugin;
}
@Bean
publicSwaggerSpringMvcPluginv2APIConfiguration(){
SwaggerSpringMvcPlugin
swaggerSpringMvcPlugin=new
SwaggerSpringMvcPlugin(this.springSwaggerConfig);
swaggerSpringMvcPlugin
.apiInfo(getApiInfo()).apiVersion("2.0")
.includePatterns("/v2/*.*").swaggerGroup("v2")
swaggerSpringMvcPlugin.useDefaultResponseMessages(fa
returnswaggerSpringMvcPlugin;
}
}
WiththisnewlyrefactoredSwaggerConfig,restarttheQuickPollapplicationand
launchSwaggerUIinaWebbrowserathttp://localhost:8080/swaggerui/index.html.AftertheUIhaslaunched,appendtherequestparameter?
group=v2tothehttp://localhost:8080/api-docs/URIintheSwagger
UI’sinputboxandhitExplore.Youshouldseeandinteractwiththev2versionoftheAPI
asshowninFigure7-1.
Figure7-1.SwaggerUIforQuickPoll2.0version
ThisconcludestheconfigurationneededtoversionourQuickPollapplicationandsets
thestageforaddingpaginationandsortingsupportinthefinaltwosectionsofthischapter.
Pagination
RESTAPIsareconsumedbyavarietyofclientsrangingfromdesktopapplicationsto
Webtomobiledevices.Hence,whiledesigningaRESTAPIcapableofreturningvast
datasets,itisimportanttolimittheamountofdatareturnedforbandwidthand
performancereasons.Thebandwidthconcernsbecomemoreimportantinthecaseof
mobileclientsconsumingtheAPI.Limitingthedatacanvastlyimprovetheserver’s
abilitytoretrievedatafasterfromadatastoreandtheclient’sabilitytoprocessthedata
andrendertheUI.Bysplittingthedataintodiscretepagesorpagingdata,RESTservices
allowclientstoscrollthroughandaccesstheentiredatasetinmanageablechunks.
BeforewestartingimplementingpaginationinourQuickPollapplication,let’slookat
fourdifferentpaginationstyles:pagenumberpagination,limitoffsetpagination,cursorbasedpagination,andtime-basedpagination.
PageNumberPagination
Inthispaginationstyle,theclientsspecifyapagenumbercontainingthedatatheyneed.
Forexample,aclientwantingalltheblogpostsinpage3ofourhypotheticalblogservice,
canusethefollowingGETmethod:
http://blog.example.com/posts?page=3
TheRESTserviceinthisscenariowouldrespondwithasetofposts.Thenumberof
postsreturneddependsonthedefaultpagesizesetintheservice.Itispossibleforthe
clienttooverridethedefaultpagesizebypassinginapage-sizeparameter:
http://blog.example.com/posts?page=3&size=20
GitHub’sRESTservicesusethispaginationstyle.Bydefault,thepagesizeissetto30
butcanbeoverriddenusingtheper_pageparameter:
https://api.github.com/user/repos?page=2&per_page=100
LimitOffsetPagination
Inthispaginationstyle,theclientsusestwoparameters:alimitandanoffsettoretrieve
thedatathattheyneed.Thelimitparameterindicatesthemaximumnumberofelementsto
returnandtheoffsetparameterindicatesthestartingpointforthereturndata.Forexample,
toretrieve10blogpostsstartingfromtheitemnumber31,aclientcanusethefollowing
request:
http://blog.example.com/posts?limit=10&offset=30
Cursor-BasedPagination
Inthispaginationstyle,theclientsmakeuseofapointeroracursortonavigatethrough
thedataset.Acursorisaservice-generatedrandomcharacterstringthatactsasamarker
foraniteminthedataset.Tounderstandthisstyle,consideraclientmakingthefollowing
requesttogetblogposts:
http://blog.example.com/posts
Onreceivingtherequest,theservicewouldsenddatasimilartothis:
{
"data":[
...Blogdata
],
"cursors":{
"prev":null,
"next":"123asdf456iamcur"
}
}
Thisresponsecontainsasetofblogsrepresentingasubsetofthetotaldataset.The
cursorsthatarepartoftheresponsecontainsaprevfieldthatcanbeusedtoretrieve
theprevioussubsetofthedata.However,becausethisistheinitialsubset,theprevfield
valueisempty.Theclientcanusethecursorvalueinthenextfieldtogetthenextsubset
ofthedatausingthefollowingrequest:
http://api.example.com/posts?cursor=123asdf456iamcur
Onreceivingthisrequest,theservicewouldsendthedataalongwiththeprevand
nextcursorfields.ThispaginationstyleisusedbyapplicationssuchasTwitterand
Facebookthatdealwithreal-timedatasets(tweetsandposts)wheredatachanges
frequently.Thegeneratedcursorstypicallydon’tliveforeverandshouldbeusedforshorttermpaginationpurposesonly.
Time-BasedPagination
Inthisstyleofpagination,theclientspecifiesatimeframetoretrievethedatainwhich
theyareinterested.Facebooksupportsthispaginationstyleandrequirestimespecifiedas
aUnixtimestamp.ThesearetwoFacebookexamplerequests:
https://graph.facebook.com/me/feed?limit=25&until=1364587774
https://graph.facebook.com/me/feed?limit=25&since=1364849754
Bothexamplesusethelimitparametertoindicatethemaximumnumberofitemstobe
returned.Theuntilparameterspecifiestheendofthetimerange,whereasthesince
parameterspecifiesthebeginningofthetimerange.
PaginationData
Allthepaginationstylesintheprevioussectionsreturnonlyasubsetofthedata.So,in
additiontosupplyingtherequesteddata,itbecomesimportantfortheserviceto
communicatepagination-specificinformationsuchastotalnumberofrecordsortotal
numberofpagesorcurrentpagenumberandpagesize.Thefollowingexampleshowsa
responsebodywithpaginationinformation:
{
"data":[
...BlogData
],
"totalPages":9,
"currentPageNumber":2,
"pageSize":10,
"totalRecords":90
}
Clientscanusethepaginationinformationtoassessthecurrentstateaswellas
constructURLstoobtainthenextorpreviousdatasets.Theothertechniqueservices
employistoincludethepaginationinformationinaspecialLinkheader.TheLink
headerisdefinedaspartofRFC
5988(http://tools.ietf.org/html/rfc5988).Ittypicallycontainsasetof
ready-madelinkstoscrollforwardandbackward.GitHubusesthisapproach;hereisan
exampleofaLinkheadervalue:
Link:<https://api.github.com/user/repos?
page=3&per_page=100>;rel="next",
<https://api.github.com/user/repos?page=50&per_page=100>;
rel="last"
QuickPollPagination
TosupportlargepolldatasetsinaQuickPollapplication,wewillbeimplementingthe
pagenumberpaginationstyleandwillincludethepaginginformationintheresponse
body.
WebegintheimplementationbyconfiguringourQuickPollapplicationtoloaddummy
polldataintoitsdatabaseduringthebootstrappingprocess.Thiswouldenableustotest
ourpollingandsortingcode.Toachievethis,copytheimport.sqlfilefromthe
downloadedchaptercodeintosrc\main\resourcesfolder.Theimport.sqlfile
containsDMLstatementsforcreatingtestpolls.Hibernateoutoftheboxloadsthe
import.sqlfilefoundundertheclasspathandexecutesalloftheSQLstatementsinit.
RestarttheQuickPollapplicationandnavigatetohttp://localhost:8080/v2/pollsin
Postman;itshouldlistalloftheloadedtestpolls.
SpringDataJPAandSpringMVCprovidesoutoftheboxsupportforthepagenumber
paginationstyle,makingourQuickPollpagingimplementationeasy.Centraltopaging
(andsorting)functionalityinSpringDataJPAisthe
org.springframework.data.repository.PagingAndSortingRepository
interfaceshowninListing7-4.
Listing7-4.SpringDataJPA’spagingandsortingrepository
publicinterfacePagingAndSortingRepository<T,IDextends
Serializable>extendsCrudRepository<T,ID>{
Page<T>findAll(Pageablepageable);
Iterable<T>findAll(Sortsort);
}
ThePagingAndSortingRepositoryinterfaceextendstheCrudRepository
interfacethatwehavebeenusingsofarintheQuickPollapplication.Additionally,itadds
twofindermethodsthatreturnentitiesmatchingthepagingandsortingcriteriaprovided.
ThefindAllmethodresponsibleforpagingtakesaPageableinstancetoread
informationsuchaspagesizeandpagenumber.Additionally,italsotakessorting
information,whichwewillzoominoninalatersectioninthischapter.ThisfindAll
methodreturnsaPageinstancethatcontainsthedatasubsetandthefollowing
information:
TotalElements—Totalelementsintheresultset
NumberofElements—Numberofelementsinthereturnedsubset
Size—Themaximumnumberofelementsineachpage
TotalPages—Totalnumberofpagesintheresultset
Number—Returnsthecurrentpagenumber
Last—Flagindicatingifitisthelastdatasubset
First—Flagindicatingifitisthefirstdatasubset
Sort—Returnsparametersusedforsorting,ifany
ThenextstepinimplementingpaginginQuickPollistomakeour
PollRepositoryextendPagingAndSortingRepositoryinsteadofcurrent
CrudRepository.Listing7-5showsthenewPollRepositoryimplementation.
BecausethePagingAndSortingRepositoryextendstheCrudRepository,all
ofthefunctionalityneededforthefirstversionofourAPIremainsintact.
Listing7-5.PollRepositoryimplementation
packagecom.apress.repository;
import
org.springframework.data.repository.PagingAndSortingRepository;
importcom.apress.domain.Poll;
publicinterfacePollRepositoryextends
PagingAndSortingRepository<Poll,Long>{
}
ChangingtherepositorytousePagingAndSortingRepositoryconcludesour
backendimplementationneededforpaging.WenowmoveontorefactoringtheV2
PollControllersothatitusesthenewpagingfindermethod.Listing7-6showsthe
refactoredgetAllPollsmethodoftheV2
com.apress.v2.controller.PollController.Noticethatwehaveaddedthe
PageableparametertothegetAllPollsmethod.OnreceivingaGETrequeston
“/polls”,SpringMVCinspectstherequestparameters,constructsaPageable
instance,andpassesittothegetAllPollsmethod.Typically,thepassed-ininstanceis
ofthetypePageRequest.ThePageableparameteristhenpassedtothenewfinder
methodandthepageddataisretunedaspartoftheresponse.
Listing7-6.getAllPollsmethodwithpagingfunctionality
importorg.springframework.data.domain.Page;
importorg.springframework.data.domain.Pageable;
@RequestMapping(value="/polls",method=RequestMethod.GET)
@ApiOperation(value="Retrievesallthepolls",
response=Poll.class,responseContainer="List")
publicResponseEntity<Page<Poll>>getAllPolls(Pageable
pageable){
Page<Poll>allPolls
=pollRepository.findAll(pageable);
returnnewResponseEntity<>(allPolls,HttpStatus.OK);
}
ThisconcludestheQuickPollpaginationimplementation.RestarttheQuickPoll
applicationandsubmitaGETrequesttohttp://localhost:8080/v2/polls?
page=0&size=2usingPostman.Theresponseshouldcontaintwopollinstanceswith
paging-relatedmetadata.Figure7-2showstherequestaswellasthemetadataportionof
theresponse.
Figure7-2.Pagedresultsalongwithpagingmetadata
NoteSpringDataJPAusesazero-index-basedpagingapproach.Hence,thefirstpage
numberstartswith0andnot1.
ChangingDefaultPageSize
SpringMVCusesan
org.springframework.data.web.PageableHandlerMethodArgumentResolve
toextractpaginginformationfromtherequestparametersandinjectingPageable
instancesintoControllermethods.Outofthebox,the
PageableHandlerMethodArgumentResolverclasssetsthedefaultpagesizeto
20.Hence,ifyouperformaGETrequestonhttp://localhost:8080/v2/polls,
theresponsewouldinclude20polls.Although20isagooddefaultpagesize,theremight
beoccasionswhenyoumightwanttochangeitgloballyinyourapplication.Todothis,
youneedtocreateandregisteranewinstanceof
PageableHandlerMethodArgumentResolverwiththesettingsofyourchoice.
SpringBootapplicationsrequiringchangestodefaultMVCbehaviorneedtocreate
classesoftype
org.springframework.web.servlet.config.annotation.WebMvcConfigure
anduseitscallbackmethodsforcustomization.Listing7-7showsthenewlycreated
QuickPollMvcConfigAdapterclassinthecom.apresspackagewiththe
configurationtosetthedefaultpagesizeto5.Hereweareusingthe
WebMvcConfigurerAdapter’saddArgumentResolverscallbackmethod.We
beginthemethodimplementationbycreatinganinstanceof
PageableHandlerMethodArgumentResolver.ThesetFallbackPageable
method,asthenamesuggests,isusedbySpringMVCwhennopaginginformationis
foundintherequestparameters.WecreateaPageRequestinstancewith5asthe
defaultpagesizeandpassittothesetFallbackPageablemethod.Wethenregister
ourPageableHandlerMethodArgumentResolverinstancewithSpringusingthe
passed-inargumentResolversparameter.
Listing7-7.Codetochangedefaultpagesizeto5
packagecom.apress;
importjava.util.List;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.data.domain.PageRequest;
import
org.springframework.data.web.PageableHandlerMethodArgumentResolve
import
org.springframework.web.method.support.HandlerMethodArgumentResol
import
org.springframework.web.servlet.config.annotation.WebMvcConfigure
@Configuration
publicclassQuickPollMvcConfigAdapterextends
WebMvcConfigurerAdapter{
@Override
publicvoid
addArgumentResolvers(List<HandlerMethodArgumentResolver>
argumentResolvers){
PageableHandlerMethodArgumentResolverphmar=new
PageableHandlerMethodArgumentResolver();
//Setthedefaultsizeto5
phmar.setFallbackPageable(newPageRequest(0,5));
argumentResolvers.add(phmar);
super.addArgumentResolvers(argumentResolvers);
}
}
RestarttheQuickPollapplicationandperformaGETrequeston
http://localhost:8080/v2/pollsusingPostman.Youwillnoticethatthe
responsenowincludesonlyfivepolls.TheassociatedpagingmetadataisshowninListing
7-8.
Listing7-8.Pagingmetadatafordefaultpagesize5
{
.....OmmitedPollData…...
"totalPages":4,
"totalElements":20,
"last":false,
"size":5,
"number":0,
"sort":null,
"numberOfElements":5,
"first":true
}
Sorting
SortingallowsRESTclientstodeterminetheorderinwhichitemsinadatasetare
arranged.RESTservicessupportingsortingallowclientstosubmitparameterswith
propertiestobeusedforsorting.Forexample,aclientcansubmitthefollowingrequestto
sortblogpostsbasedontheircreateddateandtitle:
http://blog.example.com/posts?sort=createdDate,title
SortAscendingorSortDescending
TheRESTservicescanalsoallowtheclientsspecifyoneofthetwosortdirections:
ascendingordescending.Becausethereisnosetstandardaroundthis,thefollowing
examplesshowcasepopularwaysforspecifyingsortdirection:
http://blog.example.com/posts?
sortByDesc=createdDate&sortByAsc=title
http://blog.example.com/posts?
sort=createdDate,desc&sort=title,asc
http://blog.example.com/posts?sort=-createdDate,title
Inalloftheseexamples,weareretrievingblogpostsinthedescendingorderoftheir
createddate.Postswiththesamecreateddatearethensortedbasedontheirtitles.
Inthefirstapproachthesortparameterclearlyspecifiesifthe
directionshouldbeascendingordescending.
Inthesecondapproach,wehaveusedthesameparameternamefor
bothdirections.However,theparametervaluespellsoutthesort
direction.
Thelastapproachusesthe“-”notationtoindicatethatanyproperty
prefixedwitha“-”shouldbesortedonadescendingdirection.
Propertiesthatarenotprefixedwitha“-”willbesortedinthe
ascendingdirection.
QuickPollSorting
Consideringthatsortingistypicallyusedinconjunctionwithpaging,SpringDataJPA’s
PagingAndSortingRepositoryandPageableimplementationsaredesignedto
handleandservicesortingrequestsfromthegroundup.Hence,wedon’trequireany
explicitimplementationforsorting.
Totestsortingfunctionality,submitaGETrequestto
http://localhost:8080/v2/polls/?sort=questionusingPostman.You
shouldseetheresponsewithPollssortedinascendingorderoftheirquestiontextalong
withsortmetadata.Figure7-3showsthePostmanrequestalongwiththesortmetadata.
Figure7-3.Sortmetadata
Tosortonmultiplefieldswithdifferentsortdirections,SpringMVCrequiresyouto
followthesecondapproachdiscussedintheprevioussection.Thefollowingrequestsorts
onascendingquestionvalueanddescendingidvalue:
http://localhost:8080/v2/polls/?
sort=question,asc&sort=id,desc
Summary
InthischapterwereviewedthedifferentstrategiesforversioningRESTAPI.Wethen
implementedversioninginQuickPollusingtheURLversioningapproach.Wealso
reviewedthedifferentapproachesfordealingwithlargedatasetsusingpaginationand
sortingtechniques.Finally,weusedSpringData’sout-of-the-boxfunctionalityto
implementpagenumberpaginationstyle.Inthenextchapter,wewillreviewstrategiesfor
securingRESTservices.
CHAPTER8
Security
Inthischapterwewilldiscuss:
StrategiesforsecuringRESTservices
OAuth2.0
BasicsoftheSpringSecurityframework
ImplementingQuickPollSecurity
TraditionalWebapplicationsrequiringsecuritytypicallyuseusername/passwordsfor
identificationpurposes.RESTservicesposeinterestingsecurityproblemsastheycanbe
consumedbyavarietyofclientssuchasbrowsersandmobiledevices.Theycanalsobe
consumedbyotherservicesandthismachine-to-machinecommunicationmightnothave
anyhumaninteraction.ItisalsonotuncommonforclientstoconsumeRESTserviceson
behalfofauser.Inthischapter,wewillexplorethedifferentauthentication/authorization
approachesthatcanbeusedwhileworkingwithRESTservices.Thenwewilllookat
usingsomeoftheseapproachestosecureourQuickPollapplication.
SecuringRESTServices
WebeginwithasurveyofsixpopularapproachesthatareusedforsecuringREST
services:
Session-basedsecurity
HTTPBasicAuthentication
DigestAuthentication
Certificatebasedsecurity
XAuth
OAuth
Session-basedSecurity
Thesession-basedsecuritymodelreliesonaserversidesessiontoholdontoauser’s
identityacrossrequests.InatypicalWebapplication,whenausertriestoaccessa
protectedresource,theyarepresentedwithaloginpage.Onsuccessfulauthentication,the
serverstoresthelogged-inuser’sinformationinaHTTPsession.Onsubsequentrequests,
thesessionisqueriedtoretrievetheuser’sinformationandisusedtoperform
authorizationchecks.Iftheuserdoesn’thavetheproperauthorization,theirrequestwill
bedenied.Figure8-1isapictorialrepresentationofthisapproach.
Figure8-1.Session-basedsecurityflow
FrameworkssuchasSpringSecurityprovideallthenecessaryplumbingtodevelop
applicationsusingthissecuritymodel.Thisapproachisveryappealingtodevelopersthat
areaddingRESTservicestoexistingSpringWebapplications.TheRESTserviceswill
retrievetheuseridentityfromthesessiontoperformauthorizationchecksandserve
resourcesaccordingly.However,thisapproachviolatesthestatelessnessRESTconstraint.
Also,becausetheserverholdstheclient’sstate,thisapproachisnotscalable.Ideally,the
clientshouldholdthestateandservershouldbestateless.
HTTPBasicAuthentication
UsingaLoginformtocaptureausernameandpasswordispossiblewhenthereishuman
interactioninvolved.However,thismightnotbepossiblewhenwehaveservicestalking
tootherservices.HTTPBasicauthenticationprovidesamechanismthatallowsclientsto
sendauthenticationinformationusingbothinteractiveandnoninteractivefashions.
Inthisapproach,whenaclientmakesarequesttoaprotectedresource,theserver
sendsa401“Unauthorized”responsecodeanda“WWW-Authenticate”header.The
“Basic”portionoftheheaderindicatesthatwewillbeusingBasicauthenticationand
the“realm”portionindicatesaprotectedspaceontheserver:
GET/protected_resource
401Unauthorized
WWW-Authenticate:Basicrealm="ExampleRealm"
Onreceivingtheresponse,theclientconcatenatesausernameandpasswordwitha
semicolonandBase64encodestheconcatenatedstring.Itthensendsthatinformationover
totheserverusingastandardAuthorizationheader:
GET/protected_resource
Authorization:BasicbHxpY26U5lkjfdk
Theserverdecodesthesubmittedinformationandvalidatesthesubmittedcredentials.
Onsuccessfulverification,theservercompletestherequest.Theentireflowisshownin
Figure8-2.
Figure8-2.HTTPBasicauthenticationflow
Becausetheclientincludestheauthenticationinformationineachrequest,theserver
becomesstateless.Itisimportanttorememberthattheclientissimplyencodingthe
informationandnotencryptingit.Hence,onnon-SSL/TLSconnections,itispossibleto
conductaman-in-the-middleattackandstealthepassword.
DigestAuthentication
TheDigestAuthenticationapproachissimilartotheBasicauthenticationmodeldiscussed
earlierexceptthattheusercredentialsaresentencrypted.Theclientsubmitsarequestfor
aprotectedresourceandtheserverrespondswitha401“Unauthorized”responsecodeand
aWWW-Authenticateheader.Hereisanexampleofaserverresponse:.
GET/protected_resource
401Unauthorized
WWW-Authenticate:Digestrealm="ExampleRealm",
nonce="P35kl89sdfghERT10Asdfnbvc",qop="auth"
NoticethattheWWW-AuthenticatespecifiestheDigestauthenticationscheme
alongwithaservergeneratednonceandaqop.Anonceisanarbitrarytokenusedfor
cryptographicpurposes.Theqop,or“qualityofprotection,”directivecancontaintwo
values—“auth”or“auth-int”:
Aqopvalue“auth”indicatesthatthedigestisusedfor
authenticationpurposes
Avalue“auth-int”indicatesthatdigestwillbeusedfor
authenticationandrequestintegrity
Onreceivingtherequest,iftheqopvalueissetto“auth,”theclientgeneratesa
digestusingthisformula:
hash_value_1=MD5(username:realm:password)
has_value_2=MD5(request_method:request_uri)
digest=MD5(hash_value_1:nonce:hash_value_2)
Iftheqopvalueissetto“auth-int,”theclientcomputesthedigestbyincluding
therequestbody:
hash_value_1=MD5(username:realm:password)
has_value_2
=MD5(request_method:request_uri:MD5(request_body))
digest=MD5(hash_value_1:nonce:hash_value_2)
Bydefault,theMD5algorithmisusedtocomputethehashvalues.Thedigestis
includedintheAuthorizationheaderandissenttotheserver.Onreceivingthe
request,theservercomputesthedigestandverifiestheuser’sidentity.Onsuccessful
verification,theservercompletestherequest.Acompleteflowofthismethodisshownin
Figure8-3.
Figure8-3.Digestauthenticationflow
TheDigestauthenticationapproachismoresecurethantheBasicauthentication,as
thepasswordisneversentincleartext.However,onnon-SSL/TLScommunications,itis
stillpossibleforsnooperstoretrievethedigestandreplaytherequest.Onewaytoaddress
thisproblemistolimitserver-generatednoncestoone-timeuseonly.Also,becausethe
serverhastogeneratethedigestforverification,itneedstohaveaccesstotheplaintext
versionofthepassword.Hence,itcan’temploymoresecureone-wayencryptions
algorithmssuchasbcryptandcanbecomemorevulnerabletoserversideattacks.
Certificate-BasedSecurity
TheCertificate-basedsecuritymodelreliesoncertificatestoverifyaparty’sidentity.Ina
SSL/TLS-basedcommunication,aclientsuchasabrowseroftenverifiestheserver’s
identityusingcertificatestoensurethattheserveriswhatitclaimstobe.Thismodelcan
beextendedtoperformmutualauthenticationwhereaservercanrequestaclient
certificateaspartofanSSL/TLShandshakeandverifyaclient’sidentity.
Inthisapproach,onreceivingarequestforaprotectedresource,theserverpresentsits
certificatetotheclient.TheclientensuresthatatrustedCertificateAuthority(CA)issued
theserver’scertificateandsendsitscertificateovertotheserver.Theserververifiesthe
client’scertificateandonsuccessfulverificationwillgrantaccesstotheprotected
resource.ThisflowisshowninFigure8-4.
Figure8-4.Certificate-basedsecurityflow
TheCertificate-basedsecuritymodeleliminatestheneedtosendoverasharedsecret,
makingitmoresecureoverusername/passwordmodels.However,deploymentsand
maintenanceofcertificatescanbeexpensiveandtypicallyareusedforlargesystems.
XAuth
AsRESTAPIsbecamepopular,thenumberofthird-partyapplicationsthatusethoseAPIs
alsogrewsignificantly.Theseapplicationsneedausernameandpasswordinorderto
interactwithRESTservicesandperformactionsonbehalfofusers.Thisposesahuge
securityproblemasthird-partyapplicationsnowhaveaccesstousernamesandpasswords.
Asecuritybreachinthethird-partyapplicationcancompromiseuserinformation.Also,if
theuserchangeshiscredentials,heneedstoremembertogoandupdateallofthesethirdpartyapplications.Finally,thismechanismdoesn’tallowtheusertorevokehis
authorizationtothethird-partyapplication.Theonlyoptionforrevokinginthiscase
wouldbetochangehispassword.
TheXAuthandOAuthschemesprovideamechanismtoaccessprotectedresourceson
auser’sbehalfwithoutneedingtostorepasswords.Inthisapproach,aclientapplication
wouldrequestausernameandpasswordfromtheusertypicallybyusingaloginform.
Theclientwouldthensendtheusernameandpasswordtotheserver.Theserverreceives
theuser’scredentialsandvalidatesthem.Onsuccessfulvalidation,atokenisreturnedto
theclient.Theclientdiscardstheusernameandpasswordinformationandstoresthetoken
locally.Whenaccessingauser’sprotectedresource,theclientwouldincludethetokenin
therequest.ThisistypicallyaccomplishedusingacustomHTTPheadersuchasXAuth-Token.Thelongevityofthetokenisdependentontheimplementingservice.The
tokencanremainuntiltheserverrevokesitorthetokencanexpireinadesignatedperiod
oftime.ThisflowisshowninFigure8-5.
Figure8-5.XAuthsecurityflow
ApplicationssuchasTwitterallowthird-partyapplicationstoaccesstheirRESTAPI
usinganXAuthscheme.However,evenwithXAuth,athird-partyapplicationneedsto
captureausernameandpassword,leavingthepossibilityofmisuse.Consideringthe
simplicityinvolvedinXAuth,itmightbeagoodcandidatewhenthesameorganization
developstheclientaswellastheRESTAPI.
OAuth2.0
TheOpenAuthorizationorOAuthisaframeworkforaccessingprotectedresourceson
behalfofauserwithoutstoringapassword.TheOAuthprotocolwasfirstintroducedin
2007andwassupersededbyOAuth2.0,whichwasintroducedin2010.Inthisbook,we
willbereviewingOAuth2.0.
OAuth2.0definesthefollowingfourroles:
ResourceOwner—Aresourceowneristheuserthatwantstogive
accesstoportionsoftheiraccountorresources.Forexample,a
resourceownercouldbeaTwitteroraFacebookuser.
Client—Aclientisanapplicationthatwantsaccesstoauser’s
resources.Thiscouldbeathird-partyappsuchasKlout
(https://klout.com/)thatwantstoaccessauser’sTwitter
account.
AuthorizationServer—Anauthorizationserververifiestheuser’s
identityandgrantstheclientatokentoaccesstheuser’sresources.
ResourceServer—Aresourceserverhostsprotecteduserresources.
Forexample,thiswouldbeTwitterAPItoaccesstweetsand
timelines,andsoon.
TheinteractionsbetweenthesefourrolesdiscussedaredepictedinFigure8-6.OAuth
2.0requirestheseinteractionstobeconductedonSSL.
Figure8-6.OAuth2.0securityflow
Beforeaclientcanparticipateinthe“OAuthdance”showninFigure8-6,itmust
registeritselfwiththeAuthorizationServer.FormostpublicAPIssuchasFacebookand
Twitter,thisinvolvesfillingoutanapplicationformandprovidinginformationaboutthe
clientsuchasapplicationname,basedomain,andwebsite.Onsuccessfulregistration,the
clientwillreceiveaClientIDandaClientsecret.TheClientIDisusedtouniquely
identifytheClientandisavailablepublicly.Theseclientcredentialsplayanimportantpart
intheOAuthinteractions,whichwewilldiscussinjustaminute.
TheOAuthinteractionbeginswiththeuserexpressinginterestinusingthe“Client,”a
third-partyapplication.Theclientrequestsauthorizationtoaccessprotectedresourceson
theuser’sbehalfandredirectstheuser/resourceownertotheAuthorizationserver.An
exampleURIthattheclientcanredirecttheusertoisshownhere:
https://oauth2.example.com/authorize?
client_id=CLIENT_ID&response_type=auth_code&call_back=CALL_BACK_U
TheusageofHTTPSismandatoryforanyproductionOAuth2.0interactionsand,
hence,theURIbeginswithhttps.TheCLIENT_IDisusedtoprovidetheclient’sidentity
totheauthorizationserver.Thescopeparameterprovidesacommaseparatedsetof
scopes/rolesthattheclientneeds.
Onreceivingtherequest,theauthorizationserverwouldpresenttheuserwithan
authenticationchallengetypicallyviaaloginform.Theuserprovideshisusernameand
password.Onsuccessfulverificationoftheusercredentials,theauthorizationserver
redirectstheusertotheclientapplicationusingtheCALL_BACK_URIparameter.The
authorizationserveralsoappendsanauthorizationcodetotheCALL_BACK_URI
parametervalue.HereisanexampleURLthatanauthorizationservermightgenerate:
https://mycoolclient.com/code_callback?
auth_code=6F99A74F2D066A267D6D838F88
TheclientthenusestheauthorizationcodetorequestanAccessTokenfromthe
authorizationserver.Toachievethis,aclientwouldtypicallyperformaHTTPPOSTona
URIlikethis:
https://oauth2.example.com/access_token?
client_id=CLIENT_ID&client_secret=CLIENT_SECRET&auth_code=6F99A74
Asyoucansee,theclientprovidesitscredentialsaspartoftherequest.The
authorizationserververifiestheclient’sidentityandauthorizationcode.Onsuccessful
verification,itreturnsanaccesstoken.HereisanexampleresponseinJSONformat:
{"access_token"="f292c6912e7710c8"}
Onreceivingtheaccesstoken,theclientwillrequestaprotectedresourcefromthe
resourceserverpassingintheaccesstokenitobtained.Theresourceservervalidatesthe
accesstokenandservestheprotectedresource.
OAuthClientProfiles
OneofthestrengthsofOAuth2.0isitssupportforvarietyofclientprofilessuchas“Web
application,”“Nativeapplication,”and“UserAgent/Browserapplication.”The
authorizationcodeflowdiscussedearlier(oftenreferredtoasauthorizationgranttype)is
applicableto“Webapplication”clientsthathaveaWeb-baseduserinterfaceandaserver
sidebackend.Thisallowstheclienttostoretheauthorizationcodeinasecurebackend
andreuseitforfutureinteractions.Otherclientprofileshavetheirownflowsthat
determinetheinteractionbetweenthefourOAuth2.0players.
ApureJavaScript-basedapplicationoranativeapplicationcan’tstoreauthorization
codessecurely.Hence,forsuchclients,thecallbackfromtheauthorizationserverdoesn’t
includeanauthorizationcode.Instead,animplicitgranttypeapproachistakenandan
accesstokenisdirectlyhandedovertotheclient,whichisthenusedforrequesting
protectedresources.Applicationsfallingunderthisclientprofilewillnothaveaclient
secretandaresimplyidentifiedusingtheclientID.
OAuth2.0alsosupportsanauthorizationflow,referredtoaspasswordgranttype,that
issimilartoXAuthdiscussedintheprevioussection.Inthisflow,theusersupplieshis
credentialstotheclientapplicationdirectly.Heisneverredirectedtotheauthorization
server.Theclientpassesthesecredentialstotheauthorizationserverandreceivesan
accesstokenforrequestingprotectedresources.
OAuth1.0introducedseveralimplementationcomplexitiesespeciallyaroundthe
cryptographicrequirementsforsigningrequestswithclientcredentials.OAuth2.0
simplifiedthisbyeliminatingsignaturesandrequiringHTTPSforallinteractions.
However,becausemanyofOAuth2’sfeaturesareoptional,thespecificationhasresulted
innoninteroperableimplementations.
RefreshTokensversusAccessTokens
Thelifetimeofaccesstokenscanbelimitedandclientsshouldbepreparedforthe
possibilityofatokennolongerworking.Topreventtheneedfortheresourceownerto
repeatedlyauthenticate,theOAuth2.0specificationhasprovidedanotionofrefresh
tokens.Anauthorizationservercanoptionallyissuearefreshtokenwhenitgeneratesan
accesstoken.Theclientstoresthisrefreshtoken,andwhenanaccesstokenexpires,it
contactstheauthorizationserverforafreshsetofaccesstokenaswellasrefreshtoken.
Specificationallowsgenerationofrefreshtokensforauthorizationandpasswordgrant
typeflows.Consideringthelackofsecuritywiththe“implicitgranttype,”refreshtokens
areprohibitedforsuchclientprofiles.
SpringSecurityOverview
ToimplementsecurityintheQuickPollapplicationwewillbeusinganotherpopular
Springsubproject,namely,SpringSecurity.Beforewemoveforwardwiththe
implementation,let’sunderstandSpringSecurityandthedifferentcomponentsthatmake
uptheframework.
SpringSecurity,formerlyknownasAcegiSecurity,isaframeworkforsecuringJavabasedapplications.Itprovidesanout-of-the-boxintegrationtoavarietyofauthentication
systemssuchasLDAP,Kerberos,OpenID,OAuth,andsoon.Withminimal
configuration,itcanbeeasilyextendedtoworkwithanycustomauthenticationand
authorizationsystems.Theframeworkalsoimplementssecuritybestpracticesandhas
inbuiltfeaturestoprotectagainstattackssuchasCSRF,orCrossSiteRequestForgery,
andsessionfixation,andsoon.
SpringSecurityprovidesaconsistentsecuritymodelthatcanbeusedtosecureWeb
URLsandJavamethods.Thehigh-levelstepsinvolvedduringtheSpringSecurity
Authentication/Authorizationprocessalongwithcomponentsinvolvedarelistedhere:
1. Theprocessbeginswithauserrequestingaprotectedresourceona
Spring-securedWebapplication.
2. TherequestgoesthroughaseriesofSpringSecurityfiltersreferred
toasa“filterchain”thatidentifyan
org.springframework.security.web.AuthenticationEntryPoi
toservicetherequest.TheAuthenticationEntryPointwill
respondtotheclientwitharequesttoauthentication.Thisisdone,
forexample,bysendingaloginpagetotheuser.
3. Onreceivingauthenticationinformationfromtheusersuchasa
username/password,a
org.springframework.security.core.Authentication
objectiscreated.TheAuthenticationinterfaceisshownin
Listing8-1anditsimplementationsplaysadualroleinSpring
Security.Theyrepresentatokenforanauthenticationrequestora
fullyauthenticatedprincipalafterauthenticationissuccessfully
completed.TheisAuthenticatedmethodcanbeusedto
determinethecurrentroleplayedbyanAuthentication
instance.Incaseofausername/passwordauthentication,the
getPrincipalmethodreturnstheusernameandthe
getCredentialsreturnsthepassword.The
getUserDetailsmethodcontainsadditionalinformationsuch
asIPaddress,andsoon.
Listing8-1.AuthenticationAPI
publicinterfaceAuthenticationextends
Principal,Serializable{
ObjectgetPrincipal();
ObjectgetCredentials();
ObjectgetDetails();
Collection<?extendsGrantedAuthority>
getAuthorities();
booleanisAuthenticated();
voidsetAuthenticated(boolean
isAuthenticated)throws
IllegalArgumentException;
}
4. Asanextstep,theauthenticationrequesttokenispresentedtoan
org.springframework.security.authentication.Authenticat
TheAuthenticationMangerasshowninListing8-2,contains
anauthenticatemethodthattakesanauthenticationrequesttoken
andreturnsafullypopulatedAuthenticationinstance.Spring
providesanout-of-the-boximplementationof
AuthenticationMangercalledProviderManager.
Listing8-2.AuthenticationManagerAPI
publicinterfaceAuthenticationManager{
Authentication
authenticate(Authenticationauthentication)
throwsAuthenticationException;
}
5. Inordertoperformanauthentication,theProviderManager
needstocomparethesubmitteduserinformationwithabackend
userstoresuchasLDAPordatabase.ProviderManager
delegatesthisresponsibilitytoaseriesof
org.springframework.security.authentication.Authenticat
TheseAuthenticationProvidersusean
org.springframework.security.core.userdetails.UserDetai
toretrieveuserinformationfrombackendstores.Listing8-3shows
theUserDetailsServiceAPI.
Listing8-3.UserDetailsServiceAPI
publicinterfaceUserDetailsService{
UserDetailsloadUserByUsername(String
username)throwsUsernameNotFoundException;
}
ImplementationsofUserDetailsServicesuchas
JdbcDaoImplandLdapUserDetailServicewillusethe
passed-inusernametoretrieveuserinformation.These
implementationswillalsocreateasetofGrantedAuthority
instancesthatrepresentroles/authoritiestheuserhasinthesystem.
6. TheAuthenticationProvidercomparesthesubmitted
credentialswiththeinformationinthebackendsystemandon
successfulverificationthe
org.springframework.security.core.userdetails.UserDetai
objectisusedtobuildafullypopulatedAuthentication
instance.
7. TheAuthenticationinstanceisthenputintoan
org.springframework.security.core.context.SecurityConte
TheSecurityContextHolderasthenamesuggestssimply
associatesthelogged-inuser’scontextwiththecurrentthreadof
executionsothatitisreadilyavailableacrossuserrequestsor
operations.InaWeb-basedapplication,thelogged-inuser’scontext
istypicallystoredintheuser’sHTTPsession.
8. SpringSecuritythenperformsanauthorizationcheckusingan
org.springframework.security.access.intercept.AbstractS
anditsimplementations
org.springframework.security.web.access.intercept.Filte
and
org.springframework.security.access.intercept.aopallian
TheFilterSecurityInterceptorisusedforURL-based
authorizationandMethodSecurityInterceptorisusedfor
methodinvocationauthorization.
9. TheAbstractSecurityInterceptorreliesonsecurity
configurationandasetof
org.springframework.security.access.AccessDecisionManag
todecideiftheuserisauthorizedornot.Onsuccessful
authorization,theuserisgivenaccesstotheprotectedresource.
NoteTokeepthingssimple,IhavepurposefullyomittedsomeSpringSecurityclasses
inthesesteps.ForacompletereviewofSpringSecurityandthe
authentication/authorizationsteps,pleaserefertoProSpringSecurity(Apress,2013).
NowthatyouhaveabasicunderstandingofSpringSecurity’s
authentication/authorizationflowaswellassomeofitscomponents,let’slookat
integratingSpringSecurityintoourQuickPollapplication.
SecuringQuickPoll
WewillimplementsecurityintheQuickPollapplicationtomeetthefollowingtwo
requirements:
Registereduserscancreateandaccesspolls.Thisallowsustokeep
trackofaccounts,usage,andsoon
PollscanbedeletedonlybyuserswithAdminprivileges
NoteInthischapter,wewillcontinuebuildingontheworkthatwedidonthe
QuickPollapplicationinpreviouschapters.Alternatively,astarterprojectisavailablefor
youtouseinsidetheChapter8\starterfolderofthedownloadedsourcecode.In
thischapter,wewillsecureQuickPollusingBasicAuthentication.Thenwewilladd
OAuth2.0supporttoQuickPoll.Hence,theChapter8\finalfoldercontainstwo
folders:quick-poll-ch8-final-basic-authandquick-poll-ch8-final.
Thequick-poll-ch8-final-basic-authcontainsthesolutionwithBasic
AuthenticationaddedtoQuickPoll.Thequick-poll-ch8-finalcontainsthe
completedsolutionwithbothBasicAuthenticationandOAuth2.0added.Weunderstand
thatnotallprojectsneedOAuth2.0support.Hence,splittingthefinalsolutionintotwo
projectsallowsyoutoexamineandusefeatures/codethatyouneed.Pleaserefertothe
solutionsunderthefinalfolderforcompletelistingscontaininggetters/settersand
additionalimports.ThedownloadedChapter8folderalsocontainsanexportedPostman
collectioncontainingRESTAPIrequestsassociatedwiththischapter.
Byrequiringuserauthentication,wewillbedrasticallychangingthebehaviorofthe
QuickPollapplication.ToallowexistinguserscontinueusingourQuickPollapplication,
wewillcreateanewversion3ofourAPItoimplementthesechanges.Toaccomplishthis,
createanewcom.apress.v3.controllerpackageundersrc\main\javaand
copycontrollersfromthecom.apress.v2.controllerpackage.Forthenewly
copiedcontrollers,changetheRequestMappingsfrom“/v2/”to“/v3/”andchange
thecontrollernameprefixesfromv2tov3toreflectversion3oftheAPI.Westartthe
implementationbyaddingtheSpringSecuritystarterdependencyshowninListing8-4to
QuickPollproject’spom.xmlfile.ThiswouldbringinallSpringSecurity–relatedJAR
filesintotheproject.
Listing8-4.SpringStarterPOM
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
OnseeingSpringSecurityintheclasspath,SpringBootaddsdefaultsecurity
configurationthatsecuresalloftheHTTPendpointswithaHTTPBasicauthentication.
StarttheQuickPollapplicationandsubmitaGETrequestto
http://localhost:8080/v3/pollsusingPostman.Postmandisplaysan
authenticationwindowpromptingyoutoenterausernameandpassword,asshownin
Figure8-7.
Figure8-7.BasicauthenticationwindowinPostman
SpringBoot’sdefaultsecurityconfigurationcomeswithasingleuserwithusername
user.SpringBootgeneratesarandompasswordfortheuserandprintsitatINFOloglevel
duringapplicationstartup.Inyourconsole/logfileyoushouldseeanentrylikethisone:
Usingdefaultsecuritypassword:554cc6c2-67e1-4f1e-8c5b096609e2d0b1
EntertheusernameandpasswordfoundinyourconsoleintothePostmasterlogin
windowandhitLogIn.SpringSecuritywillvalidatetheenteredcredentialsandallowthe
requesttobecompleted.
cURL
Uptothispoint,wehavebeenusingPostmanfortestingourQuickPollapplication.Inthis
chapter,wewillbeusingacommandlinetoolnamedcURLinconjunctionwithPostman.
cURLisapopularopen-sourcetoolusedforinteractingwithserversandtransferringdata
withURLsyntax.Itcomesinstalledinmostoperatingsystemdistributions.IfcURLisnot
availableonyoursystem,followtheinstructionsat
http://curl.haxx.se/download.htmltodownloadandinstallcURLonyour
machine.RefertoAppendixAforinstructionsoninstallingcURLonaWindows
machine.
TotestourQuickPollBasicauthenticationusingcURL,runthefollowingcommandat
commandline:
curl-vuuser:554cc6c2-67e1-4f1e-8c5b-096609e2d0b1
http://localhost:8080/v3/polls
Inthiscommand,the–voptionrequestscURLtoruninthedebugmode(verbose).
The–uoptionallowsustospecifytheusernameandpasswordneededforbasic
authentication.AfulllistofcURLoptionsisavailableat
http://curl.haxx.se/docs/manual.html.
UserInfrastructureSetup
AlthoughSpringBoothassimplifiedSpringSecurityintegrationsignificantly,wewould
liketocustomizesecuritybehaviorsothatitusesapplicationusersinsteadofSpring
Boot’sgenericuser.Wealsowouldliketoapplythesecuritytothev3
PollController,leavingotherendpointstobeaccessedanonymously.Beforewe
lookatcustomizingSpringSecurity,let’ssetuptheinfrastructureneededfor
creating/updatingQuickPollapplicationusers.
WestartbycreatingaUserdomainobjectasshowninListing8-5torepresentsa
QuickPolluser.TheUserclasscontainsattributessuchasusername,password,
firstname,andlastname.ItalsocontainsaBooleanflagtoindicateiftheuserhas
administrativeprivileges.Asasecuritybestpractice,wehaveannotatedthepassword
fieldwith@JsonIgnore.Therefore,thepasswordfieldwillnotbeincludedinauser’s
representation,therebypreventingclientsfromaccessingthepasswordvalue.Because
“User”isakeywordindatabasessuchasOracle,wehaveusedthe@Tableannotationto
givethename“Users”totablecorrespondingtothisUserentity.
Listing8-5.Userclass
packagecom.apress.domain;
importjavax.persistence.Table;
importorg.hibernate.annotations.Type;
importcom.fasterxml.jackson.annotation.JsonIgnore;
importorg.hibernate.annotations.Type;
importorg.hibernate.validator.constraints.NotEmpty;
@Entity
@Table(name="USERS")
publicclassUser{
@Id
@GeneratedValue
@Column(name="USER_ID")
privateLongid;
@Column(name="USERNAME")
@NotEmpty
privateStringusername;
@Column(name="PASSWORD")
@NotEmpty
@JsonIgnore
privateStringpassword;
@Column(name="FIRST_NAME")
@NotEmpty
privateStringfirstName;
@Column(name="LAST_NAME")
@NotEmpty
privateStringlastName;
@Column(name="ADMIN",columnDefinition="char(3)")
@Type(type="yes_no")
@NotEmpty
privatebooleanadmin;
//GettersandSettersommitedforbrevity
}
WewillbestoringtheQuickPollusersinadatabaseandhencewillrequirea
UserRepositorytoperformCRUDactionsontheUserentity.Listing8-6showsthe
UserRepositoryinterfacecreatedundercom.apress.repositorypackage.In
additiontothefindermethodsprovidedbytheCrudRepository,the
UserRepositorycontainsacustomfindermethodnamedfindByUsername.
SpringDataJPAwouldprovidearuntimeimplementationsothatthefindByUsername
methodretrievesauserassociatedwiththepassedinusernameparameter.
Listing8-6.UserRepositoryinterface
packagecom.apress.repository;
importorg.springframework.data.repository.CrudRepository;
importcom.apress.domain.User;
publicinterfaceUserRepositoryextendsCrudRepository<User,
Long>{
publicUserfindByUsername(Stringusername);
}
ApplicationssuchasQuickPolltypicallyhaveaninterfacethatallowsnewusersto
register.Tokeepthingssimpleforthepurposesofthisbook,wehavegeneratedsometest
usersshowninListing8-7.CopytheseSQLstatementstotheendofimport.sqlfile
undertheQuickPollproject’ssrc\main\resourcesfolder.Whentheapplicationgets
bootstrapped,Hibernatewillloadthesetestusersintothe“Users”tableandmakethem
availablefortheapplication’suse.
Listing8-7.Testuserdata
insertintousers(user_id,username,password,first_name,
last_name,admin)values(1,'mickey',
'$2a$10$kSqU.ek5pDRMMK21tHJlceS1xOc9Kna4F0DD2ZwQH/LAzH0ML0p6.',
'Mickey','Mouse','no');
insertintousers(user_id,username,password,first_name,
last_name,admin)values(2,'minnie',
'$2a$10$MnHcLn.XdLx.iMntXsmdgeO1B4wAW1E5GOy/VrLUmr4aAzabXnGFq',
'Minnie','Mouse','no');
insertintousers(user_id,username,password,first_name,
last_name,admin)values(3,'donald',
'$2a$10$0UCBI04PCXiK0pF/9kI7.uAXiHNQeeHdkv9NhA1/xgmRpfd4qxRMG',
'Donald','Duck','no');
insertintousers(user_id,username,password,first_name,
last_name,admin)values(4,'daisy',
'$2a$10$aNoR88g5b5TzSKb7mQ1nQOkyEwfHVQOxHY0HX7irI8qWINvLDWRyS',
'Daisy','Duck','no');
insertintousers(user_id,username,password,first_name,
last_name,admin)values(5,'clarabelle',
'$2a$10$cuTJd2ayEwXfsPdoF5/hde6gzsPx/gEiv8LZsjPN9VPoN5XVR8cKW',
'Clarabelle','Cow','no');
insertintousers(user_id,username,password,first_name,
last_name,admin)values(6,'admin',
'$2a$10$JQOfG5Tqnf97SbGcKsalz.XpDQbXi1APOf2SHPVW27bWNioi9nI8y',
'Super','Admin','yes');
Noticethatthepasswordforthegeneratedtestusersisnotinplaintext.Following
goodsecuritypractices,IhaveencryptedthepasswordvaluesusingtheBCrypt
(http://en.wikipedia.org/wiki/Bcrypt)adaptivehashingfunction.Table81showsthesetestusersandtheirplaintextversionofpasswords.
Table8-1.Testuserinformation
Username
Password
IsAdmin
Mickey
Cheese
No
Minnie
red01
No
Donald
Quack
No
Daisy
quack2
No
Clarabelle
Moo
No
Admin
Admin
Yes
UserDetailsServiceImplementation
IntheSpringSecurityintroductionsection,welearnedthataUserDetailsServiceis
typicallyusedtoretrieveuserinformation,whichgetscomparedwithuser-submitted
credentialsduringtheauthenticationprocess.Listing8-8showsa
UserDetailsServiceimplementationforourQuickPollapplication.
Listing8-8.UserDetailsServiceimplementationforQuickPoll
packagecom.apress.security;
importjavax.inject.Inject;
importorg.springframework.security.core.GrantedAuthority;
import
org.springframework.security.core.authority.AuthorityUtils;
import
org.springframework.security.core.userdetails.UserDetails;
import
org.springframework.security.core.userdetails.UserDetailsService;
import
org.springframework.security.core.userdetails.UsernameNotFoundExc
importorg.springframework.stereotype.Component;
importcom.apress.domain.User;
importcom.apress.repository.UserRepository;
@Component
publicclassQuickPollUserDetailsServiceimplements
UserDetailsService{
@Inject
privateUserRepositoryuserRepository;
@Override
publicUserDetailsloadUserByUsername(Stringusername)
throwsUsernameNotFoundException{
Useruser
=userRepository.findByUsername(username);
if(user==null){
thrownew
UsernameNotFoundException(String.format("Userwiththe
username%sdoesn'texist",username));
}
//Createagrantedauthoritybasedonuser's
role.
//Can'tpassnullauthoritiestouser.Hence
initializewithanemptyarraylist
List<GrantedAuthority>authorities=new
ArrayList<>();
if(user.isAdmin()){
authorities
=AuthorityUtils.createAuthorityList("ROLE_ADMIN");
}
//CreateaUserDetailsobjectfromthedata
UserDetailsuserDetails=new
org.springframework.security.core.userdetails.User(user.getUserna
user.getPassword(),authorities);
returnuserDetails;
}
}
TheQuickPollUserDetailsServiceclassmakesuseofUserRepository
toretrieveUserinformationfromthedatabase.Itthenchecksiftheretrieveduserhas
administrativerightsandconstructsanadminGrantedAuthority,namely,
ROLE_ADMIN.TheSpringSecurityinfrastructureexpectstheloadUserByUsername
methodtoreturnaninstanceoftypeUserDetails.Hence,the
QuickPollUserDetailsServiceclasscreatestheo.s.s.c.u.Userinstance
andpopulatesitwiththedataretrievedfromthedatabase.Theo.s.s.c.u.Userisa
concreteimplementationoftheUserDetailsinterface.Ifthe
QuickPollUserDetailsServicecan’tfindauserinthedatabaseforthepassed-in
username,itwillthrowaUsernameNotFoundExceptionexception.
CustomizingSpringSecurity
CustomizingSpringSecurity’sdefaultbehaviorinvolvescreatingaconfigurationclass
thatisannotatedwith@EnableWebSecurity.Thisconfigurationclasstypically
extendsthe
org.springframework.security.config.annotation.web.configuration.
classthatprovideshelpermethodstosimplifyoursecurityconfiguration.Listing8-9
showstheSecurityConfigclassthatwillcontainsecurityrelatedconfigurationfor
QuickPollapplication.
Listing8-9.SecurityconfigurationforQuickPoll
packagecom.apress;
importjavax.inject.Inject;
importorg.springframework.context.annotation.Configuration;
import
org.springframework.security.config.annotation.authentication.bui
import
org.springframework.security.config.annotation.web.configuration.
import
org.springframework.security.config.annotation.web.configuration.
import
org.springframework.security.core.userdetails.UserDetailsService;
import
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
publicclassSecurityConfigextends
WebSecurityConfigurerAdapter{
@Inject
privateUserDetailsServiceuserDetailsService;
@Override
protectedvoidconfigure(AuthenticationManagerBuilder
auth)throwsException{
auth.userDetailsService(userDetailsService)
.passwordEncoder(new
BCryptPasswordEncoder());
}
}
TheSecurityConfigclassdeclaresauserDetailsServiceproperty,which
getsinjectedwithaQuickPollUserDetailsServiceinstanceatruntime.Italso
overridesasuperclass’sconfiguremethodthattakesan
AuthenticationManagerBuilderasparameter.The
AuthenticationManagerBuilderisahelperclassimplementingtheBuilder
patternthatprovidesaneasywayofassemblinganAuthenticationManager.Inour
methodimplementationweusetheAuthenticationManagerBuildertoaddthe
UserDetailsServiceinstance.Becausewehaveencryptedthepasswordsstoredin
thedatabaseusingBCryptalgorithm,weprovideaninstanceof
BCryptPasswordEncoder.Theauthenticationmanagerframeworkwillusethe
passwordencodertocomparetheplainstringprovidedbytheuserwiththeencrypted
hashstoredinthedatabase.
Withthisconfigurationinplace,restarttheQuickPollapplicationandrunthe
followingcommandatthecommandline:
curl-umickey:cheesehttp://localhost:8080/v2/polls
Ifyourunthecommandwithoutthe–uoptionandtheusername/passworddata,you
willreceivea403errorfromtheserverasshownhere:
{"timestamp":1429998300969,"status":401,"error":"Unauthorized","m
authenticationisrequiredtoaccessthis
resource","path":"/v2/polls"}
SecuringURI
TheSecurityConfigclassintroducedintheprevioussectiongetsusonestepcloser
byconfiguringHTTPBasicauthenticationtouseQuickPollusers.Thisconfiguration,
however,protectsallendpointsandrequiresauthenticationtoaccessresources.To
implementourrequirementtojustsecurev3PollAPI,wewilloverrideanother
WebSecurityConfigurer’sconfigmethod.Listing8-10showstheconfigmethod
implementationthatneedstobeaddedtotheSecurtyConfigclass.
Listing8-10.NewconfigmethodinSecurityConfig
import
org.springframework.security.config.annotation.web.builders.HttpS
import
org.springframework.security.config.http.SessionCreationPolicy;
@Override
protectedvoidconfigure(HttpSecurityhttp)throwsException
{
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/v1/**","/v2/**","/swagger-ui/**",
"/api-docs/**").permitAll()
.antMatchers("/v3/polls/**").authenticated()
.and()
.httpBasic()
.realmName("QuickPoll")
.and()
.csrf()
.disable();
}
TheHttpSecurityparameterpassedintotheconfigmethodinListing8-10allows
ustospecifytheURIthatshouldbesecuredorunsecured.Webeginthemethod
implementationbyrequestingSpringSecuritytonotcreateaHTTPsessionandnotstore
loggedinuser’sSecurityContextinthesession.Thisisachievedusingthe
SessionCreationPolicy.STATELESScreationpolicy.Wethenuse
antMatcherstoprovideAntstyleURIexpressionsthatwedon’twantSpringSecurity
protecting.UsingthepermitAllmethodwearespecifyingthattheAPIversions
one/twoandSwaggerUIshouldbeavailableanonymously.ThenextantMatchers
alongwithauthenticatedmethodspecifiesthatSpringSecurityshouldonlyallow
authenticateduserstoaccessV3PollsAPI.Finally,weenableHTTPBasicauthentication
andsettherealmnameto“QuickPoll”.RestartQuickPollapplicationandyoushouldbe
promptedforauthenticationonlyonthe/v3/pollsresources.
NoteCross-siterequestforgery,orCSRF
(http://en.wikipedia.org/wiki/Cross-site_request_forgery),isa
typeofsecurityvulnerabilitywherebyamaliciouswebsiteforcestheendusertoexecute
unwantedcommandsonadifferentwebsiteinwhichtheyarecurrentlyauthenticated.
SpringSecuritybydefaultenablesCSRFprotectionandhighlyrecommendsusingitfor
requestssubmittedbyauserviaabrowser.Forservicesthatareusedbynonbrowser
clients,theCSRFcanbedisabled.ByimplementingcustomRequestMatchers,itis
possibletodisableCSRFonlyforcertainURLsorHTTPmethods.
Tokeepthingssimpleandmanageableforthisbook,wehavedisabledCSRFprotection.
Thelastsecurityrequirementthatwehaveistoensurethatonlyuserswith
administrativeprivilegescandeleteapoll.Toimplementthisauthorizationrequirement,
wewillapplySpringSecurity’smethodlevelsecurityonthedeletePollmethod.
Spring’smethodlevelsecuritycanbeenabledusingtheaptlynamed
org.springframework.security.config.annotation.method.configurati
EnableGlobalMethodSecurityannotation.Listing8-11showstheannotation
addedtotheSecurityConfigclass.
Listing8-11.EnableGlobalMethodSecurityannotationadded
packagecom.apress;
import
org.springframework.security.config.annotation.method.configurati
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
publicclassSecurityConfigextends
WebSecurityConfigurerAdapter{
//Contentremovedforbrevity
}
SpringSecuritysupportsarichsetofclassandmethodlevelauthorizationannotations
alongwithstandards-basedJSR250annotationsecurity.TheprePostEnabledflagin
EnableGlobalMethodSecurityrequestsSpringSecuritytoenableannotationsthat
performpre-andpostmethodinvocationauthorizationchecks.Thenextstepistoannotate
thev3PollController’sdeletePollmethodwith@PreAuthorizeannotation
asshowinginListing8-12.
Listing8-12.PreAuthorizeannotationadded
import
org.springframework.security.access.prepost.PreAuthorize;
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
publicResponseEntity<Void>deletePoll(@PathVariableLong
pollId){
//Coderemovedforbrevity
}
The@PreAuthorizeannotationdecidesifthedeletePollmethodcanbe
invokedornot.SpringSecuritymakesthisdecisionbyevaluatingtheSpring-EL
expressionpassedinastheannotation’svalue.Inthiscase,thehasAuthoritychecksif
thelogged-inuserhasthe“ROLE_ADMIN”authority.Restarttheapplicationand
performaDELETEontheendpointhttp://localhost:8080/v3/polls/12
usingPostman.Whenpromptedforcredentials,entertheusernamemickeyandthe
passwordcheese,andhitLogIn.Figure8-8showstherequestandassociatedinputs.
Figure8-8.Deletingpollwithunauthorizedusers
Sincetheusermickeydoesn’thaveadministrativerights,youwillseeanunauthorized
responsefromtheservice,asshowninFigure8-9.
Figure8-9.Unauthorizeddeleteresponse
Nowlet’sretrythisrequestusinganadminuserwithadministrativerights.In
Postman,clicktheBasicAuthtabandenterthecredentialsadmin/adminandhit“Refresh
headers”asshowninFigure8-10.Onsubmittingtherequest,youshouldseethePoll
resourcewithID12deleted.
Figure8-10.BasicAuthAdmincredentialsinPostman
TodeleteaPollusingcURLrunthecommandbelow:
curl-i-uadmin:admin-XDELETE
http://localhost:3/v3/polls/13
TheabovecommanddeletesaPollresourcewithID13.The–ioptionrequestcurlto
outputtheresponseheaders.The–XoptionallowsustospecifytheHTTPmethodname.
Inourcase,wespecifiedtheDELETEHTTPmethod.Theoutputofthisresultisshownin
Listing8-13.
Listing8-13.OutputofCURLDelete
HTTP/1.1200OK
Server:Apache-Coyote/1.1
X-Content-Type-Options:nosniff
X-XSS-Protection:1;mode=block
Cache-Control:no-cache,no-store,max-age=0,mustrevalidate
Pragma:no-cache
Expires:0
X-Frame-Options:DENY
Content-Length:0
Date:Sat,25Apr201521:50:35GMT
QuickPollOAuth2.0ProviderImplementation
ConsiderthescenarioinwhichQuickPollwantstoventureintothemobileappworldwith
theiriOSapp.Tofacilitatethat,wewillbeimplementingOAuthsothatthemobileclient
canperformoperationsonbehalfoftheloggedinuserwithoutstoringandsendinghis/her
credentialsforeachrequest.BecauseQuickPolldeveloperswillbedevelopingtheiOS
application,wewillbeimplementingOAuth’spasswordgranttype,asitprovidesthebest
userexperiencethatdoesn’tinvolveredirectsandnavigationstootherpages.
SpringSecurityprovidesanOAuthmodulethatsimplifiesOAuth1andOAuth2
integration.WebeginOAuthimplementationinQuickPollbyaddingtheSpringSecurity
OAuth2dependencytoproject’spom.xmlfile,asshowninListing8-14.
Listing8-14.SpringSecurityOAuth2Dependency
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.0.7.RELEASE</version>
</dependency>
WewanttoensurethataddingOAuthtoQuickPollshouldn’tbreakotherclients
currentlyusingBasicAuthentication(oranyfutureauthenticationmechanism
implementationssuchasForm-basedlogin).Toachievethis,wewilladdanotherrequest
mappingtothePollControllerclassasshowninListing8-15.Thenewmapping
/oauth2/v3allowsustoaccessthesamePollControllerresourcesviaan
additionalsetofURIssuchas/oauth2/v3/pollsand/outh2/v3/polls/12.It
willalsoallowustoapplyadditionalOAuthsecuritytothesePollController
resources.
Listing8-15.PollControllerwithmultiplemapping
@RestController("pollControllerV3")
@RequestMapping({"/v3/","/oauth2/v3/"})
@Api(value="polls",description="PollAPI")
publicclassPollController{
//Coderemovedforbrevity
}
ImplementingOAuth2requiresustoimplementtwocomponents—anauthorization
serverandaresourceserver.Inaproductionscenario,thesecomponentsresideontheir
ownserversforscalabilityandperformancereasons.Thesecomponentswouldsharea
backenddatabasetoadd/retrieveaccesstokens.However,tokeepthingssimple,wewill
implementbothauthenticationserverandresourceservercomponentsintheQuickPoll
applicationwithanin-memorytokenstore.
Asdiscussedintheprevioussections,anOAuth2authorizationserverisresponsible
formanagingclientdetails,verifyingaresourceowner’sauthorization,andgenerating
tokenssuchasauthorizationcode,access,andrefreshtokens.Toverifyaresourceowner’s
credentials,theauthorizationserverneedsaccesstoanAuthenticationManager
thatcanvalidateuser-submittedcredentialsagainsttheuserdatastore.Asyouwillrecall
fromListing8-9,wehavealreadyconfiguredanAuthenticationManagerinthe
SecurityConfigclasstoperformthisvalidation.Wejustneedtoexposeitasabean
sothatourauthorizationservercanuseit.Listing8-16showsthemodified
SecurityConfigclasswiththenewAuthenticationManagerbeandeclared.
Listing8-16.UpdatedSecurityConfig
@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
publicclassSecurityConfigextends
WebSecurityConfigurerAdapter{
//Othercodeomittedforbrevity
@Override
@Bean
protectedAuthenticationManager
authenticationManager()throwsException{
returnsuper.authenticationManager();
}
}
SpringSecurityprovidesan@EnableAuthorizationServerannotationand
org.springframework.security.oauth2.config.annotation.web.configu
forimplementingAuthorizationServer.The
@EnableAuthorizationServerannotationenablesthedifferentendpointsneeded
tointeractwithanauthorizationserver.Table8-2showsthedifferentendpointsthatget
enabled.TheAuthorizationServerConfigurerAdaptercanbeusedto
configuretheclientproperties,granttypessupported,andsecurityconfigurationfor
endpoints.
Table8-2.Authorizationserverendpoints
Endpoint
Description
/oauth/token
Endpointusedforgeneratingaccesstokens
/oauth/check_token
Endpointaresourceserverusestodecodeaccesstokensgeneratedby
authorizationserver
/oauth/confirm_access
Endpointaresourceuserpostsforgrantapproval
/oauth/error
Endpointresponsibleforrenderingerrorsintheauthorizationserver
/oauth/authorize
Endpointusedforgeneratingauthorizationcodes;bydefault,the
authorizationserverdoesn’tsecurethisendpoint
TheauthorizationserverforQuickPollapplicationisshowninListing8-17.Inthe
implementation,weaddtheinjectedAuthenticationManagerinstancetothe
configuration.ThisadditionofAuthenticationManagerinstanceenablesthe
passwordtypegrantthatweintendtousefortheiOSclient.
Listing8-17.QuickPollauthorizationserver
packagecom.apress;
importjavax.inject.Inject;
importorg.springframework.context.annotation.Configuration;
import
org.springframework.security.authentication.AuthenticationManager
import
org.springframework.security.oauth2.config.annotation.configurers
import
org.springframework.security.oauth2.config.annotation.web.configu
import
org.springframework.security.oauth2.config.annotation.web.configu
import
org.springframework.security.oauth2.config.annotation.web.configu
@Configuration
@EnableAuthorizationServer
publicclassOAuth2AuthorizationServerConfigextends
AuthorizationServerConfigurerAdapter{
@Inject
privateAuthenticationManagerauthenticationManager;
@Override
publicvoid
configure(AuthorizationServerEndpointsConfigurerendpoints)
throwsException{
endpoints.authenticationManager(this.authenticationM
}
@Override
publicvoidconfigure(ClientDetailsServiceConfigurer
clients)throwsException{
clients
.inMemory()
.withClient("quickpolliOSClient")
.secret("top_secret")
.authorizedGrantTypes("password")
.scopes("read","write")
.resourceIds("QuickPoll_Resources");
}
}
Inthesecondconfiguremethod;weusethe
ClientDetailsServiceConfigurerparametertoaddthedetailsofouriOS
client.Clientstypicallywouldusearegistrationformandfollowanestablishedapproval
processforgettingaddedtotheauthorizationserver.Herewehaveskippedthatprocess
andarestoringourclientdetailsinmemorytokeepourimplementationmanageable.The
clientdetailsincludethequickpolliOSClientclientIDandaclientsecret
top_secret.UsingtheauthorizedGrantTypesmethod,wespecifythattheclient
isauthorizedtousethepasswordgranttypewhencommunicatingwiththeauthorization
server.
Wealsospecifytwoscopes:readandwrite.Scopesarearbitrarystringsthatrepresent
thepermissionsthataclientisallowedtoperformonanAPI.Forexample,LinkedInuses
thescopesr_emailaddress,w_messages,andr_fullprofiletorepresent
actionssuchasreademailaddress(r_prefix)orwritemessages(w_prefix).Itispossible
todesignthescopestobeasgranularaspossibleandrestrictaccesstotheAPI.Inour
application,wejustdeclaredtwogenericreadandwritescopes.Finally,weprovidethe
QuickPoll_Resourcesresourceid.Resourceidisanarbitrarystringthatrepresenta
setofresourcesthataclientcanrequestaccess.Thisconcludestheauthorizationserver
componentimplementation.SpringSecurity’ssensibledefaultswillcreateanin-memory
tokenstoreforthisauthorizationservertouse.
Wenowmoveontoimplementingtheresourceservercomponent.Followingthe
authorizationserverconvention,SpringSecurityprovidesthe
@EnableResourceServerannotationand
ResourceServerConfigurerAdapterclassthatsimplifytheresourceserver
implementation.TheEnableResourceServerannotationenablesaSpringSecurity
filternamedOAuth2AuthenticationProcessingFilterthatauthenticates
requestsusingapassedinOAuth2token.The
ResourceServerConfigurerAdapterprovidesmethodsthatcanbeusedto
accessrulestosecureresourcesusingHttpSecurityclass.Listing8-18showsthe
resourceimplementationforourQuickPollapplication.
Listing8-18.QuickPollresourceserver
packagecom.apress;
importorg.springframework.context.annotation.Configuration;
import
org.springframework.security.config.annotation.web.builders.HttpS
import
org.springframework.security.oauth2.config.annotation.web.configu
import
org.springframework.security.oauth2.config.annotation.web.configu
import
org.springframework.security.oauth2.config.annotation.web.configu
@Configuration
@EnableResourceServer
publicclassOAuth2ResourceServerConfigextends
ResourceServerConfigurerAdapter{
@Override
publicvoidconfigure(ResourceServerSecurityConfigurer
resources){
resources.resourceId("QuickPoll_Resources");
}
@Override
publicvoidconfigure(HttpSecurityhttp)throws
Exception{
http
.requestMatchers().antMatchers("/oauth2/v3/polls/**")
.and()
.authorizeRequests().antMatchers("/oauth2/v3/polls/**").authentic
}
}
IntheOAuth2ResourceServerConfigimplementation,weusethe
ResourceServerSecurityConfigurerclasstocreatearesourceidwiththename
QuickPoll_Resources.Thenameofthisresourceidmustmatchthenamethatwe
usedintheauthorizationserver.Inthesecondconfigmethodweusethepassed-in
HttpSecurityinstancetosecurethe/oauth2/v3/polls/URIandonlyallow
authenticateduserstoaccessv3Pollresources.
Thisconcludestheconfigurationrequiredtosetuptheauthorizationandresource
servers.
TestingQuickPollOAuth2.0Implementation
SpringSecurityOAuth2moduleautomaticallysecuresthe/oauth/tokenendpoint
usingHTTPBasicauthenticationonclient’scredentials.BecauseourQuickPolliOSclient
isalreadyregisteredwiththeauthorizationserver,wecangenerateatokenfortheuser
mickeybyrunningthefollowingcurlcommand:
curl-uquickpolliOSClient:top_secret-XPOST
http://localhost:8080/oauth/token-H“Accept:
application/json”-d
“username=mickey&password=cheese&grant_type=password”
ThecommandcreatesaPOSTrequesttothe/oauth/tokenendpointandpasses
theclientcredentialsforHTTPBasicauthentication.ThePOSTrequestpayloadcontains
theresourcesuser’susernameandpasswordandtherequestedgranttype.Onreceivingthe
request,theauthorizationserverwillgeneratethefollowingresponsewithanaccess
token:
{"access_token":"77ed953e-b3b6-4ea1-820e2e9acc702293","token_type":"bearer","expires_in":42874,"scope":"r
write"}
Fromthisresponse,youwillnoticethatthereturnedtokentypeissettobearer.The
RFC-6750specifiestheusageofbearertokenstoaccessresourcesinanOAuth2.0
environment.Asthenamesuggests,abearertokenallowsthepossessingpartytoaccess
theprotectedresource.Thus,ifthebearertokeniscompromised,itispossibleforathird
partytoaccesstheresource.Theexpires_invalueindicatesthetoken’slifetimein
seconds.
Oncetheclientreceivestheaccesstoken,itcanpresentittoaresourceserverusing
theAuthorizationHTTPheaderviathefollowingformat:
Authorization:Bearer[AccessToken]
ThefollowingcURLcommandrequeststhe/oauth2/v3/pollsresourceusing
thetokenthatweobtainedinthepreviousstep:
curlhttp://localhost:8080/oauth2/v3/polls-H
"Authorization:Bearer77ed953e-b3b6-4ea1-820e-2e9acc702293"
NoteThegeneratedOAuth2accesstokensdifferfromonemachinetoanotherand
alsoexpireaftercertaintime.Makesurethatyougenerateanaccesstokenanduseitto
retrievethePollresourceinsteadofcopyingthistoken.
SpringSecurityusesthesubmittedtokentoretrievetheuserfromthebackend
databaseandcreatesauthoritiesbasedontherolesstoredinthedatabase.Hence,
submittingarequesttodeleteaPOSTbytheusermickey’stokenwouldresultinanaccess
deniederror,asshownhere:
curl-XDELETEhttp://localhost:8080/oauth2/v3/polls/11-H
"Authorization:Bearer77ed953e-b3b6-4ea1-820e-2e9acc702293"
{"error":"access_denied","error_description":"Accessis
denied"}
Now,let’sgenerateatokenfortheadminuserbyrunningthecURLcommand:
curl-uquickpolliOSClient:top_secret-XPOST
http://localhost:8080/oauth/token-H"Accept:
application/json"-d
"username=admin&password=admin&grant_type=password"
Onreceivingtheresponse,copythereturnedaccesstokenandsubmitthefollowing
cURLcommandtodeletethePollwithid11:
curl-XDELETEhttp://localhost:8080/oauth2/v3/polls/11-H
"Authorization:Bearer946e1f71-21c3-4923-bf96-3b0f8cea708a"
Youwillnoticethatthecommandransuccessfullyandthepollwasdeleted.Finally,to
ensurethattheBasicauthenticationisworking,runthefollowingcURLcommandto
retrievepolls:
curl-umickey:cheesehttp://localhost:8080/v3/polls
Inthissection,wehavelookedatimplementingaOAuth2passwordgranttypeusing
SpringSecurity.Thisimplementationcanbeeasilyextendedtoincludeothergranttypes
orpluginapersistenttokenstore.
Summary
Securityisanimportantaspectofanyenterpriseapplication.Inthischapter,wereviewed
strategiesforsecuringRESTservices.WealsotookadeeperlookintoOAuth2and
revieweditsdifferentcomponents.WethenusedSpringSecuritytoimplementBasic
AuthenticationandOAuth2inourQuickPollapplication.Inthenextchapter,wewilluse
Spring’sRestTemplatetobuildRESTclients.WewillalsousetheSpringMVCTest
frameworktoperformunitandintegrationtestingonRESTcontrollers.
CHAPTER9
ClientsandTesting
Inthischapterwewilldiscuss:
BuildingclientsusingRestTemplate
SpringTestframeworkbasics
UnittestingMVCcontrollers
IntegrationtestingMVCcontrollers
WehavelookedatbuildingRESTservicesusingSpring.Inthischapterwewilllook
atbuildingclientsthatconsumetheseRESTservices.WewillalsoexaminetheSpring
Testframeworkthatcanbeusedtoperformunitandend-to-endtestingofRESTservices.
QuickPollJavaClient
ConsumingRESTservicesinvolvesbuildingaJSONorXMLrequestpayload,
transmittingthepayloadviaHTTP/HTTPS,andconsumingthereturnedJSONresponse.
ThisflexibilityopensdoorstonumerousoptionsforbuildingRESTclientsinJava(or,as
amatteroffact,anytechnology).AstraightforwardapproachforbuildingaJavaREST
clientistousecoreJDKlibraries.Listing9-1showsanexampleofaclientreadingaPoll
usingtheQuickPollRESTAPI.
Listing9-1.ReadingapollusingJavaURLClass
publicvoidreadPoll(){
HttpURLConnectionconnection=null;
BufferedReaderreader=null;
try{
URLrestAPIUrl=new
URL("http://localhost:8080/v1/polls/1");
connection=(HttpURLConnection)
restAPIUrl.openConnection();
connection.setRequestMethod("GET");
//Readtheresponse
reader=newBufferedReader(new
InputStreamReader(connection.getInputStream()));
StringBuilderjsonData=newStringBuilder();
Stringline;
while((line=reader.readLine())!=null){
jsonData.append(line);
}
System.out.println(jsonData.toString());
}
catch(Exceptione){
e.printStackTrace();
}
finally{
//Cleanup
IOUtils.closeQuietly(reader);
if(connection!=null)
connection.disconnect();
}
}
AlthoughthereisnothingwrongwiththeapproachinListing9-1,thereisalotof
boilerplatecodethatneedstobewrittentoperformasimpleRESToperation.The
readPollmethodwouldgrowevenbiggerifweweretoincludethecodetoparsethe
JSONresponse.Springabstractsthisboilerplatecodeintotemplatesandutilityclassesand
makesiteasytoconsumeRESTservices.
RestTemplate
CentraltoSpring’ssupportforbuildingRESTclientsisthe
org.springframework.web.client.RestTemplate.TheRestTemplate
takescareofthenecessaryplumbingneededtocommunicatewithRESTservicesand
automaticallymarshals/unmarshalsHTTPrequestandresponsebodies.The
RestTemplatelikeSpring’sotherpopularhelperclassessuchasJdbcTemplateand
JmsTemplate,isbasedontheTemplateMethoddesignpattern.1
TheRestTemplateandassociatedutilityclassesarepartofthespringweb.jarfile.IfyouarebuildingastandaloneRESTclientusingRestTemplate,you
needtoaddthespring-webdependency,showninListing9-2,toyourpom.xmlfile.
Listing9-2.spring-web.jardependency
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
RestTemplateprovidesconvenientmethodstoperformAPIrequestsusingsix
commonlyusedHTTPmethods.Inthenextsections,wewilllookatsomeofthese
functionsalongwithagenericyetpowerfulexchangemethodtobuildQuickPollclients.
NoteInthischapterwewillcontinuebuildingontheworkthatwedidonthe
QuickPollapplicationinpreviouschapters.Alternatively,astarterprojectinsidethe
Chapter9\starterfolderofthedownloadedsourcecodeisavailableforyoutouse.
ThecompletedsolutionisavailableundertheChapter9\finalfolder.Pleasereferto
thissolutionforcompletelistingscontaininggetters/settersandadditionalimports.
GettingPolls
RestTemplateprovidesagetForObjectmethodtoretrieverepresentationsusing
theGETHTTPmethod.Listing9-3showsthethreef lavorsofthegetForObject
method.
Listing9-3.getForObjectmethodflavors
public<T>TgetForObject(Stringurl,Class<T>responseType,
Object…urlVariables)throwsRestClientException{}
public<T>TgetForObject(Stringurl,Class<T>responseType,
Map<String,?>urlVariables)throwsRestClientException
public<T>TgetForObject(URIurl,Class<T>responseType)
throwsRestClientException
ThefirsttwomethodsacceptaURItemplatestring,areturnvaluetype,andURI
variablesthatcanbeusedtoexpandtheURItemplate.Thethirdflavoracceptsafully
formedURIandreturnvaluetype.RestTemplateencodesthepassed-inURItemplates
and,hence,iftheURIisalreadyencodedyoumustusethethirdmethodflavor.Otherwise,
itwillresultindoubleencodingoftheURI,causingmalformedURIerrors.
TheListing9-4showstheQuickPollClientclassandtheusageof
getForObjectmethodtoretrieveaPollforagivenpollid.TheQuickPollClient
isplacedunderthecom.apress.clientpackageofourQuickPollapplicationandis
interactingwiththefirstversionofourQuickPollAPI.Intheupcomingsections,wewill
createclientsthatinteractwithsecondandthirdversionsoftheAPI.RestTemplateis
threadsafeand,hence,wecreatedaclass-levelRestTemplateinstancetobeusedby
allclientmethods.BecausewehavespecifiedthePoll.classasthesecondparameter,
RestTemplateusesHTTPmessageconvertersandautomaticallyconvertstheHTTP
responsecontentintoaPollinstance.
Listing9-4.QuickPollClientandgetForObjectusage
packagecom.apress.client;
importorg.springframework.web.client.RestTemplate;
importcom.apress.domain.Poll;
publicclassQuickPollClient{
privatestaticfinalStringQUICK_POLL_URI_V1
="http://localhost:8080/v1/polls";
privateRestTemplaterestTemplate=new
RestTemplate();
publicPollgetPollById(LongpollId){
return
restTemplate.getForObject(QUICK_POLL_URI_V1+"/{pollId}",
Poll.class,pollId);
}
}
ThislistingdemonstratesthepowerofRestTemplate.Ittookaboutadozenlines
inListing9-1,butwewereabletoaccomplishthatandmorewithacoupleoflinesusing
RestTemplate.ThegetPollByIdmethodcanbetestedwithasimplemainmethod
inQuickPollClientclass:
publicstaticvoidmain(String[]args){
QuickPollClientclient=newQuickPollClient();
Pollpoll=client.getPollById(1L);
System.out.println(poll);
}
NoteEnsurethatyouhavetheQuickPollapplicationupandrunningbeforeyourunthe
mainmethod.
RetrievingaPollcollectionresourceisalittletrickierasproviding
List<Poll>.classasareturnvaluetypetothegetForObjectwouldresultin
compilationerror.Oneapproachistosimplyspecifythatweareexpectingacollection:
ListallPolls
=restTemplate.getForObject(QUICK_POLL_URI_V1,List.class);
However,becauseRestTemplatecan’tautomaticallyguesstheJavaclasstypeof
theelements,itwoulddeserializeeachJSONobjectinthereturnedcollectionintoa
LinkedHashMap.Hence,thecallreturnsallofourPollsasacollectionoftype
List<LinkedHashMap>.
Toaddressthisissue,Springprovidesa
org.springframework.core.ParameterizedTypeReferenceabstractclass
thatcancaptureandretaingenerictypeinformationatruntime.So,tospecifythefactthat
weareexpectingalistofPollinstances,wecreateasubclassof
ParameterizedTypeReference:
ParameterizedTypeReference<List<Poll>>responseType=new
ParameterizedTypeReference<List<Poll>>(){};
RestTemplateHTTPspecificmethodssuchasgetForObjectdon’ttakea
ParameterizedTypeReferenceastheirparameter.AsshowninListing9-5,we
needtouseRestTemplate’sexchangemethodinconjunctionwith
ParameterizedTypeReference.Theexchangemethodinfersthereturntype
informationfromthepassed-inresponseTypeparameterandreturnsa
ResponseEntityinstance.InvokingthegetBodymethodonResponseEntity
givesusthePollcollection.
Listing9-5.GetAllPollsusingRestTemplate
importorg.springframework.core.ParameterizedTypeReference;
importorg.springframework.http.ResponseEntity;
importorg.springframework.http.HttpMethod;
publicList<Poll>getAllPolls(){
ParameterizedTypeReference<List<Poll>>responseType
=newParameterizedTypeReference<List<Poll>>(){};
ResponseEntity<List<Poll>>responseEntity
=restTemplate.exchange(QUICK_POLL_URI_V1,HttpMethod.GET,
null,responseType);
List<Poll>allPolls=responseEntity.getBody();
returnallPolls;
}
WecanalsoaccomplishsimilarbehaviorwithgetForObjectbyrequesting
RestTemplatetoreturnanarrayofPollinstances:
Poll[]allPolls
=restTemplate.getForObject(QUICK_POLL_URI_V1,
Poll[].class);
CreatingaPoll
RestTemplateprovidestwomethods—postForLocationandpostForObject
toperformHTTPPOSToperationsonaresource.Listing9-6givestheAPIforthetwo
methods.
Listing9-6.RestTemplate’sPOSTsupport
publicURIpostForLocation(Stringurl,Objectrequest,
Object…urlVariables)throwsRestClientException
public<T>TpostForObject(Stringurl,Objectrequest,
Class<T>responseType,Object…uriVariables)throws
RestClientException
ThepostForLocationmethodperformsaHTTPPOSTonthegivenURIand
returnsthevalueoftheLocationheader.AswehaveseeninourQuickPollPOST
implementations,theLocationheadercontainstheURIofthenewlycreatedresource.
ThepostForObjectworkssimilartopostForLocationbutconvertsaresponse
intoarepresentation.TheresponseTypeparameterindicatesthetypeofrepresentation
tobeexpected.
Listing9-7showstheQuickPollClient’screatePollmethodthatcreatesa
newPollusingthepostForLocationmethod.
Listing9-7.CreateapollusingpostForLocation
publicURIcreatePoll(Pollpoll){
returnrestTemplate.postForLocation(
QUICK_POLL_URI_V1,poll);
}
UpdatetheQuickPollClient’smainmethodwiththiscodetotestthe
createPollmethod:
publicstaticvoidmain(String[]args){
QuickPollClientclient=newQuickPollClient();
PollnewPoll=newPoll();
newPoll.setQuestion("Whatisyourfavouratecolor?");
Set<Option>options=newHashSet<>();
newPoll.setOptions(options);
Optionoption1=newOption();
option1.setValue("Red");options.add(option1);
Optionoption2=newOption();
option2.setValue("Blue");options.add(option2);
URIpollLocation=client.createPoll(newPoll);
System.out.println("NewlyCreatedPollLocation"+
pollLocation);
}
PUTMethod
TheRestTemplateprovidestheaptlynamedputmethodtosupportthePUTHTTP
method.Listing9-8showsQuickPollClient’supdatePollmethodthatupdatesa
pollinstance.Noticethattheputmethoddoesn’treturnanyresponseand
communicatesfailuresbythrowingRestClientExceptionoritssubclasses.
Listing9-8.Updateapollusingput
publicvoidupdatePoll(Pollpoll){
restTemplate.put(QUICK_POLL_URI_V1
+"/{pollId}",poll,poll.getId());
}
DELETEMethod
TheRestTemplateprovidesthreeoverloadeddeletemethodstosupportDELETE
HTTPoperations.ThedeletemethodsfollowssemanticssimilartoPUTanddoesn’t
returnavalue.TheycommunicateanyexceptionsviaRestClientExceptionorits
subclasses.Listing9-9showsthedeletePollmethodimplementationin
QuickPollClientclass.
Listing9-9.Deleteapoll
publicvoiddeletePoll(LongpollId){
restTemplate.delete(QUICK_POLL_URI_V1
+"/{pollId}",pollId);
}
HandlingPagination
InversiontwoofourQuickPollAPI,weintroducedpaging.So,theclientsupgradingto
versiontwoneedtoreimplementthegetAllPollsmethod.Allotherclientmethods
willremainunchanged.
ToreimplementthegetAllPolls,ourfirstinstinctwouldbetosimplypassthe
org.springframework.data.domain.PageImplastheparameterizedtype
reference:
ParameterizedTypeReference<PageImpl<Poll>>responseType
=newParameterizedTypeReference<PageImpl<Poll>>(){};
ResponseEntity<PageImpl<Poll>>responseEntity
=restTemplate.exchange(QUICK_POLL_URI_2,HttpMethod.GET,
null,responseType);
PageImpl<Poll>allPolls=responseEntity.getBody();
ThePageImplisaconcreteimplementationofthe
org.springframework.data.domain.Pageinterfaceandcanholdallofthe
pagingandsortinginformationreturnedbytheQuickPollRESTAPI.Theonlyproblem
withthisapproachisthatPageImpldoesn’thaveadefaultconstructorandSpring’s
HTTPmessageconverterwouldfailwiththefollowingexception:
CouldnotreadJSON:Nosuitableconstructorfoundfortype
[simpletype,class
org.springframework.data.domain.PageImpl<com.apress.domain.Poll>]
cannotinstantiatefromJSONobject(needtoadd/enable
typeinformation?)
TohandlepaginationandsuccessfullymapJSONtoanobject,wewillcreateaJava
classthatmimicsPageImplclassbutalsohasadefaultconstructor,asshowninListing
9-10.
Listing9-10.PageWrapperclass
packagecom.apress.client;
importjava.util.List;
importorg.springframework.data.domain.Sort;
publicclassPageWrapper<T>{
privateList<T>content;
privateBooleanlast;
privateBooleanfirst;
privateIntegertotalPages;
privateIntegertotalElements;
privateIntegersize;
privateIntegernumber;
privateIntegernumberOfElements;
privateSortsort;
//GettersandSettersremovedforbrevity
}
NoteThereareoccasionswhenyouneedtogenerateJavatypesfromJSON.Thisis
especiallytrueforAPIsthatdon’tprovideaJavaclientlibrary.Theonlinetool
www.jsonschema2pojo.orgprovidesaconvenientwaytogenerateJavaPOJOs
fromJSONschemaorJSONdata.
ThePageWrapperclasscanholdthereturnedcontentandhasattributestoholdthe
paginginformation.Listing9-11showstheQuickPollClientV2classthatmakesuse
ofPageWrappertointeractwithsecondversionofAPI.Noticethatthe
getAllPollsmethodnowtakestwoparameters:pageandsize.Thepage
parameterdeterminestherequestedpagenumberandsizeparameterdeterminesthe
numberofelementstobeincludedinthepage.Thisimplementationcanbefurther
enhancedtoacceptsortparametersandprovidesortingfunctionality.
Listing9-11.QuickPollClientforversiontwo
packagecom.apress.client;
importorg.springframework.core.ParameterizedTypeReference;
importorg.springframework.http.HttpMethod;
importorg.springframework.http.ResponseEntity;
importorg.springframework.web.client.RestTemplate;
importorg.springframework.web.util.UriComponentsBuilder;
importcom.apress.domain.Poll;
publicclassQuickPollClientV2{
privatestaticfinalStringQUICK_POLL_URI_2
="http://localhost:8080/v2/polls";
privateRestTemplaterestTemplate=new
RestTemplate();
publicPageWrapper<Poll>getAllPolls(intpage,int
size){
ParameterizedTypeReference<PageWrapper<Poll>>
responseType=new
ParameterizedTypeReference<PageWrapper<Poll>>(){};
UriComponentsBuilderbuilder
=UriComponentsBuilder
.fromHttpUrl(QUICK_POLL_URI
.queryParam("page",
page)
.queryParam("size",
size);
ResponseEntity<PageWrapper<Poll>>
responseEntity
=restTemplate.exchange(builder.build().toUri(),
HttpMethod.GET,null,responseType);
returnresponseEntity.getBody();
}
}
HandlingBasicAuthentication
UptothispointwehavecreatedclientsforthefirstandsecondversionofQuickPollAPI.
InChapter8,wesecuredthethirdversionoftheAPIandanycommunicationwiththat
versionrequiresBasicAuthentication.Forexample,runningadeletemethodonURI
http://localhost:8080/v3/polls/3withoutanyauthenticationwouldresult
inanHttpClientErrorExceptionwitha401statuscode.
TosuccessfullyinteractwithourQuickPollv3API,weneedtoprogrammaticallybase
64encodeauser’scredentialsandconstructanauthorizationrequestheader.Listing
9-12showssuchimplementation:weconcatenatethepassed-inusernameandpassword.
Wethenbase64encodeitandcreateanauthorizationheaderbyprefixingBasic
totheencodedvalue.
Listing9-12.Authenticationheaderimplementation
importorg.apache.tomcat.util.codec.binary.Base64;
importorg.springframework.http.HttpHeaders;
privateHttpHeadersgetAuthenticationHeader(Stringusername,
Stringpassword){
Stringcredentials=username+":"+password;
byte[]base64CredentialData
=Base64.encodeBase64(credentials.getBytes());
HttpHeadersheaders=newHttpHeaders();
headers.set("Authorization","Basic"+new
String(base64CredentialData));
returnheaders;
}
TheRestTemplate’sexchangemethodcanbeusedtoperformaHTTPoperation
andtakesinanauthorizationheader.Listing9-13showsthe
QuickPollClientV3BasicAuthclasswithdeletePollmethodimplementation
usingBasicAuthentication.
Listing9-13.QuickPollClientwithBasicAuth
packagecom.apress.client;
importorg.springframework.http.HttpHeaders;
importorg.springframework.http.HttpMethod;
importorg.springframework.web.client.RestTemplate;
importorg.springframework.http.HttpEntity;
publicclassQuickPollClientV3BasicAuth{
privatestaticfinalStringQUICK_POLL_URI_V3
="http://localhost:8080/v3/polls";
privateRestTemplaterestTemplate=new
RestTemplate();
publicvoiddeletePoll(LongpollId){
HttpHeadersauthenticationHeaders
=getAuthenticationHeader("admin","admin");
restTemplate.exchange(QUICK_POLL_URI_V3
+"/{pollId}",
HttpMethod.DELETE,newHttpEntity<Void>
(authenticationHeaders),Void.class,pollId);
}
}
NoteInthisapproach,wehavemanuallysettheauthenticationheadertoeachrequest.
AnotherapproachistoimplementacustomClientHttpRequestInterceptorthat
interceptseachoutgoingrequestandautomaticallyappendstheheadertoit.
HandlingOAuth2
SpringprovidesaspecializedversionofRestTemplatenamed
OAuth2RestTemplatethatsimplifiesconsumptionofOAuth2protectedservices.The
OAuth2RestTemplaterequiresaninstanceof
OAuth2ProtectedResourceDetailsthatcontainsdetailsofaprotectedresource
suchasclientid,clientsecret,andtokenURI.BecausetheQuickPollAPIusesa
“Password”granttype,wewillbeconstructingtheOAuth2RestTemplateusinga
ResourceOwnerPasswordResourceDetailsinstance,asshowninListing9-14.
Listing9-14.OAuth2RestTemplatecreation
import
org.springframework.security.oauth2.client.OAuth2RestTemplate;
import
org.springframework.security.oauth2.client.token.grant.password.R
privateOAuth2RestTemplaterestTemplate(){
ResourceOwnerPasswordResourceDetailsresourceDetails
=newResourceOwnerPasswordResourceDetails();
resourceDetails.setGrantType("password");
resourceDetails.setAccessTokenUri("http://localhost:8080/oa
resourceDetails.setClientId("quickpolliOSClient");
resourceDetails.setClientSecret("top_secret");
//Setscopes
List<String>scopes=newArrayList<>();
scopes.add("read");scopes.add("write");
resourceDetails.setScope(scopes);
//ResourceOwnerdetails
resourceDetails.setUsername("mickey");
resourceDetails.setPassword("cheese");
returnnewOAuth2RestTemplate(resourceDetails);
}
WebegintherestTemplatemethodimplementationbycreatinganinstanceof
ResourceOwnerPasswordResourceDetails.Wesetthegranttypeto
“password”andprovideitwithclient,scope,andresourceownerdetails.Asyouwill
recollectfromChapter8,thisisthesameinformationthatweprovidedaspartofour
cURLrequests.
TheOAuth2RestTemplateusesthe
ResourceOwnerPasswordResourceDetailsinformationtoacquireorrenewan
accesstoken.Thetemplatebydefaultcachesthistokenina
DefaultOAuth2ClientContextinstance.WhenaRESTAPIcallisperformed
usingtheOAuth2RestTemplate,thetemplatewouldgenerateanauthorization
headerusingtheaccesstokenandautomaticallyaddsittotheoutgoingrequest.Because
thisallhappensbehindthescenes,clientscansimplyusethetemplate’smethodswithout
worryingaboutOAuthdetails.Listing9-15showstheQuickPollClientV3OAuth
classwithgetPollByIdmethodimplementation.Asyoucansee,weareusingthe
restTemplatemethodtoobtainOAuth2Templateinstance.Also,noticethatwe
areinteractingwiththeAPIexposedat/oauth2/v3URI.
Listing9-15.QuickPollclientwithOAuth2
packagecom.apress.client;
import
org.springframework.security.oauth2.client.OAuth2RestTemplate;
import
org.springframework.security.oauth2.client.token.grant.password.R
importcom.apress.domain.Poll;
publicclassQuickPollClientV3OAuth{
privatestaticfinalStringQUICK_POLL_URI_V3
="http://localhost:8080/oauth2/v3/polls";
publicPollgetPollById(LongpollId){
OAuth2RestTemplaterestTemplate
=restTemplate();
return
restTemplate.getForObject(QUICK_POLL_URI_V3+"/{pollId}",
Poll.class,pollId);
}
}
TestingRESTServices
Testingisanimportantaspectofeverysoftwaredevelopmentprocess.Testingcomesin
differentflavorsandinthischapterwewillfocusonunitandintegrationtesting.Unit
testingverifiesthatindividual,isolatedunitsofcodeareworkingasexpected.Itisthe
mostcommontypeoftestingthatdeveloperstypicallyperform.Integrationtesting
typicallyfollowsunittestingandfocusesontheinteractionbetweenpreviouslytested
units.
TheJavaecosystemisfilledwithframeworksthateaseunitandintegrationtesting.
JUnitandTestNGhavebecomethedefactostandardtestframeworksandprovide
foundation/integrationtomostothertestingframeworks.AlthoughSpringsupportsboth
frameworks,wewillbeusingJUnitinthisbook,asitisfamiliartomostreaders.
SpringTest
TheSpringframeworkprovidesthespring-testmodulethatallowsyoutointegrate
Springintotests.Themoduleprovidesarichsetofannotations,utilityclasses,andmock
objectsforenvironmentJNDI,Servlet,andPortletAPI.Theframeworkalsoprovides
capabilitiestocacheapplicationcontextacrosstestexecutionstoimproveperformance.
Usingthisinfrastructure,youcaneasilyinjectSpringbeansandtestfixturesintotests.To
usethespring-testmoduleinanon–SpringBootproject,youneedtoincludetheMaven
dependencyasshowninListing9-16.
Listing9-16.spring-testdependency
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.1.6.RELEASE</version>
<scope>test</scope>
</dependency>
SpringBootprovidesastarterPOMnamedspring-boot-starter-testthat
automaticallyaddsthespring-testmoduletoaBootapplication.Additionally,the
starterPOMbringsinJUnit,Mockito,andHamcrestlibraries:
MockitoisapopularmockingframeworkforJava.Itprovidesa
simpleandeasytouseAPItocreateandconfiguremocks.More
detailsaboutMockitocanbefoundathttp://mockito.org/.
Hamcrestisaframeworkthatprovidesapowerfulvocabularyfor
creatingmatchers.Toputitsimply,amatcherallowsyoutomatchan
objectagainstasetofexpectations.Matchersimprovethewaythatwe
writeassertionsbymakingthemmorehuman-readable.Theyalso
generatemeaningfulfailuremessageswhenassertionsarenotmet
duringtesting.YoucanlearnmoreaboutHamcrestat
http://hamcrest.org/.
Tounderstandthespring-testmodule,let’sexamineatypicaltestcase.Listing917showsasampletestbuiltusingJUnitandspring-testinfrastructure.
Listing9-17.SampleJUnitTest
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes
=QuickPollApplication.class)
@WebAppConfiguration
publicclassExampleTest{
@Before
publicvoidsetup(){}
@Test
publicvoidtestSomeThing(){}
@After
publicvoidteardown(){}
}
Ourexampletestcontainsthreemethods—setup,testSomeThing,and
teardowneachannotatedwithaJUnitannotation.The@Testannotationdenotesthe
testSomeThingasaJUnittestmethod.Thismethodwillcontaincodethatensuresthat
ourproductioncodeworksasexpected.The@BeforeannotationinstructsJUnittorun
thesetupmethodpriortoanytestmethodexecution.Methodsannotatedwith@Before
canbeusedforsettinguptestfixturesandtestdata.Similarly,the@Afterannotation
instructsJUnittoruntheteardownmethodafteranytestmethodexecution.Methods
annotatedwith@Afteraretypicallyusedtoteardowntestfixturesandperformcleanup
operations.
JUnitusesthenotionoftestrunnertoperformtestexecution.Bydefault,JUnituses
theBlockJUnit4ClassRunnertestrunnertoexecutetestmethodsandassociated
lifecycle(@Beforeor@Afteretc.)methods.The@RunWithannotationallowsyouto
alterthisbehavior.Inourexample,usingthe@RunWithannotation,weareinstructing
JUnittousetheSpringJUnit4ClassRunnerclasstorunthetestcases.
TheSpringJUnit4ClassRunneraddsSpringintegrationbyperforming
activitiessuchasloadingapplicationcontext,injectingautowireddependencies,and
runningspecifiedtestexecutionlisteners.ForSpringtoloadandconfigureanapplication
context,itneedsthelocationsoftheXMLcontextfilesorthenamesoftheJava
configurationclasses.Wetypicallyusethe@ContextConfigurationannotationto
providethisinformationtotheSpringJUnit4ClassRunnerclass.
Inourexample,however,weusetheSpringApplicationConfiguration,a
specializedversionofthestandardContextConfigurationthatprovidesadditional
SpringBootfeatures.Finally,the@WebAppConfigurationannotationinstructs
SpringtocreatetheWebversionoftheapplicationcontext,namely,
WebApplicationContext.
UnitTestingRESTControllers
Spring’sdependencyinjectionmakesunittestingeasier.Dependenciescanbeeasily
mockedorsimulatedwithpredefinedbehavior,therebyallowingustozoominandtest
codeinisolation.Traditionally,unittestingSpringMVCcontrollersfollowedthis
paradigm.Forexample,Listing9-18showsthecodeunittestingPollController’s
getAllPollsmethod.
Listing9-18.UnittestingPollControllerwithmocks
importstaticorg.junit.Assert.assertEquals;
importstaticorg.mockito.Mockito.when;
importstaticorg.mockito.Mockito.times;
importstaticorg.mockito.Mockito.verify;
importjava.util.ArrayList;
importcom.google.common.collect.Lists;
importorg.junit.Before;
importorg.junit.Test;
importorg.mockito.Mock;
importorg.mockito.MockitoAnnotations;
importorg.springframework.http.HttpStatus;
importorg.springframework.http.ResponseEntity;
importorg.springframework.test.util.ReflectionTestUtils;
publicclassPollControllerTestMock{
@Mock
privatePollRepositorypollRepository;
@Before
publicvoidsetUp()throwsException{
MockitoAnnotations.initMocks(this);
}
@Test
publicvoidtestGetAllPolls(){
PollControllerpollController=new
PollController();
ReflectionTestUtils.setField(pollController,
"pollRepository",pollRepository);
when(pollRepository.findAll()).thenReturn(new
ArrayList<Poll>());
ResponseEntity<Iterable<Poll>>allPollsEntity
=pollController.getAllPolls();
verify(pollRepository,times(1)).findAll();
assertEquals(HttpStatus.OK,
allPollsEntity.getStatusCode());
assertEquals(0,
Lists.newArrayList(allPollsEntity.getBody()).size());
}
}
ThePollControllerTestMockimplementationusestheMockito’s@Mock
annotationtomockPollController’sonlydependency:PollRepository.For
MockitotoproperlyinitializetheannotatedpollRepositoryproperty,weeitherneed
torunthetestusingtheMockitoJUnitRunnertestrunnerorinvoketheinitMocks
methodintheMockitoAnnotations.Inourtest,wechoosethelatterapproachand
calltheinitMocksinthe@Beforemethod.
InthetestGetAllPollsmethod,wecreateaninstanceofPollController
andinjectthemockPollRepositoryusingSpring’sReflectionTestUtils
utilityclass.ThenweuseMockito’swhenandthenReturnmethodstosetthe
PollRepositorymock’sbehavior.Hereareweindicatingthatwhenthe
PollRepository’sfindAll()methodisinvoked,anemptycollectionshouldbe
returned.Finally,weinvokethegetAllPollsmethodandverifyfindAll()
method’sinvocationandassertcontroller’sreturnvalue.
Inthisstrategy,wetreatthePollControllerasaPOJO,andhencedon’ttestthe
controller’srequestmappings,validations,databindings,andanyassociatedexception
handlers.Startingfromversion3.2,spring-testmoduleincludesaSpringMVCTest
frameworkthatallowsustotestacontrollerasacontroller.Thistestframeworkwillload
theDispatcherServletandassociatedWebcomponentssuchascontrollersand
viewresolversintotestcontext.ItthenusestheDispatcherServlettoprocessall
therequestsandgeneratesresponsesasifitisrunninginaWebcontainerwithoutactually
startingupaWebserver.ThisallowsustoperformamorethoroughtestingofSpring
MVCapplications.
SpringMVCTestframeworkBasics
TogainabetterunderstandingoftheSpringMVCTestframework,weexploreitsfour
importantclasses:MockMvc,MockMvcRequestBuilders,
MockMvcResultMatchers,andMockMvcBuilders.Asevidentfromtheclass
names,theSpringMVCTestframeworkmakesheavyuseofBuilderPattern.2
Centraltothetestframeworkisthe
org.springframework.test.web.servlet.MockMvcclass,whichcanbe
usedtoperformHTTPrequests.Itcontainsonlyonemethodnamedperformandhasthe
followingAPIsignature:
publicResultActionsperform(RequestBuilderrequestBuilder)
throwsjava.lang.Exception
TheRequestBuilderparameterprovidesanabstractiontocreatetherequest
(GET,POST,etc.)tobeexecuted.Tosimplifyrequestconstruction,theframework
providesan
org.springframework.test.web.servlet.request.MockHttpServletReque
implementationandasetofhelperstaticmethodsinthe
org.springframework.test.web.servlet.request.MockMvcRequestBuilde
class.Listing9-19givesanexampleofaPOSTHTTPrequestconstructedusingthe
previouslymentionedclasses.
Listing9-19.POSTHTTPrequest
post("/test_uri")
.param("admin","false")
.accept(MediaType.APPLICATION_JSON)
.content("{JSON_DATA}");
ThepostmethodispartoftheMockMvcRequestBuildersclassandisusedto
createaPOSTrequest.TheMockMvcRequestBuildersalsoprovidesadditional
methodssuchasget,delete,andputtocreatecorrespondingHTTPrequests.The
parammethodispartoftheMockHttpServletRequestBuilderclassandisused
toaddaparametertotherequest.TheMockHttpServletRequestBuilder
providesadditionalmethodssuchasaccept,content,cookie,andheadertoadd
dataandmetadatatotherequestbeingconstructed.
Theperformmethodreturnsan
org.springframework.test.web.servlet.ResultActionsinstancethat
canbeusedtoapplyassertions/expectationsontheexecutedresponse.Listing9-20shows
threeassertionsappliedtotheresponseofasamplePOSTrequestusing
ResultActions’sandExpectmethod.Thestatusisastaticmethodin
org.springframework.test.web.servlet.result.MockMvcResultMatchers
thatallowsyoutoapplyassertionsonresponsestatus.ItsisOkmethodassertsthatthe
statuscodeis200(HTTPStatus.OK).Similarly,thecontentmethodin
MockMvcResultMatchersprovidesmethodstoassertresponsebody.Hereweare
assertingthattheresponsecontenttypeisoftype“application/json”andmatchesan
expectedstring“JSON_DATA”.
Listing9-20.ResultActions
mockMvc.perform(post("/test_uri"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(content().string("{JSON_DATA}"));
SofarwehavelookedatusingMockMvctoperformrequestsandasserttheresponse.
BeforewecanuseMockMvc,weneedtoinitializeit.TheMockMvcBuildersclass
providesthefollowingtwomethodstobuildaMockMvcinstance:
webAppContextSetup—BuildsaMockMvcinstanceusingafully
initializedWebApplicationContext.TheentireSpring
configurationassociatedwiththecontextisloadedbeforeMockMvc
instanceiscreated.Thistechniqueisusedforend-to-endtesting.
standaloneSetup—BuildsaMockMvcwithoutloadingany
Springconfiguration.OnlythebasicMVCinfrastructureisloadedfor
testingcontrollers.Thistechniqueisusedforunittesting.
UnitTestingUsingSpringMVCTestFramework
NowthatwehavereviewedtheSpringMVCTestframework,let’slookatusingittotest
RESTcontrollers.ThePollControllerTestclassinListing9-21demonstrates
testingthegetPollsmethod.Tothe@ContextConfigurationannotationwepass
inaMockServletContextclassinstructingSpringtosetupanempty
WebApplicationContext.AnemptyWebApplicationContextallowsusto
instantiateandinitializetheonecontrollerthatwewanttotestwithoutloadingupthe
entireapplicationcontext.Italsoallowsustomockthedependenciesthatthecontroller
requires.
Listing9-21.UnittestingwithSpringMVCtest
packagecom.apress.unit;
importstaticorg.mockito.Mockito.when;
importstatic
org.springframework.test.web.servlet.request.MockMvcRequestBuilde
importstatic
org.springframework.test.web.servlet.result.MockMvcResultMatchers
importstatic
org.springframework.test.web.servlet.result.MockMvcResultMatchers
importstatic
org.springframework.test.web.servlet.setup.MockMvcBuilders.standa
importorg.mockito.InjectMocks;
importorg.mockito.Mock;
importorg.mockito.MockitoAnnotations;
import
org.springframework.boot.test.SpringApplicationConfiguration;
importorg.springframework.mock.web.MockServletContext;
importorg.springframework.test.web.servlet.MockMvc;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes
=QuickPollApplication.class)
@ContextConfiguration(classes=MockServletContext.class)
@WebAppConfiguration
publicclassPollControllerTest{
@InjectMocks
PollControllerpollController;
@Mock
privatePollRepositorypollRepository;
privateMockMvcmockMvc;
@Before
publicvoidsetUp()throwsException{
MockitoAnnotations.initMocks(this);
mockMvc
=standaloneSetup(pollController).build();
}
@Test
publicvoidtestGetAllPolls()throwsException{
when(pollRepository.findAll()).thenReturn(new
ArrayList<Poll>());
mockMvc.perform(get("/v1/polls"))
.andExpect(status().isOk())
.andExpect(content().string("[]"));
}
}
Inthiscase,wewanttotestversiononeofourPollControllerAPI.Sowe
declareapollControllerpropertyandannotateitwith@InjectMocks.During
runtime,Mockitoseesthe@InjectMocksannotationandwillcreateaninstanceofthe
import
com.apress.v1.controller.PollController.PollController.Itthen
injectsitwithanymocksdeclaredinthePollControllerTestclassusing
constructor/fieldorsetterinjection.Theonlymockwehaveintheclassisthe
PollRepository.
Inthe@Beforeannotatedmethod,weusetheMockMvcBuilders’s
standaloneSetup()methodtoregisterthepollControllerinstance.The
standaloneSetup()automaticallycreatestheminimuminfrastructurerequiredby
theDispatcherServlettoserverequestsassociatedwiththeregisteredcontrollers.
TheMockMvcinstancebuiltbystandaloneSetupisstoredinaclass-levelvariableand
madeavailabletotests.
InthetestGetAllPollsmethod,weuseMockitotoprogramthe
PollRepositorymock’sbehavior.ThenweperformaGETrequestonthe
/v1/pollsURIandusethestatusandcontentassertionstoensurethatanempty
JSONarrayisreturned.Thisisthebiggestdifferencefromtheversionthatwesawin
Listing9-18.ThereweweretestingtheresultofaJavamethodinvocation.Hereweare
testingtheHTTPresponsethattheAPIgenerates.
IntegrationTestingRESTControllers
Intheprevioussection,welookedatunittestingacontrolleranditsassociated
configuration.However,thistestingislimitedtoaWeblayer.Therearetimeswhenwe
wanttotestallofthelayersofanapplicationfromcontrollerstothepersistentstore.Inthe
past,writingsuchtestsrequiredlaunchingtheapplicationinanembeddedTomcatorJetty
serveranduseaframeworksuchasHtmlUnitorRestTemplatetotriggerHTTP
requests.Dependingonanexternalservletcontainercanbecumbersomeandoftenslows
downtesting.
TheSpringMVCTestframeworkprovidesalightweight,outofthecontainer
alternativeforintegrationtestingMVCapplications.Inthisapproach,theentireSpring
applicationcontextalongwiththeDispatcherServletandassociatedMVC
infrastructuregetsloaded.AmockedMVCcontainerismadeavailabletoreceiveand
executeHTTPrequests.Weinteractwithrealcontrollersandthesecontrollersworkwith
realcollaborators.Tospeedupintegrationtestingcomplexservicesaresometimes
mocked.Additionally,thecontextisusuallyconfiguredsuchthattheDAO/repository
layerinteractswithanin-memorydatabase.
Thisapproachissimilartotheapproachweusedforunittestingcontrollers,exceptfor
thesethreedifferences:
TheentireSpringcontextgetsloadedasopposedtoanemptycontext
intheunittestingcase
AllRESTendpointsareavailableasopposedtotheonesconfigured
viastandaloneSetup
Testsareperformedusingrealcollaboratorsagainstin-memory
databaseasopposedtomockingdependency’sbehavior
AnintegrationtestforthePollController’sgetAllPollsmethodisshownin
Listing9-22.ThePollControllerITclassissimilartothe
PollControllerTestthatwelookedatearlier.Afullyconfiguredinstanceof
WebApplicationContextisinjectedintothetest.Inthe@Beforemethodweuse
thisWebApplicationContextinstancetobuildaMockMvcinstanceusing
MockMvcBuilders’swebAppContextSetup.
Listing9-22.IntegrationtestingwithSpringMVCTest
packagecom.apress.it;
importstaticorg.hamcrest.Matchers.hasSize;
importstatic
org.springframework.test.web.servlet.request.MockMvcRequestBuilde
importstatic
org.springframework.test.web.servlet.result.MockMvcResultMatchers
importstatic
org.springframework.test.web.servlet.result.MockMvcResultMatchers
importstatic
org.springframework.test.web.servlet.setup.MockMvcBuilders.webApp
import
org.springframework.boot.test.SpringApplicationConfiguration;
import
org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import
org.springframework.test.context.web.WebAppConfiguration;
importorg.springframework.test.web.servlet.MockMvc;
import
org.springframework.web.context.WebApplicationContext;
importcom.apress.QuickPollApplication;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes
=QuickPollApplication.class)
@WebAppConfiguration
publicclassPollControllerIT{
@Inject
privateWebApplicationContextwebApplicationContext;
privateMockMvcmockMvc;
@Before
publicvoidsetup(){
mockMvc
=webAppContextSetup(webApplicationContext).build();
}
@Test
publicvoidtestGetAllPolls()throwsException{
mockMvc.perform(get("/v1/polls"))
.andExpect(status().isOk())
.andExpect(jsonPath("$",hasSize(20)));
}
}
ThetestGetAllPollsmethodimplementationusestheMockMvcinstanceto
performaGETrequestonthe/v1/pollsendpoint.Weusetwoassertionstoensurethat
theresultiswhatweexpect:
TheisOKassertionensuresthatwegetastatuscode200.
ThejsonPathmethodallowsustowriteassertionsagainstresponse
bodyusingJsonPathexpression.TheJsonPath
(http://goessner.net/articles/JsonPath/)providesa
convenientwaytoextractpartsofaJSONdocument.Toputitsimply,
JsonPathistoJSONiswhatXPathistoXML.
Inourtestcase,weusetheHamcrest’shasSizematchertoassertthattheretuned
JSONcontains20polls.Theimport.sqlscriptusedtopopulatethein-memory
databasecontains20pollentries.Hence,ourassertionusesthemagicnumber20for
comparison.
Summary
SpringprovidespowerfultemplateandutilityclassesthatsimplifyRESTclient
development.Inthischapter,wereviewedRestTemplateandusedittoperformclient
operationssuchasGET,POST,PUT,andDELETEonresources.Wealsoreviewedthe
SpringMVCTestframeworkanditscoreclasses.Finally,weusedthetestframeworkto
simplifyunitandintegrationtestcreation.
_____________________
1http://en.wikipedia.org/wiki/Template_method_pattern.
2http://en.wikipedia.org/wiki/Builder_pattern.
CHAPTER10
HATEOAS
Inthischapterwewilldiscuss
HATEOAS
JSONhypermediatypes
QuickPollHATEOASimplementation
Consideranyinteractionwithane-commercewebsitesuchasAmazon.com.You
typicallybeginyourinteractionbyvisitingthesite’shomepage.Thehomepagemight
containtext,images,andvideosdescribingdifferentproductsandpromotions.Thepage
alsocontainshyperlinksthatallowyoutonavigatefromonepagetoanotherallowingyou
toreadproductdetails,reviews,andaddproductstoshoppingcarts.Thesehyperlinks
alongwithothercontrolssuchasbuttonsandinputfieldsalsoguideyouthrough
workflowssuchascheckingoutanorder.
EachWebpageintheworkflowpresentsyouwithcontrolstogotothenextsteporgo
backtoaprevioussteporevencompletelyexittheworkflow.Thisisaverypowerful
featureoftheWeb—youasaconsumeruselinkstonavigatethroughresourcesfinding
whatyouneedwithouthavingtorememberalloftheircorrespondingURIs.Youjust
neededtoknowtheinitialURI:http://www.amazon.com.IfAmazonweretogo
througharebrandingexerciseandchangeitsURIsforproductsoraddnewstepsinits
checkoutworkflow,youwillstillbeabletodiscoverandperformalltheoperations.
InthischapterwewillreviewHATEOAS,aconstraintthatallowsustobuildresilient
RESTservicesthatfunctionlikeawebsite.
HATEOAS
TheHypermediaAsTheEngineOfApplicationState,orHATEOAS,isakeyconstraint
ofRESTarchitecture.Theterm“hypermedia”referstoanycontentthatcontainslinksto
otherformsofmediasuchasimages,movies,andtext.Asyouhaveexperienced,theWeb
isaclassicexampleofhypermedia.TheideabehindHATEOASissimple—aresponse
wouldincludelinkstootherresources.Clientswouldusetheselinkstointeractwiththe
serverandtheseinteractionscouldresultinpossiblestatechanges.
Similartoahuman’sinteractionwithawebsite,aRESTclienthitsaninitialAPIURI
andusestheserver-providedlinkstodynamicallydiscoveravailableactionsandaccess
theresourcesitneeds.Theclientneednothavepriorknowledgeoftheserviceorthe
differentstepsinvolvedinaworkflow.Additionally,theclientsnolongerhavetohard
codetheURIstructuresfordifferentresources.ThisallowstheservertomakeURI
changesastheAPIevolveswithoutbreakingtheclients.
TobetterunderstandHATEOAS,considerRESTAPIforahypotheticalblog
application.Anexamplerequesttoretrieveablogpostresourcewithidentifier1andthe
associatedresponseinJSONformatisshownhere:
GET/posts/1HTTP/1.1
Connection:keep-alive
Host:blog.example.com
{
"id":1,
"body":"Myfirstblogpost",
"postdate":"2015-05-30T21:41:12.650Z"
}
Aswewouldexpect,thegeneratedresponsefromtheservercontainsthedata
associatedwiththeblogpostresource.WhenaHATEOASconstraintisappliedtothis
RESTservice,thegeneratedresponsehaslinksembeddedinit.Listing10-1showsan
exampleresponsewithlinks.
Listing10-1.Blogpostwithlinks
{
"id":1,
"body":"Myfirstblogpost",
"postdate":"2015-05-30T21:41:12.650Z",
"links":[
{
"rel":"self",
"href":http://blog.example.com/posts/1,
"method":"GET"
}
]
}
Inthisresponse,eachlinkinthelinksarraycontainsthreeparts:
1. href—ContainstheURIthatyoucanusetoretrievearesourceor
changethestateoftheapplication
2. rel—Describestherelationshipthatthehreflinkhaswiththe
currentresource
3. method—IndicatestheHTTPmethodrequiredtointeractwiththe
URI
Fromthelink’shrefvalue,youcanseethatthisisaself-referencinglink.Therel
elementcancontainarbitrarystringvalues,andinthiscaseithasavalue“self”toindicate
thisself-relationship.AsdiscussedinChapter1,aresourcecanbeidentifiedbymultiple
URIs.Inthosesituations,aself-linkcanbehelpfultohighlightthepreferredcanonical
URI.Inscenariosinwhichpartialresourcerepresentationsarereturned(aspartofa
collection,forexample),includingaself-linkwouldallowaclienttoretrieveafull
representationoftheresource.
Wecanexpandtheblogpostresponsetoincludeotherrelationships.Forexample,
eachblogposthasanauthor,theuserthatcreatedthepost.Eachblogpostalsocontainsa
setofrelatedcommentsandtags.Listing10-2showsanexampleofablogpost
representationwiththeseadditionallinkrelationships.
Listing10-2.Blogpostwithadditionalrelationships
{
"id":1,
"body":"Myfirstblogpost",
"postdate":"2015-05-30T21:41:12.650Z",
"self":"http://blog.example.com/posts/1",
"author":"http://blog.example.com/profile/12345",
"comments"
:"http://blog.example.com/posts/1/comments",
"tags":"http://blog.example.com/posts/1/tags"
}
TheresourcerepresentationinListing10-2takesadifferentapproachanddoesn’tuse
alinksarray.Instead,linkstorelatedresourcesarerepresentedasJSONobject
properties.Forexample,thepropertywithkeyauthorlinkstheblogpostwithits
creator.Similarly,thepropertieswithkeyscommentsandtagslinkthepostwith
relatedcommentsandtagscollectionresources.
WeusedtwodifferentapproachesforembeddingHATEOASlinksinarepresentation
tohighlightalackofstandardizedlinkingwithinaJSONdocument.Inbothscenarios,the
consumingclientscanusetherelvaluetoidentifyandnavigatetotherelatedresources.
Aslongastherelvaluedoesn’tchange,theservercanreleasenewversionsoftheURI
withoutbreakingtheclient.Italsomakesiteasyforconsumingdeveloperstoexplorethe
APIwithoutrelyingonheavydocumentation.
HATEOASDEBATE
TheRESTAPIfortheQuickPollapplicationthatweworkedonsofardoesn’tfollow
theHATEOASprinciples.Thesameappliestomanypublic/open-sourceRESTAPIs
beingconsumedtoday.In2008,RoyFieldingexpressedfrustrationinhisblog
(http://roy.gbiv.com/untangled/2008/rest-apis-must-behypertext-driven)atsuchAPIsbeingcalledRESTfulbutthatarenot
hypermedia-driven:
“WhatneedstobedonetomaketheRESTarchitecturalstyleclearonthenotion
thathypertextisaconstraint?Inotherwords,iftheengineofapplicationstate
(andhencetheAPI)isnotbeingdrivenbyhypertext,thenitcannotbeRESTful
and cannot be a REST API. Period. Is there some broken manual somewhere
thatneedstobefixed?“
Sevenyearslater,thedebatearoundhypermedia’sroleandwhatisconsidered
RESTfulstillcontinues.Theblogosphereisfilledwithmixedopinionsandpeople
takingpassionatestancesonbothsides.Theso-calledhypermediascepticsfeelthat
hypermediaistooacademicandfeelthataddingextralinkswouldbloatthepayload,
andaddsunnecessarycomplexitytosupportclientsthatreallydon’texist.
KinLaneprovidesagoodsummaryofthehypermediadebateinhisblogpost
(http://apievangelist.com/2014/08/05/the-hypermedia-apidebate-sorry-reasonable-just-does-not-sell/).
JSONHypermediaTypes
Toputitsimply,ahypermediatypeisamediatypethatcontainswell-definedsemantics
forlinkingresources.TheHTMLmediatypeisapopularexampleofahypermediatype.
TheJSONmediatypehoweverdoesn’tprovidenativehyperlinkingsemanticsand
thereforeisnotconsideredtobeahypermediatype.Thishasresultedinavarietyof
customimplementationsforembeddinglinksinaJSONdocument.Wehaveseentwo
approachesintheprevioussection.
NoteRESTservicesthatproduceXMLresponsesoftenuseanAtom/AtomPub
(http://en.wikipedia.org/wiki/Atom_(standard))formatforstructuring
HATEOASlinks.
JSONHypermediaTypes
ToaddressthisissueandprovidehyperlinkingsemanticswithinJSONdocuments,several
JSONhypermediatypeshavebeencreated:
HAL—http://stateless.co/hal_specification.html
JSON-LD—http://json-ld.org
Collection+JSON—http://amundsen.com/mediatypes/collection/
JSONAPI—http://jsonapi.org/
Siren—https://github.com/kevinswiber/siren
HALisoneofthemostpopularhypermediatypesandissupportedbytheSpring
Framework.Inthenextsection,wewillcoverthebasicsofHAL.
HAL
TheHypertextApplicationLanguage,orHAL,isaleanhypermediatypecreatedbyMike
Kellyin2011.ThisspecificationsupportsbothXML(application/hal+xml)and
JSON(application/hal+json)formats.
TheHALmediatypedefinesaresourceasacontainerofstate,acollectionoflinks,
andasetofembeddedresources.Figure10-1showstheHALresourcestructure.
Figure10-1.HALresourcestructure
TheresourcestateisexpressedusingJSONpropertiesorkey/valuepairs.Listing10-3
showsthestateofablogpostresource.
Listing10-3.BlogpostresourcestateinHAL
{
"id":1,
"body":"Myfirstblogpost",
"postdate":"2015-05-30T21:41:12.650Z"
}
Thespecificationusesareserved_linkspropertytoprovidelinkingcapabilities.
The_linkspropertyisaJSONobjectthatcontainsallofthelinks.Eachlinkinside
_linksiskeyedbytheirlinkrelationwiththevaluecontainingtheURIandasetof
optionalproperties.Listing10-4showstheblogpostresourceaugmentedwiththe
_linksproperty.Noticetheusageofanadditionalpropertytotalcountinsidethe
commentslinkvalue.
Listing10-4.BlogpostresourcewithlinksinHAL
{
"id":1,
"body":"Myfirstblogpost",
"postdate":"2015-05-30T21:41:12.650Z",
"_links":{
"self":{"href":
"http://blog.example.com/posts/1"},
"comments":{"href":
"http://blog.example.com/posts/1/comments","totalcount"
:20},
"tags":{"href":
"http://blog.example.com/posts/1/tags"}
}
}
Therearesituationsinwhichitismoreefficienttoembedaresourcethanlinktoit.
Thiswouldpreventtheclientfromtakinganextraroundtrip,allowingittoaccessthe
embeddedresourcedirectly.HALusesareserved_embeddedpropertytoembed
resources.Eachembeddedresourceiskeyedbytheirlinkrelationtothevaluecontaining
theresourceobject.Listing10-5showstheblogpostresourcewithanembeddedauthor
resource.
Listing10-5.BlogpostresourcewithembeddedresourceinHAL
{
"id":1,
"body":"Myfirstblogpost",
"postdate":"2015-05-30T21:41:12.650Z",
"_links":{
"self":{"href":
"http://blog.example.com/posts/1"},
"comments":{"href":
"http://blog.example.com/posts/1/comments","totalcount":20},
"tags":{"href":
"http://blog.example.com/posts/1/tags"}
},
"_embedded":{
"author":{
"_links":{
"self":{"href":
"http://blog.example.com/profile/12345"}
},
"id":12345,
"name":"JohnDoe",
"displayName":"JDoe"
}
}
}
HATEOASinQuickPoll
TheSpringFrameworkprovidesaSpringHATEOASlibrarythatsimplifiesthecreationof
RESTrepresentationsadheringtoHATEOASprinciples.SpringHATEOASprovidesan
APIforcreatinglinksandassemblingrepresentations.Inthissection,wewilluseSpring
HATEOAStoenrichthepollrepresentationwiththefollowingthreelinks:
Self-referencinglink
Linktothevotescollectionresource
LinktotheComputeResultresource
NoteTheQuickPollprojectthatweareusinginthischapterisavailableinthe
Chapter10\starterfolderofthedownloadedsourcecode.Tofollowtheinstructions
inthissection,importthestarterprojectintoyourIDE.Thecompletedsolutionis
availableintheChapter10\finalfolder.Pleaserefertothatsolutionforcomplete
codelistings.
WebegintheSpringHATEOASintegrationbyaddingtheMavendependencyshown
inListing10-6totheQuickPollproject’spom.xmlfile.
Listing10-6.SpringHATEOASdependency
<dependency>
<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
<version>0.17.0.RELEASE</version>
</dependency>
ThenextstepistomodifythePollJavaclasssuchthatthegeneratedrepresentation
hasassociatedlinkinformation.Tosimplifyembeddingofhypermedialinks,Spring
HATEOASprovidesanorg.springframework.hateoas.ResourceSupport
classthatresourceclassescanextend.TheResourceSupportclasscontainsseveral
overloadedmethodsforaddingandremovinglinks.ItalsocontainsagetIdmethodthat
returnstheURIassociatedwiththeresource.ThegetIdimplementationadherestothe
RESTprinciple:theIDofaresourceisitsURI.
Listing10-7showsthemodifiedPollclassextendingResourceSupport.Ifyou
remember,thePollclassalreadycontainsagetIdmethodthatreturnstheprimarykey
associatedwiththecorrespondingdatabaserecord.ToaccommodatethegetIdmethod
introducedbytheResourceSupportbaseclass,werefactoredthegetIdandsetId
PollclassmethodstogetPollIdandsetPollId.
Listing10-7.Modifiedpollclass
packagecom.apress.domain;
importorg.springframework.hateoas.ResourceSupport;
@Entity
publicclassPollextendsResourceSupport{
@Id
@GeneratedValue
@Column(name="POLL_ID")
privateLongid;
@Column(name="QUESTION")
privateStringquestion;
@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="POLL_ID")
@OrderBy
privateSet<Option>options;
publicLonggetPollId(){
returnid;
}
publicvoidsetPollId(Longid){
this.id=id;
}
//OtherGettersandSetterremoved
}
InChapter4,weimplementedPollController’screatePollmethodsothatit
constructedaURIofthenewlycreatedPollresourceusingthegetIdmethod.The
getIdtogetPollIdrefactoringjustdescribedrequiresustoupdatethe
createPollmethod.Listing10-8showsthemodifiedcreatePollmethodusingthe
getPollIdmethod.
Listing10-8.ModifiedcreatePoll()method
@RequestMapping(value="/polls",method=RequestMethod.POST)
publicResponseEntity<?>createPoll(@RequestBodyPollpoll)
{
poll=pollRepository.save(poll);
//Setthelocationheaderforthenewlycreated
resource
HttpHeadersresponseHeaders=newHttpHeaders();
URInewPollUri
=ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").
responseHeaders.setLocation(newPollUri);
returnnewResponseEntity<>(null,responseHeaders,
HttpStatus.CREATED);
}
NoteWemodifiedourdomainPollclassandhaditextendtheResourceSupport
class.AnalternativetothisapproachistocreateanewPollResourceclasstoholdthe
Poll’srepresentationandhaveitextendtheResourceSupportclass.Withthis
approach,thePollJavaclassremainsuntouched.However,weneedtomodifythe
PollControllersothatitcopiestherepresentationfromeachPolltoa
PollResourceandreturnsthePollResourceinstances.
ThefinalstepintheSpringHATEOASintegrationistomodifyPollController
endpointssothatwecanbuildlinksandinjectthemintoresponses.Listing10-9showsthe
modifiedportionsofthePollController.
Listing10-9.PollControllermodifications
packagecom.apress.controller;
importstatic
org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
importstatic
org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
@RestController
publicclassPollController{
@RequestMapping(value="/polls",
method=RequestMethod.GET)
publicResponseEntity<Iterable<Poll>>getAllPolls(){
Iterable<Poll>allPolls
=pollRepository.findAll();
for(Pollp:allPolls){
updatePollResourceWithLinks(p);
}
returnnewResponseEntity<>(allPolls,
HttpStatus.OK);
}
@RequestMapping(value="/polls/{pollId}",
method=RequestMethod.GET)
publicResponseEntity<?>getPoll(@PathVariableLong
pollId){
Pollp=pollRepository.findOne(pollId);
updatePollResourceWithLinks(p);
returnnewResponseEntity<>(p,HttpStatus.OK);
}
privatevoidupdatePollResourceWithLinks(Pollpoll){
poll.add(linkTo(methodOn(PollController.class).getAl
poll.add(linkTo(methodOn(VoteController.class).getAllVotes(
poll.add(linkTo(methodOn(ComputeResultController.class).com
result"));
}
}
Becauselinksneedtobegeneratedandinjectedinmultipleplaces,wecreateda
updatePollResourceWithLinksmethodtoholdthecommoncode.Spring
HATEOASprovidesaconvenientControllerLinkBuilderclassthatcanbuild
linkspointingtoSpringMVCcontrollers.TheupdatePollResourceWithLinks
methodimplementationusesthelinkTo,methodOnandslashutilitymethods.These
methodsarepartoftheSpringHATEOASControllerLinkBuilderclassandcan
generatelinkspointingtoSpringMVCcontrollers.ThegeneratedlinksareabsoluteURIs
toresources.Thisrelievesdevelopersfromhavingtolookupserverinformationsuchas
protocol,hostname,portnumber,andsoon,duplicatingURIpathstrings(/polls)all
overtheplace.Tobetterunderstandthesemethods,let’sdissectthiscode:
linkTo(
methodOn(PollController.class).getAllPolls()
)
.slash(poll.getPollId())
.withSelfRel()
ThelinkTomethodcantakeaSpringMVCcontrollerclassoroneofitsmethodsas
anargument.Ittheninspectstheclassormethodforthe@RequestMappingannotation
andretrievesthepathvaluetobuildthelink.ThemethodOnmethodcreatesadynamic
proxyofthepassedinPollControllerclass.WhenthegetAllPollsmethodis
invokedonthedynamicproxy,its@RequestMappinginformationisinspectedandthe
value“/polls”isextracted.FormethodssuchasgetAllVotesthatexpecta
parameter,wecanpassinanull.However,iftheparameterisusedtobuildtheURI,then
arealvalueshouldbepassedin.
Theslashmethod,asthenamesuggests,appendsthePoll’sIDasasubresourceto
thecurrentURI.Finally,thewithSelfRelmethodinstructsthatthegeneratedlink
shouldhavearelwithvalue“self”.Underthehood,the
ControllerLinkBuilderclassusestheServletUriComponentsBuilderto
obtainbasicURIinformationsuchashostnameandbuildsthefinalURI.Hence,forapoll
withID1,thiscodewillgeneratetheURI:http://localhost:8080/polls/1.
Withthesechangesinplace,runtheQuickPollapplicationand,usingPostman,
performaGETrequestonthehttp://localhost:8080/pollsURI.Youwillsee
thatthegeneratedresponseincludesthreelinksforeachpoll.Figure10-2showsthe
Postmanrequestandpartialresponse.
Figure10-2.Postmanresponsewithlinks
Summary
Inthischapter,wereviewedtheHATEOASconstraintthatenablesdeveloperstobuild
flexibleandlooselycoupledAPIs.WescratchedthesurfaceandusedSpringHATEOAS
tocreateQuickPollRESTrepresentationsthatadheretoHATEOASprinciples.
Thisbringsustotheendofourjourney.Throughoutthisbook,youhavelearnedthe
keyfeaturesofRESTandSpringtechnologiesthatsimplifyRESTdevelopment.Withthis
knowledge,youshouldbereadytostartdevelopingyourownRESTfulservices.Happy
coding!
APPENDIXA
InstallingcURLonWindows
ThecURLcommand-linetoolcomesinstalledinmostoperatingsystemdistributions.The
MacOSTerminalhasout-of-the-boxsupportforcURL.However,youneedtosetup
cURLonaWindowsmachine.Herearethestepsrequired:
1. TheeasiestwaytoinstallcURLonWindowsistousecURL’s
DownloadWizard.Toaccessthewizard,gotothe
http://curl.haxx.se/dlwiz/inyourfavoritebrowser.
2. Onthefollowingpage,clickthe“curlexecutable”link.
3. Onthe“SelectOperatingSystem”page,selectWin64.
4. Onthe“SelectforWhatFlavor”pages,choosethe“Generic”
packageandhitSelect.
5. Onthe“SelectwhichWin64Version”select“Any”andhitSelect.
6. Onthe“SelectforWhatCPU”page,selectx86_64andhitSelect.
7. Onthefollowingpage,clickthe“Download”button.Youwillbe
redirectedtohttp://www.confusedbycode.com/curl/.
8. Oncethewebsiteloads,performtheverificationfor“I’mnota
robot”andthendownloadthe“WithAdministratorPrivileges
(free)”64-bitversionoftheMSIfile.
9. OncetheMSIfileisdownloaded,launchtheMSIinstallerand
followtheinstructionsforinstallation.
10. Oncetheinstallationiscomplete,thecURLexecutablewillbe
addedtoyourclasspathandisreadyforuse.Totesttheinstallation,
openacommandlineandtype:
curlhttp://google.com
Youwillseetheoutputsimilartothisimage,indicatingthatthecURLcommand-line
toolissuccessfullyinstalled.
Index
A,B
Acceptheaderversioningapproach
AcegiSecurity
API
ComputeResultController
PollController
@Autowiredannotation
createPollmethod
declarationfile
GetAllPollsrequest
JSON
PollandPollsresources
POMfile
@RequestBodyannotation
@RequestMappingannotation
ResponseEntity
@RestControllerannotation
ServletUriComponentsBuilderutilityclass
updateanddelete
VoteController
api-docs
Aspectorientedprogramming(AOP)
authorizedGrantTypesmethod
@Autowiredannotation
C
Cache
Certificate-basedsecuritymodel
Codeondemand
Commandlineinterface(CLI)
Completecodereplication
ComputeResultresourcerepresentation
ConfigureSwaggermethod
Controller
createPollmethod
Cross-siterequestforgery(CSRF)
cURL
cURLinstallation,Windows
curlybraces{}
Cursor-basedpagination
Customheaderversioningapproach
D
DataAccessObjects(DAO)
approach
CrudRepositoryAPI
findermethods
OptionRepositoryinterface
PollRepositoryinterface
DataTransferObjects(DTOs)
DELETEmethod
DependencyInjection(DI)
DigestAuthenticationapproach
DispatcherServlet
DocumentationSeealsoSwagger
E
Error
Exception
F
findOnefindermethod
fromCurrentRequestmethod
G
GET
getContentTypemethod
GETmethod
GETrequest
GlobalExceptionHandler
H
Hamcrest
Handler
HandlerExceptionResolver
HandlerMappingcomponent
HypermediaAsTheEngineOfApplicationState(HATEOAS)
blogpostwithlinks
commentsandtags
description
QuickPollapplication
Mavendependency
modifiedcreatePollmethod
modifiedPollclass
PollControllertmodifications
Postmanresponsewithlinks
slashmethod
updatePollResourceWithLinksmethod
HypertextApplicationLanguage(HAL)
HEADmethod
HTTPBasicauthentication
HTTPmethod
DELETEmethod
GETmethod
HEADmethod
idempotency
PATCHmethod
POSTmethod
PUTmethod
safety
statuscodes
HTTPstatuscodes
ErrorDetailclass
GitHubandTwilio
QuickPollErrorResponseFormat
ResourceNotFoundExceptionerrorresponse
RestExceptionHandler
I
Idempotency
Implicitgranttypeapproach
Interceptors
InternalViewResolver
J,K
JSONhypermediatypes
JSONobjects
L
Layeredsystem
Limitoffsetpagination
Locationheader
M
Message
MethodArgumentNotValidException
Mockito
@ModelAttributeannotation
Modelinterface
N
@NonEmptyannotation
O
OAuth2.0
authorizationserver
CALL_BACK_URIparameter
client
clientprofiles
HTTPSusage
QuickPollapplication
authorizationserver
Authorizationserverendpoints
PollController,mapping
readandwritescopes
resourceserver
SpringSecurityOAuth2dependency
testing
updatedsecurityconfig
refreshvs.accesstokens
registration
resourceowner
resourceserver
verification
OAuth2AuthenticationProcessingFilter
OAuth2RestTemplate
OpenAuthorization(OAuth)
P
Pagenumberpagination
Pagination
changingpagesize
cursor-basedpagination
limitoffsetpagination
Linkheader
pagenumberpagination
QuickPollapplication
time-basedpagination
Passwordgranttypeapproach
PATCHmethod
Path
PathVariable
POST
POSTmethod
PUTmethod
Q
QuickPollapplication
API(seeAPI)
architecture
DAO(seeDataAccessObjects(DAO))
designprocess
actionidentification
endpointidentification
resourceidentification
resourcerepresentation
domainlayer
domainobjects
Optionclass
Pollclass
Voteclass
embeddeddatabase
HSQLDBPOM.XMLdependency
JPAandWeboptions
JPAtechnology
projectconfiguration
repository/dataaccesslayer
requirements
starterfolder
WebAPIlayer
QuickPollErrorHandling
externalizingerrormessages
getPollimplementation
inputfieldvalidation
boxvalidationconstraints
ErrorDetailclass
errorkey
errorvalue
GarbageinGarbageOut
JSR303annotations
JSR303validation
MethodArgumentNotValidException
org.springframework.validation.Validatorinterface
RestExceptionHandler
@Validannotations
nonexistentpoll
PollController
ResourceNotFoundException
RestExceptionHandler
verifyPollmethod
QuickPollJavaClient
QuickPollresourcelistingfile
QuickPollsorting
R
REpresentationalStateTransfer(REST)
building,RESTfulAPI
cache
Client-Server
codeondemand
contentnegotiation
HTTPmethod
DELETEmethod
GETmethod
HEADmethod
idempotency
PATCHmethod
POSTmethod
PUTmethod
safety
statuscodes
identifyingresources
layeredsystem
RMM
snapshot
stateless
uniforminterface
URItemplates
replacecommand
Repositories.SeeDataAccessObjects(DAO)
@RequestBodyannotation
@RequestMappingannotation
elements
Firefoxbrowser
GETmethod
methodargumentsanddescriptions
POSTmethod
returntypesanddescriptions
@RequestParamannotation
Resourcelistingfile
ResourceServerConfigurerAdapter
RESTAPIapplications
Postman
RESTClient
RESTAPIdocumentationSeealsoSwagger
@RestControllerannotation
RestExceptionHandler
RestTemplate
BasicAuthentication
DELETEHTTPoperations
GETHTTPmethod
handlepagination
HTTPPOSToperations
integrationtesting
SpringMVCtest
testGetAllPollsmethod
OAuth2RestTemplate
PUTHTTPmethod
Springtest
unittesting
findAll()method
MockMvcBuildersclass
parammethod
performmethod
PollControllerwithmocks
POSTHTTPrequest
SpringMVCtest
SpringMVCTestframework
Richardson’sMaturityModel(RMM)
S
Security
certificate-based
DigestAuthentication
HTTPbasicauthentication
QuickPollapplication
cURL
OAuth2.0
requirements
securityconfiguration
SpringBoot
URI
UserDetailsServiceimplementation
userinfrastructuresetup
session-based
spring
XAuth
Session-basedsecuritymodel
showHomePagemethod
SimpleMappingExceptionResolver
Sortascendingordescending
Sorting
ascending/descending
QuickPollsorting
SpringBoot
CLI
description
HelloWorldRESTapplication
annotations
codeimplementation
contents
helloGreeting()method
JARfile
main()method
pluginsanddependencies
ROOTcontext
start.spring.iowebsite
Maven,buildtool
opinionatedapproach
SpringInitializrapplication
startermodules
STS
Springframework
AOP
definition
DI
modules
SpringSecurity“r”spring
SpringToolSuite(STS)
SpringWebMVC
architecture
components
controller
HandlerExceptionResolver
interceptors
modelinterface
PathVariable
@RequestMappingannotation(see@RequestMappingannotation)
@RequestParamannotation
view
ViewResolverInterface
Stateless
Swagger
APIdeclarationfile
configuration
customization
ApiInfoBuilder
configureSwaggermethod
includePatternmethod
PollController
Springmvc
history
integration
JSON
resourcelistingfile
specification
UI
versioning
Swagger-springmvcMavendependency
SwaggerSpringMvcPlugin
SwaggerUI
configuration
GitHubsite
QuickPollSwaggerUI
urlmodifications
T
TemplateMethoddesignpattern
Time-basedpagination
Timestamp
U
UniformInterface
URIparameterversioning
V
@Validannotation
verifyPollmethod
Versioning
Acceptheader
APIusers
customheader
QuickPollRESTAPI
Swaggerconfiguration
URIparameterversioning
Versionspecificcodereplication
View
ViewResolverInterface
W
WebApplicationDescriptionLanguage(WADL)
X,Y,Z
XAuth
Download PDF
Similar pages