Upload
others
View
19
Download
0
Embed Size (px)
Citation preview
BaukeScholtzand
ArjanTijms
TheDefinitiveGuidetoJSFinJavaEE8BuildingWebApplicationswithJavaServerFaces
BaukeScholtz
Willemstad,CuraçaoArjanTijms
Amsterdam,Noord-Holland,TheNetherlands
AnysourcecodeorothersupplementarymaterialreferencedbytheauthorinthisbookisavailabletoreadersonGitHubviathebook’sproductpage,locatedatwww.apress.com/9781484233863.Formoredetailedinformation,pleasevisithttp://www.apress.com/source-code.
ISBN978-1-48423386-3
e-ISBN978-1-4842-3387-0
https://doi.org/10.1007/978-1-4842-3387-0
LibraryofCongressControlNumber:2018942178
©BaukeScholtz,ArjanTijms2018
Thisworkissubjecttocopyright.AllrightsarereservedbythePublisher,whetherthewholeorpartofthematerialisconcerned,specificallytherightsoftranslation,reprinting,reuseofillustrations,recitation,broadcasting,reproductiononmicrofilmsorinanyotherphysicalway,andtransmissionorinformationstorageandretrieval,electronicadaptation,computersoftware,orbysimilarordissimilarmethodologynowknownorhereafterdeveloped.
Trademarkednames,logos,andimagesmayappearinthisbook.Ratherthanuseatrademarksymbolwitheveryoccurrenceofatrademarkedname,logo,orimageweusethenames,logos,andimagesonlyinaneditorialfashionandtothebenefitofthetrademarkowner,withnointentionofinfringementofthetrademark.Theuseinthispublicationoftradenames,trademarks,servicemarks,andsimilarterms,eveniftheyarenotidentifiedassuch,isnottobetakenasanexpressionofopinionastowhetherornottheyaresubjecttoproprietaryrights.
Whiletheadviceandinformationinthisbookarebelievedtobetrueandaccurateatthedateofpublication,neithertheauthorsnortheeditorsnorthepublishercanacceptanylegalresponsibilityforanyerrorsoromissionsthatmaybemade.Thepublishermakesnowarranty,expressorimplied,withrespecttothematerialcontainedherein.
DistributedtothebooktradeworldwidebySpringerScience+BusinessMediaNewYork,233SpringStreet,6thFloor,NewYork,NY10013.Phone1-800-SPRINGER,fax(201)348-4505,[email protected],orvisitwww.springeronline.com.ApressMedia,LLCisaCaliforniaLLCandthesolemember(owner)isSpringerScience+BusinessMediaFinanceInc(SSBMFinanceInc).SSBMFinanceIncisaDelawarecorporation.
Tocaffeineandour(notso)patientwives.
TableofContents1. Chapter1:History
1. IntheBeginning...2. TheAdolescentYears3. OntoMaturity4. Rejuvenation
2. Chapter2:FromZerotoHelloWorld
1. InstallingJavaSEJDK
1. WhatAboutJavaEE?
2. InstallingPayara
1. HowAboutOtherServers?
3. InstallingEclipse
1. ConfiguringEclipse2. InstallingJBossToolsPlug-in3. IntegratingNewServerinEclipse
4. CreatingNewProjectinEclipse
1. CreatingtheBackingBeanClass2. CreatingtheFaceletsFile3. DeployingtheProject
5. InstallingH2
1. ConfiguringDataSource2. ConfiguringJPA3. CreatingtheJPAEntity4. CreatingtheEJBService5. AdjustingtheHelloWorld
3. Chapter3:Components
1. StandardHTMLComponents2. StandardCoreTags3. LifeCycle
1. RestoreViewPhase(FirstPhase)2. ApplyRequestValuesPhase(SecondPhase)3. ProcessValidationsPhase(ThirdPhase)4. UpdateModelValuesPhase(FourthPhase)5. InvokeApplicationPhase(FifthPhase)6. RenderResponsePhase(SixthPhase)
4. AjaxLifeCycle5. ViewBuildTime6. ViewRenderTime7. ViewState8. ViewScope9. PhaseEvents10. ComponentSystemEvents11. CustomComponentSystemEvents12. JSTLCoreTags13. ManipulatingtheComponentTree
4. Chapter4:FormComponents
1. Input,Select,andCommandComponents2. Text-BasedInputComponents3. File-BasedInputComponent4. SelectionComponents5. SelectItemTags6. SelectItemGroup7. LabelandMessageComponents
8. CommandComponents9. Navigation10. AjaxifyingComponents11. NavigationinAjax12. GETforms13. StatelessForms
5. Chapter5:ConversionandValidation
1. StandardConverters
1. <f:convertNumber>2. <f:convertDateTime>
2. StandardValidators
1. <f:validateLongRange>/<f:validateDoubleRange>2. <f:validateLength>/<f:validateRegex>3. <f:validateRequired>4. <f:validateBean>/<f:validateWholeBean>
3. ImmediateAttribute4. CustomConverters5. CustomValidators6. CustomConstraints7. CustomMessages
6. Chapter6:OutputComponents
1. Document-BasedOutputComponents2. Text-BasedOutputComponents3. Navigation-BasedOutputComponents4. Panel-BasedOutputComponents5. DataIterationComponent
1. Editable<h:dataTable>2. Add/RemoveRowsin<h:dataTable>3. SelectRowsin<h:dataTable>4. DynamicColumnsin<h:dataTable>
6. ResourceComponents7. Pass-ThroughElements
7. Chapter7:FaceletsTemplating
1. XHTML2. TemplateCompositions3. SinglePageApplication4. TemplateDecorations5. TagFiles6. CompositeComponents
1. RecursiveCompositeComponent
7. ImplicitELObjects
8. Chapter8:BackingBeans
1. Model,View,orController?2. ManagedBeans3. Scopes
1. @ApplicationScoped2. @SessionScoped3. @ConversationScoped4. @FlowScoped5. @ViewScoped6. @RequestScoped7. @Dependent
4. Whichscopetochoose?5. WhereIs@FlashScoped?6. Managedbeaninitializationanddestruction7. InjectingJSFvendedtypes8. EagerInitialization9. Layers10. NamingConventions
9. Chapter9:ExceptionHandling
1. CustomErrorPages2. AjaxExceptionHandling3. ViewExpiredExceptionHandling4. IOExceptionHandling5. EJBExceptionHandling
10. Chapter10:WebSocketPush
1. Configuration2. Usage3. ScopesandUsers4. ChannelDesignHints5. One-TimePush6. StatefulUIUpdates7. Site-WidePushNotifications8. KeepingTrackofActiveSockets9. DetectingSessionandViewExpiration10. BreakingDownMojarra’sf:websocketImplementation
11. Chapter11:CustomComponents
1. ComponentType,Family,andRendererType2. CreatingNewComponentandRenderer3. ExtendingExistingComponent4. ExtendingExistingRenderer5. CustomTagHandlers6. PackaginginaDistributableJAR7. ResourceDependencies
12. Chapter12:SearchExpressions
1. RelativeLocalIDs2. AbsoluteHierarchicalIDs3. StandardSearchKeywords4. CustomSearchKeywords
13. Chapter13:Security
1. JavaEESecurityOverviewandHistory2. ProtectAccesstoResources
1. Excluded2. Unchecked3. ByRole
3. SettingtheAuthenticationMechanism4. SettingtheIdentityStore5. ProvidingOurCustomJSFCode6. Caller-InitiatedAuthentication7. RememberMe
1. ActivatingRemember-MeService
8. LoggingOut9. CustomPrincipals10. ConditionallyRenderingBasedonAccess11. Cross-SiteRequestForgeryProtection12. WebParameterTamperingProtection13. Cross-SiteScriptingProtection14. SourceExposureProtection
14. Chapter14:Localization
1. HelloWorld,Olámundo,
2. Configuration3. ReferencingBundleinJSFPage4. ChangingtheActiveLocale5. OrganizingBundleKeys6. LocalizingConversion/ValidationMessages7. ObtainingLocalizedMessageinaCustom
Converter/Validator8. LocalizingEnums9. ParameterizedResourceBundleValues10. Database-BasedResourceBundle11. HTMLinResourceBundle
15. Chapter15:Extensions
1. ExtensionTypes2. ExtendingCDIArtifacts3. ExtendingClassicalArtifacts4. Plug-ins5. DynamicExtensions
1. ApplicationConfigurationPopulator2. TheApplicationMainClass
6. LocalExtensionandWrapping7. Introspection
16. Index
AbouttheAuthorsandAbouttheTechnicalReviewer
AbouttheAuthors
BaukeScholtzisanOracleJavaChampion,amemberoftheJSF2.3ExpertGroup,andthemaincreatoroftheJSFhelperlibraryOmniFaces.OntheInternet,heismorecommonlyknownasBalusC,whoisamongthetopusersandcontributorsonStackOverflow.BaukehasintegratedseveralOmniFacessolutionsintoJSF2.3.HeisawebapplicationspecialistandconsultsorhasconsultedforMercury1Limited,MyTutor,NavaFinance,LinkPizza,ZEEF,M4N/Zanox,ITCA,RDC,andmoreclientsfromfintech,affiliatemarketing,socialmedia,andmoreaspartofhis17yearsofexperience.ThisbookoffersBauketheopportunitytogointodepthtoanswer
mostfrequentlyaskedquestionsandcorrectlysolvemostcommonlyencounteredproblemswhileusingJSF.
ArjanTijmsworksforPayaraServicesLtdandisaJSF(JSR372)andSecurityAPI(JSR375)ExpertGroupmember.Heistheco-creatorofthepopularOmniFaceslibraryforJSF,whichwasa2015Duke’sChoiceAwardwinner,andisthemaincreatorofasetoftestsfortheJavaEEauthenticationSPI(JASPIC)usedbyvariousJavaEEvendors.ArjanholdsanMScdegreeinComputerSciencefromtheUniversityofLeiden,TheNetherlands.WritingaboutthistopicwasanaturalchoiceforArjan;Hehasalreadywrittenmuchaboutitonhisblogandwantedtoexpandthatbycontributingtoabook.
AbouttheTechnicalReviewer
Chád(“Shod”)Darbyisanauthor,instructor,andspeakerintheJavadevelopmentworld.AsarecognizedauthorityonJavaapplicationsandarchitectures,hehaspresentedtechnicalsessionsatsoftwaredevelopmentconferencesworldwide(intheUnitedStates,UK,India,Russia,andAustralia).Inhis15yearsasaprofessionalsoftwarearchitect,he’shadtheopportunitytoworkforBlueCross/BlueShield,Merck,Boeing,RedHat,andahandfulofstartupcompanies.
ChádisacontributingauthortoseveralJavabooks,includingProfessionalJavaE-Commerce(WroxPress),BeginningJavaNetworking(WroxPress),andXMLandWebServicesUnleashed(SamsPublishing).ChádhasJavacertificationsfromSunMicrosystemsandIBM.HeholdsaBSincomputersciencefromCarnegieMellonUniversity.YoucanvisitChád’sblogatwww.luv2code.comtoviewhisfreevideotutorialsonJava.YoucanalsofollowhimonTwitterat@darbyluvs2code.
(1) (2)
©BaukeScholtz,ArjanTijms2018BaukeScholtzandArjanTijms,TheDefinitiveGuidetoJSFinJavaEE8,https://doi.org/10.1007/978-1-4842-3387-0_1
1.History
BaukeScholtz andArjanTijms
Willemstad,Curaçao Amsterdam,Noord-Holland,TheNetherlands
ThischapterdescribesthehistoryofJSF,startingfromitsearlyconceptionandendingwherewearetodayatthemomentofwriting.We’lldiscusshowtheJSFAPI(applicationprogramminginterface)itselfevolved,whichimportanteventstookplaceduringthatevolution,andwhosomeofthepeoplewerethatwereinvolvedinallofthis.
Thisisinnowayacompletedescriptionofthehistoryandthereadershouldtakenoticeofthefactthatmanymoreeventstookplaceandmanymorepeoplewereinvolvedthanwewereabletomentionhere.
IntheBeginning...JSFgoesbackalongtime.ItsinitialJSR,JSR127,startedin2001.AtthattimetheStrutswebframeworkwaswildlypopular,althoughitwasn’tthatlongagothatitwasreleaseditself(around2000).DespiteStruts’popularity,alargenumberofotherwebframeworkswereinuseintheJavaspace,andnewoneswerepoppingupallthetime.JavaServer
1 2
Faces(JSF)wasconceivedasanattempttobringastandardizedMVC(model-view-controller)webframeworkbaseintotheoverallJavaEEplatform.
Controversiesarequitecommoninthewebframeworkspace,andJSFisnoexceptionhere.RightatthestartofitsinceptiontherewasabigcontroversywhereApacheopposedthecreationofJSFonthebasesthatApacheStrutsalreadyexistedandaclosedsourcealternativewouldhavelittlevalue.ApachethereforevotedagainstthecreationofJSFwiththefollowingcomment:
ThisJSRconflictswiththeApacheopensourceprojectStruts.ConsideringSun’scurrentpositionthatJSRsmaynotbeindependentlyimplementedunderanopensourcelicense,weseelittlevalueinrecreatingatechnologyinaclosedenvironmentthatisalreadyavailableinanopenenvironment.
TotheextentthatthisJSRextendsbeyondStrutstoday,wewouldencouragetheSundevelopersproposingthisJSRtojointheSundevelopersalreadyleadingStrutstocreateanopensolutionatApache,somethingwhichwhenfinishedwouldbeassuredofbeingabletobeimplementedasopensource.
EventuallytheconflictwasresolvedwhenafteraboutayearintotheprocessspecleadAmyFowler(fromSwingfame)wasreplacedbyCraigMcClanahan,theveryfatheroftheStrutsprojectthatJSFwassaidtobecompetingwith.Theopensourcerestrictionwasliftedaswell,andtheopensourceJSFimplementation,calledMyFaces,wasdevelopedinparallelwiththe(thennameless)RIandhencethespecificationitself.MyFacesinitiallystartedasanLGPLlicensedprojectatsourceforge.netinDecember2002andhad
aninitial0.1releaseconformingtowhatwasthencalledan“EarlyAccessSpecification”inJanuary2003.
OpensourceimplementationsarethemostcommonimplementationsinJavaEE8,andthere’sbarelyanyEEspecificationatthetimeofthiswriting(2018)that’sstillimplementedasclosedsource.In2001,however,thiswasnotjustuncommon;itwasactuallynotallowedfornewJSRs.Allowingforanopensourceimplementationwasthereforequiteachange,andthehonorfelltoJSFtobethefirstofitskindforwhichthiswasallowed.
Despitetheopensourceimplementationbeingallowed,theactualdevelopmentofthespecwasstilldoneinsecretandbehindcloseddoors.Therewasnopublicmailinglist,andnotracker(e.g.,aJIRAinstance)forthepublictocreateissuesorexpresswishes.Occasionallyinterviewswerebeingdone,andinthefallof2002bythenformerspecleadAmyFowlerdidrevealquiteafewdetailsaboutJSF,butlargelytheprojectwasshroudedinmysteryforthegeneralpublic.
TheteambehindJSFwas,however,hardatwork.Thefirste-mailtotheinternalJSR-127listwassentonAugust17,2001.Aswithmostprojects,theteamspenttheinitialmonthsongatheringrequirementsandlookingattheexistingcompetingproducts.Apackagenamewaschosenaswell.Theinitialplaceholderpackage,whichwas"javax.servlet.ui",now"javax.faces",waschosenasthepackagetouse.Theveryfirsttechnicalarchitecturetobeconsideredwasthecomponentmodel.Foracomponent-basedMVCframeworkthisisobviouslyoneofthemostimportantaspects.Duringthelastmonthof2001andthefirsttwomonthsof2002theteamlookedatwhatisnowknownastheManagedBean(called“ObjectManager”then).
Managedbeanswiththeirscopes,names,anddependencyinjectionareclearlyanothercornerstoneoftheJSFframework.Eventsandthemodelbehinditwerebeinglookedataswellduringthattimeframe.
Inthesecondquarterof2002twoothercornerstonesofJSFwerediscussed:theExpressionLanguage(inspiredbyJSTL),whichisinstrumentalfortheso-calledbindingsofbeansfromatemplatetobackingcode,andthefactoryfinder,whichallowedkeypartsofJSFtobereplacedandalthoughperhapsnotfullyrealizedatthetimemayhavecontributedgreatlytoJSFstillbeingrelevantsome16yearslater.
ItwasinthissamequarterthatCraigMcClanahantookoverasspeclead,fatherofStrutsandarchitectofTomcat’sServletcontainer,tookover.NotlongafterthediscussionaboutusingJSPstarted,adiscussion,perhapsunbeknownsttotheteamatthetime,thatwould,unfortunately,havearathernegativeimpactonJSFlateron.Aroundtheendoftheyear2002,EdBurns,wholikeMcClanahanhadalsoworkedonTomcatbefore,joinedtheteamasco-speclead.BurnsisthepersonwhowouldeventuallybecomethemainspecleadofJSFforwelloveradecade.
Whiletheteamcontinuedtoworkonthingsliketheaforementionedmanagedbeansandtheso-calledvaluebinding,whichistheJavarepresentationofthealsoaforementionedexpressionlanguagebinding,thefirstdarkcloudappearedwheninthespringof2003teammemberHansBergstenrealizedthattherewereveryrealandmajorissueswithusingJSPasthetemplatinglanguageforJSF.Hebroughttheseconcernstotheteam,butultimatelytheyweren’taddressedandinsteadthefollowingmonthswerespent,amongotherthings,onavariantofthevaluebinding;itlater
onbecameclearthatthemethodbindingandthestatesavingmechanismwereanotherofJSF’slessthanidealimplementations.
JSF1.0anditsstillnamelessRIwereeventuallyreleasedonMarch11,2004—coincidentally,ameretwoweeksbeforethereleaseofanotherframeworkthat’sstillstrongtoday,Spring1.0.MyFacesreleasedits1.0.0alphaversiononlydayslater,onMarch19.It’sperhapsaninterestingobservationthatJSFwentfinalwithafull-fledgedXML-baseddependencyinjection(DI)frameworkjustbeforeSpring,whichislargelyknownforitsDI,wentfinal.
JSF1.0wasgenerallywellreceived;despitearathercrowdedmarketwithcompetitorssuchasTapestry,WebObjects,Velocity,andCocoonoperating,notlessthanthreebooksfromwriterssuchasHorstCaymannandHansBergstenappearedinthemonthsafter,andtheeXoplatform(aDigitalCollaborationPlatform)startedusingJSFrightaway.
HansBergsten’searlierconcerns,however,becomepainfullyclearalmostjustasquickly;theJSPtechnologyisbasedonprocessingatemplatefromstarttoend,immediatelywritingtotheresponseastagsareencountered.JSF,however,requiresaphasedapproachwherecomponentsneedtobeabletoinspectandactonthecomponenttree,whichisbuiltfromthetagsonthepage,beforestartingtowriteanythingtotheresponse.Thismismatchledtomanystrangeissues,suchascontentdisappearingorbeingrenderedoutoforder.
OnlythreemonthsaftertheintroductionofJSF,HansBergstenmadeastrongcaseofdroppingJSPinhislegendaryarticle“ImprovingJSFbyDumpingJSP.”ThereBergstenexplainshowill-suitedJSPisforuseatemplatelanguageinJSF,buthealsopresentsaglimmerofhope;becauseofJSF’s
greatsupportforextendibility,it’srelativelyeasytointroducealternativetemplatingsimplybyreplacingtheso-calledviewhandler,somethingwhichJSFexplicitlyallows.Itwould,however,takefivelongyearsuntilJSFwouldindeedshipwithamoresuitableviewtemplatinglanguage,andeventhoughJSPhadbeenessentiallydeprecatedatthatpointit’sstillpresentinJSFatthetimeofwriting.
TheAdolescentYearsBackin2004anotherfirstbefellJSF;onJune28EdBurnsannouncedthatthesourceoftheRIwasreleasedbySun.ThisrepresentedamajormilestoneasbeforethatdatemosttechnologyinactiveusebySunwasclosedsource.InitiallythesourcewaslicensedunderthesomewhatexoticJRL,butlaterthiswouldbechangedtoduallicenses,GPLwithclasspathexceptionandCDDL.Atthesametimeasthisannouncement,thetraditionwasestablishedthateverynewfeatureorbugfixshouldbeaccompaniedbyatest,andthatallexistingtestsshouldbeexecutedbeforecommittingthechange.Some14yearslaterthere’salargelydifferentsetofpeopleworkingontheRIsource,andtheprojectstructureandcodeconventionshavechangedaswell,butthetest-driventraditionisstillbeingupholdinitsoriginalform.
AtthatpointEdBurnsdecidedtofocusmoreonthespecificationaspectsofJSFastheJSF1.2specworkhadstartedrightaway,andJayashriVisvanathan,oneoftheearlyteammembers,tookontheleadroleconcerningtheimplementationaspects,withRyanLubke,workingastheTCK(testing)engineer.
Stillonlyafewmonthsold,avarietyofcomponent
librariesforJSFhadalreadystartedtopopup,althoughallofthemcommercial.AmongthosewastheonefromOracle,ADFFaces.ADFFaceswasputonOracle’sroadmapwellbeforeJSF1.0wentfinal,andthefirstearlyaccessreleasewaspresentedonAugust17,2004.ItsleadwasAdamWiner,whorepresentedOracleintheteamthatcreatedJSF1.0.ADFFacesprimarilycontainedasetofrichcomponents,butalsoadialogframework,andremarkablyalreadyfeaturedpartialpagerendering(PPR),quiteabitaheadofthelatercropofAJAXsolutions.ADFFacesalsocontaineda“foreach”tag(af:forEach)thatactuallyworked.AdamWinerexplainedintheseearlydaysthatsuchtagisnotquitetrivialtobuildbutpromisedthatOraclewouldcontributetheknowledgebacktoJSFitself.
TheADFFacescomponentsoriginatedmostlyfromtheearlierUserInterfaceXML(UIX)framework,ofwhichAdamWinerwastheleadarchitectaswell.EarlierversionsofUIXusedthenames“Cabo,”“Baja,”and“Marlin.”UIXwasarichclientframeworkforuseinthebrowser.WithJSFsharingmorethanafewsimilaritiestoUIX,andwithitslead,AdamWiner,beingpartoftheoriginalJSFteam,it’sperhapsnotunreasonabletosurmisethatUIXinfluencedJSF.Suchsimilaritiesincludetheconceptofcomponentswithseparaterenderers,JSPtaghandlersanddeclarativeoptionstocomposeapage,andtheabilitytoinstantiatethosesamecomponentsprogrammaticallyinJava.Therewasevenaconceptuallysimilardatabinding,althoughwithalesselegantsyntax.Insteadof,say,value="#{user.age}",UIXwouldusedata:value="age@user"butalsorequiredakindofproducertobedefinedoneachpagetodeclarewhere“user”
comesfrom,andthennestthepage’scontentwithinthatdeclaration.Bycontrast,JSFandELhavealwaysusedglobaldefinitionsandleftituptotheusertoavoidnameclashes.
Oneofthefirst,ifnotthefirstopensourcecomponentlibraryin2004wasMatthiasUnverzagt’sOurFaces.AsJSFdidnothaditsownresourceAPI(applicationprogramminginterface)atthetimetoserveupthingslikeimages,OurFacesrequiredaServlettobeaddedtoweb.xml,theso-calledSkinServlet
(ourfaces.common.webapp.SkinServlet).ThesignificanceofthisisthatitbecamearathercommonthingforJSFlibrariesinthosedaystoasktheirusers:addsomethingmanuallytoweb.xmlbeforethecomponentlibrarycanbeused.
Mostofthelastmonthsof2004andearlymonthsof2005werespentbytheJSF1.2expertgroup(EG)workingonvariousJSPandELissues,suchastheJSTL<c:forEach>supportandthegenerationofIDsinJSP,aswellasonthedreaded“contentinterweaving”issue,whichreferstotheaforementionedcontentthatappearsatwrongplacesintheresponsewhenrendering.
WhileOurFacesmayhavebeenoneofthefirstcomponentlibraries,itdidn’tlastandfewwillrememberitorhaveevenheardaboutittoday.Thisisnotquitethesameforanotherframeworkthathasitsrootsinearly2005,namely,AlexanderSmirnov’sTelamonframework,laterrenamedAjax4jsf.ThisframeworkwasoneofthefirstofitskindthatcombinedJSFandthethennewandfreshAJAXtechnology.ThebeautyofAjax4JsfwasthatitcouldaddAJAXsupporttoexistingcomponents,whichweren’tbuiltwithAJAXsupportinmind
atallbyenclosingthemamongothersinthe<a4j:region>tag.ThistechnologywasincorporatedintheExadelVisualComponentPlatform,whichwasreleasedinMarch2006andwouldlaterberenamedRichFaces,andwouldbecomeoneofthemostmemorableJSFcomponentlibraries.
AtaroundthesametimeAlexanderSmirnovstartedworkonwhateventuallywouldbecomeRichFaces,acompanycalledICEsoftstartedworkingonaJSFcomponentlibrary.ICEsofthadbeeninbusinessforacoupleofyearsandhadbeenworkingonaproductcalledICEbrowser,aJava-basedbrowser,andaproductcalledICEbrowserbeans,whichwere“lightweight,configurableJavabeancomponentsthatcanberapidlyintegratedintoJavaclientapplications.”DuringJavaOne2005ofthatyear,on27June,ICEsoftannouncedtheiritscomponentlibraryforJSF—ICEfaces.ThiswasbasedonAJAXaswellbutincorporatedAJAXdirectlyintothecomponents.ICEsoftcalleditsspecifictechnique“patentpendingDirect-to-DOM™,”whichbasicallymeantthatchangescomingfromtheserverweredirectlyinjectedintotheDOMtreestructureofawebpage.Afinalversionwasn’tavailablerightawaythough,butanearlyaccessreleasewasprovided.Thiswasclosedsourcebutcost-free.
Meanwhile,JSFEGmemberJacobHookom,inspiredbyHansBergsten’sconcernsabouttheunsuitabilityofJSP,grabbedthebullbythehornsandstartedworkinghimselfonthatalternativetemplatinglanguageenvisionedbyBergsten.InAugust2005thisworkhadprogressedintoausableinitialversion.Thenameofthistemplatinglanguage?Facelets!ItimmediatelytooktheJSFworldbystorm.KitoMannpublishedthefirstpartofaseriesofarticlesaboutitonJSFCentraltheveryfirstmonth,andRichardHightower
publishedthefamousarticle“FaceletsfitsJSFlikeaglove”severalmonthslater.
Oraclehadnotbeensittingstilleitherin2005,andafterabout16(!)earlyaccessreleasesitannouncedinlate2005attheJavaPolisconferenceinAntwerpen(nowadayscalledDevoxx)thatADFFaceswouldbedonatedtoMyFacesandthusbecomeopensource.
Inthefirstmonthof2006,JacobHookomandAdamWinercontemplatedtheterribleimplementationofJSF’sstatesavemechanism.Thisworkedbyfirstcreatingacomponenttreefromatemplateandthen,neartheendoftherequest,blindlyserializingtheentiretreewithalldatathatmayhavebeenputthereduringtherequest.Duringapostbackthetreeisrestoredfromthisserializedform(hencethenameofthephase“restoreview”).Thisisatremendouswaste,asthemajorityofthisinformationisalreadyavailableinthetemplate.EspeciallywhendoingAJAXrequestswithclient-sidestatesavingthisposesaverybigburden,butitisalsoaproblemwhenstoringthisstateontheserverasitmassivelyincreasesJSF’smemoryusage.Oneofthemainreasonsfordoingstatesavinginsuchterriblewayagainhastodowiththatonedecision:tosupportJSP.WithJSF1.2abouttogofinal,therewasunfortunatelynotimelefttofixthisforversion1.2.
EventhoughitwasclearatthispointthatFaceletswasthefutureofJSF,whenJSF1.2waseventuallyreleasedinMay2006itstillcontainedonlyJSP.Notallwasbadthough.ThankstoacooperationbetweentheJSFandJSPEGs,arevisionofJSPwasreleased,JSP2.1,whichwasmuchbetteralignedwiththedemandsofJSF.Ontopofthat,JSP’sexpressionlanguageandJSF’sexpressionlanguagewere
merged.TheresultwasUEL(UnifiedExpressionLanguage).AverypracticaladvantageofUEListhatJSFcomponentsnolongerhavetoconvertStringsmanuallyintoexpressionsbutdirectlyreceiveaValueExpressionfromthetemplatinglanguage.BothJSP2.1andJSF1.2becamepartofJavaEE5,whichwasreleasedatthesametime.
OnJune13,2006,theMyFacescommunityannouncedthatthedonatedprojectwouldhaveitsnamechangedtoTrinidad.ADFFaceskeptexistingatOracle,though,butwasbasedonTrinidadwithsomeextrafeatures(suchassupportforPortals,JSR227,etc.).Justtwoweekspriortothat,onMay31,2006,ICEsoftannounceditsfree,althoughstillclosedsource,communityedition.Afewmonthslater,onNovember14,2006,ICEsoftwouldfullyopensourceICEfacesundertheMPLlicense.RichFaces,stillclosedsourceatthatpointandbeingsoldbyExadel,wouldnotstaybehindforlongthough,andsomefourmonthslater,onMarch29,2007,ExadelannouncedapartnershipwithRedHatthatmadeRichFacesavailableunderanopensourcelicenseandavailableandsupportedviaitsJBossgroup.
OntoMaturityOnMay22,2007,thespecificationworkforJSF2.0began.Thescopewashugelyambitiousandpromisednotonlytofixmanyoftheissuesthatpeoplehadbeencomplainingaboutbutalsotointroducequiteabunchofnewfeatures.MentionedamongthemanygoalsintheJSRwasaparticularlyinterestingonewhenlookingatthebiggerpicture—extractingthemanagedbeanfacilityfromJSFandmakingitavailablefortheentireplatform.
Duringthefallof2007thecommunitywaspolledforanamefortheJSFRI.Fournamesrosetothetop,butasisoftenthecasenoneofthesenamescouldbeapprovedbySun’slegaldepartment.EventuallyMojarrawasproposed,andperhapstothesurpriseofsomethisonedidpasslegal’sscrutiny.RyanLubke,oneofthemainJSFcommittersthen,madetheofficialannouncementonDecember5,2007.
Alittleunderayearlater,onOctober29,2008,ÇağatayÇivicistartedanewlibrary,PrimeFaces.ThenamederivesfromÇağatay’snickname,whichisOptimusPrime,thecourageousleaderoftheheroicautobotsinthefictionalTransformersuniverse.ÇağatayhadbeeninvolvedwithJSFdevelopmentforalongtimeandhadworkedontheYUI4JSFJSFcomponentlibrarybefore.PrimeFaceswasinitiallybasedonJSF1.x,butwithJSF2.xloomingandtheprojectstillyoungitwouldsoonafterswitchtoJSF2.x.
OnJuly1,2009,thelong-awaitedJSF2.0finallyarrived.JSF2.0indeedfixednearlyeveryproblemthattheindustryhadwithJSF;finally,Faceletswasincludedasthedefaultviewtemplatinglanguage.JSPwaseffectivelydeprecated.ThestatesavingconcernsthatHookomandWinerbroughtforwardmorethanthreeyearsearlierwereaddressedaswell;fromthenonJSFonlysaveddeltastate(statechanges),andinrestoreviewthecomponenttreewasreloadedfromthetemplate,insteadofactuallyrestored.
AnotherbigconcernbroughtforwardbytheJSFcommunityovertheyears,JSF’sover-the-topemphasisonpostbacks,wasaddressedtoo;GETrequestsbecameafirst-classcitizeninJSF2.0.Awell-knownusabilityproblemwithJSF,sometimescalled“TheTrap,”wasthatforanumberofoperationsthedatainvolvedneededtobethesameduring
boththeoriginalrequestandthepostback.ThisisnotentirelytrivialtoguaranteeinJSF1.x.JSF2.0introducedtheso-calledviewscopeforthis,whichelegantlysolvedtheproblem.Thecreationofcustomcomponents,yetanotherproblemareaofJSF1.x,wasmademuchsimpleraswell.JSF2.0alsointroducedcoresupportforAJAX,modeledafterthewayAjax4Jsfworked,aresourceAPI,systemevents,andquiteafewotherthings.
OneofJSF2.0’sgoals,makingitsmanagedbeanfacilityusableoutsideJSF,wasimplicitlyreachedbytheCDIspec,whichwasintroducedtogetherwithJSF2.0inJavaEE6.TheCDIspecitselfhasalonghistorytoo,butoneofitsdefiningcharacteristicsisthatCDIBeansarestronglybasedonJSFManagedBeansandareessentiallyasupersetofthose.
Altogethertheimpactofallthosefixesandnewfeatureswassuchthatitsplitthecommunityessentiallyintwo;thosewhohadusedJSF1.xandneverlookedatitagainandthosewhoswitchedtoJSF2.xor,specifically,theoneswhostartedusingJSFwith2.0andneversaw1.x.Thisoftenledtoheateddebates,withthe1.xsidearguingthatJSFishorrible,andthe2.xsidenotunderstandingatallwhythatwouldbethecase.Evenatthetimeofthiswriting,whichisalmostnineyearsafterJSF2.0wasreleased,andalongerperiodthanJSF1.xeverexisted,thesesentimentsstillremaintosomedegree.
DespitethemanythingsthatJSF2.0didright,therewasonemissedopportunity;eventhoughCDIwasnowavailableandsupersededJSF’sManagedBeans,JSFchosenottodeprecateitsmanagedbeanfacilityrightaway.Evenworse,itintroducedanannotation-basedalternativetotheXML-basedsystemJSF1.xusedtodefinemanagedbeans.WithCDIalreadyouttherehavingannotationslike
javax.enterprise.context.RequestScoped,simultaneouslyintroducingajavax.faces.bean.RequestScopedannotationthatdidexactlythesamethingseemsdebatableasbest.TheEGseemedtobeawareofthisconflict,asawarningwasputinplacethatthesenewannotationswouldpossiblybesupersededbyplatformfunctionalitybeforelong.
OnDecember23,CayHorstmannraisedhisconcernsaboutthisveryunwantedsituationinanarticletitled“[email protected]?”Theresponsewasquiteclear;people,includingJavaEEbookwriterAntonioGoncalves,askedforthishugemistakethatJSF2.0hadmadetobecorrectedassoonaspossibleandtodeprecatejavax.faces.bean.ManagedBeanrightawayintheupcomingJSF2.1maintenancereleasewhichwascalledfor,amongotherthings,torectifyanothermistake(namely,theproblemJSF2.0introducedthatinadditiontoacustomResourceResolveritwasalsonecessarytoprovideacustomExternalContext,whichwasveryunclear).Whyjavax.faces.bean.ManagedBeanindeedwasn’tdeprecatedintheJSF2.1MRremainsamysterytothisday.
WhileapplicationswrittenagainsttheJSF1.xAPIswouldmostlyrununchangedonJSF2.0,oronlyneededafewsmallchanges,thecomponentlibrarieshadamuchhardertime.Specifically,theplatform-providedAJAXsupportmeantthattheexistingcomponentlibrarieswouldhavetoforegotheirownAJAXimplementationsandrebaseonthestandardAPIs.Clearlythatwasnosmallfeat,andittookalongtimeforcomponentlibrariestomigrate,withsomeneverreallymaking
theswitchatall.HerePrimeFaceswasclearlyatanadvantage.Beinga
relativelynewlibrarywithoutmuchlegacy,itmadetheswitchrelativeeasy.Beitacoincidenceornot,PrimeFaces’ascensioninpopularityseemedtostartrightafterJSF2.0wasreleased,whichwasalsotheexactsametimethatbothICEfacesandRichFacesseemedtobecomelesspopular.Althoughitmustbenotedthathardstatisticsaredifficulttoobtainandcontainmanyfacets(downloads,deployments,book,questionsasked,availablejobs,takingdifferentindustriesintoaccount,etc.),somewherearound2012PrimeFaceshadseeminglybecomethemorepopularJSFcomponentlibrary.
Inthebeginningofthatsameyear,February19,2012,ArjanTijmsandBaukeScholtz(bycoincidencealsotheauthorsofthisbook)startedtheOmniFaceslibraryforJSF.ThegoalofOmniFaceswastobeautilitylibraryforJSF,essentiallywhatApacheCommonsandGoogleGuavaaretoJavaSE.TijmsandScholtzhadworkedonaJSF-basedwebsitetogetherandfoundthattheybothhadacollectionofprivateJSFutilitiesthattheyreusedfordifferentprojects,andalsothatagreatnumberofsimilarutilitieswereessentiallyrewrittenagainandagainformanyJSFprojectsandwerepartiallyfloatingaroundinplaceslikeforummessagesandblogposts.OmniFaceswassetupinparticularnottonotcompetecomponentlibrarieslikePrimeFacesbuttoworktogetherwiththose.Hence,visual-orientedcomponentswerelargelyoutofscopeforOmniFaces.
In2012thespecificationprocessforJSF2.2wasalsoinfullswing.JSF2.2waseventuallyreleasedontheMay21,thenextyear.JSF2.2specificallycameupwithaformalversion
ofthealternativemodeinwhichFaceletscouldoperate;insteadofputtingcomponenttagsonaview,plainHTMLwasputonit,withaspecialIDlinkingthetagtoacomponent.SuchamodeisgenerallyspeakingsomewhatlessinterestingtoJSFdevelopersbutappealsspecificallytowebdesignerswhocanmoreeasilyuseplainHTMLtoolsforsuchviews.JSF2.2alsointroducedaCDIcompatible@ViewScopedannotation,whichremovedoneofthelastreasonstostillusetheJSFmanagedbeanfacilityinJSF2.1,namely,thatinthatversion@ViewScopedonlyworkedonthosebeans.JSF2.2alsointroducedtwonewbigfeatures,FacesFlowandResourceContracts,buttheseseemtohaveseenlittleuptakeinpractice.
JustpriortothestartofJSF2.3,onJuly20,2014,RichFacesleadBrianLeathemannouncedonhisblogthatRichFaces5,thenext-generationversionofRichFaces,wouldbecanceled.Instead,RichFaceswould“pursueapathofstabilityoverinnovation,”whichmeansthatJBosswillmakeRichFaces4.xcompatiblewithJSF2.2andportbackafewthingsthatwereindevelopmentforRichFaces5.Whilethepostwassomewhatoptimistic,itstronglylookedlikethewritingwasonthewallforRichFaces.
OnAugust26,2014,thespecificationworkforJSF2.3started.Anewco-specleadwasintroduced—ManfredRiem,whouptothenhadbeenworkingmostlyontheimplementationsideofMojarra,doingsuchthingsasmigratinghundredsofthetestsforwhichJSFisfamousawayfromtheancientandretiredCactusframeworktoamoremodernMaven-basedone,andmakingsurethegazillionsofopenMojarraissueswerereducedtoamanageablenumber.
JSF2.3startedoffwithaperhapssomewhatremarkablemessagethatOraclehadonlyafewresourcesavailable.Duringthespecificationprocessthosefewresourcesdroppedtoanumberthatfewwouldhaveexpected—absolutelyzero.Basically,afterJavaOne2015,nearlyallofthespecleadsjustvanishedandmostspecsasaresultabruptlygroundtoahalt.JoshJuneaureportedaboutthisinhisfamousstudy,“JavaEE8,WhatIstheCurrentStatus:CaseStudyforCompletedWorkSinceLate2015,”whichundeniablemakesitclearbyshowinggraphsofe-mails,commits,andissuesresolvedthatOraclehadjustwalkedaway.
TheopennessoftheJSFanditsRIMojarrawerefortunatelysuchthatthespecificationworkandimplementationthereofinMojarracanlargelybecarriedonbytheotherEGmembers,whichindeedhappens.
MeanwhileonFebruary12,2016,RedHatannouncedthatRichFaceswouldbeendoflived(EOL)laterthatyear,namely,inJune2016.OneofthemostpopularJSFcomponentlibrariesatsomepoint,oftennamedsomethinglike“Oneofthebigthree,”effectivelywasnomore.OnJune20,2016,thelastrealcommittotheprojectwasdone,“RF-14279:updateJSDoc.”TwodayslaterRedHatreleasedRichFaces4.5.17andtheGitHubreposwereputintoarchived(readonly)mode.BrianLeathem,whoisstillaJSF2.3EGmember,announcedafewdayslateronFebruary18thathewouldnolongerbedoinganyJSF-relatedwork.
RejuvenationInlate2016theJSFspecleadsbrieflyreturned,butwiththemessagethatthespecmustbecompletedinonlyafewweeks,
sothe(somewhat)lengthyfinalizationprocesscouldstart.OnMarch28,2017,JSF2.3wastheneventuallyreleased,bringingwithitthestartofreplacingJSFnativeartifactswithCDIversions,andfinallysomethingwhichshouldhavehappenedyearsago:thedeprecationoftheJSFmanagedbeanfacilityinfavorofusingCDIbeans.OtherfeaturesaresupportforWebSocketusingtheJavaEEWebSocketAPIsdonatedbyOmniFaces,theintrospectionofavailableviewresourcesinthesystem,andasearchexpressionframeworkdonatedbyPrimeFaces.
FollowingthesomewhatturbulentdevelopmentoftheJSF2.3specistheevenmoreturbulentannouncementbyOraclein2017thatJavaEE,thusincludingJSF,wouldbetransferredtotheEclipsefoundation.Oraclewouldstopleadingthespecsitownedbefore,whichagainincludesJSF.ThiswouldmeanthatMojarrawouldbere-licensed,andJSFwouldbeevolvedbyanewprocesswithlikelydifferentleads.Atthetimeofwriting,thistransferisinfullswing.
(1) (2)
©BaukeScholtz,ArjanTijms2018BaukeScholtzandArjanTijms,TheDefinitiveGuidetoJSFinJavaEE8,https://doi.org/10.1007/978-1-4842-3387-0_2
2.FromZerotoHelloWorld
BaukeScholtz andArjanTijms
Willemstad,Curaçao Amsterdam,Noord-Holland,TheNetherlands
InthischapteryouwilllearnhowtosetupaJSF(JavaServerFaces)developmentenvironmentwiththeEclipseIDE(integrateddevelopmentenvironment),thePayaraapplicationserver,andH2databasefromscratch.
InstallingJavaSEJDKYouprobablyalreadyknowthatJavaSEisavailableasJREforendusersandasJDKforsoftwaredevelopers.EclipseitselfdoesnotstrictlyrequireaJDKasithasitsowncompiler.JSFbeingasoftwarelibrarydoesnotrequireaJDKtoruneither.Payara,however,doesrequireaJDKtorun,primarilyinordertobeabletocompileJSPfiles,eventhoughJSPhasbeendeprecatedasJSFviewtechnologysinceJSF2.0.
Therefore,youneedtomakesurethatyoualreadyhaveaJDKinstalledasperOracle’sinstructions.ThecurrentJavaSEversionis9,butasJavaEE8wasdesignedforJavaSE8whichiscurrentlymoremature,JDK8isrecommended:https://docs.oracle.com/javase/8/docs/tech
1 2
notes/guides/install/install_overview.html
.ThemostimportantpartsarethatthePATHenvironment
variablecoversthe/binfoldercontainingtheJavaexecutables(e.g.,"pathto/jdk/bin"),andthattheJAVA_HOMEenvironmentvariableissettotheJDKrootfolder(e.g.,"pathto/jdk").ThisisnotstrictlyrequiredbyJSF,butEclipseandPayaraneedthis.EclipsewillneedthePATHinordertofindtheJavaexecutables.PayarawillneedtheJAVA_HOMEinordertofindtheJDKtools.
WHATABOUTJAVAEE?NotethatyoudonotneedtodownloadandinstallJavaEEfromOracle.comeventhoughJSFitselfispartofJavaEE.JavaEEisbasicallyanabstractspecificationofwhichtheso-calledapplicationserversrepresenttheconcreteimplementations.ExamplesofthoseapplicationserversarePayara,WildFly,TomEE,GlassFish,andLiberty.ItisexactlythoseapplicationserversthatactuallyprovideamongothersJSF(JavaServerFaces),EL(ExpressionLanguage),CDI(ContextsandDependencyInjection),EJB(EnterpriseJavaBeans),JPA(JavaPersistenceAPI),Servlet,WebSocket,andJSON-P(JavaScriptObjectNotationProcessing),APIs(applicationprogramminginterfaces)outofthebox.
Therealsoexistso-calledservletcontainerswhichprovidebasicallyonlytheServlet,JASPIC(JavaAuthenticationServiceProviderInterfaceforContainers),JSP(JavaServerPages),EL,andWebSocketAPIsoutofthebox,suchasTomcatandJetty.However,itwouldrequiresomeworktomanuallyinstallandconfigure,amongothers,JSF,JSTL(JSP
StandardTagLibrary),CDI,EJB,andJPAonsuchaservletcontainer.ItisnoteventrivialinthecaseofEJBasitrequiresmodifyingtheservletcontainer’sinternals.Thatis,bytheway,exactlywhyTomEEexists.It’saJavaEEapplicationserverbuiltontopofthebarebonesTomcatservletcontainerengine.
ComingbacktotheJavaEEdownloadatOracle.com,itwouldgiveyoubasicallytheGlassFishserver,alongwithabunchofdocumentationandoptionallytheNetbeansIDE.WedonotneeditaswearealreadyusingPayaraastheJavaEEapplicationserver,andaretargetingEclipseasIDE.Therefore,theJavaSEJDKissufficient.
InstallingPayaraPayaraisanopensourceJavaEEapplicationserverwhichisin2014forkedfromGlassFish.ItisbasicallyaresponsetoOracle’sannouncementtostopitscommercialsupportforGlassFish,socompaniespreviouslyusingGlassFishcommerciallycouldeffortlesslyswitchtoPayaraandcontinueenjoyingcommercialsupport.ThankstocommercialsupportforbusinesscustomerspreviouslyusingGlassFish,thePayaraapplicationserversoftwarecancontinuouslybebug-fixedandimproved.
ThefirstPayaraversionwithJSF2.3integratedis5.Youcandownloaditfromhttps://payara.fish.Makesureyouchooseeitherthe“PayaraServerFull”or“PayaraServerWebProfile”downloadandnot,forexample,the“PayaraMicro”or“PayaraEmbedded,”astheyhaveotherpurposes.Installingisbasicallyamatterofunzippingthedownloadedfileandputtingitsomewhereinyourhomefolder.We’llleave
itthereuntilwehaveEclipseupandrunning,sothatwecanthenintegratePayarainEclipseandletitmanagetheserver.
HOWABOUTOTHERSERVERS?ThechoiceforPayarainthisbookisprimarilybecauseitisattimeofthiswritingoneoftheveryfewavailableJavaEEapplicationserverswithJSF2.3integrated.TheotheroneisGlassFish,butwewouldrathernotadvocateitasitwouldbasicallyoffernocommercialsupportorbugfixes.GlassFishmustbeseenasatruereferenceimplementationforotherapplicationservervendorssotheycan,ifnecessary,buildtheirapplicationserverimplementationbyexample.
WildFly,TomEE,andLibertydidnot,atthetimeofwriting,haveaversionavailablewithJSF2.3integrated.
InstallingEclipseEclipseisanopensourceIDEwritteninJava.Itisbasicallylikenotepadbutwiththousandsifnotmillionsofextrafeatures,suchasautomaticallycompilingclassfiles,buildingaWARfilewiththem,anddeployingittoanapplicationserverwithouttheneedtomanuallyfiddlearoundwithjavacinacommandconsole.
Eclipseisavailableinalotofflavors.Aswe’regoingtodevelopwithJavaEE,weneedtheonesaying“EclipseIDEforJavaEEdevelopers.”It’susuallythetop-rankeddownloadlinkathttp://eclipse.org/downloads/eclipse-packages/.Alsohere,installingisbasicallyamatterofunzippingthedownloadedfileandputtingitsomewhereinyourhomefolder.
InWindowsandLinuxyou’llfindtheeclipse.iniconfigurationfileintheunzippedfolder.InMacOSthisconfigurationfileislocatedinEclipse.app/Contents/Eclipse.Openthisfileforediting.WewanttoincreasetheallocatedmemoryforEclipse.Atthebottomofeclipse.ini,you’llfindthefollowinglines:-Xms256m-Xmx1024m
Thissets,respectively,theinitialandmaximummemorysizepoolwhichEclipsemayuse.ThisisabittoolowwhenyouwanttodevelopabitofadecentJavaEEapplication.Let’satleastdoubleboththevalues.
-Xms512m
-Xmx2g
Watchoutthatyoudon’tdeclaremorethantheavailablephysicalmemory.Whentheactualmemoryusageexceedstheavailablephysicalmemory,itwillcontinueintovirtualmemory,usuallyinaswapfileondisk.Thiswillgreatlydecreaseperformanceandresultinmajorhiccupsandslowdowns.
NowyoucanstartEclipsebyexecutingtheeclipseexecutableintheunzippedfolder.Youwillbeaskedtoselectadirectoryasworkspace.ThisisthedirectorywhereEclipsewillsaveallworkspaceprojectsandmetadata.
Afterthat,Eclipsewillshowawelcomescreen.Thisisnotinterestingfornow.YoucanclicktheWorkbenchbuttonontherighttoptoclosethewelcomescreen.Untickifnecessary“AlwaysshowWelcomeatstartup”onthebottomright.After
that,youwillentertheworkbench.Bydefault,itlookslikethescreenshotinFigure2-1.
Figure2-1 Eclipseworkbench
CONFIGURINGECLIPSEBeforewecanstartwritingcode,wewouldliketofine-tuneEclipseabitsothatwedon’teventuallyendupintroubleorwithannoyances.Eclipsehasanenormousamountofsettings,andsomeofitsdefaultvaluesshouldnothavebeenthedefaultvalues.YoucanverifyandconfigurethesettingsviaWindow➤Preferences.
General➤Workspace➤TextfileencodingmustbesettoUTF-8.ParticularlyinWindowsthismightotherwisedefaulttotheproprietary
encodingCP-1252whichdoesnotsupportanycharactersbeyondtheLatinrange.WhenreadingandsavingUnicodefileswithCP-1252,youriskseeingunintelligiblesequencesofcharacters.Thisisalsocalled“mojibake.”.
General➤Workspace➤NewtextfilelinedelimitermustbesettoUnix.ItworksjustfineonWindowsaswell.Thiswillparticularlykeepversioncontrolsystemshappy.Otherwise,developerspullingcodeondifferentoperatingsystemsmightfaceconfusingconflictsordiffscausedbydifferentlineendings.
General➤Editors➤Texteditors➤Spellingshouldpreferablybedisabled.Thiswillsaveyoufromapotentiallybigannoyance,becauseitunnecessarilyalsospellchecksXMLconfigurationfilessuchasfacesconfig.xmlandweb.xml,causingconfusingerrorsandwarningsinthosefiles.
Java➤Compiler➤Compilercompliancelevelmustbesetto1.8.ThisistheminimumrequiredJavaversionforJavaEE8.
Java➤InstalledJREsmustbesettotheJDK,nottotheJRE.ThissettingwillnormallyalsobeusedtoexecutetheintegratedapplicationserverwhichusuallyrequirestheJDK.
INSTALLINGJBOSSTOOLSPLUG-INStandardEclipseforJavaEEinitscurrentversiondoesnotsupportanyCDItools.IthasnowizardstocreateCDImanagedbeans,orautocompletionandhyperlinkingforCDImanagedbeansinJSFpages.TheJBossToolsplug-inisanextensiveplug-inwhichoffersamongotherstheCDItools.ThisisveryusefulwhendevelopingaJavaEEwebapplication.
Inordertoinstallit,gotoHelp➤EclipseMarketplace.Enterinthesearchfield“JBossTools”andclickGo.ScrollabitthroughtheresultsuntilyouseeJBossToolsFinalandthenclickInstall(seeFigure2-2).
1
2
Figure2-2 JBossToolsintheEclipseMarketplace
Inthenextstep,you’llseeafairlylargelistofallJBossTools’offerings.Wedon’tneedallofthem.ThelistindeedalsoincludessomeJSF-relatedtools,buttheyarenotterriblyuseful.TheVisualPageEditorisnotatalluseful.DragginganddroppingtogetheraJSFpagedoesn’tmakeyouagoodJSFdeveloper.Thatcanonlybeachievedbyjustwritingcodeyourself.Moreover,havingtoomanyunusedfeaturesinstalledandevenimplicitlyenabledmaymakeEclipseterriblyslow.Thefewerfeaturesyouselect,thelesschancethatyouwillbesurprisedaboutchangesintheIDEbehavior.So,untickthetopcheckboxandthentickonlythecheckboxwhichsays“ContextandDependencyInjectionTools”(seeFigure2-3).
Figure2-3 SelectonlytheCDItoolsfornow
Next,acceptthetermsofthelicenseagreementandcompletethewizarduntilEclipseisrestarted.
INTEGRATINGNEWSERVERINECLIPSEWeneedtofamilarizeEclipsewithanyinstalledapplicationserverssothatEclipsecanseamlesslylinkitsJavaEEAPIlibrariesintheproject’sbuildpath(read:thecompiletimeclasspathoftheproject).ThisismandatoryinordertobeabletoimportclassesfromtheJavaEEAPIinyourproject.Youknow,theapplicationserveritselfrepresentstheconcreteimplementationoftheabstractJavaEEAPI.
InordertointegrateanewapplicationserverinEclipse,firstcheckthebottomsectionoftheworkbenchwithseveraltabsrepresentingseveralViews(youcanaddnewonesvia
Window➤ShowView).ClicktheServerstabtoopentheserversview(seeFigure2-4).Clickthelinkwhichsays“Noserversareavailable.Clickthislinktocreateanewserver....”
Figure2-4 ServersviewofEclipseWorkbench
Fromthelistofavailableservertools,selectOracle➤GlassFishTools(seeFigure2-5).
Figure2-5 SelectingGlassFishToolsinNewServerwizard
AfterclickingNextforthefirsttime,itwilldownloadtheplug-ininthebackgroundandrequestyoutoacceptthelicenseagreementbeforeinstallingtheplug-in.Thisplug-inismandatoryinordertomanageanyGlassFish-basedserverfrominsidetheworkbench—amongothers,addingandremovingEclipseprojectstothedeploymentsfolder,startingandstoppingtheserver,andrunningtheserverindebugmode.Onceit’sfinishedinstalling,itwillrequestyoutorestartEclipse.Takeactionaccordingly.
Oncereturnedintotheworkspace,clickthesamelinkintheServersviewagain.You’llnowseeaGlassFish➤GlassFishoption.SelectthisandsettheServernamefieldto“Payara”(seeFigure2-6).
Figure2-6 SelectingGlassFishserverinNewServerwizardandnamingitPayara
Advancetothenextstep.Here,youshouldpointtheGlassFishlocationfieldtotheglassfishsubfolderofthePayarainstallation,therewhereyouhaveunzippeditafterdownloading(seeFigure2-7).
Figure2-7 SpecifyingGlassFishlocationinNewServerwizard
CompletetheremainderoftheNewServerwizardwithdefaultsettings.Youdon’tneedtoeditanyotherfields.ThenewlyaddedserverwillnowappearintheServersview(seeFigure2-8).
Figure2-8 ThePayaraserverinServersview
CreatingNewProjectinEclipseWe'renowreadytocreateanewprojectforourJSFapplicationinEclipse.ThiscanbedoneviatheleftsectionoftheworkbenchwhichbydefaultshowsonlyonetabrepresentingtheProjectExplorerview(alsohere,youcanaddnewviewsviaWindow➤ShowView).Right-clickanywhereinthisviewandselectNew➤Project.It’llshowtheNew
Projectwizardwhichmayhaveabittoomanyoptions.Eclipse,beinganIDEformanydifferentprojecttasks,
offersabewilderingamountofdifferentprojecttypesfromwhichtochoose.ForaJavaEE-basedapplicationwhichisgoingtobedeployedasasimpleWARfile,therearebasicallytwoprojecttypesthatwecouldchoosefrom:Web➤DynamicWebProjectandMaven➤MavenProject.
ThedifferenceisthatthefirstisanEclipsenativeprojectthatreallyonlyworksonEclipse,whilethelatterisauniversaltypeofprojectthatcanbebuiltbyanyIDE,aswellaseasilyonthecommandlineandbyvariousCIserverssuchasTravisandJenkins.Forthisreason,theMavenprojecttypeisreallytheonlyviablechoice(seeFigure2-9).
Figure2-9 SelectingMavenProjectinNewProjectwizard(notetheDynamicWebProjectasanotherbutnon-viableoption)
Inthenextstep,makesurethattheoptionCreateasimpleproject(skiparchetypeselection)ischecked(seeFigure2-10).ThiswillletusstartwithareallyemptyMavenprojectsothatwecanconfigureandpopulateitourselves.Ofcourse,youcouldalsochoosefromanarchetype,whichisbasicallyatemplateprojectwithseveralalreadypreparedfilesandconfigurations.Butwedon’tneedanyfornow.
Figure2-10 Checking“Createasimpleproject”inNewMavenProjectwizard
Inthenextstep,wecanspecifyourownMavencoordinatesoftheproject.TheMavencoordinatesconsistof,amongothers,GroupId,ArtifactId,andVersion,alsoknownasGAVintheMavenworld.TheGroupIdusuallymatchestherootpackagenameyou’regoingtouse,suchascom.example.TheArtifactIdusuallyrepresentstheprojectnameyou’regoingtouse.Forsimplicityandinordertobeconsistentintherestofthebook,we’lluseproject.TheVersioncanbekeptdefaultat0.0.1-SNAPSHOT.FinallythePackagingshouldbesettowar.
Figure2-11 FillingouttheMavenGAVinnewMavenProjectwizard
CompletetheremainderoftheNewMavenProjectwizard(seeFigure2-11).Youdon’tneedtoeditanyotherfields.Onceyou’vefinishedthewizard,you’llgettoseetheprojectstructureintheProjectExplorerview(seeFigure2-12).
Figure2-12 ThenewlycreatedMavenprojectinEclipse
Unfortunately,theEclipse-generatedpom.xml,whichisthemainindicatoroftheprojectbeingaMavenprojectandcontainingitsconfiguration,islessthanideal.It’snotcurrentanymore,evenwhengeneratedbythelatestEclipse,theOxygen2(December2017).Youcanalreadyseethatbythepom.xmlfilewhichismarkedwithanalarmingredcrossandanerrormessageintheMarkersview.Anyprojectthathasatleastonesuchredcrosscannotbebuiltandwon’tbedeployable.Theerrormessageliterallysays“web.xmlismissingand<failOnMissingWebXml>issettotrue.”Inotherwords,Mavensomehowthinksthatit’sstillapre-JavaEE6project,whenthiswasindeeddisallowed.
InordertosolvethisproblemandtocatchuptheEclipse-generatedpom.xmlwiththecurrentstandards,weneedtoopenpom.xmlforeditingandadjustitasshowninthefollowingcode:<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.example</groupId>
<artifactId>project</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>
UTF-8
</project.build.sourceEncoding>
<project.reporting.outputEncoding>
UTF-8
</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.sourc
e>
<maven.compiler.target>1.8</maven.compiler.targe
t>
<failOnMissingWebXml>false</failOnMissingWebXml>
</properties>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Onceyousavethisfile,Eclipsewillautomaticallysortitoutbyitselfandclearoutthealarmingredcross.Nowthatlooksmuchbetter.We'llbrieflygothroughthemostimportantsettingshere.
Packagingwar—indicatestheprojectisa“web”project,andthattheproject’scontentswillbeassembledintoawebarchive.
EncodingUTF-8—setstheencodingthatthesourcefilesareinandwithwhichthe(reporting)outputfilesshouldbegenerated.Thismakesthebuildrepeatable,asitotherwisewoulddefaulttothesystemdefaultencoding(again,aratherbaddefault).
Compiler1.8—setsboththeversionofJavausedinthe.javasourcefilesaswellasthebytecodeoutputinthe.classfiles.Withoutsettingthis,Mavendefaultstotheoldestversionpossible,andsometimesevenalowerversionthanthat.
failOnMissingWebXmlfalse—olderversionsofJavaEErequiredtheWEB-INFweb.xmltobepresent.EventhoughthishasnotbeenrequiredanymoresinceJavaEE6,whichwasreleasedin2009,Mavenstillchecksforthisfiletobepresent.Settingthistofalsepreventsthisunnecessarycheck.
Dependencyjavax:javaee-api:8.0provided—thisdeclaresadependencyontheJavaEE8API,andmakessurealltheJavaEEtypeslike@Namedareknowntothecompiler.Thisdependencyissettoprovided"sincethosetypesarealreadyprovidedbythetargetruntime,whichisinourcasePayara.
Theywillthenonlybeusedtocompilethesourcecodeagainstandwon’tbeincludedinthegenerated.war.Youneedtomakeabsolutelysurethatanycompiletimedependencywhichisalreadyprovidedbythetargetruntimeissettoprovided;otherwiseitwilleventuallyendupinthegenerated.warandyoumayrunintoclassloadingtroublewhereinduplicatedifferentversionedlibrariesareconflictingwitheachother.Incaseyou’reactuallynottargetingafull-fledgedJavaEEserverbutabarebonesservletcontainer,youwouldneedtoadjustthedependenciesasinstructedintheREADMEofMojarra, oneoftheavailableJSFimplementationsandactuallytheoneusedunderthecoverofPayara.
Now,inEclipse’sMarkersview,there’sonlyonewarningleftwhichsays“BuildpathspecifiesexecutionenvironmentJ2SE-1.5.TherearenoJREsinstalledintheworkspacethatarestrictlycompatiblewiththisenvironment.”Well,thatbasicallymeansthatEclipserecognizesthisMavenprojectasaJava1.5-onlyprojectwhilewedon’tactuallyhaveJavaSE5installed,andinspiteofthecompilerversioninpom.xmlbeingsetto1.8.
InordertotellEclipsethatthisisreallyaJava1.8project,weneedtoright-clicktheprojectinProjectExplorerviewandchooseProperties.IntheProjectFacetssectionyoushouldchangetheversionoftheJavafacetfrom1.5to1.8(or9ifyouhaveJDK9installed)(seeFigure2-13).Whileatit,wealsoneedtoupdatetheServletAPIversionandaddtheCDI,JSF,andJPAfacets.TheServletAPIisrepresentedbythe“DynamicWebModule”entry.Thisneedstobesettoversion4.0,whichmatchesJavaEE8.Furtherthe“CDI,”“JavaServerFaces,”and“JPA”entriesneedtobeselected.The“CDI”facetis,bytheway,onlyavailableafterhavinginstalledtheJBossToolsasinstructedinthesection“InstallingJBossToolsPlug-in.”
Unfortunately,inthelatestavailableEclipseversion,
3
Oxygen2fromDecember2017,thereisn’taJSF2.3orJPA2.2versionavailableyetinthedropdown.ThehighestavailableversionsareJSF2.2andJPA2.1.Thisisnotabigproblem.Itsonlyinfluenceisontheavailablecodegeneratorsandwizards.WecanalwaysadjusttheEclipse-generatedfacesconfig.xmlandpersistence.xmlfilesafterwardtomatchtheJavaEE8compatibleversions.
Figure2-13 TheProjectFacetssectionoftheprojectproperties(notethattheServletAPIversionisrepresentedby“DynamicWebModule”)
Asyoucanseeintheyellowwarningbar,onlyEclipserequiresfurtherconfiguration.ThisconcernsthenewlyselectedJSFandJPAfacets.Whenclickingthelink,wegettoseetheModifyFacetedProjectwizard(seeFigure2-14).
ThefirststepoftheModifyFacetedProjectwizardallows
ustoconfiguretheJPAfacet.WeneedtomakesurethatEclipseisbeinginstructedthattheJPAimplementationisalreadyprovidedbythetargetruntimeandthusEclipsedoesn’tneedtoincludeanylibraries.Thiscanbeachievedbychoosingthe“DisableLibraryConfiguration”optionintheJPAimplementationfield.Aswe’regoingtousethePayara-providedHibernateastheactualJPAimplementation,whichautomaticallysupportsdiscoveringof@Entityannotatedclasses,we’dliketoinstructEclipsetodothesame;otherwiseitwouldautomaticallyaddentitiestothepersistence.xmlwhengoingthroughtheentitycodegenerationwizard,orshowwarningswhenwecreateonemanuallyanddon’taddittothepersistence.xml.
Figure2-14 TheJPAFacetconfiguration
Notethatconfiguringadatabaseconnectionisnotnecessaryfornowaswe’regoingtouseanembeddeddatabase.
InthenextstepoftheModifyFacetedProjectwizard,wecanconfiguretheJSFcapabilities(seeFigure2-15).Alsohere,
weneedtomakesurethatEclipseisbeinginstructedthattheJSFimplementationisalreadyprovidedbythetargetruntimeandthusEclipsedoesn’tneedtoincludeanylibraries.Thiscanbeachievedbychoosingthe“DisableLibraryConfiguration”optionintheJSFImplementationLibraryfield.Further,weneedtorenametheservletnameoftheFacesServlettomatchthefictiveinstancevariablename:facesServlet.Lastbutnotleast,weneedtochangetheURLmappingpatternfromtheJurassicfaces*tothemodern*.xhtml.
Figure2-15 TheJSFCapabilitiesconfiguration
Actually,theentireregistrationoftheFacesServletin
web.xmlis,sinceJSF2.2,notstrictlynecessaryanymore;youcouldevenunchecktheConfigureJSFservletindeploymentdescriptoroptionandrelyonthedefaultauto-registeredmappingsoffaces*,*.faces,*.jsfand*.xhtml.However,asthisallowsendusersandevensearchbotstoopentheverysameJSFpagebydifferentURLs,andthuscausesconfusionamongendusersandduplicatecontentpenaltiesamongsearchbots,we’dbetterrestricttoonlyoneexplicitlyconfiguredURLpattern.
Now,finishandapplyallthewizardsanddialogs.TheJPAplug-inonlyputsthegeneratedpersistence.xmlatthewrongplace.Youneedtomanuallymoveitintosrc/main/resources/META-INF.Figure2-16showsushowtheworkbenchlooksnow.
Figure2-16 CorrectlyconfiguredJavaEE8MavenprojectinEclipse
WeonlyneedtoadjustallthedeploymentdescriptorstocatchuptotheactuallyusedServlet,JSF,JPA,andCDIversions.ThisisnormallydonebyadjustingtherootelementofthedeploymentdescriptorXMLfiletosetthedesiredXMLschemasandtheversion.
YoucanfindallJavaEE8schemasathttp://xmlns.jcp.org/xml/ns/javaee,whichisanactualwebpagewhichcurrentlyredirectstosomelandingpageatOracle.com.ThismaychangeinthefuturegiventhatJavaEE8iscurrentlyintheprocessofbeingtransferredfromOracletoEclipse.YoucanopenthedeploymentdescriptorXMLfileforeditingbydouble-clickingitandthenselecting
theSourcetabintheeditor.ThecorrectrootelementdeclarationsforJavaEE8compatibledeploymentdescriptorsarethusasfollows:
src/main/webappWEB-INFweb.xmlforServlet4.0:
<?xmlversion="1.0"encoding="UTF-8"?>
<web-app
xmlns:="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
>
<!--Servletconfigurationhere.-->
</web-app>
src/main/webappWEB-INFfacesconfig.xmlforJSF2.3:
<?xmlversion="1.0"encoding="UTF-8"?>
<facesconfig
xmlns:="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-
facesconfig_2_3.xsd"
version="2.3"
>
<!--JSFconfigurationhere.-->
</facesconfig>
src/main/resources/META-INF/persistence.xmlforJPA2.2:
<?xmlversion="1.0"encoding="UTF-8"?>
<persistence
xmlns:="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persisten
ce
http://xmlns.jcp.org/xml/ns/persistence/persistence_2
_2.xsd"
version="2.2"
>
<!--JPAconfigurationhere.-->
</persistence>
OnlythecurrentlyavailableJPAplug-inofEclipsewillshowanerroronthis.YoucouldignorethisbydisablingtheJPAvalidatorintheproject’sproperties,butyoucanalsojuststepbacktoaJPA2.1compatiblepersistence.xmlforthetimebeing.
Finally,forsakeofcompletenessweneedtocreateonemoredeploymentdescriptorfile,theoneforCDI2.0.Thisisn’tautomaticallygeneratedasit’snotrequired.CDIisbydefaultalwaysenabledinanyJavaEE8compatiblewebapplication.It’sevenmandatoryforthefunctioningofJSF.Amongothersthenew<f:websocket>reliesfullyonCDI.Right-clickthe/WEB-INFfolderoftheprojectandchooseNew➤beans.xmlFile.TheNewbeans.xmlFilewizardwhichappearsnowispartoftheJBossToolsplug-in.Justkeepalloptionsdefaultandfinishthewizard.It’llgeneratethefileasfollows:src/main/webappWEB-INFbeans.xmlforCDI2.0:<?xmlversion="1.0"encoding="UTF-8"?>
<beans
xmlns:="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/java
ee
http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd
"
version="2.0"bean-discovery-mode="annotated"
>
<!--CDIconfigurationhere.-->
</beans>
CREATINGTHEBACKINGBEANCLASSWiththeprojectnowcorrectlyconfiguredwecanstartwithdevelopingtheactualMVCapplication.TheControllerpartofMVCisalreadyconfiguredasFacesServletinweb.xml.TheModelpartofMVCiswhatwe’regoingtocreatenow.It’sbasicallyjustasimpleJavaclasswhichisbyJSFconventioncalledaBackingBeansinceit“backs”aView.
Right-clickthesrc/main/javafolderoftheprojectandchooseNew➤Bean.TheNewCDIBeanwizardwhichappearsnowisalsopartoftheJBossToolsplug-in(seeFigure2-17).Inthiswizard,setthePackagetocom.example.project.view,settheNametoHelloWorld,ticktheAdd@Namedcheckbox,andfinallysettheScopeto@RequestScoped.Therestofthefieldscanbekeptdefaultorempty.
Figure2-17 TheJBossTools-providedNewCDIBeanwizardinEclipse
Theclasseditorwillnowopenwiththenewlycreatedbackingbeanclass.We’llmodifyittogetridoftheuselessconstructor;addtwoproperties,inputandoutput;andaccompanytheinputpropertywithagetterandsetterpair,theoutputpropertywithonlyagetter,andasubmit()actionmethodwhichpreparestheoutputpropertybasedontheinputproperty.Asahint,inEclipseafterenteringthe
properties,youcanright-clickanywhereintheclasseditorandchooseSource➤GenerateGettersandSetterstohavetheIDEtogeneratethem.Initsentirety,theeditedbackingbeanclassshouldlookasfollows:
packagecom.example.project.view;
importjavax.enterprise.context.RequestScoped;
importjavax.inject.Named;
@Named@RequestScoped
publicclassHelloWorld{
privateStringinput;
privateStringoutput;
publicvoidsubmit(){
output="HelloWorld!Youhavetyped:"+input;
}
publicStringgetInput(){
returninput;
}
publicvoidsetInput(Stringinput){
this.input=input;
}
publicStringgetOutput(){
returnoutput;
}
}
We'llbrieflygothroughtheannotationsthatareusedhere.@Named—givesthebeananame,whichisprimarilyusedtoreferenceitviaEL.Withoutanyattributesthisnamedefaultstothesimpleclassnamewiththefirstletterinlowercase,thus"helloWorld"here.Itwillbeavailableby#{helloWorld}inEL.ThiscanbeusedinJSFpages.
@RequestScoped—givesthebeanascope,whichmeansthesameinstanceofthebeanisusedwithinagivenlifespan.InthiscasethatlifespanisthedurationofanHTTPrequest.Whenthescopeends,thebeanisautomaticallydestroyed.YoucanreadmoreaboutscopesinChapter8.
CREATINGTHEFACELETSFILENext,we'llcreatetheViewpartofMVC.It’sbasicallyjustaXHTMLfilewhichisbyJSFinterpretedasaFaceletsfileorjustFacelet.ThisFaceletsfilewillultimatelygeneratetheHTMLmarkupthatissenttothebrowserinresponsetoarequest.WithhelpofEL,itcanreferenceabeanpropertyandinvokeabeanaction.
Right-clickthewebappfolderoftheprojectandchooseNew➤XHTMLPage(seeFigure2-18).TheNewXHTMLPagewizardwhichappearsnowisalsopartoftheJBossToolsplug-in.Inthiswizard,settheFilenametohello.xhtmlandfinishthewizard.
Figure2-18 TheJBossTools-providedNewXHTMLPagewizardinEclipse
TheXHTMLeditorwillnowopenwiththenewlycreatedFaceletsfile.You’llalsonoticethatthePaletteviewshowsupinbottombox.ThisisessentiallynotusefulforJSF-basedwebdevelopment.Solet’scloseit.ComingbacktothenewlycreatedFaceletsfile,it’sinitiallyempty.Fillitwiththefollowingcontent:<!DOCTYPEhtml><htmllang="en"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
>
<h:head>
<title>HelloWorld</title>
</h:head>
<h:body>
<h1>HelloWorld</h1>
<h:form>
<h:outputLabelfor="input"value="Input"/>
<h:inputTextid="input"value="#
{helloWorld.input}"/>
<h:commandButtonvalue="Submit"
action="#{helloWorld.submit}">
<f:ajaxexecute="@form"render=":output"
/>
</h:commandButton>
</h:form>
<h:outputTextid="output"value="#
{helloWorld.output}"/>
</h:body>
</html>
We'llbrieflygothroughtheJSF-specificXHTMLtagsthatareusedhere.
<h:head>—generatestheHTML<head>.ItgivesJSFtheopportunitytoautomaticallyincludeanynecessaryJavaScriptfiles,suchastheonecontainingthenecessarylogicfor<f:ajax>.
<h:body>—generatestheHTML<body>.YoucanalsouseaplainHTML<body>inthisspecificFacelet,butthenitdoesn’tgiveanyotherJSFtagtheopportunitytoautomaticallyincludeanynecessaryJavaScriptintheendoftheHTML<body>.
<h:form>—generatestheHTML<form>.JSFwillautomaticallyincludetheviewstateinahiddeninputfield.
<h:outputLabel>—generatestheHTML<label>.YoucanalsouseaplainHTML<label>inthisspecificFacelet,butthenyou’dhavetomanuallytakecareoffiguringouttheactualIDofthetargetinputelement.
<h:inputText>—generatestheHTML<inputtype="text">.JSFwillautomaticallygetandsetthevalueinthebeanpropertyspecifiedinthevalueattribute.
<h:commandButton>—generatestheHTML<inputtype="submit">.JSFwillautomaticallyinvokethebeanmethodspecifiedintheactionattribute.
<f:ajax>—generatesthenecessaryJavaScriptcodeforAjaxbehavior.Youcanalsodoaswellwithoutit,butthentheformsubmitwon’tbeperformedasynchronously.Theexecuteattributeindicatesthattheentire<h:form>mustbeprocessedonsubmitandtherenderattributeindicatesthatthetagidentifiedbyid="output"mustbeupdatedoncompleteoftheAjaxsubmit.
<h:outputText>—generatestheHTML<span>.ThisistheonebeingupdatedoncompletionoftheAjaxsubmit.Itwillmerelyprintthebeanpropertyspecifiedinthevalueattribute.
ThoseJSF-specificXHTMLtagsarealsocalledJSFComponents.TherewillbemoreonFaceletsfilesandJSFcomponentsintheupcomingchapters.NotethatyoucanalsoperfectlyembedplainvanillaHTMLinaFaceletsfile.JSFcomponentsshouldonlybeusedwhenthefunctionalityrequiresso,oriseasilyachievablewiththem.
DEPLOYINGTHEPROJECTIntheServersview,firststartthePayaraserver.Youcandosobyselectingitandthenclickingthegreenarrowiconwhosetooltipsays“Starttheserver.”Youcan,ofcourse,alsousethebugiconwhosetooltipsays“Starttheserverindebugmode.”TheConsoleviewwillbrieflyshowtheserverstartuplog.
Waituntiltheserverstartsupandhas,intheServersview,gainedthestatusStarted(seeFigure2-19).
Figure2-19 ThePayaraserverinServersviewwiththestatusStarted(notethattheConsoleviewishighlightedasithasunreadserverlogs)
Nowright-clickthePayaraserverentryandchooseAddandRemove.ItwillshowtheAddandRemovewizard(seeFigure2-20)whichgivesyoutheopportunitytoaddandremoveWARprojectstotheserver.Dosoforournewlycreatedprojectandfinishthewizard.
Figure2-20 TheAddandRemovewizardwhereintheprojecthasbeendeployedtotheserverbymovingittotheright
ItmustbeexplicitlymentionedthatincaseofPayaraandGlassFishserversthisisbesttobedonewhiletheserverisalreadystarted.Whenremovingaprojectwhiletheserverisshutdown,itmaystilllingeraroundintheserver’sdeploymentfolder.That’sjustGlassFish’sownquirk.Forexample,inthecaseofWildFlyandTomcatservers,thisisnotnecessary.
Now,openatabinyourfavoritewebbrowser(seeFigure2-21)andentertheaddress
http://localhost:8080/project/hello.xhtml
inordertoopenthenewlycreatedJSFpage.
Figure2-21 TheHelloWorldpageinChromebrowserwhereintheinputfieldisfilledwiththetext“somemessage”andthesubmitbuttonhasbeenpressed
ComingbacktotheURL,the"localhost:8080"partisbyconventionthedefaultdomainofanyJavaEEserverwhichisrunningindevelopmentmode.Thesameaddressisalsousedby,amongothers,WildFlyandTomEE.The"/project"partisbyconventionthenameoftheEclipseproject.ThisisinServlettermscalledthe“contextpath”andobtainablebyHttpServletRequest#getContextPath()andinJSFdelegatedbyExternalContext#getRequestContextPath().
Thecontextpathpartcanalsobesettoanemptystring;thedeployedwebapplicationwillthenendupinthedomain
root.InEclipse,thiscanbesetintheproject’spropertiesaswell.FirstremovetheprojectfromthedeploymentusingtheAddandRemovewizard.Thenright-clicktheproject,chooseProperties,andselectWebProjectSettings.ThensettheContextrootfieldtoaforwardslash“/”andclosetheproperties.Finally,addtheprojectbacktothedeploymentusingAddandRemovewizard.NowitwillbedeployedtothedomainrootandyoucanaccesstheJSFpagebyhttp://localhost:8080/hello.xhtml.Wecanevengetastepfurtherbymakinghello.xhtmlthedefaultlandingfilesothatthisalsodoesn’tneedtobespecifiedintheURL.Thiscanbeachievedbyaddingthefollowingentrytotheweb.xml:<welcome-file-list><welcome-file>hello.xhtml</welcome-file>
</welcome-file-list>
NotethatPayaracanbeconfiguredtoautomaticallypublishchangestothedeploymentwheneveraresourceischangedintheproject.Beforesavingtheeditedweb.xml,double-clickthePayaraserverinServersview,unfoldthePublishingsection,andselectAutomaticallypublishwhenresourcechangealongwithalowintervaloflike0seconds(seeFigure2-22).
Figure2-22 PayaraserverconfigurationinEclipsewithautomaticpublishingenabledandintervalsetto0seconds
Now,savetheweb.xmlandyou’llnoticethatEclipsewillimmediatelytriggerPayaratopublishthechangeswhilestillrunning.Comingbacktothewebbrowser,you’llnoticethattheJSFpageisnowalsoaccessiblebyjusthttp://localhost:8080(seeFigure2-23).
Figure2-23 TheHelloWorldpageisnowattheroot
InstallingH2H2 isanin-memorySQLdatabase.It’sanembeddeddatabaseusefulforquicklymodelingandtestingJPAentities,certainlyincombinationwithautogeneratedSQLtablesbasedonJPAentities.AddingH2toyourwebapplicationprojectisamatterofaddingthefollowingdependencytothe<dependencies>sectionofthepom.xml:<dependency><groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
</dependency>
That’sbasicallyit.TheJDBC(JavaDatabaseConnectivity)driverisalreadybuiltin.
CONFIGURINGDATASOURCE
4
CONFIGURINGDATASOURCEInordertobeabletointeractwithaSQLdatabase,weneedtoconfigureaso-calleddatasourceinthewebapplicationproject.Thiscanbedonebyaddingthefollowingsectiontotheweb.xml:<data-source><name>java:global/DataSourceName</name>
<class-name>org.h2.jdbcx.JdbcDataSource</class-name>
<url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</url>
</data-source>
ThedatasourcenamerepresentstheJNDI(JavaNamingandDirectoryInterface)name.Theclassnamerepresentsthefullyqualifiednameofthejavax.sql.DataSourceimplementationoftheJDBCdriverbeingused.TheURLrepresentstheJDBCdriver-specificURLformat.ThesyntaxisdependentontheJDBCdriverbeingused.Foranin-memoryH2databasewithadatabasenameof“test,”that’sthusjdbc:h2:mem:test.TheH2-specificDB_CLOSE_DELAY=-1pathparameterbasicallyinstructsitsJDBCdrivernottoautomaticallyshutdownthedatabasewhenithasn’tbeenaccessedforsometime,eventhoughtheapplicationserverisstillrunning.
AconcreteinstanceoftheDataSourcecannowbeinjectedinanyservletcontainermanagedartifactsuchasaservletorfilterasfollows:@ResourceprivateDataSourcedataSource;
YoucouldgetaSQLconnectionfromitviaDataSource#getConnection()fortheplainoldJDBCwork.However,aswe’regoingtouseJavaEE,it’sbettertouseJavaEE’sownJPAforthisinstead.
CONFIGURINGJPAInordertofamiliarizeJPAwiththenewlyaddeddatasource,weneedtoaddanewpersistenceunittothepersistence.xmlwhichusesthedatasourceasaJTAdatasource.
<persistence-unitname="PersistenceUnitName"transaction-
type="JTA">
<jta-data-source>java:global/DataSourceName</jta-data-
source>
<properties>
<property
name="javax.persistence.schema-
generation.database.action"
value="drop-and-create"/>
</properties>
</persistence-unit>
Yousee,thedatasourceisidentifiedbyitsJNDIname.You’llalsonoticeaJPA-specificjavax.persistence.schema-
generation.database.actionpropertywithavalueof“drop-and-create”whichbasicallymeansthatthewebapplicationshouldautomaticallydropandcreateallSQLtablesbasedonJPAentities.Thisis,ofcourse,onlyusefulforprototypingpurposes,aswe’regoingtodowiththisprojectintherestofthebook.Forreal-worldapplications,you’dbetterpickeither“create”or“none”(whichisthedefault).Thetransactiontypebeingsetto“JTA”basicallymeansthattheapplicationservershouldautomaticallymanagedatabasetransactions.ThiswayeverymethodinvocationonanEJBfromitsclient(usually,aJSFbackingbean)transparently
startsanewtransactionandwhentheEJBmethodreturnstotheclient(usually,thecallingbackingbean),thetransactionisautomaticallycommittedandflushed.And,anyruntimeexceptionfromanEJBmethodautomaticallyrollsbackthetransaction.
CREATINGTHEJPAENTITYNowwe’regoingtocreateaJPAentityc.Basically,it’saJavaBeanclasswhichrepresentsasinglerecordofadatabasetable.Eachbeanpropertyismappedtoaparticularcolumnofthedatabasetable.Normally,JPAentitiesaremodeledagainstexistingdatabasetables.But,asyou’vereadintheprevioussection,“ConfiguringJPA,”aboutthepersistence.xml,it’salsopossibletodoittheotherwayround:databasetablesaregeneratedbasedonJPAentities.
Right-clickthesrc/main/javafolderoftheprojectandchooseNew➤JPAEntity.Inthewizard,setthePackagetocom.example.project.modelandsettheNametoMessage.Therestofthefieldscanbekeptdefaultorempty(seeFigure2-24).
Figure2-24 TheNewJPAEntitywizardinEclipse
Modifythenewentityclassasfollows:packagecom.example.project.model;
importjava.io.Serializable;
importjavax.persistence.Column;
importjavax.persistence.Entity;
importjavax.persistence.GeneratedValue;
importjavax.persistence.GenerationType;
importjavax.persistence.Id;
importjavax.persistence.Lob;
importjavax.validation.constraints.NotNull;
@Entity
publicclassMessageimplementsSerializable{
privatestaticfinallongserialVersionUID=1L;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
privateLongid;
@Column(nullable=false)@Lob
private@NotNullStringtext;
//Add/generategettersandsetters.
}
Asareminder,youcanletEclipsegenerategettersandsettersbyright-clickinganywhereintheclasseditorandchoosingSource➤GenerateGettersandSetters.
We'llbrieflygothroughtheannotationsthatareusedhere.@Entity—marksthebeanasaJPAentity,sothattheJPAimplementationwillautomaticallycollectdatabase-relatedmetadatabasedonallitsproperties.
@Id@GeneratedValue(strategy=IDENTITY)—marksapropertytobemappedtoadatabasecolumnofSQL“IDENTITY”type.InMySQLterms,that’stheequivalentof“AUTO_INCREMENT”.InPostgreSQLterms,that’stheequivalentof“BIGSERIAL”.
@Column—marksapropertytobemappedtoaregulardatabasecolumn.TheactualdatabasecolumntypedependsontheJavatypebeingused.Withouttheadditional@Lobannotation,that’saVARCHAR(255)whoselengthcanbemanipulatedby@Column(length=n).Withthe@Lobannotation,however,thecolumntypebecomesTEXT.
@Lob—marksaStringpropertytobemappedtoadatabasecolumnoftypeTEXTinsteadofalimitedVARCHAR.
@NotNull—thisisactuallynotpartofJPAbutof“BeanValidation.”Tothepoint,itensuresthatthebeanpropertyisbeingvalidatednevertobenullwhensubmittingaJSFformandwhenpersistingtheJPAentity.(SeeChapter5.)Alsonotethatthisbasicallyreplicatesthe
@Column(nullable=false),butthat’sonlybecauseJPAdoesn’tconsideranyBeanValidationannotationsasvaliddatabasemetadatainordertogenerateappropriateSQLtables.
CREATINGTHEEJBSERVICENext,weneedtocreateanEJBinordertobeabletosaveaninstanceoftheaforementionedJPAentityinthedatabase,andtoobtainalistofJPAentities.
Right-clickthesrc/main/javafolderoftheprojectandchooseNew➤Class.Inthewizard,setthePackagetocom.example.project.serviceandsettheNametoMessageService(seeFigure2-25).Therestofthefieldscanbekeptdefaultorempty.
Figure2-25 TheNewJavaClasswizardinEclipse
Modifythenewserviceclassasfollows:
packagecom.example.project.service;
importjava.util.List;
importjavax.ejb.Stateless;
importjavax.persistence.EntityManager;
importjavax.persistence.PersistenceContext;
@Stateless
publicclassMessageService{
@PersistenceContext
privateEntityManagerentityManager;
publicvoidcreate(Messagemessage){
entityManager.persist(message);
}
publicList<Message>list(){
returnentityManager
.createQuery("FROMMessagem",Message.class)
.getResultList();
}
}
That’sbasicallyit.Let’sbrieflygothroughtheannotations.@Stateless—marksthebeanasastatelessEJBservice,sothattheapplicationserverknowswhetheritshouldpoolthemandwhentostartandstopdatabasetransactions.Thealternativeannotationsare@[email protected]@Statelessdoesnotmeanthatthecontainerwillmakesurethattheclassitselfisstateless.Youasdeveloperarestillresponsibletoensurethattheclassdoesn’tcontainanysharedandmutableinstancevariables.Otherwise,you’dbettermarkitaseither@Statefulor@Singleton,dependingonitspurpose.
@PersistenceContext—basicallyinjectstheJPAentitymanagerfromthepersistenceunitasconfiguredintheproject’spersistence.xml.Theentitymanageris,inturn,responsibleformappingallJPAentitiesagainstaSQLdatabase.Itwill,undercover,doallthehardJDBCwork.
ADJUSTINGTHEHELLOWORLDNowwe’regoingtoadjusttheearliercreatedHelloWorldbackingbeaninordertosavethemessagesinthedatabaseand
displayalloftheminatable.
@Named@RequestScoped
publicclassHelloWorld{
privateMessagemessage=newMessage();
privateList<Message>messages;
@Inject
privateMessageServicemessageService;
@PostConstruct
publicvoidinit(){
messages=messageService.list();
}
publicvoidsubmit(){
messageService.create(message);
messages.add(message);
}
publicMessagegetMessage(){
returnmessage;
}
publicList<Message>getMessages(){
returnmessages;
}
}
Notethatyoudon’tneedsettersformessageandmessages.We’regoingtousethegettersandsettersoftheMessageentityitself.
Finally,adjustthe<h:body>ofhello.xhtmlasfollows:<h1>HelloWorld</h1><h:form>
<h:outputLabelfor="input"value="Input"/>
<h:inputTextid="input"value="#
{helloWorld.message.text}"/>
<h:commandButtonvalue="Submit"
action="#{helloWorld.submit}">
<f:ajaxexecute="@form"render=":table"/>
</h:commandButton>
</h:form>
<h:dataTableid="table"value="#{helloWorld.messages}"
var="message">
<h:column>#{message.id}</h:column>
<h:column>#{message.text}</h:column>
</h:dataTable>
Nowreloadthepageinyourfavoritewebbrowserandcreatesomemessages(seeFigure2-26).
Figure2-26 TheHelloWorldusingJSF,CDI,EJB,andJPA
Footnotes1https://en.wikipedia.org/wiki/Mojibake.
2http://tools.jboss.org/features/cdi.html.
3
https://github.com/javaserverfaces/mojarra/blob/master/R
EADME.md.
4http://www.h2database.com.
(1)
(2)
©BaukeScholtz,ArjanTijms2018BaukeScholtzandArjanTijms,TheDefinitiveGuidetoJSFinJavaEE8,https://doi.org/10.1007/978-1-4842-3387-0_3
3.Components
BaukeScholtz andArjanTijms
Willemstad,Curaçao
Amsterdam,Noord-Holland,TheNetherlands
JSF(JavaServerFaces)isacomponent-basedMVC(Model-View-Controller)framework.Inessence,JSFparsestheviewdefinitionintoa“componenttree.”Therootofthistreeisrepresentedbythe“viewroot”instanceassociatedwiththecurrentinstanceofthefacescontext.
UIComponenttree=
FacesContext.getCurrentInstance().getViewRoot();
TheviewisusuallydefinedusingXHTML+XMLmarkupinaFaceletsfile.XMLisamarkuplanguagewhichisverysuitablefordefiningatreehierarchyusingaminimumofcode.ThecomponenttreecanalsobecreatedandmanipulatedusingJavacodeinaJavaclass,butthisgenerallyendsupinveryverbosecodeinordertodeclarevalueormethodexpressionsandtocorrelateparentsandchildrenwitheachother.Frequently,developerswhodosoaren’tawareofhowtaghandlerssuchasJSTL(JavaServerPagesStandardTag
1 2
Library)canbeusedtomanipulatethecomponenttreeusingjustXML.
ThecomponenttreebasicallydefineshowJSFshouldconsumetheHTTPrequestinordertoapplyrequestvaluescomingfrominputcomponents,convertandvalidatethem,updatethemanagedbeanmodelvalues,andinvokethemanagedbeanaction.ItalsodefineshowJSFshouldproducetheHTTPresponsebygeneratingthenecessaryHTMLoutputusingrendererstiedtothecomponentswhoseattributescaninturnreferencemanagedbeanproperties.Inotherwords,thecomponenttreedefineshowthephasesoftheJSFlifecycleshouldbeprocessed.ThediagraminFigure3-1showshowaHTTPpostbackrequestisusuallybeingprocessedbyJSF.
Figure3-1 HowJSFprocessestheHTTPpostbackrequestwithintheMVCarchitecture(thenumbersrepresenttheordering)
Followingisabriefdescriptionofeachstep:1. EndusersendsaHTTPrequestwhichmatchesthemappingofthe
FacesServletandthusinvokesit.
2. TheFacesServletwillbuildthecomponenttreebasedontheFaceletfileidentifiedbytheHTTPrequestpath.
3. Thecomponenttreewillifnecessarygetthecurrentmodelvaluesfromthebackingbeanduringbuildingtheview.AnyattributeofaFaceletstemplatetagandaJSTLcoretagandonlythe"id"and"binding"attributesofaJSFcomponentwillgetexecuted.
4. TheFacesServletwillrestoretheJSFviewstateonthecomponenttree.
5. TheFacesServletwillletthecomponenttreeapplytheHTTPrequestparametersandinputcomponentswillstorethemas“submittedvalue.”
6. Theinputandcommandcomponentswillifnecessarygetthecurrentmodelvaluesfromthebackingbeanduringconsultingthe"rendered","disabled",and"readonly"attributesinordertocheckwhethertheyareallowedtoapplytherequestparameters.
7. ThecommandcomponentswillqueuetheActionEventwhenitdetects,basedonHTTPrequestparameters,thatitwasbeinginvokedintheclientside.
8. TheFacesServletwillletthecomponenttreeprocessallregisteredconvertersandvalidatorsonthesubmittedvaluesandinputcomponentswillstorethenewlyconvertedandvalidatedvalueas“localvalue.”
9. Theinputcomponentswillgettheoldmodelvaluefromthebackingbeanandcomparethemwiththenewvalue.
10. Ifthenewvalueisdifferentfromtheoldmodelvalue,thentheinputcomponentwillqueuetheValueChangeEvent.
11. Whenallconversionandvalidationarefinished,theFacesServletwillinvokethelistenermethodsofanyqueuedValueChangeEventonthebackingbean.
12. TheFacesServletwillletthecomponenttreeupdateallmodelvalues.
13. Theinputcomponentswillsetthenewmodelvaluesinthebackingbean.
14. TheFacesServletwillinvokethelistenermethodsofanyqueuedActionEventonthebackingbean.
15. Thefinalactionmethodofthebackingbeanwillifnecessaryreturnanon-
nullStringoutcomereferringthetargetview.
16. TheFacesServletwillletthecomponenttreerendertheresponse.
17. ThecomponenttreewillifnecessarygetthecurrentmodelvaluesfromthebackingbeanduringgeneratingtheHTMLoutput.PracticallyanyattributeofaFaceletcomponentandaJSFcomponentwhichisinvolvedingeneratingtheHTMLoutputwillgetexecuted.
18. ThecomponenttreewillwritetheHTMLoutputtotheHTTPresponse.
19. TheFacesServletwillreturntheHTTPresponsetotheenduser.
Thisisdifferentfromarequest-basedMVCframeworkwhereinthedeveloperneedstowritemoreboilerplatecodeinthe“controller”classassociatedwiththeviewinordertodefinewhichrequestparametersneedtobeapplied,and/orhowtheyshouldbeconvertedandvalidatedbeforepopulatingtheentity.Thedeveloperalsooftenneedstomanuallypopulatetheentitybymanuallyinvokingabunchofgettersandsettersbeforepassingtheentitytotheservicelayerwhileinvokingtheaction.ThisallisunnecessaryinJSF.
ItshouldbenotedthatthebackingbeanhasaratheruniquepositionintheMVCparadigm.ItcanactasaModel,aView,andtheController,dependingonthepointofview.ThisisdetailedinChapter8.
StandardHTMLComponentsThedefaultJSFimplementationalreadyprovidesanextensivesetofcomponentsforauthoringHTMLpageswiththehelpofFaceletsviewtechnology.ThoseHTMLcomponentsareavailableunderthehttp://xmlns.jcp.org/jsf/htmlXMLnamespaceURI(UniformResourceIdentifier)whichshouldbeassigned
tothe"h"XMLnamespaceprefix.
xmlns:h="http://xmlns.jcp.org/jsf/html"
ThemostimportantHTMLcomponentswhichshouldalwaysbepresentinyourJSFpagearethe<h:head>and<h:body>.Withoutthem,JSFwon’tbeabletoauto-includeanyscriptorstylesheetresourcesassociatedwithaparticularcomponent.Forexample,the<h:commandButton>,whichgeneratesaHTMLsubmitbutton,requiresforitsAjaxfunctionalitythejsf.jsscriptfiletobeincludedintheHTMLdocument.
–<h:commandButton>,generatesaHTMLsubmitbutton.
–<h:commandButton>,canoptionallycontainAjaxfunctionality.
–TheAjaxfunctionalityrequiresajsf.jsscriptfileintheHTMLdocument.
Therendererofthatcomponentwillautomaticallytakecareofthat,butthatwouldonlyworkif<h:head>ispresent.The<h:body>isslightlylessimportanthere,buttheremayexistcomponentswhichneedtoaddascripttotheendoftheHTMLbody,suchasthe<f:websocket>.Inotherwords,themostminimalandHTML5-validJSFpagewouldlookasfollows:<!DOCTYPEhtml><htmllang="en"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
>
<h:head>
<title>Title</title>
</h:head>
<h:body>
...
</h:body>
</html>
ThegeneratedHTMLresponse,asyoucaninspectbyright-clickingViewpagesourceintheaveragewebbrowser,shouldlookasfollows:
<!DOCTYPEhtml>
<htmllang="en"xmlns:="http://www.w3.org/1999/xhtml">
<head>
<title>Title</title>
</head>
<body>
...
</body>
</html>
Yousee,JSFbasicallyreplacesallcomponentsinthepagebytheirgeneratedHTMLoutput.Asdiscussedpreviously,JSFprovidesanextensivesetofstandardHTMLcomponents.Table3-1providesanoverview.
Table3-1 StandardHTMLComponentsProvidedbyJSF
Componenttag
Componentsuperclass
Valuetype
HTMLoutput
Since
<h:body>
UIOutput
-
<body>
2.0
<button
<h:button>
UIOutcomeTarget
String
<buttononclick=window.location>
2.0
<h:column>
UIColumn
-
<td>(forh:dataTable)
1.0
<h:commandButton>
UICommand
String
<inputtype=submit>
1.0
<h:commandLink>
UICommand
String
<aonclick=form.submit()>
1.0
<h:commandScript>
UICommand
-
<script>(functiontosubmitaform)
2.3
<h:dataTable>
UIData
Object[]
<table>(dynamic)
1.0
<h:doctype>
UIOutput
-
<!DOCTYPE>
2.0
<h:form>
UIForm
-
<formmethod=post>
1.0
<h:graphicImage
UIGraphic
-<imgsrc>
1.
>
UIGraphic
-
<imgsrc> 0
<h:head>
UIOutput
-
<head>
2.
0
<h:inputFile>
UIInput
Part
<inputtype=file>
2.2
<h:inputHidden>
UIInput
Object
<inputtype=hidden>
1.0
<h:inputSecret>
UIInput
Object
<inputtype=password>
1.0
<h:inputText>
UIInput
Object
<inputtype=text>
1.0
<h:inputTextarea>
UIInput
Object
<textarea>
1.0
<h:link>
UIOutcomeTarget
String
<ahref>
2.0
<h:message>
UIMessage
-
<span>(ifnecessary)
1.0
<h:messages>
UIMessages
-
<ul>
1.0
<h:messageslayout=table>
UIMessages
-
<table>
1.0
<h:outputFormat>
UIOutput
Object
<span>(ifnecessary)
1.0
<h:outputLabel>
UIOutput
String
<label>
1.0
<h:outputText>
UIOutput
Object
<span>(ifnecessary)
1.0
<h:outputScript>
UIOutput
-
<script>
2.0
<h:outputStylesheet>
UIOutput
-
<linkrel=stylesheet>
2.0
<h:panelGrid>
UIPanel
-
<table>(static)
1.0
<h:panelGroup>
UIPanel
-<span>
1.0
<h:panelGroup>
UIPanel
-
<span> 0
<h:panelGrouplayout=block>
UIPanel
-
<div>
1.2
<h:selectBooleanCheckbox>
UIInput
Boolean
<inputtype=checkbox>
1.0
<h:selectManyCheckbox>
UIInput
Object[]
<table><inputtype=checkbox>*
1.0
<h:selectManyListbox>
UIInput
Object[]
<selectmultiplesize=n><option>*
1.0
<h:selectManyMenu>
UIInput
Object[]
<selectmultiplesize=1><option>*
1.0
<h:selectOneListbox>
UIInput
Object
<selectsize=n><option>*
1.0
<h:selectOneMenu>
UIInput
Object
<selectsize=1><option>*
1.0
<h:selectOneRadio>
UIInput
Object
<table><inputtype=radio>*
1.0
<h:selectOneRadiogroup>
UIInput
Object
<inputtype=radioname=group>
2.3
The“Componentsuperclass”columnspecifiesthemostimportantUIComponentsuperclassthecomponentextendsfrom.Youmustinterpretthespecifiedclasstobefromthejavax.faces.componentpackage.
The“Valuetype”columnspecifiesthesupportedtypeofthemodelvaluebehindthecomponent’svalueattribute,ifithasany.IfthevaluetypeisString,itmeansthatonlythemodelvalue’stoString()outcomewillbeusedasvalueofthecomponent,generallyincomponentswhichwouldrenderitassomesortoflabel.Ifit’sObject,itmeansthatitsupportsanykindofvalue,generallyincomponentswhichwouldrenderitastextorparseitasinputvalue,ifnecessarywithhelpofanimplicitorexplicitConverter.IfthevaluetypeisObject[],itmeansthatitrequiresanarrayorcollectionofobjectsasmodelvalue,generallyindataandmulti-selectioninputcomponents,ifnecessarywithanimplicitorexplicitConverter.
Therearetwospecializedinputcomponents.The<h:inputFile>bindstheuploadedfiletoajavax.servlet.http.Partpropertyanddoesn’tsupportoutputtingit—forsecurityreasons—andthe<h:selectBooleanCheckbox>whichbindsthecheckedvaluetoabooleanproperty.Thosetwoinputcomponentsdon’tsupportaConverterandthereforedon’tsupportanyothermodelvaluetype.
The“HTMLoutput”columnspecifiestheminimumgeneratedHTMLoutput.IftheHTMLoutputsays“if
necessary,”thenitmeansthatthespecifiedHTMLelementisonlyemittedwhenthecomponenthasanyattributespecifiedthatrequiresbeingoutputtedasaHTMLelementattribute,suchasid,styleClass,onclick,etc.Thatis,acomponentcanhaveattributesthatdon’tendupinthegeneratedHTMLoutputatall,suchasbinding,rendered,converter,etc.IfacomponentcanhavemultipleHTMLelementrepresentations,thenthat’susuallycontrolledbythelayoutattributeasyoucanseewith<h:messages>and<h:panelGroup>.IftheHTMLoutputcontains“*”(anasterisk),thenitmeansthatthecomponentmayemitzeroormoreofthespecifiednestedHTMLelements.
The“Since”columnindicatesthefirstJSFversiontheHTMLcomponentwasavailablein.Atthetimethisbookwaswritten,thefollowingJSFversionswereavailable:1.0(March2004),1.1(May2004),1.2(May2006),2.0(July2009),2.1(November2010),2.2(March2013),and2.3(March2017).
TheindividualHTMLcomponentsaredetailedinChapters4and6.
StandardCoreTagsNexttothestandardHTMLcomponents,JSFalsoprovidesasetof“core”tags.Thoseareessentially“helper”tagswhichallowyoutodeclarativelyconfigureoneormoretargetHTMLcomponentsbyeithernestinginthemorwrappingaroundthem.Thosecoretagsareavailableunderthehttp://xmlns.jcp.org/jsf/coreXMLnamespaceURIwhichshouldbeassignedtothe"f"XMLnamespaceprefix.
xmlns:f="http://xmlns.jcp.org/jsf/core"
Technically,thosetagsareintendedtobereusableonnon-HTMLcomponents.JSFoffersthepossibilityofattachingadifferentrenderkittothecomponenttreewhichdoesn’tgenerateHTMLoutputbutadifferentmarkup—hencethedifferentXMLnamespace.Table3-2providesanoverview.
Table3-2 StandardCoreTagsProvidedbyJSF
Coretag
Creates/handles
Targetcomponent
Since
<f:actionListener>
javax.faces.event.ActionListener
ActionSource
1.0
<f:ajax>
javax.faces.component.behavior.AjaxBehavior
ClientBehaviorHolder(s)
2.0
<f:attribute>
UIComponent#getAttributes()
UIComponent
1.0
<f:attributes>
UIComponent#getAttributes()
UIComponent
2.2
<f:convertDateTime>
javax.faces.convert.DateTimeConverter
(Editable)ValueHolder
1.0
<f:convertNumber>
javax.faces.convert.NumberConverter
(Editable)ValueHolder
1.0
<f:converter>
javax.faces.convert.Converter
(Editable)ValueHolder
1.0
<f:event>
javax.faces.event.ComponentSystemEvent
UIComponent
2.0
<f:facet>
UIComponent#getFacets()
UIComponent
1.0
<f:importConstants>
javax.faces.component.UIImportConstants
UIViewRoot(metadata)
2.3
<f:loadBundle>
java.util.ResourceBundle
UIViewRoot
1.0
<f:metadata>
javax.faces.view.ViewMetadata
UIViewRoot
2.0
<f:param>
javax.faces.component.UIParameter
UIComponent
1.0
<f:passthroughAttribute>
UIComponent#getPassthroughAttributes()
UIComponent
2.2
<f:passthroughAttributes>
UIComponent#getPassthroughAttributes()
UIComponent
2.2
<f:phaseListener>
javax.faces.event.PhaseListener
UIViewRoot
1.0
<f:selectItem>
javax.faces.component.UISelectItem
UISelectOne/UISelectMany
1.0
<f:selectItems>
javax.faces.component.UISelectItems
UISelectOne/UISelectMany
1.0
<f:setPropertyActionListener>
javax.faces.event.ActionListener
ActionSource
1.0
<f:subview>
javax.faces.component.NamingContainer
UIComponents
1.0
<f:validateBean>
javax.faces.validator.BeanValidator
UIForm
2.0
<f:validateDoubleRange>
javax.faces.validator.DoubleRangeValidator
EditableValueHolder
1.0
<f:validateLength>
javax.faces.validator.LengthValidator
EditableValueHolder
1.0
<f:validateLongRange>
javax.faces.validator.LongRangeValidator
EditableValueHolder
1.0
<f:validateRegex>
javax.faces.validator.RegexValidator
EditableValueHolder
2.0
<f:validateRequired>
javax.faces.validator.RequiredValidator
EditableValueHolder
2.0
<f:validateWholeBean>
javax.faces.validator.BeanValidator
UIForm
2.3
<f:validator>
javax.faces.validator.Validator
EditableValueHolder
1.0
<f:valueChangeListener>
javax.faces.event.ValueChangeListener
EditableValueHolder
1.0
<f:view>
javax.faces.component.UIViewRoot
UIComponents
1.0
<f:viewAction>
javax.faces.component.UIViewAction
UIViewRoot(metadata)
2.2
<f:viewParam>
javax.faces.component.UIVi
UIViewRoot
2.0
<f:viewParam>
javax.faces.component.UIViewParameter
UIViewRoot(metadata)
2.0
<f:websocket>
javax.faces.component.UIWe
bsocket
UIViewRoot
(bodyresource)
2.3
Historically,there’sonemore,the<f:verbatim>,butthiswastargetedtothesinceJSF2.0deprecatedJSP(JavaServerPages)viewtechnologyandishencealsodeprecatedsinceJSF2.0.
The“Creates/handles”columnspecifiesthethingwhichthecoretagcreatesorhandlesonthespecifiedtargetcomponent.
The“Targetcomponent”columnspecifiesthetargetcomponentsuperclassorinterfacesupportedbythecoretag.Youmustinterpretthespecifiedclassorinterfacetobefromthejavax.faces.componentpackage.IfthetargetcomponentisoptionallypluralizedasinUIComponent(s),thenitmeansthatthecoretagcaneitherbenestedinthetargetcomponentorwrappedinoneormoretargetcomponents.IfthetargetcomponentisexplicitlypluralizedasinUIComponents,thenitmeansthatthecoretagcanonlywraponeormoretargetcomponentsandthusnotbenested.
Astotargetcomponentinterfaces,theActionSourceinterfaceisimplementedbyUICommandcomponents.TheClientBehaviorHolderinterfaceisimplementedbyUIForm,UIInput,UICommand,UIData,UIOutput,UIPanel,andUIOutcomeTargetcomponents.TheValueHolderinterfaceisimplementedbyUIOutputand
UIInputcomponents.TheEditableValueHolderinterfaceisimplementedbyUIInputcomponents.BasedonTable3-1youshouldbeabletoderivetheactualHTMLcomponentsfromthem.
The“Since”columnindicatesthefirstJSFversionthecoretagwasavailablein.Atthetimethisbookwaswritten,thefollowingJSFversionswereavailable:1.0(March2004),1.1(May2004),1.2(May2006),2.0(July2009),2.1(November2010),2.2(March2013),and2.3(March2017).
Mostoftheindividualcoretagsaredetailedinseparatechapters.
LifeCycleJSFhasaverywelldefinedlifecycle.Itisbrokendownintosixphases.EachofthosephasesrunstheHTTPrequestthroughthecomponenttree,performsoperationsonit,andfirescomponentsystemevents.Abriefdescriptionwasalreadygivenintheintroductionofthischapter,alongwithadiagram(Figure3-1).Thefollowingsectionsdescribeeachofthephasesofthelifecycle.
RESTOREVIEWPHASE(FIRSTPHASE)FirstcreatetheUIViewRootinstanceandsetitspropertiessuchaslocalefromany<f:view>tag.Thecomponenttreeisatthatmomentstillempty.Onlywhenthecurrentrequestisapostbackrequest,orwhentheviewhasa<f:metadata>withchildren,thenbuildthefullcomponenttreebasedontheviewdefinition.Basically,aspecific
UIComponentsubclasswillbeinstantiatedbasedonthecomponenttagdefinedintheviewandpopulatedwithallattributesdefinedintheviewandthenUIComponent#setParent()willbeinvoked,passingtheactualparentcomponent.
TheUIComponent#setParent()methodwillfirstcheckifthereisn’talreadyanexistingparent,andifso,itwillfirethePreRemoveFromViewEventontheoldparent.Then,whenthenewparenthasbeenset,andthusthecurrentcomponenthasbecomepartofthecomponenttree,itwillfirethePostAddToViewEventwiththecurrentcomponent.
Ifthecurrentrequestisapostbackrequest,thenitwillrestorethe“viewstate”identifiedbythejavax.faces.ViewStaterequestparameterintothefreshlybuiltcomponenttree.Afterthat,thePostRestoreStateEventisexplicitlyfiredforeachcomponentinthetree,evenwhenthecomponenttreehasactuallynotbeenbuiltorrestored.Inotherwords,evenwhenit’snotapostbackrequest,thateventisfired.You’dbetterreinterpretthateventas“PostRestoreViewPhase”.Incase,duringthePostRestoreStateEvent,you’reactuallyinterestedinwhetherit’sapostbackrequest,youshouldconsulttheFacesContext#isPostback()aswell.
Bytheendofthephase,ifthefullcomponenttreehasactuallynotbeenbuilt,thenimmediatelyadvancetotherenderresponsephase(sixthphase),therebyskippinganyphaseinbetween.
APPLYREQUESTVALUESPHASE
(SECONDPHASE)TheUIComponent#processDecodes()willbeinvokedonUIViewRoot.TheprocessDecodes()methodwillfirstinvokeprocessDecodes()oneachchildandfacetandtheninvokeUIComponent#decode()onitself.Finally,invokeUIViewRoot#broadCastEvents()tofireanyFacesEventqueuedforthecurrentphase.ThedefaultJSFAPI(applicationprogramminginterface)doesn’toffersuchevents,butdeveloperscancreateandqueuetheirown.
Thedefaultimplementationofthedecode()methodwilldelegatetotheRenderer#decode()method.Inthedecode()methodofeitherthecomponentortherenderer,theimplementationhastheopportunitytoextractthesubmittedvaluefromtherequestparameterandsetitasaninternalproperty.FromthestandardHTMLcomponentset,theonlycomponentsthatdothataretheHTMLform-basedcomponentsderivingfromUIForm,UIInput,andUICommand.TheUIFormcomponentwillinvokeUIForm#setSubmitted()withtrue.TheUIInputcomponentwillinvokeUIInput#setSubmittedValue()withtherequestparametervalue.TheUICommandcomponentwillqueuetheActionEventfortheinvokeapplicationphase(fifthphase).
PROCESSVALIDATIONSPHASE(THIRDPHASE)TheUIComponent#processValidators()willbeinvokedonUIViewRoot.TheprocessValidators()
methodwillbasicallyfirstfirePreValidateEventforthecurrentcomponent,theninvokeprocessValidators()oneachchildandfacet,andtheninvokePostValidateEventforthecurrentcomponent.Finally,itwillinvokeUIViewRoot#broadCastEvents()tofireanyFacesEventqueuedforthecurrentphase,whichisusuallyaninstanceofValueChangeEvent.
FromthestandardHTMLcomponentset,onlyUIInputcomponentsbehavedifferentlyhere.RightbeforecallingprocessValidators()oneachchildandfacet,theywillfirstinvokeUIInput#validate()onitself.Ifthere’sasubmittedvaluesetduringtheapplyrequestvaluesphase(secondphase),thentheywillfirstinvokeConverter#getAsObject()onanyattachedConverter.Whenitdoesn’tthrowConverterException,thentheywillinvokeValidator#validate()onallattachedValidatorinstances,regardlessofwhetheranyofthemhasthrownValidatorException.
WhennoConverterExceptionorValidatorExceptionwasthrown,thenUIInput#setValue()willbeinvokedwiththeconvertedandvalidatedvalueandtheUIInput#isLocalValueSet()flagwillreturntrueandUIInput#setSubmittedValue()willbeinvokedwithnull.
WhenanyConverterExceptionorValidatorExceptionwasthrown,thenUIInput#setValid()willbeinvokedwithfalseandthemessageoftheexceptionwillbeaddedtothefacescontext
viaFacesContext#addMessage().Finally,whenUIInput#isValid()returnsfalse,thenFacesContext#setValidationFailed()willbeinvokedwithtrue.
Bytheendofthephase,whenFacesContext#isValidationFailed()returnstrue,immediatelyadvancetotherenderresponsephase(sixthphase),therebyskippinganyphaseinbetween.
UPDATEMODELVALUESPHASE(FOURTHPHASE)UIComponent#processUpdates()willbeinvokedonUIViewRoot.TheprocessUpdates()methodwillinturninvoketheprocessUpdates()methodoneachchildandfacet.Finally,itwillinvokeUIViewRoot#broadCastEvents()tofireanyFacesEventqueuedforthecurrentphase.ThedefaultJSFAPIdoesn’toffersuchevents,butdeveloperscancreateandqueuetheirown.
Alsoduringthisphase,fromthestandardHTMLcomponentset,onlyUIInputcomponentshaveahookhere.AftercallingprocessUpdates()oneachchildandfacet,theywillinvokeUIInput#updateModel()onitself.WhenboththeUIInput#isValid()andUIInput#isLocalValueSet()returntrue,theywillinvokethesettermethodbehindthevalueattributewithgetLocalValue()asargumentandimmediatelyinvokeUIInput#setValue()withnullandclearouttheUIInput#isLocalValueSet()flag.
WhenaRuntimeExceptionisthrownhere,usuallycausedbyabuginthesettermethoditself,itwillinvokeUIInput#setValid()withfalseandqueuetheUpdateModelExceptionandimmediatelyadvancetotherenderresponsephase(sixthphase),therebyskippinganyphaseinbetween.
INVOKEAPPLICATIONPHASE(FIFTHPHASE)TheUIViewRoot#processApplication()willbeinvoked.ThismethodwillinturninvoketheUIViewRoot#broadCastEvents()tofireanyFacesEventqueuedforthecurrentphase,whichisusuallyaninstanceofAjaxBehaviorEventorActionEvent.NotethattheprocessApplication()methodisonlydefinedontheUIViewRootclassanddoesnottraversethecomponenttree.
RENDERRESPONSEPHASE(SIXTHPHASE)Whenthecomponenttreeisstillempty,i.e.,whentherequestisnotapostbackrequest,orwhentheviewhasno<f:metadata>withchildren,orwhenthedeveloperhasinthemeanwhileexplicitlyinvokedFacesContext#setViewRoot()withitsowninstance,thenbuildthefullcomponenttreebasedontheviewdefinition.Whenthecomponenttreeispresent,firstfirethePreRenderViewEventfortheUIViewRoot,theninvokeUIComponent#encodeAll()ontheUIViewRoot,and
theninvokePostRenderViewEventfortheUIViewRoot.
TheUIComponent#encodeAll()methodwillbasicallyfirstinvokeencodeBegin()onitself,thenifUIComponent#getRendersChildren()returnstrue,itwillinvokeencodeChildren()onitself,orelseinvokeUIComponent#encodeAll()oneachchild,andtheninvokeencodeEnd()onitself.ThisallhappensonlyifUIComponent#isRendered()returnstrue—thatis,whentherenderedattributeofthecomponenttagdoesn’tevaluatetofalse.
ThedefaultimplementationoftheencodeBegin()methodwillfirstfirethePreRenderComponentEventforthecurrentcomponentandthendelegatetoRenderer#encodeBegin().ThedefaultimplementationoftheencodeChildren()methodwilldelegatetoRenderer#encodeChildren().ThedefaultimplementationoftheencodeEnd()methodwilldelegatetoRenderer#encodeEnd().Ifthecomponenthasnorendererattached,thatis,whenUIComponent#getRendererType()returnsnull,thennoHTMLoutputwillberenderedtotheresponse.
IntheencodeBegin()method,thecomponentortherendererimplementationhastheopportunitytowritetheopeningHTMLelementandallofitsattributestotheresponse.IntheencodeChildren()methodthecomponentortherendererimplementationhastheopportunitytodecorateoroverridetherenderingofthechildrenifnecessary.IntheencodeEnd()methodthecomponentortherendererimplementationhastheopportunitytowritethe
closingHTMLtag.WritingtotheresponsehappenswiththeresponsewriterasavailablebyFacesContext#getResponseWriter().
ForanymentionedXxxEventclasswhichhasbeenfiredinanyphase,ifanylistenermethodthrowsjavax.faces.event.AbortProcessingException
, thenthecurrentlyrunningphasewillbeimmediatelyabortedandthelifecyclewillimmediatelyadvancetotherenderresponsephase(sixthphase),therebyskippinganyphaseinbetween.
AjaxLifeCycleThelifecycleisalmostidenticalduringAjaxrequests.Onlythesecond,third,fourth,andsixthphasesareslightlydifferent.TheprocessDecodes(),processValidators(),andprocessUpdates()methodswillonlybeinvokedontheUIViewRootitselfandanycomponentcoveredbythecomponentsearchexpressionspecifiedin<f:ajaxexecute>.And,theencodeAll()methodwillonlybeinvokedontheUIViewRootitselfandanycomponentcoveredbythecomponentsearchexpressionspecifiedin<f:ajaxrender>.ReadmoreonsearchexpressionsinChapter12.
NotethusthattherewouldbenodifferenceintheAjaxlifecyclewhenthecomponentsearchexpressioncontainsthe"@all"keyword.Inotherwords,use"@all"withcare.Therearenosensiblereal-worldusecasesfor<f:ajaxexecute="@all">.OntheHTMLside,it’snotpossibletosubmitmultipleformsatonce.Onlytheenclosingformis
1
submitted.Thebiggestvalueisthus<f:ajaxexecute="@form">.However,thereisonesensiblereal-worldusecasefor<f:ajaxrender="@all">,namely,renderingafullerrorpageincaseanexceptionisthrownduringanAjaxrequest.Eventhen,thiscanonlybeprogrammaticallytriggeredviaPartialViewContext#setRenderAll().Formoredetail,seeChapter9.
ViewBuildTimeThe“viewbuildtime”isnottiedtoaparticularphaseoftheJSFlifecycle.TheviewbuildtimeisthatmomentwhenthephysicalUIViewRootinstanceispopulatedwithallofitschildrenbasedontheviewdefinition.
WhenJSFisabouttocreateanUIComponentinstancebasedontheviewdefinition,itwillfirstcheckwhetherthebindingattributeofthecomponentrepresentationreturnsaconcreteUIComponentinstanceand,ifso,thencontinueusingitinstead,orelsecreatetheUIComponentinstancebasedonthe“componenttype”associatedwithitandtheninvokethesetterbehindthebindingattribute,ifany,withit.Iftheidattributeofthecomponentrepresentationintheviewdefinitionisspecified,thenUIComponent#setId()willbeinvokedwithit.Finally,UIComponent#setParent()willbeinvokedwiththeparentcomponentandthenthecomponentinstancebecomesphysicallypartofthecomponenttree.Thistreewillexistuntiltheendoftherenderresponsephase(sixthphase).Thenitbecomeseligibleforthegarbagecollector,alongwiththereleasedfacescontextinstance.
Effectively,UIComponentinstancesarethusrequestscoped.Thebindingattributecanreferamanagedbeanproperty,butasUIComponentinstancesareinherentlyrequestscoped,thetargetmanagedbeanmustberequestscopedandmaynotbeinabroaderscope.Thiswon’tbecheckedbytheJSFAPI,soyouasthedevelopershouldmakeabsolutelysurethatyoudon’treferenceabroaderscopedmanagedbeaninthebindingattributeofanycomponent.
However,whenthebindingattributereferencesamanagedbeaninabroaderscopethantherequestscope,thenyou’renotonlybasicallysavingtheentirecomponenttreeintheHTTPsessionincasethebeanisvieworsessionscoped,butyou’realsoessentiallysharingtheentirecomponenttreeacrossmultipleHTTPrequestswhichareconcurrentlyaccessingtheverysamemanagedbeaninstance—veryinefficientandthusdangerous.
TheviewbuildtimecantechnicallyhappenduringanyJSFlifecyclephase.Generally,that’stherestoreviewphase(firstphase),particularlyduringapostbackrequest,orwhentheviewhas<f:metadata>withchildren.Itcanalsohappenduringtherenderresponsephase(sixthphase),particularlyduringaGETrequestwhentheviewhasno<f:metadata>withchildren,orwhenanon-redirectnavigationhastakenplaceduringapostback.ItwillalsohappenwhenthedeveloperprogrammaticallyinvokesViewDeclarationLanguage#buildView(),whichcanbeimplicitlydonevia,amongothers,ViewHandler#createView()asshowninthefollowingactionmethodcodeexamplewhichforcesustofullyrebuildthecurrentviewfromscratch:publicvoid
rebuildCurrentView(){
FacesContextcontext=
FacesContext.getCurrentInstance();
UIViewRootcurrentView=context.getViewRoot();
StringviewId=currentView.getViewId();
ViewHandlerviewHandler=
context.getApplication.getViewHandler();
UIViewRootnewView=viewHandler.createView(context,
viewId);
context.setViewRoot(newView);
}
Donotethattheviewstateisnotperdefinitionduringtheviewbuildtimerestoredintothecomponenttree.Theviewstateisonlyrestoredintothecomponenttreeduringtherestoreviewphase(firstphase),andthathappensafterithasexecutedtheviewbuildtimebyitself.Inotherwords,theaboveshownrebuildCurrentView()methoddoesnotrestorethecurrentviewstateintothenewlycreatedcomponenttree.Programmaticallyrestoringtheviewstateisgenerallynotrecommendedwhenprogrammaticallyrebuildingtheviewasabove,asinareal-worldJSFapplicationthesolereasontorebuildthecurrentviewisusuallytogetridofanychangescausedbythepersistentviewstate,and/ortore-executeanyJSTLtagsbasedonthefreshlychangedvaluesinthemanagedbean.
ViewRenderTimeThe“viewrendertime”isalsonottiedtoaparticularphaseoftheJSFlifecycle.Theviewrendertimeisthatmomentwhen
UIComponent#encodeAll()ofaparticularcomponentisinvoked.
True,it’sbydefaultalwaysexecutedontheUIViewRootduringtherenderresponsephase(sixthphase),butthisdoesn’tstopyoufromprogrammaticallyinvokingitduringadifferentphase,suchastheinvokeapplicationphase(fifthphase),forexample,inordertoobtainthegeneratedHTMLoutputofanarbitrarycomponentasaStringvariable.
ViewStateAsexplainedinthesection“ViewBuildTime,”theUIComponentinstancesresemblingthecomponenttreeareinherentlyrequestscoped.Theyarecreatedduringtheviewbuildtimeandtheyaredestroyedrightaftertherenderresponsephase(sixthphase).AnychangestopropertiesoftheUIComponentinstances,whicharenotreferencedbyanEL(ExpressionLanguage)expression,andaredifferentfromthedefaultvalues,willbesavedas“viewstate.”Inotherwords,the“viewstate”isverydefinitelynotthesameasthe“componenttree.”Moreover,iftheentirecomponenttreeitselfwouldbesavedintheviewstate,thenthiswouldresultinnotonlyanunnecessarilybloatedviewstatebutalsobadapplicationbehaviorasUIComponentinstancesareinherentlynotthreadsafeandmaythereforeabsolutelynotbesharedacrossmultipleHTTPrequests.
Savingtheviewstatehappensduringtheviewrendertime.ThereinJSFwillwriteouttheviewstatetoajavax.faces.ViewStatehiddeninputfieldofthegeneratedHTMLrepresentationofeveryJSFform.WhentheJSFstatesavingmethodhasthedefaultsetting“server,”then
thehiddeninputvaluerepresentsauniqueidentifierreferringtheserializedviewstateobjectwhichisstoredintheHTTPsession.WhentheJSFstatesavingmethodisexplicitlysetto“client”usingthefollowingweb.xmlcontextparameter,thenthehiddeninputvalueitselfrepresentstheencryptedformoftheserializedviewstateobject.
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<env-entry>
<env-entry-name>jsf/ClientSideSecretKey</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>[AESkeyinBase64format]</env-entry-
value>
</env-entry>
Notethatexplicitlyspecifyingthejsf/ClientSideSecretKeyenvironmententrywithafixedAES(AdvancedEncryptionStandard)keyismandatoryincaseyou’rerunningtheJSFapplicationonaclusterofservers(“cloud”),orwhenyou’dliketheviewstatestilltobevalidafteraserverrestart.YoucangenerateaBase64-encodedAESkeyyourselfusingthefollowingplainJavasnippet:KeyGeneratorkeyGen=KeyGenerator.getInstance("AES");
keyGen.init(256);//Or128incaseyoudon'thaveJCE.
byte[]rawKey=keyGen.generateKey().getEncoded();
Stringkey=Base64.getEncoder().encodeToString(rawKey);
System.out.println(key);//PrintsAESkeyinBase64
format.
ThestandardJSFform,asrepresentedby<h:form>,submitsbydefaultusingthePOSTmethodtotheverysamerequestedURIaswheretheJSFpagecontainingtheformisbeingrequested.Inotherwords,whenyourequestaJSFpagebyhttp://example.com/project/page.xhtml,thenitwillsubmittotheverysamehttp://example.com/project/page.xhtmlURI.Thisisinwebdevelopmenttermsknownas“postback.”WhenJSFneedstoprocessanincomingpostbackrequest,thentherestoreviewphase(firstphase)will,aftertheviewbuildtime,extracttheviewstatefromthejavax.faces.ViewStateparameterandrestoreallchangedpropertiesintothenewlycreatedUIComponentinstancesofthecurrentrequestsothatthecomponenttreeultimatelyreflectsexactlythesamestateasithadduringtheviewrendertimeofthepreviousrequest.
OnanaverageJSFwebapplication,themajorityofthesavedviewstateisrepresentedbyinternalpropertiesofUIComponentinstancesimplementingthejavax.faces.component.EditableValueHolder
interface, whichcoversallUIInputcomponentssuchas<h:inputText>.WhensubmittingaJSFformfailswithaconversionorvalidationerror,thenallchanged“isvalid?”statesand“localvalue”states,whichcanbeeitherthesubmittedstringvalueorthealreadyconvertedandvalidatedvalue,willforallinvolvedUIInputcomponentsbesavedintheviewstate.Thishasthemajoradvantagethatthatthedeveloperdoesn’tneedtoworryaboutmanuallykeepingtrackoftheminordertorepresentthesubmittedformwithallvalidandinvalidvaluespreservedtothewebsiteuserwhile
2
keepingthemodel(themanagedbeanproperties)completelyfreeofthosevalues.ThisisamajorusabilityadvantageforboththeJSFdeveloperandthewebsiteuser.
Theminorityofthesavedviewstateisrepresentedbyprogrammaticchangestothecomponenttreehierarchy,ortocomponentattributes.Amongothers,anyprogrammaticchangestoreadonly,disabled,andrenderedattributesaretrackedintheviewstatesothatahackerdoesn’thaveanychancetospooftherequestinsuchwaythatthoseattributesfliptothewrongsidesothatthehackercoulddopotentiallyhazardousthings.Thisisamajorsecurityadvantage.
ViewScopeTheServletAPI,whichJSF,amongothers,isbuiltontopof,offersthreewell-definedscopes:therequestscope,thesessionscope,andtheapplicationscope.Basically,therequestscopeisestablishedbystoringtheobjectofinterestasanattributeoftheHttpServletRequest.Equivalently,thesessionscopeisestablishedbystoringtheobjectofinterestasanattributeoftheHttpSessionandtheapplicationscopeisestablishedbystoringtheobjectofinterestasanattributeoftheServletContext.
JSFaddsonemorescopetothis,theviewscope.Thismustnotbeconfusedwiththecomponenttreeitself.Thecomponenttree(thephysicalUIViewRootinstance)iscreatedanddestroyedduringtheverysameHTTPrequestandisthereforeclearlyrequestscoped.Thismustalsonotbeconfusedwiththeviewstate,althoughtheyarecloselyrelated.
WhentheenduserfiresapostbackrequestonaJSFform,
andtheapplicationdoesn’tperformanykindofnavigation(i.e.,theactionmethodreturnsnullorvoid),thentheviewstateidentifierwillstaythesameandtheviewscopewillbeprolongedtothenextpostbackrequest,untiltheapplicationperformsanexplicitnavigation,orwhentheHTTPsessionexpires.YoucanestablishtheviewscopebystoringtheobjectofinterestasanentryofUIViewRoot#getViewMap()[email protected],thismapisnotinturnstoredintheviewstate,notevenwhentheJSFstatesavingmethodisexplicitlysetto“client.”TheviewscopeisstoredintheHTTPsession,separatefromtheviewstate.Onlytheviewscopeidentifierisstoredintheviewstate.OnlythechangedattributesoftheUIViewRootinstancearestoredintheviewstate.
PhaseEventsThejavax.faces.event.PhaseListenerinterfacecanbeusedtolistenonanyphaseoftheJSFlifecycle.Thisinterfacedefinesthreemethods:getPhaseId(),whichshouldreturnthephaseyou’reinterestedin;beforePhase(),whichwillbeinvokedrightbeforethespecifiedphaseisexecuted;andafterPhase(),whichwillbeinvokedrightafterthespecifiedphaseisexecuted.InthebeforePhase()andafterPhase()methodsyouthushavetheopportunitytorunsomecodebeforeorafterthephasespecifiedbygetPhaseId().
Thejavax.faces.event.PhaseIdclass definesasetofpublicconstants.ItstilldatesfromJSF1.0whichwasreleasedonlyafewmonthsbeforeJava1.5andhencewastoo
3
4
lateinthegameinordertobecomearealenum.Theconstantsarelistedbelowwiththeirordinalvalues.
PhaseId.ANY_PHASE(0)
PhaseId.RESTORE_VIEW(1)
PhaseId.APPLY_REQUEST_VALUES(2)
PhaseId.PROCESS_VALIDATIONS(3)
PhaseId.UPDATE_MODEL_VALUES(4)
PhaseId.INVOKE_APPLICATION(5)
PhaseId.RENDER_RESPONSE(6)
Phaselistenerinstancescanberegisteredinvariousways.Declaratively,theycanberegisteredapplication-wideviafaces-config.xml.
<lifecycle>
<phase-listener>com.example.project.YourListener</phase-
listener>
</lifecycle>
Orview-widevia<f:phaseListener>tagenclosedin<f:view>.
<f:view>
<f:phaseListenertype="com.example.project.YourListener"
/>
...
</f:view>
Programmatically,theycanbeaddedandremovedapplication-wideviatheaddPhaseListener()andremovePhaseListener()methodsofjavax.faces.lifecycle.Lifecycleinstance.5
However,obtainingthecurrentLifecycleinstanceisslightlyconvolutedasthere’snodirectgettermethodforthatinthepublicJSFAPI(yet).
FacesContextcontext=FacesContext.getCurrentInstance();
StringlifecycleId=context.getExternalContext()
.getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR);
if(lifecycleId==null){
lifecycleId=LifecycleFactory.DEFAULT_LIFECYCLE;
}
LifecycleFactorylifecycleFactory=(LifecycleFactory)
FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY)
;
Lifecyclelifecycle=
lifecycleFactory.getLifecycle(lifecycleId);
Andtheycanbeaddedandremovedview-wideviatheaddPhaseListener()andremovePhaseListener()methodsofUIViewRoot.AconcreteexampleofaPhaseListenerisgiveninthesection“CustomComponentSystemEvents.”
ComponentSystemEventsAsnotedinthesection“LifeCycle,”abunchofcomponentsystemeventsarefiredduringthelifecycle.Theyextendfromthejavax.faces.event.ComponentSystemEventabstractclass. Insummary,thoseare
PreRemoveFromViewEvent:firedwhenacomponentisabouttoberemovedfromthecomponenttree.
PostAddToViewEvent:firedwhenacomponenthasbeenaddedtothecomponenttree.
PostRestoreStateEvent(read:"PostRestoreViewEvent"):firedforeachcomponentwhentherestoreviewphaseends.Notethatthiseventis
6
onlyfiredforUIViewRootwhentheviewbuildtimehasn’tyettakenplaceduringthisphase.Iftheviewbuildtimehastakenplaceduringthisphase,thenthiseventisfiredforanycomponentinthetree.
PreValidateEvent:firedwhenacomponentisabouttoprocessitsconverterandvalidators,andalsowhenthereareactuallynone.
PostValidateEvent:firedwhenacomponentisfinishedprocessingitsconverterandvalidators,andalsowhenthereareactuallynone.
PreRenderViewEvent:firedwhentheUIViewRootisabouttowriteHTMLoutputtotheHTTPresponse.NotethatthisisthelatestpossiblesafemomenttochangethedestinationoftheHTTPresponse,ortoprogrammaticallymanipulatethecomponenttree.Whendoingsoafterthismoment,there’snoguaranteethatanyprogrammaticchangestotheresponseorthecomponenttreewilltakeplaceasintended,becausebythentheresponsemayalreadybecommitted,ortheviewstatemayalreadybesaved.
PreRenderComponentEvent:firedwhenacomponentisabouttowriteitsHTMLoutputtotheHTTPresponse.
PostRenderViewEvent:firedwhentheUIViewRootisfinishedwritingtheHTMLoutputtotheHTTPresponse.NotethatthiseventisnewsinceJSF2.3.AllothersarefromJSF2.0.
Therearetwomorecomponentsystemeventsthatwereleftunmentionedinthesection“LifeCycle.”
PostConstructViewMapEvent:firedwhentheUIViewRoothasjuststartedtheviewscope.
PreDestroyViewMapEvent:firedwhentheUIViewRootisabouttodestroytheviewscope.
Thesetwoarenotstrictlytiedtothesix-phasecomponent-basedlifecycleandcanhappenbasicallyanytimeduringthelifecycle.ThePostConstructViewMapEventisfiredwhentheapplicationinvokesUIViewRoot#getViewMap()forthefirsttime.Bydefault,thishappensonlywhenthefirst@ViewScopedmanagedbeanofthecurrentviewstatehasbeencreated.ThePreDestroyViewMapEventisfiredwhentheapplication
invokesMap#clear()ontheUIViewRoot#getViewMap(),whichusuallyonlyhappenswhenFacesContext#setViewRoot()isinvokedwhilethereisalreadyanexistinginstanceset.Thiswillendtheviewscopeanddestroyanyactive@ViewScopedmanagedbean.Normally,thishappensonlywhentheactionmethodhasreturnedanon-nullnavigationoutcome.
Youcanlistenonanyofthosecomponentsystemeventsusingthejavax.faces.event.ComponentSystemEventList
enerinterface. IntheJSFAPI,theUIComponentclassitselfalreadyimplementsComponentSystemEventListener.ThisinterfaceprovidesaprocessEvent()methodwithaComponentSystemEventargumentwhichinturnhasamongothersagetComponent()methodreturningtheconcreteUIComponentinstancetheeventwasfiredon.ThedefaultimplementationofUIComponent#processEvent()basicallychecksifthecurrenteventisaninstanceofPostRestoreStateEventandifthebindingattributeisspecified,andifso,theninvokesthesettermethodwiththecomponentinstanceitselfasargument.
Therearethreewaystosubscribelistenerstothosecomponentsystemevents.Thefirstistodeclarativelyusethe<f:event>tagintheview.Thiscanbeattachedtoanycomponenttag.Oneexampleyou’llseeinrelativelyalotofJSF2.0/2.1targetedresourcesisthefollowing:
7
<f:metadata>
<f:viewParamname="id"value="#{bean.id}"/>
<f:eventtype="preRenderView"listener="#{bean.onload()}"
/>
</f:metadata>
whereintheonload()methodisoftenimplementedasfollows:
publicvoidonload(){
FacesContextcontext=FacesContext.getCurrentInstance();
if(!context.isPostback()&&
!context.isValidationFailed()){
//...
}
}
Notethatthe<f:eventlistener="#{bean.onload}">bydefaultexpectsamethodwithComponentSystemEventargument,butifyoudon’tneedit,itcanbeomittedforbrevityandthemethodexpressionshouldbeparenthesized,althoughtheELimplementationmaybeforgivinginthis.
The<f:eventtype="preRenderView">isinessenceawork-aroundinordertobeabletoperformtheinvokeapplicationphaseuponaGETrequestbasedonmodelvaluessetbythe<f:viewParam>.Thiswasneededbecausethe@PostConstructwasunsuitableasitwasinvokeddirectlyafterbean’sconstructionbutfarbeforethemodelvalueswereupdated.SinceJSF2.2withitsnew<f:viewAction>,this<f:event>trickisnotneededanymore:<f:metadata>
<f:viewParamname="id"value="#{bean.id}"/>
<f:viewActionaction="#{bean.onload}"/>
</f:metadata>
whereintheonload()methodisjustimplementedasfollows:
publicvoidonload(){
//...
}
Anotherreal-worldexampleof<f:event>istohavea@PostConstruct-likebehaviorinthebackingcomponentofacompositecomponentwhereinyoucansafelyperformanynecessaryinitializationbasedonitsattributes.
<cc:interfacecomponentType="someComposite">
...
</cc:interface>
<cc:implementation>
<f:eventtype="postAddToView"listener="#{cc.init()}"/>
...
#{cc.someInitializedValue}
</cc:implementation>
andwhereintheinit()methodoftheSomeCompositeclasslooksasfollows:
privateObjectsomeInitializedValue;//+getter
publicvoidinit(){
Map<String,Object>attributes=getAttributes();
someInitializedValue=initializeItBasedOn(attributes);
}
ThesecondwayistoprogrammaticallyuseUIComponent#subscribeToEvent()inJavacode.Thisallowsyoutoconditionallysubscribeacomponentsystemeventlisteneronanexistingcomponent.Itisimportanttokeepinmindthatacomponentsystemeventlistenerissavedintheviewstate.Inotherwords,it’srestoredinthecomponentinstanceduringtherestoreviewphaseofthesubsequentpostbackrequest.KeepthisinmindwhenusingUIComponent#ubscribeToEvent();otherwiseyoumayendupsubscribingtheverysamelistenermultipletimes.TheJSFimplementationMojarrahasaninternalguardagainstit,providedthattheequals()methodofthelistenerimplementationiscorrectlyimplemented,butMyFacesdoesn’thaveaguardherebecausetheJSFspecificationdoesn’tsayso(yet).
Thisallmakesitalittlecomplicatedtocorrectlyregisteracomponentsystemeventlistenerprogrammaticallyforaspecificcomponent.Ifit’sanexistingcomponent,you’dbetteruse<f:event>instead,orifit’sacustomcomponent,you’dbetteruse@ListenerForannotation,whichisactuallythethirdway.Belowisakickoffexampleofcorrectlyregisteringacomponentsystemeventlistenerprogrammatically,providedthatYourListenerclasshasitsequals()andhashCode()methodscorrectlyimplemented,andthatitimplementsSerializableorExternalizableorjavax.faces.component.StateHoldersothatitcanbesavedcorrectlyinviewstate.
Class<PreRenderViewEvent>event=PreRenderViewEvent.class;
ComponentSystemEventListenerlistener=newYourListener();
List<SystemEventListener>existingListeners=
component.getListenersForEventClass(event);
if(existingListeners!=null&&
!existingListeners.contains(listener)){
component.subscribeToEvent(event,listener);
}
Yes,thatnullcheckisnecessary.TheUIComponent#getListenersForEventClass()
isn’tspecifiedtoreturnanemptylistinstead.Allinall,thisisclearlynotacarefullythoughtoutAPI.You’dbetteruse<f:event>or@ListenerForinsteadtoavoiddirtycodeandconfusion.
Aspreviouslystated,thethirdwayisdeclarativelytousethe@ListenerForannotation.YoucanputthisannotationonlyonaUIComponentorRendererclass.Youcan’tputthisannotationonabackingbeanclass.Forthat,youshoulduse<f:event>instead.The@ListenerForannotationtakesthetargetevent(s)asvalue.TheconcreteComponentSystemEventListenerinstanceistheUIComponentinstanceitself.IftheannotationisdeclaredonaRendererclass,thenthetargetcomponentistheUIComponentinstancewhoseUIComponent#getRendererType()referstheparticularRendererclass.ThefollowingexampleshowsitforacustomcomponentYourComponent:
@FacesComponent("project.YourComponent")
@ListenerFor(systemEventClass=PostAddToViewEvent.class)
publicclassYourComponentextendsUIComponentBase{
@Override
publicvoidprocessEvent(ComponentSystemEventevent){
if(eventinstanceofPostAddToViewEvent){
//...
}
else{
super.processEvent(event);
}
}
//...
}
Yes,thatinstanceofcheckisnecessary.Asnotedinthesection“LifeCycle,”thePostRestoreStateEventisbydefaultexplicitlyfiredforanycomponentinthetree.Thesuper.processEvent(event)callisnecessaryincasethiscomponenthasthebindingattributespecified;thatis,thedefaultUIComponent#processEvent()implementationcallsduringPostRestoreStateEventthesettermethodbehindthebindingattribute.
CustomComponentSystemEventsYoucancreateyourownComponentSystemEventtypes.BasicallyallyouneedtodoistoextendfromtheComponentSystemEventabstractclassanddeclarethe@NamedEventannotationonitandfinallyinvokeApplication#publishEvent()atthedesiredmoment.
Imaginethatyouwanttocreateacustomcomponentsystemeventwhichisfiredbeforetheinvokeapplicationphase(fifthphase),aPreInvokeApplicationEvent.Thecustomeventlooksasfollows:
@NamedEvent(shortName="preInvokeApplication")
publicclassPreInvokeApplicationEventextends
ComponentSystemEvent{
publicPreInvokeApplicationEvent(UIComponentcomponent){
super(component);
}
}
Andhere’showyoucanuseaPhaseListenertopublishit.
publicclassPreInvokeApplicationListenerimplements
PhaseListener{
@Override
publicPhaseIdgetPhaseId(){
returnPhaseId.INVOKE_APPLICATION;
}
@Override
publicvoidbeforePhase(PhaseEventevent){
FacesContextcontext=
FacesContext.getCurrentInstance();
context.getApplication().publishEvent(context,
PreInvokeApplicationEvent.class,
context.getViewRoot());
}
@Override
publicvoidafterPhase(PhaseEventevent){
//NOOP.
}
}
Afterregisteringthisphaselistenerinfaces-config.xml,youcanuse<f:event>or@ListenerFortolistenonthisevent.Onereal-worldexamplewouldbenestedinthe
<f:view>oramastertemplate,orinaparticular<h:form>,sothatyoudon’tneedtocopy/pastetheverysame<f:actionListener>overmultipleUICommandcomponentsintemplateclientsorforms.
<f:eventtype="preInvokeApplication"
listener="#{bean.prepareInvokeApplication}"/>
JSTLCoreTagsIfyouhaveeverdevelopedwithJSP,thenyou’llmostlikelyhavestumbleduponJSTLtags.InFacelets,however,onlyalimitedsubsetofJSTLtagsisreincarnated.Theyare<c:if>,<c:choose><c:when><c:otherwise>,<c:forEach>,<c:set>,and<c:catch>.Essentially,theXMLnamespaceandtagnamesareidenticaltothosefromJSP,buttheyarecompletelyrewrittenforFacelets.
Thisgroupoftagsisformallycalled“JSTLcoreFaceletstaglibrary”insteadof“JSTLcoreJSPtaglibrary”andisalsodocumentedseparatelyfromJSP. ThoseJSTLtagsareavailableunderthehttp://xmlns.jcp.org/jsp/jstl/coreXMLnamespaceURIwhichshouldbeassignedtothe"c"XMLnamespaceprefix.
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
Yes,astonishinglywith"/jsp"pathintheURI.Historically,thepredecessorofFaceletsinJSF2.0hadthoseJSTLtagsalsoimplemented,butitdidn’tusethenamespaceURIasin
8
themorerecentJSTL1.1specification.Instead,itusedthenamespaceURIasintheJSTL1.0specification:http://java.sun.com/jstl/core.However,thiswas“rectified”forFaceletsinJSF2.0.Inmyhumbleopinion,thisisoutrightconfusingastheJSTL1.1XMLnamespaceURIsuggeststhatthoseareactuallyJSPtagsandnotFaceletstags.Butitiswhatitis.
ThetechnicalreasonbehindtheoriginalchangeoftheJSTLnamespaceURIisthemigrationofELfromJSTLtoJSP.ItwasintroducedinJSTL1.0andworkedonlyinJSTLtagsandthusnotoutsideJSTLtags.JSP2.0wantedtomakeuseofthepotentialofELaswellandsoitwasmigratedfromJSTLtoJSP.JSTL1.1thusshippedwithoutELandwasn’tbackward-compatiblewithJSTL1.0anymore—hencethenamespaceURIchangetodistinguishthis.
JSTLtagshaveadifferentlifecyclethanJSF’sstandardHTMLcomponents.JSTLtagsalreadyrundirectlyduringtheviewbuildtimewhileJSFisbusybuildingthecomponenttreebasedontheviewdefinition.JSTLtagsdon’tendupintheJSFcomponenttree.Inotherwords,youcanuseJSTLtagstocontroltheflowofbuildingoftheJSFcomponenttree.
NotethatusingJSTLtocontrolthecomponenttreebuildingisn’taseasilypossibleasusingJSFonJSPinsteadofFacelets.Thatis,JSTLtagsforJSPcanonlyrecognizeJSP-specific${}expressionsandnotJSF-specific#{}expressions.ThismeansthatJSTLtagsinJSPcan’trecognizeJSFmanagedbeansiftheyaren’tyetcreatedbyJSFatthatmoment,andthatJSFcomponentscan’taccessthevarattributeofa<c:forEach>.InFacelets,theJSTLtagsarethusretrofittedsothattheysupportthe#{}expressions.This
makesthemverypowerful.WhendevelopingJSFpageswithJSTLtagsthemost
importantthingthatyouneedtokeepinmindisthattheyrunduringtheviewbuildtimeandthattheydon’tparticipateintheJSFlifecycle.BelowIhavedemonstratedthemostimportantdifferencesbetweenaJSTLtaganditsJSF/Faceletscounterpart.
<c:forEach>versus<ui:repeat>
Followingisa<c:forEach>exampleiteratingoveraList<Item>withthreeinstancesofanexampleItementityhavingidandvalueproperties:
<c:forEachitems="#{bean.items}"var="item">
<h:outputTextid="item_#{item.id}"value="#{item.value}"
/>
</c:forEach>
Duringtheviewbuildtime,thiscreatesthreeseparate<h:outputText>componentsinthecomponenttree,roughlyrepresentedasfollows:
<h:outputTextid="item_1"value="#{bean.items[0].value}"/>
<h:outputTextid="item_2"value="#{bean.items[1].value}"/>
<h:outputTextid="item_3"value="#{bean.items[2].value}"/>
Inturn,theyindividuallygeneratetheirHTMLoutputduringviewrendertime,asfollows:
<spanid="item_1">one</span>
<spanid="item_2">two</span>
<spanid="item_3">three</span>
DonotethattheidattributeofaJSFcomponentisalsoevaluatedduringtheviewbuildtimeandthusyouneedtomanuallyensuretheuniquenessoftheresultingcomponentID.OtherwiseJSFwillthrowanIllegalStateExceptionwithamessagewhichgoeslikethis:“DuplicatecomponentIDfoundinview.”TheonlyotherJSFcomponentattributewhichisalsoevaluatedduringtheviewbuildtimeisthebindingattribute.IfyouabsolutelyneedtobindaJSTL-generatedcomponenttoabackingbeanproperty,whichisrare,thenyoushouldbespecifyingauniquearrayindex,collectionindex,ormapkey.Followingisanexampleprovidedthat#{bean.components}refersanalreadypreparedUIComponent[],List<UIComponent>,orMap<Long,UIComponent>property.
<c:forEachitems="#{bean.items}"var="item">
<h:outputTextbinding="#{bean.components[item.id]}"
id="item_#{item.id}"value="#{item.value}"/>
</c:forEach>
TheFaceletscounterpartofthe<c:forEach>isthe<ui:repeat>.ThisisinessenceaUIComponentwhichdoesn’tgenerateanyHTMLoutputbyitself.Inotherwords,the<ui:repeat>itselfalsoendsupintheJSFcomponenttreeduringtheviewbuildtime,andonlyrunsduringtheviewrendertime.Itbasicallyre-rendersitschildrenduringeveryiterationroundagainstthecurrentlyiterateditemasvarattribute.
<ui:repeatid="items"value="#{bean.items}"var="item">
<h:outputTextid="item"value="#{item.value}"/>
</ui:repeat>
DuringtheviewbuildtimetheaboveendsupexactlyasisintheJSFcomponenttree:asingleUIRepeatinstancewithonenestedHtmlOutputTextinstancewhereasthe<c:forEach>createsherethreeHtmlOutputTextinstances.Then,duringtheviewrendertime,theverysame<h:outputText>componentisbeingreusedtogenerateitsHTMLoutputbasedoncurrentiterationround.
<spanid="items:0:item">one</span>
<spanid="items:1:item">two</span>
<spanid="items:2:item">three</span>
Donotethatthe<ui:repeat>asaNamingContainercomponentalreadyensuredtheuniquenessoftheclientIDbasedontheiterationindex.It'stechnicallyalsonotpossibletoreferenceitsvarattributeintheidattributeofanychildcomponentasthevarattributeisonlysetduringviewrendertimewhiletheidattributeisalreadysetduringviewbuildtime.
<c:if>/<c:choose>versusrendered
Imaginethatwehaveacustomtagfilewhichcanbeusedasfollows:
<t:inputtype="email"id="email"label="Email"value="#
{bean.email}"/>
Andtheinput.xhtmltagfilecontainsthefollowingFaceletsmarkupconditionallyaddingdifferenttagsusinga<c:choose>(youcanalsouse<c:if>forthis):<c:choose>
<c:whentest="#{typeeq'password'}">
<h:inputSecretid="#{id}"label="#{label}"
value="#{value}"/>
</c:when>
<c:whentest="#{typeeq'textarea'}">
<h:inputTextareaid="#{id}"label="#{label}"
value="#{value}"/>
</c:when>
<c:otherwise>
<h:inputTextid="#{id}"label="#{label}"
value="#{value}"
a:type="#{type}">
</h:inputText>
</c:otherwise>
</c:choose>
Notethatamoreelaborateexamplecanbefoundinthesection“TagFiles”inChapter7.Thisconstructwillthenonlycreatethe<h:inputText>componentinthecomponenttree,roughlyrepresentedasfollows:
<h:inputTextid="email"label="Email"value="#{bean.email}"
a:type="email">
</h:inputText>
andwhenusingtherenderedattributeinsteadof<c:choose>asfollows:
<h:inputSecretid="#{id}_password"rendered="#{typeeq
'password'}"
label="#{label}"value="#{value}">
</h:inputSecret>
<h:inputTextareaid="#{id}_textarea"rendered="#{typeeq
'textarea'}"
label="#{label}"value="#{value}">
</h:inputTextarea>
<h:inputTextid="#{id}_text"
rendered="#{typene'password'andtypene'textarea'}"
label="#{label}"value="#{value}">
</h:inputText>
Thentheywillallendupinthecomponenttreeroughlyasfollows:
<h:inputSecretid="email_password"rendered="#{typeeq
'password'}"
label="Email"value="#{bean.email}">
</h:inputSecret>
<h:inputTextareaid="email_textarea"rendered="#{typeeq
'textarea'}"
label="Email"value="#{bean.email}">
</h:inputTextarea>
<h:inputTextid="email_text"
rendered="#{typene'password'andtypene'textarea'}"
label="Email"value="#{bean.email}">
</h:inputText>
Yousee,thiswillthusendupinanunnecessarilybloatedcomponenttreewithalotofunusedcomponentswhenyouhavemanyofthem,particularlywhenthetypeattributeisactuallystatic(i.e.,itdoesnoteverchange,atleastduringtheviewscope).Alsonotethattheidattributeofeachcomponenthasastaticsuffixsothatyoudon’tendupwith“DuplicatecomponentIDfoundinview”exceptions.
<c:set>versus<ui:param>
Theyarenotinterchangeable.The<c:set>setsavariableintheELscope,whichisaccessibleonlyafterthetaglocationduringtheviewbuildtime,butanywhereelseintheviewduringtheviewrendertime.The<ui:param>shouldonlybenestedin<ui:include>,<ui:decoratetemplate>,or<ui:compositiontemplate>andsetsavariableintheELscopeoftheFaceletstemplate,whichisaccessibleonlyinthetemplateitself.OlderJSFversionshadbugswherebythe<ui:param>variablewasalsoavailableoutsidetheFaceletstemplateinquestion.Thisshouldneverbereliedupon.
The<c:set>withoutascopeattributewillbehavelikeanalias.ItdoesnotcachetheresultoftheELexpressioninanyscope.ItsprimarypurposeistobeabletohaveashortcuttoarelativelylongELexpressionwhichisrepeatedseveraltimesinthesameFaceletsfile.Itcanthusbeusedperfectlywellinside,forexample,iteratingJSFcomponents.
<ui:repeatvalue="#{bean.products}"var="product">
<c:setvar="price"value="#{product.price}"/>
#{price}
</ui:repeat>
It'sonlynotsuitable,forexample,forcalculatingthesuminaloop.Thefollowingconstructwillneverwork:
<c:setvar="total"value="#{0}"/>
<ui:repeatvalue="#{bean.products}"var="product">
<c:setvar="total"value="#{total=total+
product.price}"/>
#{product.price}
</ui:repeat>
Totalprice:#{total}
Forthat,useEL3.0streamAPIinstead.
<ui:repeatvalue="#{bean.products}"var="product">
#{product.price}
</ui:repeat>
Totalprice:#{bean.products.stream().map(product-
>product.price).sum()}
However,whenyousetthescopeattributewithoneofallowablevaluesrequest,view,session,orapplication,thenitwillbeevaluatedimmediatelyduringtheviewbuildtimeandstoredinthespecifiedscope.
<c:setvar="DEV"
value="#{facesContext.application.projectStageeq
'Development'}"
scope="application"/>
ThiswillbeevaluatedonlyonceduringthefirsttimethisviewisbeingbuiltandavailableasanELvariable#{DEV}throughouttheentireapplication.You’dbestdeclaresuch<c:set>inthemastertemplatefilewhichisusedbyeverysingleFaceletsfileintheentireapplication.NotethattheELvariableiscapitalizedtoconformtoJavanamingconventionsforconstants.
CAVEATS
UsingJSTLtagswillonlyleadtounexpectedresultswhenaJSTLtagattributereferencesanELvariablewhichisnotavailableduringviewbuildtime.ExamplesofsuchELvariablesarethosedefinedbyvarattributeof
iteratingcomponentssuchas<h:dataTable>and<ui:repeat>,andthosesetinmodelby<f:viewParam>,<f:viewAction>,and<f:eventtype="preRenderView">.
Inanutshell,useJSTLtagsonlytocontrolflowofJSFcomponenttreebuildinganduseJSFUIcomponentsonlytocontrolflowofHTMLoutputgeneration.InJSTLtags,donotrelyonELvariableswhicharenotavailableduringviewbuildtime.
ManipulatingtheComponentTreeThiscanbedonedeclarativelyusingJSTLtagsaswellasprogrammaticallyusingJavacode.TheJSTLapproachhasalreadybeenelaboratedintheprevioussection.It’salsopossibletouseJavacodeinstead.Asaprecaution,thisgenerallyendsupinveryverboseandhard-to-maintaincode.Tree-basedhierarchiesincodearebestreadableandmaintainablewhenusingahierarchicalmarkuplanguagesuchasXML.FaceletsitselfisalreadyXMLbased.JSTLisalsoXMLbasedandthereforeseamlesslyintegratesinaFaceletsfile.JSTListhereforetherecommendedapproachtodynamicallymanipulatethecomponenttree,ratherthanJavacode.
TheJavadocofjavax.faces.component.UIComponent specifieswhenyoucouldsafelymanipulatethecomponenttree:
Dynamicallymodifyingthecomponenttreecanhappenatanytime,duringandafterrestoringtheview,butnotduringstatesavingandneedstofunctionproperlywithrespecttorenderingandstatesaving.
Inotherwords,theearliestmomentwhenyoucanguaranteesafelymodifyingthecomponenttreeisduringthePostAddToViewEventandthelatestmomentwhenyou
9
canguaranteesafelymodifyingthecomponenttreeisduringthePreRenderViewEvent.Anymomentinbetweenisthusalsopossible.BeforethePostAddToViewEventthere’snotnecessarilyameansofaconcreteUIViewRootinstance.AfterthePreRenderViewEventthere’sariskthatthestateisalreadysavedandyou’drathernotgettrappedhere.Inotherwords,manipulatingthecomponenttreeduringtherenderresponsephase(sixthphase)isabadidea.
WhenyouintendtomanipulatethecomponenttreebymeansofaddingnewcomponentsbasedonaJavamodelwhichisatleastviewscoped,thenlistenonthePostAddToViewEventoftheparentcomponentofinterest.Whenyouintendtomanipulatethecomponenttreebasedonthefullybuiltcomponenttreebymeansofadding/moving/removingcomponents,thenlistenonthePreRenderViewEventoftheUIViewRoot.
ThefollowingexampleprogrammaticallypopulatesadynamicformbasedonaJavamodelduringthePostAddToViewEvent:
<h:formid="dynamicFormId">
<f:eventtype="postAddToView"listener="#
{dynamicForm.populate}"/>
</h:form>
whereinthe#{dynamicForm}lookssomethinglikethefollowing:
@Named@RequestScoped
publicclassDynamicForm{
privatetransientUIFormform;
privateMap<String,Object>values=newHashMap<>();
@Inject
privateFieldServicefieldService;
publicvoidpopulate(ComponentSystemEventevent){
form=(UIForm)event.getComponent();
List<Field>fields=fieldService.list(form.getId());
fields.forEach(field->field.populate(this));
}
publicvoidcreateOutputLabel(Fieldfield){
HtmlOutputLabellabel=newHtmlOutputLabel();
label.setId(field.getName()+"_l");
label.setFor(field.getName());
label.setValue(field.getLabel());
form.getChildren().add(label);
}
publicvoidcreateInputText(Fieldfield){
HtmlInputTexttext=newHtmlInputText();
text.setId(field.getName());//ExplicitIDis
required!
text.setLabel(field.getLabel());
text.setValueExpression("value",
createValueExpression(field));
form.getChildren().add(text);
}
publicvoidcreateMessage(Fieldfield){
HtmlMessagemessage=newHtmlMessage();
message.setId(field.getName()+"_m");
message.setFor(field.getName());
form.getChildren().add(message);
}
publicstaticValueExpressioncreateValueExpression(Field
field){
Stringel="#{dynamicForm.values['"+
field.getName()+"']}"
FacesContextcontext=
FacesContext.getCurrentInstance();
ELContextelContext=context.getELContext();
return
context.getApplication().getExpressionFactory()
.createValueExpression(elContext,el,
Object.class);
}
publicMap<String,Object>getValues(){
returnvalues;
}
}
andwhereintheabstractclassFieldrepresentsyourcustommodelofaformfieldwithatleasttype,name,andlabelpropertiesandtheconcreteimplementationofaTextField#populate()lookssomethinglikethefollowing:
publicvoidpopulate(DynamicFormBeanform){
form.createOutputLabel(this);
form.createInputText(this);
form.createMessage(this);
}
NotethenamingpatternofconcreteUIComponentclasses.ForHTMLcomponentstheyfollowexactlytheconvention“Html[TagName]”.Forthe<h:inputText>that’sthusHtmlInputText,andsoon.TheaboveJavaexamplewillbasicallycreatethefollowingXMLrepresentation:
<h:outputLabelid="name_l"for="name"value="Label"/>
<h:inputTextid="name"value="#{dynamicForm.values['name']}"
/>
<h:messageid="name_m"for="name"/>
Itonlydoesthatquiteverbosely.Essentially,you’reherereinventingthejobofFacelets.There’sreallynothingwhichisimpossibleusingXMLandonlypossibleinJava.AslongasyouunderstandhowyoucanuseJSTLforthis:<h:formid="dynamicFormId">
<c:forEachitems="#{dynamicForm.fields}"
var="field">
<t:fieldtype="#{field.type}"
id="#{field.name}"label="#{field.label}"
value="#{dynamicForm.values[field.name]}">
</t:field>
</c:forEach>
</h:form>
whereinthe#{dynamicForm}insteadlookssomethinglikethefollowing:
@Named@RequestScoped
publicclassDynamicForm{
privateList<Field>fields;
privateMap<String,Object>values=newHashMap<>();
@Inject
publicFieldServicefieldService;
publicList<Field>getFields(){
if(fields=null){
FacesContextcontext=
FacesContext.getCurrentInstance();
UIComponentform=
UIComponent.getCurrentComponent(context);
fields=fieldService.list(form.getId());
}
returnfields;
}
publicMap<String,Object>getValues(){
returnvalues;
}
}
Yousee,thereisnoneedtomesswithmanuallycreatingandpopulatingUIComponentinstances.FaceletsdoesthatallforyoubasedonsimpleXML.The<t:field>canbefoundinthesection“TagFiles”inChapter7.
Footnotes1https://javaee.github.io/javaee-
spec/javadocs/javax/faces/event/AbortProcessingException
.html.
2https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/EditableValueHolder.
html.
3https://javaee.github.io/javaee-
spec/javadocs/javax/faces/event/PhaseListener.html.
4https://javaee.github.io/javaee-
spec/javadocs/javax/faces/event/PhaseId.html.
5https://javaee.github.io/javaee-
spec/javadocs/javax/faces/lifecycle/Lifecycle.html.
6https://javaee.github.io/javaee-
spec/javadocs/javax/faces/event/ComponentSystemEvent.htm
l.
7https://javaee.github.io/javaee-
spec/javadocs/javax/faces/event/ComponentSystemEventList
ener.html.
8
https://javaserverfaces.github.io/docs/2.3/vdldocs/facel
ets/c/tld-summary.html.
9https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/UIComponent.html.
(1) (2)
©BaukeScholtz,ArjanTijms2018BaukeScholtzandArjanTijms,TheDefinitiveGuidetoJSFinJavaEE8,https://doi.org/10.1007/978-1-4842-3387-0_4
4.FormComponents
BaukeScholtz andArjanTijms
Willemstad,Curaçao Amsterdam,Noord-Holland,TheNetherlands
ThesearethemostimportantcomponentsofthestandardJSF(JavaServerFaces)componentset.Withoutthem,JSFwouldn’thavebeenveryusefulinthefirstplace.WhenusingplainHTMLelementsinsteadofJSFcomponents,you’denduppollutingthecontrollerwithcodetomanuallyapply,convert,andvalidatesubmittedvalues;toupdatethemodelwiththosevalues;andtofigureouttheactionmethodtobeinvoked.That’sexactlythehardworkJSFshouldtakeoffyourhandsasbeingacomponent-basedMVC(Model-View-Controller)frameworkforHTMLform-basedwebapplications.
Input,Select,andCommandComponentsAllinputcomponentsextendfromtheUIInputsuperclass.Allselectioncomponentsextendfromasubclassthereof,whichcanbeUISelectBoolean,UISelectOne,orUISelectMany.(SeeChapter3,Table3-1,fora
1 2
comprehensivelistofstandardJSFcomponents.)AllinputandselectcomponentsimplementtheEditableValueHolderinterfacewhichallowsattachingaConverter,Validator,andValueChangeListener.AllcommandcomponentsextendfromtheUICommandsuperclassandimplementtheActionSourceinterfacewhichallowsdefiningoneormoremanagedbeanmethodswhichshouldbeinvokedduringtheinvokeapplicationphase(fifthphase).Theycanhaveonlyone“action”methodandmultiple“actionlistener”methods.
HTMLrequiresallinput,select,andcommandelementstobenestedinaformelement.ThestandardJSFcomponentsetoffersonlyonesuchcomponent,the<h:form>,whichisfromtheUIFormsuperclass.YoucouldalsouseaplainHTML<form>element,butthatwouldn’tautomaticallyincludethemandatoryjavax.faces.ViewStatehiddeninputfieldintheformwhichrepresentstheJSFviewstate.Therendererofthe<h:form>istheoneresponsibleforautomaticallyincludingitineverygeneratedHTMLrepresentationoftheJSFform.Withoutit,JSFwon’trecognizetherequestasavalidpostbackrequest.Inotherwords,theFacesContext.getCurrentInstance().isPostba
ck()wouldreturnfalseandthenJSFwouldn’tevenprocessthesubmittedvalues,letaloneinvoketheactionmethod.AplainHTML<form>elementinaJSFpageisonlyusefulforGETrequestsincombinationwith<f:viewParam>tagswhichshouldtakecareofprocessingthesubmittedinputvalues.Thiswillbedetailedlaterinthesection“GETForms.”
Allcommandcomponentshaveanactionattributewhichcanbeboundtoamanagedbeanmethod.Thismethodwillbeinvokedduringtheinvokeapplicationphase(fifthphase),aslongasthere’snoconversionorvalidationerror.ConversionandvalidationarecoveredinChapter5,sowe’llskipdetailingthisstephere.
Text-BasedInputComponentsAlltext-basedinputcomponentshaveavalueattributewhichcanbeboundtoamanagedbeanproperty.Thegetterofthispropertywill,duringtheviewrendertime,beconsultedtoretrieveanddisplayanypresetvalue.And,thesetterofthispropertywill,duringtheupdatemodelvaluesphase(fourthphase)ofthepostbackrequest,beinvokedwiththesubmittedandalreadyconvertedandvalidatedvalue,ifapplicable.Followingisabasicusageexamplewhichdemonstratesalltext-basedinputcomponents.
Faceletsfile/test.xhtml:<h:form><h:inputTextvalue="#{bean.text}"/>
<h:inputSecretvalue="#{bean.password}"/>
<h:inputTextareavalue="#{bean.message}"/>
<h:inputHiddenvalue="#{bean.hidden}"/>
<h:commandButtonvalue="Submit"action="#
{bean.submit}"/>
</h:form>
Backingbeanclasscom.example.project.view.Bean:@Named@RequestScoped
publicclassBean{
privateStringtext;
privateStringpassword;
privateStringmessage;
privateStringhidden;
publicvoidsubmit(){
System.out.println("Formhasbeensubmitted!");
System.out.println("text:"+text);
System.out.println("password:"+password);
System.out.println("message:"+message);
System.out.println("hidden:"+hidden);
}
//Add/generategettersandsettersforevery
propertyhere.
}
GeneratedHTMLoutput:
<formid="j_idt4"name="j_idt4"method="post"
action="projecttest.xhtml"
enctype="application/x-www-form-urlencoded">
<inputtype="hidden"name="j_idt4"value="j_idt4"/>
<inputtype="text"name="j_idt4:j_idt5"/>
<inputtype="password"name="j_idt4:j_idt6"/>
<textareaname="j_idt4:j_idt7"></textarea>
<inputtype="hidden"name="j_idt4:j_idt8"/>
<inputtype="submit"name="j_idt4:j_idt9"value="Submit"
/>
<inputtype="hidden"name="javax.faces.ViewState"
id="j_id1:javax.faces.ViewState:0"
value="-4091383829147627416:3884402765892734278"
autocomplete="off"/>
</form>
RenderinginChromebrowser(withnewlinesadded):
You’llnoticeseveralthingsinthegeneratedHTMLoutput.UndoubtedlythefirstthingnoticeableisthatJSFhasalsoautomaticallygeneratedidandnameattributesoftheHTMLelements,allwithaj_idprefixwhichisdefinedbythepublicAPI(applicationprogramminginterface)constantUIViewRoot.UNIQUE_ID_PREFIX.The“t”basicallystandsfor“tree”andthenumberbasicallyrepresentsthepositionofthecomponentinthecomponenttree.Thisisthuspronetobechangedwheneveryouadd,remove,ormovearoundcomponentsintheFaceletsfile.ThisisthusalsosubjecttoheadacheswhenQA(qualityassurance)needstowriteintegrationtestsforthewebapplicationwhereinmorethanoftentheHTMLelementIDsneedtobeused.
JSFwilluseanautogeneratedIDwhenit’smandatoryforthefunctionalityinordertohaveanidand/oranameattributeinthegeneratedHTMLoutput.TheidattributeismandatoryinordertobeabletofindtheHTMLelementbyanyJavaScriptcodewhichcanalsobeautogeneratedbyJSF,suchasfunctionsresponsiblefortheAjaxworks.AsthismakesthegeneratedHTMLcoderatherhardtoreadand,
frankly,ugly,we’dliketojustexplicitlyspecifytheidattributeofanyJSFform,input,select,andcommandcomponent.ThiswayJSFwilljustuseitfortheidandnameattributesoftheHTMLelementsinsteadofautogeneratingone.Now,let’srewritetheFaceletsfile/test.xhtmlforthat.AgoodpracticeistolettheIDattributeoftheinputcomponentmatchexactlythebeanpropertyname,andtheIDattributeofthecommandcomponentmatchexactlythebeanmethodname.Thiswouldendupinmoreself-documentingcodeandgeneratedHTMLoutput.
<h:formid="form">
<h:inputTextid="text"value="#{bean.text}"/>
<h:inputSecretid="password"value="#{bean.password}"/>
<h:inputTextareaid="message"value="#{bean.message}"/>
<h:inputHiddenid="hidden"value="#{bean.hidden}"/>
<h:commandButtonid="submit"value="Submit"
action="#{bean.submit}"/>
</h:form>
Now,thegeneratedHTMLoutputlooksasfollows:
<formid="form"name="form"method="post"
action="projecttest.xhtml"
enctype="application/x-www-form-urlencoded">
<inputtype="hidden"name="form"value="form"/>
<inputid="form:text"type="text"name="form:text"/>
<inputid="form:password"type="password"
name="form:password"/>
<textareaid="form:message"name="form:message">
</textarea>
<inputid="form:hidden"type="hidden"name="form:hidden"
/>
<inputid="form:submit"type="submit"name="form:submit"
value="Submit"/>
<inputtype="hidden"name="javax.faces.ViewState"
id="j_id1:javax.faces.ViewState:0"
value="-7192066430460949081:-3987350607752016894"
autocomplete="off"/>
</form>
That’salreadyclearer.NotethatwhenyouexplicitlysetacomponentID,itwillalwaysendupinthegeneratedHTMLoutput.ThegeneratedHTMLelementID,then,representsthe“clientID”whichmaybedifferentfromthecomponentID,dependingonitsparents.IfthecomponenthasanyparentwhichisaninstanceofNamingContainerinterface,thentheIDoftheNamingContainerparentwillbeprependedtotheclientIDofthecomponent.FromthestandardJSFHTMLcomponentset,onlythe<h:form>and<h:dataTable>areinstancesofNamingContainer.Othersare<ui:repeat>and<f:subview>.
IfyoulookcloseratthegeneratedHTMLoutput,there’sonlyonegeneratedIDleft.It’stheoneoftheviewstatehiddeninputfield,whichisalwaysj_id1.ItrepresentstheIDoftheUIViewRootinstance,whichbydefaultcannotbesetfromaFaceletsfileon.WhenusingJSFinPortlet-basedwebapplicationsinsteadofServlet-basedwebapplications,itisoverridableandwouldrepresenttheuniquenameofthePortlet.InaPortlet-basedwebapplicationitispossibletohavemultiplePortletviewsinasingleJSFpage.Inotherwords,asingleJSFpageinaPortlet-basedwebapplicationcanhavemultipleUIViewRootinstances.
ComingbacktothegeneratedHTMLoutput,thenameattributeoftheHTMLinputelementismandatoryforHTMLinordertobeabletosendthesubmittedvaluesasrequestparametersviaHTTP.Itwillbecometherequestparameter
name.Inanydecentwebbrowseryoucaninspecttherequestparametersinthe“Network”sectionofthewebdeveloper’stoolset,whichisaccessiblebypressingF12inthewebbrowser.Figure4-1showshowChromepresentsthepostbackrequestaftersubmittingtheformwithsomevaluesfilledout,asyoucanseeinthe“FormData”sectionofthefigure.
Figure4-1 ChromeDeveloperTools—Network—Headers—FormData
ThehiddeninputfieldwiththenamerepresentingtheIDofthe<h:form>willsignalJSFwhichformexactlywassubmittedduringthepostbackrequest.Thatis,asingleHTMLdocumentcanhavemultipleformelements.ThiswayJSFcan,duringtheapplyrequestvaluesphase(secondphase),determinewhetherthecurrentformcomponentwasactuallysubmitted.ItwillcausetheUIForm#isSubmitted()oftheformcomponenttoreturntrue.Thehiddeninputfieldwith
thenamejavax.faces.ViewStaterepresentstheuniqueidentifierreferringtheserializedviewstateobjectwhichisstoredinthesession.BothhiddeninputfieldsareautomaticallyincludedbytherendererassociatedwiththeUIFormcomponent.Theautocomplete="off"ontheviewstatehiddeninputfieldis,bytheway,notatechnicalrequirementbutjustawork-aroundagainstsomebrowsersoverridingitwiththelastknownvaluewhenthebackbuttonispressed,whichmaynotbethecorrectvalueperse.
Ourexamplehiddeninputfieldhasanemptyvalue.It’seffectivelyuselessinthisform.SuchahiddeninputfieldisgenerallyonlyusefulwhenitsvalueisbeingsetbysomeJavaScriptcodewhichyou’dliketocaptureinthemanagedbean.There’sgenerallynopointto“transferring”managedbeanpropertiesfromonetothenextrequestusinghiddeninputfields.Instead,suchpropertiesshouldbeassignedtoamanagedbeanwhichisdeclaredtobeinabroaderscopethantherequestscope,suchastheview,flow,orsessionscope.Thissavestheeffortofhasslingwithhiddeninputfields.ThebeanscopeswillbedetailedinChapter8.
Theotherrequestparametersshouldspeakforthemselvesifyou’refamiliarwithbasicHTML.Theyrepresentthename/valuepairsoftheinvolvedinputelements.Youshouldbeabletodeterminewhichvalueswereactuallyenteredintheformpriortosubmitting.JSFwillalsobeabletodothesame.Itwilltraversethecomponenttreeandusethe“clientID”ofthecomponentasrequestparameternametoobtainthevaluefromtherequestparametermap.Basically,thefollowingcodewillunderthehoodbeexecutedforeachinputcomponentduringapplyrequestvaluesphase(secondphase).This
happensintheUIInput#decode()method.
FacesContextcontext=FacesContext.getCurrentInstance();
ExternalContextexternalContext=
context.getExternalContext();
Map<String,String>formData=
externalContext.getRequestParameterMap();
StringclientId=component.getClientId(context);
StringsubmittedValue=formData.get(clientId);
component.setSubmittedValue(submittedValue);
And,duringthesamephase,thefollowingcodeisbasicallyexecutedforeachcommandcomponentinthedecodemethodoftherendererassociatedwiththecomponent:if(formData.get(clientId)!=null){
component.queueEvent(newActionEvent(context,
component));
}
Duringtheprocessvalidationsphase(thirdphase),JSFwillsetthesubmittedvalueofeveryinvolvedinputcomponentas“localvalue”afterperformingthenecessaryconversionandvalidationifanyconverterorvalidatorisregisteredonthecomponentorassociatedbeanpropertyandhasexecutedwithouterrors.ThishappensintheUIInput#validate()methodwhosecorelogicisshowninthefollowingcodeina(very!)simplifiedform:
StringsubmittedValue=component.getSubmittedValue();
try{
Converterconverter=component.getConverter();
ObjectnewValue=
component.getConvertedValue(submittedValue);
for(Validatorvalidator:component.getValidators()){
validator.validate(context,component,newValue);
}
component.setValue(newValue);
component.setSubmittedValue(null);
}
catch(ConverterException|ValidatorExceptione){
context.addMessage(clientId,e.getFacesMessage());
context.validationFailed();//Skipsphases4and5.
component.setValid(false);
}
WhentherearenovalidationerrorsandtheFacesContext#isValidationFailed()thusreturnsfalse,thenJSFwilladvancetotheupdatemodelvaluesphase(fourthphase).Duringthisphase,the“localvalue”oftheinputcomponentswillultimatelybesetasmanagedbeanpropertiesassociatedwiththevalueattributeoftheinputcomponents.ThiswillhappenintheUIInput#updateModel()methodwhichissimplifiedasfollows:
ValueExpressionel=component.getValueExpression("value");
if(el!=null){
el.setValue(context.getELContext(),
component.getValue());
component.setValue(null);
}
TheelvariablebasicallyrepresentstheExpressionLanguage(EL)statementasdefinedinthevalueattribute,whichis,incaseofour<h:inputText>example,thus#{bean.text}.TheValueExpression#setValue()willbasicallytriggerthesettermethodbehindthisexpression
withthecomponent’svalue.So,effectivelyitwillexecutebean.setText(component.getValue()).
Onceallmodelvalueshavebeenupdated,JSFwilladvancetotheinvokeapplicationphase(fifthphase).AnyActionEventwhichis,duringtheapplyrequestvaluesphase(secondphase),queuedinacommandcomponentwillbebroadcasted.Itwillultimatelyinvokeallmethodsassociatedwiththecommandcomponent.Inthecaseofour<h:commandButton>example,whichhas#{bean.submit}definedasanactionattribute,itwillinvoketheBean#submit()method.Finally,JSFwilladvancetothelastphase,therenderresponsephase(sixthphase),generatingtheHTMLoutputandtherebyinvokingthegettermethodsinordertoobtainthemodelvaluestobeembeddedintheHTMLoutput.
File-BasedInputComponentYes,thereisonlyonefile-basedinputcomponent.That’sthe<h:inputFile>.Ithasonlyoneadditionalrequirementonthe<h:form>itisbeingplacedin,itsenctypeattributehastobeexplicitlysettomultipart/form-datatoconformtheHTMLspecification.Thishasnoeffectonotherinputcomponents;theywillcontinuetoworkjustfine.It’sjustthatthedefaultformencodingapplication/x-www-form-urlencodeddoesn’tsupportembeddingbinarydata.Themultipart/form-dataencodingsupportsthis,butitisonlyslightlymoreverbose.Everyrequestparametervalueisprecededbyaboundaryline,aContent-Dispositionheaderwiththerequestparametername,aContentTypeheaderwith
thecontenttypeofthevalue,andtwonewlines.ItisveryinefficientcomparedtothedefaultencodingwhereintheURLencodedrequestparametername/valuepairsarejustconcatenatedbythe&character,butit’sactuallytheonlyreliablewaytobeabletoembedfilesinaHTTPPOSTrequestwithoutinducingambiguity,particularlywhenuploadingtextfileswhosecontentcoincidentallyresemblesname/valuepairs.
Thevalueattributeofthe<h:inputFile>shouldbeboundtoabeanpropertyofthejavax.servlet.http.Partinterface.
Faceletsfile/test.xhtml:<h:formid="form"enctype="multipart/form-data">
<h:inputFileid="file"value="#{bean.file}"/>
<h:commandButtonid="submit"value="Submit"
action="#{bean.submit}"/>
</h:form>
Backingbeanclasscom.example.project.view.Bean:@Named@RequestScoped
publicclassBean{
privatePartfile;
publicvoidsubmit()throwsIOException{
System.out.println("Formhasbeensubmitted!");
System.out.println("file:"+file);
if(file!=null){
System.out.println("name:"+
file.getSubmittedFileName());
System.out.println("type:"+
file.getContentType());
System.out.println("size:"+
file.getSize());
InputStreamcontent=file.getInputStream();
//WritecontenttodiskorDB.
}
}
//Add/generategettersandsettersforevery
propertyhere.
}
GeneratedHTMLoutput:
<formid="form"name="form"method="post"
action="projecttest.xhtml"
enctype="multipart/form-data">
<inputtype="hidden"name="form"value="form"/>
<inputid="form:file"type="file"name="form:file"/>
<inputid="form:submit"type="submit"name="form:submit"
value="Submit"/>
<inputtype="hidden"name="javax.faces.ViewState"
id="j_id1:javax.faces.ViewState:0"
value="6034213708100805615:8835868421785849982"
autocomplete="off"/>
</form>
RenderinginChromebrowser(withnewlinesadded):
Therequestprocessinglifecycleisthesameasfortext-based
inputcomponents,exceptfortheapplyrequestvaluesphase(secondphase).InsteadofextractingthesubmittedfileasarequestparameterintheUIInput#decode()method,thesubmittedfileisbeingextractedasarequestpartintherendererassociatedwiththefileinputcomponent.Thedefaultimplementationbasicallylooksasfollows:FacesContextcontext=FacesContext.getCurrentInstance();
ExternalContextec=context.getExternalContext();
HttpServletRequestrequest=(HttpServletRequest)
ec.getRequest();
StringclientId=component.getClientId(context);
PartsubmittedValue=request.getPart(clientId);
component.setSubmittedValue(submittedValue);
SelectionComponentsJSFoffersabunchofselectioncomponentsoftheUISelectBoolean,UISelectOne,andUISelectManycomponentfamilieswhichallextendfromUIInput.ExceptfortheUISelectBoolean,theyallexpecttheavailableitemsforselectiontobeprovidedvia<f:selectItems>or<f:selectItem>tagsnestedintheselectioncomponent.ThevalueattributeofaUISelectBooleancomponentcanonlybeboundtoabeanpropertyofbooleanorBooleantypeanddoesn’tsupportaconverter,whileotherssupportaconverter.ThevalueattributeofaUISelectOnecomponenthastobeboundtoasingle-valuepropertysuchasString,andthevalueattributeofaUISelectManycomponentcanonlybeboundtoa
multi-valuepropertysuchasCollection<String>orString[].
Inreal-worldHTML-basedwebapplications,the<h:selectOneListbox>(single-selectlistbox)and<h:selectManyMenu>(multi-selectdrop-down)aren’tterriblyuseful.Generallythe<h:selectOneMenu>(single-selectdrop-down)and<h:selectManyListBox>(multi-selectlistbox)arepreferredastheyaremoreuserfriendly.Followingisabasicusageexamplewhichdemonstratesallselectioncomponentsexceptfortheaforementionedleastusefulones.Incaseyouwanttousethemanyway,justfollowthedemonstratedapproachwithadifferenttagname.
Faceletsfile/test.xhtml:<h:formid="form"><h:selectBooleanCheckboxid="checked"value="#
{bean.checked}"/>
<h:selectOneMenuid="oneMenu"value="#
{bean.oneMenu}">
<f:selectItemsvalue="#{bean.availableItems}"/>
</h:selectOneMenu>
<h:selectOneRadioid="oneRadio"value="#
{bean.oneRadio}">
<f:selectItemsvalue="#{bean.availableItems}"/>
</h:selectOneRadio>
<h:selectManyListboxid="manyListbox"value="#
{bean.manyListbox}">
<f:selectItemsvalue="#{bean.availableItems}"/>
</h:selectManyListbox>
<h:selectManyCheckboxid="manyCheckbox"value="#
{bean.manyCheckbox}">
<f:selectItemsvalue="#{bean.availableItems}"/>
</h:selectManyCheckbox>
<h:commandButtonid="submit"value="Submit"
action="#{bean.submit}"/>
</h:form>
Backingbeanclasscom.example.project.view.Bean:@Named@RequestScoped
publicclassBean{
privatebooleanchecked;
privateStringoneMenu;
privateStringoneRadio;
privateList<String>manyListbox;
privateList<String>manyCheckbox;
privateList<String>availableItems;
@PostConstruct
publicvoidinit(){
availableItems=Arrays.asList("one","two",
"three");
}
publicvoidsubmit(){
System.out.println("Formhasbeensubmitted!");
System.out.println("checked:"+checked);
System.out.println("oneMenu:"+oneMenu);
System.out.println("oneRadio:"+oneRadio);
System.out.println("manyListbox:"+
manyListbox);
System.out.println("manyCheckbox:"+
manyCheckbox);
}
//Add/generategettersandsettersforevery
propertyhere.
//NotethatavailableItemspropertydoesn’tneeda
setter.
}
GeneratedHTMLoutput:
<formid="form"name="form"method="post"
action="projecttest.xhtml"
enctype="application/x-www-form-urlencoded">
<inputtype="hidden"name="form"value="form"/>
<inputid="form:checked"type="checkbox"
name="form:checked"/>
<selectid="form:oneMenu"name="form:oneMenu"size="1">
<optionvalue="one">one</option>
<optionvalue="two">two</option>
<optionvalue="three">three</option>
</select>
<tableid="form:oneRadio">
<tr>
<td>
<inputid="form:oneRadio:0"type="radio"
name="form:oneRadio"value="one"/>
<labelfor="form:oneRadio:0">one</label>
</td>
<td>
<inputid="form:oneRadio:1"type="radio"
name="form:oneRadio"value="two"/>
<labelfor="form:oneRadio:1">two</label>
</td>
<td>
<inputid="form:oneRadio:2"type="radio"
name="form:oneRadio"value="three"/>
<labelfor="form:oneRadio:2">three</label>
</td>
</tr>
</table>
<selectid="form:manyListbox"name="form:manyListbox"
multiple="multiple"size="3">
<optionvalue="one">one</option>
<optionvalue="two">two</option>
<optionvalue="three">three</option>
</select>
<tableid="form:manyCheckbox">
<tr>
<td>
<inputid="form:manyCheckbox:0"
type="checkbox"
name="form:manyCheckbox"value="one"/>
<labelfor="form:manyCheckbox:0">one</label>
</td>
<td>
<inputid="form:manyCheckbox:1"
type="checkbox"
name="form:manyCheckbox"value="two"/>
<labelfor="form:manyCheckbox:1">two</label>
</td>
<td>
<inputid="form:manyCheckbox:2"
type="checkbox"
name="form:manyCheckbox"value="three"/>
<labelfor="form:manyCheckbox:2">
three</label>
</td>
</tr>
</table>
<inputid="form:submit"type="submit"name="form:submit"
value="Submit"/>
<inputtype="hidden"name="javax.faces.ViewState"
id="j_id1:javax.faces.ViewState:0"
value="403461711995663039:117935361680169981"
autocomplete="off"/>
</form>
RenderinginChromebrowser(withnewlinesadded):
InthegeneratedHTMLoutput,you’llimmediatelynoticethat<h:selectOneRadio>and<h:selectManyCheckbox>generateanHTMLtablearoundtheinputs.SuchamarkupisindeedfrowneduponsinceWeb2.0.ThisissomewhataleftoverofJSF1.0,whenWeb2.0didn’texistyet.Forthe<h:selectManyCheckbox>thiscouldeasilybeworkedaroundbyusingabunchof<h:selectBooleanCheckbox>componentsinthedesiredHTMLmarkupwhichareboundagainstaslightlyadjustedmodel.
Faceletsfile/test.xhtml:<h:formid="form"><ul>
<ui:repeatid="many"value="#
{bean.availableItems}"var="item">
<li>
<h:selectBooleanCheckboxid="checkbox"
value="#
{bean.manyCheckboxMap[item]}"/>
<h:outputLabelfor="checkbox"value="#
{item}"/>
</li>
</ui:repeat>
</ul>
<h:commandButtonid="submit"value="Submit"
actionListener="#{bean.collectCheckedValues}"
action="#{bean.submit}"/>
</h:form>
Backingbeanclasscom.example.project.view.Bean:@Named@RequestScoped
publicclassBean{
privateList<String>manyCheckbox;
privateList<String>availableItems;
privateMap<String,Boolean>manyCheckboxMap=new
LinkedHashMap<>();
@PostConstruct
publicvoidinit(){
availableItems=Arrays.asList("one","two",
"three");
}
publicvoidcollectCheckedValues(){
manyCheckbox=
manyCheckboxMap.entrySet().stream()
.filter(e->e.getValue())
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
publicvoidsubmit(){
System.out.println("Formhasbeensubmitted!");
System.out.println("manyCheckbox:"+
manyCheckbox);
}
//Add/generategettersforavailableItemsand
manyCheckboxMap.
//Notethatsettersarenotnecessaryforthem.
}
GeneratedHTMLoutput:
<formid="form"name="form"method="post"
action="projecttest.xhtml"
enctype="application/x-www-form-urlencoded">
<inputtype="hidden"name="form"value="form"/>
<ul>
<li>
<inputid="form:many:0:checkbox"type="checkbox"
name="form:many:0:checkbox"/>
<labelfor="form:many:0:checkbox">one</label>
</li>
<li>
<inputid="form:many:1:checkbox"type="checkbox"
name="form:many:1:checkbox"/>
<labelfor="form:many:1:checkbox">two</label>
</li>
<li>
<inputid="form:many:2:checkbox"type="checkbox"
name="form:many:2:checkbox"/>
<labelfor="form:many:2:checkbox">three</label>
</li>
</ul>
<inputid="form:submit"type="submit"name="form:submit"
value="Submit"/>
<inputtype="hidden"name="javax.faces.ViewState"
id="j_id1:javax.faces.ViewState:0"
value="-2278907496447873737:-4769857814543424434"
autocomplete="off"/>
</form>
RenderinginChromebrowser:
That’salreadymoreWeb2.0friendly.Thebulletsofthe<ul>canofcoursebehiddenbysettingtheCSS(CascadingStyleSheets)list-style-typepropertytonone.NotethattheactionListenerattributeofthe<h:commandButton>alwaysrunsbeforetheactionattribute.Thesameapproachwasnotpossiblefor<h:selectOneRadio>foralongtime.There’snosuchcomponentas<h:radioButton>oranythinglikethat.Solutionsweresoughtinthird-partycomponentlibrariessuchasPrimeFaces.SinceJSF2.2thiscouldbetrickedwiththenew“pass-throughelements”and“pass-throughattributes”featureonplainHTML<inputtype="radio">elements. OnlysinceJSF2.3hasitbeennativelypossible1
withhelpofthenewgroupattributewhichbasicallyrepresentsthesameasthenameattributeoftheplainHTML<inputtype="radio">element.
Faceletsfile/test.xhtml:<h:formid="form"><ul>
<ui:repeatid="one"value="#
{bean.availableItems}"var="item">
<li>
<h:selectOneRadioid="radio"
group="groupName"
value="#{bean.oneRadio}">
<f:selectItemitemValue="#{item}"/>
</h:selectOneRadio>
<h:outputLabelfor="radio"value="#
{item}"/>
</li>
</ui:repeat>
</ul>
<h:commandButtonid="submit"value="Submit"
action="#{bean.submit}"/>
</h:form>
Backingbeanclasscom.example.project.view.Bean:@Named@RequestScoped
publicclassBean{
privateStringoneRadio;
privateList<String>availableItems;
@PostConstruct
publicvoidinit(){
availableItems=Arrays.asList("one","two",
"three");
}
publicvoidsubmit(){
System.out.println("Formhasbeensubmitted!");
System.out.println("oneRadio:"+oneRadio);
}
//Add/generategettersandsettersforevery
propertyhere.
//NotethatavailableItemspropertydoesn’tneeda
setter.
}
GeneratedHTMLoutput:
<formid="form"name="form"method="post"
action="projecttest.xhtml"
enctype="application/x-www-form-urlencoded">
<inputtype="hidden"name="form"value="form"/>
<ul>
<li>
<inputtype="radio"id="form:one:0:radio"
name="form:groupName"
value="form:one:0:radio:one"/>
<labelfor="form:one:0:radio">one</label>
</li>
<li>
<inputtype="radio"id="form:one:1:radio"
name="form:groupName"
value="form:one:1:radio:two"/>
<labelfor="form:one:1:radio">two</label>
</li>
<li>
<inputtype="radio"id="form:one:2:radio"
name="form:groupName"
value="form:one:2:radio:three"/>
<labelfor="form:one:2:radio">three</label>
</li>
</ul>
<inputid="form:submit"type="submit"name="form:submit"
value="Submit"/>
<inputtype="hidden"name="javax.faces.ViewState"
id="j_id1:javax.faces.ViewState:0"
value="3336433674711048358:164229014603307903"
autocomplete="off"/>
</form>
RenderinginChromebrowser:
Technically,the<h:selectManyCheckbox>couldsupportthegroupattributetoo,butthishasn’tyetbeenimplemented.PerhapsitwillbeinJSF.next.
SelectItemTagsProvidingavailableitemsforUISelectOneandUISelectManycomponentscanbedoneinseveralways.Asdemonstratedintheprevioussection,youcanusethe<f:selectItems>and<f:selectItem>tagsnestedintheselectioncomponentforthis.Youcanusethe<f:selectItem>tagtodefinetheavailableitemsentirelyontheviewside.Followingisanexampleusing
<h:selectOneMenu>,butyoucanuseitthesamewayinanyotherUISelectOneandUISelectManycomponent:<h:selectOneMenuid="selectedItem"value="#
{bean.selectedItem}">
<f:selectItemitemValue="#{null}"itemLabel="--
selectone--"/>
<f:selectItemitemValue="one"itemLabel="Firstitem"
/>
<f:selectItemitemValue="two"itemLabel="Second
item"/>
<f:selectItemitemValue="three"itemLabel="Third
item"/>
</h:selectOneMenu>
Notethataselectitemwithvalueof#{null}canbeusedtopresentthedefaultselectionincasethebeanpropertyassociatedwithselectioncomponent’svalueattributeisnull.Ifyouhaveconsultedthetagdocumentationof<f:selectItem>,thenyou’llperhapshavenoticedthenoSelectionOptionattributeandhavethoughtthatitwasintendedtorepresenta“noselectionoption.”Actually,thisisn’ttrue.Manystartersindeedthinkso,asyoucanseeinmanyforums,Q&Asites,andpoor-qualitytutorialsontheInternet.Inspiteofthemisleadingattributename,itdoesnotrepresenta“noselectionoption.”AbetterattributenamewouldhavebeenhideWhenOtherOptionIsSelected,andeventhenitworksonlywhentheparentselectioncomponenthasexplicitlyahideNoSelectionOption="true"attributesetliketheonethatfollows:<h:selectOneMenuid="selectedItem"value="#{bean.selectedItem}"
hideNoSelectionOption="true">
<f:selectItemitemValue="#{null}"itemLabel="--
selectone--"
noSelectionOption="true"/>
<f:selectItemitemValue="one"itemLabel="Firstitem"
/>
<f:selectItemitemValue="two"itemLabel="Second
item"/>
<f:selectItemitemValue="three"itemLabel="Third
item"/>
</h:selectOneMenu>
So,hideWhenOtherOptionIsSelectedAndHideNoSele
ctionOptionIsTruewouldultimatelyhavebeenthemostself-explanatoryattributename.Unfortunately,thiswasn’tverywellthoughtoutwhenthenoSelectionOptionwasimplementedinJSF1.2.Requiringtwoattributesforthisattributetofunctionshouldn’thavebeennecessary.Theprimarypurposeofthisattributepairistopreventthewebsiteuserfrombeingabletoreselectthe“noselectionoption”whenthecomponenthasalreadyanon-nullvalue—forexample,byhavingitpreparedina@PostConstructmethod,orbyre-renderingthecomponentafteraformsubmitwithanon-nullvalue.
Thatsaid,theitemValueattributeofthe<f:selectItem>representsthevaluethatwillbesetasbeanpropertywhentheformissubmitted,andthevaluethatwillbepreselectedfromanynon-nullbeanpropertywhentheHTMLoutputistobegenerated.TheitemLabelattributerepresentsthelabelthatwillbedisplayedtotheweb
siteuser.WhentheitemLabelattributeisabsent,JSFwilldefaulttoitemValue.Notethatthelabelisinnowaysubmittedbacktotheserver.Thatis,inthegeneratedHTMLoutput,the<option>labelisnotpartofthe<option>value.
Youcanusethe<f:selectItems>tagtoreferenceaCollection,Map,orarrayofavailableitemsinthebackingbean.Youcanevenmixthiswith<f:selectItem>tags.
<h:selectOneMenuid="selectedItem"value="#
{bean.selectedItem}">
<f:selectItemitemValue="#{null}"itemLabel="--select
one--"/>
<f:selectItemsvalue="#{bean.availableItems}"/>
</h:selectOneMenu>
Theywillberenderedinthesameorderastheyaredeclaredintheview.OnlywhenyouuseanunorderedMapimplementationasvalue,suchasHashMap,theorderofitemsprovidedby<f:selectItems>willbeundefined.It’sthereforebettertouseanorderedMapimplementation,suchasTreeMaporLinkedHashMap.WhenpopulatingtheavailableitemsasaMap,keepinmindthatthemapkeyrepresentstheitemlabelandthemapvaluerepresentstheitemvalue.You’dperhapsintuitivelyexpectittobetheotherwayaround,butthiswasatechnicallimitation.Thatis,ontheJavaside,themapkeyenforcesuniquenesswhilethemapvaluedoesn’t.AndontheHTMLside,theoptionlabelissupposedtobeuniquewhiletheoptionvaluedoesn’tneedtobe.Followingishowyoucanpopulatesuchamap:privateMap<String,String>availableItems;
@PostConstruct
publicvoidinit(){
availableItems=newLinkedHashMap<>();
availableItems.put("Firstitem","one");
availableItems.put("Seconditem","two");
availableItems.put("Thirditem","three");
}
//Add/generategetter.Notethatasetteris
unnecessary.
Assaid,youcanalsouseaTreeMaporHashMap,butthentheitemlabelswillbecome,respectively,sortedorunsorted,regardlessoftheinsertionorder.
Incaseyou’dreallyliketoswapthemapkeysandvaluesaroundontheviewside,youcanalwaysdosobymanuallyassigningthemapentryvalueasanitemlabelandthemapentrykeyasanitemvalue.Youcandothatwithhelpofthevarattributeofthe<f:selectItems>bywhichyoucandeclaretheELvariablenameofthecurrentlyiterateditem.Thiscan,inturn,beaccessedintheitemValueanditemLabelattributesofthesametag.WhenyoupassMap#entrySet()tothevalueattributeofthe<f:selectItems>,theneachiterateditemwillrepresentaMap.Entryinstance.Thishas,inturn,getKey()andgetValue()methodswhicharethusperfectlyusableasELproperties.
<f:selectItemsvalue="#{bean.availableItems.entrySet()}"
var="entry"
itemValue="#{entry.key}"itemLabel="#{entry.value}">
</f:selectItems>
ThisalsoworkswhenusingaCollectionorarrayasavailableitems.Youdon’texplicitlyneedtofirstconvertittoaSet(morespecifically,Iterable),asdemonstratedabove.ThisisparticularlyusefulwhenyouhaveaCollectionoranarrayofcomplexobjectsasavailableitems,suchasmodelentities.
Modelentityrepresentinga“country”:
publicclassCountry{
privateLongid;
privateStringcode;
privateStringname;
//Add/generategettersandsetters.
}
Backingbean:
@Named@RequestScoped
publicclassBean{
privateStringcountryCode;
privateList<Country>availableCountries;
@Inject
privateCountryServicecountryService;
@PostConstruct
publicvoidinit(){
availableCountries=countryService.getAll();
}
//Add/generategettersandsetters.
//Notethatasetterisunnecessaryfor
availableCountries.
}
View:
<h:selectOneMenuid="countryCode"value="#
{bean.countryCode}">
<f:selectItemitemValue="#{null}"itemLabel="--select
one--"/>
<f:selectItemsvalue="#{bean.availableCountries}"
var="country">
itemValue="#{country.code}"itemLabel="#
{country.name}"
</f:selectItems>
</h:selectOneMenu>
Notethatanypersistenceframework-specificannotations,suchasJPA’s@Entityand@Id,andtheactualimplementationofCountryService,areomittedforclarity.Thoseareirrelevanttoanyfront-endframework,suchasJSF.
Withtheaboveconstruct,thevalueasobtainedfromCountry#getCode()willendupasvalueofthegeneratedHTML<option>element.Now,whentheformissubmitted,itwillbecomethesubmittedvalueoftheselectioncomponent,whichwillinturninvokethesettermethodbehindthe#{bean.countryCode}propertywithexactlythatvalue.Ofcourse,youcanalsousethewholeCountryobjectasthepropertyvalueoftheselectioncomponent,butthatwouldrequireaconverterwhichcanconvertbetweenthecomplexobjectandauniquestringsuitabletobeembeddedinHTMLoutputandsentasanHTTPrequestparameter.You
canreadmoreinChapter5.
SelectItemGroupIncaseyou’dliketogroupabunchofoptionsunderacommonlabel,youcanusethejavax.faces.model.SelectItemGroup,whichyouinturnreferenceinthevalueattributeofthe<f:selectItems>.Unfortunately,thiscannotbedonedeclarativelyintheFaceletsfileonacustomnestedmodel.YoureallyhavetomapyourmodelintotheJSF-providedjavax.faces.model.SelectItemforthis.Followingisakickoffexample:privateList<SelectItem>availableItems;
@PostConstruct
publicvoidinit(){
SelectItemGroupgroup1=newSelectItemGroup("Group
1");
group1.setSelectItems(newSelectItem[]{
newSelectItem("Group1Value1","Group1Label
1"),
newSelectItem("Group1Value2","Group1Label
2"),
newSelectItem("Group1Value3","Group1Label
3")
});
SelectItemGroupgroup2=newSelectItemGroup("Group
2");
group2.setSelectItems(newSelectItem[]{
newSelectItem("Group2Value1","Group2Label
1"),
newSelectItem("Group2Value2","Group2Label
2"),
newSelectItem("Group2Value3","Group2Label
3")
});
availableItems=Arrays.asList(group1,group2);
//Add/generategetterforavailableItems.
//Notethatasetterisunnecessary.
}
NotedthatbothmodelAPIshavebasicallynotchangedsinceJSF1.0(2004)andthat’swhyyoustillseeaSelectItemGroup#setSelectItems()methodtakingaSelectItem[]arrayinsteadofaSelectItem…varargsargument.ThiswillcertainlybeworkedonforJSF.next.Whenreferencingitas<f:selectItemsvalue="#{bean.availableItems}"/>inanyselectioncomponent,belowishowitwouldlookforeachofthem.
The<h:selectOneMenu>willrendereachgroupas
HTML<optgroup>:
The<h:selectOneRadiolayout="pageDirection">willrenderitasanested
The<h:selectManyCheckboxlayout="pageDirection">willrenderitasanestedtable:
Notetheimportanceofthelayout="pageDirection"attributein<h:selectOneRadio>and<h:selectManyCheckbox>.Thiswilllookmuchbetterthanthedefaultoflayout="lineDirection"whichwouldrendereverythinginabigsingletablerow.
LabelandMessageComponents
table:
The<h:selectManyListbox>willrendereachgroupas
HTML<optgroup>:
Inanaveragewell-designedform,inputelementsareusuallyaccompaniedwithalabelelementandamessageelementtargetingtheinputfield.InHTML,labelsarerepresentedbythe<label>element.InJSF,youcanusethe<h:outputLabel>componenttogenerateanHTML<label>element.HTMLdoesnothaveadedicatedelementtorepresentamessage.InJSF,the<h:message>componentgeneratesaHTML<span>elementandthe<h:messages>componentgenerateseithera<ul>elementora<table>element,dependingonthevalueofthelayoutattribute.
ThelabelelementhasvariousSEO(searchengineoptimization)andusabilityadvantages.Ittellsintextabouttheassociatedinputelement.Screenreaders,likethoseusedbypeoplewithvisualdisabilities,willfindthelabelandtellitscontentsbysound.Searchbotswillfindthelabelandindextheassociatedinputelementassuch.And,thelabelwillfocusandactivatetheassociatedinputelementwhenbeingclickeditself.Text-basedinputelementswillthenshowthetextcursor.Checkboxandradioinputelementswillthenbetoggled.Listboxanddrop-downinputelementswillthenbefocused.Fileinputelementswillthenopenthebrowsedialog.Submitbuttonswillthenbeinvoked.
Themessageelementisusuallytobeusedtodisplayconversionandvalidationerrormessagescomingfromtheserverside.Thisway,theenduserisinformedaboutthestateoftheformandcanactaccordingly,usuallybycorrectingtheinputvalues.Youcanalsouseittodisplaywarningorinformalmessages.
InJSF,the<h:outputLabel>,<h:message>and<h:messages>componentshaveaforattributewhereinyounormallydefinetheIDoftheassociatedUIInputcomponent.Followingisanexampleintheflavorofaloginform:<h:formid="login"><fieldset>
<legend>Login</legend>
<section>
<h:outputLabelfor="email"value="Emailaddress"/>
<h:inputTextid="email"value="#{login.email}"
required="true"/>
<h:messageid="m_email"for="email"/>
</section>
<section>
<h:outputLabelfor="password"value="Password"/>
<h:inputSecretid="password"value="#{login.password}"
required="true"/>
<h:messageid="m_password"for="password"/>
</section>
<footer>
<h:commandButtonid="submit"value="Login"
action="#{login.submit}"/>
</footer>
</fieldset>
</h:form>
Youcanactuallyuseanyarbitrarycomponentsearchexpressionintheforattribute.Forthe<h:outputLabel>component,thisdoesn’tmakemuchsense.Forthe<h:message>and<h:messages>components,referringtheIDofanon-UIInputcomponentwouldonlymakesensewhenyouwanttoprogrammaticallyaddafacesmessagefromthemanagedbeanon.ButyouwouldthenneedtoknowtheclientIDofthetargetcomponent.
<h:formid="login">
...
<h:commandButtonid="submit"value="Login"
action="#{login.submit}"/>
<h:messageid="m_submit"for="submit"/>
...
</h:form>
Theabove<h:commandButton>willgenerateaclientIDof"login:submit"intheHTMLoutput.Youcanthenprogrammaticallyaddafacesmessageasfollows:publicStringsubmit(){try{
yourAuthenticator.authenticate(email,password);
return"userhome.xhtml?faces-redirect=true";
}
catch(YourAuthenticationExceptione){
FacesContextcontext=FacesContext.getCurrentInstance();
FacesMessagemessage=newFacesMessage("Authentication
failed");
context.addMessage("login:submit",message);
returnnull;
}
}
Abetterpractice,however,istoaddthefacesmessageasaglobalmessagebypassingnullastheclientID.
context.addMessage(null,message);
Suchamessagewillthenonlyendupina<h:messagesglobalOnly="true">.
<h:commandButtonid="submit"value="Login"
action="#{login.submit}"/>
<h:messagesid="messages"globalOnly="true"
rendered="#{component.namingContainer.submitted}"/>
Notethelogicintherenderedattributeofthemessagescomponent.ItwillthusonlyberenderedwhenthesubmittedpropertyoftheNamingContainerparentevaluatestotrue.Inthisspecificcase,it’s
consultingtheUIForm#isSubmitted().Thisisveryusefulincaseyouhavemultiplenon-Ajaxformseachwithitsownglobalmessagescomponentand/ora“catch-all”<h:messagesredisplay="false">componentsomewherenearthebottomoftheJSFpage,whichisthenusingCSSfixedpositionedontop.Otherwisetheglobalmessagewouldunintentionallyshowupoverthereaswell.
Thismessage-renderinglogicisnotnecessaryinAjaxformsasyoucouldjustfine-tunethemessagerenderingbysimplyexplicitlyspecifyingthemessage(s)component(s)intherenderattributeof<f:ajax>.Moreover,theUIForm#isSubmitted()wouldunexpectedlyreturnfalsewhentheexecuteattributeofthe<f:ajax>doesnotexplicitlytargettheformasinexecute="@form".
CommandComponentsYou’llhavenoticedexamplesofthe<h:commandButton>intheprevioussectionsaboutinputandselectcomponents.ThisthusgeneratesanHTML<inputtype="submit">element,whichistheHTMLwaytosendallinputvaluesofthe<form>elementitissittingintotheserver.Ontheserverside,thiscomponentisalsocapableofinvokingoneormoreJavamethods,whichareusuallydefinedintheactionoractionListenerattribute,orviaa<f:actionListener>tagnestedinthecommandcomponent.You’llalsohavereadthattheactionlistenermethodalwaysrunsbeforethemethodassociatedwiththeactionattribute.
Youcanusethe<f:actionListener>tagtoregisteroneormoreadditionalactionlistenersontheverysamecommandcomponent.Allthoseactionlistenersareinvokedinthesameorderasthey’redeclaredintheviewandattachedtothecomponent.Thetargetmethodcanbedeclaredinthreewaysonthe<f:actionListener>tag.Onewayisviathetypeattributeandtheothertwowaysareviathebindingattribute.
<h:commandButton...>
<f:actionListenertype="com.example.project.SomeActionListener"/>
<f:actionListenerbinding="#{beanImplementingActionListener}"/>
<f:actionListenerbinding="#{bean.someActionListenerMethod()}"/>
<h:commandButton>
ThetypeattributeinthefirstwaymustbasicallyrepresentthefullyqualifiednameoftheclassimplementingtheActionListenerinterface.
packagecom.example.project;
importjavax.faces.event.ActionListener;
importjavax.faces.event.ActionEvent;
publicclassSomeActionListenerimplementsActionListener{
@Override
publicvoidprocessAction(ActionEventevent){
//...
}
}
ThebindingattributeinthesecondwaymustbasicallyreferenceamanagedbeaninstanceimplementingtheActionListenerinterface.
@Named@RequestScoped
publicclassBeanImplementingActionListenerimplementsActionListener{
@Override
publicvoidprocessAction(ActionEventevent){
//...
}
}
Andthebindingattributeinthethirdwaycanbasicallyreferenceanyarbitrarymanagedbeanmethodwhichisdeclaredvoid.
@Named@RequestScoped
publicclassBean{
publicvoidsomeActionListenerMethod(){
//...
}
}
Notethatthethirdwayismoreorlessundocumented.IthasonlybeenpossiblesincetheintroductionofEL2.2(2009),whereindeveloperscouldstartexplicitlydeclaringmethodexpressionsbysimplyaddingtheparenthesis,ifnecessarywitharguments.Coincidentally,thebindingattributeofthe<f:actionListener>coulddealwiththem.UnderthehoodofthebindingattributeistreatedasaValueExpressionandthelogicexpectedtoobtainamanagedbeaninstanceimplementingtheActionListenerinterfacewheninvokingValueExpression#getValue().However,insteadofagetter,avoidmethodwasinvokedandreturnednothingwhichisinterpretedasnull.So,thelogiccontinuedsilentlyasifthereweresimplynobeaninstanceavailable.
Theactionlistenershaveanadditionalfeatureontopoftheactionattribute.Whenajavax.faces.event.AbortProcessingExceptionisexplicitlythrownfromanactionlistener,thenJSFwillswallowtheexceptionandabortprocessingtheinvokeapplicationphase(fifthphase)andimmediatelyadvancetotherenderresponsephase(sixthphase).Allremainingactionlistenersandtheactionmethod,ifany,willbeskipped.Theswallowedexceptionwon’tendupinanyerrorresponse.Giventhisfact,andthefactthatactionlistenersarealwaysinvokedbeforetheactionmethod,youcould(ab)useittoperformsomeconversionandvalidationbasedonalreadyupdatedmodelvaluesbeforetheactionmethodisinvoked.
publicvoidsomeActionListenerMethod(){
try{
convertOrValidate(this);
}catch(SomeConversionOrValidationExceptione){
FacesContextcontext=FacesContext.getCurrentInstance()
context.addMessage(null,newFacesMessage(e.getMessage()));
thrownewAbortProcessingException(e);
}
}
publicvoidsomeActionMethod(){
//Won'tbeinvokedwhenAbortProcessingExceptionisthrown.
}
I’msaying“(ab)use”becauseitisessentiallytheresponsibilityofanormalConverterorValidatorimplementationtoperformsuchtask,sothatthemodelvaluesarenotpollutedwithinvalidvalues.However,foralongtimeinJSFitwasnotpossibletoperformconversionorvalidationbasedonmultiplefields.Hencedevelopersstartedusingtheaction(listener)methods,whichisessentiallyaviolationoftheJSFlifecycle.
OnlyinJSF2.3wasanew<f:validateWholeBean>tagintroducedtoperformvalidationonmultiplefields.YoucanreadmoreaboutthisinChapter5.Sothisleavesonlyonereasonablereal-worldusecaseopenforactionlistenermethods:performingconversionbasedononeormoremodelvalues.Oneexamplehasalreadybeendemonstratedinthesection“SelectionComponents”(thecaseofusingmultiple<h:selectBooleanCheckbox>in<ui:repeat>toworkaroundthefactthatthe<h:selectManyCheckbox>generatesanHTMLtable).Anotherexamplewouldbeinvokinganexternalwebservicewiththesuppliedmodelvaluesandobtainingitsresultasa“converted”value.Theactionmethodshouldthenstillbeusedtoexecutebusinessservicelogic.Whentheactionmethodthrowsanexception,therequestwillendupasanHTTP500errorresponse.YoucanfindmoreaboutthisinChapter9.
Apartfromthe<h:commandButton>,JSFofferstwomorecommandcomponents:the<h:commandLink>andthe<h:commandScript>.The
<h:commandLink>hasbasicallyanidenticallifecycleasthe<h:commandButton>,exceptthatitgeneratesanHTML<a>elementwhichsubmitstheenclosedformwiththehelpofJavaScript.Everywherewhereyouuse<h:commandButton>,itcouldbesubstitutedwith<h:commandLink>.
<h:formid="form">
...
<h:commandLinkid="submit"value="Submit"action="#{bean.submit}"/>
</h:form>
WherebythegeneratedHTMLoutputlooksasfollows:
<formid="form"name="form"method="post"action="projecttest.xhtml"
enctype="application/x-www-form-urlencoded">
<inputtype="hidden"name="form"value="form"/>
...
<scripttype="text/javascript"
src="projectjavax.faces.resource/jsf.js.xhtml?ln=javax.faces">
</script>
<aid="form:submit"href="#"onclick="
mojarra.jsfcljs(
document.getElementById('form'),
{'form:submit':'form:submit'},
''
);returnfalse;">Submit</a>
<inputtype="hidden"name="javax.faces.ViewState"
id="j_id1:javax.faces.ViewState:0"
value="-6936791897896630173:-5064219023156239099"
autocomplete="off"/>
</form>
AndtherenderinginChromebrowser:
InthegeneratedHTMLoutput,you’llnoticethatitauto-includesthejsf.jsJavaScriptfile.Thiscontains,amongotherthings,thejsfobjectandtheJSFimplementation-specifichelperfunctionswhichareincaseoftheMojarraimplementationputinthemojarraobject.InplainHTML,there’snowaytosubmita<form>usingan<a>element.Hence,someJavaScripthastobethrownintothegame.
InMojarra’sspecificcase,themojarra.jsfcljs()functionwillbeinvokedwiththeparentformasthefirstargument,thecommandcomponent’sclientIDasarequestparameternameandvalueasthesecondargument,andthetargetattributeofthe<h:commandLink>asthethirdargument,ifany.Underthehoodofthemojarra.jsfcljs()functionwillcreate<inputtype="hidden">elementsforeachname/valuepairinthesecondargumentandaddthemtotheformprovidedasthefirstargument,makingsurethatthoseparametersendupapostbackrequest.Thenitwillcreateatemporary<inputtype="submit">button,addittotheform,andinvoketheclick()functiononit,asifyouwouldbeusingaregularsubmitbutton.Finally,itwillremoveallofthosedynamicallycreatedelementsfromtheform.
Thisfunctionisactuallyalsousedbythe<h:commandButton>,butonlywhenyouneedtopassadditionalrequestparametersviaoneormore<f:param>tagsnestedinthecommandcomponent.
<h:formid="form">
...
<h:commandButtonid="submit"value="Submit"action="#{bean.submit}">
<f:paramname="id"value="#{otherBean.id}"/>
</h:commandButton>
</h:form>
GeneratedHTMLoutput:
<formid="form"name="form"method="post"action="projecttest.xhtml"
enctype="application/x-www-form-urlencoded">
<inputtype="hidden"name="form"value="form"/>
<scripttype="text/javascript"
src="projectjavax.faces.resource/jsf.js.xhtml?ln=javax.faces">
</script>
<inputid="form:submit"type="submit"name="form:submit"
value="Submit"onclick="mojarra.jsfcljs(
document.getElementById('form'),
{'form:submit':'form:submit','id':'42'},
'');returnfalse"/>
<inputtype="hidden"name="javax.faces.ViewState"
id="j_id1:javax.faces.ViewState:0"
value="886811437739939021:6102567809374231851"
autocomplete="off"/>
</form>
RenderinginChromebrowser:
Youcanobtaintheminthemanagedbeanvia@Inject@ManagedProperty.
@Inject@ManagedProperty("#{param.id}")
privateIntegerid;
publicvoidsubmit(){
System.out.println("SubmittedID:"+id);
}
Makesurethatyouimportthe@ManagedPropertyfromtherightpackage.JSFofferstwo,onefromthejavax.faces.beanpackagewhichisdeprecatedsinceJSF2.3andanotheronefromjavax.faces.annotationpackagewhichyoushouldbeusingforCDI.YoualsoneedtomakesurethatyouexplicitlyactivatetheJSF2.3-specificfeatureofCDI-awareELresolversbyhavingatleastonemanagedbeaninthewebapplicationexplicitlyannotated
with@FacesConfig;otherwisetheCDI-aware@ManagedPropertywouldfailtofindthecurrentinstanceofFacesContextviaCDI.Alsohere,<h:commandButton>issubstitutablewith<h:commandLink>.
There’sanotherwayofpassingparametersaroundviacommandcomponents—thatis,bysimplypassingthemasanactionmethodargument.
<h:formid="form">
...
<h:commandButtonid="submit"value="Submit"
action="#{bean.submit(otherBean.id)}">
</h:commandButton>
</h:form>
Wherebythemodifiedactionmethodlooksasfollows:
publicvoidsubmit(Integerid){
System.out.println("SubmittedID:"+id);
}
Incaseofthe<h:commandButton>,thiswon’tgenerateanyJavaScriptanditthuslooksidenticalaswhenyouaren’tusinganyactionmethodarguments.Thisthusalsomeansthatthe#{otherBean.id}valueisn’tpassedviaHTMLsourcecodebacktotheserverasarequestparameter.Thisinturnmeansthatit’sonlyevaluatedduringthepostbackrequestwhenJSFisabouttoinvoketheactionmethod.Thisinturnmeansthatthe#{otherBean.id}mustatleastbe@ViewScopedinordertobestillavailableinthepostbackrequest.Inotherwords,thisargumentpassingapproachisdefinitelynotexchangeablewiththe<f:param>tagapproachwherebybothbeanscanbejust@RequestScoped.
ThelastcommandcomponentofferedbythestandardJSFcomponentsetis<h:commandScript>.ThisisnewsinceJSF2.3.Itallowsyoutoinvokea
managedbeanactionmethodbyjustcallinganamedJavaScriptfunctionfromyourownscript.ThepostbackrequestwillalwaysbeperformedviaAjax.
<h:formid="form">
<h:commandScriptid="submit"name="invokeBeanSubmit"
action="#{bean.submit}">
</h:commandScript>
</h:form>
GeneratedHTMLoutput:
<formid="form"name="form"method="post"action="projecttest.xhtml"
enctype="application/x-www-form-urlencoded">
<inputtype="hidden"name="form"value="form"/>
<scripttype="text/javascript"
src="projectjavax.faces.resource/jsf.js.xhtml?ln=javax.faces">
</script>
<spanid="form:submit">
<scripttype="text/javascript">
varinvokeBeanSubmit=function(o){
varo=(typeofo==='object')&&o?o:{};
mojarra.ab('form:submit',null,'action',0,0,{'params':o});
}
</script>
</span>
<inputtype="hidden"name="javax.faces.ViewState"
id="j_id1:javax.faces.ViewState:0"
value="3568384626727188032:3956762118801488231"
autocomplete="off"/>
</form>
IthasnovisibleHTMLrenderinginwebbrowsers.Inthegeneratedscript,you’llseethatithasgeneratedafunctionvariablewiththesamenameasspecifiedinthenameattribute.Inthisexample,it’sindeedintheglobalscope.AsthisisconsideredpoorpracticeintheJavaScriptcontext(“globalnamespacepollution”),you’dbetterprovideanamespacedfunctionname.Thisonlypre-
requiresthatyou’vealreadydeclaredyourownnamespacesomewherebeforeintheHTMLdocument,usuallyviaaJavaScriptfileinthe<head>element.Thefollowingexamplesimplifiesitwithaninlinescript:<h:head>...
<script>varmynamespace=mynamespace||{};</script>
</h:head>
<h:body>
<h:formid="form">
<h:commandScriptid="submit"name="mynamespace.invokeBeanSubmit"
action="#{bean.submit}">
</h:commandScript>
</h:form>
</h:body>
Comingbacktothegeneratedfunctionvariable,you’llalsoseethatitacceptsanobjectargumentandpassesitthroughas“params”propertyofthelastobjectargumentoftheMojarra-specificmojarra.ab()function.Thathelperfunctionwill,underthehoodofthemojarra.ab()function,prepareandinvokethejsf.ajax.request()functionofthestandardJSFJavaScriptAPI.Inotherwords,youcanpassJavaScriptvariablestoamanagedbeanactionmethodthisway.Theyareinjectablevia@ManagedPropertythesamewayasifyouwereusing<f:param>.ThefollowingexampledemonstratestheJavaScriptcallwithhard-codedvariablesinaJavaScriptobject,butyoucanofcourseobtainthosevariablesfromanywhereelseinJavaScriptcontext:varparams={
id:42,
name:"JohnDoe",
email:"[email protected]"
};
invokeBeanSubmit(params);
Backingbeanclass:
@Inject@ManagedProperty("#{param.id}")
privateIntegerid;
@Inject@ManagedProperty("#{param.name}")
privateStringname;
@Inject@ManagedProperty("#{param.email}")
privateStringemail;
publicvoidsubmit(){
System.out.println("SubmittedID:"+id);
System.out.println("Submittedname:"+name);
System.out.println("Submittedemail:"+email);
}
The<h:commandScript>canalsobeusedtodeferthepartialrenderingofanHTMLdocumenttothewindowloadevent.Toachievethis,simplysettheautorunattributetotrueandspecifytheclientIDofthetargetcomponentintherenderattribute.Thefollowingexampleloadsandrendersadatatableonlywhenthepagehasfinishedloadingintheclientside:
<h:panelGrouplayout="block"id="lazyPersonsPanel">
<h:dataTablerendered="#{notemptybean.lazyPersons}"
value="#{bean.lazyPersons}"var="person">
<h:column>#{person.id}</h:column>
<h:column>#{person.name}</h:column>
<h:column>#{person.email}</h:column>
</h:dataTable>
</h:panelGroup>
<h:formid="form">
<h:commandScriptid="loadLazyPersons"name="loadLazyPersons"
autorun="true"action="#{bean.loadLazyPersons}"
render=":lazyPersonsPanel">
</h:commandScript>
</h:form>
Wherebythebackingbeanlooksasfollows:
@Named@RequestScoped
publicclassBean{
privateList<Person>lazyPersons;
@Inject
privatePersonServicepersonService;
publicvoidloadLazyPersons(){
lazyPersons=personService.getAll();
}
publicList<Person>getLazyPersons(){
returnlazyPersons;
}
}
AndthePersonentitylooksasfollows:publicclassPerson{
privateLongid;
privateStringname;
privateStringemail;
//Add/generategettersandsetters.
}
Notethatanypersistenceframework-specificannotations,suchasJPA’s@Entityand@Id,andtheactualimplementationofPersonService,areomittedforclarity.Thoseare,namely,irrelevanttoanyfront-endframework,suchasJSF.
Comingbacktotheavailablecommandcomponents,itmayspeakforitselfthat<h:commandScript>isonlyusefulinordertobeabletoinvokeaJSF
managedbeanactionmethodusingnativeJavaScript,generallyduringaspecificHTMLDOM(DocumentObjectModel)event.However,both<h:commandLink>and<h:commandButton>seemtodoexactlythesamething;onlythevisualpresentationisdifferent.Onerendersalinkandtheotherrendersabutton.Theuserexperience(UX)consensusisthatabuttonmustbeusedtosubmitaform,andalinkmustbeusedtonavigatetoanotherpageorjumptoananchor.Usingalinktosubmitaformisthereforenotalwaysconsideredthebestpractice.It’sonlyusefulwhenyou’dliketosubmitanHTMLformusinganiconorimage.Forallothercases,useanormalbutton.ThefollowingexampleshowshowacommandlinkcanbeusedonaFontAwesomeicon:
<h:commandLinkid="delete"action="#{bean.delete}">
<iclass="fafa-trash"/>
</h:commandLink>
NavigationSometimes,you’dliketonavigatetoadifferentJSFpagewhenacertainformhasbeensuccessfullysubmitted—forexample,fromtheloginpagetotheuserhomepage,asdemonstratedinthesection“LabelandMessageComponents,”orfromthedetailpagebacktothemasterpage.
Historically,navigationtargetsmustbedefinedseparatelyin<navigation-rule>entriesinthefaces-config.xmlwhichthendoesthejobbasedontheStringreturnvaluefromtheactionmethodofUICommandcomponents.Thisapproachturnsouttobequitecumbersomeinthelongterm,andnotterriblyusefulforHTML-basedwebapplications.Thisideawasmoreorlessderivedfromdesktop-orientedapplications.Hence,JSF2.0introducedthe“Implicitnavigation”feature,whichallowsyoutodefinethenavigationtargetdirectlyintheStringreturnvalueitself.Inotherwords,insteadofthefollowingactionmethod:publicStringsomeActionMethod(){
//...
return"someOutcome";
}
Andthefollowingfaces-config.xmlentry:<navigation-rule><navigation-case>
<from-outcome>someOutcome</from-outcome>
<to-view-id>/otherview.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
youcouldjustdoasfollowsintheactionmethod:
publicStringsomeActionMethod(){
//...
return"/otherview.xhtml";
}
Youcouldevenleaveoutthedefaultsuffixoftheviewtechnologyyou’reusing.
publicStringsomeActionMethod(){
//...
return"/otherview";
}
Youcanforcearedirectbyappendingafaces-redirect=truequeryparameter.
publicStringsomeActionMethod(){
//...
return"/otherview?faces-redirect=true";
}
Returningnullwouldreturntotheverysameviewfromwheretheformwassubmitted.Inotherwords,theenduserwouldstayinthesamepage.Itis
cleaner,however,todeclaretheactionmethodasvoid.
publicvoidsomeActionMethod(){
//...
}
Comingbacktotheredirectapproach,thisisalsoknownas“Post-Redirect-Get”pattern andmakesamajordifferencewithregardtobookmarkabilityandavoidingdoublesubmits.WithoutaredirectafteraPOSTrequestonaJSFform,theURL(uniformresourcelocator)inthewebbrowser’saddressbarwouldn’tchangetotheURLofthetargetpagebutwouldjuststaythesame.That’scausedbythenatureofthe“postback”:submittingtheformbacktotheverysameURLfromwhichthepagewiththeformwasserved.WhenJSFisinstructedtonavigatetoadifferentviewwithoutaredirect,thenitwillbasicallybuildandrenderthetargetpagedirectlytotheresponseofthecurrentpostbackrequest.
Thisapproachhasdisadvantages.OneisthatrefreshingthepageinthewebbrowserwouldcausethePOSTrequesttobere-executed,andthusperformaso-calleddoublesubmit.Thiswouldpotentiallypollutethedatastoreinthebackendwithduplicateentries,particularlyiftheinvolvedrelationaltabledoesn’thaveproperuniqueconstraintsdefined.Anotherdisadvantageisthatthetargetpageisn’tbookmarkable.TheURLcurrentlyinthebrowser’saddressbarbasicallyrepresentsthepreviouspage.Youwon’tgetthetargetpagebackbybookmarking,copy/pasting,and/orsharingtheURLandthenopeningitinanewbrowserwindow.
WhenJSFisinsteadinstructedtonavigatetoadifferentviewwitharedirect,thenitwillbasicallyreturnaverysmallHTTPresponsewithastatusof302andaLocationheaderwiththeURLofthetargetpagetherein.Whenthewebbrowserretrievessucharesponse,itwillimmediatelyfireabrand-newGETrequestontheURLspecifiedintheLocationheader.ThisURLisreflectedinthewebbrowser’saddressbarandisthusbookmarkable.Also,refreshingthepagewouldonlyrefreshtheGETrequestandthereforenotcauseadouble
2
submit.
AjaxifyingComponentsAsyouhavenoticedin<h:commandScript>inthesection“CommandComponents,”JSFiscapableoffiringAjaxrequestsandperformingpartialrenderingaswell.ThiscapabilitywasintroducedinJSF2.0forthefirsttimewiththe<f:ajax>tag.ThistagcanbenestedinanycomponentimplementingClientBehaviorHolderinterface,oritcanbewrappedaroundagroupofcomponentsimplementingthisinterface.InthestandardJSFcomponentset,almostallHTMLcomponentsimplementClientBehaviorHolderaswell.IfyouconsulttheClientBehaviorHolderJavadoc, thenyou’llfindthefollowinglist:AllKnownImplementingClasses:
HtmlBody,HtmlCommandButton,HtmlCommandLink,HtmlDataTable,HtmlForm,
HtmlGraphicImage,HtmlInputFile,HtmlInputSecret,HtmlInputText,
HtmlInputTextarea,HtmlOutcomeTargetButton,HtmlOutcomeTargetLink,
HtmlOutputLabel,HtmlOutputLink,HtmlPanelGrid,HtmlPanelGroup,
HtmlSelectBooleanCheckbox,HtmlSelectManyCheckbox,HtmlSelectManyListbox,
HtmlSelectManyMenu,HtmlSelectOneListbox,HtmlSelectOneMenu,HtmlSelectOneRadio,
UIWebsocket
Thatarethusthe<h:body>,<h:commandButton>,<h:commandLink>,<h:dataTable>,<h:form>,<h:graphicImage>,<h:inputFile>,<h:inputSecret>,<h:inputText>,<h:inputTextarea>,<h:button>,<h:link>,<h:outputLabel>,<h:outputLink>,<h:panelGrid>,<h:panelGroup>,<h:selectBooleanCheckbox>,<h:selectManyCheckbox>,<h:selectManyListbox>,<h:selectManyMenu>,<h:selectOneListbox>,<h:selectOneMenu>,<h:selectOneRadio>and<f:websocket>.
You’llseethatallvisibleinput,select,andcommandcomponentsarecoveredaswell.
3
Arequirementofthe<f:ajax>isthattheClientBehaviorHoldercomponentisnestedin<h:form>,andthat<h:head>isbeingusedinthetemplate.The<h:form>basicallyenablesJavaScripttoperformapostbackrequestwiththerightJSFviewstateassociated.The<h:head>basicallyenables<f:ajax>toautomaticallyincludethenecessaryjsf.jsJavaScriptfilewhichcontains,amongothers,themandatoryjsf.ajax.request()function.
<h:headid="head">
<title>f:ajaxdemo</title>
</h:head>
<h:body>
<h:formid="form">
<h:inputTextid="text"value="#{bean.text}">
<f:ajax/>
</h:inputText>
<h:commandButtonid="submit"value="Submit"
action="#{bean.submit}">
<f:ajaxexecute="@form"/>
</h:commandButton>
</h:form>
</h:body>
GeneratedHTMLoutput:
<headid="head">
<title>f:ajaxdemo</title>
<scripttype="text/javascript"
src="projectjavax.faces.resource/jsf.js.xhtml?ln=javax.faces">
</script>
</head>
<body>
<formid="form"name="form"method="post"
action="projecttest.xhtml"
enctype="application/x-www-form-urlencoded">
<inputtype="hidden"name="form"value="form"/>
<inputid="form:text"type="text"name="form:text"
onchange="mojarra.ab(this,event,'valueChange',0,0)"/>
<inputid="form:submit"type="submit"name="form:submit"
value="Submit"onclick="mojarra.ab(
this,event,'action','@form',0);returnfalse;"/>
<inputtype="hidden"name="javax.faces.ViewState"
id="j_id1:javax.faces.ViewState:0"
value="6345708413515990903:-8460061657159853996"
autocomplete="off"/>
</form>
</body>
RenderinginChromebrowser(withnewlinesadded)isidenticalaswithoutAjax:
InthegeneratedHTMLoutput,you’llseethatthejsf.jsJavaScriptfilecontainingthenecessaryJSFAjaxAPIisauto-includedintheHTMLhead.You’llalsonoticethat<f:ajax>in<h:inputText>hasgeneratedanadditionalonchangeattribute,andin<h:commandButton>anadditionalonclickattribute,bothdefiningsomeJSFimplementation-specificJavaScriptcoderesponsibleforperformingtheAjaxrequest.
JSFspecifiestwointernalAjaxeventtypes:valueChangeandaction.Thosearethedefaulteventtypesincase<f:ajax>doesn’thavetheeventattributespecified.When<f:ajax>isattachedtoacomponentimplementingtheEditableValueHolderinterface,thenthedefaulteventtypebecomesvalueChange.ForcomponentsimplementingtheActionSourceinterface,thisisaction.ForallotherClientBehaviorHoldercomponents,thedefaulteventisclick.TheactualgeneratedHTMLDOMeventtypeforthoseinternaleventtypesdependsonthecomponentandtheassociatedrenderer.
Incaseoftext-basedinputcomponentsanddrop-down-andlistbox-based
selectioncomponents,thedefaultHTMLDOMeventtypefor<f:ajax>is"change".Incaseofradio-andcheckbox-basedselectioncomponentsandcommandcomponents,thisis"click".YoucanseethisinthegeneratedHTMLoutput,whichcanbeoverriddenbyexplicitlyspecifyingtheeventattributeonthe<f:ajax>tag.
<h:inputText...>
<f:ajaxevent="blur"/>
</h:inputText>
TheaboveexamplewillgeneratetheJavaScriptcodeintheonblurattributeinsteadoftheonclickattribute.ThesupportedvaluesfortheeventattributedependonthetargetClientBehaviorHoldercomponent.TheycanbefoundintheVDLdocumentationofthecomponentofinterest.Allon[event]attributesaredefinedoverthere.Whenyouremovethe“on”prefixonthem,thenyouhavealistofsupportedeventtypes.Forexample,theVDLdocumentationof<h:inputText> indicatesthatthefollowingeventtypesaresupported:
blur,change,click,dblclick,focus,keydown,keypress,keyup,mousedown,mousemove,mouseout,mouseover,mouseup,select
WhenthedesiredDOMeventtypeoccursontheclientsideandtriggerstheassociatedJSFimplementation-specificJavaScriptcodedefinedintheon[event]attribute,thenultimatelythejsf.ajax.request()functionofthestandardJSFJavaScriptAPIwillbeinvoked.Itwillprepareabunchofpredefinedpostbackparametersofwhichjavax.faces.sourceandjavax.faces.behavior.eventarethemostimportantones.TheformerspecifiestheclientIDofthesourcecomponent,essentiallythevalueofthis.idinJavaScriptcontext.Thelatterspecifiestheeventtype,essentiallythevalueofevent.typeinJavaScriptcontext.You’llhaveguessedthattheyarederivedfromthefirsttwoargumentspassedtotheMojarra-specific
4
mojarra.ab()functionasvisibleinthegeneratedHTMLoutput.Oncefired,theAjaxrequestwillrunthroughtheJSFlifecyclealmostthe
samewayasanon-Ajaxrequest.Therestoreviewphase(firstphase),processvalidationsphase(thirdphase),updatemodelvaluesphase(fourthphase),andinvokeapplicationphase(fifthphase)areidentical.Theapplyrequestvaluesphase(secondphase)isslightlydifferent.Itwillonlydecodethecomponentsthatarecoveredbytheexecuteattributeofthe<f:ajax>tag,whichdefaultsto@this(“thecurrentcomponent”).Therenderresponsephase(sixthphase)iscompletelydifferent.InsteadofgeneratingawholeHTMLdocument,itgeneratesaspecialXMLdocumentwhichcontainsonlythegeneratedHTMLoutputofcomponentswhicharecoveredbytherenderattributeofthe<f:ajax>tag,whichdefaultsto@none(“noonecomponent”).
Theexecuteandrenderattributesacceptaspace-separatedcollectionofcomponentsearchexpressions.ThiscanrepresentaclientIDrelativetotheclosestNamingContainerparent,oranabsoluteclientIDwhichisalwaysrelativetotheUIViewRoot,orstandardorcustomsearchkeywords,orchainedcombinationsthereof.SeeChapter12foranin-depthexplanationofthem.Fornow,weonlyneedtoknowaboutthestandardsearchkeywords@this,@form,[email protected],the@formkeywordreferstotheclosestparentcomponentoftheUIFormtype,suchas<h:form>.
Duringtheapplyrequestvaluesphase(secondphase)oftheAjaxrequest,JSFwill,foreachcomponentcoveredbytheexecuteattributeofthe<f:ajax>taginadditiontothedefaultdecodeprocess,alsocheckifthejavax.faces.sourcerequestparameterequalsthecurrentcomponent’sclientID.Ifso,thenJSFwillqueuetheAjaxBehaviorEventfortheinvokeapplicationphase(fifthphase).UnderthehoodofqueueingtheAjaxBehaviorEvent,itboilsdowntothefollowinglogic:FacesContextcontext=FacesContext.getCurrentInstance();
ExternalContextexternalContext=context.getExternalContext();
Map<String,String>formData=externalContext.getRequestParameterMap();
StringclientId=component.getClientId(context);
Stringsource=formData.get("javax.faces.source");
Stringevent=formData.get("javax.faces.behavior.event");
if(clientId.equals(source)){
component.getClientBehaviors().get(event)
.forEach(behavior->component.queueEvent(
newAjaxBehaviorEvent(context,component,behavior)));
}
Here,theClientBehaviorbasicallyrepresentsthedefinitionofthe<f:ajax>tag.Basedonthislogic,youwillconcludethatyoucanhavemultiple<f:ajax>tagsattachedintheverysamecomponent,evenonsameeventtypes.Theadvantageisthatyoucan,ifnecessary,registermultipleAjaxbehaviorlistenersontheverysameeventtype.
<h:inputTextid="foo"...>
<f:ajaxlistener="#{bean.onchangeFoo}"/>
<f:ajaxlistener="#{otherBean.onchangeFoo}"/>
</h:inputText>
ThoseAjaxbehaviorlistenermethodswillthusbeinvokedduringtheinvokeapplicationphase(fifthphase);ofcourse,onlywhenthere’snoconversionorvalidationerrorduringtheprocessvalidationsphase(thirdphase).Incaseofcommandcomponents,thoseAjaxbehaviorlistenermethodswillalwaysbeinvokedbeforetheactionlistenermethodsandtheactionmethod.Regardlessofthetargetcomponent,theAjaxbehaviorlistenermethodmustbeapublicvoidmethodwhichcanoptionallytaketheAjaxBehaviorEventargument.
publicvoidonchangeFoo(AjaxBehaviorEventevent){
//...
}
Thisgivesyou,ininputandselectcomponents,theopportunitytoperformsomebusinesstaskonaspecificAjaxevent.Mostoccurringreal-worldexamplesinvolvepreparinganotherbeanpropertywhichinturngetsrenderedinanothercomponent.Thinkofcascadingdrop-downmenuswhereintheavailableitemsofthechilddrop-downmenudependontheselecteditemoftheparentdrop-down.Inactioncomponents,<f:ajaxlistener>isn’tterriblyuseful.Youalreadyhavethepossibilitytoperformthebusinesstaskinactionlistenerand/oractionmethod.Youcanjustcontinueusingthemevenwhenhaving<f:ajax>attached.
Duringtherenderresponsephase(sixthphase)oftheAjaxrequest,JSFwillforeachcomponentcoveredbytherenderattributeofthe<f:ajax>taggenerateaXML<update>elementwhichcontainsthegeneratedHTMLoutputofonlytheparticularcomponentandallofitschildren,ifany.Thejsf.ajax.response()functionofthestandardJSFJavaScriptAPI,whichisbythejsf.ajax.request()registeredasAjaxcallbackfunction,willextracttheidattributeofthe<update>element,whichrepresentstheclientIDofthetargetcomponent,andobtainviaJavaScript’sdocument.getElementById()ontheclientIDtheconcreteHTMLelementandreplaceitintheHTMLDOMtreewiththecontentsofthe<update>element.
Followingisanexampleofaformwithonerequiredinputfieldhavingamessageattached,andacommandbuttonwhichexplicitlytargetsthemessagecomponent:
<h:formid="form">
<h:inputTextid="text"value="#{bean.text}"required="true"/>
<h:messageid="m_text"for="text"/>
<br/>
<h:commandButtonid="submit"value="Submit"action="#{bean.submit}">
<f:ajaxexecute="@form"render="m_text"/>
</h:commandButton>
</h:form>
Figure4-2showshowChromepresentstheAjaxresponseaftersubmittingtheformwiththeinputfieldnotfilledout.It’sabigone-liner,soit’sscrolledabitsoitstartsatthe<update>elementofinterest.ItcontainsthegeneratedHTMLoutputofthe<h:messageid="m_text">component.
Figure4-2 ChromeDeveloperTools—Network—Response
IfyouscrollfurtherintheXMLresponse,thenyou’llalsonoticean<updateid="j_id1:javax.faces.ViewState:0">elementcontainingthevalueofthejavax.faces.ViewStatehiddeninputelement.ThisisimportantforJSFinordertomaintaintheviewstateacrossAjaxrequests.WhentherenderattributehappenstocoveraUIFormcomponent,thenthejavax.faces.ViewStatehiddeninputelementcurrentlyintheHTMLdocumentwillbasicallybecompletelywipedoutduringthereplacementoftheelementintheHTMLDOMtreewithcontentsofthe<update>elementoftheAjaxresponse.
Themissingjavax.faces.ViewStatehiddeninputelementwilleventuallybeappendedtoevery<formmethod="post">ofthecurrent
UIViewRoot.Thisapproachisactuallybydesignfortworeasons:(1)becausetheviewstatevaluecouldchangeacrossAjaxrequestsandthereforetheexistingformscurrentlyintheHTMLdocumenthavetobeupdatedtocatchupthischange,justincasethoseformsarenotcoveredbytherenderattribute;and(2)becausethevalueofthejavax.faces.ViewStatehiddeninputfieldcangetquitelargewhentheJSFstatesavingmethodisexplicitlysetto“client”andthusotherwiserenderaninefficientAjaxresponsewhentherenderattributehappenstocovermultipleforms.
NavigationinAjaxInUICommandcomponentswithaproperlydefinedactionmethod,it’snotdifferent.However,sometimesyou’dliketoperformnavigationinanAjaxlistenerattachedtonUIInputcomponent.Therearereasonablereal-worldusecasesforthis.However,theUIInputclassdoesn’tsupportdefininganactionmethodand<f:ajaxlistener>doesn’tsupportreturninganavigationoutcome.Therefore,youronlyoptionistoperformthenavigationprogrammatically.Thiscanbedoneintwoways.Thefirstwayistousethejavax.faces.application.NavigationHandler.
publicvoidajaxListener(AjaxBehaviorEventevent){
//...
Stringoutcome="/otherview?faces-redirect=true";
FacesContextcontext=FacesContext.getCurrentInstance();
Applicationapplication=context.getApplication();
NavigationHandlerhandler=application.getNavigationHandler();
handler.handleNavigation(context,null,outcome);
}
Thesecondwayistousethejavax.faces.context.ExternalContext#redirect().
5
6
publicvoidajaxListener(AjaxBehaviorEventevent)throwsIOException{
//...
Stringpath="/otherview.xhtml";
FacesContextcontext=FacesContext.getCurrentInstance();
ExternalContextexternalContext=context.getExternalContext();
Stringuri=externalContext.getRequestContextPath()+path;
externalContext.redirect(uri);
}
Thereareseveraldifferences.Mostimportant,theNavigationHandlercandealwithimplicitnavigationoutcomevalues,butExternalContext#redirect()canonlydealwithactualpathsandrequiresmanualprefixingoftherequestcontextpathwhenitconcernsawebapplicationresource.However,itcantakebasicallyanyURI,suchasanexternalURLasinexternalContext.redirect("http://example.com"),whereastheNavigationHandlercan’tdealwiththem.
GETformsJSFhasnoconceptof“GETforms,”butyoucanjustuseplainHTMLforthis.JSFsupportsprocessingGETrequestparametersandinvokingmanagedbeanactionsonGETrequests.Forthis,<f:viewParam>and<f:viewAction>canbeused.Theymustbeplacedin<f:metadata>whichinturncanonlybedeclaredinthetop-levelpage.So,whenusingtemplating,itmustbedeclaredinthetemplateclientandyoucan’tdeclareitinthemastertemplate.Inotherwords,<f:metadata>cannotbesharedacrosstemplateclients.
Technically,thelocationof<f:metadata>intheviewdoesn’tmatter,aslongasit’sinthetop-levelpage.Mostself-documentingwouldbetoputitintheverytopoftheview,directlyaftertheroottag.
<!DOCTYPEhtml>
<htmllang="en"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
>
<f:metadata>
...
</f:metadata>
<h:head>
...
</h:head>
<h:body>
...
</h:body>
</html>
Whenusingtemplating,giveititsowntemplatedefinition.
<ui:compositiontemplate="WEB-INFtemplates/layout.xhtml"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
>
<ui:definename="metadata">
<f:metadata>
...
</f:metadata>
</ui:define>
<ui:definename="content">
...
</ui:define>
</ui:composition>
No,youcan’tput<f:metadata>inthemastertemplateandkeep<f:viewParam>and<f:viewAction>inthetemplateclient.Thisisatechnicallimitation.Thebestyoucandoistocreateacustom<f:event>type
whichrunsaftertheinvokeapplicationphase(fifthphase)andthendeclareitinthemastertemplate.Anexampleisgiveninthesection“CreateCustomComponentEvent”inChapter3.
The<f:viewParam>tagisbackedbytheUIViewParametercomponentwhichinturnextendsfromUIInputsuperclass.Thismeansthatitbehavesalmostexactlylike<h:inputText>,butthenforGETparameters.Thesubtledifferencesarefoundintheprocessvalidationsphase(thirdphase).Bydefault,anemptyparameterwouldskipanycustomvalidatorsandbeanvalidation.Forexample,the@NotNullbeanvalidationannotationwillonlyworkwhenthecontextparameterjavax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS
_NULLisexplicitlysettotrueinweb.xml.Theotherdifferenceisintherenderresponsephase(sixthphase).Basically,itrendersabsolutelynothing.
The<f:viewAction>tagisbackedbytheUIViewActioncomponentwhichinturnimplementstheActionSourceinterface.Thismeansthatitbehavesalmostexactlylike<h:commandButton>,butthenforGETrequests.Ofcourse,youcouldalsousea@PostConstructannotatedmethodona@ViewScopedmanagedbeanforperforminglogiconGETrequests,buttheproblemisthatitwouldrundirectlyafterthemanagedbeaninstanceiscreated,when<f:viewParam>hasn’tevenhadachancetorun.<f:viewAction>willbeinvokedduringtheinvokeapplicationphase(fifthphase),afterthemodelvaluesareupdated.ItevensupportsreturningaStringrepresentinganavigationoutcome,whichwillthenbehaveasaredirect.
Followingisanexampleofasearchform:Faceletsfile/search.xhtml:<f:metadata>
<f:viewParamid="query"name="query"value="#{search.query}"/>
<f:viewActionaction="#{search.onload}"/>
</f:metadata>
<h:body>
<form>
<labelfor="query">Query</label>
<inputtype="text"name="query"
value="#{emptysearch.query?param.query:search.query}">
</input>
<inputtype="submit"value="Search"/>
<h:messagefor="query"/>
</form>
<h:dataTableid="results"rendered="#{notemptysearch.results}"
value="#{search.results}"var="result">
<h:column>#{result.name}</h:column>
<h:column>#{result.description}</h:column>
</h:dataTable>
</h:body>
Backingbeanclasscom.example.project.view.Search:@Named@RequestScoped
publicclassSearch{
privateStringquery;
privateList<Result>results;
@Inject
privateSearchServicesearchService;
publicvoidonload(){
results=searchService.getResults(query);
}
//Add/generategettersandsettershere.
//Notethatresultsdoesn'tneedasetter.
}
IntheFaceletsfilethereareacoupleofthingstonoticeapartfromtheplainHTMLformapproach.Thevalueattributeofthetextinputdisplays#{param.query}when#{search.query}isempty,becausethesubmittedvaluewouldotherwisenotshowupatallwhenthere'saconversionorvalidationerroron<f:viewParam>.#{param}isactuallyanimplicitELobjectreferringtherequestparametermap.#{param.query}basicallyprintsthevalueoftherequestparameterwiththename“query”.PleasenotethatthisconstructofthevalueattributeisinvalidforJSFinputcomponents.Itwouldthrowajavax.el.PropertyNotWritableExceptionduringtheupdatemodelvaluesphase(fourthphase),and,moreover,itisalreadydoingtheverysamelogicunderthehoodofthe<f:viewParam>.
<h:message>canbeattachedto<f:viewParam>.Inthisspecificconstruct,however,it’snotreallyused.Onlywhenyouaddaconverterorvalidatorto<f:viewParam>,forexample,by<f:viewParam...required="true">wouldyouseetheerrormessagein<h:message>,andthen<f:viewAction>won’tbeinvoked.
Now,whenyouopenthepageandsubmittheform,thesubmittedvaluewillappearasaquerystringintheURLasin/search.xhtml?query=jsf.Thisisbookmarkableandre-executableeverytimeyouopentheURL.
StatelessFormsStatesavingisparticularlyhelpfulindynamicallymanipulatedformswhichuseAjaxtoconditionallyrenderpartsoftheform,suchascascadingdrop-downmenusandsecondaryinputfields.JSFremembersthestateoftheformacrossAjaxpostbacksonthesameview.Generally,itisthoseformswhereyouabsolutelyneedaview-scopedmanagedbeaninsteadofarequest-scopedmanagedbean.
Whenyourwebsitehas"public"and"private"sections,you'dliketo
postponetheHTTPsessioncreationasmuchaspossibleuntiltheenduserhasactuallyloggedin.Thiswayrobotswon’ttriggertheunnecessarycreationoftheHTTPsession.However,ifyouhaveastandardJSFloginforminthepublicsection,theHTTPsessionwouldalreadybecreatedbyjustaccessingthatpage.Thisisanunnecessarycostintermsofservermemoryiftheformhasbasicallynodynamicstateofitsownandistiedtoarequest-scopedmanagedbean.Youcouldconsiderusingclient-sidestatesavinginstead,butthiswillaffecttheentirewebsiteandithasacostintermsofnetworkbandwidthandCPU(centralprocessionunit)power.True,thecostisnegligibleifyouhavestate-of-the-arthardware,butit'snotnegligibleifyouhavealotofvisitorsand/orrelativelypoorhardware.
Incaseofstaticformstiedtoarequest-scopedbean,suchasasimpletwo-fieldloginformwhichcantheoreticallysafelybeentirelyclearedoutoneverypostback,thentheviewstatedoesn’tnecessarilyneedtobesaved.Thiscanbeachievedbysettingthetransientattributeof<f:view>totrue.
<f:viewtransient="true">
<h:formid="login">
...
</h:form>
</f:view>
ThiswayJSFwon’tcreateanyviewstateandthejavax.faces.ViewStatehiddeninputfieldwillreceiveafixedvalueof“stateless”.Notethatthisaffectstheentireviewandthere’snowaytotogglethisforonlyaspecificform.JSFcurrentlydoesnotsupportconfiguringthestatesavingmethodonaperformbasis.Also,statelessnesshasanadditionaldisadvantageinthatit'stheoreticallyeasiertoperformaCSRF(crosssiterequestforgery)attackifthere'sanopenXSShole.(Seealsothesection“CrossSiteRequestForgeryProtection”inChapter13.)Fortunately,withJSFit'salreadyveryhardtoaccidentallyintroduceaXSShole.TheonlywaytogetaXSShole
istouse<h:outputTextescape="false">toredisplayuser-controlleddata.
Footnotes1http://balusc.omnifaces.org/2015/10/custom-layout-with-hselectoneradio-
in.html.
2https://en.wikipedia.org/wiki/Post/Redirect/Get.
3https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/behavior/ClientBehaviorHolder.html.
4
https://javaserverfaces.github.io/docs/2.3/vdldocs/facelets/h/inputText.
html.
5https://javaee.github.io/javaee-
spec/javadocs/javax/faces/application/NavigationHandler.html.
6https://javaee.github.io/javaee-
spec/javadocs/javax/faces/context/ExternalContext.html#redirect-
java.lang.String-.
(1) (2)
©BaukeScholtz,ArjanTijms2018BaukeScholtzandArjanTijms,TheDefinitiveGuidetoJSFinJavaEE8,https://doi.org/10.1007/978-1-4842-3387-0_5
5.ConversionandValidation
BaukeScholtz andArjanTijms
Willemstad,Curaçao Amsterdam,Noord-Holland,TheNetherlands
Atitscore,JSF(JavaServerFaces)asanHTMLform-basedMVC(Model-View-Controller)frameworkbasicallyneedstoconvertbetweenJavaobjects(entities,beans,valueobjects,datatransferobjects,andwhatnot)andcharactersequences(strings)allthetime.TheHTTPrequestisbasicallybrokendownintoplainvanillastringsrepresentingheadersandparameters,notasJavaobjects.TheHTTPresponseisbasicallywrittenasonebigsequenceofcharactersrepresentingHTMLorXML,notassomesortofserializedformofaJavaobject.However,theaverageJavamodelbehindaJSFpagedoesn’tnecessarilycontainStringpropertieseverywhere.ThatwoulddefeatthestrongtypednatureofJava.ThisiswhereConverterscomeintothepicture:convertingbetweenobjectsinmodelandstringsinview.
Beforeupdatingthemodelvalueswithfreshlysubmittedand,ifnecessary,convertedvalues,youwouldofcourseliketovalidatewhethertheyconformtothebusinessrulesofthewebapplicationand,ifnecessary,presentendusersan
1 2
informativeerrormessagesothattheycanfixanyerrorsthemselves.Usually,thebusinessrulesarealreadyverywelldefinedinthedatastore,suchasarelationaldatabasemanagementsystem.Adecentlydesigneddatabasetablealreadyhasstrictconstraintsonthedatatype,maximumsize,nullability,anduniqueness.Youasafront-enddevelopershouldmakeabsolutelysurethatthesubmittedandconvertedvaluescanbeinsertedinthedatabasewithouterrors.
If,forexample,thee-mailaddresscolumnisconstrainedasauniqueandnon-nullablecolumnwithamaximumsizeof254characters,thenyoushouldmakesurethatthesubmittedvalueisvalidatedassuchbeforeinsertingitinthedatabase.Otherwise,thedatabaseinsertwouldthrowsomeexceptionwhichisgenerallycumbersometobreakdownintodetailedinformationinordertotelltheenduserabouttheexactmistake.ThisiswhereValidatorscomeintothepicture:validatingsubmitted(andconverted)valuesbeforeupdatingthemodel.
StandardConvertersJSFhas,fromthebeginning,providedabunchofstandardconvertersoutthebox.MostofthemevendotheirjobfullytransparentlybasedontheJavatypeofthemodelproperty.Theyareallavailableinthejavax.faces.convertpackage andtheyallimplementtheConverter<T>interface.Table5-1providesanoverviewofthem.
Table5-1 StandardConvertersProvidedbyJSF
Converterclass
ConverterID
Convertertag
Valuetype
Sinc
1
e
BigDecimalConverter
javax.faces.BigDecimal
n/a
java.math.BigDecimal
1.0
BigIntegerConverter
javax.faces.BigInteger
n/a
java.math.BigInteger
1.0
BooleanConverter
javax.faces.Boolean
n/a
boolean/java.lang.Boolean
1.0
ByteConverter
javax.faces.Byte
n/a
byte/java.lang.Byte
1.0
CharacterConverter
javax.faces.Character
n/a
char/java.lang.Character
1.0
DateTimeConverter
javax.faces.DateTime
<f:convertDateTime>
java.util.Datejava.time.LocalDatejava.time.LocalTimejava.time.OffsetTimejava.time.LocalDateTimejava.time.Offset
1.02.32.32.32.32.3
java.time.OffsetDateTimejava.time.ZonedDateTime
2.3
DoubleConverter
javax.faces.Double
n/a
double/java.lang.Double
1.0
EnumConverter
javax.faces.Enum
n/a
enum/java.lang.Enum
1.0
FloatConverter
javax.faces.Float
n/a
float/java.lang.Float
1.0
IntegerConverter
javax.faces.Integer
n/a
int/java.lang.Integer
1.0
LongConverter
javax.faces.Long
n/a
long/java.lang.Long
1.0
NumberConverter
javax.faces.Number
<f:convertNumber>
java.lang.Number
1.0
ShortConverter
javax.faces.Short
n/a
short/java.lang.Short
1.0
The“ConverterID”columnbasicallyspecifiestheconverteridentifierasyoucouldspecifyintheconverterattributeofanyValueHoldercomponent,ortheconverterIdattributeofanynested<f:converter>taginordertoactivatethespecificconverter.AllUIOutputandUIInputcomponentsimplementtheValueHolderinterface.Theconverterswhichsay“n/a”inthe“Convertertag”columnareimplicitconverters.Inotherwords,youcanjustbindanybeanpropertyoftypeBigDecimal,BigInteger,boolean/Boolean,byte/Byte,char/Character,double/Double,enum/Enum,float/Float,int/Integer,long/Long,andshort/ShorttothevalueattributeofanyValueHoldercomponentandhaveJSFtoautomaticallyconvertitwithoutanyadditionalconfiguration.Only<f:convertDateTime>and<f:convertNumber>requireexplicitregistration,becausethedesiredconversionalgorithmisn’tnecessarilyobviousfromthemodelvaluealone.
InallValueHoldercomponents,theconverterwillbeinvokedduringtherenderresponsephase(sixthphase),convertingthenon-String-basedmodelvaluetoaStringsuitableforembeddinginHTML.AndinEditableValueHoldercomponents,theconverterwillalsobeinvokedduringtheprocessvalidationsphase(thirdphase),convertingthesubmittedStringrequestparametertothenon-String-basedmodelvalue.TheEditableValueHolderinterfaceextendstheValueHolderinterfaceandisimplementedbyallUIInputcomponents.
However,thisimplicitconversiondoesn’tworkonbean
propertieswherethosetypesareparameterized.ImaginethatyouhaveaList<Integer>inthemodelandyou’dliketobeabletoedititasfollows:<ui:repeatvalue="#{bean.integers}"varStatus="loop">
<h:inputTextvalue="#{bean.integers[loop.index]}"/>
</ui:repeat>
Then,aftersubmitting,youwouldendupwithunconvertedStringvaluesinthelistandgetbaffledbyclasscastexceptionswhenattemptingtoiterateoverthelist.ThereasonisthattheEL(ExpressionLanguage)API(applicationprogramminginterface),whichisresponsibleforprocessingthose#{...}thingsthatare,behindthescenes,representedbyjavax.el.ValueExpressioninstances,isinitscurrentversionnotcapableofdetectingtheparameterizedtypeofagenericcollectionandjustreturnsObject.classonValueExpression#getType().JSFcan’tdomuchaboutthatlimitationofEL.Allyoucandoisexplicitlyspecifythedesiredconverterontheinputcomponent.
<ui:repeatvalue="#{bean.integers}"varStatus="loop">
<h:inputTextvalue="#{bean.integers[loop.index]}"
converter="javax.faces.Integer">
</h:inputText>
</ui:repeat>
AnalternativeistoreplacetheList<Integer>byInteger[]orevenint[].ELwillthenbeabletorecognizethevalueexpressionasanintegertypeandhenceJSFwillbeabletolocatethedesiredconverterforit.However,plainarraysinsteadofcollectionsinthemodelarea“no-no”thesedays.
Comingbacktotheexplicitstandardconverters<f:convertNumber>and<f:convertDateTime>,thosecanalsobenestedinanyValueHoldercomponent.Thedifferencebetween<f:convertNumber>andtheimplicitnumber-basedconvertersisthatthetagsallowmorefine-grainedsettingofconversionoptions,suchasthenumbertypeorpattern,theamountofintegerand/orfractiondigits,whethergroupingisused,andthelocale.
<F:CONVERTNUMBER><f:convertNumber> usesunderthehoodjava.text.NumberFormat. Thetypeattributespecifieswhichinstancewillbeobtainedanddefaultstonumber.Otherallowablevaluesarecurrencyandpercent.Inotherwords,thefollowingtags,
<f:convertNumbertype="number"/>
<f:convertNumbertype="currency"/>
<f:convertNumbertype="percent"/>
willunderthehoodobtaintheNumberFormatinstanceasfollows:
NumberFormatnumberFormat=
NumberFormat.getNumberInstance(locale);
NumberFormatcurrencyFormat=
NumberFormat.getCurrencyInstance(locale);
NumberFormatpercentFormat=
NumberFormat.getPercentInstance(locale);
wherethelocaleargumentcanbespecifiedbythelocaleattributeofthe<f:convertNumber>taganddefaultsto
2
3
UIViewRoot#getLocale()whichinturncanbespecifiedbythelocaleattributeof<f:view>.Inotherwords,thoseinstanceswillautomaticallyapplythestandardnumberformatpatternbasedonthenumbertypeandthespecifiedlocale.Thefollowingexample,
<f:viewlocale="pt_BR">
...
<h:outputTextvalue="#{product.price}">
<f:convertNumbertype="currency"locale="en_US"/>
</h:outputText>
</f:view>
willnotformattheprice(aBigDecimalproperty)asR$12,34(Brazilianreal),butinsteadas$12.34(USdollar).Notethatthelocaleattributeofthe<f:convertNumber>tagdoesnotnecessarilyneedtobespecifiedassupportedlocaleinfaces-config.xml.Alsonotedshouldbethatthevalueattributedoesn’tnecessarilyneedtoreferaBigDecimal;anyotherjava.lang.Numbertypeisalsosupported,butforpriceswe’dofcourseliketostorethevalueinaBigDecimalinsteadof,forexample,aDoubleorFloattoavoidarithmeticerrorsduetothefloatingnatureoffloatingpointnumbers.
Incaseyouneedtochangethestandardnumberformatpatternforsomereason—forexample,becauseyou’reworkingonabankingapplicationwhichstoresfinancialdatawithfivefractions—andyou’dliketopresentthefullvalueinsomeback-endadminscreensothathumanscanifnecessaryverifythem,thenyoucanusethepatternattributeofthe
4
<f:convertNumber>tagtooverridethestandardnumberformatpatternconformtherulesofjava.text.DecimalFormat.
<f:convertNumberpattern="¤#,##0.00000"locale="pt_BR"/>
Notethatwhenthepatternattributeisspecified,thetypeattributeisignored.The“currencysign”patterncharacter“¤”specifieswheretheactualcurrencysymbolmustbeinserted.Theactualcurrencysymboldependsonthespecifiedlocale.The“comma”patterncharacter“,”specifieswhenthegroupingseparatormustbeinserted,whichisrelativetothedecimalseparatorortheendofthevalue.TheactualinsertedgroupingseparatorsymboliscoincidentallyalsoacommainUSdollarformatbutisaperiodinBrazilianrealformat.The“period”patterncharacter“.”specifiesthelocationofthedecimalseparator.TheactualinserteddecimalseparatorsymboliscoincidentallyalsoaperiodinUSdollarformatbutisacommainBrazilianrealformat.The“optionaldigit”patterncharacter“#”isinthispatternmerelyusedtoindicatewhenthegroupingseparatorsymbolshouldbeinsertedandwon’tshowanythingwhentheactualdigitisabsent.The“requireddigit”patterncharacter“0”specifiestheminimumformatwhichwillshowzerowhentheactualdigitisabsent.Followingisanexercisecodewhichshouldgiveinsightintohow<f:convertNumber>worksunderthehood:Localelocale=newLocale("pt","BR");
DecimalFormatSymbolssymbols=new
DecimalFormatSymbols(locale);
5
System.out.println("Currencysymbol:"+
symbols.getCurrencySymbol());
System.out.println("Groupingsymbol:"+
symbols.getGroupingSeparator());
System.out.println("Decimalsymbol:"+
symbols.getDecimalSeparator());
DecimalFormatformatter=newDecimalFormat("¤
#,##0.00000",symbols);
System.out.println(formatter.format(new
BigDecimal("12.34")));
System.out.println(formatter.format(new
BigDecimal(".1234")));
System.out.println(formatter.format(new
BigDecimal("1234")));
System.out.println(formatter.format(new
BigDecimal("1234567.1234567")));
Theoutputshouldlookasfollows:
Currencysymbol:R$
Groupingsymbol:.
Decimalsymbol:,
R$12,34000
R$0,12340
R$1.234,00000
R$1.234.567,12346
<f:convertNumber>willalsorenderexactlythosevalues.Apartfromthepatternattribute,youcanalsofine-grainthetypeattributewithadditionalattributessuchas
currencySymbol,integerOnly,groupingUsed,minIntegerDigits,maxIntegerDigits,minFractionDigits,andmaxFractionDigits.Youcanbasicallyachievethesameformattingpattern“¤#,##0.00000”asfollows:<f:convertNumbertype="currency"locale="pt_BR"
minFractionDigits="5"maxFractionDigits="5"/>
Thisisactuallymorereadableandmoreconvenientincaseyouhaveahardtimegettingoutthecurrencysignplaceholderfromyourkeyboard.Thepatternattributeisrarelymoreusefulthanfine-grainingthetypeattributewithadditionalattributes.
Incaseyou’reusing<f:convertNumber>inaUIInputcomponentandthusrequiretheendusertoenterthevalue,youshouldkeepinmindthatcurrencyandpercenttypesexplicitlyrequiretheendusertoenterthecurrencyorpercentsymbolaswell.Forthecurrencyinput,youcaneasilydisablethisbyspecifyinganemptystringasacurrencysymbolsothatyoucanputitoutsidetheinputcomponent.
<spanclass="currency">
<spanclass="symbol">$</span>
<h:inputText...>
<f:convertNumbertype="currency"currencySymbol=""/>
</h:inputText>
</span>
Forthepercenttypethisis,unfortunately,notpossible.
<F:CONVERTDATETIME><f:convertDateTime> usesunderthehood6
7
java.text.DateFormat, and,sinceJSF2.3,alsojava.time.formatter.DateTimeFormatter. Inotherwords,youcanusebasicallyanykindofdateforthis.Also,thistaghasatypeattributewhichmustactuallycorrespondtotheactualtypeofthemodelvalue.Historically,itwasnotpossibletoprogrammaticallydetectthedesiredtypebasedonajava.util.Dateinstance.Thishaschangedsincethenewjava.timeAPIwhichoffersdistinctclassesforeachdatetimetype.However,inordertobeabletoreusetheexisting<f:convertDateTime>APIforthenewjava.timeAPI,newtypeshadtobeadded.Table5-2providesanoverview.
Table5-2 <f:convertDateTimetype>SupportedValues
Tagattribute
Valuetype
Actualformatter
Since
date(default)
java.util.Date(withzerotime)
DateFormat#getDateInstance()
1.0
time
java.util.Date(withzerodate)
DateFormat#getTimeInstance()
1.0
both
java.util.Date
DateFormat#getDateTimeInstance()
1.0
localDate
java.time.LocalDate
DateTimeFormatter#ofLocalizedDate()
2.3
7
8
ate()
localTime
java.time.LocalTime
DateTimeFormatter#ofLocalizedTime()
2.3
localDateTime
java.time.LocalDateTime
DateTimeFormatter#ofLocalizedDateTime()
2.3
offsetTime
java.time.OffsetTime
DateTimeFormatter#ISO_OFFSET_TIME
2.3
offsetDateTime
java.time.OffsetDateTime
DateTimeFormatter#ISO_OFFSET_DATE_TIME
2.3
zonedDateTime
java.time.ZonedDateTime
DateTimeFormatter#ISO_ZONED_DATE_TIME
2.3
Alongwiththetypeattribute,youshouldpreferablyalsospecifythepatternattribute,particularlywhenrequestingtheendusertoenterajava.util.Dateorjava.time.LocalXxxvalueviaaUIInputcomponent,becausetheactualpatternmayvaryinanotsoself-documentingwayacrossvariouslocales.java.time.OffSetXxxandZonedDateTimedon’thavethatproblembecausetheydefaulttotheuniversalISO8601format.
Thepatternattributeof<f:convertDateTime>
9
follows,forjava.util.Date,thesamerulesasspecifiedinthejava.text.SimpleDateFormatJavadoc, andforthejava.timeAPI,thesamerulesasspecifiedinjava.time.format.DateTimeFormatterJavadoc.Theyareforthemostpartthesame,butthejava.timeformatsupportsmorepatterns.ForbothAPIs,the“dayofmonth”patterncharacteris“d”,the“monthofyear”patterncharacteris“M”,the“year”patterncharacteris“y”,the“24hhour”patterncharacteris“H”,the“minute”patternis“m”,andthe“second”patternis“s”.TheISO8601dateformatis“yyyy-MM-dd”andtheISO8601timeformatis“HH:mm:ss”.Theoffsetandzonedtimesrequireanadditionaloffsetafterthetimepart,whichisrepresentedbytheISO8601timezonepatterncharacter“X”.Examplesofvalidvaluesare“+01:00”forCET(CentralEuropeanTime),“-03:00”forBRT(BrasiliaTime),and“+5:30”forIST(IndianStandardTime).Asbefore,theoffsetandzoneddateandtimeneedtobeseparatedbythe“T”characterinsteadofaspace.Followingisanoverviewofallpossible<f:convertDateTime>typeswherebythelocalizedoneshaveanexplicitlyspecifiedpattern:<h:formid="form"><h:inputTextid="date"value="#{bean.date}">
<f:convertDateTimetype="date"pattern="yyyy-MM-
dd"/>
</h:inputText>
<h:inputTextid="time"value="#{bean.time}">
<f:convertDateTimetype="time"
pattern="HH:mm:ss"/>
</h:inputText>
<h:inputTextid="both"value="#{bean.both}">
10
11
<f:convertDateTimetype="both"pattern="yyyy-MM-
ddHH:mm:ss"/>
</h:inputText>
<h:inputTextid="localDate"value="#
{bean.localDate}">
<f:convertDateTimetype="localDate"
pattern="yyyy-MM-dd"/>
</h:inputText>
<h:inputTextid="localTime"value="#
{bean.localTime}">
<f:convertDateTimetype="localTime"
pattern="HH:mm:ss"/>
</h:inputText>
<h:inputTextid="localDateTime"value="#
{bean.localDateTime}">
<f:convertDateTimetype="localDateTime"
pattern="yyyy-MM-ddHH:mm:ss">
</f:convertDateTime>
</h:inputText>
<h:inputTextid="offsetTime"value="#
{bean.offsetTime}">
<f:convertDateTimetype="offsetTime"/>
</h:inputText>
<h:inputTextid="offsetDateTime"value="#
{bean.offsetDateTime}">
<f:convertDateTimetype="offsetDateTime"/>
</h:inputText>
<h:inputTextid="zonedDateTime"value="#
{bean.zonedDateTime}">
<f:convertDateTimetype="zonedDateTime"/>
</h:inputText>
<h:commandButtonvalue="submit"action="#
{bean.submit}"/>
<h:messagesshowSummary="false"showDetail="true"/>
</h:form>
Notethat<h:messages>isherereconfiguredtoshowthedetailinsteadofjustthesummary,becausethedetailmessageofadatetimeconversionerrorincludesinstandardJSFanexamplevaluewhichismoreusefulfortheenduserinordertounderstandtherequiredformat.Followingiswhattheassociatedbackingbeanlookslike:
@Named@RequestScoped
publicclassBean{
privateDatedate;
privateDatetime;
privateDateboth;
privateLocalDatelocalDate;
privateLocalTimelocalTime;
privateLocalDateTimelocalDateTime;
privateOffsetTimeoffsetTime;
privateOffsetDateTimeoffsetDateTime;
privateZonedDateTimezonedDateTime;
publicvoidsubmit(){
System.out.println("date:"+date);
System.out.println("time:"+time);
System.out.println("both:"+both);
System.out.println("localDate:"+localDate);
System.out.println("localTime:"+localTime);
System.out.println("localDateTime:"+
localDateTime);
System.out.println("offsetTime:"+offsetTime);
System.out.println("offsetDateTime:"+
offsetDateTime);
System.out.println("zonedDateTime:"+
zonedDateTime);
}
//Add/generategettersandsetters.
}
NowthatHTML5hasbeenoutforsometimeandmoreandmorebrowserssupportthenewHTML5dateandtimeinputs, you’dbetteractivateitbydefault,becauseitcomeswithaveryusefulbuilt-indatepicker.Thewebbrowsermayshowthedatepatterninthedatepickerinalocalizedformat,butitwillalwayssubmitthevalueinISO8601format.Thisisthusveryuseful.TheHTML5dateandtimeinputscanbeactivatedbysettingthetypeattributeoftheinputtextfieldto“date”,“time”, or“datetime-local” (andthusnot“datetime”becauseithasbeendropped).WiththeJSF<h:inputText>,you’dneedtosetitasapass-throughattribute.Followingaresomeexamples:
<h:formid="form">
<h:inputTextid="localDate"a:type="date"value="#
{bean.localDate}">
<f:convertDateTimetype="localDate"pattern="yyyy-MM-
dd"/>
</h:inputText>
<h:inputTextid="localTime"a:type="time"value="#
{bean.localTime}">
<f:convertDateTimetype="localTime"pattern="HH:mm"
/>
</h:inputText>
<h:inputTextid="localDateTime"a:type="datetime-local"
value="#{bean.localDateTime}">
<f:convertDateTimetype="localDateTime"
pattern="yyyy-MM-dd'T'HH:mm">
</f:convertDateTime>
</h:inputText>
12
13
14 15
<h:commandButtonvalue="submit"action="#{bean.submit}"
/>
<h:messagesshowSummary="false"showDetail="true"/>
</h:form>
Followingishowthey’rerenderedinChromebrowser(withnewlinesadded):
StandardValidatorsWhenthesubmittedvalueissuccessfullyconvertedduringtheprocessvalidationsphase(thirdphase),thenJSFwillimmediatelyadvancetoperformvalidationontheconvertedvalue.JSFalreadyprovidesahandfulofstandardvalidatorsoutofthebox.Theyareallavailableinthejavax.faces.validatorpackage andtheyallimplementtheValidator<T>interface.Table5-3providesanoverviewofthem.
Table5-3 StandardValidatorsProvidedbyJSF
Validatorclass
ValidatorID
Validatortag
Valuetype
Since
16
LongRangeValidator
javax.faces.LongRange
<f:validateLongRange>
java.lang.Number
1.0
DoubleRangeValidator
javax.faces.DoubleRange
<f:validateDoubleRange>
java.lang.Number
1.0
LengthValidator
javax.faces.Length
<f:validateLength>
java.lang.Object
1.0
RegexValidator
javax.faces.RegularExpression
<f:validateRegex>
java.lang.String
2.0
RequiredValidator
javax.faces.Required
<f:validateRequired>
java.lang.Object
2.0
BeanValidator
javax.faces.Bean
<f:validateBean>
java.lang.Object
2.0
n/a
n/a
<f:validateWholeBean>
java.lang.Object
2.3
The“ValidatorID”columnbasicallyspecifiesthevalidatoridentifierasyoucouldspecifyinthevalidatorattributeofanyEditableValueHoldercomponent,orthevalidatorIdattributeofanynested<f:validator>
taginordertoactivatethespecificvalidator.Contrarytotheconverter,asingleEditableValueHoldercomponentcanhavemultiplevalidatorsattached.Theywillallbeexecutedregardlessofeachother’soutcome.
<F:VALIDATELONGRANGE>/<F:VALIDATEDOUBLERANGE>Thesevalidatorsallowyoutospecifyaminimumand/ormaximumallowednumbervalueforaninputcomponenttiedtoajava.lang.Number-basedproperty.Thosecanbespecifiedwiththeminimumandmaximumattributes.
<h:inputTextvalue="#{bean.quantity}">
<f:validateLongRangeminimum="1"maximum="10"/>
</h:inputText>
Thisis,viapass-throughattributes,alsocombinablewiththeHTML5inputtypes“number”(spinner)and“range”(slider),whichinturnrequiremin,max,andoptionallystepaspass-throughattributes.Inthisexample,#{bean.quantity}isjustanIntegerand#{bean.volume}isaBigDecimal.
<h:inputTextvalue="#{bean.quantity}"
a:type="number"a:min="1"a:max="10">
<f:validateLongRangeminimum="1"maximum="10"/>
</h:inputText>
<h:inputTextvalue="#{bean.volume}"
a:type="range"a:min="1"a:max="10"a:step="0.1">
<f:validateLongRangeminimum="1"maximum="10"/>
</h:inputText>
Donotethatyoucanjustuse<f:validateLongRange>onaBigDecimalproperty.Itdoesn’tcareabouttheactualjava.lang.NumbertypeofthepropertybeingaLongornot,butonlythespecifiedminimumandmaximumattributesbeingaLong.Incaseyouwanttospecifyafractional-basednumberasminimumand/ormaximum,thenuse<f:validateDoubleRange>instead.
<h:inputTextvalue="#{bean.volume}"
a:type="range"a:min="0.1"a:max="10.0"a:step="0.1">
<f:validateDoubleRangeminimum="0.1"maximum="10.0"/>
</h:inputText>
<F:VALIDATELENGTH>/<F:VALIDATEREGEX>Thesevalidatorsareprimarilydesignedforjava.lang.String-basedproperties.<f:validateLength>willfirstconvertthesubmittedvaluetostringbycallingObject#toString()onitandthenvalidatetheString#length()resultbasedonthespecifiedminimumand/ormaximumattributes.<f:validateRegex>willcastthesubmittedvaluetoStringandthencheckifString#matches()returnstrueforthespecifiedpatternattribute.Inotherwords,itdoesn’tacceptanyotherpropertytypethanjava.lang.String.Imaginethatyouwanttovalidateavaluetobealwaysthreedigits;thustherearethreepossibleways:<h:inputTextvalue="#{bean.someStringOrInteger}"maxlength="3">
<f:validateLengthminimum="3"maximum="3"/>
</h:inputText>
<h:inputTextvalue="#{bean.someString}"maxlength="3">
<f:validateRegexpattern="[0-9]{3}"/>
</h:inputText>
<h:inputTextvalue="#{bean.someInteger}"maxlength="3">
<f:validateLongRangeminimum="100"maximum="999"/>
</h:inputText>
Themaxlength="3"attributeisjusttheresothattheendusercan’tentermorethanthreecharactersontheclientsideanyway.Storingnumbersasstringsisplainnonsense,sothesecondwayisscratched.Thatleavesuswiththefirstorthirdway.Technicallyitdoesnotreallymatterwhichoneyoupick.Thefirstwayisarguablymoreself-documentingbecauseyouactuallywanttovalidatethelength,nottherange.
Comingbackto<f:validateRegex>,thepatternattributefollowsexactlythesameregularexpressionrulesasspecifiedinjava.util.regex.Pattern. However,there’sonepotentialcaveat:thenecessaryamountofescapebackslashesdependsonthecurrentlyusedELimplementation.InOracle’sELimplementation(com.sun.el.*),youneedtwobackslashes,exactlyasinaregularJavaString,butinApache’sELimplementation(org.apache.el.*),youmustuseonebackslash,otherwiseitwillerroroutoritwon’tmatchasyou’dexpect.Asofnow,Payara,WildFly,Liberty,andWebLogicuseOracle’sELimplementation,andTomEEandTomcatuseApache’sELimplementation.Inotherwords,thefollowingexamplewillworkonserversusingOracleELbutwon’tworkonserversusingApacheEL.
17
<h:inputTextvalue="#{bean.someString}"maxlength="3">
<f:validateRegexpattern="\\d{3}"/>
</h:inputText>
WhenusingApacheEL,youneedpattern="\d{3}"instead.Ontheotherhand,theregularexpressionpattern\dactuallymeans“anydigit”andthusmatchesnotonlytheLatindigitsbutalsotheHebrew,Cyrillic,Arabic,Chinese,etc.Ifthatwasnotyourintent,you’dbetterusethe[0-9]pattern.
<F:VALIDATEREQUIRED>Thisisaslightlyoddbeast.Thatis,allUIInputcomponentsalreadyhavearequiredattributeofferingexactlythedesiredfunctionality.Whywouldyouthenuseawhole<f:validateRequired>taginstead?ItwasaddedinJSF2.0specificallyfor“CompositeComponents”(moreonthislater,inChapter7).Moretothepoint,insomecompositecomponentcompositionsthetemplateclientisgiventheopportunitytoattachconvertersandvalidatorstoaspecificEditableValueHolderinterfaceexposedbythecompositecomponent,whichinturnreferencesoneormoreUIInputcomponentsenclosedinthecompositecomponentimplementation.Followingisanexampleofsuchacompositecomponent:<cc:interface>...
<cc:editableValueHolder
name="inputs"targets="input1input3">
</cc:editableValueHolder>
</cc:interface>
<cc:implementation>
...
<h:inputTextid="input1".../>
<h:inputTextid="input2".../>
<h:inputTextid="input3".../>
...
</cc:implementation>
Andfollowingisanexampleofthetemplateclient:
<my:compositeComponent...>
<f:validateRequiredfor="inputs"/>
</my:compositeComponent>
Asyoumighthaveguessed,theforattributemustexactlymatchthenameattributeofexposed<cc:editableValueHolder>andthisvalidatorwillbasicallytargettheenclosedinputcomponentsidentifiedbyinput1andinput3(andthusnotinput2)andthuseffectivelymakethemrequired="true".Thisforattributeis,bytheway,alsopresentonallotherconverterandvalidatortags.
<F:VALIDATEBEAN>/<F:VALIDATEWHOLEBEAN>Whenused,thesetagshavearequireddependencyontheBeanValidationAPI(applicationprogramminginterface),previouslymorecommonlyknownas“JSR303.”LikeJSF,BeanValidationispartoftheJavaEEAPI,alreadyincludedinanyJavaEEapplicationserver.InTomcatandotherservletcontainers,you’dneedtoinstallitseparately.InJavacode,BeanValidationisrepresentedbyannotationsandinterfaces
ofthejavax.validation.*package,suchas@NotNull,@Size,@Pattern,ConstraintValidator,etc.CurrentlythemostpopularimplementationisHibernateValidator.
JSFautomaticallydetectsthepresenceofBeanValidationandwillinsuchacasetransparentlyprocessallBeanValidationconstraintsduringtheendoftheprocessvalidationsphase(thirdphase),regardlessoftheoutcomeofJSF’sownvalidators.Ifdesired,thiscanbedisabledapplication-widewiththefollowingcontextparameterinweb.xml:
<context-param>
<param-name>
javax.faces.validator.DISABLE_DEFAULT_BEAN_VALIDATOR
</param-name>
<param-value>true</param-value>
</context-param>
Or,ifthisisalittletoorough,youcanfine-grainitwithhelpofthe<f:validateBean>tagwrappingagroupofUIInputcomponents,ornestedinthem.Whenthedisabledattributeofthe<f:validateBean>tagissettotrue,thenanyBeanValidationwillbedisabledonthetargetUIInputcomponents.ThefollowingcodewilldisableanyBeanValidationonlyontheparentUIInputcomponent.
<h:inputText...>
<f:validateBeandisabled="true"/>
</h:inputText>
AndthefollowingcodewilldisableanyBeanValidationonly
18
onUIInputcomponentsidentifiedbyinput3,input4,andinput5:<h:inputTextid="input1".../><h:inputTextid="input2".../>
<f:validateBeandisabled="true">
<h:inputTextid="input3".../>
<h:inputTextid="input4".../>
<h:inputTextid="input5".../>
<f:validateBean>
ItisimportanttokeepinmindisthatthiswillonlydisableJSF-managedBeanValidationandthusnot,forexample,JPA-managedBeanValidation.So,ifyouhappentouseJPA(JavaPersistenceAPI)topersistyourentitieswhicharefilledoutbyJSFcomponentswithBeanValidationdisabled,thenJPAwouldstillperformBeanValidationonitsbehalf,fullyindependentlyfromJSF.IncaseyouwanttodisableBeanValidationontheJPAsideaswell,youneedtosetthepropertyjavax.persistence.validation.modetoNONEinpersistence.xml(seealsothejavax.persistence.ValidationModeJavadoc).
<property
name="javax.persistence.validation.mode">NONE</property>
WiththevalidationGroupsattributeofthe<f:validateBean>tagyoucanifnecessarydeclareoneormorevalidationgroups.Insuchacase,onlytheBeanValidationconstraintswhichareregisteredonthesamegroupwillbeprocessed.Imaginethefollowingmodel:@NotNullprivateStringvalue1;
19
@NotNull(groups=NotNull.class)
privateStringvalue2;
@NotNull(groups={NotNull.class,Default.class})
privateStringvalue3;
NotethatthegroupsattributeofanyBeanValidationconstraintmustreferenceaninterface,butitmaybeanyoneyouwant.Forsimplicity,intheaboveexamplewe’rejustreusingthejavax.validation.constraints.NotNull
interfaceasagroupidentifier.Thecommonpracticeis,however,tocreateyourownmarkerinterfaceforthedesiredgroup.
Alsonotunimportantisthatthe@NotNullwouldonlyworkwhenyou’veconfiguredJSFtointerpretemptystringsubmittedvaluesasnull;otherwiseitwouldpollutethemodelwithemptystringsinsteadofnullsandcausethe@NotNullnottobeabletodoitsjobbecauseanemptystringisnotnull.Asareminder,theweb.xmlcontextparameterofinterestisasfollows:
<context-param>
<param-name>
javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_A
S_NULL
</param-name>
<param-value>true</param-value>
</context-param>
Now,whensubmittinganemptyformwhilehavingthosemodelpropertiesreferencedinthefollowinginput
components,withoutany<f:validateBean>onthem:<h:inputTextvalue="#{bean.value1}"/>
<h:inputTextvalue="#{bean.value2}"/>
<h:inputTextvalue="#{bean.value3}"/>
youwillreceiveavalidationerroronBeanValidationconstraintsbelongingtothejavax.validation.groups.Defaultgroup,whicharethusthegrouplessvalue1andtheexplicitlygroupedvalue3.Thevalue2won’tbeBeanValidatedasitdoesn’thavethedefaultgroupexplicitlydeclared.
And,whensubmittinganemptyformwhilehaving<f:validateBean>withvalidationGroupssettoNotNull.class:<f:validateBeanvalidationGroups="javax.validation.constraints.NotNull">
<h:inputTextvalue="#{bean.value1}"/>
<h:inputTextvalue="#{bean.value2}"/>
<h:inputTextvalue="#{bean.value3}"/>
</f:validateBean>
youwillreceiveavalidationerroronBeanValidationconstraintsbelongingtothejavax.validation.constraints.NotNullgroup,whicharethusthevalue2andvalue3,whichexplicitlyhavethisgroupdeclared.Thegrouplessvalue1won’tbeBeanValidatedasitonlyimpliesthedefaultgroup.
Finally,whensubmittinganemptyformwhilehavinga<f:validateBean>withbothgroupsspecifiedinvalidationGroupsattributeasacommaseparatedstring:
<f:validateBean
validationGroups="javax.validation.groups.Default,
javax.validation.constraint
s.NotNull">
<h:inputTextvalue="#{bean.value1}"/>
<h:inputTextvalue="#{bean.value2}"/>
<h:inputTextvalue="#{bean.value3}"/>
</f:validateBean>
youwillreceiveavalidationerroronallinputs,becausetheyallmatchatleastoneofthespecifiedgroups.Inreal-worldapplications,however,thisgroupingfeaturehasverylittleuse.It’sonlyreallyusefulwhenthegroupedfieldscanbevalidatedatthesametimebythesamevalidator.WithBeanValidation,theonlywaytoachievethatistoputacustomConstraintannotationonthebeanclassitself,getaninstanceofthatbeanwiththevaluespopulated,andthenpassittothecustomConstraintValidatorassociatedwiththecustomConstraintannotation.Imaginea“period”entityhavinga“startdate”propertywhichshouldalwaysbebeforethe“enddate”property.Itwouldlooksomethinglikethefollowing:@PeriodConstraint
publicclassPeriodimplementsSerializable{
@NotNull
privateLocalDatestartDate;
@NotNull
privateLocalDateendDate;
//Add/generategettersandsetters.
}
Withthefollowingcustomconstraintannotation:
@Constraint(validatedBy=PeriodValidator.class)
@Target(TYPE)
@Retention(RUNTIME)
public@interfacePeriodConstraint{
Stringmessage()default"Startdatemustbebeforeend
date";
Class<?>[]groups()default{};
Class<?>[]payload()default{};
}
Andthefollowingcustomconstraintvalidator:
publicclassPeriodValidator
implementsConstraintValidator<PeriodConstraint,Period>
{
@Override
publicbooleanisValid
(Periodperiod,ConstraintValidatorContextcontext)
{
return
period.getStartDate().isBefore(period.getEndDate());
}
}
Yousee,BeanValidationexpectsthatthemodelvaluesarepresentwhenperformingthevalidation.IntheJSFperspective,thismeansthatthemodelvaluesmustbeupdatedbeforeprocessingthevalidations.However,thisdoesn’tfitintotheJSFlifecyclewhereinthemodelvaluesareonlyupdatedafterthevalidationsaresuccessfullyprocessed.Essentially,JSFwouldneedtoclonethebeaninstance,populateitwiththedesiredmodelvalues,invokeBeanValidationonit,collectanyvalidationerrors,andthendiscardtheclonedbeaninstance.
Thisisexactlywhatthe<f:validateWholeBean>
tag,introducedwithJSF2.3,isdoingunderthehood.Followingisanexampleformwhereinthiscodeisbeingused:<h:form>
<h:inputTexta:type="date"value="#
{booking.period.startDate}">
<f:convertDateTimetype="localDate"
pattern="yyyy-MM-dd"/>
</h:inputText>
<h:inputTexta:type="date"value="#
{booking.period.endDate}">
<f:convertDateTimetype="localDate"
pattern="yyyy-MM-dd"/>
</h:inputText>
<h:commandButtonvalue="Submit"/>
<h:messages/>
<f:validateWholeBeanvalue="#{booking.period}"/>
</h:form>
Withthisbackingbean:
@Named@ViewScoped
publicclassBookingimplementsSerializable{
privatePeriodperiod=newPeriod();
//Add/generategetter.
}
Donotethat<f:validateWholeBean>isexplicitlyplacedasthelastchildoftheparent<h:form>,whichensuresthatthevalidationisperformedasthelastthingafterallindividualinputcomponentsinthesameform.Thisisasperthespecification;theJSFimplementationmaythrowa
runtimeexceptionwhenthetagismisplaced.
ImmediateAttributeTheEditableValueHolder,ActionSource,andAjaxBehaviorinterfacesalsospecifyanimmediatepropertywhichbasicallymapstotheimmediateattributeofallUIInputandUICommandcomponentsandthe<f:ajax>tag.WhensettotrueonanEditableValueHoldercomponent,thenanythingthatnormallytakesplaceduringtheprocessvalidationsphase(thirdphase)aswellastheupdatemodelvaluesphase(fourthphase)willbeperformedduringtheapplyrequestvaluesphase(secondphase).Whenconversionorvalidationfailsonthem,thenthelifecyclewillalsoskiptheprocessvalidationsphase(thirdphase).WhensettotrueonanActionSourcecomponentorAjaxBehaviortag,thenanythingthatnormallytakesplaceduringtheinvokeapplicationphase(fifthphase)willbeperformedduringtheapplyrequestvaluesphase(secondphase)andthenonlyifconversionandvalidationhaven’tfailed.
Historically,thisattributewasmainlyusedtobeabletoperforman“inner”actionontheform,usuallytoloadachildinputcomponentdependingonthesubmittedvalueoftheparentinputcomponent,withoutbeingblockedbyconversionorvalidationerrorscomingfromotherinputcomponentsinthesameform.Acommonusecasewaspopulatingachilddrop-downonthechangeofaparentdrop-down.
<h:selectOneMenuvalue="#{bean.country}"required="true"
immediate="true"
onchange="submit()"valueChangeListener="#
{bean.loadCities}">
<f:selectItemsvalue="#{bean.countries}"/>
</h:selectOneMenu>
<h:selectOneMenuvalue="#{bean.city}"required="true">
<f:selectItemsvalue="#{bean.cities}"/>
</h:selectOneMenu>
ThisapproachobviouslypredatestheWeb2.0erawhereinyou’djustuseAjaxforthis.Understandthattheimmediateattributehasessentiallybecomeuselessforthispurposesincetheintroductionof<f:ajax>inJSF2.0.Exactlythesameusecasecanbeachievedinamuchcleanerwayasfollows:<h:selectOneMenuvalue="#{bean.country}"
required="true">
<f:selectItemsvalue="#{bean.countries}"/>
<f:ajaxlistener="#{bean.loadCities}"render="city"
/>
</h:selectOneMenu>
<h:selectOneMenuid="city"value="#{bean.city}"
required="true">
<f:selectItemsvalue="#{bean.cities}"/>
</h:selectOneMenu>
AsyoulearnedinChapter4,theexecuteattributeof<f:ajax>alreadydefaultsto@this,soit’sjustomitted.ThisalsomeansthatallotherEditableValueHoldercomponentsinthesameformwon’tbeprocessedandthuswon’tcause#{bean.loadCities}evertobeblockedbyconversionorvalidationerrorscomingfromotherinputs.
Thesedays,withAjaxmagicandall,theimmediateattributehasthuslostitsmainusecase.JSFcoulddoaswell
withoutit.Duetoitshistoricusecase,manystartersmaymistakeitsprimarypurposetobe“skipallvalidation.”Thisis,however,nottrue.Forthat,you’dneedtofine-tunetheexecuteattributeof<f:ajax>sothatitonlycoverstheinputcomponentsthatreallyneedtobevalidated.Incaseyouwanttoactually“skipallvalidation”whilesubmittingtheentireform,you’dbestuseBeanValidationconstraintsinstead(the@NotNullandfriends)andsimplyhave<f:validateBeandisabled="true">wrappingtheentireform.
CustomConvertersFromthebeginningJSFhassupportedcustomconverters.Themainusecaseistobeabletoconvertanon-standardmodelvalue,suchasapersistenceentityspecifictothewebapplication.Thelesscommonusecaseistoextendanexistingstandardconverterandsetsomecommonlyuseddefaultsinitsconstructorsothatyoucangetawaywithlesscodeinordertodeclarethedesiredstandardconverterconfigurationintheview.
Imaginethatyouwanttobeabletocreatemaster-detailpagesonyourpersistenceentitieswhereinyou’dliketopasstheIDoftheentityaroundasarequestparameterfromthemasterpagetothedetailpage.Followingisanexampledatatableinthemasterpageproductslist.xhtmlbasedonafictiveProductentity:
<h:dataTablevalue="#{listProducts.products}"var="product">
<h:column>#{product.id}</h:column>
<h:column>#{product.name}</h:column>
<h:column>#{product.description}</h:column>
<h:column>
<h:linkvalue="Edit"outcome="edit">
<f:paramname="id"value="#{product.id}"/>
</h:link>
</h:column>
</h:dataTable>
Notethelastcolumnofthetable.Itgeneratesalinktothedetailpageproductsedit.xhtmlwherebytheIDoftheentityispassedasaGETrequestparameterasin/product.xhtml?id=42.Inthedetailpage,youcanuse<f:viewParam>tosettheGETrequestparameterinthebackingbean.
<f:metadata>
<f:viewParamname="id"value="#{editProduct.product}"
required="true"requiredMessage="Badrequest">
</f:viewParam>
</f:metadata>
...
<h:form>
<h1>Editproduct#{editProduct.product.id}</h1>
<h:inputTextvalue="#{editProduct.product.name}"/>
<h:inputTextvalue="#{editProduct.product.description}"
/>
...
</h:form>
However,there’sonesmallproblem:theGETrequestparameterisinJavaperspectivebasicallyaStringrepresentingtheproductIDwhiletheproductpropertyoftheEditProductbackingbeanactuallyexpectsawholeProductentityidentifiedbythepassed-inID.
@Named@ViewScoped
publicclassEditProductimplementsSerializable{
privateProductproduct;
//Getter+setter.
}
Forexactlythisconversionstep,acustomconverterhastobecreatedwhichiscapableofconvertingbetweenaStringrepresentingtheproductIDandanObjectrepresentingtheProductentity.JSFoffersthejavax.faces.convert.Converterinterface togetstarted.FollowingisaconcreteexampleofsuchaProductConverter:@FacesConverter(forClass=Product.class,managed=true)
publicclassProductConverterimplements
Converter<Product>{
@Inject
privateProductServiceproductService;
@Override
publicStringgetAsString
(FacesContextcontext,UIComponentcomponent,
Productproduct)
{
if(product==null){
return"";
}
if(product.getId()!=null){
returnproduct.getId().toString();
20
}
else{
thrownewConverterException(
newFacesMessage("InvalidproductID"),
e);
}
}
@Override
publicProductgetAsObject
(FacesContextcontext,UIComponentcomponent,
Stringid)
{
if(id==null||id.isEmpty()){
returnnull;
}
try{
return
productService.getById(Long.valueOf(id));
}
catch(NumberFormatExceptione){
thrownewConverterException(
newFacesMessage("InvalidproductID"),
e);
}
}
}
Thereareseveralimportantthingstonotehereinthe@FacesConverterannotation.First,theforClass
attributebasicallyspecifiesthetargetentitytypeforwhichthisconvertershouldautomaticallyrunduringtheprocessvalidationsphase(thirdphase)andtherenderresponsephase(sixthphase).Thiswayyoudon’tneedtoexplicitlyregistertheconverterintheview.Incaseyouwantedtodoso,you’dreplacetheforClassattributebythevalueattributespecifyingtheuniqueidentifieroftheconverter,forexample:@FacesConverter(value="project.ProductConverter",
managed=true)
ThenyoucanspecifyexactlythatconverterIDintheconverterattributeofanyValueHoldercomponent,ortheconverterIdattributeofanynested<f:converter>tag.
<f:viewParamname="id"value="#{editProduct.product}"
converter="project.ProductConverter"
required="true"requiredMessage="Badrequest">
</f:viewParam>
ButthisisnotnecessarywhenyoujustkeepusingtheforClassattribute.Notethatyoucan’tspecifyboth.It’soneortheotherwherethevalueattributetakesprecedenceovertheforClass.So,ifyouspecifyboth,theforClassattributeisessentiallyignored.Wedon’twanttohavethatasit’smuchmorepowerfulforthisparticularpurposeoftransparentlyconvertingwholeentities.
Thesecondthingtonoteintheannotationisthemanagedattribute.ThisisnewsinceJSF2.3.Essentially,thismanagestheconverterinstanceintheCDIcontext.Settingthemanagedattributetotrueismandatoryinordertogetdependencyinjectiontoworkintheconverter.Previously,this
wasworkedaroundbymakingtheconverteritselfamanagedbean.
IfyouhaveworkedwithJSFconvertersbefore,you’llalsonoticetheinterfacenowfinallybeingparameterized.TheinterfacepredatesJava1.5andwashencenotparameterizedfromthebeginning.WithaConverter<T>,thegetAsObject()nowreturnsaTinsteadofObjectandthegetAsString()nowtakesaTasvalueargumentinsteadofObject.Thissavesunnecessaryinstanceofchecksand/orcasts.
NotethatJSF’sownstandardconverterswhichpredateJSF2.3(currently,basicallyallofthemthus)arefrozenintimeandcannottakeadvantageofthisastheywouldotherwisenolongerbebackwardcompatible.Inotherwords,theyarestillrawtypes.Thatis,there’sasmallbutnotunavoidablechancethatsomeoneisprogrammaticallyusingJSFconvertersinplainJavacodeinsteadoflettingJSFdealwiththem.ThatplainJavacodewouldnolongercompileifthestandardconverterswereparameterized.It’sessentiallythesamereasonthattheMap#get()explicitlytakesObjectinsteadofKasargument.Furtherthere’sayetsmallerbutstillnotunavoidablechancethatsomeonehascreatedacustomconverterwhichextendsastandardconverter,butalsoexplicitlyredeclarestheinterface.Somethinglikethefollowing:publicclassExtendedNumberConverterextendsNumberConverterimplementsConverter
{
//...
}
Suchanobscureconverterwouldnolongercompileif
21
NumberConverterwasparameterizedinsomeway.EvenifweparameterizeNumberConverterasaConverter<Object>,thecompilerwoulderroronExtendedNumberConverterasfollowsandhencebreakbackwardcompatibility:
TheinterfaceConvertercannotbeimplementedmorethanoncewithdifferentarguments:Converter<Object>andConverter
ComingbacktoourProductConverterimplementation,inthegetAsString()you’llnoticethattheconverterexplicitlyreturnsanemptystringwhenthemodelvalueisnull.ThisisaspertheJavadoc. ThetechnicalreasonisthatJSFwon’trendertheassociatedHTMLattributewhentheevaluatedvalueisnull.Ingeneral,thisisnotabigproblem.ThefewerunusedattributesinthegeneratedHTMLoutput,thebetteritis.Only,thiswon’tworkasexpectedforthe<option>ofa<select>element.Ifthecustomconverterwouldreturnnullinsteadofanemptystring,thenthe<option>elementwouldberenderedwithoutanyvalueattributeandthusfallbacktosubmittingitstextcontentinstead.Awkwardindeed,butthisisliterallyspecifiedintheHTMLspecification. Inotherwords,ifyouhaveaconverterthatincorrectlyreturnsnullinsteadofanemptystring,andyouhaveadrop-downlistwiththeassociatedentitiesalongwithadefaultoptionasfollows:<h:selectOneMenuvalue="#{bean.product}">
<f:selectItemitemValue="#{null}"itemLabel="Please
select..."/>
<f:selectItemsvalue="#{bean.products}"
22
23
var="product"itemLabel="#{product.name}">
</f:selectItems>
</h:selectOneMenu>
thenthewebbrowserwould,duringsubmittingthedefaultoption,sendtheliteralstring“Pleaseselect…”totheserverinsteadofanemptystring.ThiswouldcauseaNumberFormatExceptioninProductConverter#getAsObject()whileweintendtoreturnnullhere.ThecorrectsolutionisthustoletthegetAsString()returnanemptystringincasethemodelvalueisnull.
IncaseyouhavemorepersistenceentitiesforwhichyouneedaJSFconverter,andwanttoavoidrepeatingessentiallythesameProductConverterlogicforallotherpersistenceentities,youcancreateagenericJSFconverterforthem.ThisworksonlyifallyourpersistenceentitiesextendfromthesamebaseclasswhereinthegetId()isdefined.
@MappedSuperClass
publicabstractclassBaseEntityimplementsSerializable{
@Id@GeneratedValue(strategy=IDENTITY)
privateLongid;
publicLonggetId(){
returnid;
}
}
Andifyouhaveabaseentityserviceforallofthem:
@Stateless
publicclassBaseEntityService{
@PersistenceContext
privateEntityManagerentityManager;
@TransactionAttribute(SUPPORTS)
public<EextendsBaseEntity>EgetById(Class<E>type,
Longid){
returnentityManager.find(type,id);
}
}
thegenericconvertercanthenlookasfollows:
@FacesConverter(forClass=BaseEntity.class,managed=true)
publicclassBaseEntityConverterimplements
Converter<BaseEntity>{
@Inject
privateBaseEntityServicebaseEntityService;
@Override
publicStringgetAsString
(FacesContextcontext,UIComponentcomponent,
BaseEntityentity)
{
if(entity==null){
return"";
}
if(entity.getId()!=null){
returnentity.getId().toString();
}
else{
thrownewConverterException(
newFacesMessage("InvalidentityID"),e);
}
}
@Override
publicBaseEntitygetAsObject
(FacesContextcontext,UIComponentcomponent,String
id)
{
if(id==null||id.isEmpty()){
returnnull;
}
ValueExpressionvalue=
component.getValueExpression("value");
Class<?extendsBaseEntity>type=(Class<?extends
BaseEntity>)
value.getType(context.getELContext());
try{
returnbaseEntityService.getById(type,
Long.valueOf(id));
}
catch(NumberFormatExceptione){
thrownewConverterException(
newFacesMessage("InvalidentityID"),e);
}
}
}
ThekeyhereisthustheValueExpression#getType()call.ThisreturnstheactualtypeofthepropertybehindtheELexpressionassociatedwiththecomponent’svalueattribute.Incaseof<f:viewParamvalue="#{editProduct.product}">thiswouldthusreturnProduct.class,whichfitsClass<?extendsBaseEntity>.
Comingbacktothelesscommonusecaseofacustomconverter,extendingastandardconverter,imaginethatyouhavea<f:convertDateTime>configurationwhichis
repeatedeverywhereinyourwebapplication:
<f:convertDateTimetype="localDate"pattern="yyyy-MM-dd"/>
Andyou’dliketoreplaceitwithsomethinglikethefollowing:<t:convertLocalDate/>
Thenonewayistojustextendit,setthedefaultsintheconstructor,registeritinthe*.taglib.xmlfile,andthat’sit.FollowingiswhatsuchaLocalDateConvertercanlooklike:@FacesConverter("project.ConvertLocalDate")publicclassLocalDateConverterextends
DateTimeConverter{
publicLocalDateConverter(){
setType("localDate");
setPattern("yyyy-MM-dd");
}
}
Andhere’stheWEB-INFexample.taglib.xmlentry.
<tag>
<tag-name>convertLocalDate</tag-name>
<converter>
<converter-id>project.ConvertLocalDate</converter-id>
</converter>
</tag>
Alternatively,youcanalsomakeitanimplicitconverterbygettingridoftheconverterIDandmakingitaforClassconverter.
@FacesConverter(forClass=LocalDate.class)
Thiswayyoudon’tevenneedany<t:convertLocalDate>tag.Don’tforgettoremovethe<tag>entryinexample.taglib.xml.Theycannotbeusedsimultaneously.Ifyouneedsuchcase,forexample,becauseyouwanttoabletochangetheLocalDatepattern,createanothersubclass.
YoucanevenhaveaforClassconverterforjava.lang.Stringtypedproperties.Thisisveryusefulwhenyouwanthaveanautomaticapplication-widestring-trimmingstrategywhichshouldpreventthemodelfrombeingpollutedwithleadingortrailingwhitespaceonuser-submittedvalues.Followingiswhatsuchaconvertercanlooklike:@FacesConverter(forClass=String.class)
publicclassTrimConverterimplementsConverter<String>
{
@Override
publicStringgetAsString
(FacesContextcontext,UIComponentcomponent,
StringmodelValue)
{
returnmodelValue==null?"":modelValue;
}
@Override
publicStringgetAsObject(FacesContextcontext,
UIComponentcomponent,StringsubmittedValue)
{
if(submittedValue==null||
submittedValue.isEmpty()){
returnnull;
}
Stringtrimmed=submittedValue.trim();
returntrimmed.isEmpty()?null:trimmed;
}
}
Lastbutnotleast,whenyouneedtoprovidewholeentitiesasSelectItemvaluesofaselectioncomponentasbelow(seealsoChapter4),alongwithacustomconverterforCountry.class:<h:selectOneMenuvalue="#{bean.country}">
<f:selectItemitemValue="#{null}"itemLabel="--
selectone--"/>
<f:selectItemsvalue="#{bean.availableCountries}"
var="country">
itemValue="#{country}"itemLabel="#
{country.name}"
</f:selectItems>
</h:selectOneMenu>
wheretheassociatedbackingbeanpropertiesaredeclaredasfollows:privateCountrycountry;privateList<Country>availableCountries;
thenyouneedtokeepinmindthattheentityhasitsequals()andhashCode()properlyimplemented.OtherwiseJSFmaythrowaconfusingvalidationerrorwhensubmittingtheform.
ValidationError:Valueisnotvalid
Thismayhappenwhenthebeanisrequestscopedinsteadofviewscopedandthusrecreatesthelistofavailablecountriesduringeverypostback.Aspartofsafeguardagainsttamperedrequests,JSFwillreiterateovertheavailableoptionsinordertovalidateiftheselectedoptionisindeedamongthem.JSFwillusetheObject#equals()methodtotesttheselectedoptionagainsteachavailableoption.Ifthishasn’treturnedtrueforanyoftheavailableoptions,thentheabove-mentionedvalidationerrorwillbethrown.
ContinuingwiththeBaseEntityexample,here’showyou’dbestimplementitsequals()andhashCode()methods.
@Override
publicbooleanequals(Objectother){
if(getId()!=null
&&getClass().isInstance(other)
&&other.getClass().isInstance(this))
{
returngetId().equals(((BaseEntity)other).getId());
}
else{
return(other==this);
}
}
@Override
publicinthashCode(){
if(getId()!=null){
returnObjects.hash(getId());
}
else{
returnsuper.hashCode();
}
}
NotethebidirectionalClass#isInstance()testintheequals()method.ThisisdoneinsteadofgetClass()==other.getClass(),becausethatwouldreturnfalsewhenyourpersistenceframeworkusesproxies,suchasHibernate.
CustomValidatorsAlso,validatorscanbecustomizedinJSFfromthebeginning.AsalmosteveryeverybasicusecaseisalreadycoveredbystandardJSFvalidatorsandevenBeanValidationconstraints,suchaslength,range,andpatternvalidation,themostcommonusecaselefttoacustomJSFvalidatorisvalidatingthedataintegritybytestingthesubmittedvalueagainstdatabase-basedconstraints.Generally,thoseconcernuniqueconstraints.
Agoodreal-worldexampleisvalidatingduringe-mail-basedsignuporwhilechangingthee-mailaddressintheuseraccountmanagementpagewhenthespecifiede-mailaddressisnotalreadyinuse.Particularly,thechangeeventcan’tbetestedwithaBeanValidationconstraintinasimpleway,becauseBeanValidationdoesn’toffertheopportunitytocomparetheoldvaluewiththenewvaluewithoutre-obtainingtheentityfromthedatabase.Tostart,justimplementthejavax.faces.validator.Validatorinterfaceaccordingly.
@FacesValidator(value="project.UniqueEmailValidator",
managed=true)
24
publicclassUniqueEmailValidatorimplements
Validator<String>{
@Inject
privateUserServiceuserService;
@Override
publicvoidvalidate
(FacesContextcontext,UIComponentcomponent,String
email)
throwsValidatorException
{
if(email==null||email.isEmpty()){
return;//Let@NotNullorrequired=truehandle
this.
}
StringoldEmail=(String)((UIInput)
component).getValue();
if(!email.equals(oldEmail)&&
userService.exist(email)){
thrownewValidatorException(
newFacesMessage("Emailalreadyinuse"));
}
}
}
Inordertogetittorun,justspecifyexactlythedeclaredvalidatorIDinthevalidatorattributeofanyEditableValueHoldercomponent,orthevalidatorIdattributeofanynested<f:validator>tag.
<h:inputTextvalue="#{signup.user.email}"
validator="project.UniqueEmailValidator">
</h:inputText>
WhenlookingattheUniqueEmailValidatorclass,you’llnoticethattheannotationandtheinterfacealsogotthesameJSF2.3changesastheconverter.Likethe@FacesConverter,the@FacesValidatorannotation,sinceJSF2.3,alsogotanewmanagedattributewhichshouldenabledependencyinjectioninthevalidatorimplementation.And,liketheConverter<T>,theValidator<T>alsogotparameterizedwherebythevalidate()methodnowtakesaTinsteadofObjectasavalueargument.
Youalsoneedtomakesurethatyourvalidatorsareimplementedsothattheyskipvalidationwhenthevalueargumentisnullorempty.Historically,inJSF1.x,thevalidate()methodwouldalwaysbeskippedwhenthevalueargumentisnull.However,thishaschangedsincetheintegrationofBeanValidationinJSF2.0,therebybreakingbackwardcompatibilityonexistingJSF1.x-basedcustomvalidators.Thisbreakingchangecouldbeturnedoffbyexplicitlysettingthefollowingweb.xmlcontextparameter:<context-param>
<param-
name>javax.faces.VALIDATE_EMPTY_FIELDS</param-name>
<param-value>false</param-value>
</context-param>
Thedisadvantageofthisisthatthe@NotNullofBeanValidationwon’tbetriggeredbyJSFandyou’dbasicallyneedtorepeatthisconstraintforallJSFinputcomponentsbyexplicitlysettingtheirrequiredattributetotrue.You’dbetternotdothisandjustkeepperformingthenullandemptycheckinyourcustomvalidator.HavingvalidationconstraintsatasingleplaceinthemodelwithhelpofBean
ValidationismoreDon’tRepeatYourself(DRY)thanrepeatingthevalidationconstraintsacrossdifferentlayersusingtheverysamemodel.
Finally,theoldvaluecansimplybeobtainedfromUIInput#getValue()whichbasicallyreturnsthecurrentvalueattributeoftheUIInputcomponent.
Comingbacktotheusecaseofvalidatingtheuniquenessofthesubmittedvalue,ofcourseyoucouldalsoskipthisandinsertthedataanywayandcatchanyconstraintviolationexceptioncomingfromthepersistencelayeranddisplayafacesmessageaccordingly.However,thisdoesn’tgowellwiththecurrenttrendofimmediatefeedbackdirectlyafterchangingtheinputfieldintheuserinterface.
Inthisspecificusecaseofvalidatingauniquee-mailaddressduringsignup,however,theremaybeanotherreasonnottogiveawaytoomuchdetailabouttheuniquenessofthespecifiede-mailaddress:security.Insuchacase,you’dbestletthesignupcompleteexactlythesamewayasifitwassuccessfulwherebyyoutelltheusertocheckthemailbox,butbehindthescenesactuallysendadifferente-mailtothetargetrecipient,ratherthananactivatione-mail,preferablynotmorethanoncedaily.Thee-mailwouldbesimilartothefollowing:
Dearuser,Itlookslikeyouorsomeoneelsetriedtosignuponour
websiteusingyouremailaddressfoo@example.comwhileitisalreadyassociatedwithanexistingaccount.Perhapsyouactuallywantedtologinortoresetyourpassword?Ifitactuallywasn’tyou,pleaseletusknowbyreplyingtothisemailandwe’llinvestigatethis.
Sincerely,ExampleCompany
Finally,youmightalsowanttoconsiderinvalidatingordeduplicatinge-mailsthatcontainthe“+”characterintheusernamepart,followedbyasequenceofcharacters,representingane-mailalias.Foralotofe-mailproviders,notablyGmail,[email protected][email protected],therebybasicallyallowingtheendusertocreateanearlyunlimitedamountofaccounts.
CustomConstraintsWhilenotpartoftheJSF,forthesakeofcompletenesswe’dliketoshowanotherexampleofacustomBeanValidationconstraint.Anearlierexamplewasalreadygiveninthesectionabout<f:validateWholeBean>.TheBeanValidationAPIalreadyoffersalotofexistingconstraintsoutoftheboxwhichyoucanfindinthejavax.validation.constraintspackage. AlotofnewconstraintshavebeenaddedinBeanValidation2.0,alsopartofJavaEE8likeJSF2.3,suchas@Email.
MostcommonusecasesforacustomBeanValidationconstraintarerelatedtolocalizedpatterns.Thinkofphonenumbers,zipcodes,bankaccountnumbers,andpasswords.Ofcourse,mostofthosecouldbedonewithjusta@Pattern,butthismayendupinlessself-documentingcode,particularlyifthedesiredpatternisrelativelycomplex.
Followingisanexampleofacustom@Phoneconstraintwhichshouldmatchasmanyaspossibleinternationallyknownphonenumbers:
@Constraint(validatedBy=PhoneValidator.class)
25
@Target(FIELD)
@Retention(RUNTIME)
public@interfacePhone{
Stringmessage()default"Invalidphonenumber";
Class<?>[]groups()default{};
Class<?>[]payload()default{};
}
Andhere’stheassociatedPhoneValidator:publicclassPhoneValidator
implementsConstraintValidator<Phone,String>
{
privatestaticfinalPatternSPECIAL_CHARS=
Pattern.compile("[\\s().+-]|ext",
Pattern.CASE_INSENSITIVE);
privatestaticfinalPatternDIGITS=
Pattern.compile("[0-9]{7,15}");
@Override
publicbooleanisValid
(Stringphone,ConstraintValidatorContext
context)
{
if(phone==null||phone.isEmpty()){
returntrue;//Let@NotNull/@NotEmpty
handlethis.
}
returnisValid(phone);
}
publicstaticbooleanisValid(Stringphone){
Stringdigits=
SPECIAL_CHARS.matcher(phone).replaceAll("");
returnDIGITS.matcher(digits).matches();
}
}
Inordertoactivateit,simplyannotatetheassociatedentityproperty.
@Phone
privateStringphone;
ThiswillbetriggeredonboththeJSFandJPAsides:inJSF,duringtheprocessvalidationsphase(thirdphase);inJPAduringthepersistandmerge.Asnotedinthe<f:validateBean>/<f:validateWholeBean>section,itcanbedisabledonbothsides.
CustomMessagesConversionandvalidationerrormessagescomingfromJSFaswellasBeanValidationarefullycustomizable.Application-wide,theycanbecustomizedbysupplyingapropertiesfilewhichspecifiesthedesiredmessageasthevalueofapredefinedkey.YoucanfindpredefinedkeysforJSFconversionandvalidationmessagesinChapter2.5.2.4,“LocalizedApplicationMessages,”oftheJSF2.3specification. YoucanfindpredefinedkeysforBeanValidationmessagesinAppendixB,“StandardResourceBundleMessages,”oftheBeanValidation2.0specification. ForJSF,thefullyqualifiednameofthepropertiesfilemustberegisteredas<message-bundle>in
26
27
faces-config.xml.ForBeanValidation,theexactfullyqualifiednameofthepropertiesfileisValidationMessages.
Asanexample,we’regoingtomodifythedefaultmessageoftheJSFrequired="true"validationandtheBeanValidation@NotNullconstraint.
main/java/resources/com/example/project/i18n/messages.propert
ies
javax.faces.component.UIInput.REQUIRED={0}isrequired.
javax.faces.validator.BeanValidator.MESSAGE={1}{0}
main/java/resources/ValidationMessages.properties
javax.validation.constraints.NotNull.message=isrequired.
NotetheabsenceofthelabelplaceholderintheBeanValidationmessage.Instead,the{1}ofthejavax.faces.validator.BeanValidator.MESSAG
ErepresentsthelabelassociatedwiththeJSFcomponentand{0}representstheBeanValidationmessage.ThecustomBeanValidationmessagebundlefileisalreadyautomaticallypickedup.ThecustomJSFmessagebundlefileneedstobeexplicitlyregisteredinthefaces-config.xmlfirst.
<application>
<message-
bundle>com.example.project.i18n.messages</message-bundle>
</application>
Withthosepropertiesfilesinplace,thefollowinginputcomponentswillthusshowexactlythesamevalidationerrormessage:<h:inputTextid="field"label="Firstinput"value="#{bean.field}"required="true">
</h:inputText>
<h:messagefor="field"/>
<h:inputTextid="notNullField"label="Secondinput"
value="#{bean.notNullField}">
</h:inputText>
<h:messagefor="notNullField"/>
Incaseyouwanttofine-grainthemessageonaper-componentbasis,youcanusetheconverterMessage,validatorMessage,and/orrequiredMessageattributeoftheUIInputcomponent.TheconverterMessagewillbedisplayedonanyconversionerror.
<h:inputTextvalue="#{bean.localDate}"
converterMessage="PleaseenterdateinpatternYYYY-MM-
DD.">
<f:convertLocalDatetype="localDate"pattern="yyyy-MM-dd"
/>
</h:inputText>
ThevalidatorMessagewillbedisplayedonanyvalidationerror,aswellasthosetriggeredbyBeanValidation.
<h:inputTextvalue="#{bean.dutchZipCode}"required="true"
validatorMessage="Pleaseenterzipcodeinpattern
1234AB.">
<f:validateRegexpattern="[0-9]{4}[A-Z]{2}"/>
</h:inputText>
Notethatthiswon’tbeshownwhenrequired="true"isn’tsatisfied.Forthat,youneedtouserequiredMessage
instead.
<h:inputTextvalue="#{bean.dutchZipCode}"required="true"
requiredMessage="Pleaseenterzipcode."
validatorMessage="Pleaseenterzipcodeinpattern
1234AB.">
<f:validateRegexpattern="[0-9]{4}[A-Z]{2}"/>
</h:inputText>
Notethatthiswon’tbeshownforanyBeanValidation@NotNull.YoushouldthenusevalidatorMessageinstead.
Footnotes1https://javaee.github.io/javaee-
spec/javadocs/javax/faces/convert/package-summary.html.
2
https://javaserverfaces.github.io/docs/2.3/vdldoc/f/conv
ertNumber.html.
3
https://docs.oracle.com/javase/8/docs/api/java/text/Numb
erFormat.html.
4http://floating-point-gui.de.
5
https://docs.oracle.com/javase/8/docs/api/java/text/Deci
malFormat.html.
6
https://javaserverfaces.github.io/docs/2.3/vdldoc/f/conv
ertDateTime.html.
7
https://docs.oracle.com/javase/8/docs/api/java/text/Date
Format.html.
8
https://docs.oracle.com/javase/8/docs/api/java/time/form
at/DateTimeFormatter.html.
9https://en.wikipedia.org/wiki/ISO_8601.
10
https://docs.oracle.com/javase/8/docs/api/java/text/Simp
leDateFormat.html.
11
https://docs.oracle.com/javase/8/docs/api/java/time/form
at/DateTimeFormatter.html.
12https://developer.mozilla.org/en-
US/docs/Web/HTML/Element/input/date.
13https://developer.mozilla.org/en-
US/docs/Web/HTML/Element/input/date.
14https://developer.mozilla.org/en-
US/docs/Web/HTML/Element/input/time.
15https://developer.mozilla.org/en-
US/docs/Web/HTML/Element/input/datetime-local.
16https://javaee.github.io/javaee-
spec/javadocs/javax/faces/validator/package-summary.html
.
17
https://docs.oracle.com/javase/8/docs/api/java/util/rege
x/Pattern.html.
18http://hibernate.org/validator/.
19https://javaee.github.io/javaee-
spec/javadocs/javax/persistence/ValidationMode.html.
20https://javaee.github.io/javaee-
spec/javadocs/javax/faces/convert/Converter.html..
21https://stackoverflow.com/q/7665673/157882..
22https://javaee.github.io/javaee-
spec/javadocs/javax/faces/convert/Converter.html..
23https://html.spec.whatwg.org/multipage/form-
elements.html#attr-option-value..
24https://javaee.github.io/javaee-
spec/javadocs/javax/faces/validator/Validator.html..
25https://javaee.github.io/javaee-
spec/javadocs/javax/validation/constraints/package-
summary.html.
26http://download.oracle.com/otn-pub/jcp/jsf-2_3-final-
eval-spec/JSF_2.3.pdf.
27http://beanvalidation.org/2.0/spec/#standard-resolver-
messages.
(1) (2)
©BaukeScholtz,ArjanTijms2018BaukeScholtzandArjanTijms,TheDefinitiveGuidetoJSFinJavaEE8,https://doi.org/10.1007/978-1-4842-3387-0_6
6.OutputComponents
BaukeScholtz andArjanTijms
Willemstad,Curaçao Amsterdam,Noord-Holland,TheNetherlands
Technically,theinputcomponentsasdescribedinChapter4arealsooutputcomponents.Theyarenotonlycapableofprocessinganysubmittedinputvaluesbutalsocapableofoutputtingthemodelvalueduringtherenderresponsephase(sixthphase).ThisisalsovisibleintheJSF(JavaServerFaces)API(applicationprogramminginterface):theUIInputsuperclassextendsfromtheUIOutputsuperclass.
TherearealsoabunchofcomponentsthatmerelyoutputtheirmodelvalueorevenjustanHTMLelement.Thosearethepureoutputcomponents.Theydon’tparticipateinallphasesoftheJSFlifecycle.Sometimestheyparticipateduringtherestoreviewphase(firstphase),incasetheyaredynamicallycreatedormanipulated,butthemajorityoftheirjobisexecutedduringtherenderresponsephase(sixthphase),whilegeneratingtheHTMLoutput.Duringtheotherphases,theydon’tdomanyadditionaltasks.
Document-BasedOutputComponents
1 2
Thesecomponentsare<h:doctype>,<h:head>,and<h:body>.Notethatthere’snosuchcomponentas<h:html>.<h:doctype>isarguablytheleastusedHTMLcomponentoftheentirestandardJSFHTMLcomponentset.Youcouldgetawaywithjustaplain<!DOCTYPEhtml>element.<h:doctype>isonlyusefulwhenyouwanttohaveapureXMLrepresentationofthe<!DOCTYPE>element,whichisgenerallyonlythecasewhenyouneedtostoreentireJSFviewsaspartofanotherXMLstructureofsomehigher-levelabstractlayeraroundJSF.
<h:head>and<h:body>are,sinceJSF2.0,themostimportanttagsafter<f:view>becameoptionalinFacelets.Historically,<f:view>wasmandatoryinJSPinordertodeclaretheJSPpagebeingaJSFview.Whilegeneratingthe<head>and<body>elementsoftheHTMLdocumentdoesn’trequireanyspeciallogic,and<h:head>and<h:body>aren’tmandatoryforaFaceletspageinordertoberecognizedasaJSFview,thosetagsaremandatoryfortheproperautomatichandlingofJavaScriptandCSS(CascadingStyleSheets)resourcedependencies,alsointroducedinJSF2.0.
<h:head>and<h:body>allowJSFtoautomaticallyrelocateJavaScriptandCSSresourcedependenciestotherightplacesinthecomponenttreesothattheyultimatelyendupintherightplaceinthegeneratedHTMLoutput.FromthestandardJSFcomponentsetonly<h:commandLink>,<h:commandScript>,<f:ajax>,and<f:websocket>utilizethisfacility.Theyallrequirethejsf.jsJavaScriptfilebeingincludedinthefinalHTMLdocument.Duringtheviewbuildtime,theywillbasicallyuse
1
UIViewRoot#addComponentResource() toregisterthecomponentresourcedependencyatthespecifiedtargetcomponent,whichcanbeeither<h:head>or<h:body>.Duringtheviewrendertime,therendererassociatedwiththe<h:head>and<h:body>componentwillobtainallsofarregisteredcomponentresourcedependenciesbyUIViewRoot#getComponentResources() andgeneratetheappropriate<linkrel="stylesheet">and<script>elementswithaURL(uniformresourcelocator)referringtheassociatedresourcedependency.
Asshowninthesection“StandardHTMLComponents”inChapter3,thefollowingcodeiswhatthemostminimalandHTML5-validJSFpagelookslike:
<!DOCTYPEhtml>
<htmllang="en"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
>
<h:head>
<title>Title</title>
</h:head>
<h:body>
...
</h:body>
</html>
Text-BasedOutputComponentsThesecomponentsare<h:outputText>,<h:outputFormat>,<h:outputLabel>,and<h:outputLink>.TheyallextendfromtheUIOutputsuperclassandhaveavalueattributewhichcanbeboundto
1
2
amanagedbeanproperty.Duringtheviewrendertime,thegetterwillbeconsultedtoretrieveanddisplayanypresetvalue.Thesecomponentswillneverinvokethesettermethodandthereforeitcouldbesafelyleftoutofthemanagedbeanclassinordertoreduceunusedcode.
Historically,inJSF1.xonJSP(JavaServerPages),<h:outputText>wasmandatoryinordertooutputabeanpropertyastext.JSPdidn’tsupportJSF-styleEL(ExpressionLanguage)#{...}intemplatetext.FaceletssupportedJSF-styleEL#{...}intemplatetextandhencebeanpropertiescouldbeoutputteddirectlyinFaceletswithouttheneedforawholeJSFcomponent.Inotherwords,thefollowingcodesareequivalentinJSFonFacelets,<h:outputText>:<p>Welcome,<h:outputTextvalue="#{user.name}">!<p>
AndELintemplatetext:
<p>Welcome,#{user.name}!</p>
Itdoesn’tneedexplanationthatthelattercodesnippetismoreterseandreadable.<h:outputText>has,however,notbecomeuselessinFacelets.It’sstillusefulforthefollowingpurposes:
DisablingimplicitHTMLescaping.
Attachinganexplicitconverter.
Referencingin<f:ajaxrender>.
JSFhasimplicitHTMLescapingeverywhere.AnythingoutputtedtotheHTMLresponseischeckedontheHTMLspecialcharacters“<”,“>”,“&”,andoptionallyalso“"”whenoutputtedwithinanattributeofanHTMLelement.Those
HTMLspecialcharacterswillbereplacedby“<”,“>”,“&”,and“"”,respectively.ThewebbrowserwillthennotinterpretthosecharactersaspartofthegeneratedHTMLoutputbutasplaintextand,ultimately,willpresentthemasliteralcharacterstotheenduser.
Imaginethatauserchooses<script>alert('xss')</script>asausername,andit’semittedvia#{user.name}througheitheroneoftheaboveshowncodesnippets;thenJSFwillrenderitasfollowsinthegeneratedHTMLoutput:<p>Welcome,<script>alert('xss')</script>!</p>
Andthewebbrowserwilldisplayitliterallyas“Welcome,<script>alert('xss')</script>!”insteadofonly“Welcome,!”alongwithaJavaScriptalertwiththetext“xss”wherebytheuser-controlledJavaScriptisunintentionallyactuallyexecuted.TheenduserbeingabletoexecutearbitraryJavaScriptcodeisdangerous.Itwouldallowthemalicioususertoexecutespecificcodewhichtransfersinformationaboutsessioncookiestoanexternalhostwhensomeoneelselogsinandviewsapagewhereintheusernameofthemalicioususerisbeingrendered.(Seealsothesection“Cross-SiteScriptingProtection”inChapter13.)Ontheotherhand,theremayalsobecaseswherebyyou’dliketoembedsafeHTMLcodeinthegeneratedHTMLoutput.Mostcommonusecasesarerelatedtopostingmessagesforotherusersonawebsite,wherebyalimitedsubsetofformattingisallowed,suchasbold,italics,links,lists,headings,etc.Generally,thosearetobeenteredinatextareaelementusingapredefinedhuman-friendlymarkupformat,suchasMarkdown,orthelesserknownWikicode,ortheancientBBCode.Theyareallcapableofparsingtheraw
textwiththemarkupandconvertingittosafeHTMLcodewherebyanymaliciousHTMLcodeisalreadyescapedorstrippedout.
<h:inputTextareavalue="#{message.text}"/>
Therawtextisatleastalwayssavedinthedatabasefortherecord,andtheresultingsafeHTMLcode,alongwiththeversionoftheparserused,canalsobesavedinthedatabaseforperformance,sothattheparserdoesn’tneedtobeunnecessarilyre-executedforthesamepieceofrawtext.Giventhatwe’regoingtouseMarkdownwithCommonMarkandhavingthefollowingMarkdowninterface,
privateinterfaceMarkdown{
publicStringgetText();
publicvoidsetHtml(Stringhtml);
publicStringgetVersion();
publicvoidsetVersion(Stringversion);
}
AndthefollowingMarkdownListenerentitylistener,publicclassMarkdownListener{
privatestaticfinalParserPARSER=
Parser.builder().build();
privatestaticfinalHtmlRendererRENDERER=
HtmlRenderer.builder().escapeHtml(true).build();
privatestaticfinalStringVERSION=
getCommonMarkVersion();
@PrePersist
3
publicvoidparseMarkdown(Markdownmarkdown){
Stringhtml=
RENDERER.render(PARSER.parse(markdown.getText()));
markdown.setHtml(html);
markdown.setVersion(VERSION);
}
@PreUpdate
publicvoidparseMarkdownIfNecessary(Markdown
markdown){
if(markdown.getVersion()==null){
parseMarkdown(markdown);
}
}
@PostLoad
publicvoidupdateMarkdownIfNecessary(Markdown
markdown){
if(!VERSION.equals(markdown.getVersion())){
parseMarkdown(markdown);
}
}
privatestaticStringgetCommonMarkVersion(){
try{
Propertiesproperties=newProperties();
properties.load(Parser.class.getResourceAsSt
ream(
"/META-
INF/maven/com.atlassian.commonmark"
+"commonmarkpom.properties"));
returnproperties.getProperty("version");
}
catch(IOExceptione){
thrownewUncheckedIOException(e);
}
}
}
thentheMessageentityimplementingtheMarkdowninterfaceandregisteredwiththeMarkdownListenerentitylistenercanlookasfollows:
@Entity@EntityListeners(MarkdownListener.class)
publicclassMessageimplementsMarkdown,Serializable{
@Id@GeneratedValue(strategy=IDENTITY)
privateLongid;
@Column(nullable=false)@Lob
private@NotNullStringtext;
@Column(nullable=false)@Lob
privateStringhtml;
@Column(nullable=false,length=8)
privateStringversion;
@Override
publicvoidsetText(Stringtext){
if(!text.equals(this.text)){
this.text=text;
setVersion(null);//TriggerforMarkdownListener
@PreUpdate.
}
}
//Add/generateremaininggettersandsetters.
}
Finally,inordertopresentthesafeHTMLcodetotheenduser,youcanuse<h:outputText>withtheescapeattributesettofalse,wherebyyouthusinstructJSFthatitdoesn’tneedtoimplicitlyHTML-escapethevalue.
<h:outputTextvalue="#{message.html}"escape="false"/>
NexttoimplicitHTMLescaping,JSFalsosupportsimplicitconversion.Foranypropertytypewhichisemittedvia<h:outputText>orevenELintemplatetext,JSFwilllookuptheconverterbyclass,invokeitsConverter#getAsString()method,andrendertheresult.Incaseyouwanttoexplicitlyuseaspecificoradifferentconverter,youhavetoreplaceanyELintemplatetextby<h:outputText>andexplicitlyregistertheconverteronit.Generally,itisthosekindsofnumber-ordatetime-relatedpropertiesthatneedtobeformattedinalocale-specificpattern.
<h:outputTextvalue="#{product.price}">
<f:convertNumbertype="currency"locale="en_US"/>
</h:outputText>
Thelastpurposeof<h:outputText>isbeingabletoreferenceapieceofinlinetextin<f:ajaxrender>.Bydefault,<h:outputText>doesn’tgenerateanyHTMLcode.ButifithasatleastanattributespecifiedwhichmustendupinthegeneratedHTMLoutput,suchasidorstyleClass,thenitwillgenerateanHTML<span>
element.ThisisreferenceableviaJavaScriptandthususefulforAjax-updatingspecificpartsoftext.Ofcourse,youcouldalsooptforAjax-updatingsomecommoncontainercomponent,butthisisfarlessefficientthanAjax-updatingonlyspecificpartswhichreallyneedtobeupdated.
<h:outputFormat>isanextensionof<h:outputText>whichparsesthevalueusingjava.text.MessageFormatAPI beforehand.Thisisparticularlyusefulincombinationwithlocalizedresourcebundles.Anexamplecanbefoundinthesection“ParameterizedResourceBundleValues”inChapter14.
<h:outputLabel>basicallygeneratestheHTML<label>element,whichisanessentialpartofHTMLforms.Thiswasalreadydescribedinthesection“LabelandMessageComponents”inChapter4.Itisimportanttonotethat<h:outputLabel>and<h:outputText>are,inHTMLperspective,absolutelynotinterchangeable.Inarelativelyrecentburstoflow-qualityprogrammingtutorialsitesontheInternetwhichbasicallyshowcodesnippetswithoutanytechnicalexplanationforthesakeofadvertisementincomes,<h:outputLabel>isoftenincorrectlybeingusedtooutputapieceoftextinaHelloWorldJSFpage.Suchtutorialsitescanbetterbeignoredentirely.
<h:outputLink>generatesanHTML<a>element.It’ssomewhataleftoverofJSF1.xandisn’tterriblyusefulsincetheintroductionofthemuchmoreuseful<h:link>inJSF2.0.Whenyoudon’tneedtoreferenceaJSFviewwithalink,forwhichyou’duse<h:link>instead,youcouldaswelljustuseaplainHTML<a>elementinsteadof<h:outputLink>.Thefollowingtagsgenerateexactlythe
4
sameHTML.
<h:outputLinkvalue="http://google.com">Google</h:outputLink>
<ahref="http://google.com">Google</a>
TheplainHTMLequivalentisterser.
Navigation-BasedOutputComponentsThesecomponentsare<h:link>and<h:button>,bothextendingfromtheUIOutcomeTargetsuperclass.TheyhaveanoutcomeattributewhichacceptsalogicalpathtoaJSFview.Thepathwillactuallybevalidatedifit’savalidJSFview;otherwise,thelinkorbuttonwillberenderedasdisabled.Inotherwords,theydon’tacceptapathtoanon-JSFresource,letaloneanexternalURL.Forthis,you’dneed<h:outputLink>orplainHTMLinstead.
<h:link>willgenerateanHTML<a>elementwiththeURLofthetargetJSFviewspecifiedasanhrefattribute.<h:button>willgenerateanHTML<inputtype="button">elementwithanonclickattributewhichassigns,withhelpofJavaScript,theURLofthetargetJSFviewtowindow.location.hrefproperty.Thisisindeedsomewhatawkward,butthat’sjustalimitationofHTML.Neither<inputtype="button">nor<button>supportsanhref-likeattribute.
GiventhefollowingfolderstructureinaMavenWAR
projectinEclipse,
Thefollowing<h:link>and<a>pairsenclosedinfolder1page1.xhtmlwillallgenerateexactlythesamelinks.
<h:linkoutcome="page2"value="link1"/>
<ahref="#{request.contextPath}folder1page2.xhtml">link1</a>
<h:linkoutcome="folder2page1"value="link2"/>
<ahref="#{request.contextPath}folder2page1.xhtml">link2</a>
<h:linkoutcome="folder2page2"value="link3"/>
<ahref="#{request.contextPath}folder2page2.xhtml">link3</a>
<h:linkoutcome="page1"value="link4">
<ahref="#{request.contextPath}/page1.xhtml">link4</a>
<h:linkoutcome="page2"value="link5">
<ahref="#{request.contextPath}/page2.xhtml">link5</a>
Notethusthat<h:link>alreadyautomaticallyprependsanycontextpathofthewebapplicationprojectandappendsthe
currentlyactiveURLpatternoftheFacesServletmapping.Alsonotethatwithouttheleadingslash,theoutcomeisinterpretedrelativetothecurrentfolder,andwithaleadingslash,theoutcomeisinterpretedrelativetothecontextpath.
Panel-BasedOutputComponentsThesecomponentsare<h:panelGroup>and<h:panelGrid>,bothextendingfromtheUIPanelsuperclass.<h:panelGroup>hasmultipleresponsibilities.ItcangenerateanHTML<span>,or<div>,oreven<td>,dependingonthelayoutattributeandwhetherit’senclosedina<h:panelGrid>.
Bydefault,<h:panelGroup>generatesjustanHTML<span>element,like<h:outputText>.Themaindifferenceisthat<h:panelGroup>doesn’thaveavalueattribute.Instead,thecontentisrepresentedbyitschildren.Italsodoesn’tsupportdisablingHTMLescapingorattachingaconverter.That’suptoany<h:outputText>child.Inthiscontext,it’snotterriblyuseful.<h:panelGroup>isonlymoreusefulthan<h:outputText>whenyouneedtobeabletoreferenceusing<f:ajaxrender>aninlineelementwhichinturngroupsabunchofcloselyrelatedinlineelements.Somethinglikethefollowingrepresentsthe“userprofile,”whichshouldbeAjax-updatablefromwithinsomesortofuserprofileeditpage.
<p>
Welcome,
<h:panelGroupid="userProfile">
<imgsrc="#{user.imageUrl}"/>
#{user.name}
</h:panelGroup>
</p>
...
<h:form>
...
<f:ajax...render=":userProfile"/>
...
</h:form>
Whensettingthelayoutattributeof<h:panelGroup>toblock,thenitwillgenerateanHTML<div>element.InstandardHTML,“inlineelements” don’tstartatanewlinebydefaultanddon’tallowblockelementchildren.And,“block-levelelements” alwaysstartatanewlinebydefaultandallowinlineaswellasblockelementsaschildren.Hencethesupportedvaluesofthelayoutattributeof<h:panelGroup>are“inline”and“block”.Historically,thelayoutattributewasonlyaddedinJSF1.2aftercomplaintsfromJSFdevelopersaboutamissingJSFcomponenttorepresentanHTML<div>element.(SeealsoChapter7.)ThiscouldbeusedtowraplargersectionswhichneedtobeAjax-updatable;otherwiseaplainHTML<div>isalsosufficient.
<h:panelGrouplayout="block"id="userProfile">
<p>
Welcome,
<imgsrc="#{user.imageUrl}"/>
#{user.name}
</p>
</h:panelGroup>
Notethatit’sillegalinHTMLtohaveablockelementnested
5
6
inaninlineelement.The<p>isablockelementandhencethelayout="block"isabsolutelymandatoryintheaboveconstruct.Ifyoudon’tspecifythisattributeandthuseffectivelyletJSFrenderanHTML<span>element,thenthewebbrowserbehaviorisunspecified.TheaveragewebbrowserwillrendertheblockelementchildrenoutsidetheinlineelementandevenpossiblyerroroutwhenthisconstructismanipulatedbyJavaScript,suchasduringaJSFAjaxupdateaction.
Alsokeepinmindthatintheaboveconstruct,the<p>tagsandthe“Welcome”textarealsoupdatedduringanyJSFAjaxupdateactiononthe<h:panelGroup>.Thisisessentiallyawasteofhardwareresources,onboththeserversideandtheclientside,asthosearestaticandneversubjecttochanges.WhenAjax-updatingthings,youshouldpreferablyensurethat<f:ajaxrender>onlyreferencescomponentsthatabsolutelyneedtobeAjax-updatedandthusnotanunnecessarilylargesection.
When<h:panelGroup>isbeingnestedina<h:panelGrid>component,whichgeneratesanHTML<table>element,thenthelayoutattributeof<h:panelGroup>isignoredandthecomponentwillbasicallyactasacontainerofcomponentswhichshouldultimatelyendupintheverysamecellofthetable.Thatis,therendererof<h:panelGrid>considerseverydirectchildcomponentasanindividualtablecell.
Giventhefollowingtwo-column<h:panelGrid>,whichshouldgenerateatwo-columnHTMLtable,whatwouldyouguesstheactualgeneratedHTMLoutputshouldlooklike?
<h:panelGridcolumns="2">
one
<h:outputTextvalue="two"/>
three
four
<h:panelGroup>five</h:panelGroup>
six
seven
<h:panelGroup>
eight
nine
</h:panelGroup>
</h:panelGrid>
Hint
EachsectionoftemplatetextbetweentwoJSFcomponentsisinternallyconsideredasingleJSFcomponent.InMojarra,it’srepresentedbytheinternalUIInstructionscomponent.Theactualcomponenttreehierarchyisthusroughlyrepresentedasbelow.
<h:panelGridcolumns="2">
<ui:instructions>one</ui:instructions>
<h:outputTextvalue="two"/>
<ui:instructions>
three
four
</ui:instructions>
<h:panelGroup>five</h:panelGroup>
<ui:instructions>
six
seven
</ui:instructions>
<h:panelGroup>
eight
nine
</h:panelGroup>
</h:panelGrid>
Noteagainthatthere’snosuchcomponentas<ui:instructions>inFacelets.Theabovemarkupispurelyforvisualizationsothatyourbraincanbetterprocessit.This<h:panelGrid>hasthuseffectivelysixdirectchildrenwhichwilleachendupintheirowntablecell.Withtwocolumns,thiswillthuseffectivelygeneratethreerows.Here’stheactualgeneratedHTMLoutput(reformattedforreadability).
<table>
<tbody>
<tr>
<td>one</td>
<td>two</td>
</tr>
<tr>
<td>threefour</td>
<td>five</td>
</tr>
<tr>
<td>sixseven</td>
<td>eightnine</td>
</tr>
</tbody>
</table>
RenderinginChromebrowser:
Yousee,<h:panelGroup>makessurethat“five”and
“eightnine”don’tendupintheverysametablecellas“sixseven.”Alsonotethatit’sunnecessarytowrapanyJSFcomponentin<h:panelGroup>ifitshouldrepresentasinglecellalready.Therefore,<h:outputText>behind“two”doesn’tneedtobewrappedin<h:panelGroup>.Youcan,ofcourse,dosoforbettersourcecodereadability,butthisistechnicallyunnecessary.
Ifyouhappentohaveadynamicamountofcellsbasedonaview-scopedmodel,thenyoucannestaJSTL(JSPStandardTagLibrary)<c:forEach>in<h:panelGrid>tohaveitgeneratethemasadatagridwithafixedamountofcolumns.
<h:panelGridcolumns="3">
<c:forEachitems="#{viewProducts.products}"
var="product">
<h:panelGroup>
<h3>#{product.name}</h3>
<p>#{product.description}</p>
</h:panelGroup>
</c:forEach>
</h:panelGrid>
Notethusthat<ui:repeat>isunsuitablehereascomparedto<c:forEach>,asexplainedinthesection“JSTLCoreTags”inChapter3.Itwilltechnicallyworkjustfine,buttherendererof<h:panelGrid>willinterpretitasasingletablecell.
Alsonotethatit’sveryimportantforthemodeltobeviewscoped,particularlyifyouhaveJSFformcomponentsinside<h:panelGrid>.Thetechnicalreasonisthatduringprocessingthepostbackrequest,JSFexpectsthemodelitembehindtheiterationindextobeexactlythesameasitwas
whenthepagewaspresentedtotheenduser.Inotherwords,whenJSFisabouttoprocessaformsubmit,andanitemhasbeenaddedorremovedorevenreorderedinthemeanwhile,causingtheiterationindextobechanged,thesubmittedvaluesand/ortheinvokedactionwouldpossiblybeperformedagainstthewrongitemcurrentlyattheinitiallyknownindex.Thisisdangerousfortheintegrityofthemodel.Ifyoudon’thaveanyJSFformcomponentsinside<h:panelGrid>,orifthemodelisn’tsubjecttochangesduringtheviewscope,e.g.,becauseit’sonlycreatedorupdatedduringapplicationstartup,thenthebackingbeanbehind#{viewProducts}cansafelyberequestscoped.
DataIterationComponentYes,there’sonlyone,<h:dataTable>,whichextendsfromtheUIDatasuperclassandgeneratesanHTML<table>basedonaniterabledatamodelwherebyeachitemisrepresentedasasinglerow.TheotherdataiterationcomponentavailableinJSF,theFacelets<ui:repeat>,doesn’textendfromtheUIDatasuperclassanddoesn’temitanyHTMLoutputandthereforedoesn’ttechnicallycountasan“outputcomponent.”Also,inthestandardJSFcomponentsetnosuchcomponentgeneratesanHTML<ul>,<ol>,or<dl>,butthiscanrelativelyeasilybecreatedasacustomcomponentextendingfromUIData.(Seealsothesection“CreatingNewComponentandRenderer”inChapter11.)ThevalueattributeofUIDatasupportsjava.lang.Iterable.Inotherwords,youcansupplyanyJavacollectionasamodelvalue.Asindex-basedaccessis
mostusedinUIData,mostefficientisthejava.util.ArrayListasitoffersO(1)accessbyindex.Therendererofthe<h:dataTable>componentsupportsonly<h:column>asadirectchildcomponent.Anythingelseisignored.Asitsnamehints,<h:column>representsasinglecolumn.Eachiterationroundoverthevalueof<h:dataTable>willbasicallyre-renderallcolumnsagainstthecurrentlyiterateditem.Aswith<c:forEach>and<ui:repeat>,thecurrentlyiterateditemisexposedinELscopebythevarattribute.FollowingisabasicexamplewhichiteratesoveraList<String>.
<h:dataTableid="strings"value="#{bean.strings}"
var="string">
<h:column>#{string}</h:column>
</h:dataTable>
Backingbeanclasscom.example.project.view.Bean:@Named@RequestScoped
publicclassBean{
privateList<String>strings;
@PostConstruct
publicvoidinit(){
strings=Arrays.asList("one","two","three");
}
publicList<String>getStrings(){
returnstrings;
}
}
GeneratedHTMLoutput:
<table>
<tbody>
<tr><td>one</td></tr>
<tr><td>two</td></tr>
<tr><td>three</td></tr>
</tbody>
</table>
RenderinginChromebrowser:
Itisimportanttonotethatthevariablenameasspecifiedbythevarattributeshouldn’tclashwithexistingmanagedbeannamesorevenwithimplicitELobjects.ImplicitELobjectshavehigherprecedenceinELresolving.OneexampleofanimplicitELobjectis#{header}whichreferstoExternalContext#getRequestHeaderMap(). Sowhenyouhappentohave#{bean.headers}andyou’dliketopresentitinaniteratingcomponent,thenyoucan’tusevar="header"andyou’dbetterthinkofadifferentname,suchasvar="head".
Followingisamoreelaborateexamplewhichshowsalistofproducts.AsimilartablewasshownearlierinChapter5.
<h:dataTableid="products"value="#{products.list}"
var="product">
<h:column>
7
<f:facetname="header">ID</f:facet>
#{product.id}
</h:column>
<h:column>
<f:facetname="header">Name</f:facet>
#{product.name}
</h:column>
<h:column>
<f:facetname="header">Description</f:facet>
#{product.description}
</h:column>
</h:dataTable>
Backingbeanclasscom.example.project.view.Products:@Named@RequestScoped
publicclassProducts{
privateList<Product>list;
@Inject
privateProductServiceproductService;
@PostConstruct
publicvoidinit(){
list=productService.list();
}
publicList<Product>getList(){
returnlist;
}
}
Productentity:com.example.project.model.
Product:
@Entity
publicclassProduct{
@Id@GeneratedValue(strategy=IDENTITY)
privateLongid;
@Column(nullable=false)
private@NotNullStringname;
@Column(nullable=false)
private@NotNullStringdescription;
//Add/generategetters+setters.
}
Productservice:com.example.project.service.ProductService
:@StatelesspublicclassProductService{
@PersistenceContext
privateEntityManagerentityManager;
@TransactionAttribute(SUPPORTS)
publicList<Product>list(){
returnentityManager
.createQuery("FROMProductORDERBYid
DESC",Product.class)
.getResultList();
}
}
GeneratedHTMLoutput:
<table>
<thead>
<tr>
<thscope="col">ID</th>
<thscope="col">Name</th>
<thscope="col">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>3</td>
<td>Three</td>
<td>Thethirdproduct</td>
</tr>
<tr>
<td>2</td>
<td>Two</td>
<td>Thesecondproduct</td>
</tr>
<tr>
<td>1</td>
<td>One</td>
<td>Thefirstproduct</td>
</tr>
</tbody>
</table>
RenderinginChromebrowser:
Itisimportanttonotethatthemodelbehindthevalueattributeof<h:dataTable>mustreferabeanpropertywhichisalreadypreparedbeforehandinaone-timelifecycle
event,suchas@PostConstructor<f:viewAction>.Thisdoesn’tapplyspecificallytoUIDatacomponentsbuttobasicallyeveryJSFcomponent.Thatis,thegettermethodmaybeinvokedmultipletimesduringtheJSFlifecycle,especiallywhenreferencedinthevalueattributeofaniterationcomponentorintherenderedattributeofanyJSFcomponent.
ThetechnicalreasonisthatanyELvalueexpressionis,behindthescenes,createdasajavax.el.ValueExpressioninstancewhichinternallybasicallyjustholdstheliteralELstringsuchas#{products.list}andanyValueExpression#getValue()callonitwouldsimplyre-evaluatetheexpressionagainsttheprovidedELcontext.Thisisnormallyaverycheapoperation,doneinnanoseconds,butitmayslowdowndrasticallywhenthegettermethodinturnperformsarelativelyexpensivedatabasequerywhichmaytaketensorevenhundredsofmilliseconds.
IterationcomponentsmayinvokethegettermethodduringeveryphaseoftheJSFlifecyclewhentheiterationcomponenthappenstohaveformcomponentsnested.Whenyoupreparethemodelbyobtainingalistfromthedatabaseinthegettermethod,thiswouldcausethedatabasetobequeriedoneverysinglegettermethodcall,whichisplainlyinefficient.Moreover,thesameproblemswithregardtoresolvingtheiterateditemofinterestbasedontheiterationindexmayoccurasdescribedinthelastparagraphoftheprevioussectionabout<h:panelGrid>with<c:forEach>.
Anotherthingtonoteis<f:facetname="header">.Thisgeneratesbasically<thead>with
thecontentin<th>.<h:dataTable>alsosupports<f:facetname="footer">whichwillthengeneratethe<tfoot>withthecontentin<td>.Youcanusuallyfindallsupported<f:facet>namesinthetagdocumentation,aswellasinthe<h:dataTable>tagdocumentation.
Basically,youcanputanythinginside<h:column>torepresentthecellcontent.Evenformcomponentsoranested<h:dataTable>or<ui:repeat>.FollowingisasmallexamplewhichshowsafictiveSet<Tag>tagspropertyofProductentityinanested<ui:repeat>.
<h:dataTableid="products"value="#{products.list}"
var="product">
...
<h:column>
<ui:repeatvalue="#{product.tags}"var="tag">
#{tag.name}<br/>
</ui:repeat>
</h:column>
</h:dataTable>
EDITABLE<H:DATATABLE>Astoformcomponentsnestedinside<h:column>,youcansubstituteELintemplatetextwithinputcomponentsasfollows:
<h:formid="list">
<h:dataTableid="products"value="#{products.list}"
var="product">
<h:column>
<f:facetname="header">ID</f:facet>
#{product.id}
</h:column>
8
<h:column>
<f:facetname="header">Name</f:facet>
<h:inputTextid="name"value="#{product.name}"/>
<h:messagefor="name"/>
</h:column>
<h:column>
<f:facetname="header">Description</f:facet>
<h:inputTextareaid="description"
value="#{product.description}">
</h:inputTextarea>
<h:messagefor="description"/>
</h:column>
</h:dataTable>
<h:commandButtonid="save"value="Save"action="#
{products.save}">
<f:ajaxexecute="@form"render="@form"/>
</h:commandButton>
</h:form>
Wherebythesave()methodofthebackingbeanclassbasicallylooksasfollows,afterhavingchangedthebackingbeanclasstobea@ViewScopedoneinsteadofa@RequestScopedone:publicvoidsave(){productService.update(products);
}
Andtheupdate()methodoftheserviceclassinturnlooksasfollows:@TransactionAttribute(REQUIRED)publicvoidupdate(Iterable<Product>products){
products.forEach(entityManager::merge);
}
Notethatyoudon’tneedtoworryatallaboutcollectingthesubmittedvalues.JSFhasalreadydonethattaskforyou.Alsonotethatyoudon’tneedtoworryaboutuniquenessofthe
componentIDswithin<h:dataTable>,asthatcomponentalreadyimplementstheNamingContainerinterfaceandprependsitsownclientIDandtheiterationindextotheclientIDofthechildcomponents,asyoucanseeinthefollowinggeneratedHTMLoutput:
<tableid="list:products">
<thead>
<tr>
<thscope="col">ID</th>
<thscope="col">Name</th>
<thscope="col">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>3</td>
<td>
<inputid="list:products:0:name"type="text"
name="list:products:0:name"
value="Three">
</input>
</td>
<td>
<textareaid="list:products:0:description"
name="list:products:0:description"
>Thethirdproduct</textarea>
</td>
</tr>
<tr>
<td>2</td>
<td>
<inputid="list:products:1:name"type="text"
name="list:products:1:name"value="Two">
</input>
</td>
<td>
<textareaid="list:products:1:description"
name="list:products:1:description"
>Thesecondproduct</textarea>
</td>
</tr>
<tr>
<td>1</td>
<td>
<inputid="list:products:2:name"type="text"
name="list:products:2:name"value="One">
</input>
</td>
<td>
<textareaid="list:products:2:description"
name="list:products:2:description"
>Thefirstproduct</textarea>
</td>
</tr>
</tbody>
</table>
Itmustbesaidthathavinganeditabletablelikethisisnotterriblyefficient,certainlynotwhenthetablecontainsalotofcolumnsandrows.JSFcanhandleitprettywell;onlytheaveragewebbrowserwillhaveahardtimehandlingit,certainlywhenthenumberofrowsexceedsafewthousand.AndthenI’mnotspeakingabouttheenduserpotentiallygoingcrazyfromscrollingthroughthewholepageallthetimeandbasicallyhavingnoclearoverview.Thereareseveralsolutionstothis:firstandforemostispagination;secondisfiltering;thirdisrow-basedinlineeditingandupdating;andfourthisexternaleditinginadialogordetailpage.
Allthementionedtable-specificperformanceandusabilitysolutionsarenotofferedbythestandard<h:dataTable>andthereforerequirequiteanamountofcustomcode.It’sstronglyrecommendedthatyoulookforanexistingJSF
componentlibrarysupportingthesefeaturesinordertomakeyourJSFlifeeasierwithouttheneedtoreinventthewheel.CurrentlythemostwidelyusedoneisPrimeFaceswithits<p:dataTable>. Thiscanevenbefurthersimplifiedwith<op:dataTable>ofOptimusFaces, whichisinturnbasedon<p:dataTable>.EditinginadetailpageisonlydoablewithstandardJSF,andit’sdemonstratedinthe“CustomConverters”sectionofChapter5.
ItshouldbesaidthatinthespecificcaseofthepreviouslyshownList<String>example,turningthecolumnfromoutputtoinputisn’taseasilydoneaswiththeList<Product>example.Inotherwords,thefollowingexamplewon’tworkatall.
<h:form>
<h:dataTablevalue="#{bean.strings}"var="string">
<h:column>
<h:inputTextvalue="#{string}"/>
</h:column>
</h:dataTable>
<h:commandButtonvalue="Save"action="#{bean.save}">
<f:ajaxexecute="@form"/>
</h:commandButton>
</h:form>
Thetechnicalproblemisthatjava.lang.Stringisimmutableanddoesn’thaveapublicsettermethodforitsinternalvalue.True,itindeedalsodoesn’thaveagetter,butELalreadydefaultstoObject#toString()whichincaseofStringjustreturnstheverystringitself.Thiscanbesolvedbyreferencingthemodelvaluebyanindexasfollows:<h:form>
<h:dataTablebinding="#{table}"value="#
9
10
{bean.strings}"var="string">
<h:column>
<h:inputTextvalue="#
{bean.strings[table.rowIndex]}"/>
</h:column>
</h:dataTable>
<h:commandButtonvalue="Save"action="#{bean.save}">
<f:ajaxexecute="@form"/>
</h:commandButton>
</h:form>
Notethebindingattribute.Basically,duringtheviewbuildtime,thissetsthecurrentUIComponentinstanceasanELvariableidentifiedbythegivenname.Inthisparticularsnippet,itwillthusmakethe#{table}variabletoreferencetheconcreteHtmlDataTableinstancebehindthe<h:dataTable>tag.The#{table}variableis,thenduringtheviewbuildtime,referenceableonlyafterthetaglocationintheviewandduringtheviewrendertimeanywhereintheview.Inthisway,youcanaccessitspropertiesasifitwereabean.#{table.rowIndex}basicallythusreferstotheUIData#getRowIndex()method, whichreturnsthecurrentiterationindex.And,finally,thisisusedtoreferencetheitemofinterestinthelist.Duringtheupdatemodelvaluesphase(fourthphase)JSFwillsimplyreplacetheitematthespecifiedindex.
Alsoforthebindingattributeit’sveryimportantthatthevariablenameshouldn’tclashwithexistingmanagedbeannamesorimplicitELobjectsandforsurenotwithothercomponentsinthesameview.Youcanalternativelyletthebindingattributereferenceabackingbeanpropertyas
11
follows:<h:dataTablebinding="#{bean.table}"...>
With:
privateUIDatatable;//+getter+setter
Butthisisfairlyuselessifitisn’tusedanywhereelseinthebackingbean.Moreover,thisisdangerouswhenthemanagedbeanscopeiswiderthanrequest(seealsothesection“ViewBuildTime”inChapter3.Itisbetternottobindcomponentinstancestoabackingbeanatall;itmightindicateapoorpractice.Theonlyreasonablereal-worldusecaseinJSF2.xisbindingcompositecomponentchildrentoabackingcomponent(seealsothesection“CompositeComponents”inChapter7).
Incaseyou’reusing<ui:repeat>or<c:forEach>insteadof<h:dataTable>onsomethinglikeaList<String>,thenyoucanobtaintheiterationindexinamuchsimplerway,viathevarStatusattribute.
<h:form>
<ui:repeatvalue="#{bean.strings}"var="string"
varStatus="loop">
<h:inputTextvalue="#{bean.strings[loop.index]}">
<br>
</ui:repeat>
<h:commandButtonvalue="Save"action="#{bean.save}">
<f:ajaxexecute="@form"/>
</h:commandButton>
</h:form>
ADD/REMOVEROWSIN<H:DATATABLE>
Comingbacktothe<h:dataTable>withList<Product>,theremaybecasesinwhichyou’dliketobeabletoaddorremoveitemswhilestayinginthesameview,usuallyinsomesortofanadminpage.InordertoaddanewProduct,weneedtoprepareanewinstanceinthemanagedbean,fillitinaseparateform,persistit,andthenrefreshthetable.
<h:formid="list">
<h:dataTableid="products"value="#{products.list}"...>
...
</h:dataTable>
</h:form>
<h:formid="add">
<h:outputLabelfor="name"value="Name"/>
<h:inputTextid="name"value="#{products.product.name}"
/>
<h:messagefor="name"/>
<h:outputLabelfor="description"value="Description"/>
<h:inputTextareaid="description"
value="#{products.product.description}">
</h:inputTextarea>
<h:messagefor="description"/>
<h:commandButtonid="add"value="Add"action="#
{products.add}">
<f:ajaxexecute="@form"render="@form:list:products"
/>
</h:commandButton>
</h:form>
Wherebytherelevantbackingbeancodelooksasfollows:privateList<Product>list;//+getter
privateProductproduct=newProduct();//+getter
@PostConstruct
publicvoidinit(){
list=productService.list();
}
publicvoidadd(){
productService.create(product);
list.add(0,product);
product=newProduct();
}
Withthiscreate()methodinserviceclass:@TransactionAttribute(REQUIRED);
publicLongcreate(Productproduct){
entityManager.persist(product);
returnproduct.getId();
}
Removingcouldbedoneinseveralways.Inanycase,youmayneedanadditionalcolumntoholdthesubmitbuttonsorradiobuttonsorcheckboxes.Theeasiestwayisacolumnwithacommandbuttonwhichdeletesthecurrentlyiterateditemandthenrefreshesthetable.
<h:formid="list">
<h:dataTableid="products"value="#{products.list}"
var="product">
...
<h:column>
<h:commandButtonid="delete"value="Delete"
action="#{products.delete(product)}">
<f:ajaxrender="@namingcontainer"/>
</h:commandButton>
</h:column>
</h:dataTable>
</h:form>
Withthisdelete(Product)methodina@ViewScopedbackingbeanclass:publicvoiddelete(Productproduct){productService.delete(product);
list.remove(product);
}
Andthisdelete()methodinserviceclass:@TransactionAttribute(REQUIRED)
publicvoiddelete(Productproduct){
if(entityManager.contains(product)){
entityManager.remove(product);
}
else{
ProductmanagedProduct=
getById(product.getId());
if(managedProduct!=null){
entityManager.remove(managedProduct);
}
}
}
Notetherenderattributeof<f:ajax>.Itspecifies@namingcontainer,whichbasicallyreferencestheclosestparentNamingContainercomponent.FromthestandardJSFHTMLcomponentset,only<h:form>and<h:dataTable>areinstancesofNamingContainer.Inthisspecificconstruct,@namingcontainerthusreferencesthe<h:dataTable>.Youcouldalsohaveused<f:ajax
render=":list:products">instead;it’sonlyslightlyverbose.The<f:ajaxrender="products">wouldn’twork,becauseitwilltrytofinditwithinthecontextofthecurrentlyiteratedrow,whichisbasicallywithinall<h:column>components.
SELECTROWSIN<H:DATATABLE>Havingaradiobuttoncolumnina<h:dataTable>isnativelypossiblesinceJSF2.3thankstothenewgroupattributeofthe<h:selectOneRadio>(seealsothesection“SelectionComponents”inChapter4).
<h:formid="list">
<h:dataTableid="products"value="#{products.list}"
var="product">
<h:column>
<h:selectOneRadioid="selected"group="selected"
value="#{products.selected}">
<f:selectItemitemValue="#{product}"/>
</h:selectOneRadio>
</h:column>
...
</h:dataTable>
<h:commandButtonid="deleteSelected"value="Delete
selectedproduct"
action="#{products.deleteSelected}">
<f:ajaxexecute="@form"render="products"/>
</h:commandButton>
</h:form>
WiththisdeleteSelected()methodina@ViewScopedbackingbeanclass:privateProductselected;//+getter+setter
publicvoiddeleteSelected(){
productService.delete(selected);
list.remove(selected);
}
NotethatyouneedaProductConverterorBaseEntityConverterhereaswell.Thoseareelaboratedinthesection“CustomConverters”inChapter5.
Thecheckboxselectionisalittlemoreconvoluted.You’dintuitivelygrab<h:selectManyCheckbox>,butthisdoesn’tyetsupportthegroupattributeas<h:selectOneRadio>does.You’dneedtofallbackto<h:selectBooleanCheckbox>withaMap<Product,Boolean>wherebythemapkeyrepresentsthecurrentlyiteratedproductandthemapvaluerepresentsthecheckboxvalue.
<h:formid="list">
<h:dataTableid="products"value="#{products.list}"
var="product">
<h:column>
<h:selectBooleanCheckboxid="selection"
value="#{products.selection[product]}">
</h:selectBooleanCheckbox>
</h:column>
...
</h:dataTable>
<h:commandButtonid="deleteSelected"value="Delete
selectedproducts"
action="#{products.deleteSelected}">
<f:ajaxexecute="@form"render="products"/>
</h:commandButton>
</h:form>
ThemodifieddeleteSelected()methodinthe@ViewScopedbackingbeanlooksasfollows:privateMap<Product,Boolean>selection=newHashMap<>();//
+getter
publicvoiddeleteSelected(){
List<Product>selected=
selection.entrySet().stream()
.filter(Entry::getValue)
.map(Entry::getKey)
.collect(Collectors.toList());
productService.delete(selected);
selected.forEach(list::remove);
selection.clear();
}
TheoverloadedProductService#delete(Iterable)methodlooksasfollows:@TransactionAttribute(REQUIRED)publicvoiddelete(Iterable<Product>products){
products.forEach(this::delete);
}
DYNAMICCOLUMNSIN<H:DATATABLE>With<h:dataTable>,withthehelpofJSTL<c:forEach>,itisalsopossibletodynamicallycreatemultiple<h:column>instancesbasedonaJavamodelwhichisatleastviewscoped.Arequest-scopedmodelcanalso,butthisdoesn’tguaranteethatduringapostbackrequestitisexactlythesameasitwasduringtheprecedingrequest,
andthereforethereisariskofthedynamic<h:column>compositionbeingoff.
Thevalueofthe<c:forEach>shouldreferenceacollectionincludingatleasttheentitypropertynamesorevenmapkeys.YoucanthenusethebracenotationinELasin#{entity[propertyName]}or#{map[key]}toreferencetheactualvalue.ThisworksforbothUIOutputandUIInputcomponents.ThefollowingexampleillustrateshowyoucouldachievethisforaList<Product>.
Backingbean:
@Named@RequestScoped
publicclassProducts{
privateList<Product>list;
privateList<String>properties;
@Inject
privateProductServiceproductService;
@PostConstruct
publicvoidinit(){
list=productService.list();
properties=Arrays.asList("id","name",
"description");
}
//Add/generategetters(settersnotneededhere).
}
Faceletsfile:
<h:dataTablevalue="#{products.list}"var="product">
<c:forEachitems="#{products.properties}"var="property">
<h:column>#{product[property]}</h:column>
</c:forEach>
</h:dataTable>
Youcouldevengeneralizethisfurtherforotherentitiesofacommonsuperclass,suchasBaseEntity,wherebyyouobtaintherelevantpropertynamesfromtheentityservice.
ResourceComponentsJSFoffersthreeresourcecomponents,<h:graphicImage>,<h:outputScript>,and<h:outputStylesheet>,forimageresources,JavaScriptresources,andCSSresources,respectively.Theycanreferencephysicalresourcefilesaswellasdynamicresourcefiles.Thephysicalresourcefilesthemselvesmustbeplacedinthe/resourcessubfolderofthemainwebfolder.ThedynamicresourcefilescanbehandledwithacustomResourceHandlerwhichinterceptsonaspecificlibrarynameand/orresourcename.GiventhefollowingfolderstructureinaMavenWARprojectinEclipse,
theresourcesarereferenceableasfollows:
<h:graphicImagename="images/some.svg"/>
<h:outputScriptname="scripts/some.js"/>
<h:outputStylesheetname="styles/some.css"/>
ThegeneratedHTMLoutputlooksasfollows,providedthat/projectisthecontextpathofthewebapplication:
<imgsrc="projectjavax.faces.resource/images/some.svg.xhtml"
/>
<scripttype="text/javascript"
src="projectjavax.faces.resource/scripts/some.js.xhtml">
</script>
<linktype="text/css"rel="stylesheet"
href="projectjavax.faces.resource/styles/some.css.xhtml"
/>
You’llseethatit’sprefixedwith/javax.faces.resourcepathandsuffixedwiththecurrentlyactiveURLpatternoftheFacesServlet.The/javax.faces.resourceisrepresentedbytheconstantResourceHandler#RESOURCE_IDENTIFIER. ThattheresourceURLmatchestheURLpatternoftheFacesServletensuresthatitwillactuallyinvoketheFacesServletwhichinturnknowshowtohandletheresource.ItwillfirstinvokeResourceHandler#isResourceRequest(),whichbydefaultdeterminesiftheURLprefixstartswiththeknownRESOURCE_IDENTIFIERconstant,andifsothendelegatestoResourceHandler#handleResourceRequest()insteadofgoingthroughtheJSFlifecycle.
Alsonotethatthewebresourcesarenotplacedinthe
12
src/main/resourcesfolderbutinthesrc/main/webapp/resourcesfolder.Thesrc/main/resourcesfolderisonlyfornon-classresourceswhichmustendupintheclasspath,suchasresourcebundlefiles.TheseclasspathresourcesarethenobtainablebyClassLoader#getResource().src/main/webapp/resourcesdoesn’tendupintheclasspath;instead,itendsupinthewebcontent.ThesewebresourcesarethenobtainablebyExternalContext#getResource(), whichdelegatesunderthehoodtoServletContext#getResource().
Thenameattributethusbasicallyrepresentsthepathtotheresourcerelativetothesrc/main/webapp/resourcesfolder.Thesecomponentsalsosupportalibraryattribute.ThelibraryattributemustrepresenttheuniqueresourcelibrarynameofaJSFlibrary.ForstandardJSF,theresourcelibrarynameis“javax.faces”,forPrimeFaces, theresourcelibrarynameis“primefaces”,forOmniFaces,theresourcelibrarynameis“omnifaces”,forBootsFaces, theresourcelibrarynameis“bsf”,andsoon.Normally,theselibrary-specificresourcesarealreadyautomaticallyincludedbytheJSFlibraryinquestion,usuallydeclarativelyviathe@ResourceDependencyannotationontheUIComponentorRendererclass,andsometimesprogrammaticallyviaUIViewRoot#addComponentResource().Thisiselaboratedinthesection“ResourceDependencies”inChapter11.Thoseresourcescanifnecessarybereferencedusingaresourcecomponentwherebyyouthusexplicitlyspecifythelibraryattribute.
13
14
15
16
17
18
ThefollowingexampleexplicitlyincludesthestandardJSFjsf.jsfile:<h:head>...
<h:outputScriptlibrary="javax.faces"name="jsf.js"
/>
</h:head>
ThisisusuallyunnecessaryastheJSFcomponentsdependingonthisscript,suchas<h:commandLink>,<f:ajax>,and<f:websocket>alreadyautomaticallyincludeit.Here’sanotherexamplewhichexplicitlyincludesthejquery.jsfilefromPrimeFaceslibrary—thisworksofcourseonlyifyouhavePrimeFacesinstalled.
<h:head>
...
<h:outputScriptlibrary="primefaces"
name="jquery/jquery.js"/>
</h:head>
Thiscanbeusefulwhenyou’dliketoreusethePrimeFaces-providedjQuerylibraryonapagethatdoesn’tnecessarilycontainPrimeFacescomponents.Thatis,thisscriptwon’tbeautomaticallyincludedwhenthepagedoesn’tcontainanyPrimeFacescomponent,butyoumighthappentohavesomewebproject-specificscriptswhichinturndependonjQuery.JSFresourcemanagementwillalreadymakesurethatbothautomaticallyincludedandexplicitlyincludedJavaScriptandCSSresourcesdon’tgetduplicatedinthegeneratedHTMLoutput.Inotherwords,theabovelinewhichexplicitlyincludesjQuerycansafelybeusedonapagethatdoescontainaPrimeFacescomponent.
Notethattherearecurrentlyarelativelylargenumberofpoor-qualityJSFtutorialsontheInternetwhichdon’tcorrectlyusethelibraryattribute.Instead,thosetutorialsincorrectlydemonstratethelibraryattributetorepresentthesubfolderwithinthesrc/main/webapp/resourcesfolder—somethinglikethefollowing:<h:graphicImagelibrary="images"name="some.svg"/>
<h:outputScriptlibrary="scripts"name="some.js"/>
<h:outputStylesheetlibrary="styles"name="some.css"/>
Thisisoutrightwrong.Itdoesn’tofferanycustomresourcehandlertodistinguishlibrary-specificresourcesfromeachother.Intheaboveexample,you’dbasicallyneedtocheckthreedifferentresourcelibrarieseventhoughallthoseresourcesbelongtotheverysamelibrary—thewebprojectitself.
Talkingaboutcustomresourcehandlers,imaginethatyouwanttocompelthewebbrowsertoforciblyreloadtheimage,JavaScript,and/orCSSresourcewhenithaschangedintheserversideinstead.ThiscanbeachievedbyaddingaquerystringparametertotheresourceURLwhosevaluerepresentstheversionoftheresource.Thisisalsocalled“cachebusting.”InJSF,thiscanbeachievedwithacustomResourceHandlerwhichdecoratestheResourcetoreturnitslastmodifiedtimestampasaquerystringparameter.
publicclassVersionResourceHandlerextends
ResourceHandlerWrapper{
publicVersionResourceHandler(ResourceHandlerwrapped){
super(wrapped);
}
@Override
publicResourcecreateResource(Stringname,String
library){
Resourceresource=super.createResource(name,
library);
if(resource==null||library!=null){
returnresource;
}
returnnewResourceWrapper(resource){
@Override
publicStringgetRequestPath(){
Stringurl=super.getRequestPath();
returnurl
+(url.contains("?")?"&":"?")
+"v="+getLastModified();
}
privatelonggetLastModified(){
try{
returngetWrapped().getURL()
.openConnection().getLastModified();
}
catch(IOExceptionignore){
return0;
}
}
};
}
}
Inordertoactivateit,registeritinthefaces-config.xmlasfollows:<application><resourcehandler>
com.example.project.resourcehandler.VersionResou
rceHandler
</resourcehandler>
</application>
NotethatthecreateResource()methodreturnsthecreatedresourceunmodifiedwhenit’snullorwhenthelibraryisnotnull.Theresourceitselfisnullwhenthenameisunknown.Thelibraryisnullwhenit’sunspecifiedandthusspecifictothewebproject.Youcouldofcoursealsoapplythislogictoallresourcesofotherlibraries,buttheyusuallyalreadyhavetheirownversionofaresourcehandler.
Comingbacktotheresourcecomponents,youcanplace<h:graphicImage>onlyinsidethebody,whichcanbeboththeplainHTML<body>ortheJSF<h:body>.Obviously,inanHTMLdocument,youcanhavean<img>elementonlyinsidethedocumentbody.Youcanplace<h:outputScript>and<h:outputStylesheet>basicallyanywhereintheJSFpage.The<h:outputScript>willbydefaultgeneratetheHTML<script>elementatexactlythedeclaredlocation,regardlessofbeingintheheadorthebodyofthedocument.<h:outputStylesheet>,however,willbydefaultbemovedtotheendof<h:head>whendeclaredinside<h:body>.Thatis,inHTMLit’sillegaltohavea<linkrel="stylesheet">outside<head>.The<h:outputScript>canalsobeautomaticallymovedtoendofdocumentheadwhendeclaredinside<h:body>withthetargetattributesettohead.Whenthetargetattributeof<h:outputScript>issettobody,thenitwillbeautomaticallymovedtoendofthedocumentbody.
<h:outputStylesheet>doesn’tsupportit.Inotherwords,thefollowingtestFacelet,<h:head><title>Resourcecomponentrelocationdemo.</title>
<h:outputStylesheetname="style1.css"/>
<h:outputScriptname="script1.js"/>
<h:outputScriptname="script2.js"target="head"/>
<h:outputScriptname="script3.js"target="body"/>
</h:head>
<h:body>
<p>Paragraph1</p>
<h:outputStylesheetname="style2.css"/>
<h:outputScriptname="script4.js"/>
<h:outputScriptname="script5.js"target="head"/>
<h:outputScriptname="script6.js"target="body"/>
<p>Paragraph2</p>
</h:body>
willbasicallygeneratethefollowingHTMLoutput(URLssimplifiedforbrevity).
<head>
<title>Resourcecomponentrelocationdemo.</title>
<scripttype="text/javascript"src="script1.js"></script>
<linktype="text/css"rel="stylesheet"href="style1.css"
/>
<scripttype="text/javascript"src="script2.js"></script>
<linktype="text/css"rel="stylesheet"href="style2.css"
/>
<scripttype="text/javascript"src="script5.js"></script>
</head>
<body>
<p>Paragraph1</p>
<scripttype="text/javascript"src="script4.js"></script>
<p>Paragraph2</p>
<scripttype="text/javascript"src="script3.js"></script>
<scripttype="text/javascript"src="script6.js"></script>
</body>
Inotherwords,theresourcerenderingorderinthedocumentheadis:
1. 1. <h:outputScript>from<h:head>withouttarget.
2. 2. <h:outputStylesheet>from<h:head>.
3. 3. <h:outputScript>from<h:head>withtarget="head".
4. 4. <h:outputStylesheet>from<h:body>.
5. 5. <h:outputScript>from<h:body>withtarget="head".
Notethat<h:outputStylesheet>implicitlyinferstarget="head"andisthereforerenderedafter<h:outputScript>withoutanytarget.AllJavaScriptandCSSresourceswhichareautomaticallyincludedviathe@ResourceDependencyannotationofcomponentswillendupbetweentheresourcesdeclaredin<h:head>andthosedeclaredin<h:body>.SoifyouhappentouseaJSFlibrarywhichautomaticallyincludesabunchofCSSresources,andyou’dliketooverridesomeofthem,you’dbestputsuch<h:outputStylesheet>in<h:body>sothatyoucanguaranteethatit’sloadedafterthelibrary’s.
Bewarethough,someJSFlibrarieswillautomaticallyoverridethedefaultrendererof<h:head>whichmaymessupthedefaultresourceordering.Insuchacase,you’dbestconsultthedocumentationoftheJSFlibraryinquestionforneworderingrules,ortorestorethedefaultrendererof<h:head>viathewebproject’sfaces-config.xml.
<renderkit>
<renderer>
<component-family>javax.faces.Output</component-
family>
<renderer-type>javax.faces.Head</renderer-type>
<renderer-class>
com.sun.faces.renderkit.html_basic.HeadRenderer
</renderer-class>
</renderer>
</renderkit>
Incaseyou’reusingMyFacesinsteadofMojarraasJSFimplementation,useorg.apache.myfaces.renderkit.html.HtmlHead
Rendererinsteadastherendererclass.IncaseyouintendtodevelopsuchaJSFlibrarywhich
automaticallyincludesspecificresources,keepinmindtouse@ResourceDependencyorUIViewRoot#addComponentResource()insteadofreplacingthedefaultrendererof<h:head>forthepurpose.Asannotationsdon’tallowspecifyingdynamicvalues,anydynamicresourcescanbestbeaddedduringthePostAddToVieweventof<h:head>.Thiscanbeachievedapplication-widewithaSystemEventListenerasfollowsassumingthattheJSFlibrary’sresourcelibrarynameis“foo”:publicclassDynamicResourceListenerimplementsSystemEventListener{
privatestaticfinalStringLIBRARY="foo";
@Override
publicbooleanisListenerForSource(Objectsource){
UIOutputoutput=(UIOutput)source;
return
"javax.faces.Head".equals(output.getRendererType());
}
@Override
publicvoidprocessEvent(SystemEventevent){
FacesContextcontext=event.getFacesContext();
StringscriptName="foo.js";//Canbedynamic.
addResource(context,scriptName);
StringstylesheetName="foo.css";//Canbe
dynamic.
addResource(context,stylesheetName);
}
privatevoidaddResource(FacesContextcontext,
Stringname){
UIComponentresource=newUIOutput();
resource.getAttributes().put("library",
LIBRARY);
resource.getAttributes().put("name",name);
resource.setRendererType(context.getApplication(
)
.getResourceHandler().getRendererTypeForReso
urceName(name));
context.getViewRoot()
.addComponentResource(context,resource,
"head");
}
}
whichisregisteredinfaces-config.xmlasfollows:<system-event-listener>
<system-event-listener-class>
com.example.project.listener.DynamicResourceList
ener
</system-event-listener-class>
<system-event-class>
javax.faces.event.PostAddToViewEvent
</system-event-class>
<source-
class>javax.faces.component.UIOutput</source-class>
</system-event-listener>
Notethat<source-class>couldbetterhavebeenajavax.faces.component.html.HtmlHead,butthisdoesn’tnecessarilyworkacrossallJSFimplementations.In,forexample,Mojarra,<h:head>implicitlycreatesaninstanceofUIOutputinsteadofHtmlHead.
Onceinstalled,thisDynamicResourceListenerwillresultinthefollowingHTMLoutputforexactlythelastshowntestFaceletwithstyle1.css,script1.js,script2.js,etc.(alsohere,URLsaresimplifiedforbrevity).
<head>
<title>Resourcecomponentrelocationdemo.</title>
<scripttype="text/javascript"src="script1.js"></script>
<scripttype="text/javascript"src="foo.js"></script>
<linktype="text/css"rel="stylesheet"href="foo.css"/>
<linktype="text/css"rel="stylesheet"href="style1.css"
/>
<scripttype="text/javascript"src="script2.js"></script>
<linktype="text/css"rel="stylesheet"href="style2.css"
/>
<scripttype="text/javascript"src="script5.js"></script>
</head>
<body>
<p>Paragraph1</p>
<scripttype="text/javascript"src="script4.js"></script>
<p>Paragraph2</p>
<scripttype="text/javascript"src="script3.js"></script>
<scripttype="text/javascript"src="script6.js"></script>
</body>
Theresourcerenderingorderinthedocumentheadisthus1. 6. <h:outputScript>from<h:head>withouttarget.
2. 7. DynamicscriptaddedtoheadduringPostAddToView.
3. 8. DynamicstylesheetaddedtoheadduringPostAddToView.
4. 9. <h:outputStylesheet>from<h:head>.
5. 10. <h:outputScript>from<h:head>withtarget="head".
6. 11. <h:outputStylesheet>from<h:body>.
7. 12. <h:outputScript>from<h:body>withtarget="head".
Yousee,theorderingisquitepredictable.Itshouldn’thavebeennecessarytooverridetherendererof<h:head>.Moreover,overridingtherendererof<h:head>fromaJSFlibraryrisksthepossibilitythatitbecomesincompatiblewithanyotherJSFlibrarywhichcoincidentallyalsooverridestherendererof<h:head>.You’dreallywanttoavoidthat.
AnotheradvantageofusingresourcecomponentsisthatJSFwillautomaticallypushallresourcesassociatedwiththedocumenttotheclient,sothattheclientwillbeabletoretrievethemsoonerthanthetimeneededtoparsetheHTMLdocumentandlocateall<link>,<script>,and<img>elements.ThisisnewsinceJSF2.3.Thisonlyrequiresthatthe
Pass-ThroughElementsJSFalsosupportsimplicitlyinterpretinganyarbitraryHTMLelementasafull-fledgedJSFcomponent.ThisfeaturewasintroducedinJSF2.2andisformallyknownas“pass-throughelements.”ThisisparticularlyusefulwhenyouwanttouseHTML5elementssuchas<main>,<article>,<section>,<aside>,<nav>,<header>,<footer>,etc.andwanttobeabletoreferencethemin<f:ajaxrender>.Previously,beforeJSF2.2,thoseelementsdidn’thaveaJSFcomponentequivalentandyou’rethereforeforcedtowrapthemin<h:panelGrouplayout="block">whichonlymakestheHTMLlesssemantic.Thepass-throughelementtriggerisavailablebythehttp://xmlns.jcp.org/jsfnamespace.Allyouneedtodoistospecifyatleastoneattributeonthisnamespace.Thedefaultnamespaceprefixisjust“jsf”.
<!DOCTYPEhtml>
<htmllang="en"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:jsf="http://xmlns.jcp.org/jsf"
xmlns:h="http://xmlns.jcp.org/jsf/html"
>
<h:head>
<title>Title</title>
</h:head>
<h:body>
<header>
...
JSF2.3webapplicationisdeployedtoaServlet4.0-compatiblecontainer(Payara5,GlassFish5,Tomcat9,WildFly12,etc.),andthatHTTPSisbeingusedinsteadofHTTP,andthattheclientsupportsHTTP/2protocol. ThisdoesnotrequireadditionalconfigurationfromtheJSFsideon.
19
<navjsf:id="menu">
...
</nav>
</header>
<mainjsf:id="main">
...
</main>
<footer>
...
</footer>
</h:body>
</html>
Underthehood,intheJSFcomponenttree,thoseHTML5elementsareturnedintoaUIPanelcomponentandaretreatedintheJSFcomponenttreeexactlylike<h:panelGroup>.ThiswayyoucancleanlykeepusingsemanticHTML5markupwhilestillbeingabletoreferencethemvia<f:ajaxrender>.Inotherwords,thefollowingconstructwon’twork:<mainid="main">
...
<h:formid="form">
...
<h:commandButtonid="submit"...>
<f:ajaxrender=":main"/>
</h:commandButton>
</h:form>
</main>
ItfailsbecauseUIViewRoot#findComponent()doesn’treturnanythingwhenpassing“main”.JSFcan’tfindanycomponentwiththegivenID.The<main>elementisherebasicallyinterpretedastemplatetext.Butthefollowingconstructwillwork:<mainjsf:id="main">...
<h:formid="form">
...
<h:commandButtonid="submit"...>
<f:ajaxrender=":main"/>
</h:commandButton>
</h:form>
</main>
UIViewRoot#findComponent()on“main”willthenreturnaUIPanelinstancerepresentingthe<main>element.JSFwillthenbeabletorenderitintotheAjaxresponse.Thepass-throughelementfeaturealsoworksonotherHTMLelements,onlytheydon’tnecessarilyturnintoaUIPanelinstance.Instead,theywillbeturnedintoaJSFcomponentwhosegeneratedHTMLoutputmatchestheveryHTMLelement(seeTable6-1).Thefollowingconstructis,underthehood,identicaltothepreviousone:<mainjsf:id="main">...
<formjsf:id="form">
...
<inputtype="submit"jsf:id="submit"...>
<f:ajaxrender=":main"/>
</input>
</form>
</main>
Table6-1 PassthroughElementsRecognizedbyJSF
PassthroughHTMLelement
ImpliedJSFcomponent
<ajsf:action="…">
<h:commandLink>
<ajsf:actionListener="…">
<h:commandLink>
<ajsf:actionListener="…">
<h:commandLink>
<ajsf:value="…">
<h:outputLink>
<ajsf:outcome="…">
<h:link>
<bodyjsf:id="…">
<h:body>
<buttonjsf:id="…">
<h:commandButtontype="button">
<buttonjsf:outcome="…">
<h:button>
<formjsf:id="…">
<h:form>
<headjsf:id="…">
<h:head>
<imgjsf:id="…">
<h:graphicImage>
<inputjsf:id="…"type="button">
<h:commandButtontype="button">
<inputjsf:id="…"type="checkbox">
<h:selectBooleanCheckbox>
<inputjsf:id="…"type="file">
<h:inputFile>
<inputjsf:id="…"type="hidden">
<h:inputHidden>
<inputjsf:id="…"type="password">
<h:inputSecret>
<inputjsf:id="…"type="reset">
<h:commandButtontype="reset">
<inputjsf:id="…"type="submit">
<h:commandButtontype="submit">
<inputjsf:id="…"type="*">
<h:inputText>
<labeljsf:id="…">
<h:outputLabel>
<linkjsf:id="…">
<h:outputStylesheet>
<scriptjsf:id="…">
<h:outputScript>
<selectjsf:id="…">
<h:selectOneListbox>
<selectjsf:id="…"multiple="*">
<h:selectManyListbox>
<*jsf:id="…">
<h:panelGroup>
Anyattributespecifiedonsuchapass-throughelementisimplicitlymappedtothecorrespondingattributeoftheJSFcomponent.Inthefollowingexample,theJSFcomponentandpass-throughelementpairsareequivalent.
<h:graphicImagelibrary="common"name="some.svg"/>
<imgjsf:library="common"name="some.svg"/>
<h:inputTextvalue="#{bean.name}"/>
<inputtype="text"jsf:value="#{bean.name}"/>
<h:inputTexta:type="email"value="#{bean.email}"/>
<inputtype="email"jsf:value="#{bean.email}"/>
<h:linkoutcome="contact"value="Contact"/>
<ajsf:outcome="contact">Contact</a>
Notethatyoudon’tnecessarilyneedtoregistereverysingleattributeofapass-throughelementonthe“jsf”namespace.Onlyoneissufficienttotriggerthepass-throughelementfeature,preferablythefirstone.Thiskeepsthecodeconcise.
Footnotes1https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/UIViewRoot.html#addComponentResource
-javax.faces.context.FacesContext-javax.faces.component.UIComponent-
java.lang.String-.
2https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/UIViewRoot.html#getComponentResource
s-javax.faces.context.FacesContext-java.lang.String-.
3https://github.com/atlassian/commonmark-java.
4https://docs.oracle.com/javase/8/docs/api/java/text/MessageFormat.html.
5https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements.
6https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements.
7https://javaee.github.io/javaee-
spec/javadocs/javax/faces/context/ExternalContext.html#getRequestHeaderM
ap--.
8
https://javaserverfaces.github.io/docs/2.3/vdldocs/facelets/h/dataTable.
html.
9https://www.primefaces.org/showcase/ui/data/datatable/basic.xhtml.
10https://github.com/omnifaces/optimusfaces.
11https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/UIData.html#getRowIndex--.
12https://javaee.github.io/javaee-
spec/javadocs/javax/faces/application/ResourceHandler.html#RESOURCE_IDEN
TIFIER.
13
https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html#get
Resource-java.lang.String-.
14https://javaee.github.io/javaee-
spec/javadocs/javax/faces/context/ExternalContext.html#getResource-
java.lang.String-.
15https://javaee.github.io/javaee-
spec/javadocs/javax/servlet/ServletContext.html#getResource-
java.lang.String-.
16http://www.primefaces.org.
17http://omnifaces.org.
18http://bootsfaces.net.
19https://caniuse.com/#feat=http2.
(1) (2)
©BaukeScholtz,ArjanTijms2018BaukeScholtzandArjanTijms,TheDefinitiveGuidetoJSFinJavaEE8,https://doi.org/10.1007/978-1-4842-3387-0_7
7.FaceletsTemplating
BaukeScholtz andArjanTijms
Willemstad,Curaçao Amsterdam,Noord-Holland,TheNetherlands
WhenJSF(JavaServerFaces)cameoutforfirsttimein2004,onlyJSP(JavaServerPages)hadviewtechnology.Itwasimmediatelyclearthatitwasanill-suitedviewtechnologyforwebdevelopmentwithJSF.TheproblemwithJSPisthatitwritestotheHTTPresponseassoonasitencounterstemplatetext,whileJSFwouldliketofirstcreateacomponenttreebasedontheviewdeclarationinordertobeabletoperformthelifecycleprocessingonit.Forexample,thefollowingJSF1.0/1.1pageusingJSPviewtechnology,<h:commandLinkaction="...">
<strong>stronglinktext</strong>
</h:commandLink>
wouldproducethefollowingHTMLoutput,withtemplatetextemittedbyJSPbeforethegeneratedHTMLoutputofJSFcomponents :<strong>stronglinktext</strong><ahref="..."onclick="..."></a>
The“correctapproach”wouldbetowraptemplatetextin
1 2
1
<f:verbatim>tags,<h:commandLinkaction="..."><f:verbatim><strong>stronglinktext</strong>
<f:verbatim>
</h:commandLink>
whichwouldproducethefollowingHTMLoutput:<ahref="..."onclick="..."><strong>stronglink
text</strong></a>
Thishasobviouslyreceivedalotofcriticism,andthegeneralrecommendationnowisthatpeopleshouldnotbemixingJSFwithHTML.AnotherproblemwithJSF1.0/1.1wasthattherewasnocomponenttorepresentanHTML<div>.Thatwasaroundthetime“web2.0”hadjuststarted,andpeoplealsostartedtodiscommendtheuseofHTMLtablestolayoutawebpage.Theprevailingviewwasthatonlydivsshouldbeused,whichmadepeopledislikeJSF1.0/1.1evenmore.
ThepeculiarJSPbehaviorofcausingadisorganizedHTMLoutputwasworkedaroundinJSF1.2whichwasreleasedonlytwoyearslaterin2006,andthemissingcomponenttorepresentanHTML<div>wassolvedbygivingthe<h:panelGroup>componentanewlayout="block"attributesothatitrendersa<div>insteadof<span>.So,already,sinceJSF1.2,peoplecansafelymixplainHTMLwithJSFcomponentsinaJSPpageandcontinueusingdivsinbothplainHTMLandJSFways.However,therecommendedplantoavoidplainHTMLwhileauthoringJSF1.0/1.1pagesturnedintoapersistentmythwhichiseventodaystillaliveamongsomepeople.
AnotherproblemwithJSPisthatexistingJSPtaglibssuchasJSTL(JSPStandardTagLibrary)andexistingJSP
expressionsintheformof${…}didn’tintegrateatallintotheJSFlifecycleandthereforemixingexistingJSPtaglibsandJSPexpressionswithJSFcomponentsinaJSPpagewouldresultinconfusingandunintuitivebehavior.Peoplecouldn’tuse<c:forEach>torenderalistofJSFcomponentsasthosecomponentswouldn’tseethevariabledeclaredby<c:forEachvar>.TherewasnodedicatedJSFcomponenttoloopoveralistotherthan<h:dataTable>andultimatelypeoplearestuckwithtableswhilecreatinglistsintheJSFpage.
Finally,JSPalsooffersverylimitedtemplatingcapabilitieswithactuallyonlyone“templating”tag,<jsp:include>;therefore,templatingwithJSPwouldrequireafairlycomplicatedapproachofcreatingabunchofcustomtagsforeverydefinitionofareusabletemplatesection. ThiscontradictsthephilosophyofJSF,tohavereusablecomponentstominimizecodeduplication.WithJSP,youwouldendupduplicatingJSFcomponentsthemselves.ExistingtemplatingframeworkssuchasTilesandThymeleafareeitherJSPcentricordon’tsupportJSFatallandthuscannotbeused.
Therewasclearlyastrongneedforanew-JSForientedviewtechnologywhichshouldsupplantJSPandsolveallofitsproblemswiththeJSFlifecycle.AndthenFaceletswasintroducedin2006.ItcouldbeinstalledseparatelyforJSF1.1and1.2,anditshippedbuilt-inwithJSF2.0.ItbecamethedefaultviewtechnologyforJSFandJSPwasdeprecatedasviewtechnologyforJSF.NewtagsintroducedinJSF2.0,suchas<f:ajax>,<h:head>,<h:body>,<h:outputScript>,and<h:outputStylesheet>,
2
areonlyavailableforFaceletsandnotforJSP.JSF2.0alsointroducedanewinterfacetomoreeasilyplugacustomviewdeclarationlanguage(VDL)asalternativetoJSPandevenFacelets,theViewDeclarationLanguageAPI.Thisway,onecouldcreate,forexample,apureJava-basedVDLforJSF.
XHTMLTheFaceletsVDLspecifiesthattheviewsaredefinedinXML-basedfileswhicharecompiledusingaSAXparserandkeptaroundinmemory.Thismemorycacheis,sinceJSF2.1,configurableusingacustomFaceletCacheFactory.WhentheJSFprojectstageissettoDevelopment,theSAX-compiledrepresentationsofFaceletsfilesarebydefaultnotcached.ThisallowseasierdevelopmentagainstanalreadyrunningserverbyjusteditingtheFaceletsfilesfrominsideanIDE(integrateddevelopmentenvironment)whichsupportshot-publishingthelocalchangestothetargetruntime.
TheFaceletsfilesthemselvesusuallyusethe.xhtmlextensionandthereforestartersoftencallthem“XHTML”insteadof“Facelets.”ThisisokaywhentalkinginthecontextofJSF,buttheterm“XHTML”hasanothersideinthewebdevelopmentworld.Atitscore,XHTMLisamarkuplanguageforHTMLpageswhichneedtobecompiledusinganXML-basedtool.Inotherwords,developersbasicallycreateXMLfileswithHTMLmarkupmixedwithwebframework-specificXMLtags,afterwhichthewebframeworkwillparsethemintoanXMLtree,generatesomewebframework-specificrepresentationoftheXMLtree(whichisinJSFtheUIViewRoot),andultimatelygeneratethedesiredHTML
3
outputbasedontheframework’sinternalrepresentationoftheXMLtree.
ButaroundthetimeofFacelets’introductionin2006,XHTMLwasbeingoverhypedbyanothergroupofwebdeveloperswhowerebasicallydisappointedbyseeingtheW3validatorinvalidatetheirHTML4documents—generallybecauseofthedesiretoexplicitlyclosealltagsforconsistency,includingthosewhichshouldactuallynotbeclosedasperHTML4specification,suchas<link>,<meta>,<br>,and<hr>—andsometimesbecauseofthedesiretospecifycustomtagattributesinordertohavesomeJavaScriptplug-instointeractmorecleanlywiththeHTMLdocument,whichisalsodisallowedbytheHTML4specification.
Eventhoughbasicallyeverysinglewebbrowserintheworldlenientlyacceptedthat,includingJurassicIE6,thosedevelopersdidn’twanttoseetheircarefullycraftedHTML4documentsbeinginvalidatedbytheW3validatorandchangedtheirHTMLdoctypedeclarationtousetheXHTMLDTD,anextensiontoHTMLwhichrequireseverytagtobeclosedandallowscustomattributestobespecifiedonexistingtags.However,thisisessentiallyabuseofXHTMLastheydidn’tatallactuallyuseanyXMLtooltocompilethedocumentandgeneratethedesiredHTMLoutput.ItwasmerelytokeeptheW3validatorhappy.
Coincidentally,alsoaroundthattime,HTML5wasjuststartedindraft.Essentially,developerscouldjuststripoutanyDTDfromtheHTMLdoctypedeclarationinordertogetanHTMLdocumentwhichallowsXML-basedsyntaxwhereinalltagsarealwaysclosedandcustomelementsandattributesare
allowedand,importantly,validatedcorrectlyintheW3validator.Inotherwords,<!DOCTYPEhtml>wasbeensufficientforthosedevelopers,eveninIE6.Unfortunately,ittookagesbeforeHTML5wasofficiallyfinished,sodeveloperskeptabusingtheXHTMLdoctypebeforetheycouldswitchbacktotheHTMLdoctype.WhentalkingaboutFaceletstothosegroupofdevelopers,don’tcallit“XHTML”butjust“Facelets”;otherwiseitwillgenerateconfusion.
TemplateCompositionsFaceletsprovidestagsforeasilycreatingtemplatecompositionsbasedonasinglemastertemplatefile.Thisshouldreducecodeduplicationforsite-widesectionswhicharerepeatedacrossallwebpages,suchasheader,navigationmenu,andfooter.Themastertemplatefileshouldrepresentafull-blownwebpagelayoutwithallsite-widesectionsanduse<ui:insert>tagstorepresentplaceswherethepage-specificsectionscanbeinserted.Followingisabasicexampleofsuchamastertemplatefile,WEB-INFtemplates/layout.xhtml:<!DOCTYPEhtml><htmllang="en"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
>
<h:head>
<title>#{title}</title>
</h:head>
<h:body>
<header>
<ui:includesrc="WEB-
INFincludes/layout/header.xhtml"/>
</header>
<main>
<ui:insertname="content"/>
</main>
<footer>
<ui:includesrc="WEB-
INFincludes/layout/footer.xhtml"/>
</footer>
</h:body>
</html>
Notethatthemastertemplatefileandincludefilesareexplicitlyplacedinthe/WEB-INFfolder.ThisisdoneinordertopreventdirectaccessbyuserswhoareguessingthepathinURL.AlsonotethatthepagetitleisdeclaredasasimpleEL(ExpressionLanguage)expression#{title}insteadof<ui:insert>.Itisnotallowedtohaveanymarkupinthe<title>element.
Thexmlnsattributebasicallydefines,viaaURI(uniformresourceidentifier),whichtagscanbeusedinthedeclaredXMLnamespace.TherootXMLnamespacespecifiestheURIoftheW3XHTMLstandardhttp://www.w3.org/1999/xhtmlwhichthusdefines“anyXHTMLandHTML5+tag,”suchas<html>,<title>,<header>,<main>,and<footer>inaboveexample.TheFaceletscompilerisawareofthisstandardXMLnamespaceandwillpassthroughallelementsas“generalUIinstructions.”The“h”XMLnamespacespecifiestheJSFHTMLtaglibURIandthe“ui”XMLnamespacespecifiesthe
JSFFaceletstaglibURI,whicharebothpresentintheJSFimplementationJARfileandregisteredtoFaceletsduringthewebapplication’sstartup.TheFaceletscompilercanthiswayfindtheassociatedtaghandlers,components,andcompositecomponents,whichinturndothehardworkofbuildingtheview,decodingtheHTTPrequest,andencodingtheHTTPresponse.ThoseURIsarethusnotperdefinitionliveInternetaddresses.Youcanevenspecifyyourownviaa*.taglib.xmlfile.Thiswillbeexpandedlaterinthesection“TagFiles.”
FollowingiswhattheincludefileWEB-INFincludes/layout/header.xhtmllookslike:<ui:composition
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
>
<ahref="#{request.contextPath}/">
<h:graphicImagename="images/layout/logo.svg"/>
</a>
<nav>
<ul>
<li><h:linkoutcome="about"value="About">
</li>
<li><h:linkoutcome="help"value="Help">
</li>
<li><h:linkoutcome="contact"
value="Contact"></li>
</ul>
</nav>
</ui:composition>
Notethelinkaroundthelogo.Itpointsto#{request.contextPath}/.Thisbasicallyprintsthedomain-relativeURLtotheapplication’sroot.#{request}isanimplicitELobjectreferringthecurrentHttpServletRequestinstance.contextPathreferstooneofitsproperties,impliedbythegetContextPath()method.
FollowingiswhattheincludefileWEB-INFincludes/layout/footer.xhtmllookslike:<ui:composition
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
>
<nav>
<ul>
<li><h:linkoutcome="/terms-of-service"
value="TermsofService"><li>
<li><h:linkoutcome="/privacy-policy"
value="PrivacyPolicy"><li>
<li><h:linkoutcome="/cookie-policy"
value="CookiePolicy"><li>
</ul>
</nav>
<small>©ExampleCompany</small>
</ui:composition>
Finally,here’showthetemplateclientcanlook,e.g.,/home.xhtml:<ui:compositiontemplate="WEB-
INFtemplates/layout.xhtml"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
>
<ui:paramname="title"value="Welcome!"/>
<ui:definename="content">
<h1>WelcometoExampleCompany!</h1>
<p>Loremipsumdolorsitamet.</p>
</ui:define>
</ui:composition>
Notethetemplateattributeofthe<ui:composition>.Thismustrepresenttheserver-sidepathtothemastertemplate,preferablyasanabsolutepath,thusstartingwith“/”.
Inatemplateclient,<ui:param>letsyoudefineasimpleparameterspecificforthemastertemplate.Basically,youcanuseanyparametername,aslongasitissupportedbythemastertemplateanddoesn’tclashwithanexistingmanagedbeanname.Inthisspecificcase,thevaluefortheELvariable#{title}isspecifiedas“Welcome!”.Thiswillultimatelyendupinsidethe<title>elementofthemastertemplate.
Andthe<ui:define>letsyoudefineablockofmarkupspecificforthemastertemplate.Itwillultimatelyendupintheplacewherethe<ui:insert>withexactlythesamenameisdeclaredinthemastertemplate.Figure7-1givesaclearoverviewofhowthisallfitstogether.
Figure7-1 Therelationshipbetweenthemastertemplatelayout.xhtml,includefilesheader.xhtml,andfooter.xhtml,andthetemplateclienthome.xhtml.Notethattemplatefilepathsandsometagattributesareomittedforbrevity.Referthepreviouslyshowncodesnippetsfortheactualcoding.
Finally,opening/home.xhtmlshouldproducethefinalHTMLoutputwhichyoucaninspectbyright-clickingViewpagesourceintheaveragewebbrowser.
SinglePageApplicationArecenttrendistheso-calledSinglePageApplication(SPA).Thisconceptisonitsownnotsonew;infact,itisolderthanJSFitself,butitwasheavilypopularizedduring“web2.0”withJavaScript-basedframeworkssuchasAngular.Basically,anSPAletsthewebapplicationbehavelikeadesktop-orientedapplicationbydynamicallychangingthemaincontentwithanAjaxrequestwhennavigatingtoadifferentpageinsteadofloadingtheentirepageviaaGETrequest.GmailisonesuchknownexampleofanSPA.
SuchanSPAisalsoachievablewithJSFbysimplyusing
<ui:include>whosesrcattributeisdynamicallyupdatedbyAjax.Followingisanexampleutilizingthesamemastertemplateasshownintheprevioussection,/spa.xhtml:<ui:compositiontemplate="WEB-INFtemplates/layout.xhtml"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:jsf="http://xmlns.jcp.org/jsf"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
>
<ui:paramname="title"value="SinglePage
Application"/>
<ui:definename="content">
<aside>
<nav>
<h:form>
<f:ajaxrender=":content">
<ul>
<li><h:commandLink
value="page1"
action="#
{spa.set('page1')}"><li>
<li><h:commandLink
value="page2"
action="#
{spa.set('page2')}"><li>
<li><h:commandLink
value="page3"
action="#
{spa.set('page3')}"><li>
</ul>
</f:ajax>
</h:form>
</nav>
</aside>
<articlejsf:id="content"data-page="#
{spa.page}">
<ui:includesrc="WEB-INFincludes/spa/#
{spa.page}.xhtml"/>
</article>
</ui:define>
</ui:composition>
Donotethatthe<article>elementisdeclaredasaso-calledpassthroughelementbyexplicitlyspecifyingaJSFidentifierviajsf:id="…".ThisfeaturewasintroducedinJSF2.2.Underthehood,whendeclaringanHTMLelementwhichhasnoJSFcomponentequivalent,suchas<header>,<footer>,<main>,<article>,<section>,etc.asapassthroughelementthisway,itisturnedintoaUIPanelcomponentandistreatedintheJSFcomponenttreeexactlylike<h:panelGroup>.ThiswayyoucancleanlykeepusingsemanticHTML5markupwhilestillbeingabletoreferenceitasifitwereaJSFcomponentandthusbeabletoAjax-updateit.
Asyoumighthavedecipheredintheabove/spa.xhtmlexample,there’sasidenavigationmenuwhichsetsthecurrentpageinthemanagedbeanidentifiedby#{spa}andAjax-updatesthecomponentidentifiedbyid="content"whichinturncontainsadynamicinclude.Theaboveexample
exceptsthefollowingincludefilestobepresentintheWEB-INFincludes/spafolder:page1.xhtml,page2.xhtml,andpage3.xhtml.Eachofthemisasimpleincludefilewhichlooksasfollows:<ui:compositionxmlns:="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
>
<h1>Firstpage</h1>
<p>Loremipsumdolorsitamet.</p>
</ui:composition>
Thebackingbeanassociatedwiththe#{spa}managedbeanisfairlysimple;itlooksasfollows:@Named@ViewScopedpublicclassSpaimplementsSerializable{
privateStringpage;
@PostConstruct
publicvoidinit(){
page="page1";
}
publicvoidset(Stringpage){
this.page=page;
}
publicStringgetPage(){
returnpage;
}
}
[email protected],theusermightfaceanerrorpagewiththemessage“Invalidpath:WEB-INFincludes/spa/.xhtml”.
Notethatthebackingbeanisdeclared@ViewScoped.Thisisimportantinordertorememberacrosspostbackswhichpageiscurrentlybeingopened.Ifitwere@RequestScoped,andtheusernavigatesto,e.g.,page2andsubmitsaformtherein,whichcreatesanewHTTPrequest,thenthe@RequestScopedmanagedbeanwouldberecreatedagain,withpage1aspagevalueandthusnotpage2.Thishastheconsequencethat<ui:include>won’treferencepage2.xhtmlwhenJSFisabouttodecodeanyinputcomponentsinordertoprocesstheformsubmitduringthepostbackrequest,andthereforeJSFwouldfailtofindtheinputcomponentsdeclaredinpage2.xhtml.A@ViewScopedbeanlivesaslongastheuserpostbackstotheverysameview,inthiscase/spa.xhtml,andthereforecorrectlyremembersthecurrentlyselectedpage.
WhenplayingaroundwiththisSPAexample,youmighthavenoticedonedisadvantage:thepagesarenotbookmarkable.ThisiscausedbythefactthatthepagesarenotopenedbyanidempotentGETrequest.YoucansolvethatbyutilizingtheHTML5history.pushStateAPI.Basically,oncompletionoftheAjaxrequestyoushouldpushtheintendedURLtothebrowserhistory,whichwillbereflectedinthebrowser’saddressbar.And,youshouldmodifytheSpabackingbeantocheckifanyspecificpagehasbeenopenedandthenpreparethepagevariableaccordingly.
Followingisakickoffexamplewhichjustappendsthe?
4
page=xxxquerystringparameter.Firstadjustthe<f:ajax>ofthespa.xhtmltospecifytheoneventattributeasfollows:<f:ajax...onevent="pageChangeListener">
AndcreatethefollowingJavaScriptfunction:functionpageChangeListener(event){
if(event.status=="success"){
varpage=
document.getElementById("content").dataset.page;
varurl=location.pathname+"?page="+page;
history.pushState(null,document.title,url);
}
}
And,finally,adjusttheSpabackingbeanasfollows:@Inject@ManagedProperty("#{param.page}")
PrivateStringpage;
publicvoidinit(){
if(page==null){
page="page1";
}
}
Note
The@ManagedPropertyiscurrentlyavailableintwoflavors:thedeprecatedonefromthejavax.faces.beanpackageandtheJSF2.3introducedonefromthejavax.faces.annotationpackage.
Youneedthelatterone.Also,notethatyoumightwanttovalidatetheprovidedpageparameter.Pathprobingbyhackersisinnocent,bytheway,asJSFalreadydoesn’tallowtraversingintotheparentpathasin/spa.xhtml?page=../../templates/layout.
TemplateDecorationsIncaseyouwouldliketohaveareusableincludefilewhichiscapableofinsertingtemplatedefinitionsasifyouwouldbeusing<ui:include>toreferenceanincludefilewithoneormore<ui:insert>sections,thenyoucanuse<ui:decorate>.Followingisonesuchexample,theWEB-INFdecorations/contact.xhtml:<ui:compositionxmlns:="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
>
<sectionclass="contact">
<header><ui:insert><header>
<nav>
<ul>
<li>✆<ahref="tel:+31612345678"
title="Phone">+31(0)612345678</a>
</li>
<li>✉<ahref="mailto:[email protected]"
title="Email">[email protected]</a>
</li>
</ul>
</nav>
</section>
</ui:composition>
Hereishowitcanbeused,youcanputthe<ui:decorate>anywhereinyourtemplateclient,asa<ui:include>:<ui:decoratetemplate="WEB-
INFdecorations/contact.xhtml">
<h2>Questions?Contactus!</h2>
</ui:decorate>
Notethatthecontact.xhtmlhasonlyone<ui:insert>andthatithasnoname.Thiswillinserttheentire<ui:decorate>tagbodyatthedeclaredplaceof<ui:insert>.Youcan,ofcourse,specifyaname,butthenyouwouldneedtoexplicitlyspecify<ui:define>withanameforthat.Thatwouldbeonlyusefulifyouhavemorethanoneinsertsections.
Youcan,ifnecessary,use<ui:param>topassparameters.Thisworksthesamewayaswith<ui:compositiontemplate>.Thefollowingexampleparameterizesthee-mailusernameWEB-INFdecorations/contact.xhtmlwithadefaultvalueof“info.”
...
<li>✉<ahref="mailto:#{emptymailto?'info':
mailto}@example.com"
title="Email">#{emptymailto?'info':
mailto}@example.com</a></li>
...
Thiscanthenbeusedasfollows:
<ui:decoratetemplate="WEB-INFdecorations/contact.xhtml">
<ui:paramname="mailto"value="press"/>
<h3>Contactus</h3>
<p>
Forpressinquiriesyoucancontactusbythebelow
phonenumberandemailaddress.
</p>
</ui:decorate>
TagFilesAswith<ui:compositiontemplate>and<ui:decorate>,youcanalsouse<ui:param>in<ui:include>.However,watchoutthatyoudon’tgooverboard.
<ui:includesrc="WEB-INFincludes/field.xhtml">
<ui:paramname="id"value="firstName"/>
<ui:paramname="label"value="FirstName"/>
<ui:paramname="value"value="#{profile.user.firstName}"
/>
</ui:include>
WhereintheWEB-INFincludes/field.xhtmllookssomethinglikethefollowing:<ui:compositionxmlns:="http://www.w3.org/1999/xhtml"
xmlns:jsf="http://xmlns.jcp.org/jsf"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:a="http://xmlns.jcp.org/jsf/passthrough"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
>
<divclass="field"jsf:rendered="#{renderedne
false}">
<h:outputLabelid="#{id}_l"for="#{id}"value="#
{label}"/>
<c:choose>
<c:whentest="#{typeeq'password'}">
<h:inputSecretid="#{id}"label="#
{label}"
value="#{value}">
</h:inputSecret>
</c:when>
<c:whentest="#{typeeq'textarea'}">
<h:inputTextareaid="#{id}"label="#
{label}"
value="#{value}">
</h:inputTextarea>
</c:when>
<!--Moretypescanbeaddedasc:whenhere
-->
<c:otherwise>
<h:inputTextid="#{id}"label="#{label}"
value="#{value}"a:type="#{type}">
</h:inputText>
</c:otherwise>
</c:choose>
<h:messagesid="#{id}_m"for="#{id}"
styleClass="messages"/>
</div>
</ui:composition>
Insuchacase,youwouldprefertohavesomethingmoreconcise,likethefollowing,instead:<t:fieldid="firstName"label="FirstName"
value="#{profile.user.firstName}">
</t:field>
Having<ui:include>withtwoormore<ui:param>isastrongsignthattheincludefilecanbetterberegisteredasatagfilesothatitcanbeusedwithlessboilerplatecodeintheFacelet.
Firstmovetheincludefileintoadifferentsubfolder,WEB-INFtags/field.xhtml.Thisisnotatechnicalrequirement.Itwillworkjustfinewhereveryouputit,butwejustwanttoorganizethefilesclearly.MastertemplatefilesgoinWEB-INFtemplates,includefilesgothereinWEB-INFincludes,decoratefilesgoinWEB-INFdecorations,andtagfilesgoinWEB-INFtags.
Then,createthefollowingWEB-INFexample.taglib.xml:<?xmlversion="1.0"encoding="UTF-8"?>
<facelettaglib
xmlns:="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/java
ee
http://xmlns.jcp.org/xml/ns/javaee/web-
facelettaglibrary_2_3.xsd"
version="2.3"
>
<namespace>http://example.com/tags</namespace>
<short-name>t</short-name>
<tag>
<description>Renderslabel+input+message
field.</description>
<tag-name>field</tag-name>
<source>tags/field.xhtml</source>
<attribute>
<description>Thetypeoftheinput
component.</description>
<name>type</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description>TheIDoftheinputcomponent.
</description>
<name>id</name>
<required>true</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description>Thelabeloftheinput
component.</description>
<name>label</name>
<required>true</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description>Thevalueoftheinput
component.</description>
<name>value</name>
<required>false</required>
<type>java.lang.Object</type>
</attribute>
<attribute>
<description>Whetherthefieldisrendered.
</description>
<name>rendered</name>
<required>false</required>
<type>boolean</type>
</attribute>
</tag>
</facelettaglib>
That’sadmittedlyquitesomeboilerplatecode.Itisgoodtoknowthatthe<attribute>elementsaren’tmandatoryforthetechnicalfunctioningofthetagfile.Youcouldevenomitthemaltogether.ButthentheIDEwon’tbeabletoloadthemintoautosuggestboxeswhileattemptingtoautocompletethecustomtag.Thisisnotreallydeveloper-friendly.Soyou’dbetterkeepthemin.The<required>propertyofthetagattribute,bytheway,onlyresultsinaruntimeerrorwhentheJSFprojectstageissettoDevelopment.InotherJSFprojectstages,it’signored.AndtheaverageIDEwillimmediatelypromptthoserequiredattributesduringautocompletingthetag.
Thefilename,example.taglib.xml,isfreetoyourchoice.InorderforJSFtoautomaticallypickupataglibfileduringtheapplication’sstartup,thereareonlytworequirements:itmusthavea.taglib.xmlextensionanditmustbeplacedin/WEB-INFfolder(orincaseofaJARfilewhichendsupinWEB-INFlib,thenin/META-INFfolderofthatJARfile).Unfortunately,placingthefilein/WEB-INFdoesn’talwaysworkquitewellinsomeservers,suchasGlassFish/Payara.Inthatcase,you’dhavetoexplicitlyregisteritviathefollowingcontextparameterinweb.xml
whosevaluerepresentsthefullpathtothe*.taglib.xmlfilefromthewebrooton.
<context-param>
<param-name>javax.faces.FACELETS_LIBRARIES</param-name>
<param-value>WEB-INFexample.taglib.xml</param-value>
</context-param>
Inordertouseanytagdefinedinthe*.taglib.xml,youfirsthavetodeclareexactlythe<namespace>URIofthetaglibinyourFacelet,alongwithanarbitraryXMLnamespaceprefix.Forbettermaintainabilityofthecodeit’srecommendedtopickthetaglib’spreferredXMLnamespaceprefixasspecifiedinits<short-name>,whichis“t”incaseofourexample.taglib.xml.
<ui:compositiontemplate="WEB-INFtemplates/layout.xhtml"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:t="http://example.com/tags"
>
<ui:paramname="title"value="LogIn"/>
<ui:definename="content">
<h:form>
<fieldset>
<header>
<h1>LogIn</h1>
</header>
<t:fieldtype="email"id="email"
label="Email"
value="#{login.email}">
</t:field>
<t:fieldtype="password"id="password"
label="Password"
value="#{login.password}">
</t:field>
<footer>
<t:buttonid="submit"label="LogIn"
action="#{login.submit()}">
</t:button>
</footer>
</fieldset>
</h:form>
</ui:define>
</ui:composition>
Donotethatthetype="email"ofthee-mailfieldthusendsupin<c:otherwise>ofthetagfileimplementationwhereinitgetspassedthroughthea:type="#{type}"attributeof<h:inputText>.ThisallowsyoutoeasilyuseHTML5inputfields,suchastype="email",type="number",type="tel",etc.Thetypeattributebeingdefinedasapassthroughattributea:typeismandatory,becausethe<h:inputText>bydefaultignoresanycustomtypeattributeandstubbornlyrenderstype="text".
Youmightalsohavenoticedanothercustomtag,<t:button>.Here’showitisimplementedinWEB-INFtags/button.xhtml.
<ui:composition
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:jsf="http://xmlns.jcp.org/jsf"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
>
<divclass="button"jsf:rendered="#{renderednefalse}">
<h:commandButtonid="#{id}"value="#{label}">
<f:actionListenerbinding="#{action}"/>
<f:ajaxexecute="@form"render="@form"/>
</h:commandButton>
<h:messagesid="#{id}_m"globalOnly="true"
redisplay="false"/>
</div>
</ui:composition>
It’sregisteredinexample.taglib.xmlnearlythesamewayas<t:field>,withoneexceptionfortheactionattribute.Technically,youneedtospecifya<method-signature>insteadofa(property)<type>:<attribute><description>
Actionmethodofthebutton.
NOTE:mustincludemethodparenthesis.
</description>
<name>action</name>
<required>true</required>
<method-signature>voidaction()</method-signature>
</attribute>
Youmighthavenoticedthattheactualtagimplementationuses<f:actionListenerbinding="#{action}">insteadofaction="#{action}".Thisisactuallyanecessarytrickinordertogetittoproperlyinvokethemethod.Thatis,the<method-signature>wasinitiallyintendedforUIcomponents,notfortagfiles.It’signoredintagfiles.ThismaybeworkedonforJSF.next.Fornow,youcangetawaywiththe<f:actionListenerbinding>trick.Thishasonlyoneadditionalrequirement:youneedtoexplicitlyincludethemethodparenthesisinthetagfileclientasin<t:buttonaction="#{login.submit()}">.
<t:buttonaction="{login.submit}">willotherwisefailwithjavax.el.PropertyNotFoundException:Theclass‘com.example.project.view.Login’doesnothavetheproperty‘submit’.
Incaseyouwouldliketocustomizetagfilesfromthetagfileclientsideon,e.g.,byaddingmorespecificinputattributes,ornestingcoretags,orbyprependingorappendingcontenttothelabelormessage,thenyoucanuse<ui:define>and<ui:insert>thesamewayyou’reusedtodoingwithmastertemplatefilesanddecoratefiles.ThefollowingexampledemonstrateshowyoucanenhancetheWEB-INFtags/field.xhtmlonthiswithabunchofnew<ui:insert>tags:<ui:compositionxmlns:="http://www.w3.org/1999/xhtml"
xmlns:jsf="http://xmlns.jcp.org/jsf"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:a="http://xmlns.jcp.org/jsf/passthrough"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
>
<divclass="field"jsf:rendered="#{renderedne
false}">
<ui:insertname="beforeLabel"/>
<ui:insertname="label">
<h:outputLabelid="#{id}_l"for="#{id}"
value="#{label}">
<span><ui:insertname="insideLabel">
<span>
</h:outputLabel>
</ui:insert>
<ui:insertname="beforeInput"/>
<ui:insertname="input">
<c:choose>
<c:whentest="#{typeeq'password'}">
<h:inputSecretid="#{id}"label="#
{label}"
value="#{value}">
<ui:insert/>
</h:inputSecret>
</c:when>
<c:whentest="#{typeeq'textarea'}">
<h:inputTextareaid="#{id}"label="#
{label}"
value="#{value}">
<ui:insert/>
</h:inputTextarea>
</c:when>
<!--Moretypescanbeaddedasc:when
here-->
<c:otherwise>
<h:inputTextid="#{id}"label="#
{label}"
value="#{value}"a:type="#
{type}">
<ui:insert/>
</h:inputText>
</c:otherwise>
</c:choose>
</ui:insert>
<ui:insertname="beforeMessages"/>
<ui:insertname="messages">
<h:messagesid="#{id}_m"for="#{id}"
styleClass="messages"/>
</ui:insert>
</div>
</ui:composition>
Now,that’salotofflexibility!Inthetagfileclientyoucanuse<ui:definename="beforeLabel">todefinesomecontentwhichshouldappearbeforethelabelofthefield.Andyoucanuse<ui:definename="label">tooverridethelabelaltogether.Andyoucanuse<ui:definename="insideLabel">toappendsome(HTML)contentinsidethelabel,andsoforth.ThefollowingexampledemonstrateshowinsideLabelcanbeusedtoappenda“Forgotpassword?”linktothelabelofthepasswordfield:<t:field...>
<ui:definename="insideLabel">
<h:linkoutcome="reset-password"value="Forgot
password?">
</ui:define>
</t:field>
Notethat<ui:insertname="insideLabel">iswrappedinanHTML<span>.Thisallowsyoutomoreeasilyselect“anything”thatendsupinthereviaCSS,sothatyoucan,forexample,letitfloattotherightusingjust.fieldlabel>span{float:right;}.
Anythingelseinthetagfileclientwhichisnot<ui:define>willendupinsidethenameless<ui:insert>tagnestedinthechoseninputcomponent.Thisallowsyoutoeasilynestany<f:xxx>coretag
specificallyfortheinputcomponent:<t:field...><f:attributename="onkeypress"value="return
event.key!='Enter'"/>
<f:validateRegexpattern="[0-9]{4}"/>
<f:ajaxrender="otherField"/>
</t:field>
ThisspecificexamplepreventstheformfromsubmittingwhentheEnterkeyispressedbyreturningfalsewhentheKeyBoardEvent.keyequals“Enter”,andregistersaregexvalidatortoacceptonlyavalueoffourdigitsbymatchingagainstaregularexpressionpatternof“[0-9]{4}”,andinstructsJSFtoupdatethecomponentidentifiedby“otherField”byAjaxwhenthevaluechangeeventhasoccurred.
ComingbacktothetagfileimplementationinWEB-INFtags/field.xhtml,youmighthavenoticedthatgoodoldJSTLisbeingusedthereinsteadofJSF’sownrenderedattribute.ThishasadvantagesasJSTLhasadifferentlifecyclethanJSFUIcomponents.JSTLisexecutedwhentheJSFcomponenttreeisabouttobebuilt,duringtheviewbuildtime.JSFcomponentsareexecutedwhenHTMLoutputisabouttobegenerated,duringtheviewrendertime.Moreover,ifyouwereusingJSF’sownrenderedattribute,thenyouwouldface“duplicatecomponentID”errorsbecauseofmultiplecomponentswiththesameIDphysicallyendingupintheJSFcomponenttree.
MightithappenthatyouareconsideringtheuseofplainJavacodetodynamicallycreatethecomponenttreebasedonatleastaview-scopedmodel,youshouldabsolutelyreconsiderusingJSTLinstead.AsJSTLitselfisalsoXML-basedandyou
canthusjustputtogethereverythinginanXHTMLfile,youwillendupwithmuchbetterreadableandmaintainablecodefora“dynamic”component.
CompositeComponentsSometimes,youwouldliketohaveagroupofrelatedinputcomponentstorepresentasinglemodelvalue.Aclassicexampleishavingthree<h:selectOneMenu>drop-downsrepresentingday,month,andyearwhichareultimatelyboundtoasinglejava.time.LocalDatepropertyinthebackingbean.Thisisnottrivialtoimplementwithjustanincludefileoratagfile.YouwouldneedsomeadditionalJava-basedlogicwhichmakessurethat,e.g.,thedaydrop-downdoesn’tshowthevalues29,30,or31dependingonthecurrentlyselectedmonth,andthatitconvertsthesubmittedvaluestoafull-fledgedLocalDateinstance,andviceversa.Butyoucan’tandshouldn’tputanyJavacodeinanyFacelet.
You’llperhapsthinkofjustcreatingadedicatedbackingbeanforthiscase.Butthisisnotsufficienteither.Itdoesn’tallowyoutocleanlyhookonthecomponent’slifecyclethroughtheJSFphases:collectingtheindividualsubmittedvaluesfrommultiplecomponents,convertingthemintoasingleLocalDateinstance,ifnecessarythrowingaconverterexceptionduringthevalidationsphase,andlettingtheJSFlifecycleautomaticallyskiptheremainingphases.Abackingbean’ssettermethodoractionmethodisfarfromtherightplaceforthatlogic.Itwouldbeinvokedtoolateanyway.And,itwouldfeelstrangetobeabletoreferenceandpotentiallymanipulatetheverysamebackingbeanviaanELexpressiononanarbitraryplaceintheFacelet.
Thisisexactlywherecompositecomponentscomeintothepicture:composingabunchofexistingJSFcomponentsintovirtuallyasinglecomponenttiedtoasinglemodelvalueandultimatelyusingitexactlythesamewayasyouwouldbeusingaplain<h:inputText>.Imaginea<t:inputLocalTime>compositecomponentcomposedoftwo<h:selectOneMenu>componentstiedtoasinglejava.time.LocalTimemodelvalue.Insteadofabackingbean,youcanuseafull-fledgedUIComponentinstanceasaso-calledbackingcomponent.
Firstcreateadedicatedsubfolderinmain/webapp/resourcesfolder(andthusnotmain/java/resources!),forexample,main/webapp/resources/components.ThereyoucanputFaceletsfilesrepresentingcompositecomponents.ThesubfolderisthentobeusedintheXMLnamespaceURIafterhttp://xmlns.jcp.org/jsf/compositeasshownnext.
xmlns:t="http://xmlns.jcp.org/jsf/composite/components"
NotethattheXMLnamespaceprefixof“t”clasheswithonewhichwealreadydefinedbeforefortagfiles.Thisisofcoursenottheintent.YoumaychooseadifferentXMLnamespaceforcompositecomponents.It’s,however,alsopossibletoletthemsharethesamecustomXMLnamespaceURIhttp://example.com.tags.Thiscanbeachievedbyaddinga<composite-library-name>tothe*.taglib.xmlwhichinturnmustrepresentthenameofthededicatedsubfolder.
<composite-library-name>components</composite-library-name>
ThiswayallcompositecomponentsarealsoavailablebythesameXMLnamespaceastagfiles.
<...xmlns:t="http://example.com/tags">
...
<t:inputLocalTime.../>
ThefilenameoftheFaceletsfilerepresentingthecompositecomponentwillbecomethetagname.So,inordertohavea<t:inputLocalTime>,weneedaninputLocalTime.xhtmlfileinthemain/webapp/resources/componentsfolder.Followingisakickoffexampleofwhatitcanlooklike:<ui:component
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
>
<cc:interfacecomponentType="inputLocalTime">
<cc:attributename="value"
type="java.time.LocalTime"
shortDescription="Selectedtime.Defaultsto
00:00.">
</cc:attribute>
<cc:attributename="required"type="boolean"
shortDescription="Requiredstate.Defaults
tofalse.">
</cc:attribute>
</cc:interface>
<cc:implementation>
<spanid="#{cc.clientId}"
class="inputLocalTime">
<h:selectOneMenuid="hour"binding="#
{cc.hour}"
required="#{cc.attrs.required}">
<f:selectItemitemValue="#{null}"/>
<f:selectItemsvalue="#{cc.hours}"/>
</h:selectOneMenu>
:
<h:selectOneMenuid="minute"binding="#
{cc.minute}"
required="#{cc.attrs.required}">
<f:selectItemitemValue="#{null}"/>
<f:selectItemsvalue="#{cc.minutes}"/>
</h:selectOneMenu>
</span>
</cc:implementation>
</ui:component>
Thereareseveralthingstonoticeherewhichmakesacompositecomponentdifferentfromatagfile.First,thecompositecomponent’sbodyisalwaysdividedintotwosections:aninterfaceandanimplementation.
TheinterfacedeclaresacomponentTypeattributewhichshouldreferencethevalueofeitherthe@FacesComponentannotationonaUIComponentsubclassorthe<component-type>entryofa<component>asdeclaredineitherfaces-config.xml
or*taglib.xml.WhenthecomponentTypeattributeisabsent,itdefaultstoUINamingContainer.Theinterfacealsodeclaresthesupportedattributes.Tokeepitsimple,werestricttoonlytwo:valueandrequired.TherearealsoimplicitlyinheritedattributesfromtheUIComponentsuperclass,whichwedon’tneedtoexplicitlydefineas<cc:attribute>:id,binding,andrendered.Thatmakesitatotaloffiveattributeswhichyoucanuseintheimplementation.
Theimplementationdefinestheactualmarkupofthecompositecomponent.Thereyoucanfindthetwo<h:selectOneMenu>drop-downswrappedina<span>element.ThereyoucanalsofindseveraloccurrencesofthespecialELvariable#{cc}whichreferstothecurrentUIComponentinstancebehindthecompositecomponent,whichisthusoneofthetypeasdeclaredinthecomponentTypeattribute,or,ifabsent,aUINamingContainer.#{cc.attrs}isashortcuttothecomponentattributemapasavailablebyUIComponent#getAttributes().#
{cc.attrs.required}asusedin<t:inputLocalTime>thusrefersto<cc:attributename="required">.
#{cc.clientId}inthe<span>elementjustprintsthecompositecomponent’sclientIDastheidattributeof<span>.Thisisactuallyatrickinordertobeabletoreferencethe“whole”compositecomponentusingaclientIDsearchexpressionfromthetemplateclienton.Imaginethefollowingcase:<h:inputText...><f:ajaxrender="time"/>
</h:inputText>
<t:inputLocalTimeid="time".../>
Thiscasewouldn’thaveworkedwithoutthe#{cc.clientId}beingrenderedasanIDofanyplainHTMLelementwhichwrapstheentirebodyof<cc:implementation>,usuallya<span>or<div>.Thetechnicalproblemis,whilethecompositecomponentitselfisfindableintheJSFcomponenttreebythecomponentIDsearchexpression,theHTMLrepresentationofthecompositecomponentisbydefaultnotavailablebydocument.getElementById(clientId)inJavaScript.Inotherwords,JSFAjaxwouldn’tbeabletoupdateit.ExplicitlyaddingaplainHTMLelementwiththecompositecomponent’sclientIDthussolvesthat.
Finally,thereareabunchof#{cc}expressionswhichdon’treferencetheattributesdirectly.Bothofthe<h:selectOneMenu>drop-downsaredirectlyboundaspropertiesoftheso-calledbackingcomponent,theconcreteUIComponentinstancebehindthecompositecomponent.And,both<f:selectItems>optionsobtaintheirvaluesdirectlyfromthebackingcomponentaswell.Here’sthebackingcomponentclass,com.example.project.composite.InputLocalTi
me.
@FacesComponent("inputLocalTime")
publicclassInputLocalTimeextendsUIInputimplements
NamingContainer{
privatestaticfinalList<String>HOURS=
IntStream.rangeClosed(0,23).boxed()
.map(InputLocalTime::pad).collect(Collectors.toLi
st());
privatestaticfinalList<String>MINUTES=
IntStream.rangeClosed(0,59).boxed()
.map(InputLocalTime::pad).collect(Collectors.toLi
st());
privateUIInputhour;
privateUIInputminute;
@Override
publicStringgetFamily(){
returnUINamingContainer.COMPONENT_FAMILY;
}
@Override
publicvoidencodeBegin(FacesContextcontext)throws
IOException{
LocalTimelocalTime=(LocalTime)getValue();
if(localTime!=null){
hour.setValue(pad(localTime.getHour()));
minute.setValue(pad(localTime.getMinute()));
}
super.encodeBegin(context);
}
@Override
publicObjectgetSubmittedValue(){
StringsubmittedHour=(String)
hour.getSubmittedValue();
StringsubmittedMinute=(String)
minute.getSubmittedValue();
if(submittedHour==null||submittedMinute==null)
{
returnnull;
}
elseif(submittedHour.isEmpty()||
submittedMinute.isEmpty()){
return"";
}
else{
returnsubmittedHour+":"+submittedMinute;
}
}
@Override
protectedObjectgetConvertedValue
(FacesContextcontext,ObjectsubmittedValue)
{
StringsubmittedTime=(String)submittedValue;
if(submittedTime==null||submittedTime.isEmpty())
{
returnnull;
}
try{
returnLocalTime.parse(submittedTime,
DateTimeFormatter.ISO_LOCAL_TIME);
}
catch(DateTimeParseExceptione){
thrownewConverterException(e);
}
}
privatestaticStringpad(Integervalue){
returnString.format("%02d",value);
}
publicUIInputgetHour(){returnhour;}
publicvoidsetHour(UIInputhour){this.hour=hour;}
publicUIInputgetMinute(){returnminute;}
publicvoidsetMinute(UIInputminute){this.minute=
minute;}
publicList<String>getHours(){returnHOURS;}
publicList<String>getMinutes(){returnMINUTES;}
}
Now,thatwasabitofcode.Notonlywillyouseethatthegettersandsettersarecollapsedforbrevity,butyou’llalsoseethatourcompositeextendsUIInputandimplementsNamingContainer.ExtendingfromUIInputgivesusthebenefitthatwedon’tneedtorepeatmostofthedefaultencodinganddecodingbehaviorofUIInputinourbackingcomponent,soweonlyneedtooverrideafewmethods.ImplementingNamingContainerisatechnicalrequirementof<cc:interface>.Thisenablesyoutousemultipleinstancesofthecompositecomponentinthesamecontextwithoutfacing“duplicatecomponentID”errors.ThisrequirementisalsoreflectedbytheoverriddengetFamily()method,whichmustasperthecompositecomponent’scontractreturntheUINamingContainer.COMPONENT_FAMILYconstant.
TheactualUIInputcomponentscomposingthecompositecomponentaredeclaredaspropertiesofthebackingcomponent.Inthiscasetheyareboth<h:selectOneMenu>drop-downswhichare,viathebindingattribute,tiedtothoseproperties.Thisenablesustoeasilysettheirvaluesduringencoding(read:processingtheHTTPresponse),andtoobtainthesubmittedvaluesduringdecoding(read:processingtheHTTPrequest).YoucanfindthelogicforthatintheoverriddenencodeBegin()andgetSubmittedValue()methods,respectively.
IntheencodeBegin()methodyouthushavetheopportunitytopreparethedisplayedvaluesbasedonthemodelvalue,ifany.ThegetValue()methodisinheritedfromtheUIInputsuperclassandistiedtothevalueattribute.Youcanbreakdownthemodelvalueandsetthe
desiredvaluesintheindividualUIInputcomponentsofthecompositecomponent.Thepad()helpermethodjustpadsthedigitwithaleadingzerosothat,e.g.,the“1”getsdisplayedas“01”.Thishelpermethodisalsousedduringstaticinitializationofthelistsofavailablehoursandminutesspecificallyfor<f:selectItems>.
InthegetSubmittedValue()methodyoushouldcomposethesubmittedvaluesoftheindividualUIInputcomponentstogethertoasingleString.Inthespecificcaseof<t:inputLocalTime>,wecomposeaStringfollowingtheISOlocaltimepatternHH:mm.Inturn,theUIInputsuperclasspassesthisvaluethroughthegetConvertedValue()whereinwethushavetheopportunitytoconvertthecomposedStringtotheconcretemodelvalue,whichisinourcasetheLocalTime.UltimatelytheUIInputsuperclasswillmakesurethatthisgetssetinthebackingbeanduringtheupdatemodelvaluesphase.Nowyoucanuseitasfollows:<ui:compositiontemplate="WEB-INFtemplates/layout.xhtml"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:cc="http://xmlns.jcp.org/jsf/composite/compone
nts"
>
<ui:definename="content">
<h:form>
<h:outputLabelfor="time:hour"value="Time"
/>
<t:inputLocalTimeid="time"value="#
{bean.time}"/>
<h:commandButtonvalue="Submit"action="#
{bean.submit}">
<f:ajaxexecute="@form"/>
</h:commandButton>
</h:form>
</ui:define>
</ui:composition>
wherethebackingbeanrepresentedby#{bean}looksasfollows:@Named@RequestScopedpublicclassBean{
privateLocalTimetime;
publicvoidsubmit(){
System.out.println("Submittedlocaltime:"+
time);
}
publicLocalTimegetTime(){
returntime;
}
publicvoidsetTime(LocalTimetime){
this.time=time;
}
}
Mightithappenthatyouneedtonest<f:ajax>insidethe
compositecomponentinordertorunsomeAjaxduringthechangeeventofanyoftheindividualdrop-downs,thenyoucanachievethatbyadding<cc:clientBehavior>targetingbothdrop-downsasfollows:<cc:interface...>...
<cc:clientBehaviorname="change"default="true"
targets="hourminute"event="change">
</cc:clientBehavior>
</cc:interface>
Thenameattributerepresentstheeventnamewhichyoushoulddeclareintemplateclientinordertotriggerit.
<t:inputLocalTimeid="time"...>
<f:ajaxevent="change"execute="time".../>
</t:inputLocalTime>
Thedefault="true"indicatesthatthisisthedefaultevent,whichmeansthatyoucouldjustomitit,asyoucoulddowithevent="change"forexistinginputtextanddrop-downcomponents,andwithevent="click"forexistingcheckboxandradiobuttoncomponents,andwithevent="action"forexistingcommandcomponents.
<t:inputLocalTimeid="time"...>
<f:ajaxexecute="time".../>
</t:inputLocalTime>
Thetargetsattributemustdefineaspace-separatedcollectionofIDsoftargetUIInputcomponentsresemblingthecompositecomponentonwhichyouwouldliketotriggertheAjaxevent,andtheeventattributemustdefinethe
desiredeventnametobeactuallytriggeredonthetargetUIInputcomponents.Inotherwords,thisdoes,underthehood,effectivelythesameasifthefollowingisimplementedinthecompositecomponent:<h:selectOneMenuid="hour"...>
<f:ajaxevent="change".../>
</h:selectOneMenu>
<h:selectOneMenuid="minute"...>
<f:ajaxevent="change".../>
</h:selectOneMenu>
Inthisspecificexample,theeventattributecoincidentallyhasjustthesamevalueasthenameattribute.Thisisperhapsconfusingatfirst,butitallowsyoutoeasilydefineacustomeventname.Forexample,followingisthecodetousewhenyouwanttotriggeraneventsolelyonthechangeofthehourdrop-down:<cc:interface...>...
<cc:clientBehaviorname="hourChange"
targets="hour"event="change">
</cc:clientBehavior>
</cc:interface>
Withthis,thefollowingAjaxlistenerwillthusbefiredonlywhenthehourdrop-downischangedandnotwhentheminutedrop-downischanged.
<t:inputLocalTimeid="time"...>
<f:ajaxevent="hourChange"execute="time"
listener="#{bean.hourChanged}">
</f:ajax>
</t:inputLocalTime>
Notethatexecute="time"isconsistentlyexplicitlyspecifiedinthegivenexamples.Thisisbecausethedefaultofexecute="@this"stilldoesnot,inthecurrentJSF2.3version,workcorrectlywithinthecontextofthecompositecomponentimplementation.JSFcouldhavederivedthetargetcomponentsfromany<cc:clientBehaviordefault="true">,butitisn’tspecifiedassuchyet.
Allinall,itmustbesaidthatcompositecomponentshavebeenoverhypedaftertheywerefirstintroducedinJSF2.0.Peoplestartedusingthemto“composite”wholetemplates,includes,decorations,andevenmultipletags,withoutusinganybackingcomponent.Thatis,thezero-configurationnatureascomparedtotagfilesisveryattractive.Everythingisdeclaredinthecompositecomponentfileitselfvia<cc:interface>.AndtheycanbedirectlyusedinthetemplateclientjustbyfollowingaconventionwithouttheneedtoconfigureitinsomeXMLfile.
Thecaveatisthatcompositecomponentsare,duetotheirinternaldesign,relativelyexpensiveduringbuildingandrestoringtheviewascomparedtoplainincludefiles,decoratefiles,andtagfiles,especiallywhendeeplynested.Thebestpracticeisthereforetousethemonlyifyouactuallyneedabackingcomponentvia<cc:interfacecomponentType>.Foranyothercase,justuseaninclude,adecorate,oratagfileinstead.IntheprevioussectionyoumayalreadyhavelearnedthattagfilescanbequitepowerfulwithhelpofJSTL.
RECURSIVECOMPOSITECOMPONENT
Youcansafelynestcompositecomponentsineachother.However,whenyounesttheverysamecompositecomponentrecursivelyinitself,thenitwouldfailwithastackoverflowerrorwhenELattemptstoresolvetheconcretecompositecomponentinstancebehind#{cc}.
Imaginethatyou’vegotarecursivetreemodelwhichrepresentssomesortofdiscussionthread,suchase-mailmessagesandalltheirreplies,orblogcommentsandalltheirreplies,wherebyeachreplycaninturnhaveanothersetofreplies.ThiscanberepresentedasasingleJPAentityasfollows:@EntitypublicclassMessage{
@Id@GeneratedValue(strategy=IDENTITY)
privateLongid;
@Lob
@Column(nullable=false)
private@NotNullStringtext;
@ManyToOne
privateMessagereplyTo;
@OneToMany(mappedBy="replyTo")
privateList<Message>replies=
Collections.emptyList();
//Add/generateremaininggettersandsetters.
}
NotethatthereplyTopropertyrepresentstheparent
5
messagewhichthecurrentmessageisareplyto,andthattherepliespropertyrepresentsallrepliestothecurrentmessage.ThetreestructurecanthenbequeriedasfollowsinaMessageService:publicList<Message>tree(){returnentityManager.createQuery(
"SELECTDISTINCTmFROMMessagem"
+"LEFTJOINFETCHm.repliesr"
+"ORDERBYm.idASC",Message.class)
.getResultList().stream()
.filter(m->m.getReplyTo()==null)
.collect(toList());
}
Notethatthefilteringoftheresultlistafterwardisatfirstglanceinefficient,butinrealityeverysinglemessageisretrievedonlyonceandsimplyreferencedintherepliesproperty.
Now,you’dintuitivelyimplementthe<t:message>compositecomponentassomethinglikethefollowing:<cc:interface>
<cc:attributename="value"
type="com.example.Message"/>
</cc:interface>
<cc:implementation>
#{cc.attrs.value.text}
<c:iftest="#{notemptycc.attrs.value.replies}">
<ul>
<c:forEachitems="#{cc.attrs.value.replies}"
var="reply">
<li>
<t:messagevalue="#{reply}"/>
</li>
</c:forEach>
</ul>
</c:if>
</cc:implementation>
whichisinturnusedasfollows:
<c:forEachitems="#{messages.tree}"var="message">
<t:messagevalue="#{message}"/>
</c:forEach>
You’llperhapsonlywonderwhy<c:forEach>isbeingusedinsteadof<ui:repeat>.Theexplanationisrelativelysimple:<ui:repeat>isignoredduringviewbuildtime.Inotherwords,<t:message>wouldincludeitselfinaninfiniteloop.Ifyouneedtorememberthewhyandhow,headbacktothesection“JSTLCoreTags”inChapter3.
Butevenwiththeaboveimplementationyouwouldstillrunintoaninfiniteloop.Youknowthat#{cc}referencesthecurrentinstanceofthecompositecomponent.Underthehood,when#{reply}ispassedtothenestedcomposite,theninrealityareferenceto#{cc.attrs.value.replies[index]}isbeingpassed.Thisis,onitsown,noproblem.Butwhenthenestedcompositeinturnevaluatesthe#{cc}partfromthisalias,thenitwouldreferenceitselfinsteadoftheparentcomposite.Hence,theinfiniteloop.
Theoretically,youcouldsolvethisbyreplacing#{cc}with#{cc.parent}whichreturnsUIComponent#getParent().
<c:forEachitems="#{cc.attrs.value.replies}"
varStatus="loop">
...
<t:messagevalue="#
{cc.parent.attrs.value.replies[loop.index]}"/>
...
</c:forEach>
However,itstilldoesn’twork.Underthehood,insidethenestedcomposite,whentheELevaluatorcomesto#{cc.parent}andattemptstoevaluate“attrs.value”onit,thentheparentcompositecomponentwouldreturnyetanotherELexpressioninformof#{cc.attrs.value}whichultimatelygetsevaluated.However,the#{cc}partstillgetsinterpretedas“currentcompositecomponent,”whichisinsidethenestedcompositecomponentandthusthenestedcompositeitself.
WecouldonlysolveittolettheparentcompositecomponentnotreturnyetanotherELexpressionbutinsteadthealready-evaluatedvalue.ThiscanbeachievedbyoverridingUIComponent#setValueExpression()inthebackingcomponentwhereyoucheckwhethertheValueExpressionrepresenting#{cc.attrs.value}isabouttobesetonthecomponentandthenimmediatelyevaluateitandstoretheresultasalocalvariableofthecompositecomponent.Thisshouldn’tcauseharmasit’ssupposedtobearead-onlyattribute.
@FacesComponent("messageComposite")
publicclassMessageCompositeextendsUINamingContainer{
privateMessagemessage;
@Override
publicvoidsetValueExpression
(StringattributeName,ValueExpressionexpression)
{
if("value".equals(attributeName)){
ELContextelContext=
getFacesContext().getELContext();
message=(Message)
expression.getValue(elContext);
}
else{
super.setValueExpression(attributeName,
expression);
}
}
publicMessagegetMessage(){
returnmessage;
}
}
Withthisbackingcomponentinplace,andreplacing“attrs.value”by“message”,itfinallyworks.
<cc:interfacecomponentType="messageComposite">
<cc:attributename="value"type="com.example.Message"/>
</cc:interface>
<cc:implementation>
#{cc.message.text}
<c:iftest="#{notemptycc.message.replies}">
<ul>
<c:forEachitems="#{cc.message.replies}"
varStatus="loop">
<li>
<t:message
value="#
{cc.parent.message.replies[loop.index]}">
</t:message>
</li>
</c:forEach>
</ul>
</c:if>
</cc:implementation>
ImplicitELObjectsInFaceletsfilesabunchofimplicitELobjectsavailable.Theyaremainlyshortcutstoimportantartifacts,scopes,maps,andcomponentsavailableinthecurrentfacescontext.Table7-1providesanoverviewofthem.
Table7-1 ImplicitELObjectsAvailableinELContextofJSF
ImplicitELobject
Resolvesto
Returns
Since
#{facesContext}
FacesContext#getCurrentInstance()
FacesContext
2.0
#{externalContext}
FacesContext#getExternalContext()
ExternalContext
2.3
#{view}
FacesContext#getViewRoot()
UIViewRoot
2.0
#{component}
UIComponent#getCurrentComponent()
UIComponent
2.0
#{cc}
UIComponent#getCurrentCompositeComponent()
UIComponent
2.0
#{request}
ExternalContext#getRequest()
HttpServletRequest
1.0
#{session}
ExternalContext#getSession()
HttpSession
1.0
#{application}
ExternalContext#getContext()
ServletContext
1.0
#{flash}
ExternalContext#getFlash()
Flash
2.0
#{requestScope}
ExternalContext#getRequestMap()
Map<String,Object>
1.0
#{viewScope}
UIViewRoot#getViewMap()
Map<String,Object>
2.0
#{flowScope}
FlowHandler#getCurrentFlowScope()
Map<Object,Object>
2.2
#{sessionScop
ExternalContext#getSessionMap()
Map<String,Object>
1.0
{sessionScope}
ExternalContext#getSessionMap() Object>
1.0
#
{applicationScope}
ExternalContext#getApplicationMa
p()
Map<String,
Object>
1.0
#{initParam}
ExternalContext#getInitParameterMap()
Map<String,String>
1.0
#{param}
ExternalContext#getRequestParameterMap()
Map<String,String>
1.0
#{paramValues}
ExternalContext#getRequestParameterValuesMap()
Map<String,String[]>
1.0
#{header}
ExternalContext#getRequestHeaderMap()
Map<String,String>
1.0
#{headerValues}
ExternalContext#getRequestHeaderValuesMap()
Map<String,String[]>
1.0
#{cookie}
ExternalContext#getRequestCookieMap()
Map<String,Cookie>
1.0
#{resource}
ResourceHandler#createResource()
Resource
2.0
#{resource}
ResourceHandler#createResource()
Resource
2.0
FortheJSFartifactsandcomponents,iftheclassinturnspecifiesagettermethodsomewhere,suchasHttpServletRequest#getContextPath(),thenyoucanofcourseaccessitinELtheusualwayasin#{request.contextPath}.
Forscopedmaps,anypropertywillbeinterpretedasthemapkey.Ifthepropertyhappenstocontainperiodcharacters,thenyoucanusethebracenotationasin#{map['key.with.periods']}inordertoaccessthemapvalue.Notethat#{flash}essentiallyextendsfromMap<String,Object>,soitcouldbetreatedassuch.Itshouldalsobesaidthat#{flowScope}indeeddeviatesfromotherscopedmapsbyacceptingObjectinsteadofStringasamapkey.Thisismostlikelyahistoricalmistake.ThecanonicalapproachtoaccessscopedmapsistouseaString-basedkey.
#{cookie}ismappedbythecookienameandthevalueactuallyreturnsajavax.servlet.http.CookiewhichinturnhasagetValue()property.So,inordertoaccesstheJSESSIONIDcookie,youbasicallyneed#{cookie.JSESSIONID.value}.Ofcourse,youcanalsojustuse#{session.id}instead.
#{resource}actuallyhasitsownELresolverwhichinterpretsanypropertyasaresourceidentifierin“library:name”formatandthenpassesittoResourceHandler#createResource()andfinallyreturnstheURLoftheresourcevia
Resource#getRequestPath().ThisisveryusefulinCSSresourcesinordertoreferenceaJSFimageresourceasaCSSbackgroundimage.ThefollowingexamplewillactuallyrendertheURLofsrc/main/webapp/resources/images/backgroun
d.svg.
body{
background-image:url("#
{resource['images/background.svg']}");
background-size:cover;
}
NotethatresolvingELexpressionsinCSSresourcesonlyworkswhentheCSSresourceitselfisincludedvia<h:outputStylesheet>insteadof<link>.AlsonotedshouldbethatJSFremembersbydefaultonlyontheveryfirstrequestoftheCSSresourcewhetheritcontainsELexpressionsornot.Ifitdidn’t,thenJSFwon’trecheckitonalaterrequest,evennotinthedevelopmentstage.SoifyounoticethatyourfirstELexpressioninanexistingCSSresourcedoesn’tseemtowork,you’dbetterrestartthewebapplication.ThisfeatureofELresolvinginCSSresourcesisactuallyprettyuseful.IfSCSS(SassyCSS)isasteptoofarforyou,thenyoucoulduseELtoparameterizesomerepeatedCSSproperties,suchascolors.
.color-gray{
color:#{applicationScope['gray']='#B8B8B8'};
}
...
.someSelector{
border:1pxsolid#{gray};
}
.otherSelector{
color:#{gray};
}
...
No,thisfeatureofELresolvinginCSSresourcesisn’tavailableinJSresources.Forthat,you’dinsteadneedtoprintaJSobjectintheglobalscopeandletyourJSresourcesinterceptitifnecessary.Forexample,<h:outputScript>varconfig=#{configuration.script};</h:outputScript>
<h:outputScriptname="scripts/some.js"/>
wherebythe#{configuration.script}justreturnsaJSONobjectasstringfromyourmanagedbean.Or,youcouldletELprintitasadataattributeofanHTMLelement<htmllang="en"data-baseuri="#{request.contextPath}/">
...
</html>
whichisinturnaccessibleinJSasfollows:varbaseuri=document.documentElement.dataset.baseuri;
orifyou’reajQueryfan:
varbaseuri=$("html").data("baseuri");
Thatsaid,whencreatingmanagedbeansontheJavaside,orwhendeclaringcustomELvariablesontheFaceletsside,suchas<h:dataTablevar="foo">,<ui:repeatvar="foo">,or<c:setvar="foo">,youneedto
makeabsolutelysurethatyoudon’texplicitlyorimplicitlychooseamanagedbeannameorELvariablenamewhichclasheswithoneofthepreviouslylistedimplicitELobjects,becauseimplicitELobjectshavehigherprecedenceinELresolvingthanuserdefinednames.So,forexample,thefollowingconstructwouldn’tworkasyoumightexpect:<ui:repeatvalue="#{bean.parameters}"var="param">
#{param}<br/>
</ui:repeat>
Itwouldprintliterally“{}”foreachiterationround,whichisbasicallythedefaultMap#toString()formatofanemptyMap.Whenyoureopenthesamepagewithaquerystringlike?foo=bar,thenitwouldprintliterally“{foo=bar}”foreachiterationround.You’dbetterrenamethevar="param"tosomethingelsethen.
Footnotes1www.onjava.com/pub/a/onjava/2004/06/09/jsf.html.
2https://stackoverflow.com/q/1296235/157882..
3http://arjan-tijms.omnifaces.org/2011/09/authoring-jsf-
pages-in-pure-java.html.
4https://developer.mozilla.org/en-
US/docs/Web/API/History_API#Adding_and_modifying_history
_entries..
5http://balusc.omnifaces.org/2016/02/recursive-tree-of-
composite-components.html..
(1) (2)
©BaukeScholtz,ArjanTijms2018BaukeScholtzandArjanTijms,TheDefinitiveGuidetoJSFinJavaEE8,https://doi.org/10.1007/978-1-4842-3387-0_8
8.BackingBeans
BaukeScholtz andArjanTijms
Willemstad,Curaçao Amsterdam,Noord-Holland,TheNetherlands
The“backingbean”isaJSF-specificconcept.ItrepresentsthesoleJavaBeanclasswhichisultimatelyusedasa“managedbean”responsibleforprovidingdata,actions,and/orUI(UserInterface)componentsinaJSFpage.
Model,View,orController?JSF(JavaServerFaces)isaMVC(model-view-controller)framework.It’sawidelyusedarchitecturaldesignpatternforsoftwareapplicationswhichhasitsrootsindesktopapplicationdevelopment.
InaJSFframework’spointofview,themodelisrepresentedbythebackingbean,theviewisrepresentedbythecomponenttree,whichinturnisusuallydefinedinaFaceletsfile,andthecontrollerisrepresentedbytheFacesServletwhichisalreadyprovidedbyJSF.FromaJavaEEapplicationserver’spointofview,however,themodelisrepresentedbytheservicelayerwhichinturnis
1 2
1
usuallydefinedinEJB(EnterpriseJavaBeans)classesandJPA(JavaPersistenceAPI)entities,theviewisrepresentedbyallyourJSF-basedcode,andthecontrolleristheFacesServlet.InaJSFdeveloper’spointofview,themodelisrepresentedbytheservicelayer,theviewisrepresentedbytheFaceletsfile,andthecontrollerisrepresentedbythebackingbean.
Thebackingbeanclasscanthusbeeitherthemodel,view,orcontroller,dependingonyourpointofview,whiletheservicelayerisalwaysthemodel,andtheFaceletsfileisalwaystheview,andtheFacesServletisalwaysthecontroller.Notethatinthiscontext,the“JSFdeveloper”isyou,whodevelopsawebapplicationusingtheJSFframeworkforaJavaEEapplicationserver.
Figure8-1illustratesthepositionofthebackingbeaninJSF’sMVCparadigm.It’saVenndiagramwheretheintersectionofthecontrollerandtheviewisrepresentedbytheJSFcomponenttreewhichcouldbeboundtoabackingbeanviathecomponent’sbindingattribute.TheintersectionoftheviewandthemodelisrepresentedbypropertygettersandsettersofEL(ExpressionLanguage)valueexpressionswhichcouldbeboundtoabackingbean,usuallyviathecomponent’svalueattribute.TheintersectionofthecontrollerandthemodelisrepresentedbyactionmethodinvocationsofELmethodexpressionswhichcouldbeboundtoabackingbeanviathecomponent’sactionattribute.Finally,theintersectionofallintersectionsisrepresentedbythebackingbeanitself.
Figure8-1 ThepositionofthebackingbeaninJSF’sMVCparadigm
InthisMVCparadigmthebackingbeanhasthusaratheruniqueposition.Notethatthebackingbeandoesn’tnecessarilyneedtoberepresentedbyasingleclass.Itcanevenberepresentedbymultipleclasses,eachwithitsownmanagedbeanscope,liketheviewcanberepresentedbymultipleFaceletsfilesandthemodelcanberepresentedbymultipleEJB/JPAclasses.
ComingbacktotheJSFdeveloper’spointofview,wecanevengetastepfurtherwithconsideringwhetherthebackingbeanisamodeloracontroller,dependingonhowyoucodethebackingbeanclass.Followingisoneway:@Named@RequestScoped@Stateful
publicclassProductBacking{
privateStringproductName;
privateStringproductDescription;
@Inject
privateActiveUseractiveUser;
@PersistenceContext
privateEntityManagerentityManager;
publicvoidsave(){
Productproduct=newProduct();
product.setName(productName);
product.setDescription(productDescription);
product.setCreatedBy(activeUser.get());
entityManager.persist(product);
FacesContext.getCurrentInstance().addMessage(nul
l,
newFacesMessage("Productcreated!"));
}
//Add/generategettersandsettersforproductname
anddescription.
}
Inthisrathernaïveway,theentity’spropertiesareessentiallyduplicatedinthebackingbeanclassandthebusinesslogicistightlycoupledinthebackingbeanclass.Inotherwords,thebackingbeanclasshasincorrectlytakenovertheresponsibilitiesoftherealmodel.Onewouldmisinterpretsuchbackingbeanclassasbeingthesolemodel.Whenweeliminatethisduplicationandunreusability,wefindanotherway:@Named@RequestScopedpublicclassProductBacking{
privateProductproduct=newProduct();
@Inject
privateProductServiceproductService;
publicvoidsave(){
productService.create(product);
FacesContext.getCurrentInstance().addMessage(nul
l,
newFacesMessage("Productcreated!"));
}
publicProductgetProduct(){
returnproduct;
}
}
wherebytheProductServicelooksasfollows:@Stateless
publicclassProductService{
@PersistenceContext
privateEntityManagerentityManager;
@Inject
privateActiveUseractiveUser;
publicLongcreate(Productproduct){
product.setCreatedBy(activeUser.get());
entityManager.persist(product);
returnproduct.getId();
}
}
Thisisactuallythecorrectwayofauthoringbackingbeans.Whencomparingittothefirstway,youcouldarguethatthebackingbeanhas,intheJSFdeveloper’spointofview,becomeacontrollerfortheEJB/JPAmodel.ThebackingbeanbeingacontrollerisnotwrongfromtheJSFdeveloper’spointofview,butthisisnotactuallycorrectfromtheJSFframework’spointofviewwheretheFacesServletistherealcontroller.TheFacesServlettreatsthebackingbeanasamodel,becausetheFacesServletdoesn’thavedirectaccesstotherealmodel,theservicelayer.YouastheJSFdevelopercan,ofcourse,inyourcontexttreatthebackingbeanasacontroller,becauseyoucaneasilyignorealldutiesoftheFacesServletasyoudon’tneedtoworryaboutitsjobwhilewritingJSFcode.AllyouneedtoworryaboutwhilewritingJSFcodeistheview,themodel,andthebackingbean.TherestisdonetransparentlybyJSF.
ManagedBeansTheconceptualdifferencebetweena“backingbean”anda“managedbean”canberepresentedbythefollowinglinesofcodeexecutedunderthehoodofthebeanmanagementfacility:BackingBeanClassmanagedBeanInstance=newBackingBeanClass();
someContext.put("managedBeanName",managedBeanInstance,
someScope);
Inotherwords,thebackingbeanistheconcreteclasscreatedbyyou,theJSFdeveloper,andregisteredintosomebean
managementfacility,suchasCDI.Thebeanmanagementfacilitywillautomaticallymanagethebean’slifecyclebyperformingconstruction,dependencyinjection,anddestructionwhennecessary,withoutyouhavingtodoitmanually.Ifyou’veeverdevelopedwithJSP/Servlets,thisbasicallyremovestheneedtomanuallyinstantiatebeansandputthemasanattributeoftheServletContext,HttpSession,orServletRequest. ToregisterabackingbeanclassasaCDImanagedbeanforJSFviews,[email protected] ontheclasssignature.
@Named
publicclassBackingBeanClass{
//...
}
ItwillthenimmediatelybeavailableinELcontextby#{backingBeanClass}andinallothermanagedbeansvia@Inject.TheELcontextisdirectlyavailableinFaceletsfiles.Bydefault,themanagedbeannameisderivedfromthebackingbean’sclassnamebylowercasingthefirstcharacter.Thiscanoptionallybeoverriddenbyspecifyingthevalueofthe@Namedannotation.
@Named("managedBeanName")
publicclassBackingBeanClass{
//...
}
ThisisnowinELcontextavailableby#{managedBeanName}.Nothinghaschangedforthe
2
3
@Injectapproach.OnceaJSFbackingbeanbecomesamanagedbean,itwillbeautomaticallyinstantiatedandinitializedwheneverit’saccessedforthefirsttimeinthecontextassociatedwiththebean’sscope.Itwillbeautomaticallydestroyedwhenthelifecycleassociatedwiththebean’sscopehasended.Moreaboutmanagedbeanscopesinthenextsection.
Historically,JSFprovidedanativewaytoregisterbackingbeanclassesasmanagedbeans:first,inJSF1.xvia<managed-bean>entriesinfaces-config.xml,[email protected],whichis,sinceJSF2.3,officiallydeprecatedinfavorofCDI@Named.CDIwasintroducedforfirsttimeinJavaEE6,atthesametimeasJSF2.0,withtheaimofunifyingthemanagementofcontext-sensitiveinstancesandinjectingthecurrentlyavailableinstancesineachother.Unfortunately,theJSF2.0@ManagedBeanwasalreadysetinstonelongbeforeCDIwasfinished,sothosetwowaysofmanagingbeansdidexistinparallelforsometime.TheCDIbeanmanagementfacilityhasseveraladvantagesontopofJSFbeanmanagementfacility.
First,injectingonemanagedbeaninanothermanagedbeanusingCDI’s@Injectdoesn’trequireagetter/setterpairintheparentbackingbeanclass,whileJSF’[email protected]/setterpair,whichisconsideredpoorpracticeasthisexposestoomuchinformationtotheoutside,whichispotentiallyconfusing.Shouldweaccesstheinjectedbeanvia#{bean}or#{parentBean.bean}?
Second,theinjectedCDImanagedbeancanbeofa
narrowerscopethantheparentmanagedbean.ThisispossiblebecauseCDI@Injectactuallyinjectsaproxyinstancewhichinturndelegatestothecurrentlyavailableinstance,whileJSF@ManagedProperty“injects”theactualinstancebyinvokingthesettermethoddirectlyaftertheconstructionoftheparentbean.
Third,CDImanagedbeansareaccessibleinallotherJavaEEartifactswhicharenotdirectlymanagedbyJSF,suchaswebservlets,webfilters,weblisteners,socketendpoints,webserviceendpoints,enterprisebeans,etc.Thisallowsaveryeasywayofexchangingdataacrossvariouslayerswithinthesameapplication,particularlywithinthesameHTTPsession.
Onceagain,theJSFbeanmanagementfacilityisofficiallydeprecatedsinceJSF2.3.YoushouldabsolutelynotuseitanymoreinnewJSFapplications.ItwillstillbethereintheJSFAPIforbackwardcompatibility,butchancesarethatthejavax.faces.beanpackagewillberemovedaltogetherinafutureJSFversion.ExistingJSFapplicationsshouldbemigratedtoCDIassoonaspossible.CDIisnativelyavailableinnormalJavaEEapplicationserversandrelativelyeasytoinstallinbarebonesservletcontainers.Forexample,JBossWeld,oneoftheCDIimplementations,canalreadybeinstalledinTomcatbysimplyaddingthesingledependencyorg.jboss.weld.servlet:weld-servlet-shaded
totheproject withoutanyfurthereffort.
ScopesThemanagedbeanscopebasicallyrepresentsthelifetimeofthemanagedbean.Ashintedintheprevioussection,inplainJSP/Servletperspectivethescopesarerepresentedbythe
4
objectbeingputasanattributeoftheServletContext,HttpSession,orServletRequest.Thoseobjectswillthenbecomeapplicationscoped,sessionscoped,andrequestscoped,respectively.ThisisstillhowitworkswithCDIandall;itonlyaddsanextraabstractlayeroveritsothatyoudon’tanymoreneedtomanuallycreateandputtheobjectsinacertainscope.
InstandardJSF,thefollowingCDImanagedbeanscopesareavailableforJSFbackingbeans,orderedfromthelongestlivingtotheshortestliving.
1. @javax.enterprise.context.ApplicationScoped
2. @javax.enterprise.context.SessionScoped
3. @javax.enterprise.context.ConversationScoped
4. @javax.faces.flow.FlowScoped
5. @javax.faces.view.ViewScoped
6. @javax.enterprise.context.RequestScoped
7. @javax.enterprise.context.Dependent
Notethatthejavax.faces.beanpackagealsodefinesasetofscopes,buttheyareonlyapplicableonthebeansmanagedbyJSF’s@ManagedBean,notonbeansmanagedbyCDI.Moreover,thejavax.faces.beanpackageisdeprecatedsinceJSF2.3infavorofCDI.
@APPLICATIONSCOPEDAnapplication-scopedmanagedbeaninstanceistiedtothelifetimeofthewebapplicationitself.It’sunderthehoodrepresentedasanattributeoftheServletContextwhichiscreatedonthewebapplication’sdeploymentanddestroyed
onthewebapplication’sundeployment.Notethatthisisnotequaltotheserver’sstartupandshutdown.Webapplicationscanbedeployedandundeployedonarunningserver.
Inotherwords,there’sonlyoneinstanceofanapplication-scopedmanagedbeanthroughoutthewebapplication’slifetimewhichissharedacrossallrequestsandsessions.Youcouldarguethatitbehaveslikeasingleton.However,itdoesn’tactuallyfollowthesingletondesignpattern.Itfollowsthe“justcreateone”designpattern. Arealsingletondoesn’thaveanypublicconstructorbutonlyastaticmethodwhichreturnsastaticallyinitializedlazyloadedinstance.ArealJavaBean,ontheotherhand,requiresthepresenceofadefaultconstructor.
Bydefault,anapplication-scopedbeanmanagedinstanceiscreatedforthefirsttimewhenthewebapplication’scodeaccessesitforthefirsttimeduringthewebapplication’slifetime.It’sthusnot,perdefinition,immediatelycreatedwhentheServletContextinstanceiscreated.Itis,however,guaranteedwhentheServletContextinstanceisdestroyed.
Application-scopedmanagedbeansareusefulforapplication-widedatawhichneedstobeinitializedonlyonceintheapplication’slifetime,orneedstoprovidenon-staticgetterswhichdelegatetostaticvariables,orneedstoprovidefunctionsforusageinEL.ThefollowingexamplemakessurethatapplicationsettingsstoredinthedatabaseareloadedonlyonceandprovidedasaMapby#{settings}duringtherestoftheapplication’slifetime.
@ApplicationScoped
publicclassApplicationSettingsProducer{
5
privateMap<String,String>settings;
@Inject
privateApplicationSettingsService
applicationSettingsService;
@PostConstruct
publicvoidinit(){
settings=applicationSettingsService.getAll();
}
@Produces@Named
publicMap<String,String>getSettings(){
returnsettings;
}
}
Notethatthe@Namedannotationisplacedonthegetter,whichimpliesamanagedbeannamematchingthepropertyname:#{settings}.Alsonotethatthegetterinturnneedsthe@Producesannotationinordertoberecognizedasamanagedbeanproducer.Followingisanotherexamplewhichofferstextformattingfunctions.
@Named@ApplicationScoped
publicclassFormat{
publicStringdate(LocalDatelocalDate){
if(localDate!=null){
return
localDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
}
else{
return"n/a";
}
}
publicStringcurrency(BigDecimalamount){
if(amount!=null){
return
NumberFormat.getCurrencyInstance(Locale.US)
.format(amount);
}
else{
return"n/a";
}
}
}
Thiscouldbeusefulin,forexample,adatatabletokeeptheFaceletscodeterse.
<h:dataTablevalue="#{cart.products}"var="product">
<h:column>#{format.date(product.lastModified)}</h:column>
<h:column>#{format.currency(product.discount)}</h:column>
</h:dataTable>
It’salsousefulincaseyouneedaformattedvalueinanattributewhichdoesn’tallowanested<h:outputText><f:convertXxx>.
<h:commandLink...title="Lastvisited#
{format.date(user.lastVisited)}">
@SESSIONSCOPEDAsession-scopedmanagedbeaninstanceistiedtothelifetimeoftheestablishedHTTPsession.It’sunderthehoodrepresentedasanattributeoftheHttpSessionwhichiscreatedforeveryuniqueclientondemandofthewebapplication’scode.Whenthewebapplication’scodedirectly
orindirectlypokestheHttpSessionforthefirsttimeviaHttpServletRequest#getSession(),theservletcontainerwillcreateanewHttpSessioninstance,generatealonganduniqueID,andstoreitinserver’smemory.TheservletcontainerwillalsosetasessioncookieontheHTTPresponsewith“JSESSIONID”asthecookienameandtheuniquesessionIDasthecookievalue.A“sessioncookie”isidentifiedbytheabsenceofthe“maximumage”attribute.
AspertheHTTPcookiespecification,theclient(thewebbrowser)isrequiredtosendthiscookiebackintheheaderofthesubsequentrequestsaslongasthecookieisvalid.Inanydecentwebbrowseryoucaninspecttherequestandresponseheadersinthe“Network”sectionofthewebdeveloper’stoolsetwhichisaccessiblebypressingF12inthewebbrowser.TheservletcontainerwillcheckeveryincomingHTTPrequestforthepresenceofthecookiewiththename“JSESSIONID”anduseitsvalue(thesessionID)togettheassociatedHttpSessioninstancefromserver’smemory.
Ontheserverside,theHttpSessioninstancestaysaliveuntilithasnotbeenaccessedformorethanthetimeoutvalueasspecifiedinthe<session-timeout>settingofweb.xml,whichdefaultsto30minutesonmostifnotallservletcontainers.So,whentheclientdoesn’tvisitthewebapplicationforlongerthanthetimespecified,theservletcontainerwilldestroytheHttpSessioninstance.EverysubsequentHTTPrequest,evenwiththecookiespecified,willnothaveaccesstotheHttpSessioninstanceanymore;theservletcontainerwillcreateanewHttpSessioninstanceandoverwritethecookievaluewiththenewsessionID.
Ontheclientside,bydefault,allsessioncookiesstayalive
foraslongasthebrowserinstanceisrunning.So,whentheclientshutdownsthebrowserinstance,allsessioncookiesaredestroyedontheclientside.Inanewbrowserinstance,thesessioncookiesfromapreviousbrowsersessionarenotavailableanymore,sothebrowserwon’tsendanyJSESSIONIDcookie.Theserverwilltheninterpretitasabrand-newsession.TheHttpSessioninstanceassociatedwiththepreviousbrowsersessionwillsilentlyexpireontheserverside.
Bydefault,asession-scopedmanagedbeaninstanceiscreatedforthefirsttimewhenthewebapplication’scodeaccessesitforthefirsttimeduringtheHTTPsession’slifetime.It’sthusnot,perdefinition,immediatelycreatedwhentheHttpSessioninstanceiscreated.Itis,however,guaranteedtobedestroyedwhentheHttpSessioninstanceisdestroyed.Session-scopedmanagedbeansareeffectivelysharedacrossallbrowsertabswithinthesamebrowsersession.
Session-scopedmanagedbeansareusefulforkeepingtrackofclient-specificdata,suchastheentityrepresentingthecurrentlylogged-inuser,theselectedlanguage,andotheruser-relatedpreferences.Thefollowingexamplecalculatesthecurrentlocaleandprovidesagetter/setterforitsothatitcanbeobtainedintheviewandmodifiedbyanUIInputcomponent.
@Named@SessionScoped
publicclassActiveLocaleimplementsSerializable{
privateLocalecurrent;
@PostConstruct
publicvoidinit(){
FacesContextcontext=
FacesContext.getCurrentInstance();
current=context.getApplication()
.getViewHandler().calculateLocale(context);
}
//Getter+setter.
}
Amoreelaborateexamplecanbefoundinthesection“ChangingtheActiveLocale”inChapter14.Donotethatsession-scopedmanagedbeansmustimplementSerializablebecausetheHttpSessioninstanceitself,wherethosebeansarebeingstored,issubjecttobeingwrittentodiskincaseofaserverrestartoreventobeingtransferredoverthenetworktoadifferentserverincaseofaserverclusterconfigurationwithadistributablesession.
Anotherclassicexampleisa“shoppingcart.”
@Named@SessionScoped
publicclassCartimplementsSerializable{
privateList<Product>products=newArrayList<>();
publicvoidaddProduct(Productproduct){
products.add(product);
}
//...
}
@CONVERSATIONSCOPEDAconversation-scopedmanagedbeanistiedtothelifetimeof
theinjectedjavax.enterprise.context.Conversation
instancewhichoffersbegin()andend()methodswhichmustbeexplicitlyinvokedbythewebapplication’scodeinordertoindicatethestartandendoftheconversationscope.TheconversationscopeisrepresentedbyapredefinedHTTPrequestparameterwithadefaultnameof“cid”(“ConversationID”)whosevaluereferencesrepresentstheconversationID.TheconversationIDinturnreferencesanisolatedmappinginthecurrentHTTPsessionwheretheconversation-scopedmanagedbeaninstanceswillbestored.
Aslongastheconversationscopehasnotstarted,theconversation-scopedmanagedbeanwillbehavelikearequest-scopedmanagedbean.WhentheapplicationcodeexplicitlyinvokesConversation#begin(),thentheconversationscopewillstartandacustomjavax.faces.application.ViewHandlerprovidedbytheCDIimplementationwillmakesurethatallitsgetXxxURL()methodssuchasgetActionURL()andgetBookmarkableURL()returnaURL(uniformresourcelocator)withtheconversationIDparameterincluded.IncaseofWeld,that’stheConversationAwareViewHandler.AllJSFUIFormandUIOutcomeTargetcomponentsderivetheiractionandtargetURLsfromthosemethodsoftheViewHandler.ThegeneratedHTMLoutputofthosecomponentswillthusultimatelyincludetheconversationIDinthetargetURL.
OnanincomingHTTPrequest,whentheconversationIDparameterispresentintherequest,anditisstillvalid,theCDIimplementationwillobtaintheassociatedconversationscope
6
fromtheHTTPsessionandmakesurethatallconversation-scopedmanagedbeansareobtainedfromexactlythisconversationscopeidentifiedbytheconversationID.ThisworksonbothGETandPOSTrequests.Anyformsubmitoranylink/navigationtoaURLwiththeconversationIDincludedwillprovideaccesstotheverysameconversationscope,aslongasit’sstillvalid.TheconversationscopeendswhentheapplicationcodeexplicitlyinvokesConversation#end().Whentheenduserreusesthe“cid”requestparameterlater,ormanipulatesitsvaluetoonewhichisn’tstartedinitsownbrowsersession,orwhentheunderlyingHttpSessioninstanceisdestroyed,thenCDIwillthrowajavax.enterprise.context.NonexistentConver
sationException.Conversation-scopedmanagedbeansareparticularly
usefulinordertobeabletoreturntoaparticularstatefulpagewithinthesamebrowsersessionafterbeingredirectedelsewhere.Aclassicexampleisathird-partywebservicewhichisincludedinanHTML<iframe>oropenedinanewbrowsertaboreventargetedintheactionattributeofaplainHTML<form>,andcan,viaaspecificrequestparameter,beconfiguredtoredirectbacktoyourwebapplicationaftercompletingtheservice.WhenyouincludetheconversationIDintheredirectURL,thenyouwillintheredirectedpagebeabletoresumewithexactlythesameconversation-scopedmanagedbeaninstanceasitwasbeforetheredirect.Thisallowsyoutheopportunitytofinalizeandunlockanypendingtransactionsand,ofcourse,endtheconversation.
Givenacheckoutbuttonwhichlooksasfollows,
<h:form>
...
<ui:fragmentrendered="#{emptypayment.url}">
...
<h:commandButtonvalue="Checkout"action="#
{payment.checkout}">
<f:ajaxrender="@form"/>
</h:commandButton>
</ui:fragment>
<ui:fragmentrendered="#{notemptypayment.url}">
<iframesrc="#{payment.url}"></iframe>
</ui:fragment>
</h:form>
here’swhattheassociatedconversation-scopedbeanbehind#{payment}lookslike:@Named@ConversationScopedpublicclassPaymentimplementsSerializable{
privateOrderorder;
privateStringurl;
@Inject
privateCartcart;
@Inject
privateOrderServiceorderService;
@Inject
privateConversationconversation;
publicvoidcheckout(){
conversation.begin();
order=
orderService.lockProductsAndPrepareOrder(cart);
url="http://third.party.com/pay?returnurl="
+
URLEncoder.encode("http://my.site.com/paid?cid="
+conversation.getId(),"UTF-8");
}
publicvoidconfirm(){
orderService.saveOrderAndCreateInvoice(order);
conversation.end();
}
@PreDestroy
publicvoiddestroy(){
orderService.unlockProductsIfNecessary(order);
}
publicStringgetUrl(){
returnurl;
}
}
Basically,thecheckoutbuttonisonlyrenderedwhenthere’snopaymentURLset.Oncethebuttonispressed,allproductsoftheshoppingcartarelockedandtheorderisprepared.Also,dependingonthethird-partypaymentservice,theURLreferringitmustbepreparedwherebyyouincludethereturnURLassomequeryparameterintheURLofthepaymentservice.ThereturnURLshouldinturnincludethe“cid”
requestparameterrepresentingtheconversationID.Intheredirectedpagewhichwillactuallybeloadedinthe<iframe>,youcanjustmarktheconversationcompletewith<f:viewAction>.
<f:metadata>
<f:viewActionaction="#{payment.confirm}"/>
</f:metadata>
Ofcourse,theaveragethird-partypaymentserviceshouldhaveamoreelaborateJavaorevenJavaScriptAPIinsteadof<iframe>;also,itshouldbepossibletoprovidedifferentreturnpagesforeachpaymentoutcomesuchaspaymentfailedandpaymentaborted.Theaboveexampleisjusttogivethegeneralidea.
@FLOWSCOPEDAflow-scopedmanagedbeanistiedtothelifetimeofaJSFflow.Itusesthesameprincipleasconversationscope,onlytheconversationisfurthernarroweddowntoaspecificsetofJSFviewsinanisolatedsubfolder.OncetheenduserclicksaJSFlinkorbuttoncomponentwhichnavigatestoaspecificentrypageofaJSFflow,thentheflowscopewillautomaticallystart.TheflowscopecannotbestartedwhenyouopentheentrypagewithoutnavigatingviaaJSFcomponent.Thatis,JSFwill,withhelpoftheViewHandler,automaticallyappendthepredefinedHTTPrequestparameter“jfwid”(“javax.facesWindowID”)totheoutcomeURLwhosevaluerepresentstheJSFclientwindowID.TheJSFclientwindowIDinturnreferencesanisolatedmappinginthecurrentHTTPsessionwheretheflow-scopedmanagedbeansarestored.
Additionally,particularlywhenusingaUIOutcomeTargetcomponentinsteadofaUICommandcomponenttonavigate,thequerystringmaybeaccompaniedwith“jffi”(javax.facesFlowID)and“jftfdi”(javax.facesToFlowDocumentID)requestparameters.ThoseareactuallyonlymandatoryforstartingaJSFflowusingaGETrequest.Technically,fortherestoftheJSFflow,“jfwid”issufficient.Aslongasthe“jfwid”parameterispresent,andisstillvalid,thentheJSFflowisidempotentandcanberesumedusingaGETrequest.WhenyouopenanewbrowsertabandnavigateintotheJSFflow,thenactuallyanewflowscopewillbestarted,independentoftheJSFflowinothertab.OnceapostbackrequestwithintheJSFflownavigatestoapageoutsidetheJSFflow,thentheflowscopewillautomaticallyend.Whentheenduserreusesthe“jfwid”requestparameterlater,ormanipulatesitsvaluetoonewhichisn’tstartedinitsownbrowsersession,orenterstheflowdirectly,orwhentheunderlyingHttpSessioninstanceisdestroyed,thenCDIwillthrowajavax.enterprise.context.ContextNotActiveE
xception.Themajordifferencebetweentheflowscopeandthe
conversationscopeisthusthatthepageswithinaJSFflowcannotbeentereddirectly.TheywillautomaticallystartwhentheendusernavigatestotheentrypageoftheJSFflowandtheywillautomaticallyendwhenapostbacknavigatesoutsidetheJSFflow.Flow-scopedmanagedbeansareusefulinordertoisolateaconversationtoaspecificsetofJSFpages.Aclassicreal-worldexampleisabookingapplicationwhichisspreadovermultipleformsinphysicallydifferentpages.
TherearevariouswaystodefineaJSFflow.Onewayisbyconvention,anotherwayisbydeclarativeconfigurationinthe[flowId][flowId]-flow.xmlfile,andyetanotherwayisbyprogrammaticconfigurationusingjavax.faces.flow.FlowBuilderAPI. Inthisbookwe’llrestrictourselvestoconventionoverconfiguration.First,createthefollowingfolderstructure:
Thefirstconventionisthattheflowentrypagemusthaveexactlythesamenameasthesubfolderitissittingin.Inthiscase,that’s“booking”.ThisisconsideredtheFlowID.Thesecondconventionisthattheremustbea*-flow.xmlfileinthesubfolderwhosenameisprefixedwiththeFlowID,i.e.,booking-flow.xml.ThisXMLconfigurationfilecanbekeptemptyfornow.It’sonlyusefulwhenyouwanttofine-graintheJSFflowconfiguration,e.g.,byspecifyingadifferententrypage.Withoutthisfile,theJSFflowscopewon’tbeactivated.OnedisadvantageofatleastoneactivatedJSFflowinthewebapplication,however,isthattheJSFclientWindowIDparameter“jfwid”willbeappendedtoevery
7
singlenavigationURL,evenwhenitdoesn’ttargetaJSFflow.ThisURLpollutionmayforsomedevelopersbethemainreasontonotusetheJSFflowscopeatall.
ThenavigationcomponentinordertoenteraJSFflowmustbeplacedinaJSFpageoutsidetheflowsubfolder.Thenavigationoutcomemustreferencethesubfoldername,whichistheFlowID.Here’sanexamplein/home.xhtml.
<h:buttonvalue="Startbooking"outcome="booking"/>
Ofcourse,thiscanbesubstitutedwith<h:link>.It’srecommendedtouseGETjustforthissothatthebookingpage’sURLreflectsinthebrowser’saddressbar.Then,inallpageswithinthesubfolder,youcanreferenceaflow-scopedmanagedbeanwhichwillbesharedacrossallthesepages.Youcannavigatebackandforthbetweenthesepagesaswellwhileretainingtheflow-scopedmanagedbeaninstance.WerecommendthatyouuseAjaxwithredirectforthis.TheAjaxsubmitswillimprovetheuserexperience.Theredirectswillmakesurethattheindividualpagesarestillbookmarkable.
bookingbooking.xhtml:<h:form><h:inputTextvalue="#{booking.startDate}"/>
...
<h:commandButtonvalue="Next"action="persons?faces-
redirect=true">
<f:ajaxexecute="@form"render="@form"/>
</h:commandButton>
</h:form>
bookingpersons.xhtml:<h:form><ui:repeatvalue="#{booking.persons}"var="person">
<h:inputTextvalue="#{person.name}"/>
...
</ui:repeat>
...
<h:commandButtonvalue="Back"action="booking?faces-
redirect=true">
<f:ajaxexecute="@form"render="@form"/>
</h:commandButton>
<h:commandButtonvalue="Next"action="confirm?faces-
redirect=true">
<f:ajaxexecute="@form"render="@form"/>
</h:commandButton>
</h:form>
bookingconfirm.xhtml:<h:form><h:outputTextvalue="#{booking.startDate}"/>
...
<h:commandButtonvalue="Back"action="persons?faces-
redirect=true">
<f:ajaxexecute="@form"render="@form"/>
</h:commandButton>
<h:commandButtonvalue="Next"action="payment?faces-
redirect=true">
<f:ajaxexecute="@form"render="@form"/>
</h:commandButton>
</h:form>
bookingpayment.xhtml:<h:form><h:selectOneMenuvalue="#{booking.paymentMethod}">
...
</h:selectOneMenu
...
<h:commandButtonvalue="Back"action="confirm?faces-
redirect=true">
<f:ajaxexecute="@form"render="@form"/>
</h:commandButton>
<h:commandButtonvalue="Submit"actionListener="#
{booking.submit()}"
action="/home?faces-redirect=true">
<f:ajaxexecute="@form"render="@form"/>
</h:commandButton>
</h:form>
And,finally,theflow-scopedbeanbehind#{booking}:
@Named@FlowScoped("booking")
publicclassBookingimplementsSerializable{
privateLocalDatestartDate;
privateList<Person>persons;
privatePaymentMethodpaymentMethod;
//...
publicvoidsubmit(){
//...
}
}
Yousee,themostofnavigationtaskisdonebytheactionattributeofthecommandcomponents.?faces-redirect=trueisaspecialrequestparameterwhichisinternallyrecognizedbyJSFasaninstructiontoperformaredirectafterpostbackandofcoursestripofffromthetargetURLbeforeperformingtheactualredirect.Oncethepostbackleavestheflow,theflow-scopedmanagedbeanisdestroyed
andthepreviouslypresentedpageURLsarenotreusableanymore.
@VIEWSCOPEDAview-scopedmanagedbeanistiedtothelifetimeoftheJSFviewstate.TheJSFviewstateiselaboratedinthesection“ViewState”inChapter3.Inanutshell,aview-scopedmanagedbeanlivesaslongastheenduserisperformingpostbackrequestsontheverysameJSFviewandtheinvokedactionmethodskeepreturningnullorvoid.Oncetheactionmethodreturnsnon-null,evenifit’sanemptystringorrepresentsthesameview,thentheviewscopewillend.View-scopedmanagedbeansarenotsharedacrossbrowsertabswithinthesamebrowsersession.Eachonegetsitsownuniqueinstance.Effectively,theyareindirectlyidentifiedbythejavax.faces.ViewStatehiddeninputfieldinthegeneratedHTMLrepresentationofaJSFform.
However,view-scopedmanagedbeansarenotstoredintheJSFviewstate,notevenwhenclient-sidestatesavingisenabled.TheyareactuallystoredintheHTTPsession,regardlessoftheJSFstatesavingmethod.TheyarenotimmediatelydestroyedwhentheenduserunloadsthewebpageeitherbyperformingaGETrequestviaalinkorbookmarkoreditingtheURLinbrowser’saddressbar,orbyclosingthebrowsertab.TheywillstickaroundintheHTTPsessionandonlygetdestroyedwhentheHTTPsessionexpires.
Asanendusercanintheoryspawnanunlimitedamountofbrowsertabswithinthesamesession,andthusalsoasmanyJSFviewstatesandview-scopedmanagedbeans,there’sa
configurablemaximumlimitontheamountofJSFviewstatesandview-scopedmanagedbeanswhichwillbestoredintheHTTPsession.Oncethislimitisreached,theleastrecentlyusedJSFviewstateandview-scopedmanagedbeanwillbeexpiredanddestroyed.WhentheenduseractuallygoesbacktothetabthatoriginallyreferencedthenowexpiredJSFviewstate,andperformsapostbackrequestonit,JSFwillthrowaViewExpiredException.ThelimitontheamountofJSFviewstatesisdependentontheJSFimplementationused.InMojarra,thislimitisconfigurablebythecom.sun.faces.numberOfLogicalViewscontextparameterinweb.xmlwhosedefaultvalueis15.
<context-param>
<param-name>com.sun.faces.numberOfLogicalViews</param-
name>
<param-value>25</param-value>
</context-param>
Ifyourwebapplication,however,invitesbeingopenedinmanymorebrowsertabs,suchasadiscussionforumoraQ&Asite,thenyou’dbetterswitchtoclient-sidestatesaving.ThiswaytheJSFviewstatesarenolongerstoredintheHTTPsessionandwillthereforealsoneverexpire.However,theassociatedview-scopedmanagedbeansarestillstoredintheHTTPsessionandexpirable.Whentheenduseractuallygoesbacktothetabthatoriginallyreferencedthenowexpiredviewscoped-managedbean,andperformsapostbackrequestonit,JSFwillnotthrowaViewExpiredExceptionbutinsteadwillcreateanewonefromscratch,therebylosingallthestatechangestotheoriginalmanagedbeaninstance.Thelimiton
8
theamountofview-scopedmanagedbeansisalsodependentontheJSFimplementationused.InMojarra,thislimitisnotyetconfigurablebyaweb.xmlcontextparameter.It’sonlyconfigurablebyexplicitlysettingthesessionattributewiththenamecom.sun.faces.application.view.activeViewM
apsSizewhosedefaultvalueis25.Thiscanbeachievedwithanapplication-scopedmanagedbeanasfollows,whichobservestheinitializationofthesessionscope.
@ApplicationScoped
publicclassConfig{
publicvoidsessionCreated
(@Observes@Initialized(SessionScoped.class)
HttpSessionsession)
{
session.setAttribute
("com.sun.faces.application.view.activeViewMapsSi
ze",15);
}
}
ThisconfigurationactuallydecreasesthedefaultvaluetobeequaltodefaultmaximumamountofJSFviewstatesinsession.Thisisfinewhenyou’reusingserver-sidestatesavingandallyourJSFviewseffectivelyreferenceonlyoneview-scopedmanagedbeaninstance.However,inadecentlydevelopedandrefactoredJSFwebapplication,theaverageJSFpageusuallyreferencesmultipleview-scopedmanagedbeans.Ifyouhave,forexample,amaximumamountofthreedifferentview-scopedmanagedbeansperJSFview,thenyou’dbestsetthelimittothreetimesthevalueof
com.sun.faces.numberOfLogicalViews.Youonlyneedtotakeintoaccountthepossiblememoryconsumption.Itwillquicklygooverboardwhentheview-scopedmanagedbeansinturnhold,relatively,alotofdata.
View-scopedmanagedbeansareveryusefulforretainingstateacrossAjax-basedpostbacksonthesameJSFview,particularlyifthosepostbacksresultinchangesinthevalueoftherenderedattributeofanyUIComponent,orthedisabledorreadonlyattributeofanUIInputcomponent,orthedisabledattributeofanyUICommandcomponentwithinthesameJSFview.Thatis,onasubsequentpostback,JSFwill,aspartofasafeguardagainstatamperedrequest,recheckthembeforeactuallyprocessingthecomponent.Ifthemanagedbeanholdingthestatewasrequestscopedinsteadofviewscoped,thenthosechangesintheconditionswouldgetlostinasubsequentpostbackandthepostbackwouldn’tgetprocessedasintuitivelyexpected.Inotherwords,view-scopedmanagedbeansareparticularlyusefulindynamicforms.
Oneexampleisadrop-downwhichconditionallyrendersafreetextfieldwhenthe“other”optionischosen.
<f:metadata>
<f:importConstantstype="com.example.project.model.Title"
/>
</f:metadata>
...
<h:form>
<h:selectOneMenuvalue="#{bean.customer.title}">
<f:selectItemsvalue="#{Title}"/>
<f:ajaxrender="other"/>
</h:selectOneMenu>
<h:panelGroupid="other">
<h:inputTextrendered="#{bean.customer.titleeq
'OTHER'}"
value="#{bean.customer.titleOther}"
required="true"/>
</h:panelGroup>
...
<h:commandButtonvalue="Save"action="#{bean.save}"/>
</h:form>
Thisconstructwon’tworkwhenthemanagedbeanisrequestscopedandisthusrecreatedoneveryrequest.Whenthedrop-downchanges,itcreatesanewinstanceoftherequest-scopedbean,setsthetitlethere,andrendersthefreetextfieldandfinallytherequest-scopedbeaninstancegetsdestroyed.Whentheformsubmits,itcreatesanewinstanceoftherequest-scopedbean,thuswithoutthecustomertitle,andwhenJSFcheckstherenderedattributeduringtheapplyrequestvaluesphase(secondphase)ofthefreetextfieldandnoticesit’sfalse,ultimatelyitwon’tprocessthefreetextfieldatall.Itwillonlyworkwhenthemanagedbeanisviewscopedbecausethecustomertitlesetduringthedrop-downchangeisstillavailableduringtheapplyrequestvaluesphase(secondphase)oftheformsubmit.
Thereis,however,awork-around.YoucouldlettherenderedattributechecktheHTTPrequestparameterinsteadofthemodelvalue.AsexplainedinChapter4,theHTTPrequestparameternameisspecifiedbythecomponent’sclientID.Youcouldbindthedrop-downcomponenttotheviewandthenuseitsclientIDtoobtaintheHTTPrequestparametervalue.
<h:selectOneMenubinding="#{title}"value="#
{bean.customer.title}">
<f:selectItemsvalue="#{Title}"/>
<f:ajaxrender="other"/>
</h:selectOneMenu>
<h:panelGroupid="other">
<h:inputTextrendered="#{param[title.clientId]eq
'OTHER'}"
value="#{bean.customer.titleOther}"required="true"
/>
</h:panelGroup>
Thisway,whenJSFcheckstherenderedattributeduringtheapplyrequestvaluesphase(secondphase)oftheformsubmit,itwillnoticeit’strueandcontinueprocessingthefreetextfield,evenwhenthemanagedbeanisrequestscopedandthus#{bean.customer.title}isstillnullatthatpoint.Notethatthebindingattributedoesn’treferenceamanagedbeanproperty.Thisisunnecessaryasitwouldn’tbeusedoverthere.AllofthisisalsoapplicableonthereadonlyattributeofanyUIInputcomponentandthedisabledattributeofanyUIInputandUICommandcomponent.
Theremayalsobecaseswhereinarequest-scopedmanagedbeanwillworkjustfinebutimposesariskofacorruptedstateascomparedtoaview-scopedmanagedbean,certainlywhenrelyingondatacomingfromashareddatabasewhichcouldbemutatedbyotherusers.ThisaffectsprimarilyusecaseswherebyUIInputorUICommandcomponentsarenestedinaniteratingcomponentsuchas<h:dataTable>,<ui:repeat>and<c:forEach>whichiteratesoveramodelcomingfromthedatabase.ThiswasexplainedpreviouslyinChapter6,butforthesakeofrefreshment,wewillexplainitoncemore.Imagineatableofproductswithadeletebuttoninacolumn.
<f:formid="list">
<h:dataTableid="products"value="#{products.list}"
var="product">
...
<h:column>
<h:commandButtonid="delete"value="Delete"
action="#{products.delete(product)}">
<f:ajaxrender="@namingcontainer"/>
</h:commandButton>
</h:column>
</h:dataTable>
</h:form>
Withthisbackingbean:
@Named@ViewScoped
publicclassProductsimplementsSerializable{
privateList<Product>list;
@Inject
privateProductServiceproductService;
@PostConstruct
publicvoidinit(){
list=productService.list();
}
publicvoiddelete(Productproduct){
productService.delete(product);
list.remove(product);
}
publicList<Product>getList(){
returnlist;
}
}
Thesubmittedbuttonisunderthehoodidentifiedbytheiterationindex.WhenJSFisabouttoprocesstheformsubmit,andaproducthasbeenaddedorremovedorevenreorderedinthemeanwhile,causingtheiterationindextobechanged,thentheinvokedactionwouldpossiblybeperformedagainstthewrongitemcurrentlyattheinitiallyknownindex.Thisisdangerousfortheintegrityofthemodel.Insuchacasethevalueoftheiterationcomponentmustreferaview-scopedmodel.
Alsohere,thereisawork-around.Insteadofrelyingontheiterationindex,youcanalsorelyontheuniqueidentifieroftheiteratedobjectwhichmustbepassedasanHTTPrequestparameterinsteadofasanELmethodargument.
<f:formid="list">
<h:dataTableid="products"value="#{products.list}"
var="product">
...
<h:column>
<h:commandButtonid="delete"value="Delete"
action="#{products.delete}">
<f:paramname="id"value="#{product.id}"/>
<f:ajaxrender="@namingcontainer"/>
</h:commandButton>
</h:column>
</h:dataTable>
</h:form>
Wherebythebackingbeanisadjustedasfollows:
@Named@RequestScoped
publicclassProducts{
privateList<Product>list;
@Inject@ManagedProperty("#{param.id}")
privateLongid;
@Inject
privateProductServiceproductService;
@PostConstruct
publicvoidinit(){
list=productService.list();
}
publicvoiddelete(){
productService.delete(id);
list.removeIf(product->product.getId().equals(id));
}
publicList<Product>getList(){
returnlist;
}
}
No,theaction="#{products.delete(product.id)}"insteadofusing<f:param>won’twork.Thetechnicalreasonisthat<f:param>isexecutedimmediatelyduringtherenderresponsephaseoftheform,longbeforetheenduserpressesthedeletebutton.Thus,atthemomenttheenduserpressesthedeletebutton,it’sguaranteedtohavethecorrectvalue.TheELmethodargument,onthecontrary,isonlyevaluatedaftertheenduserhaspressedthedeletebutton.Whenthemodelhaschangedinthemeanwhile,itwouldthusevaluatetothewrongIDwhentheiterationindexoftheparticularproducthaschanged.
Asexplainedinthebeginningofthissection,thestandardJSFview-scopedbeanmanagementfacilityhasthustwomajor
disadvantages:first,theinstancesdon’timmediatelyexpirewhentheenduserunloadsthewebpageandstickaroundintheHTTPsession;second,evenwithclient-sidestatesavingenabledtheyarestoredintheHTTPsession.ThoseproblemsarecurrentlynotyetsolvedinthestandardJSFAPI.
Fornow,theJSFutilitylibraryOmniFacesoffersanenhanced@ViewScopedannotationwhichsolvesthosetwodisadvantages. View-scopedmanagedbeansannotatedwith@org.omnifaces.cdi.ViewScopedwillactuallygetdestroyedwhentheenduserunloadsthepage.ThisisunderthehooddonewithhelpofNavigator.sendBeaconAPI inJavaScript,andaspecializedViewHandlerimplementationprovidedbyOmniFaceswhichlistensonthoseunloadrequests.Therehavebeenproductionapplicationsmakingheavyuseofview-scopedmanagedbeanswherebythememoryusagehasreducedforupto80%afterswitchingfromstandardJSF@ViewScopedtoOmniFaces@ViewScoped.Thismakesthedestroy-on-unloadfeatureamajorcandidatetobeaddedtothefutureversionofthestandardJSFAPI.
Inordertosavethephysicalview-scopedmanagedbeanintheJSFviewstatewhenclient-sidestatesavingisenabled,thesaveInViewStateattributeoftheOmniFaces@ViewScopedannotationmustbesettotrue.Youonlyneedtokeepinmindthatthosebeanswillneverexpire,notevenwhenthepagegetsunloaded,orwhentheHTTPsessionexpires.Infact,theentirebeanhasphysicallybecomepartofthegeneratedHTMLoutput,inthejavax.faces.ViewStatehiddeninputfield.TherehavebeencommunityrequeststomakeJSFstatemanagementmoreflexible,suchastogglingbetweenclient-andserver-sidestate
9
10
savingonaper-view(perUIViewRoot)orevenperform(perUIForm)basis,andbeingabletostoreview-scopedmanagedbeansintheactualviewstateinsteadofintheHTTPsession.ThismayalsobereconsideredinafutureversionofthestandardJSFAPI.
@REQUESTSCOPEDArequest-scopedmanagedbeanisamongotherstiedtothelifetimeoftheHTTPrequest,whichisforJSFthemostimportantcase.OthercasesincludethelifetimesofacalltoanEJBasynchronousmethodinvocation(methodannotatedby@Asynchronous),anEJBtimertimeoutmethod,orwhenamessage-drivenbean(MDB)processesamessage.NotethatanAjaxrequestalsocountsasasingleHTTPrequest.
WhentheclientsendsanHTTPrequesttotheserver,theservletcontainerwillcreateHttpServletRequestandHttpServletResponseinstancesrepresentingtheHTTPrequestandresponse,andpassthemthroughtheauthenticationmodules,filters,andservlets.Theywillbedestroyedimmediatelyafterallauthenticationmodules,filters,andservletsarefinishedprocessingtherequestandresponse.Inotherwords,everyHTTPrequestcreatesanewinstanceofarequest-scopedmanagedbeanwhichisavailableonlyduringthatrequestandnotinotherrequests.
Request-scopedmanagedbeansareusefulforsimpleandstaticformswhichdon’thaveanydynamicAjax-basedupdates,forwhichyouwouldratheruseaview-scopedmanagedbean.Thinkofaloginformoracontactform.
@Named@RequestScoped
publicclassLogin{
privateStringusername;
privateStringpassword;
//...
}
Sure,thoseformscanbetiedtoaview-scopedmanagedbeanaswellwithoutproblems,butthat’sawasteofmemoryspace.NotethatyoushouldabsolutelynotmaketheJPAentityitselfamanagedbean.Inotherwords,thefollowingapproachisfactuallywrong:@Named@RequestScoped@Entity
publicclassProduct{
//...
}
NotonlydoesthisviolatetheLawofDemeter, butitalsorisksthatJPAwon’tbeabletopersistit,becauseCDIactuallywrapsthemanagedbeaninaproxyclassandJPAwouldthennotbeabletoobtaintheentityinformationfromitwhenyou’reabouttopassaninjectedinstancetoJPA.Hibernatewouldinsuchcasethrow“Unknownentity:com.example.Entity$Proxy$_$$_WeldClientPro
xy”,whichthusactuallyrepresentstheCDIproxyclass.YoumightatthispointwonderhowexactlyCDIactually
works.First,itwillduringthewebapplication’sstartupcollectallclassesthatareannotatedwithaCDI-compatiblescopeannotation.Then,itwillgenerateproxyclassesforallofthem.Ultimatelyinstancesofthoseproxyclassesarebeinginjected.Givenanexamplebeanclasscom.example.Bean,the
11
generatedCDIproxyclassmaylookasfollows:publicclassBean$Proxy$_$$_CDIextendsBeanimplementsSerializable
{
publicStringgetSomeProperty(){
BeanactualInstance=CDI.resolveItSomehow();
returnactualInstance.getSomeProperty();
}
publicvoidsetSomeProperty(StringsomeProperty){
BeanactualInstance=CDI.resolveItSomehow();
actualInstance.setSomeProperty(someProperty);
}
}
Yousee,itextendsthebeanclass,makesitserializable,andusesan“impossibletoclash”classnameandletsallmethodsdelegatetotheactualinstanceobtainedfromtheCDIcontext.You’llprobablyalsoimmediatelyunderstandwhytheCDIbeanmanagementfacilityrequiresthebeanclassestobepublicandhaveapublicdefaultconstructor.You’llalsoseethatwhensuchaproxyclassiscreatedandinjected,theunderlyingactualinstanceisnotnecessarilycreated.It’sonlyautomaticallycreatedwhenthefictiveCDI.resolveItSomehow()methodisinvoked.Underthehood,itwillobtainthecontextfromathreadlocalvariable,exactlyhowFacesContext#getCurrentInstance()works.
Bytheway,EJBalsoworkswithserializableproxiesthisway.That’swhyitcouldseeminglymagicallyperformalltheheavyliftingofstartingorjoiningatransactionandusepooled
instances.ThelegacyJSF@ManagedBeanfacility,however,didnotuseproxiesatall.That’sexactlywhyitwasimpossibletoinjectaJSFmanagedbeanofanarrowerscopeinaJSFmanagedbeanofabroaderscope.WithCDIbeanmanagementfacilitythisisjustpossible.
NotethatCDIhasalsoa@javax.enterprise.inject.Modelstereotypeannotationwhichbasicallybundlesboth@Namedand@RequestScopedintoasingleannotation.Thisisinnowaydifferentfromarequest-scopedmanagedbean.Unfortunately,itdoesnotrepresentanon-proxyinstance;[email protected]@Modelannotationexistsjustforconvenience.
@DEPENDENTAdependent-scopedmanagedbeanistiedtothelifetimeofthescopewhereinit’sbeingcreatedforthefirsttime.So,ifyouinjectitintoan@ApplicationScoped,thenitwillbecomeapplicationscopedtoo.Andifyouinjectitintoa@ViewScoped,itwillbecomeviewscopedtoo.Andsoon.ThisisthedefaultCDIscope.
Thishas,however,acaveat.WhenyouforgettodeclaretheCDIscopeannotationonyourbackingbean,orimportascopewithexactlythesamenamefromthewrongpackage,e.g.,javax.faces.bean.RequestScopedinsteadofjavax.enterprise.context.RequestScoped,andyoureferenceitdirectlyinEL,asin#{dependentScopedBean},insteadofreferencingitviaanothermanagedbean,asin#{requestScopedBean.dependentScopedBean},
theneveryELevaluationwillbasicallycreateabrand-newinstancewhichexistsonlywithinthatELcontext.Inotherwords,imagineaJSFformwithtwoinputfieldsandasubmitbutton,eachboundtoadependent-scopedmanagedbean,thenyouwilleffectivelyendupwiththreeseparateinstances.Onewhereinthefirstinputfieldisset,onewhereinthesecondinputfieldisset,andonewhereintheactionmethodisinvoked.So,ifyoueverobserveoddbehaviorofnullsubmittedvaluesintheactionmethodeventhoughtherequiredvalidationhaspassed,thefirstthingtocheckistheactuallyusedCDImanagedbeanscope.
Themajortechnicaldifferencewithotherscopesisthatdependent-scopedmanagedbeansarenotproxied.Inotherwords,what’sbeinginjectedistheactualinstance.
@Dependent
publicclassEntities{
@Produces
publicProductgetProduct(){
returnnewProduct();
}
}
@Named@RequestScoped
publicclassProducts{
@Inject
privateProductproduct;
@Inject
PrivateProductServiceproductService;
publicvoidadd(){
productService.create(product);
}
publicProductgetProduct(){
returnproduct;
}
}
Notethatyoustillcan’tuse<h:inputTextvalue="#{product.name}">,becauseitwouldgetitsowninstance.Youstillneedtouse#{products.product.name}.Forexactlythisreason,theproducerisn’[email protected],you’dneedtoforceJSFtorestarttheviewscopebyreturninganon-nulloutcomefromactionmethod;otherwisetheinjectedProductinstancewouldbereusedforthenextview.
Whichscopetochoose?Whichscopetochoosedependssolelyonthedata(instancevariablesakathestate)thebeanholdsandrepresents.Youshouldstrivetoputthestateintheshortestpossibleacceptablescope.Startwitha@RequestScopedbean.Onceyounoticethatsomestateneedstoberetainedafterapostbackonthesameview,splitthatstateexactlyintoanew@ViewScopedbeanwhichyou,inturn,@Injectinthe@RequestScopedbean.OnceyounoticethatsomestateneedstoberetainedonanotherGETrequestwithinthesamesession,splitthatstateexactlyintoanew@ConversationScopedbeanwhichyouinturn@[email protected].
Abusingan@ApplicationScopedbeanforsession-,conversation-,flow-,view-,orrequest-scopeddatawouldmakeittobesharedamongallusers,soanyoneelsecansee
eachother’sdata,whichisjustplainwrong.Abusinga@SessionScopedbeanforconversation-,flow-,view-,orrequest-scopeddatawouldmakeittobesharedamongallbrowsertabsinthesamesession,sotheendusermayexperienceinconsistencieswheninteractingwitheveryviewafterswitchingbetweentabs,whichisbadforuserexperience.Abusinga@RequestScopedbeanforview-,flow-,orconversation-scopeddatawouldmakeview-,flow-,orconversation-scopeddatabereinitializedtodefaultoneverysingle(Ajax)postback,causingpossiblynon-workingforms.Abusinga@ViewScoped,@FlowScoped,or@ConversationScopedbeanforrequest-,session-,orapplication-scopeddata,andabusinga@SessionScopedbeanforapplication-scopeddatadoesn’taffecttheenduser,butitunnecessarilyoccupiesservermemoryandisplaininefficient.
Notethatthescopeshouldrathernotbechosenbasedonperformanceimplications,unlessyoureallyhavealowmemoryfootprintandwanttogocompletelystateless.You’dthenneedtoexclusivelyusestatelessformswith@RequestScopedbeansandfiddlewithrequestparameterstomaintainanyclient’sstate.Inotherwords,youwouldpossiblyneedtoreinventwhateveralreadyisbeingdonebythejavax.faces.ViewStatehiddeninputfield.
WhereIs@FlashScoped?Atlast,JSFalsosupportstheflashscope.Itisbackedbyashortlivingcookiewhichisassociatedwithadataentryinthesessionscope.Beforetheredirect,acookiewillbesetontheHTTPresponsewithavaluewhichisuniquelyassociatedwith
thedataentryinthesessionscope.Aftertheredirect,thepresenceoftheflashscopecookiewillbecheckedandthedataentryassociatedwiththecookiewillberemovedfromthesessionscopeandbeputintherequestscopeoftheredirectedrequest.FinallythecookiewillberemovedfromtheHTTPresponse.Thiswaytheredirectedrequesthasaccesstorequest-scopeddatawhichwasbeenpreparedintheinitialrequest.
ThisisactuallynotavailableasamanagedbeanscopebystandardJSFAPI.Inotherwords,thereisnosuchthingas@FlashScoped.TheflashscopeisonlyavailableasamapviaExternalContext#getFlash()inmanagedbeansand#{flash}inEL.Historically,theflashscopewasprimarilyintroducedinordertobeabletoshowafacesmessagesetintheactionmethodintheredirectedpage.Imaginetheusecaseofsavinganeditedproductinadetailpageandredirectingbacktothemasterpage.
publicStringsave(){
FacesContextcontext=FacesContext.getCurrentInstance();
try{
productService.update(product);
context.addMessage(null,newFacesMessage("Product
saved!"));
return"/products?faces-redrect=true";
}
catch(Exceptione){
context.addMessage(null,newFacesMessage(
"Cannotsaveproduct.Error:"+
e.getMessage()));
returnnull;
}
}
Thefacesmessage“Productsaved!”wouldn’tshowupinthe<h:messagesglobalOnly>oftheredirectedpagebecausefacesmessagesareinherentlyrequestscoped(actually,“facescontextscoped”).Historically,duringtheJSF1.xera,thisproblemwassolvedwithaphaselistenerwhichcopiesaftertherenderresponsephaseallundisplayedfacesmessagesintotheHTTPsessionandre-addsaftertherestoreviewphaseanyofthembackintothefacescontext.SincetheintroductionoftheflashscopeinJSF2.0,thisproblemcouldbesolvedinaneasierwaybysimplyinvokingFlash#setKeepMessages() .
productService.update(product);
context.addMessage(null,newFacesMessage("Productsaved!"));
context.getExternalContext().getFlash().setKeepMessages(true)
;
return"/products?faces-redrect=true";
Thisway,thefacesmessagesarebeforeredirectautomaticallystoredintheflashscopeandrestoredafterredirect.
Theflashscopeisnotonlyusefulforfacesmessages.It’salsousefulforpassingentireobjectswhileredirectingfromoneviewtoanotherview,withoutneedingtopasssomeobjectidentifierasarequestparameter.Followingisanexamplewhichpreparesanentityforthenextstepwithoutneedingtosaveitinthedatabasefirst:@Named@RequestScoped//or@ViewScoped
publicclassHome{
privateProductproduct=newProduct();
12
publicStringprepareProduct(){
FacesContextcontext=
FacesContext.getCurrentInstance();
context.getExternalContext().getFlash().put("pro
duct",product);
return"/next?faces-redirect=true";
}
publicProductgetProduct(){
returnproduct;
}
}
Wherebythebeanofthenextsteplooksasfollows:
@Named@ViewScoped
publicclassNextimplementsSerializable{
@Inject@ManagedProperty("#{flash.product}")
privateProductproduct;
publicvoidsave(){
//...
}
publicProductgetProduct(){
returnproduct;
}
}
Andthe/next.xhtmlredirectsbackto/home.xhtmlwhentheentityisabsentintheflashscope.
<f:metadata>
<f:viewActionaction="home"if="#{emptyflash.product}">
</f:metadata>
Notethatthisredirectwilltakeplacewhenyouopen/next.xhtmldirectly,orwhenyourefreshthepageinthewebbrowser.Incaseyou’dliketoavoidthat,youcaninstructtheflashscopetokeeptheentryvalueintheflashscopebyprefixingtheentrykeywiththepredefined“keep”keyonthe#{flash}map.
@Named@RequestScoped
publicclassNext{
@Inject@ManagedProperty("#{flash.keep.product}")
privateProductproduct;
//...
}
Thisway,thelifetimeoftheflashscopewillexpanduntiltheenduserclosesthebrowserwindow,orwhentheapplicationnavigatestoadifferentview,orwhentheunderlyingHTTPsessionexpires.Thiswayyoucanevenmakethemanagedbeanrequestscopedinsteadofviewscopedandnotlosetheentitywhilesubmittingaforminthe/next.xhtmlpageorevenrefreshingthepage.Thisisarelativelypowerfulfeatureoftheflashscope.
ManagedbeaninitializationanddestructionManagedbeaninstancescanbeinitializedbasedoninjecteddependenciesina@PostConstructannotatedmethod.Managedbeaninstancescanhookondestroyeventina@PreDestroyannotatedmethod.
@Named
publicclassBean{
@PostConstruct
publicvoidinit(){
//...
}
@PreDestroy
publicvoiddestroy(){
//...
}
}
Themethodnamesarenotpredefined.Themethodnamesinit()anddestroy()arebasicallytakenoverfromtheHttpServlet.Youcanofcoursechooseyourown,suchasonload()andcleanup().It’susefultoknowthatthoseannotationsareinheritable.Inotherwords,youcouldputthosemethodsandannotationsinanabstractbaseclass.
Inthepostconstructmethodyouhavetheopportunitytoperforminitializationbasedoninjecteddependencies.Theyarenotavailableintheconstructoryet.Thebeanmanagementfacilitycanonlyinjectthedependenciesafterhavingconstructedthemanagedbeaninstance.Itwillthenimmediatelyinvokethe@PostConstructannotatedmethod.Inthepre-destroymethodyouhavetheopportunitytoperformanynecessarycleanup,suchasclosingresources,deletingfiles,etc.
InjectingJSFvendedtypesBackingbeanscanobviouslybeinjectedasdemonstrated
manytimesoverintheexamplesabove.Nexttoinjectingyourowntypes,avarietyofJSFvendedtypescanbeinjectedviaCDIaswell.ThesetypeslargelycorrespondtotheimplicitELobjectswesawinTable7-1inChapter7.Thisisnocoincidence.InternallyimplicitobjectsinJSFareimplementedbyso-calledBean<T>instancesfromCDI.TheseCDIBean<T>instancesareeffectivelythefactoryobjectsthatknowhowtogeneratebeans,withwhattypeandoptionalqualifierand/orwithwhatname.
Whenthenameofanimplicitobjectisusedinexpressionlanguage,theCDIELresolverdoesalookupforthatobjectbyname,whichresultsinacalltoacertainBean<T>instance.Whenwe’reinjecting,thetypeintowhichweinject,togetherwithanyexplicitorimplicitqualifiers,formsanalternativekeythat’sbeingusedforthislookup.BothtypesofkeyswillresultintheexactsameCDIBean<T>instancebeingused.
ItshouldbenotedthatcomparedtotheimplicitELobjectsmentionedinTable8-1afewaremissingforCDI-injectableJSFvendedtypes,namely:
#{component}
#{cc}
#{request}
#{session}
#{application}
Both#{component}and#{cc}resolvetoUIComponentwhichisnotinjectable,sincethiswouldrequireaspecialproxyorcustomscopethat’snarrowenoughtoresolvethe“current”instanceofthoseatthetimetheinjectedtypeisaccessed.SincesuchscopeisnotavailableinJSFyet,andtherewasonlyalittletimeandfewresources
remainingtofinishtheJSF2.3spec,thesehadbeenexcludedfromCDIinjection.
The#{request},#{session},and#{application},respectively,representingHttpServletRequest,HttpSession,andServletContextinaServletcontainerhavebeenomittedsincethesetypesarenotownedbyJSFandthereforeJSFshouldnotprovideCDIinjectioncapabilitiesforthose.ThefactthatJSFdoesprovideimplicitELobjectsforthoseismostlyhistorical.TheonlyspecificationthatshouldprovideinjectionforthosetypesistheServletAPI,whichownsthosetypesdirectly.
Table8-1showstheJSFvendedtypesthatareinjectableviaCDI.
Table8-1 InjectableJSFVendedtypes,AllSince2.3
InjectableJSFtype
Resolvesto
javax.faces.context.FacesContext
FacesContext#getCurrentInstance( )
javax.faces.context.ExternalContext
FacesContext#getExternalContext( )
javax.faces.component.UIViewRoot
FacesContext#getViewRoot( )
javax.faces.context.Flash
ExternalContext#getFlash( )
@RequestMapMap<String,Object>
ExternalContext#getRequestMap( )
@RequestMapMap<String,Object>
ExternalContext#getRequestMap( )
@ViewMapMap<String,Object>
UIViewRoot#getViewMap( )
@FlowMapMap<Object,Object>
FlowHandler#getCurrentFlowScope( )
@SessionMapMap<String,Object>
ExternalContext#getSessionMap( )
@ApplicationMapMap<String,Object>
ExternalContext#getApplicationMap( )
@InitParameterMapMap<String,String>
ExternalContext#getInitParameterMap( )
@RequestParameterMapMap<String,String>
ExternalContext#getRequestParameterMap( )
@RequestParameterValuesMapMap<String,String[]>
ExternalContext#getRequestParameterValuesMap( )
@HeaderMapMap<String,String>
ExternalContext#getRequestHeaderMap( )
@HeaderValuesMapMap<String,String[]>
ExternalContext#getRequestHeaderValuesMap( )
String[]>
ValuesMap( )
@RequestCookieMapMap<String,Cookie>
ExternalContext#getRequestCookieMap( )
javax.faces.application.ResourceHandler
Application#getResourceHandler( )
AscanbeseenfromTable8-1,forobjectsforwhichthereisonlyoneinstanceandthatinstanceisvended(owned)byJSF,noqualifierisneeded.Whenitconcernsmoregeneraltypes,suchasthevariousmaps,aqualifierisneeded.Allthesequalifierannotationsareavailablefromthejavax.faces.annotationpackage.
Acaveatisthatalloftheabovetypesarerequestscoped,butthetimeduringwhichtheyarevalidisactuallysmaller,namely,fromshortlyafterthemomenttheservice()methodoftheFacesServletiscalleduntilshortlybeforethemomentthatmethodisexited.Careshouldbetakennottoinjectandaccessthesetypesoutsidethattime.It’sexpectedthatafuturerevisionoftheJSFspecwilladdressthisproblem.
ThefollowingshowsanexampleofinjectingtwooftheJSFvendedtypes:
@Named@RequestScoped
publicclassBean{
@Inject
privateFlashflash;
13
@Inject@RequestParameterMap
privateMap<String,String>requestParameterMap;
publicvoidsomeMethod(){
if(requestParameterMap.containsKey("something")){
flash.put("aKey","aValue");
}
}
}
EagerInitializationManagedbeansarebydefaultlazilyinitializedwhenevertheyare,forthefirsttime,referencedinanELexpressionorasaninjecteddependency.Managedbeanscanbeeagerlyinitializedonthestartofanyscopebyanobservermethodwhichobservestheinitializationofthescopeofinterest.Oneexamplewaspreviouslygiveninthe“@ViewScoped”section.Themethodpatternisasfollows:publicvoidstartup(@Observes@Initialized(XxxScoped.class)Sscope)
{
//...
}
WhereXxxScoped.classcanbeanyCDI-compatiblescopeandtheSrepresentstheownerofthescope.Forthefollowingscopesthatarethus:
ApplicationScoped.class–javax.servlet.ServletContext
SessionScoped.class–javax.servlet.http.HttpSession
ConversationScoped.class–javax.servlet.ServletRequest
FlowScoped.class–javax.faces.flow.Flow
ViewScoped.class–javax.faces.component.UIViewRoot
RequestScoped.class-javax.servlet.ServletRequest
Notethatthecontainingbeanmustbeofatleastthesamescopeinordertohave@Observes@Initializedtakeeffect.Eagerinitializationhasforanapplication-scopedmanagedbeantheadvantagethatyoucanconfigureitasa“startup”beanwithoutneedingtoreferenceitinsomeotherbeaninordertogetitinitialized.
@ApplicationScoped
publicclassStartup{
publicvoidcontextInitialized
(@Observes@Initialized(ApplicationScoped.class)
ServletContextcontext)
{
//...
}
}
Eagerinitializationhasforarequest-scopedbeantheadvantagethatyoucanifnecessaryinvokeanasynchronousDBquerylongbeforetheFacesServletisinvoked.
@Named@RequestScoped
publicclassEagerProducts{
privateFuture<List<Product>>list;
@Inject
privateProductServiceproductService;
publicvoidrequestInitialized
(@Observes@Initialized(RequestScoped.class)
HttpServletRequestrequest)
{
if
("/products.xhtml".equals(request.getServletPath())){
list=productService.asyncList();
}
}
publicList<Product>getList(){
try{
returnlist.get();
}
catch(InterruptedExceptione){
Thread.currentThread().interrupt();
thrownewFacesException(e);
}
catch(ExecutionExceptione){
thrownewFacesException(e);
}
}
}
WheretheProductServicelookslikethis:@StatelesspublicclassProductService{
@TransactionAttribute(SUPPORTS)
publicList<Product>list(){
returnentityManager
.createQuery("FROMProductORDERBYid
DESC",Product.class)
.getResultList();
}
@Asynchronous
publicFuture<List<Product>>asyncList(){
returnnewAsyncResult<>(list());
}
}
NoteparticularlytherequestInitialized()methodwhichobservesthestartofanyrequestscopeandthusneedstodeterminetheactualpathbeforehandsothatitdoesn’tunnecessarilyhitthebusinessservice.Inthisspecificexample,thatwillonlyhappenoncetherequesthits/products.xhtml.ThatJSFpagecaninturnjustreferencetheproductlistasusual.
<h:dataTablevalue="#{eagerProducts.list}"var="product">
...
</h:dataTable>
WhenopeningthisJSFpage,therequestscopedbeanwillbeinitializedbeforetheFacesServletisinvokedandasynchronouslyfetchtheList<Products>fromthedatabase.Dependingontheserverhardwareused,theavailableserverresourcesandallcoderunningbetweentheinvocationofthefirstservletfilterandenteringtheJSFrenderresponse,thisapproachmaygiveyouatimespaceof10ms~500ms(orperhapsevenmorewhenthere’ssomeinefficientcodeinthepipeline)tofetchdatafromDBinadifferentthreadparallelwiththeHTTPrequest,andthusaspeedimprovementequivalenttothetimetheDBneedstofetchthedata(whichthusdoesn’tneedtobedoneinthesamethreadastheHTTPrequest).
LayersWhileimplementingbackingbeans,it’sveryimportantto
understandtheimportanceoftheseparationoftheJSFbackingbeanfromtheJPAentityandtheEJBservice.Inotherwords,whendevelopingbackingbeans,youshouldmakesurethatyourbackingbeansareasslickaspossibleandthattheydelegateasmuchaspossiblemodelpropertiestoJPAentitiesandbusinesslogictoEJBservices.YoushouldrealizethatJPAentitiesandEJBservicesshouldbefullyreusableonacompletelydifferentfrontendthanJSF,suchasJAX-RSorevenplainJSP/Servlet.
Thisthusalsomeansthatyoushouldmakesurethatyoudon’tdirectlyorindirectlyincludeaJSF-specificdependencyinaJPAentityoranEJBservice.Forexample,thefollowingapproachisfactuallywrong:@EntitypublicclassProduct{
privateStringname;
privateStringdescription;
publicstaticProductof(ProductBackingbackingBean)
{
Productproduct=newProduct();
product.setName(backingBean.getName());
product.setDescription(backingBean.getDescriptio
n());
returnproduct;
}
//...
}
Here,theJPAentityistightlycoupledtoaJSFbackingbean.
Notonlyaretheentitypropertiesreusedinthebackingbean,butalsotheentityhasadependencyonthebackingbean.Itwouldn’tbepossibletoextracttheentityintoaJARmodulewhichisreusableacrossdifferentJSFwebapplications.
Andthefollowingapproachisalsofactuallywrong:
@Stateless
publicclassProductService{
@Inject
privateEntityManagerentityManager;
publicvoidcreate(Productproduct){
entityManager.persist(product);
FacesContext.getCurrentInstance().addMessage(null,
newFacesMessage("Productcreated!"));
}
}
Here,theEJBpokesthefacescontextandsetsamessagethere.ThiswouldfailwithaNullPointerExceptionwhentheEJBmethodisbeinginvokedfrom,e.g.,aJAX-RSserviceoraServlet,becausethere’snoinstanceofthefacescontextanywhereoverthere.ThisUImessagingtaskisnottheresponsibilityoftheback-endcodebutofthefront-endcode.Inotherwords,addingafacesmessageshouldonlyhappenintheJSFartifact,suchasabackingbean.
Thecorrectapproachisasfollows,asdemonstratedpreviouslyinthesection“Model,View,orController?”:@Named@RequestScoped
publicclassProductBacking{
privateProductproduct=newProduct();
@Inject
privateProductServiceproductService;
publicvoidsave(){
productService.create(product);
FacesContext.getCurrentInstance().addMessage(nul
l,
newFacesMessage("Productcreated!"));
}
publicProductgetProduct(){
returnproduct;
}
}
NamingConventionsThereisnostrictconventionspecifiedbyJSFitself.I’veseenthefollowingconventionsacrossvariousJSFprojects:
Foo
FooBean
FooBacking
FooBackingBean
FooManager
FooManagedBean
FooController
wherebyFoocaninturnrepresentoneofthefollowing:JSFviewID,
e.g.,EditProductforeditproduct.xhtml
JSFviewname,e.g.,Productsforviewproducts.xhtml
JPAentityname,e.g.,Productfor@EntityclassProduct
JSFformID,e.g.,EditProductfor<h:formid="editProduct">
Firstandforemost,namesendingwithBeanlikeFooBean,FooBackingBean,andFooManagedBeanmustbeavoidedtoallextent.The“bean”suffixissuperfluousandtooambiguousaspracticallyanyclassinJavacanbemarkedasaJavaBean.Youdon’timmediatelyuse“ProductBean”foryourJPAentityor“ProductServiceBean”oreven“ProductServiceEnterpriseBean”foryourEJBservice,right?True,#{bean}or#{myBean}oreven#{yourBean}toindicateaJSForCDImanagedbeanisveryoftenusedacrossgenericcodeexamplesinblogs,forums,Q&Asites,andeventhisbook.Butthisismerelydoneforclarityandsimplicityofthecodesnippets.
ThatleavesuswithFoo,FooBacking,FooManager,andFooController.Allareequallyacceptable.Personally,ItendtouseFooBackingforrequest-,view-,flow-,andconversation-scopedbeans,andFooManagerforsession-andapplication-scopedbeans.AstothenamingconventionofFoopart,thatgenerallydependsonwhetherthebackingbeanistightlytiedtoaparticularJSFvieworaJSFform,orgenerallyreusableacrossmultipleJSFviewsorformsreferringaparticularentity.
Inanycase,thisisaprettysubjectiveitemwhichcanhardlybeansweredobjectivelywith“theOneandCorrect”answer.Itreallydoesn’tmatterthatmuchtomeoranyoneelse
whatyoumakeofit,aslongasyou’reconsistentwithitthroughouttheentireproject.
Footnotes1
https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%9
3controller.
2https://stackoverflow.com/q/3106452/157882.
3https://javaee.github.io/javaee-
spec/javadocs/javax/inject/Named.html.
4http://balusc.omnifaces.org/2013/10/how-to-install-cdi-
in-tomcat.html.
5
http://butunclebob.com/ArticleS.UncleBob.SingletonVsJust
CreateOne.
6
https://github.com/weld/core/blob/master/modules/jsf/src
/main/java/org/jboss/weld/module/jsf/
ConversationAwareViewHandler.java.
7https://javaee.github.io/javaee-
spec/javadocs/javax/faces/flow/builder/FlowBuilder.html.
8https://stackoverflow.com/q/4105439/157882.
9http://showcase.omnifaces.org/cdi/ViewScoped.
10https://developer.mozilla.org/en-
US/docs/Web/API/Navigator/sendBeacon.
11https://en.wikipedia.org/wiki/Law_of_Demeter.
12https://javaee.github.io/javaee-
spec/javadocs/javax/faces/context/Flash.html#setKeepMess
ages-boolean-.
13https://javaee.github.io/javaee-
spec/javadocs/javax/faces/annotation/package-
summary.html.
(1) (2)
©BaukeScholtz,ArjanTijms2018BaukeScholtzandArjanTijms,TheDefinitiveGuidetoJSFinJavaEE8,https://doi.org/10.1007/978-1-4842-3387-0_9
9.ExceptionHandling
BaukeScholtz andArjanTijms
Willemstad,Curaçao Amsterdam,Noord-Holland,TheNetherlands
Sometimesthingscanunexpectedlygowrong.InJava,thatusuallymanifestsasanExceptionbeingthrown.InJavaEE,it’sbasicallynodifferent.Theissueishowandwhentoproperlyhandlethem.Bydefault,anyuncaughtexceptionduringanHTTPrequestwillendupinadefaulterrorpageprovidedbytheapplicationserver.Figure9-1showswhatWildFly’sdefaultHTTP500errorpagelookslike:
1 2
Figure9-1 ThedefaultHTTP500errorpageofWildFly11.0.0
TheJSF(JavaServerFaces)implementationbeingusedmayevenprovideitsowndefaulterrorpage.BothMojarraandMyFacesprovideaninternaldefaultimplementationbasedonthejavax.faces.context.ExceptionHandlerAPI, whichisonlyshownwhentheJSFprojectstageissetto1
Development.Figure9-2showshowthatpagelooksforMojarra.
Figure9-2 ThedefaultHTTP500errorpageofMojarra2.3.3indevelopmentstage
ItnotonlyincludesthestacktracebutalsothetextualrepresentationoftheJSFcomponenttreeandanyscopedvariables,whichmightbehelpfulinnailingdowntherootcause,although,inreality,thestacktracealoneandre-executingtheusecasewithadebuggerismuchmorehelpfulthanthat.
CustomErrorPagesWhileusefultotheaveragewebdeveloper,thosedefaulterrorpagesare,quitefrankly,scarytotheaverageenduser.The“look’n’feel”iscompletelyofffromtherestofthesiteandthetextislikeabracadabratotheaverageenduser.Suchanerrorpagedoesn’tevenprovideescapepointsfortheenduser.Thedisgruntledendusercannotquicklyfinditswaytothehomeorcontactpage.Fortunatelyfortheenduser,youcanoverridethosedefaulterrorpagesbyincludinganerrorpage
inthewebapplicationandregisteringitslocationinan<errorpage>entryinweb.xml.
<errorpage>
<error-code>500</error-code>
<location>WEB-INFerrorpages/500.xhtml</location>
</errorpage>
Thecustomerrorpageispurposefullybeingplacedina/WEB-INFfoldersothatenduserscan’taccessorbookmarkthemdirectly.Bydefault,theservletcontainerwillseterrorpagerelatedattributesintherequestscopewhosekeysaredefinedasjavax.servlet.RequestDispatcher.ERROR_XXX
constants. Thiswayyoucan,ifnecessary,includetheminthecustomerrorpage.Followingisanexample:<dl><dt>RequestURI</dt>
<dd>#
{requestScope['javax.servlet.error.request_uri']}</dd>
<dt>Exceptiontype</dt>
<dd>#{requestScope['javax.servlet.error.exception']
['class']}</dd>
<dt>Exceptionmessage</dt>
<dd>#
{requestScope['javax.servlet.error.exception'].message}
</dd>
<dt>Stacktrace</dt>
<dd><pre>#{
facesContext.externalContext.response.writer.flu
sh()
}#{
2
requestScope['javax.servlet.error.exception'].pr
intStackTrace
(facesContext.externalContext.response.write
r)
}</pre></dd>
</dl>
Notethetricktoprintthestacktrace.It’simportantthattheresponsewriterisflushedbeforeprintingthestacktrace,andthatthere’snowhitespaceintemplatetextoutsidetheEL(ExpressionLanguage)expressionswithinthe<pre>element,otherwiseitwouldbeappendedtothestacktrace.
Comingbacktothescarinessofsuchanerrorpage,you’dbetterhideawayalltheerrordetailbehindaconditionthatevaluatesonlytruewhentheJSFprojectstageequalsDevelopment.Firstsetanapplication-scopedshortcutvariableinsomemastertemplate:
<c:setvar="DEV"scope="application"
value="#{facesContext.application.projectStageeq
'Development'}">
</c:set>
NowyoucanconditionallydisplaytechnicalinformationintheerrorpagewhentheJSFprojectstageequalsDevelopment.
<c:iftest="#{DEV}">
<h3>Errordetailfordeveloper</h3>
<dl>
...
</dl>
</c:if>
Inanycase,it’sveryimportantthattheerrorpageisentirelystateless.Inotherwords,adecenterrorpagemaynotcontainanyJSFforms,notevenalogoutform.NotonlywillyouavoidtheriskthattheformsubmitfailsbecausetheinitialexceptionwasactuallycausedbyacorruptedJSFstate,butthoseformswillactuallysubmittothewrongURL(uniformresourcelocator),namely,theoneoftheerrorpageitself.Astheerrorpageishiddeninthe/WEB-INFfolder,theformsubmitwouldonlyresultina404error.Insteadofmovingtheerrorpageoutsidethe/WEB-INFfolder,youcouldworkaroundthelogoutcasebyusingaplainHTMLformwhichsubmitstoaplainServlet.TheSecurityContextisalsojustinjectableoverthere,asaresession-scopedmanagedbeans,ifany.
AjaxExceptionHandlingBydefault,whenanexceptionoccursduringaJSFAjaxrequest,theenduserwouldnotgetanyformoffeedbackwhetherornottheactionwassuccessfullyperformed.InMojarra,onlywhentheJSFprojectstageissettoDevelopment,theenduserwouldseeabareJavaScriptalertwithonlytheexceptiontypeandmessage(seeFigure9-3).
Figure9-3 TheJavaScriptalertwhenanexceptionisthrownduringanAjaxrequestinMojarra2.3.3inDevelopmentstage
Thisisn’tterriblyuseful.AndintheProductionstagetheenduserwouldn’tevengetanyfeedback.Thewebapplicationwouldfailsilently,leavingtheenduserbehindasifnothinghadhappened,whichisjustconfusingandbadforuserexperience.ItwouldmakemoresenseifexceptionsduringJSFAjaxrequestsarehandledthesamewayasexceptionsduringsynchronousrequests,whichreuseexactlythesameerrorpageastheonedeclaredas<errorpage>inweb.xml.Inotherwords,theendusershouldbeabletoseetheerrorpageinfullglory.ThiscanbeachievedbycreatingacustomExceptionHandlerimplementationwhichbasicallyinstructsJSFtocreateanewUIViewRootbasedontheerrorpageandthenbuildandrenderit.It’sonlyquiteabitofcode.Atitssimplestitcanlookasfollows:
publicclassAjaxExceptionHandlerextends
ExceptionHandlerWrapper{
publicAjaxExceptionHandler(ExceptionHandlerwrapped){
super(wrapped);
}
@Override
publicvoidhandle(){
handleAjaxException(FacesContext.getCurrentInstance()
);
getWrapped().handle();
}
protectedvoidhandleAjaxException(FacesContextcontext)
{
Iterator<ExceptionQueuedEvent>
unhandledExceptionQueuedEvents=
getUnhandledExceptionQueuedEvents().iterator();
if(context==null
||
context.getExternalContext().isResponseCommitted()
||
!context.getPartialViewContext().isAjaxRequest()
||!unhandledExceptionQueuedEvents.hasNext()
){
return;
}
Throwableexception=unhandledExceptionQueuedEvents
.next().getContext().getException();
while(exception.getCause()!=null
&&(exceptioninstanceofFacesException
||exceptioninstanceofELException)
){
exception=exception.getCause();
}
ExternalContextexternal=
context.getExternalContext();
Stringuri=external.getRequestContextPath()
+external.getRequestServletPath();
Map<String,Object>requestScope=
external.getRequestMap();
requestScope.put(RequestDispatcher.ERROR_REQUEST_URI,
uri);
requestScope.put(RequestDispatcher.ERROR_EXCEPTION,
exception);
StringviewId="WEB-INFerrorpages/500.xhtml";
Applicationapplication=context.getApplication();
ViewHandlerviewHandler=
application.getViewHandler();
UIViewRootviewRoot=viewHandler.createView(context,
viewId);
context.setViewRoot(viewRoot);
try{
external.responseReset();
ViewDeclarationLanguageviewDeclarationLanguage=
viewHandler.getViewDeclarationLanguage(contex
t,viewId);
viewDeclarationLanguage.buildView(context,
viewRoot);
context.getPartialViewContext().setRenderAll(true
);
viewDeclarationLanguage.renderView(context,
viewRoot);
context.responseComplete();
}
catch(IOExceptione){
thrownewFacesException(e);
}
finally{
requestScope.remove(RequestDispatcher.ERROR_EXCEP
TION);
}
unhandledExceptionQueuedEvents.remove();
while(unhandledExceptionQueuedEvents.hasNext()){
unhandledExceptionQueuedEvents.next();
unhandledExceptionQueuedEvents.remove();
}
}
publicstaticclassFactoryextends
ExceptionHandlerFactory{
publicFactory(ExceptionHandlerFactorywrapped){
super(wrapped);
}
@Override
publicExceptionHandlergetExceptionHandler(){
returnnewAjaxExceptionHandler
(getWrapped().getExceptionHandler());
}
}
}
ThehandleAjaxException()willfirstcheckifthereisafacescontext,iftheresponseisn’tyetcommitted,iftherequestisanAjaxrequest,andifthere’sanyunhandledexceptioneventinthequeue.Ifnoneofthoseconditionsmatches,itwillreturnandletJSFcontinueasusual.
TheHTTPresponseisconsideredcommittedwhentheresponseorapartthereofhasphysicallyalreadybeensenttotheclient.Thisisapointofnoreturn.Youcan’ttakethealreadysentbytesback.Thismayhappenwhentheexceptionoccurshalfwayduringtherenderresponsephase.ThefirsthalfoftheHTTPresponsemayalreadyhavebeensenttotheclient.AlsothedefaultexceptionhandlerofJSFandtheservercan’tdealwithit.Effectively,theclientgetsahalf-bakedHTMLpage.Bestwhatyoucoulddotoavoidthisistomakesurethatyouaren’tperformingbusinesslogicingettermethodsofbackingbeans,whichonitsownisalwaysabadidea,andthatbackingbeansareinitializedassoonaspossible.Thatcouldbeachievedbyexecutingexception-sensitivebusinesslogicin<f:viewAction>insteadof@PostConstruct.AnotheroptionistoincreasetheHTTPresponsebuffersizetomatchthesizeofthegeneratedHTMLresponseofthelargestexception-sensitivepage.Assumingthatit’s100kB,thefollowingweb.xmlcontextparametercanbeused.
<context-param>
<param-name>javax.faces.FACELETS_BUFFER_SIZE</param-name>
<param-value>102400</param-value><!--100kB.-->
</context-param>
ThenextstepinhandleAjaxException()isextractingtherootcauseofinterestfromtheunhandledexceptioneventsqueue.AnyexceptionthatoccursduringtheprocessingoftheJSFlifecyclewillbewrappedinjavax.faces.FacesException.AnyexceptionthatoccursduringtheevaluationofanELexpressionwillbewrappedinjavax.el.ELException.Thosearenotourinterest.
Next,the#{requestScope['javax.servlet.error.request
_uri']}and#{requestScope['javax.servlet.error.excepti
on']}variableswillbesetsothattheerrorpagecanaccessthem.AlsotheUIViewRootinstancerepresentingtheerrorpagewillbecreatedwithhelpoftheViewHandlerandsetintheJSFcontext.Youcould,ifnecessary,conditionallypreparetheviewIDoftheerrorpagebasedontheactualrootcauseoftheexception.Forexample:StringviewId;
if(exceptioninstanceofViewExpiredException){
viewId="WEB-INFerrorpages/expired.xhtml";
}
else{
viewId="WEB-INFerrorpages/500.xhtml";
}
ComingbacktothehandleAjaxException(),inthe
tryblock,theHTTPresponsebufferwillbecleared,theUIViewRootwillbepopulatedwithcomponents,theAjaxcontextwillbeinstructedtorendertheentireview,theUIViewRootwillberendered,theJSFcontextwillbeinstructedthattheresponseisalreadymanuallytakencareofsothatitwon’tperformanynavigation,andfinallytheexceptionwillberemovedfromtherequestscope.Theremovaloftheexceptioninthefinallyblockisnotmandatory,butservletcontainersexistwhichconsiderthisatriggertowriteaninternalerrorpagetotheresponse,suchasTomcat.
Finally,thequeueofunhandledexceptioneventswillbedrained.Thisisalsonotmandatorybutdoneonpurposesothatitmatchesthedefaultbehaviorofweb.xml-configurederrorpages,andalsotopreventanynextExceptionHandlerinthechainfromhandlinganyremainingexceptionevents.
Inordertogetittorun,youactuallyneedtocreateafactoryaswell.Asmanyotherapplication-widecustomizationsinJSF,customexceptionhandlerscanonlyberegisteredthroughafactory.Itmightlookverbose,butthat’sjustpartofthedesign.Inthisspecificcase,itallowsyoutoreturnadifferentexceptionhandlerimplementationdependingonsomeglobalconfigurationsetting.YoucanfindtheFactoryasanestedclassinthebottomofthepreviouslyshownAjaxExceptionHandlerclass.Itextendsfromjavax.faces.context.ExceptionHandlerFactor
y andcanberegisteredinfaces-config.xmlasfollows:<factory><exceptionhandler-factory>
3
com.example.project.exceptionhandler.AjaxExcepti
onHandler$Factory
</exceptionhandler-factory>
</factory>
Bytheway,itmustbesaidthatsuchanexceptionhandlerisalsosuitableonnon-Ajaxrequests.YoujusthavetoremovethePartialViewContext#isAjaxRequest()check.YouonlyneedtokeepinmindtomanuallysettheHTTPresponsestatuscodeto500dependingonwhetherornotit’sanAjaxrequest.DothisaftertheExternalContext#responseReset()line.
if(!context.getPartialViewContext().isAjaxRequest()){
external.setResponseStatus
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
WhenyoudosoonanAjaxrequest,theJSFAjaxscriptwon’tprocesstheAjaxresponseasyou’dexpect.Insteadofdisplayingtheerrorpage,itwilltriggertheonerrorhandler.
ViewExpiredExceptionHandlingIfyou’veworkedwithJSFbefore,thenthechanceisgreatthatyou’veseenorheardaboutaViewExpiredException.Tothepoint,itwillbethrownwhentheJSFviewstateassociatedwiththepostbackcannotbefoundintheHTTPsessionanymore—inotherwords,whenthejavax.faces.STATE_SAVING_METHODcontextparameterissettoitsdefaultvalueof“server”,andtheendusersubmitsaJSFformwhoseviewstatecannotbefoundany
moreontheserverside.Notethatwhenthecontextparameterissetto“client”,youstillneeda“jsf/ClientSideSecretKey”environmententryinordertoavoidexpiredviewswhentheserverrestarts.Thisalliselaboratedinthesection“ViewState”inChapter3.Also,inthesection“@ViewScoped”inChapter8,youcanfindhowtoconfiguretheamountofviewsandmanagedbeansJSFwillsaveintheHTTPsession.
ThereareseveralcircumstanceswhereaViewExpiredExceptionmayunexpectedlyoccur.Allofthemarerelatedtopagenavigationandthebrowsercachewhiletheenduserhasrecentlyloggedoutfromthewebapplication.Normally,theHTTPsessionis,duringlogout,invalidatedasasecuritymeasure,andtheenduserisredirectedtosomelandingpage.Whenthepreviouslyvisitedwebpageiscacheable,andtheenduserpressesthebrowser’sbackbuttonafterlogout,thentheendusermaysuccessfullygettoseethepreviouslyvisitedwebpagefromthebrowsercache.IfitcontainsanyJSFform,thenitsjavax.faces.ViewStatehiddeninputfieldwillactuallyreferaviewstatewhichdoesnotexistinthecurrentsessionanymore.WhentheendusersubmitssuchJSFform,thenJSFwillinevitablythrowaViewExpiredException.
Althoughthisisanexcellentsecuritymeasure,theendusermaygetconfusedasthepreviouslyvisitedwebpageactuallygotsuccessfullyloadedinenduser’sexperience.Thisisnotgoodforuserexperience.You’dthereforebestmakesurethatstatefulJSFpagesarenotcacheable,sothatthewebbrowserisforcedtoactuallyhitthewebserverwhentheenduserpressesthebackbuttonandthusloadafreshnewpage
withaviewstatewhichisactuallyvalidinthecurrentHTTPsession.ThiscanbeachievedwithaservletfilterwhichsetsspecificresponseheaderswhichinstructtheclientnottocachetheHTTPresponse.Followingisanexampleofsuchaservletfilter:@WebFilter(servletNames="facesServlet")publicclassNoCacheFilterimplementsFilter{
@Override
publicvoiddoFilter
(ServletRequestreq,ServletResponseres,
FilterChainchain)
throwsIOException,ServletException
{
HttpServletRequestrequest=
(HttpServletRequest)req;
HttpServletResponseresponse=
(HttpServletResponse)res;
StringresourcePath=request.getContextPath()
+ResourceHandler.RESOURCE_IDENTIFIER;
if
(!request.getRequestURI().startsWith(resourcePath)){
response.setHeader
("Cache-Control","no-store,must-
revalidate");
}
chain.doFilter(request,response);
}
}
Basically,ithooksonallrequestswhicharegoingtohittheFacesServlet,providedthatit’sconfiguredonaservletnameof“facesServlet”inweb.xmlasfollows:<servlet>
<servlet-name>facesServlet</servlet-name>
<servlet-
class>javax.faces.webapp.FacesServlet</servlet-class>
</servlet>
Alternatively,ifyouhavedividedyourwebapplicationintoapublicareawithonlystatelessJSFpagesandarestrictedareawithonlystatefulJSFpages,thenyoucouldalsomapthisfilteronaspecificURLpatternwhichmatchesonlythestatefulsection,suchasadmin*.
@WebFilter("admin*")
ThefilterexamplechecksfirstiftheHTTPrequestdoesn’trepresentaJSFresourcerequestbeforesettingthecachecontrolheader.Thoserequestsareidentifiedbythe/javax.faces.resourcepathafterthecontextpath,whichisavailablebytheResourceHandler#RESOURCE_IDENTIFIERconstant.ThisisautomaticallyusedwhenyouuseaJSFresourcecomponent,suchas<h:graphicImage>,<h:outputScript>,or<h:outputstyleSheet>.(Seealsothesection“ResourceComponents”inChapter6.)Youdon’twanttodisablethebrowsercacheonthemasthatwouldotherwiseimpactthepageloadingperformance.
ThecachecontrolheaderbeingsetisactuallyonlyrecognizedbyHTTP1.1-capableclients.HTTP1.1was
introducedin1997.IncaseyouwouldliketocoverHTTP1.0clientsaswell,whichthesedaysaregenerallyonlyancientproxiesorpooruserswhocan’tgetsomethingbetterthanInternetExplorer6.0,thenyou’dbestaddtwomoreresponseheaders.
response.setHeader("Pragma","no-cache");//HTTP1.0.
response.setDateHeader("Expires",0);//Proxies.
Now,withthisfilterinplace,theenduserwon’tanymoregettoseeanyJSFpagefromthebrowsercacheandthuscannolongergetaViewExpiredExceptiononthem.However,therearestillcaseswhereaViewExpiredExceptionisunavoidable.OnesuchcaseisanenduserwhohasseveralJSFpagesopenindifferentbrowsertabsandlogsoutinoneofthemandthenperformsanactioninanothertabwithoutrefreshingthepagebeforehand.Forsuchacase,you’dreallyneeda“Sorry,yoursessionhastimedout”errorpage.Thiscaneasilybeconfiguredasanothererrorpageinweb.xmlasfollows:<errorpage><exception-type>
javax.faces.application.ViewExpiredException
</exception-type>
<location>WEB-INFerrorpages/expired.xhtml</location>
</errorpage>
DonotletitpointtoapublicJSFpage,suchasaloginpage.IncaseyoureallyneedittobeapublicJSFpage,useametarefreshheaderintheerrorpagewhichredirectstheenduserfurthertoapublicJSFpage.
<!DOCTYPEhtml>
<htmllang="en">
<head>
<title>Sessionexpired</title>
<metahttp-equiv="refresh"
content="0;url=#
{request.contextPath}/login.xhtml"/>
</head>
<body>
<h1>Sorry,yoursessionhastimedout</h1>
<h3>Youwillberedirectedtologinpage</h3>
<p>
<ahref="#{request.contextPath}/login.xhtml">
Clickhereiftheredirectdidn'twork,
orwhenyou’reimpatient.
</a>
</p>
</body>
</html>
Notethatthe“0”inthemetarefreshcontentrepresentstheamountofsecondsbeforeredirect.Thus,“0”means“redirectimmediately.”Youcanuse,forexample,“3”toletthebrowserwait3secondsbeforetheredirect.
KeepinmindalsotoconfigurethislocationinanycustomexceptionhandlersuchastheAjaxExceptionHandlerdemonstratedintheprevioussection.Youalsoneedtomakesurethatyour“general”errorpageismappedonanerrorcodeof500insteadofanexceptiontypeof,e.g.,java.lang.Exceptionorjava.lang.Throwable;otherwiseallexceptionswrappedinServletExceptionwouldstillendupinthegeneralerrorpage.JSFwillwrapanyexceptioninaServletExceptionwhenit’sthrownduringasynchronous(non-Ajax)postbackinsteadofan
asynchronous(Ajax)postback.Theweb.xmlerrorpagemechanismwillonlyextracttherootcausefromtheServletExceptionforasecondpassthrougherrorpagesbyexceptiontypewhennomatchingerrorpagebyexceptiontypeisfound.
AcompletelydifferentalternativetoavoidViewExpiredExceptionistousestatelessJSFviews.ThiswaynothingoftheJSFviewstatewillbesavedandtheJSFviewswillneverexpirebutwilljustberebuiltfromscratchoneveryrequest.Youcanturnonstatelessviewsbysettingthetransientattributeof<f:view>totrue.Thisiselaboratedinthesection“StatelessForms”inChapter4.
Regardlessofthesolution,makesureyoudonotusetheMojarra-specificcom.sun.faces.enableRestoreView11Compatibi
litycontextparameter.ThiswillbasicallyturnonJSF1.0/1.1behaviorwithregardtoexpiredviews.Itwon’tthrowtheViewExpiredExceptionanymore,butneitherdoesitactuallyrestoretheoriginalviewstateatall.Itbasicallyrecreatestheviewandallassociatedviewscopedbeansfromscratchandherebythuslosesalloforiginalstate.Astheapplicationwillbehaveinaconfusingway(“Hey,wherearemyinputvalues..??”),thisisbadforuserexperience.BetterusestatelessJSFviewsinsteadsothatyoucanmanagetheapplicationinspecificviewsonlyinsteadofforallviews.
IOExceptionHandlingSomemethodsoftheunderlyingHttpServletRequestandHttpServletResponseobjectsmaythrowan
IOException.Usually,thatonlyhappenswhenthenetworkconnectionunexpectedlybreaks:forexample,whentheenduserabruptlystopstheHTTPrequestbypressingtheEscbuttoninthewebbrowser,ortheenduserabruptlynavigatestoadifferentwebpagewhilethecurrentpageisstillloading,orevenwhentheenduser’scomputerornetworkcablecatchesfire.Thosecircumstancesarereallyunavoidable.Foryou,aswebapplicationdeveloper,it’sbesttoletanyIOExceptionbubbleupintotheservletcontainer.Inotherwords,there’sabsolutelynoneedtocatchitasfollows:publicvoidsomeAjaxListener(){
try{
FacesContext.getCurrentInstance()
.getExternalContext().redirect(url);
}
catch(IOExceptione){
thrownewUncheckedIOException(e);
}
}
Instead,justletitgo.
publicvoidsomeAjaxListener()throwsIOException{
FacesContext.getCurrentInstance()
.getExternalContext().redirect(url);
}
EJBExceptionHandlingSometimes,invokingservicemethodsmayalsocauseanexception.Generally,thoseareonpurpose,suchas“entitynot
found,”“uniqueconstraintviolation,”“invalidusercredentials”,“entityisinmeanwhilemodifiedbyanotheruser,”etc.Bydefault,anynon-application-specificRuntimeException,suchasNullPointerExceptionandevenJPA’sPersistenceExceptionwhichisthrownfromanEJBservicemethod,iswrappedinanEJBException.ThismakesitclumsytonaildowntheactualrootcauseintheJSFactionmethod.
publicvoidaddProduct(){
FacesMessagemessage;
try{
Longid=productService.create(product);
message=new
FacesMessage(FacesMessage.SEVERITY_INFO,
"Productsuccessfullysaved","IDis"+id);
}
catch(EJBExceptione){
if(e.getCause()instanceof
ConstraintViolationException){
message=new
FacesMessage(FacesMessage.SEVERITY_ERROR,
"Duplicateproduct!",e.getMessage());
context.validationFailed();
}
else{
throwe;
}
}
context.addMessage(null,message);
}
Thisisnotthebestpractice.NotonlywouldyouneedtodeterminetherootcauseoftheEJBexceptionbyinspectingException#getCause(),theweb.xmlerrorpagemechanismwouldalsonotbeabletoshowaspecificerrorpagefor,forexample,aConstraintViolationException,becauseit’swrappedinanEJBException.InordertogetEJBtothrowitunwrapped,youneedtocreateacustomexceptionsuperclassfirstwhichyouthenannotatewith@javax.ejb.ApplicationException.
@ApplicationException(rollback=true)
publicabstractclassBusinessExceptionextends
RuntimeException{
publicBusinessException(){
super();
}
publicBusinessException(Exceptioncause){
super(cause);
}
}
Notetherollback=trueattributeontheannotation.Thisisveryimportantincaseyou’dliketheEJBcontainertorollbackanyactivetransactionfromwherethisexceptionisbeingthrown.Followingaresomeexamplesofsubclassesofthiscustombusinessexception.
publicabstractclassQueryExceptionextends
BusinessException{}
publicclassEntityNotFoundExceptionextendsQueryException
{}
publicclassDuplicateEntityExceptionextendsQueryException
{}
4
publicabstractclassCredentialsExceptionextends
BusinessException{}
publicclassInvalidUsernameExceptionextends
CredentialsException{}
publicclassInvalidPasswordExceptionextends
CredentialsException{}
Notethatyoudon’tnecessarilyneedtorepeatthe@ApplicationExceptionoverallsubclassesasit’salready@Inherited.Followingaresomeconcreteusecasesonwhichthoseexceptionscouldbethrown:publicUsergetById(Longid){
try{
returnentityManager
.createQuery("FROMUseruWHEREu.id=:id",
User.class)
.setParameter("id",id)
.getSingleResult();
}
catch(NoResultExceptione){
thrownewEntityNotFoundException(e);
}
}
publicOptional<User>findByEmail(Stringemail){
try{
returnOptional.of(entityManager
.createQuery("FROMUseru"
+"WHEREu.email=:email",User.class)
.setParameter("email",email)
.getSingleResult());
}
catch(NoResultExceptione){
returnOptional.empty();
}
}
publicUsergetByEmailAndPassword(Stringemail,String
password){
Useruser=findByEmail(email)
.orElseThrow(InvalidUsernameException::new);
Credentialscredentials=user.getCredentials();
byte[]passwordHash=digest(password,
credentials.getSalt());
if(!Arrays.equals(passwordHash,
credentials.getPasswordHash())){
thrownewInvalidPasswordException();
}
returnuser;
}
publicLongcreate(Useruser){
if(findByEmail(user.getEmail()).isPresent()){
thrownewDuplicateEntityException();
}
entityManager.persist(user);
returnuser.getId();
}
IntheJSFbackingbeanactionmethods,youcouldthenhandlethemaccordingly.
publicvoidsignup(){
FacesMessagemessage;
try{
userService.create(product);
message=newFacesMessage("Youaresuccessfully
signedup!");
}
catch(DuplicateEntityExceptione){
message=new
FacesMessage(FacesMessage.SEVERITY_ERROR,
"Sorry,usernamealreadytaken!",
e.getMessage());
context.validationFailed();
}
context.addMessage(null,message);
}
Inordertofurtherreducetheboilerplatecode,youcouldevenletallbusinessexceptionsgoandhaveacustomexceptionhandlertohandlethem.
publicclassBusinessExceptionHandlerextends
ExceptionHandlerWrapper{
publicBusinessExceptionHandler(ExceptionHandlerwrapped)
{
super(wrapped);
}
@Override
publicvoidhandle(){
handleBusinessException(FacesContext.getCurrentInstan
ce());
getWrapped().handle();
}
protectedvoidhandleBusinessException(FacesContext
context){
Iterator<ExceptionQueuedEvent>
unhandledExceptionQueuedEvents=
getUnhandledExceptionQueuedEvents().iterator();
if(context==null
||!unhandledExceptionQueuedEvents.hasNext()
){
return;
}
Throwableexception=unhandledExceptionQueuedEvents
.next().getContext().getException();
while(exception.getCause()!=null
&&(exceptioninstanceofFacesException
||exceptioninstanceofELException)
){
exception=exception.getCause();
}
if(!(exceptioninstanceofBusinessException)){
return;
}
context.addMessage(null,newFacesMessage(
FacesMessage.SEVERITY_FATAL,
exception.toString(),
exception.getMessage()));
context.validationFailed();
context.getPartialViewContext()
.getRenderIds().add("globalMessages");
unhandledExceptionQueuedEvents.remove();
while(unhandledExceptionQueuedEvents.hasNext()){
unhandledExceptionQueuedEvents.next();
unhandledExceptionQueuedEvents.remove();
}
}
publicstaticclassFactoryextends
ExceptionHandlerFactory{
publicFactory(ExceptionHandlerFactorywrapped){
super(wrapped);
}
@Override
publicExceptionHandlergetExceptionHandler(){
returnnewBusinessExceptionHandler
(getWrapped().getExceptionHandler());
}
}
}
Yes,it’sindeedsimilartotheAjaxExceptionHandlerasshownintheearliersection“AjaxExceptionHandler.”However,thefirstdifferenceisthatitdoesn’tskiphandlingtheexceptionwhentheresponseisalreadycommittedorwhenit’snotanAjaxrequest.Theseconddifferenceisthelogicbetweenextractingtherootcauseoftheexceptionanddrainingtheremainingunhandledexceptionevents.ThisBusinessExceptionHandlerwillinsteadcheckiftherootcauseisaninstanceofBusinessExceptionandifso,itwilladdafacesmessageandinstructJSFtoexplicitlyupdatethecomponentidentifiedby“globalMessages”,whichshouldrefertoaglobalmessagescomponentinyourmastertemplatesomethinglikethefollowing:<h:messagesid="globalMessages"globalOnly="true"/>
Ultimately,allbusinessexception-relatedfacesmessageswillendupthere.Youmighthavenoticedthatthefacescontextis
explicitlymarkedas“validationfailed”viatheFacesContext#validationFailed()call.ThisisgenerallyusefulincaseanycodeintheFacelettemplateisrelyingonit.IfyouwouldliketorunittogetherwiththeAjaxExceptionHandlerfornon-businessexceptions,thenyouneedtoregisteritinthefaces-config.xmlaftertheAjaxExceptionHandler.Anythingthatisdeclaredlaterinthefaces-config.xmlwilleffectivelywrapthepreviouslydeclaredone.Thisalsoappliestocustomexceptionhandlerfactories.WhentheBusinessExceptionHandlerconfirmsthattheexceptionisnotaninstanceofBusinessException,thenitwillleavetheunhandledexceptioninthequeueandreturnfromthemethodandfinallydelegatetothehandle()methodofthewrappedExceptionHandler,whichshallbetheAjaxExceptionHandler.
<factory>
<exceptionhandler-factory>
com.example.project.AjaxExceptionHandler$Factory
</exceptionhandler-factory>
<exceptionhandler-factory>
com.example.project.BusinessExceptionHandler$Factory
</exceptionhandler-factory>
</factory>
WiththeBusinessExceptionHandlerinplace,youcouldfurtherreducethebackingbeanactionmethodasfollows:publicvoidsignup(){userService.create(product);
context.addMessage(null,
newFacesMessage("Youaresuccessfullysigned
up!");
}
Footnotes1https://javaee.github.io/javaee-
spec/javadocs/javax/faces/context/ExceptionHandler.html.
2https://javaee.github.io/javaee-
spec/javadocs/javax/servlet/RequestDispatcher.html#ERROR
_EXCEPTION.
3https://javaee.github.io/javaee-
spec/javadocs/javax/faces/context/ExceptionHandlerFactor
y.html.
4https://javaee.github.io/javaee-
spec/javadocs/javax/ejb/ApplicationException.html.
(1) (2)
©BaukeScholtz,ArjanTijms2018BaukeScholtzandArjanTijms,TheDefinitiveGuidetoJSFinJavaEE8,https://doi.org/10.1007/978-1-4842-3387-0_10
10.WebSocketPush
BaukeScholtz andArjanTijms
Willemstad,Curaçao Amsterdam,Noord-Holland,TheNetherlands
JSF1.0introducedHTMLform-basedPOSTactionsupport.JSF2.0introducedAJAX-basedPOSTsupport.JSF2.0introducedGETquerystringparametermappingsupport.JSF2.2introducedGET-basedactionsupport.JSF2.3introducesWebSocketsupport.
JSF’sWebSocketsupportisrepresentedbythenew<f:websocket>tag,thePushContextinterface,andthe@Pushannotation.ItisbuiltontopoftheJSR-356WebSocketsspecification,introducedinJavaEE7.Therefore,itistechnicallypossibletouseitinaJavaEE7environmentaswell.JSR-356isevennativelysupportedinTomcatsince7.0.27andinJettysince9.1.0.
InMojarra,the<f:websocket>hasanadditionalJavaEE7dependency:JSON-P(JSR-353).Incaseyou’retargetingTomcatorJettyinsteadofaJavaEEapplicationserver,youmightneedtoinstallitseparately.JSON-PisinternallyusedtoconvertJavaobjectstoaJSONstringsothatitcan,withoutmuchhassle,betransferredtotheclientsideandbeprovided
1 2
asanargumentofJavaScriptlistenerfunctionattachedto<f:websocket>.
ConfigurationTheJSR-356WebSocketspecificationdoesnotofficiallysupportprogrammaticinitializationofthesocketendpointduringruntime.Sowecannotinitializeitbysimplydeclaring<f:websocket>intheviewandwaituntilaJSFpagereferencingitisbeingopenedforthefirsttime.Wereallyneedtoinitializeitexplicitlyduringdeploymenttime.Wecoulddothatbydefault,buthavinganunusedWebSocketendpointopenforeverisnotreallyniceifit’sneverusedbythewebapplication.Sowecannotavoidhavingacontextparametertoexplicitlyinitializeitduringdeploymenttime.
<context-param>
<param-name>javax.faces.ENABLE_WEBSOCKET_ENDPOINT</param-
name>
<param-value>true</param-value>
</context-param>
Ifyoupreferprogrammaticinitializationoverdeclarativeinitialization,thenyoucanalwaysuseServletContext#setInitParameter()inaServletContainerInitializerofyourwebfragmentlibraryasfollows:publicclassYourInitializerimplementsServletContainerInitializer{
@Override
publicvoidonStartup(Set<Class<?>>types,
ServletContextcontext){
context.setInitParameter(
PushContext.ENABLE_WEBSOCKET_ENDPOINT_PARAM_
NAME,"true");
}
}
NotethatitisnotpossibletoperformthistaskinaServletContextListenerasJSFwillactuallyregistertheWebSocketendpointinitsownServletContainerInitializerimplementationwhichalwaysrunsbeforeanyServletContextListener.
OncetheWebSocketendpointisenabledandsuccessfullyinitializedduringdeployment,itwilllistenforWebSockethandshakerequestsontheURL(uniformresourcelocator)patternjavax.faces.push*.ThefirstpathelementwillrepresenttheWebSocketchannelname.
Comingbackto“officially,”someWebSocketimplementationsdo,however,supportprogrammaticinitialization,suchastheoneprovidedbyUndertow,whichisinturnusedinWildFly.Unfortunately,thespecdoesn’tsayso,andtheremaybeWebSocketimplementationsthatsimplydonotsupportprogrammaticinitialization,suchasTyrusasusedinPayara.
TheWebSocketcontainerwill,bydefault,listenforhandshakerequestsonthesameportastheapplicationserverislisteningforHTTPrequests.Youcanoptionallychangetheportwithanotherweb.xmlcontextparameter,<context-param>
<param-
name>javax.faces.WEBSOCKET_ENDPOINT_PORT</param-name>
1
<param-value>8000</param-value>
</context-param>
orprogrammaticallyinaServletContainerInitializer:context.setInitParameter(
PushContext.WEBSOCKET_ENDPOINT_PORT_PARAM_NAME,
"8000");
UsageInyourJSFpage,justdeclarethe<f:websocket>tagalongwiththerequiredchannelattributerepresentingthechannelnameandtheoptionalonmessageattributerepresentingareferencetoaJavaScriptfunction.
<f:websocketchannel="test"onmessage="logMessage"/>
<script>
functionlogMessage(message,channel,event){
console.log(message);
}
</script>
TheJavaScriptfunctionwillbeinvokedwiththreearguments.1. message:thepushmessageasJSONobject.
2. channel:theWebSocketchannelname.Thismaybeusefulincaseyouintendtohaveagloballistener,orwanttomanuallycontrolthecloseoftheWebSocket.
3. event:theoriginalMessageEventobject.ThismaybeusefulincaseyouintendtoinspectitintheJavaScriptfunction.
OntheWARside,youcaninjectthePushContextviathe
@PushannotationinanywebartifactthatsupportsCDIinjection.ThiscanbeasimpleCDImanagedbean,butitcanalsobea@WebServlet,@WebFilteror@WebListener.
importjavax.inject.Named;
importjavax.enterprise.context.RequestScoped;
importjavax.inject.Inject;
importjavax.faces.push.Push;
importjavax.faces.push.PushContext;
@Named@RequestScoped
publicclassBean{
@Inject@Push
privatePushContexttest;
publicvoidsubmit(){
test.send("HelloWorld!");
}
}
ThePushContextvariablenametestmustmatchthechannelnamedeclaredintheJSFpage.Incaseyoucannotmatchthevariablenamewiththechannelname,youcanalwaysspecifythechannelnameintheoptionalchannelattributeofthe@Pushannotation.
@Inject@Push(channel="test")
privatePushContextfoo;
Oncethesubmit()methodofthebeanshownbeforeisinvokedbysomeJSFcommandcomponent,eveninadifferentJSFpage,thepushmessage“HelloWorld!”willbe
senttoallopenedsocketsontheverysamechannelname,applicationwide.
ScopesandUsersAsyoumayhaverealized,<f:websocket>isthus,bydefault,applicationscoped.Youcancontrolthescopebytheoptionalscopeattribute.Allowedvaluesareapplication,session,andview.
Whensettosession,themessagewillbesenttoallopenedsocketsontheverysamechannelinthecurrentsessiononly.
<f:websocketchannel="progress"scope="session"/>
Thisisparticularlyusefulforprogressmessagescomingfromlong-runningsession-scopedbackgroundtasksinitiatedbytheuseritself.Thiswaytheusercanjustcontinuebrowsingthesitewithouttheneedtowaitfortheresultontheverysamepage.
Alternatively,youcanalsosettheoptionaluserattributetoaserializablevaluerepresentingtheuniqueuseridentifier,whichcanbeaStringrepresentingtheuserloginnameoraLongrepresentingtheuserID.Whenthisattributeisset,thescopeofthesocketwillautomaticallydefaulttosessionanditcannotbesettoapplication.
<f:websocketchannel="chat"user="#{loggedInUser.id}"/>
Thisofferstheopportunitytosendamessagetoaspecificuserasfollows:privateStringmessage;
privateUserrecipient;
@Inject@Push
privatePushContextchat;
publicvoidsendMessage(){
LongrecipientId=recipient.getId();
chat.send(message,recipientId);
}
YoucanevensendittomultipleusersbyprovidingaSetargument.
privateStringmessage;
privateSet<User>recipients;
@Inject@Push
privatePushContextchat;
publicvoidsendMessage(){
Set<Long>recipientIds=recipients.stream()
.map(User::getId)
.collect(Collectors.toSet());
chat.send(message,recipientIds);
}
Inotherwords,youcaneasilyimplementachatboxthisway.Incidentally,real-timeusertargetednotificationsat,forexample,StackOverflowandFacebookworkthisway.
Whenthescopeissettoview,themessagewillbesenttotheopenedsocketonthespecifiedchannelinthecurrentviewonly.Thiswon’taffectanysocketsonthesamechannelinallotherviewsthroughouttheapplication.
<f:websocketchannel="push"scope="view"/>
Thisisalsosupportedincombinationwiththeuserattribute.
<f:websocketchannel="chat"user="#{user.id}"scope="view"/>
Thisconstructissomewhatunusualthoughandshouldonlybeusedifthelogged-inuserrepresentedbyuserattributecanhaveashorterlifetimethantheHTTPsession.Thisis,however,inturnconsideredapoorsecuritypractice.Thebestsecuritypracticeis,namely,thattheHTTPsessionisinvalidatedduringloginandduringlogout.InvalidatingtheHTTPsessionduringloginpreventssessionfixationattacksandinvalidatingthesessionduringlogoutpreventsdirtyuser-specificdatalingeringaroundinHTTPsession.
ChannelDesignHintsYoucandeclaremultiplepushchannelsondifferentscopeswithorwithoutusertargetthroughouttheapplication.However,beawarethatthesamechannelnamecaneasilybereusedacrossmultipleviews,evenifitisviewscoped.It’smoreefficientifyouuseasfewdifferentchannelnamesaspossibleandtiethechannelnametoaspecificpushsocketscope/usercombination,nottoaspecificJSFview.Incaseyouintendtohavemultipleview-scopedchannelsfordifferentpurposes,itisbesttouseonlyoneview-scopedchannelandhaveaglobalJavaScriptlistenerwhichcandistinguishitstaskbasedonthedeliveredmessage,forexample,bysendingthemessageinaserverasfollows,Map<String,Object>message
=newHashMap<>();
message.put("functionName","someFunction");
message.put("functionData",functionData);//CanbeMap
orBean.
someChannel.send(message);
whichisthenprocessedintheonmessageJavaScriptlistenerfunctionasfollows:functionsomeSocketListener(message){
window[message.functionName](message.functionData);
}
functionsomeFunction(data){
//...
}
functionotherFunction(data){
//...
}
//...
One-TimePushYoucanusetheconnectedattributetopreventthesocketfromautomaticallyconnectingduringpageload.
<f:websocket...connected="false"/>
Thisisparticularlyusefulwhenyouwanttoperformaone-timepushoftheresultafterinvokingaview-scopedAjax
actionmethodwhichmighttakeabitmoretimetocomplete,andyou’dliketheusertoimmediatelycontinueusingtheverysamepagewithoutbeingannoyedabouta“slowwebsite”experience.Thisapproachonlyrequiresabitofadditionalworkwiththejsf.pushJavaScriptAPI(applicationprogramminginterface). Ithasthreefunctions,butonlytwoareofinteresttous:jsf.push.open(...)andjsf.push.close(...).Thethirdone,jsf.push.init(...),basicallyinitializesthesocketandthat’suptotherendererofthe<f:websocket>tag.
RightbeforeinvokingtheAjaxactionmethod,you’dneedtoexplicitlyopenthesocketbyinvokingthejsf.push.open(...)functionwiththesocketclientIDasargument.Andrightafterthepushmessagearrives,you’dneedtoexplicitlyclosethesocketbyinvokingthejsf.push.close(...)functionwiththesocketclientIDasargument.Thefollowingexampledemonstratesthisapproach:<script>functionstartLongRunningProcess(){
jsf.push.open("push");
document.getElementById("status").innerHTML=
"Longrunningprocesshasstarted...";
}
functionendLongRunningProcess(result){
jsf.push.close("push");
document.getElementById("status").innerHTML=
result;
}
</script>
<h:form>
2
<h:commandButtonvalue="submit"
onclick="startLongRunningProcess()"
action="#{longRunningProcess.submit}">
<f:ajax/>
</h:commandButton>
</h:form>
<divid="status"/>
<f:websocketid="push"channel="push"scope="view"
connected="false"onmessage="endLongRunningProcess">
</f:websocket>
Itmustbesaidthatit’sapoorpracticetoputJavaScriptcoderightintheHTMLsourceasshownabove.It’s,ofcourse,fordemonstrationpurposesonly.Forbettermaintenance,performance,andtoolingsupport,youshould,inreal-worldcode,putJavaScriptcodeinaJSfileandincludeitvia<h:outputScript>.AndthenI’mnottalkingaboutthelackofjQuerymagicfordemonstrationpurposes.
Intheexample,openingthesocketisperformedduringtheonclickofthecommandbutton.Theonmessagelistenerfunctioninturnclosesthesocket.Ofcourse,youcanalsokeepthesocketopenallthetimewithoutfiddlingwithJavaScript,butitmaybeawasteofresourcesifthesocketisn’tusedforpurposesotherthanpresentingtheresultofaview-scopedAjaxactionmethod.Hereiswhattheassociatedbackingbeanlookslike.
@Named@RequestScoped
publicclassLongRunningProcess{
@Inject
privateLongRunningProcessServiceservice;
@Inject@Push
privatePushContextpush;
publicvoidsubmit(){
service.asyncSubmit(result->push.send(result));
}
}
Andhereiswhattheserviceclasslookslike.
@Stateless
publicclassLongRunningProcessService{
@Asynchronous
publicvoidasyncSubmit(Consumer<String>callback){
Stringresult=someLongRunningProcess();
callback.accept(result);
}
}
NotetheEJB@Asynchronousannotation.Thisisveryimportantinthisconstruct.ItwillensurethattheEJB(EnterpriseJavaBean)methodisexecutedinaseparatethread.ThisallowsthebackingbeanmethodtoreturnimmediatelywithoutwaitingfortheEJBmethodtocomplete.
StatefulUIUpdatesAsyoumayhavenoticed,theonmessageJavaScriptlistenerfunctionisgenerallyonlyusefulforsmallstatelesstasks,suchasdisplayingafeedbackmessageoraddinganewitemtosomestatelesslistusingJavaScript.Itisn’tterriblyusefulwhenyouwanttoupdateastatefulUI(userinterface)
representedbyanotherJSFcomponent.ThinkofreplacingatrivialloadingimagewithawholeJSFtable.
Forthatyou’dbetternest<f:ajax>listeningonaspecificpushmessage.ViaitsrenderattributeyouhavetheopportunitytoautomaticallyupdateanarbitraryJSFcomponentinanincomingpushmessage.Followingisanexamplewhichinitiallyshowsaloadingimageandthenthetablewhenit’sreadytoload:
<h:form>
<f:websocketchannel="push"scope="view">
<f:ajaxevent="loaded"render=":results"/>
</f:websocket>
</h:form>
<h:panelGroupid="results"layout="block">
<h:graphicImagename="images/loading.gif"
rendered="#{emptylongRunningSearch.results}">
</h:graphicImage>
<h:dataTablevalue="#{longRunningSearch.results}"
var="result"
rendered="#{notemptylongRunningSearch.results}">
<h:column>#{result.id}</h:column>
<h:column>#{result.name}</h:column>
<h:column>#{result.value}</h:column>
</h:dataTable>
</h:panelGroup>
Notethat<f:websocket>isplacedin<h:form>.Thisismandatorywhenithas<f:ajax>nested.Normallythisisnotrequired.Hereiswhatthebackingbeanlookslike.
@Named@ViewScoped
publicclassLongRunningSearchimplementsSerializable{
privateList<Result>results;
@Inject
privateLongRunningSearchServiceservice;
@Inject@Push
privatePushContextpush;
@PostConstruct
publicvoidinit(){
service.asyncLoadResults(results->{
this.results=results;
push.send("loaded");
});
}
publicList<Result>getResults(){
returnresults;
}
}
Notethatthepushmessage"loaded"matchesexactlythe<f:ajaxevent>value.Youcanuseanyvalueyouwantandyoucannestasmany<f:ajax>tagsasyouneed.It’simportantthatthemanagedbeanis@ViewScopedastheAjaxcallisbasicallyperformedinadifferentrequestwithinthesameview.Finallytheserviceclasslooksasfollows:@Stateless
publicclassLongRunningSearchService{
@Asynchronous
publicvoidasyncLoadResults(Consumer<List<Result>>
callback){
List<Result>results=someLongRunningProcess();
callback.accept(results);
}
}
ThesomeLongRunningProcess()methodrepresentsyourimplementationofsomelong-runningprocess(e.g.,callingathird-partywebserviceAPI).
Site-WidePushNotificationsForthis,youcanuseanapplication-scopedsocket.Suchasocketisparticularlyusefulforapplication-widefeedbackmessagestriggeredbythewebapplicationitselfonaparticulareventwhichmaybeinteresttoallapplicationusers.Thinkofsite-widestatistics,real-timelists,stockupdates,etc.Thefollowingexampleshowsthecaseofareal-timetop10list:
<h:dataTableid="top10"value="#{bean.top10}"var="item">
<h:column>#{item.ranking}</h:column>
<h:column>#{item.name}</h:column>
<h:column>#{item.score}</h:column>
</h:dataTable>
<h:form>
<f:websocketchannel="top10Observer">
<f:ajaxevent="updated"render=":top10"/>
</f:websocket>
</h:form>
Hereiswhattheserviceclasslookslike,withalittlehelpfromCDIevents.
@Stateless
publicclassItemService{
@Inject
privateEntityManagerentityManager;
@Inject
privateBeanManagerbeanManager;
publicvoidupdate(Itemitem){
List<Item>previousTop10=getTop10();
entityManager.merge(item);
List<Item>currentTop10=getTop10();
if(!currentTop10.equals(previousTop10)){
beanManager.fireEvent(newTop10UpdatedEvent());
}
}
pulicList<Item>getTop10(){
returnentityManager
.createNamedQuery("Item.top10",Item.class)
.getResultList();
}
}
NotethattheTop10UpdatedEventis,inthisspecificexample,basicallyjustanemptyclasslikepublicclassTop10UpdatedEvent{}.Alsonotethatwe’renotinjectingthePushContexthere.Thisisotherwiseconsideredtightcouplingoflayers.AllJSF-relatedcodebelongsinthefrontend,notinthebackend.Thiswaytheback-endserviceclassesarebetterreusableacrossallkindsoffront-endframeworksotherthanJSF,suchasJAX-RSorevenplainvanillaJSP/Servlet.Inotherwords,youshouldensurethatnoneofyourback-endclassesdirectlyorindirectlyuseanyfront-end-specificclassessuchasthosefromjavax.faces.*,javax.ws.*,andjavax.servlet.*packages.
AnyeventfiredwiththeBeanManager#fireEvent()methodcanbeobservedusingCDI@Observesannotation.Thisworksacrossall
layers.Inotherwords,evenwhenit’sfiredinthebackend,youcanobserveitinthefrontend.Theonlyrequirementisthatthebackingbeanmustbe@ApplicationScoped.Thatis,there’snotnecessarilyanymeansofanHTTPrequest,HTTPsession,orJSFviewanywhereatthatmoment.
@Named@ApplicationScoped
publicclassBean{
privateList<Item>top10;
@Inject
privateItemServiceservice;
@Inject@Push
privatePushContexttop10Observer;
@PostConstruct
publicvoidload(){
top10=service.getTop10();
}
publicvoidonTop10Updated(@ObservesTop10UpdatedEvent
event){
load();
top10Observer.send("updated");
}
publicList<Item>getTop10(){
returntop10;
}
}
KeepingTrackofActiveSocketsInordertokeeptrackofactivesockets,youcaninan
[email protected]@WebsocketEvent.Closedevents.Thefollowingexampleassumesthatyouhave<f:websocketchannel="chat"user="...">andthatyouintendtocollect“activechatusers”:@ApplicationScopedpublicclassWebsocketEventObserver{
privateMap<Serializable,AtomicInteger>users;
@PostConstruct
publicvoidinit(){
users=newConcurrentHashMap<>();
}
publicvoidonopen(@Observes@OpenedWebsocketEvent
event){
if("chat".equals(event.getChannel())){
getCounter(event.getUser()).incrementAndGet(
);
}
}
publicvoidonclose(@Observes@ClosedWebsocketEvent
event){
if("chat".equals(event.getChannel())){
getCounter(event.getUser()).decrementAndGet(
);
}
}
privateAtomicIntegergetCounter(Serializableuser)
{
returnusers.computeIfAbsent(user,k->new
AtomicInteger());
}
publicSet<Serializable>getActiveUsers(){
returnusers.entrySet().stream()
.filter(entry->entry.getValue().intValue()
>0)
.map(entry->entry.getKey())
.collect(Collectors.toSet());
}
}
YoucanusetheabovegetActiveUsers()methodtoobtainasetof“activechatusers.”Donotethatasingleusercanopenthesamewebpagemultipletimeswithinthesamesession(e.g.,multiplebrowsertabs)andthat’sexactlywhyacounterisusedinsteadsimplyaddingandremovingusersfromaSet.
DetectingSessionandViewExpirationThe<f:websocket>tagwillbydefaultkeeptheconnectionopenforever,aslongasthedocumentisopen—aslongasthere’snoconnected="false"beingset,orjsf.push.close(clientId)beinginvoked,ofcourse.Whenthefirstconnectionattemptfails,itwillimmediatelyreportanerror.Youcanoptionallyusetheoncloseattribute
toreferenceaJavaScriptfunctionwhichactsasacloselistener.
<f:websocket...onclose="logClose"/>
<script>
functionlogClose(code,channel,event){
if(code==-1){
//WebSocketAPInotsupportedbyclient.E.g.
IE9.
}
elseif(code==1000){
//Normalcloseasresultofexpiredviewor
session.
}
else{
//Abnormalcloseasresultofaclientorserver
error.
}
}
</script>
TheJavaScriptfunctionwillbeinvokedwiththreearguments.1. code:theclosereasoncodeasinteger.Ifthisis-1,thentheWebSocket
JavaScriptAPIissimplynotsupported bytheclient.Ifthisis1000,thenanormalclosurehasoccurredasconsequenceofanexpiredvieworsessionintheserverside.
2. channel:theWebSocketchannelname.Thismaybeusefulincaseyouintendtohaveagloballistener.
3. event:theoriginalCloseEventobject.ThismaybeusefulincaseyouintendtoinspectitintheJavaScriptfunction.
Whenthefirstconnectionattemptsucceedsbutitlatergetsdisconnectedforsomereason(e.g.,becausetheserverisrestarting),thenitwillbydefaultkeeptryingtoreconnect.InthecaseofMojarra,itwillkeepretryingupto25times,with
3
anintervalwhichisincremented500mseachtime,anditwilleventuallyreportanerror.
Asyoumighthavenoticedintheaforementionedoncloselistenerfunctionexample,youcouldjustcheckiftheclosecodeofa<f:websocket>equals1000inordertoperformsomeclient-sideactionviaJavaScript(e.g.,displayingawarningmessageand/orredirectingtosome“Sessionexpired”page).
<f:websocketchannel="push"scope="session"
onclose="closeListener"/>
<script>
functioncloseListener(code){
if(code==1000){
window.location=jsf.contextPath+
"/expired.xhtml";
}
}
</script>
Thisworksforbothview-andsession-scopedsockets.Application-scopedsockets,however,remainopenforeveraslongasthedocumentisstillopenonclientside,evenwhentheunderlyingvieworsessionhasexpired.
BreakingDownMojarra’sf:websocketImplementationThe<f:websocket>APIspecifiesthefollowingclassesandmethods:
javax.faces.push.Push,aCDIqualifiertofor@Inject.Withhelpofthisqualifierthesocketchannelnamecanbespecified.
javax.faces.push.PushContext,aninterfacewiththreesend()methods:send(Stringmessage),send(Stringmessage,Suser),andsend(Stringmessage,Collection<S>users).AllthosemethodsacceptthepushmessageasObjectandwillforJavaScriptconvertittoaJSONstring.AllthosemethodsreturnFuture<Void>foreachmessage.Ifitreturnsnull,thenthetargetsocketisn’topenatall.Ifitdoesn’tthrowExecutionExceptiononFuture#get()methodcall,thenthemessagewassuccessfullydelivered.
javax.faces.component.UIWebsocket,acomponentwhichimplementsClientBehaviorHolderinordertosupportnested<f:ajax>.Historically,theprototypetagusedaTagHandlerinsteadofUIComponent.Itwaslaterdecidedtoletthetagsupport<f:ajax>asthatwouldmakecomplexandstatefulUIupdatesmucheasier.However,itisn’tpossibletoletaTagHandlerimplementClientBehaviorHolderandbenefitallofbuilt-inAjaxmagic,hencetheconversiontoUIComponent.
ViewHandler#getWebsocketURL()methodwhichtakesachannelnameandreturnstheabsoluteWebSocketURLinformofws://host:port/contextjavax.faces.pushchannelwithhelpofExternalContext#encodeWebsocketURL().
ExternalContext#encodeWebsocketURL()methodwhichbasicallytakesarelativeWebSocketURIinformof/contextjavax.faces.pushchannelandreturnstheabsoluteWebSocketURL.
Theactualimplementationisfairlyextensive.It’sdirectlybasedonOmniFaces<o:socket> with,hereandthere,afewadjustmentssuchasusingacomponent’sclientIDinsteadofachannelnameinJavaScriptAPIfunctions.
com.sun.faces.renderkit.html_basic.WebsocketRenderer,afacesrendererclasswhichregistersduringencodingthesocketchannel,scopeanduserinWebsocketChannelManagerandretrievestheWebSocketURLfromit.Thenitauto-includesthejsf.jsscriptcontainingthenecessaryjavax.push.*functions,andrendersthejsf.push.init(...)inlinescriptfunctioncallwithamongotherstheWebSocketURLasanargument.ThisfunctionshouldinturninJavaScriptcreateanewWebSocket(url).TheWebsocketRendererwillalso
4
subscribetheWebsocketFacesListenertothecurrentview.
com.sun.faces.push.WebsocketChannelManager,asession-scopedCDImanagedbeanwhichkeepstrackofallsofarregistered<f:websocket>channels,scopes,andusersandensuresthateachsocketgetsitsownuniquechannelidentifier.ItwillregistereverychannelidentifierinWebsocketSessionManagerandthoseofuser-targetedsocketsinWebsocketUserManager.
com.sun.faces.push.WebsocketFacesListener,asystemeventlistenerwhichlistensonPreRenderViewEventandrendersifnecessarythejsf.push.open(...)orjsf.push.close(...)inlinescriptfunctioncallsdependingonwhethertheconnectedattributerepresentsadynamicELexpressionwhichgotchangedduringanAjaxrequest.
com.sun.faces.push.WebsocketEndpoint,aclasswhichimplementsJSR-356javax.websocket.EndpointandlistensontheURItemplatejavax.faces.push{channel}.WhenanewWebSocket(url)iscreatedandopenedonclient-sideJavaScript,thenanewjavax.websocket.Sessioniscreatedonserver-sideJavaandtheWebsocketEndpointwilladdthisSessiontoWebsocketSessionManager.Equivalently,whenasocketisclosed,thentheWebsocketEndpointwillremoveitfromWebsocketSessionManager.
com.sun.faces.push.WebsocketSessionManager,anapplication-scopedCDImanagedbeanwhichcollectsallsofaropenedsocketsessionsandvalidatesthattheiruniqueWebSocketURLhasbeenregisteredbyWebsocketChannelManager.
com.sun.faces.push.WebsocketUserManager,anapplication-scopedCDImanagedbeanwhichcollectsthechannelidentifiersofallsofaropeneduser-targetedsockets.
com.sun.faces.push.WebsocketPushContext,theconcreteimplementationofthePushContextinterface.ItwillsendthepushmessageviaWebsocketSessionManagerandifnecessaryobtaintheuser-targetedchannelsviaWebsocketUserManager.
com.sun.faces.push.WebsocketPushContextProducer,theCDIproducerwhichcreatestheWebsocketPushContextinstancebasedonchannelnameasobtainedfrom@Pushqualifier,theWebsocketSessionManagerandWebsocketUserManager.
Footnotes1https://github.com/javaee/websocket-spec/issues/211.
2
https://javaserverfaces.github.io/docs/2.3/jsdocs/symbol
s/jsf.push.html.
3http://caniuse.com/#feat=websockets.
4http://showcase.omnifaces.org/push/socket.
(1) (2)
©BaukeScholtz,ArjanTijms2018BaukeScholtzandArjanTijms,TheDefinitiveGuidetoJSFinJavaEE8,https://doi.org/10.1007/978-1-4842-3387-0_11
11.CustomComponents
BaukeScholtz andArjanTijms
Willemstad,Curaçao Amsterdam,Noord-Holland,TheNetherlands
InChapter7youshouldhavelearnedthatFacelettemplatesasin<ui:composition>,<ui:include>,and<ui:decorate>areusefulwhenyouwanttosplitmainpagelayoutfragmentsintoreusabletemplates,suchasheader,menu,maincontent,andfooter.AndthatFacelettagfilessuchas<t:field>areusefulwhenyouwanttohaveareusablegroupofcomponentsinordertominimizecodeduplication.Andthatcompositecomponentssuchas<t:inputLocalTime>areusefulwhenyouwanttocreateacustomcomponentwithasingleresponsibilitybasedonexistingcomponentsand,ifnecessary,abunchofHTML.
However,theremaybecasesinwhichnosinglecomponentexistsforthepurposeyouhadinmind,evennotwhencomposedofexistingcomponentsandabunchofHTML.Or,perhapsthecomponentdoesexist,butitsrendererisnotdoingthingsyouhadinmind.Atthispoint,youwouldneedtocreateacustomcomponentoracustomrenderer.
JSF(JavaServerFaces)hassincethebeginningoffereda
1 2
highdegreeofabstractionaroundtheUIComponentAPI(applicationprogramminginterface).Youcancustomizecomponentsbycreatingabrand-newcustomUIComponent,orbyextendinganexistingcomponentfromthestandardHTMLcomponentset,orbypluggingacustomRendererforanexistingcomponent.
ComponentType,Family,andRendererTypeEachUIComponentinstancehas“componenttype,”“componentfamily,”and“renderertype”associatedwithit.Thecomponenttypebasicallyrepresentstheuniquecomponentidentifierassociatedwiththecomponenttag.ItcanberegisteredintoJSFviaeitherthe@FacesComponentannotationorthe<component>entryinfaces-config.xml.Thefollowingexampledemonstratestheusageoftheannotationonaminimalcomponentclass:@FacesComponent(SomeComponent.COMPONENT_TYPE)
publicclassSomeComponentextendsUIComponentBase{
publicstaticfinalStringCOMPONENT_TYPE=
"project.SomeComponent";
publicstaticfinalStringCOMPONENT_FAMILY=
"project.SomeFamily";
publicSomeComponent(){
setRendererType(SomeRenderer.RENDERER_TYPE);
}
@Override
publicStringgetFamily(){
returnCOMPONENT_FAMILY;
}
}
Andthefollowingexampledemonstratestheusagewiththeentryinfaces-config.xml:<component><component-type>project.SomeComponent</component-
type>
<component-class>
com.example.project.component.SomeComponent
</component-class>
</component>
NotethatwhenyouregisteraJavaEEartifactviaboththeannotationandtheXMLwaysusingthesameidentifier,thentheXMLdeclarationwillalwaysgetprecedenceovertheannotationdeclaration.ThesameholdstrueforallJSFannotations.
ThepublicconstantsCOMPONENT_TYPEandCOMPONENT_FAMILYinthecomponentclassarenotmandatory,buttheyfollowthesameconventionasthestandardJSFcomponentsetdoesandthereforegivemoreconsistencyindevelopingwithJSF.ThepublicconstantCOMPONENT_TYPEallowsthedevelopertoprogrammaticallycreatethecomponentwithoutneedingtohard-codethecomponenttype.
UIComponentcomponent=FacesContext.getCurrentInstance()
.getApplication().createComponent(SomeComponent.COMPONENT
_TYPE);
NotethatprogrammaticallycreatingUIComponentinstancesthiswayisgenerallynotthenormalpracticeinanaverageJSFwebapplication.Instead,younormallydefinethecomponentsintheviewandleavethejobofcreatingUIComponentinstancesuptoJSForanypluggablecomponentlibrary.InthecaseofFaceletsviewtechnology,thecomponenttagscanberegisteredintoJSFviaeither@FacesComponent(createTag=true)ora<tag>entryina*.taglib.xmlfilealongwiththecomponenttypeasfollows:<tag><tag-name>someComponent</tag-name>
<component>
<component-
type>project.SomeComponent</component-type>
</component>
</tag>
Assaid,thestandardJSFcomponentsethasthecomponenttypealsodefinedintheconcreteUIComponentclassesbehindthecomponenttags.ThoseUIComponentclassesarealllocatedinthejavax.faces.component.htmlpackage.TheUIComponentclassnamecanbederivedfromthecomponenttagnamebyprefixingitwith“Html”.Soisthecomponenttag<h:outputText>backedbytheHtmlOutputTextcomponent.JSFcan,viaeitherthe@FacesComponentannotationorthe<component>entryinfaces-config.xml,figureoutwhichcomponentclassexactlyisassociatedwiththecomponenttag,soJSFknowsthat,forthe<h:outputText>tag,itshouldcreateaconcreteHtmlOutputTextinstance.
OnceJSFhastheconcreteUIComponentinstanceathand,itcanfigureoutthecomponentfamilyaswellastherenderertypebyinvokingthemethodsUIComponent#getFamily()andUIComponent#getRendererType(),respectively.ThisinformationismandatoryinordertocreateaconcreteRendererinstanceforthegivenUIComponentinstance,asyoucanseeinthefollowingsnippet:Rendererrenderer=FacesContext.getCurrentInstance().getRenderKit()
.getRenderer(component.getFamily(),
component.getRendererType());
Thecomponentfamilyisbasicallya“hard-coded”constantwhichcanbesharedacrossmultiplecomponenttypes.It’s“hard-coded”insuchwaythatthere’snosetterforit.ThisisneededinordertogettheconcreteRendererinstancesastheyarenotregisteredintoJSFbycomponenttypebutratherbycomponentfamily.Thisallowsthedeveloperofthepluggablerenderkittoregistertherenderertypejustonceinsteadofmultipletimesforeachknownstandardcomponenttypeandunknowncustomcomponenttype.Normally,thecomponentfamilyandrenderertypeareregisteredintoJSFviaeitherthe@FacesRendererannotationorthe<renderer>entryinfaces-config.xml.Thefollowingexampledemonstratestheusagewiththeannotationonaminimalrendererclass.
@FacesRenderer(
componentFamily=SomeComponent.COMPONENT_FAMILY,
rendererType=SomeRenderer.RENDERER_TYPE)
publicclassSomeRendererextendsRenderer{
publicstaticfinalStringRENDERER_TYPE=
"project.SomeRenderer";
}
Andthefollowingexampledemonstratestheusagewiththeentryinfaces-config.xml:<renderkit><renderer>
<component-family>project.SomeFamily</component-
family>
<renderer-type>project.SomeRenderer</renderer-
type>
<renderer-class>
com.example.project.renderer.SomeRenderer
</renderer-class>
</renderer>
</renderkit>
Therenderertypeisbydefaultdefinedintheconstructoroftheconcretecomponentclass,asyoumightalreadyhavenoticedinthecodesnippetoftheSomeComponentclassasshownpreviously.Incaseit’sneeded,thecomponentsubclassdeveloperorevenyourselfascomponentendusercanalwaysoverridethedefaultrendererinstanceofacomponentwiththedesiredrendererinstance.Thiscanbedoneinvariousways,allviaXML.Thefirstwayisviathe<tag>entryassociatedwiththecomponenttaginthe*.taglib.xmlfile.
<tag>
<tag-name>someComponent</tag-name>
<component>
<component-type>project.SomeComponent</component-
type>
<renderer-type>custom.OtherRenderer</renderer-type>
</component>
</tag>
Thisaffectsapplication-wideandtargetsonlythespecificcomponenttag.Thesecondwayisviaanew<renderer>entryinfaces-config.xmlwhichtargetsexactlythedesiredcomponentfamilyanditsdefaultrenderertype.
<renderkit>
<renderer>
<component-family>project.SomeFamily</component-
family>
<renderer-type>project.SomeRenderer</renderer-type>
<renderer-class>
com.example.custom.renderers.OtherRenderer
</renderer-class>
</renderer>
</renderkit>
Thisaffectsapplication-wideandtargetseverycomponenttagassociatedwiththegivencomponentfamilycurrentlyassociatedwiththegivenrenderertype.ThethirdwayisviatherendererTypeattributeofthecomponenttag.
<x:someComponent...rendererType="custom.OtherRenderer"/>
Thisaffectsonlythedeclaredcomponenttagandnotothers.Table11-1providesanoverviewofallcomponenttypes,families,andrenderertypesofthestandardJSFcomponentset.
Table11-1 Componentclass,componenttype,componentfamilyandrenderertypeofstandardJSFHTMLcomponentset
Componenttag
Componentclass
Componenttype
Componentfamily
Renderertype
<h:body>
HtmlBody
javax.faces.OutputBody
javax.faces.Output
javax.faces.Body
<h:button>
HtmlOutcomeTargetButton
javax.faces.HtmlOutcomeTargetButton
javax.faces.OutcomeTarget
javax.faces.Button
<h:column>
HtmlColumn
javax.faces.HtmlColumn
javax.faces.Column
null
<h:commandButton>
HtmlCommandButton
javax.faces.HtmlCommandButton
javax.faces.Command
javax.faces.Button
<h:commandLink>
HtmlCommandLink
javax.faces.HtmlCommandLink
javax.faces.Command
javax.faces.Link
<h:commandScript>
HtmlCommandScript
javax.faces.HtmlCommandScript
javax.faces.Command
javax.faces.Script
<h:dataTable>
HtmlDataTable
javax.faces.HtmlDataTable
javax.faces.Data
javax.faces.Table
<h:doctype
HtmlDocty
javax.faces.Outp
javax.faces
javax.faces.
<h:doctype>
HtmlDoctype
javax.faces.OutputDoctype
javax.faces.Output
javax.faces.Doctype
<h:form>
HtmlForm
javax.faces.HtmlForm
javax.faces.Form
javax.faces.Form
<h:graphicImage>
HtmlGraphicImage
javax.faces.HtmlGraphicImage
javax.faces.Graphic
javax.faces.Image
<h:head>
HtmlHead
javax.faces.OutputHead
javax.faces.Output
javax.faces.Head
<h:inputFile>
HtmlInputFile
javax.faces.HtmlInputFile
javax.faces.Input
javax.faces.File
<h:inputHidden>
HtmlInputHidden
javax.faces.HtmlInputHidden
javax.faces.Input
javax.faces.Hidden
<h:inputSecret>
HtmlInputSecret
javax.faces.HtmlInputSecret
javax.faces.Input
javax.faces.Secret
<h:inputText>
HtmlInputText
javax.faces.HtmlInputText
javax.faces.Input
javax.faces.Text
<h:inputTextarea>
HtmlInputTextarea
javax.faces.HtmlInputTextarea
javax.faces.Input
javax.faces.Textarea
<h:link>
HtmlOutcomeTargetLink
javax.faces.HtmlOutcomeTargetLink
javax.faces.OutcomeTarget
javax.faces.Link
<h:message>
HtmlMessage
javax.faces.HtmlMessage
javax.faces.Message
javax.faces.Message
<h:messages>
HtmlMessages
javax.faces.HtmlMessages
javax.faces.Messages
javax.faces.Messages
<h:outputFormat>
HtmlOutputFormat
javax.faces.HtmlOutputFormat
javax.faces.Output
javax.faces.Format
<h:outputLabel>
HtmlOutputLabel
javax.faces.HtmlOutputLabel
javax.faces.Output
javax.faces.Label
<h:outputText>
HtmlOutputText
javax.faces.HtmlOutputText
javax.faces.Output
javax.faces.Text
<h:outputScript>
UIOutput
javax.faces.Output
javax.faces.Output
javax.faces.Script
<h:outputStylesheet>
UIOutput
javax.faces.Output
javax.faces.Output
javax.faces.resource.Stylesheet
<h:panelGrid>
HtmlPanelGrid
javax.faces.HtmlPanelGrid
javax.faces.Panel
javax.faces.Grid
<h:panelGr
oup>
HtmlPanel
Group
javax.faces.Html
PanelGroup
javax.faces
.Panel
javax.faces.
Group
<h:selectBooleanCheckbox>
HtmlSelectBooleanCheckbox
javax.faces.HtmlSelectBooleanCheckbox
javax.faces.SelectBoolean
javax.faces.Checkbox
<h:selectManyCheckbox>
HtmlSelectManyCheckbox
javax.faces.HtmlSelectManyCheckbox
javax.faces.SelectMany
javax.faces.Checkbox
<h:selectManyListbox>
HtmlSelectManyListbox
javax.faces.HtmlSelectManyListbox
javax.faces.SelectMany
javax.faces.Listbox
<h:selectManyMenu>
HtmlSelectManyMenu
javax.faces.HtmlSelectManyMenu
javax.faces.SelectMany
javax.faces.Menu
<h:selectOneListbox>
HtmlSelectOneListbox
javax.faces.HtmlSelectOneListbox
javax.faces.SelectOne
javax.faces.Listbox
<h:selectOneMenu>
HtmlSelectOneMenu
javax.faces.HtmlSelectOneMenu
javax.faces.SelectOne
javax.faces.Menu
<h:selectOneRadio>
HtmlSelectOneRadio
javax.faces.HtmlSelectOneRadio
javax.faces.SelectOne
javax.faces.Radio
Ifyoucarefullyinspectthetable,you’llseeacertainpatterninthecomponentfamilyandrenderertype,particularlywithinput,select,andcommandcomponents.You’llnoticethatarenderertypecanbesharedacrossmultiplecomponents,evenofadifferentfamily.
You’llalsonoticethatthere’soneHTMLcomponentwithoutarenderertype,<h:column>.Thisisaspecialcomponentwhichcannotbeusedstand-alonebutcanonlybeusedwhennestedinaspecificparentcomponent.FromthestandardJSFcomponentset,that’ssofaronly<h:dataTable>.ItsrendererrecognizeschildrenofthetypeUIColumnandcanactonthemaccordingly.
CreatingNewComponentandRendererIfyoupaidcloserattentiontoTable3-1inChapter3,youmighthavenoticedthatJSFdoesn’tprovideanycomponenttorenderadynamic<ul>or<ol>oreven<dl>elementbasedonaprovidedarrayorcollectionvalue.Itonlysupportsthatforthe<table>element.True,thesamecouldbeachievedwithFacelets<ui:repeat>andabitofcustomHTMLcode,butwe’lltakethisasanopportunitytocreateanewcustomcomponentwhichrendersan<ul>or<ol>.
ThefirststepistocheckwhichUIComponentsubclassissuitableforthetaskwehaveinmind,sothatwecanreducethecustomcodelogictoaminimum.Inthe
javax.faces.componentpackageyoucanfindabunchofUIXxxcomponentsubclasses.Ifyouwanttocreateanewformcomponent,extendfromUIForm.Ifyouwanttocreateanewinputcomponent,extendfromUIInput.Ifyouwanttocreateanewoutputcomponent,extendfromUIOutput.Ifyouwanttocreateanewdataiteratorcomponent,extendfromUIData.ThereisrarelyanyneedtoextendfromUIComponentdirectly.We’dliketobeabletoiterateoveracollectioninordertogenerate<li>elementsinsidethe<ul>,sowe’llpickUIData.Ithasalotofiterationandstatesavinglogicalreadyimplemented.Followingisthecustomcomponentclasscom.example.project.component.DataList:@FacesComponent(createTag=true)
publicclassDataListextendsUIData{
publicDataList(){
setRendererType(DataListRenderer.RENDERER_TYPE);
}
}
Isthatreallyall?Yes,theUIDatasuperclassalreadyhsdeverythingweneedandalltheHTMLproducingcodejustgoesintotheDataListRendererwhichwillbeshownshortly.You’llnoticethatthe@FacesComponentannotationdeclaresacreateTag=trueattribute.ThisbasicallyinstructsJSFthatitshouldautomaticallycreateacomponenttagthepredefinedXMLnamespacehttp://xmlns.jcp.org/jsf/component.Inotherwords,theabovetagisavailableintheFaceletsfileas
follows:<...xmlns:my="http://xmlns.jcp.org/jsf/component">
...
<my:dataList...>
...
</my:dataList>
TheXMLnamespaceprefix“my”isofcourseyourchoice.Generally,you’dliketopicksomesortofabbreviationofyourcompanynamehere.YoucanalsooverridethepredefinedXMLnamespacewiththenamespaceattribute.
@FacesComponent(createTag=true,
namespace="http://example.com/ui")
Thiswillthenbeavailableasfollows:
<...xmlns:ex="http://example.com/ui">
...
<ex:dataList...>
...
</ex:dataList>
Thisnamespaceisunfortunatelynotunifiablewiththe<namespace>ofacustom*.taglib.xmlfile.Ifyouusethesamenamespaceforboth,thenJSFwillpreferthe*.taglib.xmloneoverthe@FacesComponentoneandhencebeunabletofindthecustomcomponenttag.Thatis,inpracticallyanythingJavaEErelated,anyXML-basedregistrationofathinghashigherprecedencethanJavaannotation-basedregistrationoftheverysamething.
You’dbasicallyneedtoexplicitlyregisterthecustom
componentoverthereinthe*.taglib.xmlfileaswell.Here’showtheWEB-INFexample.taglib.xmlascreatedinthesection“TagFiles”inChapter7couldbeextendedwiththeregistrationofthecustomcomponent,whichisessentiallythesameaswhatthe@FacesComponentisdoingforyou.
<?xmlversion="1.0"encoding="UTF-8"?>
<facelettaglib
xmlns:="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-
facelettaglibrary_2_3.xsd"
version="2.3"
>
<namespace>http://example.com/tags</namespace>
<short-name>t</short-name>
<!--Othertagshere-->
<tag>
<description>RendersaHTMLlist.</description>
<tag-name>dataList</tag-name>
<component>
<component-type>dataList</component-type>
</component>
</tag>
</facelettaglib>
Thiswaythecustomcomponentisavailableinthesamenamespaceastheothertags.
<...xmlns:t="http://example.com/tags">
...
<t:dataList...>
...
</t:dataList>
Now,youcanessentiallyremoveallattributesofthe@[email protected],asyouhaveseenintheexample.taglib.xml,thecomponenttypedefaultstotheclassnamewiththefirstcharacterlowercased.Youcanalwaysoverrideitbyexplicitlyspecifyingitasthevalueofthe@FacesComponentannotation.Generally,you’dliketoprefixitwiththenameofthecompany.It’sagoodpracticetodefineitasapublicconstantsothatotherscould,ifnecessary,lookitupintheJavadocand/oruseitforApplication#createComponent().
@FacesComponent(DataList.COMPONENT_TYPE)
publicclassDataListextendsUIData{
publicstaticfinalStringCOMPONENT_TYPE=
"example.DataList";
publicDataList(){
setRendererType(DataListRenderer.RENDERER_TYPE);
}
}
Nowadjustthecomponenttypeintheexample.taglib.xmlaccordingly.
<component>
<component-type>example.DataList</component-type>
</component>
The*.taglib.xmlalsogivesyouroomtoregisterthe
attributesvia<attribute>entries,althoughthatmayendupinsomeverbosecode.Youshouldalreadyhaveseenthatinthesection“Tagfiles”inChapter7.Unfortunately,thecurrentJSFversiondoesn’tofferanannotationtodeclarativelydeclarean“official”componentattribute.There’snosuchthingas@FacesAttributeprivateIterablevalue.Yet.ThismaycomeinaJSF.next.Thenon-officialway,withoutany<attribute>,alsoworksjustfine.Youcandeclareanyattributeyouwantonthecomponenttagintheview.
<t:dataListfoo="bar"bar="foo"/>
That’sjustthefreedomofXML.Whethertheactualcomponentorrendererimplementationactuallydoessomethingwithitisanotherstory.YoucouldevendeclareacustomattributeonanexistingcomponentandjustpluganextendedRendererinordertoprocessthatattribute.Morelaterinthesection“ExtendingExistingRenderer.”Talkingaboutrenderers,our<t:dataList>stillneedsitsrendererasregisteredinitsconstructor.Here’swhatthecom.example.project.renderer.DataListRende
rerlookslike.
@FacesRenderer(
componentFamily=UIData.COMPONENT_FAMILY,
rendererType=DataListRenderer.RENDERER_TYPE)
publicclassDataListRendererextendsRenderer{
publicstaticfinalStringRENDERER_TYPE=
"example.List";
@Override
publicvoidencodeBegin
(FacesContextcontext,UIComponentcomponent)
throwsIOException
{
ResponseWriterwriter=context.getResponseWriter();
UIDatadata=(UIData)component;
if(data.getRowCount()>0){
writer.startElement("ul",component);
}
}
@Override
publicvoidencodeChildren
(FacesContextcontext,UIComponentcomponent)
throwsIOException
{
ResponseWriterwriter=context.getResponseWriter();
UIDatadata=(UIData)component;
for(inti=0;i<data.getRowCount();i++){
data.setRowIndex(i);
writer.startElement("li",component);
if(component.getChildCount()>0){
for(UIComponentchild:
component.getChildren()){
child.encodeAll(context);
}
}
writer.endElement("li");
}
data.setRowIndex(-1);
}
@Override
publicvoidencodeEnd
(FacesContextcontext,UIComponentcomponent)
throwsIOException
{
ResponseWriterwriter=context.getResponseWriter();
UIDatadata=(UIData)component;
if(data.getRowCount()>0){
writer.endElement("ul");
}
}
}
Inhindsight,it’srelativelysimple.We’redelegatingasmuchaspossibleofthehardworktotheJSF-providedUIDatasuperclass.IntheencodeBegin()youstartthe<ul>elementwhenthedatamodelisnotempty.ThisistobecheckedbyexaminingtheresultofUIData#getRowCount().ItsJavadoc basicallysays:
Returnthenumberofrowsintheunderlyingdatamodel.Ifthenumberofavailablerowsisunknown,return-1.
Theterm“rows”isindeedstronglyrelatedtotables.Thisisalsowhatthissuperclassisoriginallydesignedfor:<h:dataTable>.Theterm“item”wouldhavebeenmoregeneral,butitiswhatitis.
Then,intheencodeChildren()method,wesetthecurrentrowindexviatheUIData#setRowIndex()method,startthe<li>element,encodeallchildrenasisviaUIComponent#encodeAll()oneachofthem,andfinallyendthe<li>element.Oncetheloopisdone,weexplicitlymakeitcleartotheUIDatasuperclassbyinvokingUIData#setRowIndex()withavalueof-1.ItsJavadocsays:
1
2
IfthenewrowIndexvalueis-1:Ifthevarpropertyisnotnull,removethecorrespondingrequestscopeattribute(ifany).Resetthestateinformationforalldescendantcomponents.
Itthusclearsoutanystaterelatedtotheiteration.Thisisveryimportant;zotherwiseitmightcausesideeffectsfurtherdowninthecomponenttreeorevencauseacorruptedviewstatewhenitbyitselfneedstotraversethedatamodel.Finally,intheencodeEnd()method,itwillendthe<ul>elementbasedonsameconditionsasinencodeBegin().
TheUIData#setRowIndex()callintheencodeChildren()methodwillunderthehoodextractthedatamodelfromthevalueattributeandwrapitinasuitableimplementationofthejavax.faces.model.DataModelabstractclass. Sofar,aspertheJavadocofUIData#getValue(), thefollowingtypesoftheobjectbehindthevalueattributearesupported,inthisscanningorder:
1. java.util.List(since1.0)
2. Arrays(since1.0)
3. java.sql.ResultSet(since1.0)
4. javax.servlet.jsp.jstl.sql.Result(since1.0)
5. java.util.Collection(since2.2)
6. java.lang.Iterable(since2.3)
7. java.util.Map(since2.3)
8. TypesforwhichasuitableDataModelhasbeenregisteredvia@FacesDataModel(since2.3)
9. AllothertypeswillbeadaptedusingtheScalarDataModelclass,whichwilltreattheobjectasasinglerowofdata(since1.0)
Youwouldindeednotexpecttoseeanyonepassingarounda
3
4
plainjava.sql.ResultSetinamodernJavaEEapplication,letaloneseeJSPpageswithJSTL<sql:xxx>tags.Butthisallisforbackwardcompatibility.Remember,JSFwasintroducedin2004.Backwardcompatibilitywasoneofitsstrongestkeysinsurvivinguptotoday.They’recertainlycandidatestoberemoved,butnotnow.
And,there’sindeedanoverlapbetweensometypes;ListandCollectioncouldeasilybecoveredbyIterableastheybothimplementthisinterface.Butthishasaperformancereason.ForaList,theitemsareaccesseddirectlybyindexbytheListDataModel;foraCollection,theitemsareextractedfirstviaCollection#toArray()andthenaccessedbyindexbytheCollectionDataModel;andforanIterable,theitemsaresimplyiteratedandcollectedintoanewListfirstbytheIterableDataModel.Itmaymakeadifference.
You’llalsoseethatJSF2.3hasnotonlyaddedtwonewdatamodelsbutevenintroducesanewannotationtoregisteracustomone.Previously,you’dneedtomanuallywrapthecustomcollectioninthecustomdatamodeleverytimebeforepassingtoaUIDatacomponent.TheDataModelabstractclasshasthedisadvantageofnotbeingSerializableitselfwhichmoreorlessforcesyoutomakea@ViewScopedbeanholdingsuchadatamodeltohavealazyloadinggetteronatransientdatamodelpropertyasfollows:privateYourCollectionyourCollection;
privatetransientYourDataModeldataModel;
publicDataModelgetDataModel(){
if(dataModel==null){
dataModel=newYourDataModel(yourCollection);
}
returndataModel;
}
Ideally,theUIDatashouldbyitselfrecognizeYourCollectiontypeandautomaticallywrapitinaYourDataModel.Thenew@FacesDataModelannotationdoesexactlythat.
@FacesDataModel(forClass=YourCollection.class)
publicclassYourDataModel<E>extendsDataModel<E>{}
Comingbacktothecustomrenderer,there’sonemethodleftunexplained:thegetRendersChildren().It’sbeenoverriddentoexplicitlyreturntrue.You’llprobablyaskyourself,whywasitinitiallyfalse?WhynotjustletitbethedefaultbehaviorofencodeChildren()andrelyonanyoverriddenencodeChildren()methodwhetheritwantstoinvokeencodeAll()onthechildren?Thiswasactuallyanhistoricoversightinthespecification.Originally,theencodeAll()methoddidn’texist.ItwasonlyaddedinJSF1.2anditbasicallymadethegetRendersChildren()obsolete.Butforbackwardcompatibilitythiscomplexitywasintroduced.
Inanutshell,alwaysletgetRendersChildren()returntrueifyouhaveoverriddentheencodeChildren()method.Otherwisethechildrenwon’tbeencodedatall.
Lastbutnotleast,you’llprobablyalsowonderwhywedon’t“simply”overridetheencodeBegin(),encodeChildren(),(andgetRendersChildren()),
andencodeEnd()oftheDataListcomponentbutinsteadcreatea“whole”rendererimplementation.Themainreasonis:simplicityandextensibility.ThosemethodsareonUIComponentspecifiedtodomorethanonlyrendering.TheyalsocheckiftheUIComponent#isRendered()returnstrue.encodeBegin()alsofiresPreRenderComponentEventandpushesthecurrentcomponentintotheEL(ExpressionLanguage)scopeas#{component}.encodeEnd()pops#{component}outoftheELscope.Andtheyalsocheckifthere’sarendererattachedand,ifso,delegatetoit.That’sallspecifiedintheirJavadoc.You’dneedtomanuallytakecareofthemyourselfwhenyouoverridethosemethods.That’sunnecessarilyrepeatedwork.And,whensomeoneinthefuturewantstoadjusttherenderingofthecomponent,theywon’tbeabletoplugacustomrendererifyourcomponentdoesn’tcheckforit.
ExtendingExistingComponentImaginethatthere’sanexistingcomponentwhosebehavioryou’dliketoadjust,usuallybyaddingoneormorenewcustomattributes.Ifthoseattributesarepurelyoutput-only,thenyoucouldjustmakeuseofthepassthroughattributesfeaturewhichwasintroducedinJSF2.2.Previously,anycustomattributewhichwasn’tofficiallysupportedbythecomponentwassimplyignoredduringviewrendertime.Forexample,whenyou’dliketoaddthemissingacceptattribute totheexisting<h:inputFile>component,simplyaddingtheattributeasfollowswon’twork.
<h:inputFileid="photo"value="#{editProfile.photo}"
5 6
accept="image/*"/>
WiththepassthroughattributesfeatureyoucouldexplicitlyinstructJSFtorenderthecustomattributeanyway.Thiscanbedoneintwoways.Thefirstwayisregisteringitviathehttp://xmlns.jcp.org/jsf/passthroughXMLnamespace.
<...xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:a="http://xmlns.jcp.org/jsf/passthrough">
...
<h:formenctype="multipart/form-data">
<h:inputFile...a:accept="image/*"/>
</h:form>
Anotherwayisdeclaringitviathe<f:passThroughAttribute>tag.
<...xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html">
...
<h:formenctype="multipart/form-data">
<h:inputFile...>
<f:passThroughAttributename="accept"
value="image/*"/>
</h:inputFile>
</h:form>
It’lljustworkfineontheclientsideeitherway.Onthebrowserssupportingthisattribute,thefilebrowsedialogwillonlyshowthefilesmatchingthecommaseparatedIANA(InternetAssignedNumbersAuthority)mediatypesspecifiedintheacceptattribute.However,thiswon’tworkinbrowsersnotsupportingthisattribute, norwillitvalidate
7
8
anythingontheserverside.Evenifthebrowsersupportsit,anymalicious-mindedendusercaneasilymanipulatetheretrievedHTMLdocumentandremovetheacceptattributeandhencebeabletouploadadifferentfiletype.
Forexactlythatserver-sidework,you’dliketoextendthe<h:inputFile>componenttoperformvalidationbasedontheacceptattribute.ThefirststepislookingatwhichUIComponentclassexactlyisrepresentedby<h:inputFile>.AsyoucanseeinTable11-1,that’sjavax.faces.component.html.HtmlInputFile.Let’sstartbyextendingitandaddingthenewacceptattribute.
@FacesComponent(createTag=true)
publicclassInputFileextendsHtmlInputFile{
@Override
publicvoidencodeBegin(FacesContextcontext)throws
IOException{
Stringaccept=getAccept();
if(accept!=null){
getPassThroughAttributes().put("accept",accept);
}
super.encodeBegin(context);
}
publicStringgetAccept(){
return(String)getStateHelper().eval("accept");
}
publicvoidsetAccept(Stringaccept){
getStateHelper().put("accept",accept);
}
}
Notethatthere’snopropertyfortheacceptattribute.Anypubliccomponentattributemustberepresentedbyagetter/setterpairwhichdelegatesfurthertoUIComponent#getStateHelper().Basically,youmustdelegateallview-scopedcomponentattributestotheStateHelper.ThiswillinturntakecarethattherightdeltasendupintheJSFviewstate.Thisisofcourseoptional,butnotdoingsowillmakethecomponentinstancenotprogrammaticallymanipulatable.AnychangesperformedduringapreviousHTTPrequestwouldgetlostduringthesubsequentHTTPpostbackrequestfortheverysimplereasonthattheUIComponentinstanceisrecreatedfromscratch.
AlsonotethatthenewacceptattributeissimplyaddedasapassthroughattributeintheencodeBegin()methodbeforedelegatingtothesuperclassmethodwheretheactualrenderingjobtakesplace.Thisremovestheneedtocreateawholecustomrendererfortheparticularpurposeofrenderinganewattribute.Let’stestitnow.
<...xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:my="http://xmlns.jcp.org/jsf/component">
...
<h:formenctype="multipart/form-data">
<my:inputFileid="photo"value="#{editProfile.photo}"
accept="image/*"required="true">
<f:ajaxlistener="#{editProfile.upload}"
render="photo_m"/>
</my:inputFile>
<h:messageid="photo_m"for="photo"/>
</h:form>
Forthesakeofcompleteness,here’swhatthebackingbeanlookslike.
@Named@RequestScoped
publicclassEditProfile{
privatePartphoto;
publicvoidupload(){
StringfileName=photo.getSubmittedFileName();
StringfileType=photo.getContentType();
longfileSize=photo.getSize();
System.out.println("Filename:"+fileName);
System.out.println("Filetype:"+fileType);
System.out.println("Filesize:"+fileSize);
}
//Add/generategetterandsetter.
}
NowwehavebasicallycreatedtheUIComponentequivalentof<h:inputFile>withapassthroughacceptattribute.Thenextstepisimplementingserver-sidevalidationofwhetherthemediatypeoftheuploadedfilematchesthespecifiedacceptattribute.Forthis,we’dliketooverrideUIInput#validateValue()inourInputFileclassasbelow.Thisrunsduringtheprocessvalidationsphase(thirdphase).
@Override
protectedvoidvalidateValue(FacesContextcontext,Object
newValue){
Stringaccept=getAccept();
if(accept!=null&&newValueinstanceofPart){
Partpart=(Part)newValue;
StringcontentType=context.getExternalContext()
.getMimeType(part.getSubmittedFileName());
StringacceptPattern=accept.trim()
.replace("",".").replaceAll("\\s*,\\s*","|");
if(contentType==null||
!contentType.matches(acceptPattern)){
Stringmessage="Unacceptablefiletype";
context.addMessage(getClientId(context),new
FacesMessage(
FacesMessage.SEVERITY_ERROR,message,null));
setValid(false);
}
}
if(isValid()){
super.validateValue(context,newValue);
}
}
Asyousee,itwillbasicallycheckiftheacceptattributeisspecifiedandifthere’sasubmittedfile,andifsothenconverttheacceptattributetoaregularexpressionpatternandmatchthecontenttypeofthesubmittedfileagainstit.TheacceptattributerepresentsacommaseparatedstringofIANAmediatypeswhereintheasteriskisusedasawildcardandthecommaisusedasadisjunctionoperator.Anexampleacceptvalueof"image/*,application/pdf"isthiswayconvertedtoaregularexpressionof"image/.*|application/pdf".Ifitdoesn’tmatch,thenitwilladdafacesmessageassociatedwiththecomponenttothefacescontextandmarkthecomponentasinvalidbycallingUIInput#setValid()withfalse.Intheend,ifthecomponentisvalid,itwillcontinuethevalidationcalltothesuperclass.
Furtherthere’sanotherthingtomention:thecontenttype
isnotobtainedfromPart#getContentType()butfromExternalContext#getMimeType()basedonthesubmittedfilename.Thisisjusttocoverthecornercasethattheclientdoesn’tsendacontenttypealong,orevensendsonewhichisnotunderstoodbytheserver.ExternalContext#getMimeType()basicallyobtainsthelistofknowncontenttypesfrom<mime-mapping>entriesinweb.xml.Theserveritselfhassomedefaultvaluesandyoucanoverrideorextendtheminthewebapplication’sownweb.xml.
Nowthefile’scontenttypeattributeisfilteredontheclientsideandvalidatedontheserverside.Allgoodandwell,butthisofcourseonlyvalidatesthefile’scontenttypebasedonthefilenameandnotthefile’sactualcontent.ImaginethatonecreatesaZIPfileandsimplyrenamesthefileextensiontobecomeanimagefile,orevenanexecutablefilewithmalware.Itwouldstillpassthroughthefiletypevalidationonboththeclientandserverside.Frankly,thisresponsibilityisnotuptothecomponentitself,buttoyou,theJSFdeveloper.Thecorrectsolutionwouldbetocreateacustomvalidatorandattachittothecomponent.Here’swhatsuchanimagefilevalidatorcanlooklike,withalittlehelpfromtheJava2DAPIwhichiscapableofparsingimagefiles.Ifitthrowsanexceptionorreturnsnull,thenit’sverydefinitelynotanimagefile.
@FacesValidator("project.ImageFileValidator")
publicclassImageFileValidatorimplementsValidator<Part>{
@Override
publicvoidvalidate
(FacesContextcontext,UIComponentcomponent,Part
value)
throwsValidatorException
{
if(value==null){
return;//Let@NotNullorrequired="true"
handle.
}
try{
ImageIO.read(value.getInputStream()).toString();
}
catch(Exceptione){
Stringmessage="Notanimagefile";
thrownewValidatorException(new
FacesMessage(message),e);
}
}
}
Inordertogetittorun,declareitasvalidatorattributeofthecomponenttag.
<my:inputFile...validator="project.ImageFileValidator"/>
Itworksbeautifully.Now,whenvalidationpassesaswell,thebackingbeanactionmethodisinvokedwhereinyoucansavetheuploadedfiletothedesiredlocation.Thiscouldbeimplementedasfollows:
publicvoidupload(){
Pathfolder=Paths.get("/path/to/uploads");
StringfileName=Paths.get(photo.getSubmittedFileName())
.getFileName().toString();
intindexOfLastDot=fileName.lastIndexOf('.');
Stringname=fileName.substring(0,indexOfLastDot);
Stringextension=fileName.substring(indexOfLastDot);
FacesMessagemessage=newFacesMessage();
try(InputStreamcontents=photo.getInputStream()){
Pathfile=Files.createTempFile(folder,name+"-",
extension);
Files.copy(contents,file,
StandardCopyOption.REPLACE_EXISTING);
message.setSummary("Uploadedfilesuccessfully
saved.");
}
catch(IOExceptione){
message.setSummary("Couldnotsaveuploadedfile,try
again.");
message.setSeverity(FacesMessage.SEVERITY_ERROR);
e.printStackTrace();
}
FacesContext.getCurrentInstance().addMessage(null,
message);
}
Youmightwonderwhyitseemstosavetheuploadedfileasatemporaryfile.Thisisactuallynottrue.We’rejustutilizingtheFiles#createTempFile()facilityinordertoguaranteetheuniquenessofthefilenameofthesavedfile.Itwillautomaticallyincludeauniquerandomstringbetweenthefilenameandthefileextension.Otherwise,whenmultiplepeopleuploaddifferentfileswithcoincidentallythesamename,theymayoverwriteeachotherandwe’dloseinformation.
ExtendingExistingRendererImaginethatthere’sanexistingrendererwhichhaslogicbugsorshortcomingsandyou’dliketoquicklypatchitbyextendingitinsteadofrewritingfromscratch.Unfortunately,
itsoundsfareasierthanitactuallyis.Thatis,standardrendererimplementationsarenotpartofthestandardJSFAPI,contrarytostandardHTMLcomponentimplementationswhichareavailableinthejavax.faces.component.htmlpackage.TheactualstandardHTMLrendererimplementationsareprovidedbytheJSFimplementationitself.Mojarrahastheminthecom.sun.faces.renderkit.html_basicpackageandMyFaceshasthemintheorg.apache.myfaces.renderkit.htmlpackage.
AnotherproblemwiththosestandardHTMLrenderersisrelativelypoorabstractionofthecode.BasicallyallthosestandardHTMLrenderersdon’thaveabstracted-outpiecesofcodewhichsolelyemitHTMLmarkupinsuchwaythatit’sfullyseparatedfromthelogic.Inotherwords,whenyouneedtofixsomelogic,you’dalmostalwaysalsohavetowriteorcopy/pasteallthecoderesponsibleforemittingHTML.
Acommonreal-worldexampleisthedesiretolet<h:message>or<h:messages>renderthefacesmessageunescaped,sothatyoucanembedsomeHTMLcodeinafacesmessage,morethanoftentoprovidealinktothedesiredtargetpage(e.g.,“Unknownuser,perhapsyouwantto<ahref="login">Login</a>?”).Thestandard<h:message>componentdoesn’tsupportsuchfacilityandtheHTML-escapingiscontrolledbyitsrendererwhichinturnisthusJSFimplementationdependent.ThisJSFbuilt-inHTMLescapingisfoundoverallplaceandisaveryimportantguardagainstpossibleXSSattackholeswhenyou’reabouttoembeduser-controlleddatainthewebpage.Thereareahandfulofcomponentswhichhavean
explicitattributetoturnoffthisHTML-escaping,suchas<h:outputText>withitsescapeattribute,<f:selectItem>withitsitemEscapedattribute,and<f:selectItems>withitsitemLabelEscapedattributes.Suchanattributeis,however,absentin<h:message>and<h:messages>.SeealsoJSFspecissue634. PerhapsitwillbeaddedinJSF.next,butfornowyoucan’tgoaroundathird-partycomponentlibraryorextendingtheexistingstandardHTMLrenderer.
We’lltakethatasanexampletoextendanexistingstandardHTMLrendererfor<h:message>.Thefirststepislookingatwhichrendererexactlyiscurrentlyusedbythe<h:message>component.InTable11-1youwillseethatthiscomponentisbackedbytheHtmlMessageclass.Thecurrentlyusedrendererimplementationisprogrammaticallyasfollows:StringcomponentFamily=HtmlMessage.COMPONENT_FAMILY;
StringrendererType=new
HtmlMessage().getRendererType();
Rendererrenderer=
FacesContext.getCurrentInstance().getRenderKit()
.getRenderer(componentFamily,rendererType);
System.out.println(renderer.getClass());
Incaseyou’reusingMojarraasJSFimplementation,it’llprintasfollows:classcom.sun.faces.renderkit.html_basic.MessageRenderer
That’sthusexactlytherendererclasswe’dliketoextend.Mojarraisopensourceanditssourcecodeiscurrentlyavailableat
9
https://github.com/javaserverfaces/mojarra
.Onceyou’vegottentheMessageRenderersourcecodeathands,thenextstepistofigureoutwhereexactlythesummaryanddetailoftheFacesMessageisbeingrenderedandhowexactlywecanoverrideitwithaminimumofcode.WecanseeinthesourcecodethatittakesplaceintheencodeEnd()methodwhich,inthecurrentMojarra2.3.3version,isalready182linesofcode.It’susingResponseWriter#writeText() torenderthesummaryanddetail.We’dliketoreplacethisbyResponseWriter#write()sothatitdoesn’tperformanyescaping.
Wecanofcourseextendtheclass,copy/pasteallthe182linesoftheencodeEnd()method,andadjustthewriteText()callsforthesummaryaswellasdetailvariablesasfollows:Objectescape=component.getAttributes().get("escape");
if(escape==null||
Boolean.parseBoolean(escape.toString())){
writer.writeText(summary,component,null);
}
else{
writer.write(summary);
}
However,thisisnotterriblyelegant.WhatifweweretocaptureallwriteText()callsduringtheencodeEnd()methodandtransparentlydelegatetowrite()?Thatwouldlookmuchbetter.YoucanachievethisbywrappingtheResponseWriter,settingitonthefacescontext,and
10
passingitthroughthesuperclass.AlmostanypublicJSFAPIartifacthasanequivalentXxxWrapperclassaswellintheAPI.Youcanfindthemallinthe“Allknownimplementingclasses”sectionofthejavax.faces.FacesWrapperJavadoc. AllthosewrapperclassesmakeJSFveryeasilycustomizableandextensible.Allofthemhaveaconstructortakingtheto-be-wrappedclassandyoubasicallyjustneedtopickoneormoreofthemethodsyou’dliketodecorate.
Allinall,here’showwecouldextendtheMessageRenderertodelegateallwriteText()callsduringtheencodeEnd()methodtowrite().
publicclassEscapableMessageRendererextendsMessageRenderer
{
@Override
publicvoidencodeEnd
(FacesContextcontext,UIComponentcomponent)
throwsIOException
{
ResponseWriterwriter=context.getResponseWriter();
try{
context.setResponseWriter(new
ResponseWriterWrapper(writer){
@Override
publicvoidwriteText
(Objecttext,UIComponentcomponent,
Stringproperty)
throwsIOException
{
Stringstring=text.toString();
Objectescape=component.getAttributes()
.get("escape");
if(escape==null
||
11
Boolean.parseBoolean(escape.toString()))
{
super.writeText(string,component,
property);
}
else{
super.write(string);
}
}
});
super.encodeEnd(context,component);
}
finally{
context.setResponseWriter(writer);
}
}
}
Donotethatit’sveryimportanttorestoretheoriginalresponsewriterinthefinallyofthetryblockwhereinthewrappedresponsewriterisbeingused.Inordertogetittorun,registeritasfollowsinfaces-config.xmlonthecomponentfamilyandrenderertypeasassociatedwiththe<h:message>component:<renderkit><renderer>
<component-
family>javax.faces.Message</component-family>
<renderer-type>javax.faces.Message</renderer-
type>
<renderer-class>
com.example.project.renderer.EscapableMessag
eRenderer
</renderer-class>
</renderer>
</renderkit>
No,[email protected]’tworkwhenextendinganexistingrenderer.Theoriginalrenderersarebythemselvesalreadyregisteredontheverysamecomponentfamilyandrenderertype,somewhereinanXMLfile.Andyouknow,anXML-basedconfigurationalwaysgetshigherprecedenceoveranannotation-basedconfigurationwhenbotharediscovered.
Nowyoucanjustsettheescapeattributeoftheexisting<h:message>componenttofalseinordertogettheextendedrenderertodoitsjob.
<h:message...escape="false"/>
Bewarethatyoudon’tembeduser-controlledinputinanyfacesmessagewhichgetsdisplayedinthere,oryou’llopenupapotentialXSSattackhole.
CustomTagHandlersInChapter3,youlearnedaboutthedifferencebetweentheviewbuildtimeandtheviewrendertime,andthattaghandlerssuchasJSTLrunwhilebuildingtheJSFcomponenttreewhileJSFcomponentsrunwhileprocessingtheHTTPrequestandresponsethroughtheJSFlifecycle.NotonlycanJSFcomponentsbecustomizedbutalsotaghandlers.ThisisparticularlyusefulwhenyouwanttocontrolthebuildingoftheJSFcomponenttreeinsteadofprocessingtheHTTPrequestandresponse.
<f:viewParam>isusefulonmaster-detailpages.From
themasterpage,youcanlinktothedetailpagewiththeentityIDastheparameter.Inthedetailpage,youcanloadtheentitybyIDvia<f:viewParam>.Itgoesasfollows:<f:metadata>
<f:viewParamname="id"value="#{editItem.item}"
converter="project.ItemConverter"
converterMessage="Unknownitem"
required="true"requiredMessage="Badrequest">
</f:viewParam>
</f:metadata>
Whenconversionorvalidationfails,afacesmessagewillbeaddedtothefacescontextofthecurrentpage.However,moreoftenyou’djustliketodirectlyredirecttheuserbacktothemasterpage.Thisisrelativelytrivialtoimplementwith<f:event>onPostValidateEvent.No,<f:viewAction>won’tworkasthatwouldn’tbeinvokedinfirstplacewhenthere’saconversionorvalidationerror.
<f:metadata>
...
<f:eventtype="postValidate"listener="#
{editItem.onload()}"/>
</f:metadata>
Whereintheonload()methodlooksasfollows:publicvoidonload()throwsIOException{
FacesContextcontext=
FacesContext.getCurrentInstance();
if(context.isValidationFailed()){
ExternalContextec=
context.getExternalContext();
ec.redirect(ec.getRequestContextPath()+
"/items.xhtml");
}
}
Okay,thatworks,butthiswillendupinboilerplatecodewhenyouhavemoreofsuchpages.Ideally,you’dliketobeabletodeclarativelyregisteraneventlisteneron<f:viewParam>itselfinaself-documentingwaylikebelowsothatyoucankeepthebackingbeancodefreeofmanualrequest-responseprocessingclutter.
<f:metadata>
<f:viewParam...>
<t:viewParamValidationFailedredirect="/items.xhtml"
/>
</f:viewParam>
</f:metadata>
ThiscanbeachievedwithataghandlerwhichbasicallyregistersanewsystemeventlistenerontheUIViewParametercomponentrepresentedbythe<f:viewParam>tag.Thetaghandlerclassmustextendfromjavax.faces.view.facelets.TagHandler.
publicclassViewParamValidationFailedextendsTagHandler
implementsComponentSystemEventListener
{
privateStringredirect;
publicViewParamValidationFailed(TagConfigconfig){
super(config);
redirect=
getRequiredAttribute("redirect").getValue();
}
@Override
publicvoidapply(FaceletContextcontext,UIComponent
parent)
throwsIOException
{
if(parentinstanceofUIViewParameter
&&!context.getFacesContext().isPostback())
{
parent.subscribeToEvent(PostValidateEvent.class,
this);
}
}
@Override
publicvoidprocessEvent(ComponentSystemEventevent)
throwsAbortProcessingException
{
UIComponentparent=event.getComponent();
parent.unsubscribeFromEvent(PostValidateEvent.class,
this);
FacesContextcontext=event.getFacesContext();
if(context.isValidationFailed()){
try{
ExternalContextec=
context.getExternalContext();
ec.redirect(ec.getRequestContextPath()+
redirect);
}
catch(IOExceptione){
thrownewAbortProcessingException(e);
}
}
}
}
Indeed,thisalsoimplementsjavax.faces.event.ComponentSystemEventList
ener.Thisisnotstrictlyrequiredforataghandler;it’sjustdoneforcodeconvenienceinthisspecificexample.Theoverriddenapply()methodisforTagHandlerandtheoverriddenprocessEvent()methodisforComponentSystemEventListener.Intheapply()methodwehavetheopportunitytoprogrammaticallymanipulatetheparentcomponent,beforeit’sbeingaddedtothecomponenttree.
Wecanprogrammaticallyachievethesamebehavioras<f:event>bycallingUIComponent#subscribeToEvent(),passingthecomponentsystemeventtypeandlistenerinstanceofinterest.Thelistenerinstanceofinteresthappenstobejustthecurrenttaghandlerinstance.Whenthecomponentsystemeventofinteresthasbeenpublishedbytheapplication,thentheprocessEvent()methodofthelistenerinstancewillbeinvoked.
ThefirstthingwedoinprocessEvent()istounsubscribethelistenerinstance.ThisisdoneonpurposebecausecomponentsystemeventlistenersareconsideredstatefulandthereforeareinherentlysavedintheJSFviewstate.AneasywaytoobservethisistoreconfigureJSFtosavetheviewstateontheclientsidebyexplicitlysettingthejavax.faces.STATE_SAVING_METHODcontextparametertoclientinweb.xmlandinspectingthesizeofthejavax.faces.ViewStatehiddeninputfieldinthegeneratedHTMLoutputofanyJSFform.Everytimeyouadd<f:event>,ordon’tunsubscribeComponentSystemEventListenerafterithasdoneitsjob,thesizeoftheJSFviewstategrowswiththeserialized
formofthelistenerinstance.Inthisspecificusecaseofalistenerwhichshouldonlyrunduringanon-postback,that’sjustunnecessary;hencetheexplicitunsubscribe.
Now,inordertogetittorun,registeritinWEB-INFexample.taglib.xmlasfollows:<tag><tag-name>viewParamValidationFailed</tag-name>
<handler-class>
com.example.project.taghandler.ViewParamValidati
onFailed
</handler-class>
</tag>
PackaginginaDistributableJARIncaseyouhavedevelopedabunchofreusablecomponents,renderers,taghandlers,tagfiles,compositecomponents,andwhatnot,andyou’dliketopackageitinaJARfileforinclusioninWEB-INFlibofawebapplication,thenyouneedtocreateaso-calledwebfragmentproject.BasicallyallJSF-orientedcomponentsandutilitylibrariessuchasOmniFaces,PrimeFaces,OptimusFaces,BootsFaces,ButterFaces,andDeltaSpikearebuiltlikethis.InMavenperspective,it’smerelyaJARproject.Thekeyistoputfileswhichyounormallyputinthesrc/main/webappfolderofaMavenWARprojectinthesrc/main/resources/META-
INF/resources/[libraryName]folderoftheMavenJARproject.Thereisonemainexception,alldeploymentdescriptorfileswhichyounormallyputinsrc/main/webapp/WEB-INF,suchasweb.xml,
faces-config.xml,*.taglib.xml,andbeans.xmlgodirectlyinthesrc/main/resources/META-INFfolder.Anotherexceptionisthattheweb.xmlfileistobereplacedbyweb-fragment.xml.
The[libraryName]subfolderintheresourcesfolderrepresentsthe“libraryname”whichisgenerallytheURL-friendlyformoftheprojectname.Forexample,“omnifaces”,“primefaces”,“optimusfaces”,“bsf”,“butterfaces”,etc.Exactlythislibrarynameisthenusableinthelibraryattributeofresourcecomponentssuchas<h:outputScript>,<h:outputStylesheet>,and<h:graphicImage>.BelowiswhatsuchaMavenJARprojectlookslikeinEclipsewhenorganizedtoconformtothe“webfragment”rules.Noteparticularlythestructureofthesrc/main/resourcesfolder.Ofcourse,anyJavaclassescangoinsidethesrc/main/javafolderintheusualway.
Wherebythecommon.taglib.xmllookslikethefollowing,withthecompositelibrarynamesettoapathrelativetosrc/main/resources/META-INF/resourcesandthetagfilesourcesettoapathrelativetothelocationofthe*.taglib.xmlfileitself.
<?xmlversion="1.0"encoding="UTF-8"?>
<facelettaglib
xmlns:="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-
facelettaglibrary_2_3.xsd"
version="2.3"
>
<namespace>http://example.com/common</namespace>
<short-name>common</short-name>
<composite-library-name>common/components</composite-
library-name>
<tag>
<tag-name>someTag</tag-name>
<source>resourcescommontags/someTag.xhtml</source>
</tag>
</facelettaglib>
Andwherebytheweb-fragment.xmllooksasfollows,nearlyidenticaltoweb.xml,onlywithadifferentrootelement,<web-fragment>insteadof<web-app>.
<?xmlversion="1.0"encoding="UTF-8"?>
<web-fragment
xmlns:="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-
fragment_4_0.xsd"
version="4.0"
>
<name>common</name>
</web-fragment>
OncesuchawebfragmentprojectisbuiltasaJARfileandincludedinWEB-INFlibofthemainwebapplicationproject,thentheresourcesoftheJARareavailableintheFaceletsfilesofthemainwebapplicationprojectviathelibraryname“common”asfollows:<ui:composition
template="commontemplates/some.xhtml"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:common="http://example.com/common"
>
<ui:definename="content">
<common:someComposite/>
<h:graphicImagelibrary="common"
name="js/some.svg"/>
<ui:includesrc="commonincludes/some.xhtml"/>
<h:outputScriptlibrary="common"
name="scripts/some.js"/>
<h:outputStylesheetlibrary="common"
name="styles/some.css"/>
<common:someTag/>
</ui:define>
</ui:composition>
ResourceDependenciesTheremaybecaseswherebyyourcustomcomponentorrendererdependsonaspecificJavaScriptorStylesheetresource,forwhichyouwouldliketoavoidtheenduserhavingtomanuallyincludeitvia<h:outputScript>or<h:outputStylesheet>.Insuchcases,[email protected]
yannotation useful.Imaginethatyouwouldliketoautomaticallyincludecommon:scripts/some.jsandcommon:styles/some.cssalongwithaparticularcustomcomponent;thenyoucandosoasfollows:
12
@ResourceDependency(library="common",name="some.css",
target="head")
@ResourceDependency(library="common",name="some.js",
target="head")
publicclassSomeCustomComponentextendsUIComponent{
//...
}
YoucanofcoursealsoincludeJSF’sownjavax.faces:jsf.jswhennecessary,i.e.,whenyourcustomcomponenthappenstorelyon,forexample,thejsf.ajax.request()orotherfunctionsprovidedbythestandardJSFJavaScriptAPI.javax.faces:jsf.jscanbeincludedasfollows,withthescriptlibraryandnameavailableasconstantsofResourceHandler.
@ResourceDependency(
library=ResourceHandler.JSF_SCRIPT_LIBRARY_NAME,
name=ResourceHandler.JSF_SCRIPT_RESOURCE_NAME,
target="head")
publicclassSomeCustomComponentextendsUIComponent{
//...
}
Footnotes1https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/UIData.html#getRowCo
unt--.
2https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/UIData.html#setRowIn
dex-int-.
3https://javaee.github.io/javaee-
spec/javadocs/javax/faces/model/DataModel.html.
4https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/UIData.html#getValue
--.
5https://developer.mozilla.org/en-
US/docs/Web/HTML/Element/input/file.
6
https://javaserverfaces.github.io/docs/2.3/vdldocs/facel
ets/h/inputFile.html.
7www.iana.org/assignments/media-types/media-types.xhtml.
8https://caniuse.com/#feat=input-file-accept.
9https://github.com/javaee/javaserverfaces-
spec/issues/634.
10https://javaee.github.io/javaee-
spec/javadocs/javax/faces/context/ResponseWriter.html#wr
iteText-java.lang.Object-
javax.faces.component.UIComponent-java.lang.String-.
11https://javaee.github.io/javaee-
spec/javadocs/javax/faces/FacesWrapper.html.
12https://javaee.github.io/javaee-
spec/javadocs/javax/faces/application/ResourceDependency
.html.
(1) (2)
©BaukeScholtz,ArjanTijms2018BaukeScholtzandArjanTijms,TheDefinitiveGuidetoJSFinJavaEE8,https://doi.org/10.1007/978-1-4842-3387-0_12
12.SearchExpressions
BaukeScholtz andArjanTijms
Willemstad,Curaçao Amsterdam,Noord-Holland,TheNetherlands
Asstatedinthesections“AjaxLifeCycle”inChapter3and“AjaxifyingComponents”inChapter4,theexecuteandrenderattributesof<f:ajax>tagtakeaspace-separatedcollectionofso-calledcomponentsearchexpressions.
ThesearchexpressionshavealwaysbeenpartofJSF(JavaServerFaces)sincethebeginningasthisisusedintheforattributeof<h:outputLabel>,<h:message>,and<h:messages>,buttheyhaveonlybecomeessentialknowledgefortheJSFdevelopersincetheintroductionof<f:ajax>inJSF2.0.Namely,labelsandmessagesareinalmostanycasealreadywithintheverysamenamingcontainerparentasthetargetinputcomponent,sosimplyspecifyingtheIDofthetargetinputcomponentintheforattributealreadysuffices,butthisisnotnecessarilytruefortheexecuteandrenderattributesof<f:ajax>asthetargetcomponentmaysitinadifferentnamingcontainercontextoreveninaphysicallydifferentFaceletsfile.
Toovercomethesedifficulties,JSF2.0introducedafew
1 2
moreabstractsearchexpressions:"@this","@form","@all",and"@none".Somethinglike"@form"isparticularlyeasytouse,asitjustmeanstargetwhateverthecurrentformis.Ifthatcurrentformisdefinedtwoparenttemplatesupfromthepagewhereit’sreferenced,thisreallymakesreferencingitmucheasier.
Althoughtheymadethingsmucheasier,thesekeywordswerequitelimited.Notonlyaretherejustfourofthem,butthey'realsonotextensibleandthedefaultJSFcomponentsetonlyusestheminternallyinthe<f:ajax>tag.Usingtheminothercomponents,eveninJSF’sown<h:outputLabel>,<h:message>,and<h:messages>,aswellasusingthemprogrammatically,wasleftout.Therefore,inJSF2.3a"ComponentSearchExpressionFramework"wasatthelastmomentintroducedthatgreatlyexpandsuponthosefourkeywords.ItwaslargelybasedonaprovenAPI(applicationprogramminginterface)ofPrimeFaces.
RelativeLocalIDsThisisthesimplestformofacomponentsearchexpression.Themostcommonusecasesarefoundintheforattributeof<h:outputLabel>,<h:message>,and<h:messages>components.ItsimplyreferencesthesoleIDofthetargetUIInputcomponent.
<h:outputLabelfor="email"value="Emailaddress"/>
<h:inputTextid="email"value="#{login.email}"
required="true"/>
<h:messagefor="email"/>
1
Thisonlyprerequiresthatthetargetcomponentisalsosittingwithintheverysamenamingcontainerparent.AnamingcontainerparentisacomponentthatimplementstheNamingContainerinterface. InstandardJSF,only<h:form>,<h:dataTable>,<ui:repeat>,and<f:subview>areaninstanceofNamingContainer.AllcompositecomponentsarealsoaninstanceofNamingContainer,buttagfilesarenot.
IncaseyouneedtoreferenceaspecificUIInputcomponentwithinanamingcontainerfrom<h:outputLabel>on,thenyouneedtoappendtheso-callednamingcontainerseparatorcharactertotheIDofthenamingcontainercomponentandthentheIDofthetargetUIInputcomponent.Thedefaultnamingcontainerseparatorcharacterisacolon“:”.ThecurrentlyconfiguredseparatorcharacterisprogrammaticallyavailablebyUINamingContainer#getSeparatorCharacter().
charseparatorCharacter=UINamingContainer
.getSeparatorCharacter(FacesContext.getCurrentInstance);
Thisisconfigurableviathejavax.faces.SEPARATOR_CHARcontextparameterinweb.xml.
<context-param>
<param-name>javax.faces.SEPARATOR_CHAR</param-name>
<param-value>-</param-value>
</context-param>
Caution
2
3
Changingthistosomethingelselikeahyphen“-”orevenanunderscore“_”isnotrecommended.
Inthelongterm,itisconfusingandbrittleasthosecharactersarealsoallowedintheIDattributeitself.JSFdoesnotvalidatethecomponentIDagainstthecurrentlyconfigurednamingcontainerseparatorcharacterandthusitmayeasilyslipthroughandcausetroublewhilereferencingsuchacomponentinasearchexpression.
ComingbacktoreferencingaspecificUIInputcomponentwithinanamingcontainerfrom<h:outputLabel>on,intheexamplecompositecomponent<t:inputLocalTime>asdemonstratedinthesection“CompositeComponents”inChapter7,thehourdropdowncomponenthasanIDof“hour”.Thus,for<h:outputLabel>,whenusingthedefaultnamingcontainerseparatorcharacter,therelativelocalIDofthehourdropdowninsidethecompositecomponentis“time:hour”.
<h:outputLabelid="l_time"for="time:hour"value="Time"/>
<t:inputLocalTimeid="time"value="#{schedule.time}"
required="true"/>
<h:messageid="m_time"for="time"/>
Notethatthisisnotnecessaryfor<h:message>asfacesmessagesareunderthehoodalreadyaddedtothefacescontextwiththeclientIDofthecompositecomponentitself.
UsingrelativelocalIDsalsoworkswithinthecontextof<h:column>of<h:dataTable>.It’stheninterpretedinthecontextofthecurrentlyiteratedrow,evenwhenthetargetcomponentissittinginanothercolumn.Thefollowing
4
exampledemonstratesthat:<h:dataTableid="users"value="#{admin.users}"var="user">
...
<h:column>
<f:facetname="header">Country</f:facet>
<h:selectOneMenuid="country"value="#
{user.address.country}">
<f:selectItemsvalue="#{data.countries}"/>
<f:ajaxrender="city"/>
</h:selectOneMenu>
</h:column>
<h:column>
<f:facetname="header">City</f:facet>
<h:selectOneMenuid="city"value="#
{user.address.city}">
<f:selectItemsvalue="#
{user.address.country.cities}"/>
</h:selectOneMenu>
</h:column>
...
</h:dataTable>
Underthehood,relativelocalIDsareresolvedusingthealgorithmasdescribedintheUIComponent#findComponent()API. Thismeansthatyoucanalsoresolvethemprogrammatically.YouonlyneedtoensurethatthefindComponent()methodisinvokedonthecorrectbasecomponent,noton,forexample,UIViewRoot.
AbsoluteHierarchicalIDs
5
Incasethetargetcomponentisnotwithinthesamenamingcontainerparentasthecurrentcomponent,thenyouneedanabsolutehierarchicalIDinsteadofalocalrelativeID.ThekeydifferenceisthatanabsolutehierarchicalIDstartswiththenamingcontainerseparatorcharacter.ItwillthensearchforthetargetcomponentfromtheUIViewRooton.Suchconstructisoftenusedintherenderattributeof<f:ajax>whenitneedstoreferenceacomponentthatisnotlocatedinsidethesameform.
<h:formid="search">
...
<h:commandButtonid="submit"...>
<f:ajaxexecute="@form"render=":results"/>
</h:commandButton>
</h:form>
<h:panelGroupid="results"layout="block">
...
</h:panelGroup>
AlesscommonusecasewhereanabsolutehierarchicalIDisneedediswhenyouneedtoreferenceacomponentthatisinturnnestedinanothernamingcontainer—forexample,whenyouwanttoupdate<h:message>associatedwithacompositecomponentduringa<cc:clientBehavior>eventinsidethecompositecomponent.
<h:formid="form">
<h:outputLabelid="l_time"for="time:hour"value="Time"
/>
<t:inputLocalTimeid="time"value="#{schedule.time}"
required="true">
<f:ajaxrender=":form:m_time"/>
<h:messageid="m_time"for="time"/>
</h:form>
YoucouldarguethatthisisabugoranoversightintheJSFspecification.ThisisverytrueandshouldbeworkedonforanextversionofJSF.Anotheryetlesscommonusecaseiswhenyouneedtoupdateaspecificiterationroundofaniterationcomponent,suchas<h:dataTable>and<ui:repeat>.
<h:formid="form">
<h:dataTableid="table"value="#{bean.items}"var="item">
<h:column>
<h:panelGroupid="column1"layout="block">
...
</h:panelGroup>
</h:column>
<h:column>
<h:panelGroupid="column2"layout="block">
...
</h:panelGroup>
</h:column>
</h:dataTable>
<h:commandButtonvalue="Updatesecondrow">
<f:ajaxrender=":form:table:1:column1
:form:table:1:column2">
</f:ajax>
</h:commandButton>
</h:form>
Notethattheiterationindexiszero-basedaswithnormalJavacollectionsandarrays.Alsonotethatyoubasicallyneedtowrapthecell’scontentinanothercomponentinordertoproperlyreferencethecell’scontent,andthatyouneedtoexplicitlyspecifyeverycolumninordertoupdatetheentirerow,asdemonstratedabove.Updatingtheentirecolumnis
alsopossible,butlessconvenientbecauseyoubasicallyneedtospecifythesearchexpressionforeverysinglerow.Fortunately,therenderattributecantakeanEL(ExpressionLanguage)expressionandtheELstreamAPIcanbeusedtoconcatenateabunchofstringsinthe:form:table:[i]:columnformatdependingontheamountofitemsinthetable.
<h:commandButtonvalue="Updatesecondcolumn">
<f:ajaxrender="#{bean.items.stream()
.map(i->':form:list:'+=bean.items.indexOf(i)+=
':column2')
.reduce((l,r)->(l+=r)).get()}">
</f:ajax>
</h:commandButton>
Admittedly,thisisnotthemostelegantapproach.You’dbetterdelegatetoacustomfunctioninanapplication-scopedbean.Itcouldlooksomethinglikethefollowing:<h:commandButtonvalue="Updatesecondcolumn">
<f:ajax
render="#{ajax.columnIds(bean.items,
':form:table::column2')}"
</f:ajax>
</h:commandButton>
Wherebythe#{ajax}application-scopedbeanlookssomethinglikethefollowing:@Named@ApplicationScopedpublicclassAjax{
publicStringcolumnIds(List<?>list,String
idTemplate){
returnIntStream.range(0,list.size()).boxed()
.map(i->idTemplate.replace("::",":"+i+
":"))
.collect(Collectors.joining(""));
}
}
That’salreadysomethingbetter,butstillboilerplate-ish.Ifnecessary,youcanalsoprogrammaticallyaddAjaxrenderIDsfromabackingbeanon.YoucanusethePartialViewContext#getRenderIds() forthis.Thereturnedcollectionis,namely,mutableandonlyconsultedduringtherenderresponsephase(sixthphase).YoualsoneedtospecifyanabsolutehierarchicalIDhere,butwithonlyoneimportantdifference:itcannotstartwiththenamingcontainerseparatorcharacter.Inotherwords,“:form:table:0:column2”isn’tgoingtowork;youneedtospecify“form:table:0:column2”instead.It’salwaysresolvedrelativetoUIViewRoot.
FacesContextcontext=FacesContext.getCurrentInstance();
PartialViewContextajaxContext=
context.getPartialViewContext();
ajaxContext.getRenderIds().add("form:table:0:column2");
Asatip,incaseyou’rehavingahardtimefiguringouttheabsolutehierarchicalIDand/ormemorizingwhichcomponentsexactlyarenamingcontainers,thenyoucanalwayslookinthegeneratedHTMLoutput.OpentheJSFpageinyourfavoritewebbrowser,doaViewPageSource,locatetheHTMLelementrepresentationoftheJSFcomponentofinterest,takethevalueofitsIDattribute,and
6
finallyprefixitwiththenamingcontainerseparatorcharacter.Also,incaseyouencounteranautogeneratedIDprefixed
withj_id,thenyouabsolutelyneedtogivetheassociatedJSFcomponentafixedID;otherwiseitsvaluewouldbeoffwhenthecomponent’spositioninthecomponenttreeissubjecttobechangedbecauseof,forexample,aconditionallyincludedcomponentsomewherebeforethepositionofthecomponentofinterest.(Seealsothesection“Text-BasedInputComponents”inChapter4.)LikerelativelocalIDs,absolutehierarchicalIDscanbeprogrammaticallyresolvedusingthealgorithmasdescribedintheUIComponent#findComponent()API. ThefollowingexampledemonstrateshowtogetholdoftheUIDatacomponentrepresenting<h:formid="form"><h:dataTableid="table">.
UIViewRootroot=
FacesContext.getCurrentInstance().getViewRoot();
UIDatatable=(UIData)root.findComponent("form:table");
StandardSearchKeywordsJSFprovidesasetofmoreabstractsearchexpressions,knownas“searchkeywords.”Theyallstartwiththe“@”character.TheycanbeusedtosubstituteafixedcomponentIDinthesearchexpression.Table12-1providesanoverviewofthem.
Table12-1 StandardSearchKeywordsProvidedbyJSF
Keyword
Resolvesto
Sinc
7
e
@this
UIComponent#getCurrentComponent()
2.0
@form
UIComponent#getNamingContainer()untilanUIFormisencountered
2.0
@all
Everything
2.0
@none
Nothing
2.0
@parent
UIComponent#getParent()
2.3
@child(index)
UIComponent#getChildren()atgivenindex
2.3
@next
UIComponent#getParent()andthenUIComponent#getChildren()atnextindex
2.3
@previous
UIComponent#getParent()andthenUIComponent#getChildren()atpreviousindex
2.3
@namingcontainer
UIComponent#getNamingContainer()
2.3
@composite
UIComponent#getCompositeComponentParent()
2.3
@id(id)
UIComponent#findComponent()withgivenID
.
2.3
@root
FacesContext#getViewRoot()
2.3
InastandardJSFcomponentset,allsearchkeywords,includingcustomones,canbeusedinthefollowingcomponentattributes:
<f:ajaxexecute>—Specifiescomponentswhichmustbeprocessedduringtheapplyrequestvalues,processvalidations,updatemodelvalues,andinvokeapplicationphases(second,third,fourth,andfifthphases)oftheAjaxpostbackrequest.Defaultsto@this.
<f:ajaxrender>—Specifiescomponentswhichmustbeprocessedduringtherenderresponsephase(sixthphase)oftheAjaxpostbackrequest.Defaultsto@none.
<h:outputLabelfor>—SpecifiesthetargetcomponentofthegeneratedHTML<label>element.Defaultsto@none.
<h:messagefor>—Specifiesthetargetcomponentforwhichthefirstfacesmessagemustberendered.Defaultsto@none.
<h:messagesfor>—Specifiesthetargetcomponentforwhichallfacesmessagesmustberendered.Defaultsto@none.
Notethatusingsearchkeywordsis,fortheforattributeof<h:outputLabel>,<h:message>,and<h:messages>,hasonlybeenpossiblesinceJSF2.3.Inolderversions,onlyrelativeandabsoluteIDsweresupported.
Themostcommonlyusedsearchkeywordisundoubtedly@form.Youhavebasicallynootherchoicewhenusing<f:ajax>inaUICommandcomponentwhichissupposedtoprocesstheentireform.
<h:form>
...
<h:commandButtonvalue="Submit"...>
<f:ajaxexecute="@form"/>
</h:commandButton>
</h:form>
AlsonewsinceJSF2.3isthatsearchkeywordscanbechainedwithregularcomponentIDs.Followingisanexamplewhichexpandsontheexamplegivenbeforeinthesection“AbsoluteHierarchicalIDs.”
<h:form>
<h:outputLabelid="l_time"for="time:hour"value="Time"
/>
<t:inputLocalTimeid="time"value="#{schedule.time}"
required="true">
<f:ajaxrender="@form:m_time"/>
<h:messageid="m_time"for="time"/>
</h:form>
Andfollowingisanexamplethatupdatestheentiretablewhenarowisdeleted.
<h:form>
<h:dataTablevalue="#{products.list}"var="product">
...
<h:column>
<h:commandButtonid="delete"value="Delete"
action="#{products.delete(product)}">
<f:ajaxrender="@namingcontainer"/>
</h:commandButton>
</h:column>
</h:dataTable>
</h:form>
NoteparticularlythatitthusreferencestheclosestcomponentimplementingtheNamingContainerinterface,which,inthiscontext,is<h:dataTable>andthusnot<h:form>.
Astoprogrammaticresolution,searchexpressionswithkeywordscannotbeprogrammaticallyresolvedusingUIComponent#findComponent().Forthat,youneedSearchExpressionHandler#resolveComponent()
orresolveComponents() instead.SearchExpressionHandlerisinturnavailablebyApplication#getSearchExpressionHandler().YoualsoneedtocreateSearchExpressionContextbeforehandwhichwrapsthecomponenttostartsearchingfrom.
FacesContextcontext=FacesContext.getCurrentInstance();
UIComponentbase=context.getViewRoot();//Canbeany
component.
Stringexpression="@namingcontainer";
SearchExpressionContextsearchContext=
SearchExpressionContext
.createSearchExpressionContext(context,base);
SearchExpressionHandlersearchHandler=
context.getApplication()
.getSearchExpressionHandler();
handler.resolveComponent(searchContext,expression,(ctx,
found)->{
System.out.println(found);
});
8
9
Frankly,it’squiteverbose,butit’sJSFAPI’sown.Fortunately,utilitylibrariessuchasOmniFacesexist.
CustomSearchKeywordsTheComponentSearchExpressionFrameworkintroducedinJSF2.3alsocomeswithanAPIwhichallowsustocreatecustomsearchkeywords.Imaginethatyouhaveaformwithmultiple<h:message>components,andthatyou’dliketore-renderthemallwhensubmittingtheform.Thenyou’dbetemptedalsotouse@formintherenderattributeof<f:ajax>.
<h:form>
<h:outputLabelfor="input1".../>
<h:inputTextid="input1".../>
<h:messagefor="input1"/>
<h:outputLabelfor="input2".../>
<h:inputTextid="input2".../>
<h:messagefor="input2"/>
<h:outputLabelfor="input3".../>
<h:inputTextid="input3".../>
<h:messagefor="input3"/>
<h:commandButtonvalue="Submit"...>
<f:ajaxexecute="@form"render="@form"/>
</h:commandButton>
</h:form>
Butthisisnotterriblyefficient.Infact,italsounnecessarilyre-rendersalllabelandinputcomponentsandanyotherstaticcontentinsidetheverysameformwhichdoesn’tatallchange
duringtheAjaxpostbackrequest.Thisisawasteofresources.Ideally,weshouldhaveasearchkeywordlike“@messages”whichbasicallyreferencesallmessagecomponentswithinthesameform.
<h:form>
<h:outputLabelfor="input1".../>
<h:inputTextid="input1".../>
<h:messageid="m_input1"for="input1"/>
<h:outputLabelfor="input2".../>
<h:inputTextid="input2".../>
<h:messageid="m_input2"for="input2"/>
<h:outputLabelfor="input3".../>
<h:inputTextid="input3".../>
<h:messageid="m_input3"for="input3"/>
<h:commandButtonvalue="Submit"...>
<f:ajaxexecute="@form"render="@messages"/>
</h:commandButton>
</h:form>
Notethateach<h:message>componenthasanexplicitIDset,becausewithoutanexplicitID,theywillbydefaultrendernothingtotheHTMLoutput,andthentheJSFAjaxAPIJavaScriptwouldn’tbeabletofindtheminordertoupdateitscontentbasedontheAjaxresponse.
InordertogetJSFtorecognizethenewsearchkeyword@messages,firstextendthejavax.faces.component.search.SearchKeyword
Resolver asfollows:publicclassMessagesKeywordResolverextendsSearchKeywordResolver{
10
@Override
publicbooleanisResolverForKeyword
(SearchExpressionContextcontext,String
keyword)
{
return"messages".equals(keyword);
}
@Override
publicvoidresolve
(SearchKeywordContextcontext,UIComponentbase,
Stringkeyword)
{
UIComponentform=base.getNamingContainer();
while(!(forminstanceofUIForm)&&form!=
null){
form=form.getNamingContainer();
}
if(form!=null){
Set<String>messageClientIds=newHashSet<>
();
VisitContextvisitContext=
VisitContext.createVisitContext
(context.getSearchExpressionContext().ge
tFacesContext());
form.visitTree(visitContext,(visit,child)
->{
if(childinstanceofUIMessage){
messageClientIds.add(child.getClient
Id());
}
returnVisitResult.ACCEPT;
});
if(!messageClientIds.isEmpty()){
context.invokeContextCallback(new
UIMessage(){
@Override
publicString
getClientId(FacesContextcontext){
returnString.join("",
messageClientIds);
}
});
}
}
context.setKeywordResolved(true);
}
}
Itshouldbenotedthatthisapproachisalreadyslightlyhacky.Namely,theintentoftheSearchKeywordResolveristoresolveakeywordtoexactlyonecomponentwhoseclientIDwillthenbeusedtosubstitutethekeyword.ThiscomponentwillthenbepassedtoSearchKeywordContext#invokeContextCallback
().Intheaboveapproach,weinsteadcollecttheclientIDsofallUIMessagecomponentsfoundwithintheparentUIFormandthensupplyafakeUIMessagecomponentto
invokeContextCallback()whowillinturncallthegetClientId()ofthefakeUIMessagecomponentwhichactuallyreturnsthedesiredcollectionofclientIDs.
ItshouldalsobenotedthatUIComponent#visitTree() isbeingusedinsteadofrecursingoverUIComponent#getChildren()inordertocollecttheclientIDsofanyUIMessagecomponent.Namely,whenplainiteratingoverchildren,youmaysoonerorlatercomeacrossaniteratorcomponentsuchas<h:dataTable>or<ui:repeat>,andifithappenstohaveonlyoneUIMessagecomponentnested,thenyou’lleffectivelyendupwithonlyoneclientID,namely,theonewithouttheiterationindex.UIComponent#visitTree()doesn’tdothat;anyiteratorcomponentinthepathwillactuallyiterateoveritsmodelvalueandvisuallygivebackmultipleUIMessagecomponents,eachwiththecorrectclientIDwiththeiterationindexincluded.
Intheend,SearchKeywordContext#setKeywordResolved()
mustbecalledwithtrueinordertoinformthesearchcontextthatthekeywordhassuccessfullybeenresolved,evenifitactuallyresolvedtonothing.Itdoesn’tactuallydoanyharmifyouforgotthis,butifyoudon’tmarkthesearchkeywordresolvedthisway,thenthesearchcontextwillcontinueconsultingallothersearchkeywordresolvers,whichmightendupbeinglessefficient.
Finally,inordertogetthenewMessagesKeywordResolvertorun,registeritinfaces-config.xmlasfollows:<application><search-keyword-resolver>
11
com.example.project.MessagesKeywordResolver
</search-keyword-resolver>
</application>
Or,programmaticallyina@WebListenerasfollows:@WebListener
publicclassApplicationConfigimplements
ServletContextListener{
@Override
publicvoidcontextInitialized(ServletContextEvent
event){
FacesContext.getCurrentInstance().getApplication
()
.addSearchKeywordResolver(new
MessagesKeywordResolver());
}
}
NotethattheFacesContextmustbeavailableatthispointandhenceaServletContainerInitializerwon'tnecessarilywork,andthattheFacesServletmaynothaveservicedarequestyetandhenceregistrationinsomemanagedbeanwon'tnecessarilywork.
Footnotes1https://www.primefaces.org/search-expression-framework/
.
2https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/NamingContainer.html
.
3https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/UINamingContainer.ht
ml#getSeparatorChar-javax.faces.context.FacesContext-.
4https://stackoverflow.com/q/10726653/157882.
5https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/UIComponent.html#fin
dComponent-java.lang.String-.
6https://javaee.github.io/javaee-
spec/javadocs/javax/faces/context/PartialViewContext.htm
l#getRenderIds--.
7https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/UIComponent.html#fin
dComponent-java.lang.String-.
8https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/search/SearchExpress
ionHandler.html#resolveComponent-
javax.faces.component.search.SearchExpressionContext-
java.lang.String-javax.faces.component.ContextCallback-.
9https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/search/SearchExpress
ionContext.html.
10https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/search/SearchKeyword
Resolver.html.
11https://javaee.github.io/javaee-
spec/javadocs/javax/faces/component/UIComponent.html#vis
itTree-javax.faces.component.visit.VisitContext-
javax.faces.component.visit.VisitCallback-.
(1) (2)
©BaukeScholtz,ArjanTijms2018BaukeScholtzandArjanTijms,TheDefinitiveGuidetoJSFinJavaEE8,https://doi.org/10.1007/978-1-4842-3387-0_13
13.Security
BaukeScholtz andArjanTijms
Willemstad,Curaçao Amsterdam,Noord-Holland,TheNetherlands
Securityforwebapplicationsisabroadfieldandincludesanumberoftopicssuchasprotectingaccesstoresources,shieldingagainstinjectionattacksofvariouskinds,andpreventingusersfrombeingtrickedintodoingmaliciousactionsonbehalfofanattacker.
JSF(JavaServerFaces)supportsthesetopicsinvariousways,eitherbyprovidingnativesolutionsorbyintegratingwiththeJavaEEplatform’sfacilities.Foraccesstoresources,whichincludesbothauthentication(thecallerprovingitsidentity)andauthorization(thesystemdeterminingtowhichresourcesthecallerhasaccess)JSF(JavaServerFaces)integrateswiththeJavaEESecuritymachinery,whichinturnisdefinedby,amongothers,theServletspecandtheJavaEESecurityspec.
JavaEESecurity(JSR375)issupportedbyboththeWeb-andFullprofileinJavaEE8.Additionally,thereferenceimplementationSoteriaworksonJavaEE7servers,andbecauseit’sbuiltonJASPIC(JavaEEauthenticationSPI)
1 2
(JSR196),italsoworksonServletcontainersthatsupportJASPIC(suchasTomcatsince8.5andJettysince7.0).
JavaEESecurityOverviewandHistoryInJavaEE,securityisnotspecifiedinasinglespecificationbutisinfactspreadovermultiplespecificationsthatintegratewitheachotherinvariousways.Whilethisallowsfordifferentaspectsofsecuritytobeevolvedattheirownpace,andpracticallyspeakingevenallowssometobereplacedbynon-specimplementations,itdoesmuddythewatersofwhatspecisresponsibleforwhat.
Inthisintroductorysectionwe'llstartoffwithprovidingasomewhatbroadoverviewofwhatsomeofthepiecesareandhowtheyfittogether.We'llfocusonthewebaspectsofsecurity.SecurityisalsopresentinthingslikeEJB(EnterpriseJavaBeans)andJCA(JavaConnectorArchitecture)connectors,buttheseareoutsidethescopeofthisbook.
Historically,securityinJavaEEismostlybasedonthesecuritymodelintroducedbytheServletspec.Thatis,amodelwherethecoreelementsarean"authenticationmechanism"(FORM,BASIC,...),asetofsecurityconstraintsinweb.xmlwhereaspecificpattern(theURLpattern)iscombinedwithacollectionofroles(includingtheemptycollection),andafewwaystoprogrammaticallytestthecaller'sdetails,suchasHttpServletRequest.isUserInRole.
Whileeffective,manydetailswereleftoutintheearlydays.TheServletspecdidaskforimplementations(Servletcontainers)tobeextendiblewithrespecttotheauthenticationmechanismsbutdidnotspecifyhowexactlythisshouldbe
done.Likewise,theServletspecimplicitlyrequiresan"identitystore"(File,Database,LDAP,…)thatholdsthecallerdetailssuchascredentials,name,androlesbutleftalldetailsabouthowtoconfigurethesetotheServletcontainer.
TheServletspecdidnotdefineanywaytoaccessthesecurityconstraintsdefinedinweb.xmlinaprogrammaticway,ortoprogrammaticallyinfluencetheirexecution(e.g.,tomakecertainconstraintstimebased).Furthermore,theveryearlyversionsoftheServletspecdidnotspecifytheconstraintresolutioninthestrictestway,whichallowedforsomesmalldifferencesininterpretationbetweenvariousServletcontainers.
ThefirstadditionalspectoaddresstheseconcernswasJACC(JSR115),which,simplystated,dealswithauthorizationconcerns.JACCspecifieshowtheweb.xmlsecurityconstraintsshouldberepresentedincode,namely,asacollectionofPermissioninstances.JACCalsospecifieshowthesecanbeaccessed(queried)bycode,andfinallyallowsforcustomauthorizationmodulesthatcanreplaceoraugmentthelogicthatthecontainerexecutestodetermineitsaccessdecision.AperhapssomewhatdifficultthingtounderstandaboutJACCisthatit'snotsomuchsomethingthatcanbeimplementedonitsownandthenaddedtoaServletcontainer(aswecandowithJSF),butitstandardizesandmoreconciselyspecifieswhatallServletcontainersinternallyarealreadydoing.
Thesecondadditionalspectoaddresstheabove-mentionedconcernswasJASPIC(JSR196),whichdealswithauthenticationconcerns.JASPICspecifiesatwhatmomentstheServletcontainershouldcalltheauthenticationmechanism
anddefinesanexplicitinterfacetypefortheseauthenticationmechanisms,socustomauthenticationmechanismscanusethisinterfaceinsteadoftheServletcontainer'sproprietaryoneandthusbeportable.JASPICsaysafewthingsabouttheidentitystore,buttoolittletobereallyusableinpractice.LikeJACC,JASPICisn'tsomethingthatcanbeimplementedindependently,butitstandardizessomethingthatallServletcontainersarealreadydoing.
BothJACCandJASPICdefinelow-levelSPIs(serverproviderinterfaces)thataremainlyintendedtobeimplementedandusedbyvendorstoprovideextensionproducts.Theyarequitebareandfairlyabstract.Assuchtheyarenot(rich)APIs(applicationprogramminginterfaces)thataretargetedatapplicationdevelopers.HereiswheretheJavaEESecurityAPI(JSR375)comesin.JavaEESecurityoffersahigher-levelandeasier-to-register(justimplementtheinterface)andeasier-to-use(CDIbased)versionoftheJASPICauthenticationmodule.TheJavaEESecurityAPIalsofullydefinestheidentitystoreartifact,butperhapsmostimportantitprovidesanumberofconcreteimplementationsofallofthese,amongwhichisaFORM-basedauthenticationmechanismoptimizedforusewithJSFandseveralidentitystoressuchasaJDBC(JavaDatabaseConnectivify)storeandanLDAP(LightweightDirectoryAccessProtocol)store.
TheRI(referenceimplementation)oftheJavaEESecurityAPIiscalledSoteria.ThisimplementationisofcourseprovidedbytheJavaEE8RIGlassFishanditsderivativePayara.Soteria,likeMojarraandMyFaces,hasbeendesignedtorunindependentlyonbasicallyeveryServletcontainerprovidedthatitadherestotheJASPICspecs(whichbothTomcatandJettydoasmentionedinthechapter's
introduction).Itsdependencies(atthetimeofwriting,forSoteria1.0)areCDI1.2andExpressionLanguage3.0.ThesetwodependenciesareprovidedbyallJavaEE7implementations.ServletcontainerstypicallyprovideEL(ExpressionLanguage)supportwhileCDIcanbeaddedseparately,forinstance,usingtheRIWeld.ForafewoptionalfeaturesSoteriatakesadvantageofServletcontainersthatadheretotheJACCspec.
ProtectAccesstoResourcesInJSFthemainresourcestoprotectareviews,whichareaccessedviaURLpatterns.It'sthereforetheseURLpatternsweneedtodefinesecurityconstraints.Theseconstraintsareprimarilydefinedinweb.xml.Infact,asofJavaEE8,web.xmlisprettymuchtheonlyviableplacetodefinesecurityconstraintsforJSFviews.
JavaEEhasthreekindsofsecurityconstraints:Excluded
Unchecked
Byrole
EXCLUDED“Excluded,”aka“denyall,”meansthatnoexternalcallerwillbegrantedaccesstotheresourcescoveredbythisconstraint.Onemightaskwhatthepurposeofsuchaconstraintis.Iftheresourcescanneverbeaccessed,whyarethoseresourcesthereinthefirstplace?
Theansweristwofold.Forone,asingleapplicationmaybeconfiguredfordifferentpurposes.Anexcludedsecurity
constraintisaneasywaythentoquicklydisableanumberofresourcesthatarenotapplicabletoacertainconfigurationoftheapplication.Amoreimportantusecaseistobeabletomakeadistinctionbetweenexternaluseandinternaluseofaresource.Thekeyinsighthereisthatsecurityconstraintsareonlyappliedtoexternalrequestsforthatresource,i.e.,toacallerrequestinghttps://example.com/resources/template.xht
ml,butnottointernalrequestssuchasincludes,forwards,andanyofthemethodstoloadaresourcefromtheclasspathorfilesystem.
ThisisspecificallyimportantforJSFbecauseofasomewhatunfortunatedesignchoiceregardingcompositecomponents.CompositecomponentsarecomponentsthatareimplementedviaaFaceletinsteadofaJavaclass.Byconvention,theyhavetobeplacedinadirectoryinsideadirectorynamed/resourcesthatresidesinthewebroot.Forinstance,/resources/bar/foo.xhtml.Thiswillmakeacompositecomponent“foo”availableinthenamespacehttp://xmlns.jcp.org/jsf/composite/bar.
Componentsareofcoursenotviewsandthecallershouldnotbeabletorequestthosedirectly.Unfortunately,/resourcesisnotinanywayaspecialdirectorytoJavaEE.JSFassignsaspecialmeaningtoitbyconvention,buttotheServletcontainerit’sadirectorylikeanyother.Thisspecificallymeansthere’snoprotectionappliedtoitandanycallercandirectlyrequestresourcesfromit.Inotherwords,thisdirectoryis“unchecked,”aka“worldreadable.”Evenwithan*.xhtmlmapping,thisnotonlyallowstheusertoguess
whichcomponentswehavebutletstheuserattempttoexecutethoseaswell.Clearlythisisnotwhatwewant.Therearetwosolutionsforthis:
ConfigureanotherdirectorytobetheJSFresourcesdirectory
Addthementionedsecurityconstrainttoweb.xml
Viathejavax.faces.WEBAPP_RESOURCES_DIRECTORY
contextparameteranotherdirectorycanbeconfiguredtobetheJSFresourcesdirectoryinsteadof/resources.Forexample,<context-param><param-
name>javax.faces.WEBAPP_RESOURCES_DIRECTORY</param-name>
<param-value>WEB-INF/resources</param-value>
</context-param>
Notethatthepathisrelativetothewebrootandmustnotbeginwitha“/.”
Whilethisisagooddefaultforourownapplications,itstilldoesn’ttotallyprotectus.Namely,third-partyjarscanstillprovidetheirresourcesvia/resourcesandaren’taffectedbythatcontextparameter.Forthatreason,theaforementioned“Excluded”constraintisneeded.Itlooksasfollows:<security-constraint>
<web-resource-collection>
<web-resource-name>Theresourcesfolder<web-
resource-name>
<url-pattern>/resources/*</url-pattern>
</web-resource-collection>
</security-constraint>
Ascanbeseen,defininganexcludedconstraintboilsdownto
definingtheURLpatternthatwewishtoconstrain,withoutdefininganyspecificconstraints.
UNCHECKED“Unchecked,”aka“permitall,”“public,”and“worldreadable,”meansthatallcallers,independentofwhetherornottheyareauthenticated,haveaccesstotheresourcescoveredbythe“constraint.”Internallyanexplicitconstraintmayexistforthis,butinweb.xmlthis“constraint”isdefinedsimplybynotdefininganyconstraintatallforaURLpattern.Inotherwords,everyURLthat’snotcoveredexplicitlybyanyotherpatternis“unchecked.”
BYROLE“Byrole”meansaso-calledroleisassociatedwithaURLpattern,andtheauthenticatedcallermusthavethatroleinordertoaccesstheresource.Aroleitselfisfrequentlyseenasaconceptwithstrictsemantics,butit’sessentiallylittlemorethanjustanopaquestringthatneedstomatchfromthesetofsuchstringsassociatedwithanauthenticatedcaller,andthesetofstringsassociatedwithaURLpattern.Thecontentofthatstringiscompletelyuptotheapplication,meaningitcouldbea“typeofcaller,”like“admin”,“user”,etc.,butalsosomethingfine-grainedas“may_add_item”,oreventokenssuchas“AYUDE-OPWR-BM1OP”.
Thefollowinggivesanexample:
<security-constraint>
<web-resource-collection>
<web-resource-name>Userpages</web-resource-name>
<url-pattern>user*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>VIEW_USER_PAGES</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Adminpages</web-resource-name>
<url-pattern>admin*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>VIEW_ADMIN_PAGES</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>VIEW_USER_PAGES</role-name>
</security-role>
<security-role>
<role-name>VIEW_ADMIN_PAGES</role-name>
</security-role>
Intheabovefragmentwedefinetwosecurityconstraints—onefortheuser*pattern,forwhichthecallerneedstohavetherole“VIEW_USER_PAGES”,andonefortheadmin*pattern,forwhichthecallerneedstherole“VIEW_ADMIN_PAGES”.Withtheaboveconstraintsinplace,acalleraccessing,say,useraccount.xhtmlhastobeauthenticatedandhastohavethementionedrole.
Anauth-constraintcancontainmultipleroles,forwhichORsemanticsareapplied.Thismeanstheauthenticatedcalleronlyneedstohaveoneoftherolesinthatconstraintinordertobegrantedaccess.ConstraintscanadditionallyberestrictedtoaspecificHTTPmethod(suchasGETorPOST).Thisdoes
comewithacaveat,sincebydefaultallmethodsthatarenotspecifiedwillbeunchecked(public).Thiscanbecounteredbyusingthetop-level<deny-uncovered-http-methods/>tag.Thefollowinggivesanexampleofthis:<deny-uncovered-http-methods/>
<security-constraint>
<web-resource-collection>
<web-resource-name>Userpages</web-resource-
name>
<url-pattern>user*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>VIEW_USER_PAGES</role-name>
</auth-constraint>
</security-constraint>
SettingtheAuthenticationMechanismAfterthesecurityconstraintshavebeendefined,weneedtosethowthecallerwillauthenticate.Theartifactthathandlestheinteractionwiththecaller(i.e.,asksthecallerforcredentialsinacertainway)iscalledan“authenticationmechanism.”JavaEEprovidesanumberoftheseoutofthebox.TheServletspecprovidesfour,namely,FORM,BASIC,DIGEST,andCERT,whileJavaEESecurityprovidesFORMandBASICaswell(withthedifferencethatthesecorrespondtoCDIbeans),butalsoavariantofFORMcalled“CustomFORM”.
FORMandCustomFORMarebothsuitableforinteractivewebapplications,suchastheoneswewouldbuildprimarilywithJSF.Contraryto,say,BASIC,theFORMauthenticationmechanismscallbackintotheapplicationandletitrendertheformthatasksthecallerforcredentials,hence,thenameofthesemechanisms.
Thedifferencebetweenthetwoismainlyinhowthemechanismrequirestheapplicationtocontinuetheso-calledauthenticationdialogafterithasrenderedtheform.InFORMthisisdonebylettingthecallerpostthefilled-outformtothevirtualURLj_security_check,whileinCustomFORMthisisdoneprogrammaticallyviaacalltotheinjectedSecurityContext.ThissmalldifferencemakesallthedifferenceforJSFthough.InJSF,aformviewbydefaultsubmitstothesameURLitwasrequestedfrom,sopostingbacktoasinglemechanismmandatedURLisnotatallnatural.Inadditiontothat,inJSFweoftenneedtohaveserver-sidecoderunningafterapostback;justthinkaboutconvertersandvalidatorsandtheabilitytoemitafacesmessage.Wecan’treallydoanyofthisifwehavetopostbacktothevirtualnon-facesj_security_checkURL,butit’squitedoablewhenwecancontinuetheauthenticationdialogprogrammaticallyfromthebackingbean’sactionmethod.
We’llfirstshowyouhowtoconfigureawebapplicationtousetheCustomFORMauthenticationmechanism.Lateron,we’llexplainhowtoactuallyuseJSFtofulfilltherequirementsthismechanismimposesontheapplication.
AlloftheauthenticationmechanismsprovidedbyJavaEESecurityareinstalledandconfiguredviaitsown
AuthenticationMechanismDefinitionannotation.Thisannotationtellsthecontainerwhichtypeofauthenticationmechanismtoinstall,andwithwhichconfiguration.Theannotationcanbeplacedonprettymucheveryclassontheclasspath.Logicallyitfitsinquitewellwiththe@FacesConfigannotation.Thefollowingshowsanexample:
@CustomFormAuthenticationMechanismDefinition(
loginToContinue=@LoginToContinue(
loginPage="/login.xhtml",
errorPage=""
)
)
@FacesConfig@ApplicationScoped
publicclassApplicationConfig{
//...
}
AperhapssomewhatunfortunaterequirementhereisthattheerrorPageattributehastobespecifiedandsettotheemptystring.Thisisneededsincethe@LoginToContinueelementhasadefaultvalueforanactualerrorpage.InJSFwerarelyuseanexpliciterrorpagetodisplayerrorsbutinsteadredisplaytheoriginalpagewiththeerrormessagesrenderedonitviafacesmessages.
DonotethatthecurrentversionofJavaEESecurityonlyallowsoneauthenticationmechanismtobeactiveatthesametime.Technicallywhathappenswhenthecontainerencountersthe@CustomFormAuthenticationMechanismDefiniti
onannotationisthatitaddsanenabledCDIbeantothesystemoftypeHttpAuthenticationMechanism.This
isimportanttoknow,sinceitsbeingaregularCDIbeanmeanswecaninjectit,decorateit,interceptit,andbasicallydoeverythingwithitthatwecannormallydowithCDIbeans.
TheloginToContinueattributeisusedtoconfiguretheviewthatthecontainerforwardstowheneverthecallertriestoaccessaprotectedview.Thisiscalled“containerinitiatedauthentication”;thecontainerstartstheauthenticationdialogasopposedtotheapplication.
Notethatthedefaultistoforwardtotheloginpage,meaningthatifthecallertriestoaccesshttps://example.com/foo.xhtml,and/foo.xhtmlisprotected,thecallerwillstillsee/foo.xhtmlintheaddressbarandnot/login.xhtml.However,thepostbackisto/login.xhtml,soafterenteringcredentialsthecallerwouldgettoseethisintheaddressbar.AlternativelywecanconfiguretheloginToContinueattributetousearedirectinstead.
@CustomFormAuthenticationMechanismDefinition(
loginToContinue=@LoginToContinue(
loginPage="/login.xhtml",
useForwardToLogin=false,
errorPage=""
)
)
@FacesConfig@ApplicationScoped
publicclassApplicationConfig{
//...
}
SettingtheIdentityStoreAfterhavingdeclaredthesecurityconstraintsandsettingthe
mechanismthatwe’dliketousetoauthenticate,there’sonefinalpieceofthepuzzleremaining:settingtheartifactthatcontainsthecaller’sdata,suchascredentials,name,androles.InJavaEESecuritythisartifactiscalledanidentitystore.
JavaEEprovidestwooftheseoutofthebox;onetoconnecttoadatabaseandonetoconnecttoLDAP(LightweightDirectoryAccessProtocol).TheJavaEESecurityRI(Soteria)additionallyshipswithanembeddedidentitystore.Mostapplicationserversprovideadditionalonesoftheirown,whichareoftenconfiguredoutsidetheapplication(e.g.,viaanadminconsole,CLI,orXMLconfigurationfilethat’sstoredinsidetheserver).
SettingandconfiguringtheJavaEESecurity-providedidentitystoresandtheoneprovidedbytheRIhappensinasimilarfashionastheauthenticationmechanisms:viaanIdentityStoreAnnotation.Justliketheauthenticationmechanismversion,thiswillcausethecontainertoaddanenabledCDIbeantothesystem,thistimeoneimplementingtheIdentityStoreinterface.
Thefollowingshowsanexampletogetherwithourearlierdefinitionoftheauthenticationmechanism:
@CustomFormAuthenticationMechanismDefinition(
loginToContinue=@LoginToContinue(
loginPage="/login.xhtml",
useForwardToLogin=false,
errorPage=""
)
)
@EmbeddedIdentityStoreDefinition({
@Credentials(
callerName="[email protected]",
password="secret1",
groups={"VIEW_USER_PAGES","VIEW_ADMIN_PAGES"}
),
@Credentials(
callerName="[email protected]",
password="secret2",
groups={"VIEW_USER_PAGES"})
)
})
@FacesConfig@ApplicationScoped
publicclassApplicationConfig{
//...
}
Theabovecausesanembedded(in-memory)storetobecreated,withtwocallers(users),thefirstonebeinginthegroups“VIEW_USER_PAGES”and“VIEW_ADMIN_PAGES”andthesecondoneonlyinthegroup“VIEW_USER_PAGES”.Theauthenticationmechanismwillusethisidentitystoretovalidatethatthecredentials(callernameandpassword)matchand,iftheydo,togetthecorrectgroupsfromtheidentitystore.
Theobservantreadermaynoticethattheterminologyhaschanged.Beforeweweretalkingabout“roles,”whileallofasuddenthishaschangedto“groups.”Isthisamistake?Well,notreally.Groupsandrolesaresubtlydifferent.Botharejustopaquestringstothecontainer,butgroupscanbeoptionallymappedtoroles.Wewon’telaborateonthisprocessfurtherhere,butsufficeittosaythatbydefault,JavaEEmandatesaso-called1:1group-to-rolemapping,whichsimplymeansgroupsandrolesarethesame.
Tobetterunderstandidentitystoreswe’llgivetwomoreexampleshere.Forthefirstexamplewe’lllookatoneoftheidentitystoresthat’sprovidedbytheJavaEESecurityAPI—
thedatabaseidentitystore.Thisstoreisactivatedandconfiguredusingthe@DatabaseIdentityStoreDefinitionannotation.Thethreemostimportantattributestoconfigurearethedatasource(whichrepresentstheSQLdatabase),theSQLquerytoobtainthe(hashed!)passwordgivenacallername,andtheSQLquerytoidentifywhichgroupsacallerisingiventhecallername.Thefollowinggivesanexample:@CustomFormAuthenticationMechanismDefinition(
loginToContinue=@LoginToContinue(
loginPage="/login.xhtml",
errorPage=""
)
)
@DatabaseIdentityStoreDefinition(
dataSourceLookup="java:app/MyDataSource",
callerQuery="SELECTpasswordFROMcallerWHERE
name=?",
groupsQuery="SELECTnameFROMgroupsWHERE
caller_name=?"
)
@DataSourceDefinition(
name="java:app/MyDataSource",
className="org.h2.jdbcx.JdbcDataSource",
url="jdbc:h2:~/test;DB_CLOSE_ON_EXIT=FALSE"
)
@FacesConfig@ApplicationScoped
publicclassApplicationConfig{
//...
}
Inthisexample,adatasourceisdefinedfortheH2databaseusingtheorg.h2.jdbcx.JdbcDataSourcedriver.NaturallythiscanbedoneinasimilarwayforanyotherdatabasethathasaJDBCdriver.Alternatively,thedatasourcecanbedefinedexternallytotheapplication.Thecallerquerythatweusedis“selectpasswordfromcallerwherename=?”,whichmeansweassumeatablewithatleasttwocolumns—oneholdingthecallername,andtheotherthehashedpassword.Suchtablecouldbecreatedby,forexample,thefollowingSQLstatement:CREATETABLEcaller(nameVARCHAR(32)PRIMARYKEY,
passwordVARCHAR(255)
)
Thequerythatweusedforthegroupsis“selectnamefromgroupswherecaller_name=?”,whichassumesatablewithatleasttwocolumns—thecallername,andthegroupname,withonerowforeachgroupthecallerisin.Suchatablecouldbecreatedby,forexample,thefollowingSQLstatement:
CREATETABLEcaller_groups(
caller_nameVARCHAR(32),
nameVARCHAR(32)
)
Whenpopulatingthecallertable,itmustbenotedthatadefaulthashalgorithmisassumedforthepasswordcolumn,namely,PBKDF2WithHmacSHA256.Thisalgorithmcan(should)becustomizedbysettingthenumberofiterations,thekeysize,andthesaltsize.
Insteadofusinganidentitystorethat'sprovidedbythe
JavaEESecurityAPIwealsohavetheoptionofprovidingourowncustomone.Acommonusecaseforthatisusingtheapplication'sownservicestoloadtheapplication-specificuserdata.
Thefollowingshowsanexampleofsuchanidentitystore:
@ApplicationScoped
publicclassUserServiceIdentityStoreimplements
IdentityStore{
@Inject
privateUserServiceuserService;
@Override
publicCredentialValidationResultvalidate(Credential
credential){
UsernamePasswordCredentiallogin=
(UserNamePasswordCredential)credential;
Stringemail=login.getCaller();
Stringpassword=login.getPasswordAsString();
Optional<User>optionalUser=
userService.findByEmailAndPassword(email,
password);
if(optionalUser.isPresent()){
Useruser=optionalUser.get();
returnnewCredentialValidationResult(
user.getEmail(),
user.getRolesAsStrings()
);
}
else{
returnCredentialValidationResult.INVALID_RESULT;
}
}
}
Thereisnospecificregistrationneeded;theabovegivenclasssimplyneedstobepresentintheapplication(beonitsclasspath).WithsuchcustomidentitystorepresenttheApplicationConfigclassthereforedoesn’tneedanyconfigfortheidentitystore.
@CustomFormAuthenticationMechanismDefinition(
loginToContinue=@LoginToContinue(
loginPage="/login.xhtml",
errorPage=""
)
)
@FacesConfig@ApplicationScoped
publicclassApplicationConfig{
//...
}
TheUserServiceIdentityStoreasgivenabovedelegatesmostoftheworktoaUserService,whichwouldberesponsibleforhandlingUserentitiesintheapplication.Fullydiscussingsuchserviceisoutsidethescopeofthisbook,butwecanimagineitcoulduse,forexample,JPAtopersistandloadUserentities.OurcustomidentitystoreusestheservicetotrytofindaUserbasedontheusernameandthepasswordthat'sbeingpassedinviathecredentials.IfaUserinstanceisreturned,itmeansthenamereferredtoanexistinguser,andthepasswordwasthecorrectone.InthatcasetheidentitystoreinturnreturnsCredentialValidationResultwhichdoestwothings:itindicatesthatauthenticationwassuccessful,anditprovidesthecontainerwiththedatathatwilleventuallybeusedtosettheauthenticatedidentityforthecurrentrequest.Iftheservicecouldn’tfindtheuser,theneitherthenameorthepassword
waswrong.InthatcasethestorereturnsINVALID_RESULTtoindicatethatauthenticationwasnotsuccessful.
ProvidingOurCustomJSFCodeInthesectionsabove,wefirstdefinedoursecurityconstraints(whichviewsareprotected),thenwesetuptheauthenticationmechanism(howdoourcallersinteractwithourapplicationinordertoauthenticate),andfinallywesetuptheidentitystore(wherethecallerdataresides).
It’snowtimetoplugourowncustomcodeintotheauthenticationprocess.Thisprimarilyhappensbyprovidingtheviewthattheauthenticationmechanismdirectstowhenitneedstocollectthecaller’scredentials(e-mailandpasswordinthiscase).
Theloginpagecanbekeptrelativesimple—astandardformpage,withtwoinputsboundtoourbackingbean,andabuttontosubmit.
<!DOCTYPEhtml>
<htmllang="en"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
>
<h:head>
<title>LogIn</title>
</h:head>
<h:body>
<h1>LogIn</h1>
<h:form>
<h:outputLabelfor="email"value="Email"/>
<h:inputTextid="email"value="#{login.email}"/>
<br/>
<h:outputLabelfor="password"value="Password"/>
<h:inputSecretid="password"value="#
{login.password}"/>
<br/>
<h:commandButtonvalue="Login"action="#
{login.submit}"/>
<h:messages/>
</h:form>
</h:body>
</html>
ThepageaboveisatotallynormalJSFview.ThisspecificallymeansthatcontrarytoclassicalHTML<formmethod="post"action="j_security_check">ofFORMauthenticationtheauthenticationmechanismdoesnotmonitorthepostbackURLinanywayandthereforetherearen’tanyconstraintsbeingplacedontheinputelementsthatcollectthecredentials.
Instead,theJSFbackingbeanhastocollectthesecredentialsandthenprogrammaticallypassthesealongandsignaltheauthenticationmechanismtocontinuethedialog.Beforepassingthecredentialsalong,JSFisfreetodoitsownvalidationandengageinitsowndialogwiththecallerwithouttheauthenticationmechanismhavingtobeinvolvedwiththis.Notethatthecommandbuttonexplicitlydoesn’tuseAjaxtosubmit.Youcandoso,butthentheaveragewebbrowserwon’tsuggesttheendusertorememberthelogincredentialsonitsbehalf.
Thefollowingshowsafullexampleofthebackingbeanhandlingthelogincall.We’lldiscussthepartsindividuallybelow.
@Named@RequestScoped
publicclassLogin{
@NotNull
privateStringemail;
@NotNull
@Size(min=8,message="Passwordmustbeatleast8
characters")
privateStringpassword;
@Inject
privateSecurityContextsecurityContext;
@Inject
privateExternalContextexternalContext;
@Inject
privateFacesContextfacesContext;
publicvoidsubmit(){
switch(continueAuthentication()){
caseSEND_CONTINUE:
facesContext.responseComplete();
break;
caseSEND_FAILURE:
facesContext.addMessage(null,new
FacesMessage(
FacesMessage.SEVERITY_ERROR,"Login
failed",null));
break;
caseSUCCESS:
facesContext.addMessage(null,new
FacesMessage(
FacesMessage.SEVERITY_INFO,"Login
succeed",null));
break;
caseNOT_DONE:
//Doesn’thappenhere
}
}
privateAuthenticationStatuscontinueAuthentication(){
returnsecurityContext.authenticate(
(HttpServletRequest)
externalContext.getRequest(),
(HttpServletResponse)
externalContext.getResponse(),
AuthenticationParameters.withParams().credential(
newUsernamePasswordCredential(email,
password))
);
}
publicStringgetEmail(){
returnemail;
}
publicvoidsetEmail(Stringemail){
this.email=email;
}
publicStringgetPassword(){
returnpassword;
}
publicvoidsetPassword(Stringpassword){
this.password=password;
}
}
Westartthebackingbeanwithtwoinstancevariablescorrespondingwiththecredentialswecollect.
@NotNull
privateStringemail;
@NotNull
@Size(min=8,message="Passwordmustbeatleast8
characters")
privateStringpassword;
ThisclearlyshowstheadvantageovertheFORMauthenticationmechanisminthatwecaneasilypre-validatetheuser’sinputusingbeanvalidation.BecauseofJSF’sbuilt-inintegrationwithbeanvalidationastandardfacesmessagewillbemadeavailableforrenderingshouldtheinputnotpassvalidation.
Next,wedefinedinjectingthecontextualobjectsitneeds.Theseare@InjectprivateSecurityContextsecurityContext;
@Inject
privateExternalContextexternalContext;
@Inject
privateFacesContextfacesContext;
TheobservantreaderwillrecognizeExternalContextandFacesContextasbeingtwowell-knownnativeJSFclasses,withtheSecurityContextbeingtheoddoneout.ThisclassisfromJavaEESecurityandwe’llusethatheretocommunicatewiththeauthenticationmechanism.
ContinuingthedialoghappensinthecontinueAuthentication()methodasfollows:
privateAuthenticationStatuscontinueAuthentication(){
returnsecurityContext.authenticate(
(HttpServletRequest)externalContext.getRequest(),
(HttpServletResponse)externalContext.getResponse(),
AuthenticationParameters.withParams().credential(
newUsernamePasswordCredential(email,password))
);
}
ThecalltoSecurityContext#authenticate()willtriggertheauthenticationmechanismagain.Sincethatmechanismwillbeastatewhereitwaitsforcredentialstobepassed,itwillindeedlookforthecredentialswepassin,andusethosetocontinue.Aswe’lllatersee,wecanalsorequestthatanypotentiallyexistingstateisdiscardedandanewdialogisstarted.NotethatwehavetocasttherequestandresponseobjectstoHttpServletRequestandHttpServletResponse.Unfortunately,thisisneededsinceExternalContextabstractsoverServletandPortletrequestsandonlyreturnsObjectforthosetwo.
TheSecurityContext#authenticate()methodreturnsastatusthatindicatesinbroadlineswhattheauthenticationmechanismdid.TheactionmethodofourJSFbackingbeanhastohandlethefollowing:
switch(continueAuthentication()){
caseSEND_CONTINUE:
facesContext.responseComplete();
break;
caseSEND_FAILURE:
facesContext.addMessage(null,newFacesMessage(
FacesMessage.SEVERITY_ERROR,"Loginfailed",
null));
break;
caseSUCCESS:
facesContext.addMessage(null,newFacesMessage(
FacesMessage.SEVERITY_INFO,"Loginsucceed",
null));
break;
caseNOT_DONE:
//Doesn'thappenhere
}
Ascanbeseen,therearefourpossibleoutcomes.ThefirstoneisSEND_CONTINUE,whichbasically
means“authenticationinprogress.”Theauthenticationmechanismreturnedthatstatuswhenittookoverthedialogagain(e.g.,byrenderingitsownresponseor,morelikely,byredirectingthecallertoanewlocation).AJSFbackingbeanshouldmakesuretheJSFlifecycleisendedbycallingFacesContext#responseComplete()andfurthermorerefrainfrominteractingwiththeresponseitselfinanyway.
ThesecondoneisSEND_FAILURE,whichbasicallymeans“authenticationfailed.”Thisstatusisreturnedwhentheauthenticationmechanismwasn’tabletovalidatethecredentialsthatwereprovided.Inmostcasesthisiswhenthecallerprovidedthewrongcredentials.AJSFbackingbeancanrespondtothisbysettingafacesmessageandredisplaytheloginform.
ThethirdstatusisSUCCESS,whichmeans“authenticationsucceeded.”Thisisreturnedwhentheauthenticationmechanismsuccessfullyvalidatedthecredentialsprovided.It’sonlyafterthisstatusisreturnedthatHttpServletRequest#getUserPrincipal(),SecurityContext#getCallerPrincipal(),etc.,returnnon-nullvaluestoindicatethecurrentcallerisauthenticated.AJSFbackingbeancanrespondtothisin
variousways(e.g.,bysettingafacesmessageandcontinuingtorendertheview,orissuingaredirectofitself).
ThefourthandfinalstatusisNOT_DONE,whichisreturnedwhentheauthenticationmechanismchoosestonotauthenticateatall.Thishappens,forinstance,whentheauthenticationmechanismispre-emptivelycalledbutauthenticationappearednottobenecessary.Typically,aJSFbackingbeanwouldnotneedtotakeanyspecialactionhere.
Caller-InitiatedAuthenticationThepreviouscodediscussedthesituationwhereanunauthenticatedcallertriestoaccessaprotectedresource(URL/page)andtheauthenticationdialogisautomaticallystarted.Sincethisauthenticationdialogisstartedbythecontainer,wecallthis“container-initiatedauthentication.”
Anothercaseiswhereacallerexplicitlystartstheauthenticationdialog(e.g.,byclickingona“login”button).Becausethecallerstartsthisdialogwecallit“caller-initiatedauthentication.”
Incaseofcaller-initiatedauthentication,thecoreauthenticationmechanismiseffectivelydirectlyinvokedandtheplatform-providedlogin-to-continuefunctionalityisskipped.Thismeansthatifanauthenticationmechanismdependsonlogin-to-continuetoredirecttoaloginpageandafterauthenticationtoredirectbacktotheprotectedresource,neitherofthesetwoactionswillhappenwhentheapplicationprogrammaticallytriggersauthentication.
TheCustomFormAuthenticationMechanismthatwedefinedearlierviaanannotationisindeedamechanismthatusestheplatform’slogin-to-continueservice,sowe’ll
starttheauthenticationdialogbydirectingtothesameloginviewweusedbefore.Toindicatethisisanewlogin,anextrarequestparameterisprovided.Theviewfromwhichwestartlooksasfollows:<!DOCTYPEhtml><htmllang="en"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
>
<h:head>
<title>Welcome</title>
<h:head>
<h:body>
<c:iftest="#{notemptyrequest.userPrincipal}">
<p>Logged-inas#{request.userPrincipal}</p>
</c:if>
<c:iftest="#{emptyrequest.userPrincipal}">
<h:form>
<h:buttonvalue="Login"
outcome="/login">
<f:paramname="new"value="true"/>
</h:button>
</h:form>
</c:if>
</h:body>
</html>
Inthebackingbeanwe’llinjecttwoadditionalobjects:aninstancetoobtainandstorethementionedrequestparameterandareferencetotheFlash,whichwe’lluselater.
@Inject
privateFlashflash;
@Inject@ManagedProperty("#{param.new}")
privatebooleanisNew;
Themanagedbean’sscopeneedstobechangedto@ViewScoped,sowecanretainthevalueoftheisNewinstancevariableaftertheloginform’spostback.
AnimportantadditionistotheSecurityContext#authenticate()methodwherewe’llnowprovideanextraparameter:newAuthentication.Theauthenticationmechanismdoesnotstrictlyneedthisthough,andit’sjustsmartenoughtodistinguishbetweenaninitialnewauthenticationandcontinuinganauthenticationdialogthat’sinprogress.However,thingsgetmoredifficultwhenacallerisinthemidstofanauthenticationdialogandthennavigatesaway,onlytoexplicitlyclickaloginbuttonlater.Ifthestateassociatedwithsaiddialoghasn’texpiredatthatpoint,theauthenticationmechanismdoesn’tknowanewauthenticationisrequiredandwilllikelycontinuetheabortedbutstillvaliddialog.
Topreventthis,wecanforceanewauthenticationbysettingnewAuthenticationtotrue.Thiswilldiscardallexistingstates.ThemodifiedcontinueAuthentication()methodlooksasfollows:
privateAuthenticationStatuscontinueAuthentication(){
returnsecurityContext.authenticate(
(HttpServletRequest)externalContext.getRequest(),
(HttpServletResponse)externalContext.getResponse(),
AuthenticationParameters.withParams()
.newAuthentication(isNew).credential(
newUsernamePasswordCredential(email,
password))
);
}
Notethatthisversioncanbeusedforthecasewherewecontinuethedialogaswellastostartanewone.Whenwecontinuethedialog,isNewwillsimplybefalse,whichalsohappenstobethedefaultwhentheparameterisnotspecifiedatall.
WhenusingtheCustomFormAuthenticationMechanismweknowtherewillnotbeanyredirectsorotherwritestotheresponseafterweprovidethecredentialsincaller-initiatedauthentication,sothatgivesusaconvenientlocationtohandletheredirecttoalandingpageafterthecallerauthenticates:theSUCCESScase.
caseSUCCESS:
flash.setKeepMessages(true);
facesContext.addMessage(null,newFacesMessage(
FacesMessage.SEVERITY_INFO,"Loginsucceed",null));
externalContext.redirect(
externalContext.getRequestContextPath()+
"/index.xhtml");
break;
We’reredirectingthecallerheretotheindex.xhtmllandingpage.Notethatthisisalsotheviewwherethecallerinitiatedtheauthenticationdialog,butthat’sjustacoincidenceinthisexample.Ingeneral,thevieworevenURLwhereweredirectthecallertoiscompletelyfreefortheapplication
developertochoose.Typically,alandingpageofsomesortischosen,whichcouldbetheindexoftheapplicationoradashboardcorrespondingtothemainrolethecallerisin.Aswementionedabove,whenSUCCESSisreturnedthecallerisfullyauthenticated.Thismeanswecanquerythecaller’srolesandusetheseinourdecisionwheretoredirectto.
Outlook:afutureversionofJavaEESecuritymayintroduceahybridoptionwherecaller-initiatedauthenticationcanstillstartwiththesameredirectascontainer-initiatedauthenticationandallowsfortheredirect-backURLtobeprovidedbytheapplication.
RememberMeOnceacallerhasbeenauthenticatedforaJSF(web)application,wenaturallydon’twanttoaskthecallertore-authenticatewitheveryrequest.Topreventthis,theresultofasuccessfulauthenticationistypicallycoupledinsomewaytothecaller’sHTTPsession.Infact,theCustomFormAuthenticationMechanisminternallyusestheJavaEESecurity’sprovided“auto-apply-session”servicetodojustthis.Thisservicestoresthedataassociatedwithsaidsuccessfulauthentication(atleastthecallernameplusanygroupsthecallerisin).Althoughimplementationultimatelydependsonwherethisdataexactlylivesandwithwhatlifetime,inpracticeit’stypicallyinaspecialsectionoftheserver’smemoryassociatedwiththeHTTPsession.Thissectionisspecialinthewaythatit’stypicallysessionscoped,butthedataisnotaccessibleviaHttpSession#getAttribute().
Inordertonotexhausttheserver’smemory,anHTTP
sessionexpiresafteracertainamountoftime.Typicalexpirationtimesarebetween10minutesandanhour.Ifthecalleraccessestheapplicationafterthistime,authenticationisrequiredtobeperformedagain.
Oftenthoughevenre-authenticatingafteraperiodofinactivityaslongasanhourisundesirable.ButextendingtheHTTPsessiontoalongerperiodisundoablefortheaforementionedreasonsofserverresourceexhaustion.
Here’swhere“RememberMe”(remember-me)comesin.Remember-meisasomewhatplayfultermforaprocesswherethecaller’scredentialsareexchangedforatoken,andwherethistokenistypicallystoredinacookiethat’sdistinctfromtheHTTPsessioncookieandhasalongertimetolive.
Aremember-metokeneffectivelyfunctionsasanewcredentialforthecaller,withoutexposingthecaller’soriginalpassword.Aremember-metokencanbasicallybevendedmultipletimes,forinstance,onceperdeviceorIP(Internetprotocol)thatthecallerusestoconnecttotheapplication.Caremustbetakenthatwhilethetokendoesnotexposethecaller’soriginalcredentials,itstillfunctionsasthekeytoacaller’saccountandthereforeshouldbetreatedwiththesameprecautionsasonewouldapplytoanyothertypeofcredential.Specifically,cookiescontainingtheremember-metokenshouldbesentoverHTTPS/SSLonly,andapplicationsshouldnotstoretheactualtokenverbatimbutastronghashofit.
Astheprimaryreasonforhavingremember-meistonotexhaustservermemoryandtobelong-lived,theremember-metokenisalmostalwaysstoredinstablestorage(e.g.,adatabase).Assuch,alookupfromsuchstorageiscostlierthanalookupfromtheserver’smemory,andthiscouldseriouslyaffectperformancewhenrequiredtobedoneforevery
request,especiallywhenmanyAjaxrequestsarebeingdoneallthetime.
Forthisreason,remember-meisalmostalwaysusedincombinationwithsomekindofcache.ThemodularnatureoftheservicesthattheCustomFormAuthenticationMechanismusesmakesitpossibleforremember-metobeinsertedbetweentheauto-apply-sessionservicementionedaboveandtheactualauthenticationmechanism.Thatwayweeffectivelygetakindofmemoryhierarchy;theauthenticationdataisfirstattemptedtobefoundintheHTTPsessionstorage,ifit’snottheretheremember-meserviceisattempted,andifthatonedoesn’tcontainthedatathenfinallytheauthenticationmechanismistried.
Tomakeuseofremember-me,twothingshavetobedone.1. Activatingtheremember-meservicefortheinstalledauthentication
mechanism
2. Providingaspecialidentitystorethat’scapableofvendingandvalidatingtheremember-metoken
ACTIVATINGREMEMBERMESERVICETheremember-meserviceinJavaEESecurityisrepresentedbyanInterceptor.Viatheinterceptorbindingannotation@RememberMe[theremember-meservice]iseasilyappliedtoourowncustomauthenticationmechanism,oneforwhichwehavethesourcecode.Unfortunately,itisn’taseasywhenthesehavetobeappliedtoabeanforwhichwedon’thavethesourcecodeandinfactforwhichwedon’tevenknowtheexactimplementationtype.
AstheCustomFormAuthenticationMechanismthatwe’vebeenusingfortheexamplesaboveisindeedofthelattertype,there’sabitmoreworktodo.Essentially,weneedtoobtainareferenceoftheactualCustomFormAuthenticationMechanism
implementationthatthecontainermakesavailableandthenusetheCDI2.0InterceptionFactorytoprogrammaticallyaddthe@RememberMeannotation.Theresultisthentobereturnedfromanalternativeproducermethod.
ThisisdemonstratedinthefollowingcodeviathenewmethodproduceAuthenticationMechanism()intheApplicationConfigbeanwhichweshowedbefore:@CustomFormAuthenticationMechanismDefinition(
loginToContinue=@LoginToContinue(
loginPage="/login.xhtml",
useForwardToLogin=false,
errorPage=""
)
)
@FacesConfig@ApplicationScoped
@Alternative@Priority(500)
publicclassApplicationConfig{
@Produces
publicHttpAuthenticationMechanism
produceAuthenticationMechanism(
InterceptionFactory<HttpAuthenticationMechanismW
rapper>
interceptionFactory,BeanManagerbeanManager
){
@RememberMe
classRememberMeClass{};
interceptionFactory.configure().add(
RememberMeClass.class.getAnnotation(Remember
Me.class));
return
interceptionFactory.createInterceptedInstance(
newHttpAuthenticationMechanismWrapper(
(HttpAuthenticationMechanism)
beanManager
.getReference(beanManager
.resolve(beanManager
.getBeans((HttpAuthenticationMechanism.c
lass).stream()
.filter(b->b.getBeanClass()!=
ApplicationConfig.class)
.collect(Collectors.toSet())),
HttpAuthenticationMechanism.class,
beanManager.createCreationalContext(
null))));
}
}
TheApplicationConfigbeanisannotatedwiththe@Alternativeand@Priorityannotations.@Alternativeisusedheretoindicatethattheproducerisnotjustanyregularproducerbutonethatshouldbecalledinsteadofanyexistingproducerorbean.Thatis,thebeanweareproducinghereisanalternativeforthebeanwiththesametypethatwouldotherwisebeselectedbyCDIforinjection.
@Priorityisusedtoenable(activate)ouralternativeproducer.Withoutthisannotationtheproducerispresentbutnotenabled,meaningthatCDIwon’tcallit.Anotherwayofenablinganalternativeisusingbeans.xml.Thenumber500hereisusedtoselectbetweenvariousalternativesifmultiplealternativesareenabled.Inthatcasetheonewiththehighestnumberisselected.
Thecodeshownaboveusesthesomewhatwell-knownCDIpatternBeanManager#getBeans()/resolve()/getRefere
nce()toobtaintheCustomFormAuthenticationMechanismthatthecontainermakesavailable.ThispatternismoreverbosethanthesimplerCDI.current().select(...)variant,butitallowsustofilterouttheBean<T>thatrepresentstheproducermethod.GettingareferencefromthatBean<T>fromwithintheproducermethodwouldinvokethatsameproducermethodagain,andthuswouldcausearecursiveseriesofcallseventuallyleadingtoastackoverflow.Itgoeswithoutsayingthisisunwanted,hencethereasonwefilterthatparticularBean<T>out.
ThebeaninstancethatisreturnedfromtheBeanManager#getReference()isalmostcertainlyaproxy;CustomFormAuthenticationMechanismisspecifiedtobeapplicationscoped,anditimplicitlymakesuseofaninterceptor.Duetothetechnicaldifficultyofproxyinganexistingproxy(thinkofgeneratedproxiesoftenbeingfinalandproxycachesbeingused)CDI2.0imposesalimitationonwhattypesofobjectsitcancreateaninterceptedinstancefrom.Toworkaroundthislimitation,wehavelittlechoicebut
toinsertanextramanuallycreated“pass-throughwrapper”HttpAuthenticationMechanismWrapperinstanceasshowninthecodeabove.Thecodeofthiswrapperisasfollows:publicclassHttpAuthenticationMechanismWrapperimplementsHttpAuthenticationMechanism
{
privateHttpAuthenticationMechanismwrapped;
publicHttpAuthenticationMechanismWrapper(){
//
}
publicHttpAuthenticationMechanismWrapper
(HttpAuthenticationMechanism
httpAuthenticationMechanism)
{
this.wrapped=httpAuthenticationMechanism;
}
publicHttpAuthenticationMechanismgetWrapped(){
returnwrapped;
}
@Override
publicAuthenticationStatusvalidateRequest(
HttpServletRequestrequest,
HttpServletResponseresponse,
HttpMessageContextcontext)throws
AuthenticationException
{
returngetWrapped().validateRequest(request,
response,context);
}
@Override
publicAuthenticationStatussecureResponse(
HttpServletRequestrequest,
HttpServletResponseresponse,
HttpMessageContextcontext)throws
AuthenticationException
{
returngetWrapped().secureResponse(request,
response,context);
}
@Override
publicvoidcleanSubject(
HttpServletRequestrequest,
HttpServletResponseresponse,
HttpMessageContextcontext)
{
getWrapped().cleanSubject(request,response,
context);
}
}
Outlook:it’sexpectedthataconveniencemethodfortheabovetaskwillbeaddedtoafutureversionofJavaEESecurity,therebygreatlysimplifyingthistask.
LoggingOut
Regardlessofwhichmethodtologinhasbeenused,atsomepointthecallermaywishtoexplicitlylogout.Anormallogin(authentication)inJavaEEisalwaysprimarilyvalidperrequestonly,butvariousauthenticationmechanismsortheservicesthey’reusing(suchas@AutoApplySessionand@RememberMe)maykeepthestatebeyondasinglerequestandautomaticallyre-authenticatethecallerateverynextrequest.
Thisstatemaybekeptatvariousplaces:incookies,intheHTTPsession,inclientstorage,etc.Inordertologoutwehavetomakesureallthisstateiscleared.InJSFwecandothissimplybycallingtheHttpServletRequest#logout()method.ThiswillimmediatelyremovetheauthenticatedidentityfromthecurrentrequestandcallthecleanSubject()methodoftheauthenticationmechanism,whichinturnwillremoveanysessiondata,cookies,etc.,thatitused.
Thefollowinggivesanexample:
@Named@RequestScoped
publicclassLogout{
@Inject
privateHttpServletRequestrequest;
publicvoidsubmit()throwsServletException{
request.logout();
request.getSession().invalidate();
}
}
Notethatforafulllogoutit'stypicallygoodpracticetoinvalidatethesessionaswell.Thecallto
HttpServletRequest#logout()shouldonlyremovethesessionstateusedbytheauthenticationmechanism(ifany),whileafterafulllogoutweoftendon’twantanyothersessionstatelingeringaroundeither.Dependingontheapplicationdesignit'stypicaltoredirectthecallertothehomepageoftheapplicationafteralogoutaswell.
CustomPrincipalsThedefaultprincipalthatwecanobtainfromthesecuritycontextcontainsverylittleotherthanjustthenameor,moreexactly,thecallerprincipalname(alsoknownastheuserprincipalname).Thisistypicallyauniquenameandoften,butnotnecessarily,thenamethecallerusedtoauthenticatewith.
Inpractice,awebapplicationalmostalwaysneedsmoreinformationthanjustthisname,andaricherapplication-specificmodelobjectrepresentingtheuserisoftendesired.Thelifetimeofthismodelobjectdoesneedtobeverytightlycoupledtothatoftheprincipal.Forexample,ifthecallerisloggedoutmid-request,theassociatedmodelobjectmustdisappearrightaway,andifthecallerisloggedinagainrightafter(possiblystillinthesamerequest)anewmodelobjectmustbecomeavailable.
Therearevariouspatternstorealizethis,someofthemincludingServletfiltersandotherscontainingCDIproducers.Thepatternwe'regoingtoshowhere,though,involvesacustomprincipal.
AcustomprincipalmeansthataspecificPrincipaltypeisreturnedfromtheidentitystore,insteadofjustprovidingaStringandlettingthecontainerdecidethetype.ThisspecificPrincipaltypecantheneithercontainour
modelobject(aggregation)orbethemodelobject(inheritance).We'llgiveanexampleoftheaggregationapproachhere.
FirstconsiderthefollowingcustomPrincipal:publicclassUserPrincipalextendsCallerPrincipal{
privatefinalUseruser;
publicUserPrincipal(Useruser){
super(user.getEmail());
this.user=user;
}
publicUsergetUser(){
returnuser;
}
}
Thisprincipalextendsfromjavax.security.enterprise.CallerPrincipal
whichistheJavaEESecurityAPI-specificcallerprincipalrepresentation.
WiththisPrincipalimplementationwecannowadjusttheidentitystorethatwepresentedearliertoreturnourcustomprincipalinstead.
@ApplicationScoped
publicclassUserServiceIdentityStoreimplements
IdentityStore{
@Inject
privateUserServiceuserService;
@Override
publicCredentialValidationResultvalidate(Credential
credential){
UsernamePasswordCredentiallogin=
(UserNamePasswordCredential)credential;
Stringemail=login.getCaller();
Stringpassword=login.getPasswordAsString();
Optional<User>optionalUser=
userService.findByEmailAndPassword(email,
password);
if(optionalUser.isPresent()){
Useruser=optionalUser.get();
returnnewCredentialValidationResult(
newUserPrincipal(user),//Principalinstead
ofString.
user.getRolesAsStrings()
);
}
else{
returnCredentialValidationResult.INVALID_RESULT;
}
}
}
Subsequently,wecanaccessourmodelobjectagainfromaninjectedsecuritycontext.
@Inject
privateSecurityContextsecurityContext;
[...]
Optional<User>OptionalUser=
securityContext.getPrincipalsByType(UserPrincipal
.class)
.stream()
.map(e->e.getUser())
.findAny();
ConditionallyRenderingBasedonAccessInwebapplicationsoneoftenwantstorenderpartsofaviewdifferentlybasedonwhetheracallerisauthenticatedornot,andifsobasedonwhatrolesthiscallerisin.
JSFcomponenttagsdon’treallyneedspecialattributesforthis,astheexistingimplicitobjectscombinedwithexpressionlanguagearepowerfulenoughtodomostofthechecksneededforthis.
Oneofthemostcommonchecksisdeterminingwhethertheuserisauthenticated.Thiswasbrieflyshownintheindex.xhtmlviewabove:<c:iftest="#{notemptyrequest.userPrincipal}">
<p>Logged-inas#{request.userPrincipal}</p>
</c:if>
Youcan,ofcourse,alsousetherenderedattributeofanyJSFcomponenthere.
<ui:fragmentrendered="#{notemptyrequest.userPrincipal}">
<p>Logged-inas#{request.userPrincipal}</p>
</ui:fragment>
However,asyoulearnedinthesection“JSTLCoreTags”inChapter3,thiswillonlyendupinaslightlymoreverbosecomponenttree.Moreover,therenderedattributecheckswillbedonethroughouttheJSFlifecycleoverandoverwhileJSTLtagsareexecutedonlyonceduringviewbuildtime.
Notethatwe’reusingtheimplicitobject#{request}hereinsteadofthemoregeneralSecurityContext.ThisisbecauseinJavaEE8there’snoimplicitELobjectavailable
correspondingtothisSecurityContext.InJavaEESecurity,aswellasintheServletAPI(fromwhichtherequest,whichisoftypeHttpServletRequestoriginates)it’sdefinedthatanullreturnfromgetUserPrincipal()meanstheuserisnotauthenticated.AbetteralignmentbetweenJavaEESecurityandExpressionLanguageisplannedforafutureversionofJavaEE.
Anothercommoncheckasmentionedistotestforthecallerbeinginaspecificrole.Heretoowecanusetheimplicitobject#{request},asshowninthefollowing:<c:iftest="#{request.isUserInRole('foo')}">
<!--foospecificthingshere-->
</c:if>
It’sgoodtorememberthatasexplainedinthebeginningofthischapter,therole“foo”doesn’thavetobesomethingthatwewouldcallaroleinournormalusageoftheword.Thatits,itdoesn’thavetobesomethinglike“admin”,or“manager”.Infact,forsuchverylocalusageasinafragmentonaviewit’softenpreferredtouseafiner-grainedname(e.g.,“CAN_UPDATE_SALARY”).Acommontechniqueistomapfine-grainedrolestomorecoarse-grainedroles,suchas,indeed,“ADMIN”.Viathistechniqueauserisgiventhesemorecoarse-grainedroles,andthedatastorethatstorestheauthenticationdatathenonlycontainsthesecoarse-grainedrolesaswell.Whenanidentitystoresuchaswesawaboveretrievesthisauthenticationdataforacertaincallerandsees“ADMIN”itwouldreturnacollectionofrolestowhich“ADMIN”ismapped(e.g.,{"CAN_UPDATE_SALARY","CAN_ADJUST_MARGINS",...}).
Aspecialrolethatwecantestforisthe“**”rolewhichis
analternativeforthe#{notemptyrequest.userPrincipal}check.Thisroleisimplicitlyassignedtoanyauthenticatedcaller,butwiththecaveatthattheapplicationhasnotdeclaredthisinanyway.Ifithasdoneso,“**”losesitsspecialmeaningandisjustanotheropaquestringforwhichthesecuritysystemexplicitlytests.Usingthe“**”check,thefirstfragmentthatweshowedinthissectionlooksasfollows:<c:iftest="#{request.isUserInRole('**')}">
<p>Logged-inas#{request.userPrincipal}</p>
</c:if>
InthestandardJavaEEprogrammaticAPIstherearenomethodsavailabletotestwhetherthecallerisinanyoftwoormoreroles,orinalloftwoormoreroles.Ifthisisrequired,utilitymethodssuchasshowninthefollowingcodecanbeused:
publicstaticbooleanisInAnyRole(HttpServletRequestrequest,
String...roles){
for(Stringroles:roles){
if(request.isUserInRole(role)){
returntrue;
}
}
returnfalse;
}
publicstaticbooleanisInAllRoles(HttpServletRequest
request,String...roles){
for(Stringroles:roles){
if(!request.isUserInRole(role)){
returnfalse;
}
}
returntrue;
}
Sometimesit’snecessarynotonlytorendercontentonaviewdifferently,dependingonwhatrolesacallerisin,butalsototakeintoaccountwhatotherviews(webresources)acallerisallowedtoaccess.Thiscomesintoplay,forinstance,whenrenderingnavigationmenus(omittingtheentriesforviewsacallerdoesnothaveaccessto),orrenderinglinksorbuttonsthatnavigatetoviewstowhichthecallerdoesnothaveaccessinaspecialway(e.g.,inredorwithalockiconnexttoit).
Atraditionalwaytoimplementthisistotestfortherolesthattheprogrammerknowsgiveaccesstothegivenview.Whilethismayseemtoworkwell,it’softenbrittleinpracticeasitletsthecodeworkundertheassumptionofaspecificrole/viewrelationshipwithoutanystrongguaranteesthatthisrelationshipactuallyholds.
Amorestablewaytotestwhetheracallerhasaccesstoagivenviewisquitesimplytotestdirectlyforexactlythat;doesthecallerhaveaccesstothisview(webresource).TheSecurityContexthasamethodthatcanbeusedforalmostexactlythis:SecurityContext#hasAccessToWebResource().SincetheSecurityContextisnotanamedbeanorimplicitobject,wehavetocreateasmallhelperbeaninordertousethisinEL.Thisisshownasfollows:@Named@ApplicationScoped
publicclassSecurity{
@Inject
privateSecurityContextsecurityContext;
publicbooleanhasAccessToWebResource(String
resource){
return
securityContext.hasAccessToWebResource(resource,"GET");
}
}
Therearetwothingstobeawareofhere.First,thehasAccessToWebResource()method
takesawebresourcepattern,whichisthesamepatternasusedfortheurl-patternintheweb.xmlfragmentwelookedatearlier.Thisiscloseto,butnotexactlythesameas,theJSFview.TheJSFviewisoftenspecifiedinamappingindependentway(e.g.,/fooinsteadoffacesfooor/foo.xhtml).Thewebresourcepattern,however,hastobetheURLitself,withthemappingincluded.
Second,hasAccessToWebResource()requiresustospecifytheHTTPmethodforwhichwetesttheaccess.ThisisrequiredsinceinJavaEESecurityconstraintsactuallyapplyperURLandperHTTPmethod.Forinstance,acallercanhaveaccesstoPOSTto/foo.xhtmlbutnottoGET/foo.xhtml.Aswe’regoingtouseourutilitymethodfornavigationtests,GETistypicallytherightHTTPmethodtouse,butweshouldbeawarethatsometimeswemayneedtotestforanotherHTTPmethod.
Withthehelperbeaninplace,wecannoweasilycheckforaccesstoatargetresourceonaviewandaltertherenderingbasedonthat.Todemonstratethis,we’llfirstdefinethreenewwebresourceconstraintsinweb.xml.
<security-constraint>
<web-resource-collection>
<web-resource-name>Bar</web-resource-name>
<url-pattern>/bar.xhtml</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>bar</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Foo</web-resource-name>
<url-pattern>/foo.xhtml</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>foo</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Baz</web-resource-name>
<url-pattern>/baz.xhtml</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>baz</role-name>
</auth-constraint>
</security-constraint>
Aftertheseconstraintshavebeendefinedwecanrenderlinkstothemwithaccesschecksontheenabledattribute.
<h:linkvalue="GotoBar"outcome="/bar"
disabled="#{not
security.hasAccessToWebResource('/bar.xhtml')}"/>
<h:linkvalue="GotoFoo"outcome="/foo"
disabled="#{not
security.hasAccessToWebResource('foo.xhtml')}">
<h:linkvalue="GotoBaz"outcome="/baz"
disabled="#{not
security.hasAccessToWebResource('/baz.xhtml')}"/>
Authenticatingwithacallerhaving,forinstance,theroles“bar”and“foo”,butnot“baz”,willresultinthelinkto/bazbeingrenderedasdisabled.
Cross-SiteRequestForgeryProtectionCross-siterequestforgery(CSRF)isanattackthatletsuserswithouttheirconsentorevenknowledgedoarequesttoasitewheretheymaypossiblybeloggedin.Suchrequestthenhassomesideeffectthatinsomeparticularwaymaybebeneficialtotheattacker.
Forinstance,supposeyourbankhasaURLoftheformhttps:/example.com/transferAmount=4000&tar
getAccount=7836whichmeans“transfer4000eurosfrommyaccounttotheaccountwithID7836.”Inthisstatementthe“myaccount”isbeingdeterminedviathelogged-insession(typicallyacookie)thatyouhavewithyourbank.Nowanattackermightnotbeabletocaptureyoursessioncookie,butthat’snotnecessaryinthisexampleifonlyyourbrowsercanbetrickedintosendingthehttps:/example.com/transferAmount=4000&tar
getAccount=7836requestfromanyotherwebsitethatyouvisitwhileyou’reloggedintoyourbankinanothertaborwindow,withthetargetAccountparametersettoanIDofanaccountthattheattackercontrols.NotethatinpracticeaGETrequestandrequestparameterswouldnotlikelybeused,butPOSTandPOSTparameterswouldbeusedinstead.However,forboththesamebasicvulnerabilityholds.
Ifwewanttoprotectourapplicationagainstreceivingsuchmaliciousrequests,thenoneofthewaystodosoisincluding
“something”(i.e.,atoken)intherequestthatis1. Specifictoacertaincaller.
2. Expiresaftersometime.
3. Can’tbeeasilyguessed.
AsithappensJSFalreadyhassomethingthatfulfillsallthesethreerequirements,andthat’sthejavax.faces.ViewStatehiddenparameterthatonefindswithinJSFforms.
Caution
There’sacaveatthough,andthat’sthatthisparameteronlyfullyfulfillsthoserequirementswhenusingpostbacks,statesavingonserverisused,andtheviewinquestionisnotstateless.Onlyinthatcaseisthevalueofjavax.faces.ViewStateeffectivelyatoken.Sincethisisthedefault,JSFisrelativelysafeoutoftheboxhere,butthisprotectioniscompromisedassoonaswedeviatefromthesedefaults,forinstance,byusingstatelessviews.Seealsothesection“StatelessForms”inChapter4.
NexttothisimplicitCSRFprotection,JSFalsohasexplicitCSRFprotection.ThisexplicitCSRFprotectionaddsatokenforallcases,andadditionallyaddschecksforthe“referer”and“origin”HTTPheaders.NotethatHTTPheadersshouldnormallynotbetrustedforincomingrequests,astheycanbeveryeasilyspoofed.However,forthisparticularattackwe’renottryingtodefendagainstjustanyrandomHTTPrequestbutspecificallyagainstrequestssentfromatrustedbrowser.Theheaderchecksarealsoinadditiontothetokencheck,andthetokencheckmustalwayspassfirst.
DefiningwhichviewsshouldbeprotectedbyaCSRFtokenandtheadditionalheadercheckshappensinawaythat’ssomewhatsimilartohowwedefinedrolesforviews—acollectionofURLpatternsinadeploymentdescriptor.Thistimethedeploymentdescriptorisfaces-config.xmlinsteadofweb.xmlthough.Thefollowinggivesanexamplefortheseentriesinfaces-config.xml:<protected-views>
<url-pattern>/bar.xhtml</url-pattern>
<url-pattern>/foo.xhtml</url-pattern>
<url-pattern>/baz.xhtml</url-pattern>
</protected-views>
Notethattheurl-patternhereistheexactsamepatternthatisusedinweb.xml.Onethingtobeawareofhereisthatdespitefaces-config.xmlbeingaJSF-specificdeploymentdescriptor,theurl-patternhereisagainforthefullURLrelativetotheapplicationrootwhichmeansithastoincludeallmappingsusedsuchasfacesand.xhtml.
Addingafaces-config.xmlwiththeaboveshownprotected-viewsfragmenttotheexampleapplicationcodewe’vebeenworkingoninthischapterwillrenderthelinkonindex.xhtmlto,forexample,/bar.xhtmlinthefollowingway:/bar.xhtml?javax.faces.Token=gdMoNbfOycv2v80gr
Ascanbeseen,thejavax.faces.TokenrequestparameterhasbeenaddedbyJSF.Thetokenistiedtotheuser’sHTTPsession,soiftheHTTPsessionexpires,thetokenalsoexpires.Thetokeniscryptographicallystrong,meaningthatitfulfillsallthreerequirementsforaCSRFprotection
tokenasstatedabove.Ifthetokenistampered(e.g.,weuse
xxMoNbfOycv2v80grinsteadofgdMoNbfOycv2v80gr),orismissingaltogether(i.e.,werequest/bar.xhtml)JSFwillthrowajavax.faces.application.ProtectedViewExcep
tion.Incasethelinkoriginatedfromastatelesspage,JSFapplicationscanhandlethisexceptionbynotifyingtheuserandallowingtore-rendertheoriginalpageagain.Intheexampleabovethatwouldmeanre-renderingindex.xhtml.ThegenuineuserwouldgetanewCSRFprotectiontokenthen,whileanattackerwillnotgettheexpectedsideeffectfromtheoriginalrequest.
Asmentioned,there’sarefererandoriginheadercheckaswell.Forthischeck,JSFcheckswhethertherefererheader,ifavailable,issettoaURLthatoriginatesfromthesameapplication.Todemonstratethis,considerasecondJSFapplicationwithonlyanemptyfaces-config.xmlandthefollowingview:<!DOCTYPEhtml><htmllang="en"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
>
<h:head/>
<h:body>
<h:outputLink
value="http://localhost:8080projectbar.xhtml?
javax.faces.Token=gdMhNbfOycv2v80gr">
test
</h:outputLink>
</h:body>
</html>
AssumingourfirstapplicationwiththeCSRF-protectedbar.xhtmlisdeployedtohttp://localhost:8080/project,clickingtheoutputlinkfromthissecondapplicationwillcausethejavax.faces.application.ProtectedViewExcep
tiontobethrownagaininthefirstapplication.Notethattherefererisanoptionalcheckthat’sonlydonewhentherefererheaderisactuallypresent,asisthecasewiththelinkinthesecondapplication.
Ifweenterhttp://localhost:8080projectbar.xhtml?
javax.faces.Token=gdMhNbfOycv2v80grdirectlyintotheaddressbarofabrowser,orrequestitdirectlyviaacommand-lineutilitysuchaswgetorcurl,therewon’tbearefererandtherequestwillbeaccepted.
Inpractice,aGETrequesthaslessvalueofbeingprotectedagainstCSRFattackssincetheseactuallyshouldbeidempotent(shouldnothavesideeffectsandshouldonlydisplaydata).InsteadofshieldingGETrequestswithCSRFprotectiontokens,it’sprobablyafarbetterideatorefactoranapplicationtonothavenon-idempotentGETrequests.
ForpostbackstheCSRFprotectiontokenworksinmuchthesamewaythough.Todemonstrate,considerchangingthe/bar.xhtmlviewintothefollowing:<!DOCTYPEhtml><htmllang="en"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
>
<h:head/>
<h:body>
<h:form>
<h:commandButtonvalue="Test"action="#
{bar.submit}"/>
</h:form>
</h:body>
</html>
RenderingthispagewillresultintheformtargetURLhavingtheCSRFprotectiontokenappliedtoitinthesamewayaswesawfortheGETrequests—forexample,<formmethod="post"action="projectbar.xhtml?
javax.faces.Token=gdMhNbfOycv2v80gr"...>
Thisisthusanotherdifferencewiththeimplicitjavax.faces.ViewStatetoken,whichisalwaysaPOSTparameter.
WebParameterTamperingProtectionWebparametertamperingisanattackagainstanapplicationwhereanattackermodifiesparametervaluesthataresent(back)totheserverhostingtheapplication.Iftheapplicationdoesn’tvalidatethosevaluescorrectly,anattackercouldgainmorebenefitsthanentitledto,ormaygettheopportunitytocarryoutadditionalattacks.
Forexample,supposeawebapplicationrendersalistofrolesthatcanbeassignedtoanotheruser,say“user,”“manager,”and“sales.”Anattackercouldattempttomodifythedatapostedbackandchangetheselectionof“user”into“admin.”Iftheserverblindlyacceptstheinputand“admin”is
anexistingvalue,thisallowstheattackertogiveanotheruserthe“admin”role,evenwhentheattackerisnotprivilegedtodothat.
JSFhasanimplicitprotectionagainstasubsetofthisattack;namely,againstvaluesbeingpostedbackfromaselection(specifically,fromUISelectOne-andUISelectMany-basedcomponents).ThisworksbyJSFeitherrestoringtheviewfromjavax.faces.ViewStateafterapostback(whenfullstatesavingisused)orre-creatingit(whenpartialstatesavingisused).Onlyvaluesthatwerealsorenderedareaccepted.Theusualcaveatappliesthough,andthat’sthatwithpartialstatesaving,thedataboundtothecomponentneedstobeidenticalbeforeandafterthepostback.This,however,caneasilybeaccomplishedbyusingtheviewscope.
Todemonstrate,considerthefollowingview:
<!DOCTYPEhtml>
<htmllang="en"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
>
<h:head/>
<h:body>
<h:form>
<h:selectOneMenuvalue="#{bean.selected}">
<f:selectItemsvalue="#{bean.available}"/>
</h:selectOneMenu>
<h:commandButtonvalue="Select"/>
</h:form>
<p>Chosenvalue:#{bean.selected}</p>
</h:body>
</html>
Andthefollowingbackingbean:
@Named@RequestScoped
publicclassBean{
privateList<String>available=Arrays.asList("foo",
"bar","kaz");
privateStringselected;
publicList<String>getAvailable(){
returnavailable;
}
publicStringgetSelected(){
returnselected;
}
publicvoidsetSelected(Stringselected){
this.selected=selected;
}
}
Choosing,forexample,“bar”andclicking“select”willrender“Chosenvalue:bar,”asexpected.Changingthevaluebeingsentcanbedoneinvariousways,forinstanceviaaninterceptingproxylikeBurpProxyorbyeditingthelivesourceviathedevelopertoolsfromabrowsersuchasChrome.
Figure13-1 TamperingtheselectedvalueinHTMLsourcecode
Selectingthe“foo”entryafterthechangeasshowninFigure13-1andclickingtheselectbuttonagainwillcausethe“foox”valuetobesenttotheapplication.ThisvaluewillberejectedbyJSFandasaresult“Chosenvalue:”willberendered,indicatingthatourtamperedvalueindeedhasnotbeenaccepted.
Cross-SiteScriptingProtectionCross-sitescriptingorXSSisanattackthathasacoupleofvariations,butpracticallyitboilsdowntoawebapplicationrenderingdatathatitgotfrom(other)usersdirectlyaspartofthemarkupsenttotheclient.Ifthisdataitselfcontainsscriptingcode(typicallyJavaScript),thebrowsermayexecuteitblindly,allowingtheattackertoread,forexample,cookiedataandtosendthatovertoaservercontrolledbytheattacker.
JSFprovidesprotectionagainstthistypeofattackby
havingcontextualoutputescapingenabledformanycommoncontexts.ThemostcommoncontextiswritingoutHTML,whereallJSF’soutputwritersbydefaultXMLescapetheiroutput.
Todemonstrate,considerthefollowingbackingbean:
@Named@RequestScoped
publicclassBean{
privateStringvalue="<script>alert('hi')</script>";
publicStringgetValue(){
returnvalue;
}
}
Thevalueinstancevariablecontainsascriptthatwedon’twantthebrowsertoexecute.Inthisexampleit’shard-coded,butinpracticeitcouldcomefromstoreddatain,forexample,adatabase.
Nowwe’llrenderthisvalueusingtwosimpledefaultconstructsofJSF:adirectexpressionlanguageexpressiononaFaceletandthe<h:outputText>component.
<!DOCTYPEhtml>
<htmllang="en"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
>
<h:head/>
<h:body>
<p>#{bean.value}</p>
<p><h:outputTextvalue="#{bean.value}"><p>
</h:body>
</html>
WhenrequestingthisviewandlookingattheHTMLsource,we’llseethatbothtimesthevaluehasbeenrenderedas“<script>alert('hi')</script>”(i.e.,inanescapedformthatthebrowserwon’texecute).
Incaseweexplicitlydon’twantthisescapingtobedone,theescapeattributeof<h:outputText>canbesettofalse.Forexample,<h:outputTextvalue="#{bean.value}"escape="false"/>
RequestingtheviewagainwiththeabovecomponentonitwillcauseaJavaScriptalerttoappeartobesaying“hi.”Hadthisbeenmaliciouscodebeinginputbyanattacker,thesecurityoftheclient’ssystemwouldhavebeencompromised.Theescapeattributeshouldthereforeonlybeusedwiththeutmostcare.
OutputforusageinURLsisescapedaswell,butthereit’sescapeddifferently,sinceit’sadifferentcontext.Todemonstrate,consideraddingthefollowingcomponenttotheview:<h:linkoutcome="/foo">Gotofoo
<f:paramname="param"value="#{bean.value}"/>
</h:link>
Afterrequestingthisview,we’llseethelinkhasbeenrenderedas<ahref="projectfoo.xhtml?param=%3Cscript%3Ealert%28%27hi%27%29%3C%2Fscript%3E">
Gotofoo
</a>
Whathashappenedhereisthatouroriginalvaluehasbeen
escapedforusageasaURLparameterusingURLencoding,whichascanbeseenisdifferentfromXMLescaping,hencetheterm"contextualoutputescaping."
RelatedtoXSSprotection,sensitivecookiesthattheapplicationusesshouldbesettoHttpOnly,meaningthey’llbesenttotheserverwitheachrequestbutcan’tbereadbyscriptsontheclient.ForthesessionIDcookiethiscanbedoneinweb.xmlasfollows:<session-config><cookie-config>
<http-only>true</http-only>
<secure>true</secure>
</cookie-config>
</session-config>
Notethatthecookieissetto"secure"aswellhere.ThisisnotrelatedtoXSS,butsetsthatthecookieistobesentonlywhenHTTPS/SSLisused.Thisprotectsthecookiefrombeingeavesdropped(e.g.,onasharedWiFinetwork).SincedevelopmentoftenhappensoverHTTPusinglocalhost,suchasettingmaybeproblematicfordevelopmentpurposes.Ifthisisthecase,thenalternativelythecookiecanbesettosecureornotusingServletContext#getSessionCookieConfig()inaServletContextListener
@WebListener
publicclassApplicationConfigimplements
ServletContextListener{
@Override
publicvoidcontextInitialized(ServletContextEventevent)
{
if(...){
event.getServletContext()
.getSessionCookieConfig()
.setSecure(false);
}
}
}
where“...”isanapplication-specificchecktoseeifit’srunningin“devmode.”YoucouldevenuseJSF’sownApplication#getProjectStage()forthis.
SourceExposureProtectionSourceexposureinthecontextofserver-sidewebapplicationsreferstotheunwanteddisclosureofpartsofthewebapplication’ssource.Thisisasecurityrisk,notonlybecauseoftheexposureofthesourceitself(whichmaybeatradesecret)butalsobecauseitmaygiveanattackerinsightonwhichtobasefollow-upattacks(thesourcemaycontainreferencestoothersystems,beans,orevencommentswithpasswords,althoughthosekindsofcommentsshouldofcoursenotbetheretobeginwith).
Duetoanumberofsomewhatperhapsunfortunatedesignchoicesinthepast,JSFhassomespecificvulnerabilitieshere,whichmostlyconcernhowURLmappingisdonebutalsoconcernthelocationofresourcefiles.TounderstandthisvulnerabilitywefirstexplainhowJSFmappingandtheFacesServletwork.
ThemainentryintoeveryJSFapplicationistheFacesServlet.ThisisaServletprovidedbytheJSFframeworkthatactsasaso-calledfrontcontrollerthroughwhichallJSFrequestsarerouted.
ArequesttoaJSFviewsuchasfoo.jsfwillthusfirstneedtogothroughthisServlet,whichwilltheninsomewaylocatethedefinitionofthetreeofcomponentsthatrepresenttheviewfoo.jsf.We’llcallthisactualdefinitionthe“physicalresource.”Outofthebox,JSFsupportstwotypesofphysicalresources:FaceletsandJSPfileswiththeextension.xhtml,.view.xml,and.jsp.NotethatJSPfilesarelargelydeprecated.
InorderfortheFacesServlettobeabletohandlealltheserequestsithastobemappedtooneormoreURLpatternsthatcapturethem.Tobeabletodothis,JSFsupportsprefix,suffix,andexactmapping.
Ifnoexplicitmappinginweb.xmlisspecifiedandarecognizedJSFartifactisfoundintheapplication(suchasanemptyfaces-config,xmlorthe@FacesConfigannotation)thentheFacesServletisautomaticallymapped.SinceJSF2.1andServlet3.0thisautomaticmappingistothefollowingpatterns:
faces*(prefixmapping)
*.jsf(suffixmapping)
*.faces(suffixmapping)
SinceJSF2.3thisalsoincludes:*.xhtml(suffixmapping)
Especiallyolder(existing)JSFapplicationsstillexplicitlymaptheFacesServletinweb.xml,whichthenlooksforexampleasfollows:<servlet><servlet-name>facesServlet</servlet-name>
<servlet-
class>javax.faces.webapp.FacesServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>facesServlet</servlet-name>
<url-pattern>faces*</url-pattern>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
InsuffixmappingandwhenusingFacelets,theFacesServletwillfirsttrytolocatethephysicalresourcewiththesamepathandnameastherequestedresource,butwiththesuffixreplacedby.xhtml.Forexample,arequestforpathfoo.jsfwillresultinalookupforthefilepathfoo.xhtml.
Withprefixmapping,theFacesServletwilltrytolocatethephysicalresourcewiththesamenameandpathastherequestedresource,butminustheprefixpath.Forexample,arequestforfacespath/foo.xhtmlwillresultinalookupforthefilepathfoo.xhtml.
This,however,mayintroduceasecurityissuethatwillresultinexposureoftheFaceletsourcecode.Namely,thelookupforpathfoo.xhtmlisdoneinthewebrootoftheWARinwhichtheFacesServletresides.Unlessotherwise(implicitly)mapped,everyfileinthewebrootisdirectlyaccessiblefordownload.Inthiscase,ifpathfoo.xhtmlisdirectlyrequestedinsteadofpathfoo.jsforfacespath/foo.xhtmlthisrequestwillnotgothroughtheFacesServletandreturnsthebareFaceletssourcecodeofthatpageinsteadoftherenderedmarkup.
Therearetwowaystopreventthisexposureofsourcecode.
1. MaptheFacesServletdirectlyto*.xhtml
2. Addasecurityconstrainttoweb.xml
MappingtheFacesServletdirectlyto*.xhtmlmaybethemostnaturalsolution.Withthismapping,therequestedresourceisidenticaltothephysicalresource.Ifpathfoo.xhtmlisrequested,thentheFacesServletwilltrytolocatepathfoo.xhtml.Viathismapping,thereisnosecondpathtoreachtheFaceletssourceandhencenoriskofexposingit.
Sidenote:beingabletouse*.xhtmlmappingwasanewfeatureinJSF2.0.DoingthisinJSF1.xresultedinaninfiniteloop.
TomaptheFacesServletto*.xhtml,theeasiestwayistorelyonthedefaultmappingofJSF2.3asexplainedabove.Ifthisisnotpossibleforsomereason,addthemappingtotheFacesServletmappinginweb.xmlasshowninthefollowingcode:<servlet-mapping><servlet-name>facesServlet</servlet-name>
<url-pattern>faces*</url-pattern
<url-pattern>*.jsf</url-pattern>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
Asanalternative,asecurityconstraintcanbedefinedthatpreventsaccessto*.xhtmlresources.Thereisrarelyagoodreasontopreferthisoverthesimpler*.xhtmlto*.xhtmlmapping,butforcompleteness,thiscanbedoneasfollows:<security-constraint>
<display-name>NoaccesstoFaceletssource</display-
name>
<web-resource-collection>
<web-resource-name>XHTML</web-resource-name>
<url-pattern>*.xhtml</url-pattern>
</web-resource-collection>
<auth-constraint/>
</security-constraint>
AspecialcaseofexposingFaceletssourcecodehappenswithcompositecomponents.CompositecomponentsarecomponentsthatareimplementedviaaFaceletinsteadofaJavaclass.Byconvention,theyhavetobeplacedinadirectoryinsideadirectorynamed/resourcesthatresidesinthewebroot—forinstance,/resources/bar/foo.xhtml.Thiswillmakeacomponent“foo”availableinthenamespace“http://xmlns.jcp.org/jsf/composite/bar”.
Componentsareofcoursenotviewsandtheusershouldnotbeabletorequestthosedirectly.Unfortunately,/resourcesisnotinanywayaspecialdirectorytoJavaEE.JSFassignsaspecialmeaningtoitbyconvention,buttotheServletcontainerit’sadirectorylikeanyother.Thisspecificallymeansthere’snoprotectionappliedtothisandanyusercandirectlyrequestresourcesfromit.Inotherwords,thisdirectoryis“worldreadable.”Evenwithan*.xhtmlmapping,thisnotonlyallowstheusertoguesswhichcomponentswehavebutletstheuserattempttoexecutethoseaswell.Clearlythisisnotwhatwewant.
Thereareagaintwosolutionsforthis:1. ConfigureanotherdirectorytobetheJSFresourcesdirectory
2. Addasecurityconstrainttoweb.xml
InJSF2.2amethodwasintroducedtoaddressthissecurityvulnerability.Namely,viathejavax.faces.WEBAPP_RESOURCES_DIRECTORY
contextparameteranotherdirectorycanbeconfiguredtobetheJSFresourcesdirectoryinsteadof/resources.Forexample,<context-param><param-
name>javax.faces.WEBAPP_RESOURCES_DIRECTORY</param-name>
<param-value>WEB-INF/resources</param-value>
</context-param>
Notethatthepathisrelativetothewebrootandmaynotbeginwitha“/”.
Alternatively,orforJSF2.0/2.1,asecurityconstraintcanbeconfiguredinweb.xmlagainthatprohibitscalleraccessto/resources.Thiscanbedoneinasimilarwayasprotectingfor*.xhtmlaccess.
<security-constraint>
<web-resource-collection>
<web-resource-name>resources</web-resource-name>
<description>Theresourcesdirectory</description>
<url-pattern>/resources/*</url-pattern>
</web-resource-collection>
<auth-constraint/>
</security-constraint>
(1) (2)
©BaukeScholtz,ArjanTijms2018BaukeScholtzandArjanTijms,TheDefinitiveGuidetoJSFinJavaEE8,https://doi.org/10.1007/978-1-4842-3387-0_14
14.Localization
BaukeScholtz andArjanTijms
Willemstad,Curaçao Amsterdam,Noord-Holland,TheNetherlands
JSFhasalwayshaddecentinternationalizationsupport.SinceJSF1.0youcansupplyjava.util.ResourceBundle-basedbundlefilesindifferentlocales,whichinturngetdynamicallyincludedastextinthewebpageatthedeclaredplaces.Also,allJSFconvertersandvalidatorshavetheirownsetoflocalizeddefaultmessageswhichyoucaneasilycustomizeviaamessagebundleor,sinceJSF1.2,vianewrequiredMessage,converterMessage,andvalidatorMessageattributes.JSF2.0adds,viathenewjavax.faces.application.ResourceHandler,API(applicationprograminginterface)supportforlocalizableassetssuchasstylesheets,scripts,andimages.
Theactofinternationalization,“I18N,”isdistinctfromtheactoflocalization,“L10N.”TheinternationalizationpartisbasicallyalreadydonebyJSF(JavaServerFaces)itselfasbeingaMVC(model-view-controller)framework.Allyouneedtodoistotakecareofthelocalizationpart.Basically,youneedtospecifythe“activelocale”intheview,supplythe
1 2
desiredresourcebundlefiles,ifnecessarytranslatedwithhelpofathird-partytranslationservice,anddeclarereferencestothebundlefileinyourJSFpage.
InthischapteryouwilllearnhowtoprepareaJSFwebapplicationfordifferentlanguagesandhowtodevelopitinordertomakelocalizationeasierforyourselfastomaintenance.
HelloWorld,Olámundo,
Tostartoff,createabunchofnewbundlefilesinmain/java/resourcesfolderoftheproject.Themain/java/resourcesfolderofaMavenWARprojectisintendedfornon-classfileswhicharesupposedtoendupintheWEB-INFclassesfolderofthefinalbuild.Thebundlefilescanbeinjava.util.Propertiesformat,withthe.propertiesextension.
Thefilenameofthosefilesmusthaveacommonprefix(e.g.,“text”),followedbyanunderscoreandthetwo-letterISO639-1-Alpha-2 languagecode(e.g.,“en”forEnglish,“pt”forPortuguese,and“hi”forHindi).Itcanoptionallybefollowedbyanotherunderscoreandthetwo-letterISO3166-1-Alpha-2 countrycode(e.g.,“GB”forGreatBritain,“US”forUnitedStates,“BR”forBrazil,“PT”forPortugal).
main/java/resources/com/example/project/i18n/text.properties
title=Localizationexample
heading=HelloWorld
paragraph=Welcometomywebsite!
main/java/resources/com/example/project/i18n/text_pt_BR.prope
1
2
rties
title=Exemplodelocalização
heading=Olámundo
paragraph=Bem-vindoaomeusite!
main/java/resources/com/example/project/i18n/text_hi.properti
es
Donotethatallthosebundlefileshavecommonkeys“title”,“heading”,and“paragraph”,whichareusuallyinEnglish.It’sbasicallythelinguafrancaoftheInternetandwebdevelopers.It’sconsideredthebestpracticetokeepthesourcecodeentirelyinEnglish,particularlyifitisopensource.
AlsonotethattheEnglishbundlefiledoesn’thavethe“en”languagecodeinthefilenameasintext_en.propertiesbutisjusttext.properties.Basically,ithasbecomethefallbackbundlefilewhichissupposedtocontaineverysinglebundlekeyusedintheentirewebapplication.Thisway,whenabundlefilewithaspecificlanguagecodedoesn’tcontainthedesiredbundleentry,thenthevaluewillbelookedupfromthefallbackbundlefile.Thisisusefulforsituationswhereinyou’dliketograduallyupgradethebundlefiles,orwhenyouhavecertainsectionsinthewebapplicationwhichdon’tnecessarilyneedtobelocalized,suchasback-endadminpages.
ConfigurationInordertofamiliarizetheJSFapplicationwiththosebundle
filesandthedesiredlocales,weneedtoedititsfaces-config.xmlfiletoaddthefollowingentriestothe<application>element:<application><locale-config>
<default-locale>en</default-locale>
<supported-locale>pt_BR</supported-locale>
<supported-locale>hi</supported-locale>
</locale-config>
<resource-bundle>
<base-name>com.example.project.i18n.text</base-
name>
<var>text</var>
</resource-bundle>
</application>
The<base-name>mustspecifythefullyqualifiedname(FQN)followingthesameconventionasforJavaclassesandthatitdoesn’tincludethefileextension.The<var>basicallydeclarestheEL(ExpressionLanguage)variablenameofthebundlefile.ThiswillmakethecurrentlyloadedresourcebundleavailableasaMap-likeobjectinELvia#{text}.Toavoidconflicts,youonlyneedtomakesurethatthisnameisn’talreadypossessedbyanymanagedbeanoranyimplicitELobjects.
ReferencingBundleinJSFPageIt’srelativelysimple,justtreat#{text}asaMapwiththebundlekeysasmapkeys.
<!DOCTYPEhtml>
<htmllang="#{view.locale.toLanguageTag()}"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>#{text['title']}</title>
</h:head>
<h:body>
<h1>#{text['heading']}</h1>
<p>#{text['paragraph']}</p>
</h:body>
</html>
JSFwillalreadyautomaticallydeterminetheclosestmatchingactivelocalebasedontheHTTPAccept-Languageheader andsetitaslocalepropertyofUIViewRoot.TheAccept-Languageheaderisconfigurableinbrowser’ssettings.In,forexample,Chrome,youcanconfigureitviachrome://settings/languages.Ifyouplayaroundwithit,forexample,byswitchingbetweenEnglish,Portuguese,andHindiasthetop-rankedlanguagesettinginbrowserandrefreshtheJSFpage,thenyou’llnoticethatitchangesthetexttoconformthebrowser-specifiedlanguagesetting.Ifyoucheckthebrowser’sdevelopertools—usuallyaccessiblebypressingF12—andinspecttheHTTPrequestheadersinthenetworkmonitor,thenyou’llalsonoticethattheAccept-Languageheaderchangesaccordingly.
Youmighthavenoticedthatthelangattributeofthe<html>tagreferences#{view.locale.toLanguageTag()}.Basically,thiswillprinttheIETFBCP47languagetag ofthelocalepropertyofthecurrentUIViewRoot,whichisinturnavailableasanimplicitELobject#{view}.Thelocalepropertyisaninstanceofjava.util.Localewhichhas
3
4
actuallynogettermethodforthelanguagetagsuchasgetLanguageTag(),butonlyatoLanguageTag()method,hencethedirectmethodreferenceinELinsteadoftheexpectedpropertyreference.
Thelangattributeofthe<html>tagisnotmandatoryforthefunctioningofJSFlocalizationfeature.Moreover,JSFtreatsitastemplatetextanddoesnothingspecialwithit.Youcansafelyleaveoutit.Itis,however,importantforsearchengines.ThiswayasearchenginelikeGooglewillbeinformedwhichlanguagethepage’scopyisin.Thisisnotonlyimportantinordertoendupcorrectlyinlocalizedsearchresults,butalsoimportantincaseyouservetheverysamepageindifferentlanguages.Thiswouldotherwisebytheaveragesearchenginealgorithmbepenalizedas“duplicatecontent,”whichisthusbadforSEO(searchengineoptimization)ranking.
You’llalsohavenoticedthatthebundlekeysarespecifiedintheso-calledbracenotation#{text['...']}.Thestringbetweensinglequotesbasicallyrepresentsthebundlekey.Inthisspecificcaseyoucouldalsohaveused#{text.title},#{text.heading},and#{text.paragraph}instead.Thisis,however,notthecommonpractice.UsingthebracenotationnotonlygivesagenerallyclearmeaningtowhattheELvariablerepresents(aresourcebundle),butitalsoallowsyoutousedotsinthebundlekeynamesuchas#{text['meta.description']}.TheELexpression#{text.meta.description}has,namely,anentirelydifferentmeaning:“getthedescriptionpropertyofthenestedmetapropertyofthetextobject,”whichisincorrect.
ChangingtheActiveLocaleYoucanalsochangetheactivelocaleontheserverside.Thisisbesttobedoneinasingleplaceinasite-widemastertemplatewhichcontainsthe<f:view>tag.Theactivelocalecanbesetinthelocaleattributeofthe<f:view>whichcanaccepteitherastaticstringrepresentingthelanguagetagoraconcretejava.util.Localeinstance.ThelocaleattributeacceptsanELexpressionandcanbechangedprogrammaticallyviaamanagedbean.Thisoffersyoutheopportunitytolettheuserchangeitviathewebpagewithoutfiddlingaroundinthebrowser’slanguagesettings.YoucouldpresenttheavailablelanguageoptionstotheuserinaJSFpageandleteachselectionchangetheactivelocale.ThiscanbeachievedwiththefollowingJSFpage:<!DOCTYPEhtml><htmllang="#{activeLocale.languageTag}"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<f:viewlocale="#{activeLocale.current}">
<h:head>
<title>#{text['title']}</title>
</h:head>
<h:body>
<h1>#{text['heading']}</h1>
<p>#{text['paragraph']}</p>
<h:form>
<h:selectOneMenuvalue="#
{activeLocale.languageTag}">
<f:selectItems
value="#
{activeLocale.available}"var="l"
itemValue="#{l.toLanguageTag()}"
itemLabel="#
{l.getDisplayLanguage(l)}">
</f:selectItems>
<f:ajaxlistener="#
{activeLocale.reload()}"/>
</h:selectOneMenu>
</h:form>
</h:body>
</f:view>
</html>
Itisslightlyadjustedfromthepreviousexample;thereisnow<f:view>around<h:head>and<h:body>.Thelocaleattributeof<f:view>referencesthecurrentlyactivelocaleviathe#{activeLocale}managedbean,whichisasfollows:@Named@SessionScopedpublicclassActiveLocaleimplementsSerializable{
privateLocalecurrent;
privateList<Locale>available;
@Inject
privateFacesContextcontext;
@PostConstruct
publicvoidinit(){
Applicationapp=context.getApplication();
current=
app.getViewHandler().calculateLocale(context);
available=newArrayList<>();
available.add(app.getDefaultLocale());
app.getSupportedLocales().forEachRemaining(avail
able::add);
}
publicvoidreload(){
context.getPartialViewContext().getEvalScripts()
.add("location.replace(location)");
}
publicLocalegetCurrent(){
returncurrent;
}
publicStringgetLanguageTag(){
returncurrent.toLanguageTag();
}
publicvoidsetLanguageTag(StringlanguageTag){
current=Locale.forLanguageTag(languageTag);
}
publicList<Locale>getAvailable(){
returnavailable;
}
}
Toreiterate,@InjectFacesContextworksonlyifyouhaveplaced@FacesConfigonanarbitraryCDIbean
somewhereinthewebapplication.OtherwiseyouhavetoreplaceitbyinlineFacesContext.getCurrentInstance()calls.There’sonlyonecaveatwiththoseinlinecalls:youneedtomakeabsolutelysurethatyoudon’tassignitasafieldin,forexample,@PostConstruct,becausetheactualinstanceissubjecttobeingchangedacrossmethodcallsontheverysamebeaninstance.InjectingasafieldviaCDItakestransparentlycareofthis,andisthereforesafe,butmanuallyassigningisnot.
In@PostConstruct,ViewHandler#calculateLocale()isusedtocalculatethecurrentlocalebasedonAccept-Languageheaderandthedefaultandsupportedlocalesasconfiguredinfaces-config.xml.ThisfollowsexactlythesameJSF-internalbehaviorasifwhenthere’sno<f:viewlocale>defined.Finally,theavailablelocalesarecollectedbasedontheconfigureddefaultandsupportedlocales.
Theavailablelocalesare,via<f:selectItems>of<h:selectOneMenu>,presentedtotheuserasdrop-downoptions(seeFigure14-1).Thenested<f:ajax>makessurethattheselectedoptionissetinthemanagedbeanassoonastheuserchangestheoption.
Figure14-1 Changingtheactivelocale
The#{activeLocale.languageTag}propertydelegatesinternallytothecurrentjava.util.Localeinstance.Thisisbasicallydoneforconveniencesothatwedon’tnecessarilyneedtoaddaconverterfor#{activeLocale.current}incasewewanttouseitin<h:selectOneMenu>.
<f:ajaxlistener>basicallyperformsafullpagereloadwiththehelpofapieceofJavaScriptwhichisexecutedoncompletionoftheAjaxrequest.ThisisdonebyaddingascripttothePartialViewContext#getEvalScripts()method,whichisnewsinceJSF2.3.Anyaddedscriptwillenduporderedinthe<eval>sectionoftheJSFAjaxresponse,whichinturngetsexecutedafterJSFAjaxenginehasupdatedtheHTMLDOM(DocumentObjectModel)tree.
Thescriptitself,location.replace(location),basicallyinstructsJavaScripttoreloadthecurrentdocumentwithoutkeepingthepreviousdocumentinhistory.Thismeans
thatthebackbuttonwon’tredisplaythesamepage.Youcanalsouselocation.reload(true)instead,butthiswon’tworknicelyifasynchronous(non-Ajax)POSTrequesthasbeenfiredonthesamedocumentbeforehand.Itwouldbere-executedandcauseadoublesubmit.And,itunnecessarilyremembersthepreviouspageinthehistory.Thismayendupinconfusingbehavior,becausethebackbuttonwouldthenseemtohavenoeffectasitwouldredisplayexactlythesamepageastheactivelocaleisstoredinthesession,notintherequest.
Alternatively,insteadofinvoking<f:ajaxlistener>,youcaninthisspecificusecasealsousejust<f:ajaxrender="@all">withoutanylistener.Ithasatleastonedisadvantage:thedocument’stitlewon’tbeupdated.Inanycase,[email protected]’sonlyonelegitimatereal-worldusecaseforit:displayingafullerrorpageonanAjaxrequest.
Asacompletelydifferentalternative,youcouldmaketheactivelocalerequestscopedinsteadofsessionscopedbyincludingthelanguagetagintheURLasinhttp://example.com/en/page.xhtml,http://example.com/pt/page.xhtml,http://example.com/hi/page.xhtml.Thiswayyoucanchangetheactivelocalebysimplyfollowingalink.Thisonlyinvolvesaservletfilterwhichextractsthejava.util.LocaleinstancefromtheURLandforwardsittothedesiredJSFpage,andacustomviewhandlerwhichincludesthelanguagetaginthegeneratedURLofany<h:form>,<h:link>,and<h:button>component.YoucanfindakickoffexampleintheJavaEEKickoff
5
Application.
OrganizingBundleKeysWhenthewebapplicationgrows,youmaynoticethatbundlefilesstarttobecomeunmaintainable.Thekeyistoorganizethebundlekeysfollowingaverystrictconvention.Reusablesite-wideentries,usuallythoseusedasinputlabels,buttonlabels,linklabels,tableheaderlabels,etc.,shouldbekeyedusingageneralprefix(e.g.,“label.save=Save”).Page-specificentriesshouldbekeyedusingapage-specificprefix(e.g.,“foldername_pagename.title=SomePageTitle”).FollowingisanelaborateexampleofalocalizedFaceletstemplate,WEB-INFtemplates/page.xhtml:<!DOCTYPEhtml>
<htmllang="#{activeLocale.language}"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:fn="http://xmlns.jcp.org/jsp/jstl/functions"
>
<c:setvar="page"value="page#{fn:replace(
fn:split(view.viewId,'.')[0],'','_')}"
scope="view">
<f:viewlocale="#{activeLocale.current}">
<h:head>
<title>#{text[page+='.title']}</title>
<metaname="description"
content="#{text[page+=
5
'.meta.description']}"/>
</h:head>
<h:bodyid="#{page}">
<header>
<nav>
<h:linkoutcome="/home"
value="#{text['label.home']}"/>
<h:linkoutcome="/login"
value="#{text['label.login']}"
/>
<h:linkoutcome="/signup"
value="#{text['label.signup']}"
/>
</nav>
<h:form>
<h:selectOneMenu
value="#
{activeLocale.languageTag}">
<f:selectItems
value="#
{activeLocale.available}"var="l"
itemValue="#
{l.toLanguageTag()}"
itemLabel="#
{l.getDisplayLanguage(l)}">
</f:selectItems>
<f:ajaxlistener="#
{activeLocale.reload()}"/>
</h:selectOneMenu>
</h:form>
</header>
<main>
<h1>#{text[page+='.title']}</h1>
<ui:insertname="content"/>
</main>
<footer>
©#{text['page_home.title']}
</footer>
</h:body>
</f:view>
</html>
TheJSTL<c:set>basicallyconvertstheUIViewRoot#getViewId()toastringwhichissuitableasapage-specificprefix.TheJSFviewIDbasicallyrepresentstheabsoluteserver-sidepathtothephysicalfilerepresentingtheJSFpage(e.g.,“useraccount.xhtml”).Thisneedstobemanipulatedtoaformatsuitableasaresourcebundlekey.Thefn:split()callextractsthepart“useraccount”fromitandthefn:replace()callconvertstheforwardslashtounderscoresothatitbecomes“useraccount”.Finally,<c:set>storesitas“pageuseraccount”intheviewscopeunderthename“page”sothatit’savailableas#{page}elsewhereinthesameview.
You’llnoticethat#{page}isinturnbeingusedas,amongothers,theIDof<h:body>.ThismakesiteasiertoselectaspecificpagefromageneralCSS(CascadingStyleSheets)filejustincasethat’sneeded.#{page}isalsobeingusedinseveralresourcebundlereferences,suchas#{text[page+='.title']}whichultimatelyreferencesincaseof“/home.xhtml”thekey
“page_home.title”.Withsuchatemplateyoucanhavethefollowingpage-specificresourcebundleentries:
page_home.title=MyWebsite
page_home.meta.description=AHelloWorldJSFapplication.
page_login.title=LogIn
page_login.meta.description=LogintoMyWebsite.
page_signup.title=SignUp
page_signup.meta.description=SignuptoMyWebsite.
FollowingisanexampleofatemplateclientwhichutilizesthepreviouslyshowntemplateWEB-INFtemplates/page.xhtml,the/login.xhtml:<ui:compositiontemplate="WEB-INFtemplates/page.xhtml"
xmlns:="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
>
<ui:definename="content">
<h:formid="login">
<fieldset>
<h:outputLabelfor="email"
value="#{text['label.email']}"/>
<h:inputTextid="email"required="true"
value="#{login.email}"/>
<h:messagefor="email"
styleClass="message"/>
<h:outputLabelfor="password"
value="#{text['label.password']}"/>
<h:inputSecretid="password"
required="true"
value="#{login.password}"/>
<h:messagefor="password"
styleClass="message"/>
<h:commandButtonid="submit"action="#
{login.submit}"
value="#{text['label.login']}"/>
<h:messagefor="login"
styleClass="message"/>
</fieldset>
</h:form>
</ui:define>
</ui:composition>
Followingiswhattheassociatedresourcebundleentrieslooklike:
label.email=Email
label.password=Password
label.login=LogIn
Note:incaseyoufindthatthepagelookscrippled,simplyaddaCSSfilewiththefollowingruletostartwith:nava,fieldsetlabel,fieldsetinput{
display:block;
}
LocalizingConversion/ValidationMessages
IncaseyouhavepreparedasimplebackingbeanclassLoginwithtwostringpropertiesemailandpasswordandamethodsubmit(),andsubmittheaboveshownloginpagewithoutfillingoutthee-mailinputfield,thenyou’llfaceavalidationerrorinthefollowingformat:login:email:ValidationError:Valueisrequired.
WhenyouswitchthelanguagetoPortugueseandresubmittheemptyform,thenyou’llseethatit’salsolocalized.However,whenyouswitchthelanguagefurthertoHindi,thenyou’llnoticethatthere’snostandardHindimessagebundleinthestandardJSFimplementation.You’dneedtoprovideyourown.Thereareseveralwaystoachievethis.
First,JSFinputandselectcomponentssupportthreeattributestooverridethedefaultmessage:requiredMessage,validatorMessage,andconverterMessage.Thefollowingexampleshowshowtooverridethedefaultrequiredmessage:<h:inputText...requiredMessage="#{text['message.required']}"/>
Thisisarguablytheeasiestapproach.Themajorcaveatisthatyouhavetocopy/pasteiteverywhereincaseyouhaven’twrappeditinareusabletagfilelike<my:inputText>.ThisisnotDRY.
AnotherwayistosupplyacustommessagebundlewhichoverridesallpredefinedbundlekeysspecificforJSFconversion/validationmessagesandregisteritas<message-bundle>infaces-config.xml.Youcanfindthepredefinedbundlekeysinchapter2.5.2.4“LocalizedApplicationMessages”oftheJSFspecification. Thebundlekeyofthedefaultrequiredmessage“ValidationError:Value
6
7
isRequired”isthusjavax.faces.component.UIInput.REQUIRED.Wecanadjustitinnewmessagebundlefilesasfollows:
main/java/resources/com/example/project/i18n/messages.propert
ies
javax.faces.component.UIInput.REQUIRED={0}isrequired.
main/java/resources/com/example/project/i18n/messages_pt_BR.p
roperties
javax.faces.component.UIInput.REQUIRED={0}éobrigatório.
main/java/resources/com/example/project/i18n/messages_hi.prop
erties
Finally,configureitinthe<application>elementofthefaces-config.xmlfile:<application>...
<message-
bundle>com.example.project.i18n.messages</message-
bundle>
</application>
You’llperhapshavenoticedthe{0}placeholdersinthemessages.Theyrepresentthelabelsoftheassociatedinputandselectcomponents.Thelabelsdefaulttothecomponent’sclientID,whichisbasicallytheIDoftheJSF-generatedHTMLelementasyoucanfindinthebrowser’spagesource.Youcanoverrideitbyexplicitlysettingthelabelattributeofthecomponent.
<h:inputTextid="email"...label="#{text['label.email']}"/>
<h:inputSecretid="password"...label="#
{text['label.password']}"/>
Notethatputtingthemessagebundleinadifferentfilethantheresourcebundleisnotstrictlynecessary.Youcanalsojustputthemessagebundleentriesintext.propertiesfilesandadjustthe<message-bundle>entrytopointtothesameFQNas<resource-bundle>.
ObtainingLocalizedMessageinaCustomConverter/ValidatorThevalueofthe<message-bundle>entrycanbeobtainedprogrammaticallyviaApplication#getMessageBundle().Youcaninturnuseittoobtaintheactualbundleviathejava.util.ResourceBundleAPI,alongwithUIViewRoot#getLocale().Thisallowsyoutoobtainalocalizedmessageinacustomconverterandvalidator.Followingisanexampleofsuchavalidator,whichchecksifthespecifiede-mailaddressisalreadyinuse:@FacesValidator(value="duplicateEmailValidator",
managed=true)
publicclassDuplicateEmailValidatorimplements
Validator<String>{
@Inject
privateUserServiceuserService;
@Override
publicvoidvalidate
(FacesContextcontext,UIComponentcomponent,
Stringvalue)
throwsValidatorException
{
if(value==null){
return;
}
Optional<User>user=
userService.findByEmail(value);
if(user.isPresent()){
thrownewValidatorException(new
FacesMessage(getMessage(
context,
"message.duplicateEmailValidator")));
}
}
publicstaticStringgetMessage(FacesContext
context,Stringkey){
returnResourceBundle.getBundle(
context.getApplication().getMessageBundle(),
context.getViewRoot().getLocale()).getString
(key);
}
}
Youmighthavenoticedthenewmanagedattributeofthe@FacesValidatorannotation.ThiswillbasicallyturnonCDIsupportonthevalidatorinstanceandhenceallowyoutoinjectabusinessserviceintoavalidator.Thesameattributeisalsoavailablefor@FacesConverter.
Theshownvalidatorexampleassumesthatthefollowingentryispresentintheresourcebundlefilesasidentifiedby<message-bundle>:message.duplicateEmailValidator=Emailisalreadyinuse.
LocalizingEnumsThecleanestapproachtolocalizeenumsistosimplyusetheirownidentityasabundlekey.ThiskeepstheenumclassfreeofpotentialUI-specificcluttersuchashard-codedbundlekeys.Generally,thecombinationoftheenum’ssimplenameandtheenumvalueshouldsufficetorepresentasite-wideuniqueidentifier(UI).Giventhefollowingcom.example.project.model.Groupenumrepresentingausergroup:publicenumGroup{USER,
MANAGER,
ADMINISTRATOR,
DEVELOPER;
}
andthefollowingresourcebundleentriesintext.properties:Group.USER=UserGroup.MANAGER=Manager
Group.ADMINISTRATOR=Administrator
Group.DEVELOPER=Developer
youcaneasilylocalizethemasfollows:
<f:metadata>
<f:importConstantstype="com.example.project.model.Group"
/>
</f:metadata>
...
<h:selectManyCheckboxvalue="#{editUserBacking.user.groups}">
<f:selectItemsvalue="#{Group.values()}"var="group"
itemLabel="#{text['Group.'+=group]}"/>
</h:selectManyCheckbox>
Notethatthe<f:importConstants>isnewsinceJSF2.3.Itisrequiredtobeplacedinside<f:metadata>.Itstypeattributemustrepresentthefullyqualifiednameoftheenumoranyclassorinterfacewhichcontainspublicconstantsinflavorofpublicstaticfinalfields.<f:importConstants>willautomaticallyimportthemintotheELscopeasaMap<String,Object>whereinthemapkeyrepresentsthenameoftheconstantasstringandthemapvaluerepresentstheactualvalueoftheconstant.
With#{Group.values()}youcanthusobtainacollectionofallconstantvaluesandeachvalueisthenlocalizedinitemLabel.AlsonotethatitemValueisomittedasitdefaultstothevalueofthevarattributewhichisalreadysufficient.
ParameterizedResourceBundleValuesYoucanalsoparameterizeyourresourcebundleentriesusingthe{0}placeholdersofthejava.text.MessageFormatAPI. TheycanontheJSFsideonlybesubstitutedwith<h:outputFormat>wherebytheparametersareprovidedas<f:param>children,inthesameorderastheplaceholders.Giventhefollowingentry:page_products.table.header=There{0,choice,0#areno
products
8
|1#isone
product
|1<are{0}
products}.
itcanbesubstitutedusing<h:outputFormat>asfollows:<h:outputFormatvalue="#
{text['page_products.table.header']}">
<f:paramvalue="#{bean.products.size()}"/>
</h:outputFormat>
Database-BasedResourceBundleJSFalsosupportsspecifyingacustomResourceBundleimplementationas<base-name>.Thisallowsyoutoprogrammaticallyfillandsupplythedesiredbundles,forexample,frommultiplebundlefiles,orevenfromadatabase.Inthisexamplewe’llreplacethedefaultpropertiesfile-basedresourcebundlebyonewhichloadstheentriesfromadatabase.Thistakesusastepfurtherastoorganizingtheresourcebundlekeys.Thisway,youcaneveneditthemviaaweb-basedinterface.FollowingiswhattheJPAentitylookslike:
@Entity
@Table(uniqueConstraints={
@UniqueConstraint(columnNames={"locale","key"})
})
publicclassTranslation{
@Id@GeneratedValue(strategy=GenerationType.IDENTITY)
privateLongid;
@Column(length=5,nullable=false)
private@NotNullLocalelocale;
@Column(length=255,nullable=false)
private@NotNullStringkey;
@Lob@Column(nullable=false)
private@NotNullStringvalue;
//Add/generategettersandsettershere.
}
NotethattheJPA(JavaPersistenceAPI)annotationsprovidesufficienthintsastowhattheDDL(DataDefinitionLanguage)ofthetableshouldlooklike.Whenhavingthepropertyjavax.persistence.schema-generation.database.actionsettocreateordrop-and-createinpersistence.xml,thenitwillautomaticallygeneratetheproperDDL.Forsakeofcompleteness,hereitisinHSQL/pgSQLflavor.
CREATETABLETranslation(
idBIGINTGENERATEDBYDEFAULTASIDENTITYPRIMARYKEY,
localeVARCHAR(5)NOTNULL,
keyVARCHAR(255)NOTNULL,
valueCLOBNOTNULL
);
ALTERTABLETranslation
ADDCONSTRAINTUK_Translation_locale_key
UNIQUE(locale,key);
Andhere’swhattheEJB(EnterpriseJavaBeans)servicelookslike.
@Stateless
publicclassTranslationService{
@PersistenceContext
privateEntityManagerentityManager;
@TransactionAttribute(value=REQUIRES_NEW)
@SuppressWarnings("unchecked")
publicObject[][]getContent
(Localelocale,Localefallback)
{
List<Object[]>resultList=
entityManager.createQuery(
"SELECTt1.key,COALESCE(t2.value,t1.value)"
+"FROMTranslationt1"
+"LEFTOUTERJOINTranslationt2"
+"ONt2.key=t1.key"
+"ANDt2.locale=:locale"
+"WHEREt1.locale=:fallback")
.setParameter("locale",locale)
.setParameter("fallback",fallback)
.getResultList();
returnresultList.toArray(new
Object[resultList.size()][]);
}
}
ForJPAweonlyneedanadditionalconverterwhichconvertsbetweenjava.util.LocaleinthemodelandVARCHARinthedatabase,whichisrepresentedbyjava.lang.String.YoucanusetheJPA2.0AttributeConverterforthis.It’smuchlikeaJSFconverterbutforJPAentities.It’srelativelysimple;there’snoadditionalconfigurationnecessary.Seethefollowing:publicclassLocaleConverter
implementsAttributeConverter<Locale,String>
{
@Override
publicStringconvertToDatabaseColumn(Localelocale)
{
returnlocale.toLanguageTag();
}
@Override
publicLocaleconvertToEntityAttribute(String
languageTag){
returnLocale.forLanguageTag(languageTag);
}
}
NowwehavethecustomResourceBundle;it’scalledDatabaseResourceBundle.Putitinthepackagecom.example.project.i18n.
publicclassDatabaseResourceBundleextendsResourceBundle{
privatestaticfinalControlCONTROL=new
DatabaseControl();
@Override
publicObjecthandleGetObject(Stringkey){
returngetCurrentInstance().getObject(key);
}
@Override
publicEnumeration<String>getKeys(){
returngetCurrentInstance().getKeys();
}
privateResourceBundlegetCurrentInstance(){
FacesContextcontext=
FacesContext.getCurrentInstance();
Stringkey=CONTROL.getClass().getName();
return(ResourceBundle)context.getAttributes()
.computeIfAbsent(key,k->
ResourceBundle.getBundle(key,
context.getViewRoot().getLocale(),
Thread.currentThread().getContextClassLoader(
),
CONTROL));
}
privatestaticclassDatabaseControlextendsControl{
@Override
publicResourceBundlenewBundle
(StringbaseName,Localelocale,Stringformat,
ClassLoaderloader,booleanreload)
throwsIllegalAccessException,
InstantiationException,
IOException
{
FacesContextcontext=
FacesContext.getCurrentInstance();
finalObject[][]contents=CDI.current()
.select(TranslationService.class).get()
.getContent(
locale,
context.getApplication().getDefaultLocale
());
returnnewListResourceBundle(){
@Override
protectedObject[][]getContents(){
returncontents;
}
};
}
}
}
Finally,adjustthe<resource-bundle><base-name>entryinfaces-config.xmltospecifythefullyqualified
nameofthecustomResourceBundleasfollows:<base-name>com.example.project.i18n.DatabaseResourceBundle</ba
se-name>
TheactualimplementationofthisResourceBundleisfranklysomewhathacky,onlyandonlybecauseofthefollowinglimitations:
1. JSFdoesn’tallowdefiningacustomResourceBundle.Controlviafaces-config.xml.
2. ProvidingacustomResourceBundle.ControlviaSPI(SerialPeripheralInterface)asjava.util.spi.ResourceBundleControlProviderdoesn’tworkfromWARon.
3. CreatemultipleseparateDatabaseResourceBundlesubclassesforeachsinglelocaleregisteredinfaces-config.xml,suchasDatabaseResourceBundle_en,DatabaseResourceBundle_pt_BR,andDataBaseResourceBundle_hi,inordertosatisfythedefaultResourceBundle.Controlbehaviorisnotmaintenancefriendlyinlongterm.
AnadditionaladvantageofthisapproachisthatitallowsyoutoprogrammaticallyclearoutanydatabasebundlesinthecachebysimplycallingResourceBundle#clearCache().Namely,theJSFimplementationmayinturncacheitinitsApplicationimplementation,causingtheResourceBundle#clearCache()toseemtohavenoeffectatall.Mojarraisknowntodothat.
HTMLinResourceBundleThisisabadpractice.Itaddsamaintenanceburden.Forlarge
9
sectionsofcontentyou’dbetterpickamorelightweightmarkuplanguagethanHTML,suchasMarkdown. ThisisnotonlysaferastoXSS(cross-sitescripting)risksbutalsoeasierfortheusertoeditviaatextareainsomeContentManagementSystem(CMS)screens.Thisisbesttoimplementincombinationwithadatabase-basedresourcebundle.YoucouldaddanextrabooleanflagtotheTranslationmodelindicatingwhetherthevalueshouldbeparsedasMarkdown.
@Column(nullable=false)
privatebooleanmarkdown;
Then,insideTranslationService#getContents(),selectitasthethirdcolumn.
"SELECTt1.key,COALESCE(t2.value,t1.value),t1.markdown"
And,finally,intheDatabaseControl#newBundle()method,afterretrievingthecontents,youcouldpostprocessthembasedontheboolean.YoucoulduseanyJava-basedMarkdownlibraryforthis,suchasCommonMark.
staticfinalParserPARSER=Parser.builder().build();
staticfinalHtmlRendererRENDERER=
HtmlRenderer.builder().build();
...
for(Object[]translation:contents){
if((boolean)translation[2]){
translation[1]=
RENDERER.render(PARSER.parse(translation[1]));
}
}
10
11
Footnotes1https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes.
2https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2.
3https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4.
4https://en.wikipedia.org/wiki/IETF_language_tag.
5https://github.com/javaeekickoff/java-ee-kickoff-app.
6https://en.wikipedia.org/wiki/Don%27t_repeat_yourself.
7http://download.oracle.com/otn-pub/jcp/jsf-2_3-final-
eval-spec/JSF_2.3.pdf.
8
https://docs.oracle.com/javase/8/docs/api/java/text/Mess
ageFormat.html.
9https://stackoverflow.com/q/4325164/157882.
10https://en.wikipedia.org/wiki/Markdown.
11https://github.com/atlassian/commonmark-java.
(1) (2)
©BaukeScholtz,ArjanTijms2018BaukeScholtzandArjanTijms,TheDefinitiveGuidetoJSFinJavaEE8,https://doi.org/10.1007/978-1-4842-3387-0_15
15.Extensions
BaukeScholtz andArjanTijms
Willemstad,Curaçao Amsterdam,Noord-Holland,TheNetherlands
IfthereisonesingleelementorvirtueofJSF(JavaServerFaces)towhichwecanattributeitslastingforsolong,it’sprobablyitsabilitytobeextendedinalargevarietyofways.FromtheonsetJSFmadeitpossiblytohavemostofitscoreelementsreplaced,decorated,oraugmented.
Thisgaverisetoalargenumberofextensionlibrariesandprojects.IntheveryearlydaysthesewereA4J(Ajax4JSF),Tomahawk,RichFaces,thestand-aloneFaceletsproject,PrettyFaces,andmany,manymore.A4JwasmergedintoRichFaces,andRichFacesitselfwaseventuallysunsetin2016.FaceletswasincorporatedintoJSFitself,whilePrettyFacesbecamepartoftheRewriteframework.Thesedayswell-knownandactiveextensionlibrariesarePrimeFaces,OmniFaces,andBootsFaces,amongothers.Whileindividuallibrarieshavecomeandgone,themainconstantistheextensibilityofJSFfromitsfirstdaysuntilthepresent.
It’ssometimessaidthatallthoselibrariesaddressdefects
1 2
oromissionsinJSF,butthisisnotentirelyaccurate.Infact,JSFwasexplicitlydesignedtomakesuchextensionspossibleandthereforetoallow,evenstimulate,suchextensionlibrariestoappear.Forinstance,acontemporarypeertechnologyofJSF,EJB(EnterpriseJavaBeans),hadfewtonoextensionpointsand,thus,despiteitsmanyshortcomings,weneversawmuchofanecosystemflourisharoundit.
ExtensionTypesThereareacoupleofdifferentwaysbywhichtousethevariousextensionpointsinJSF.Amajordistinctionisbetweenthe“classical”approachandthe“CDI-centricapproach.”
Inthelatterapproachthere’sverylittletonothingthatJSFhastoexplicitlysupportextensibillity,asCDIhasanumberofmechanismsbuiltintosupportextendingorreplacingCDIartifacts.ThisistheplannedfutureforJSF(makingmostifnoteverythingaCDIartifact),butforthemomentJSF2.3isinanearlytransitionalphaseandonlyafewartifactsarevendedviaCDI.Table8-1inChapter8showedtheseartifacts.
ExtendingCDIArtifactsOneofthewaysCDIaugmentsorreplacesatypefullyistoprovideanalternativeproducer.WealreadysawthistechniquebeingusedinChapter13,albeitforaslightlydifferentusecase.
Theeasiestwayisifyouneedtofullyreplacethetype.Ifaugmentingisneeded,somecodeisnecessarytoobtaintheprevioustype,whichwiththecurrentversionofCDI(2.0)is
slightlyverbose.Thefollowingshowsanexamplewherewereplacethe
requestparametermapwithanewmapthathasallthevaluesoftheoriginalmap,plusanadditionalvaluethatweaddourselves:
@Dependent@Alternative@Priority(APPLICATION)
publicclassRequestParameterMapProducer{
@Produces@RequestScoped@RequestParameterMap
publicMap<String,String>producer(BeanManager
beanManager){
Map<String,String>previousMap=
getPreviousMap(beanManager);
Map<String,String>newMap=newHashMap<>
(previousMap);
newMap.put("test","myTestValue");
returnnewMap;
}
}
ThegetPreviousMap()methodis,asmentioned,somewhatverbose.It’sdefinedasfollows:
privateMap<String,String>getPreviousMap(BeanManager
beanManager){
classRequestParameterMapAnnotationLiteral
extendsAnnotationLiteral<RequestParameterMap>
implementsRequestParameterMap
{
privatestaticfinallongserialVersionUID=1L;
}
TypeMAP_TYPE=newParameterizedType(){
@Override
publicTypegetRawType(){
returnMap.class;
}
@Override
publicType[]getActualTypeArguments(){
returnnewType[]{String.class,String.class};
}
@Override
publicTypegetOwnerType(){
returnnull;
}
};
return(Map<String,String>)beanManager
.getReference(beanManager
.resolve(beanManager
.getBeans(MAP_TYPE,new
RequestParameterMapAnnotationLiteral())
.stream()
.filter(bean->bean
.getBeanClass()!=
RequestParameterMapProducer.class)
.collect(Collectors.toSet())),
MAP_TYPE,
beanManager.createCreationalContext(null));
}
}
It’sexpectedthatthetaskofobtainingthis“previous”or“original”typewillbemadeeasierinafuturerevisionofanyofthespecsinvolved.Forinstance,afutureversionofJSFwilllikelyintroduceready-to-useannotationliteralsforits(CDI)annotations,suchastheRequestParameterMapAnnotationLiteralshownhere.
Inordertotestthatthisalternativeproducerworks,considerthefollowingbackingbean:
@Named@RequestScoped
publicclassTestBean{
@Inject@RequestParameterMap
privateMap<String,String>requestParameterMap;
publicStringgetTest(){
returnrequestParameterMap.get("test");
}
publicStringgetFoo(){
returnrequestParameterMap.get("foo");
}
}
AndthefollowingFacelet:
<!DOCTYPEhtml>
<htmllang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
>
<h:head/>
<h:body>
<p>Test:#{testBean.test}</p>
<p>Foo:#{testBean.foo}</p>
</h:body>
</html>
Deployinganapplicationcontainingtheseartifactswitharequestparameterof,say,“foo=bar”,willrevealthatthenewmapindeedcontainstheoriginalrequestparametersaswellasthevaluethatweaddedourselves.
ExtendingClassicalArtifactsTheclassicalapproachtoaugmentorfullyreplaceatypein
JSFisbyinstallingafactoryforthattype.ThebasicwaysuchafactoryworksisinbroadlinesidenticaltotheCDIapproachdemonstratedabove;thefactoryreturnsanimplementationoftherequestedtypeandobtainsareferencetothe“previous”or“original”type.
BeingclassicalinJavaEEtypicallymeansXML,andindeedtheclassicalfactoryinvolvesXML.Specifically,registeringafactoryentailsusingthe<factory>elementinfaces-config.xmlandaspecificelementpertypeforwhichafactoryistobeprovided.AsofJSF2.3thefollowingfactoriesaresupported:
<application-factory>
<exception-handler-factory>
<external-context-factory>
<faces-context-factory>
<facelet-cache-factory>
<partial-view-context-factory>
<lifecycle-factory>
<view-declaration-language-factory>
<tag-handler-delegate-factory>
<render-kit-factory>
<visit-context-factory>
<flash-factory>
<flow-handler-factory>
<client-window-factory>
<search-expression-context-factory>
Nexttothese,thereareanothernumberofartifactsthatcanbereplaced/augmentedinasomewhatsimilarbutstilldifferentway;herethere’snofactoryreturningthetype,butan
implementationofthetypeisspecifieddirectly.Thisvariantisspecifiedusingthe<application>elementinfaces-config.xml.AsofJSF2.3thefollowingtypescanbereplaced/augmenteddirectly:
<navigation-handler>
<view-handler>
<resource-handler>
<search-expression-handler>
<flow-handler>
<state-manager>
<action-listener>
Notethatallofthesearesingletons.FromtheJSFruntimepointofviewthere’sonlyoneofeach,butmultipleimplementationseachaddingsomethingaresupportedbymeansofwrapping,whichthereforeformsachain(implementationAwrappingimplementationB,wrappingimplementationC,etc.).
Theabovespecificelementsusedforeachtypeimmediatelyhighlightanissuewiththeclassicalapproach;JSFhastoprovideexplicitsupportforeachspecifictypetobereplaced/augmentedinthisway.Bycontrast,theCDIapproachallowsustoprettymuchreplace/augmentanytypewithoutrequiringanyspecialsupportfromJSFotherthanthatJSFusesCDIforthatartifact.
Onthebrightside,thefactoryimplementationiscurrentlysomewhatsimplercomparedtotheCDIversion,asthe“previous”or“original”typeissimplybeingpassedtoitinitsconstructorinsteadofhavingtobelookedupusingverbosecode.
Asanexample,we’llshowhowtoaugmenttheexternal
contextfactory.Forthiswestartwiththementionedregistrationinfaces-config.xml.
<factory>
<external-context-factory>
com.example.project.ExternalContextProducer
</external-context-factory>
</factory>
Theimplementationthenlooksasfollows:publicclassExternalContextProducerextendsExternalContextFactory{
public
ExternalContextProducer(ExternalContextFactorywrapped)
{
super(wrapped);
}
@Override
publicExternalContextgetExternalContext
(Objectcontext,Objectrequest,Object
response)
{
ExternalContextpreviousExternalContext=
getWrapped().getExternalContext(context,
request,response);
ExternalContextnewExternalContext=
new
ExternalContextWrapper(previousExternalContext){
@Override
publicStringgetAuthType(){
return"OurOwnAuthType";
}
};
returnnewExternalContext;
}
}
Thereareafewthingstoobservehere.Firstofall,everyfactoryofthiskindhastoinheritfromapre-describedparentfactory,whichinthiscaseisExternalContextFactory.Second,there’sanimplicitcontractthatmustbefollowed,andthat’simplementingtheconstructorexactlyasshownintheexample.Thatis,addapublicconstructorwithasingleparametertheexactsametypeasthesuperclass,andpassthisparameterontothesuperconstructor.ThisinstanceisthenavailableinothermethodsusingthegetWrapped()method.
Testingthatthisindeedworksisrelativelyeasy.WeuseasimilarbackingbeanaswiththeCDIversion:
@Named@RequestScoped
publicclassTestBean{
@Inject
privateExternalContextexternalContext;
publicStringgetAuth(){
returnexternalContext.getAuthType();
}
}
AndthefollowingFacelet:
<!DOCTYPEhtml>
<htmllang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
>
<h:head/>
<h:body>
<p>Test:#{testBean.auth}</p>
</h:body>
</html>
Astheexternalcontextisalsoatypethat’sinjectableviaCDI,theobservantreadermaywonderwhathappenswhenbothaCDIalternativeproducerandaclassicfactoryareprovidedforthattype.Theansweristhatthisisstrictlyspeakingnotspecified(thusundefinedbehavior),yetinpracticeit’sstronglyimpliedthattheclassicfactoryisusedasthesourcetoultimatelygettheexternalcontextfrom.ThismeansthatanalternativeproducerforExternalContextwillonlyaffectthedirectinjectionofExternalContext,andnotanysituationwhenthistypeisobtainedinanyotherway,forinstance,bycallingFacesContext#getExternalContext().Thisissomethingusersshouldclearlybeawareof.Theexpectationis,though,thatafuturerevisionofthespecwillmakeaCDIproducertheinitialsource.
Plug-insAdifferenttypeofextendingthatJSFoffersnexttothealternativeproducersandfactoriesiswhat’sessentiallyaplug-in.Here,nocoreJSFtypeisreplacedoraugmented,butanadditionalfunctionalityisaddedtosomepartoftheruntime.Mostoftheseadditions,therefore,havetodeclareinsomewaywhatitistheyareexactlyadding,whichisdifferentfrom
thefactorieswhichjustprovidedanimplementationoftypeXorY.
Plug-insareaddedaselementsofthe<application>elementinfaces-config.xml,justassomeofthefactory-liketypesmentionedabove.Thefollowingaresupported:
<el-resolver>
<property-resolver>(deprecated)
<variable-resolver>(deprecated)
<search-keyword-resolver>
WealreadysawanexampleoftheSearchKeywordResolverinthesection“CustomSearchKeywords”inChapter12.Characteristicforthatonebeingaplug-inwasthemethodisResolverForKeyWord(),bywhichtheplug-incouldindicateforwhichkeyword,orkeywordpattern,itwouldoperate.
We’lltakealookatoneotherexamplehere,namely,theELresolver.ThepropertyresolverandvariableresolverarebothdeprecatedandhavebeenreplacedbytheELresolver.TheELresolveritselfisnotaJSF-specifictypebutoriginatesfromtheExpressionLanguage(EL)spec.Thisspecdoes,however,haveimportantpartsofitsoriginsinJSF.
TheELresolverallowsustointerprettheso-calledbaseandpropertyofanexpressioninacustomway.Consideringtheexpression#{foo.bar.kaz},then“foo”isthebaseand“bar”isthepropertywhenresolving“bar”,while“bar”isthebaseand“kaz”isthepropertywhenresolving“kaz”.Perhapssomewhatsurprisingatfirstisthatwhen“foo”isbeingresolved,thebaseisnulland“foo”isthe
property.Inpractice,addingacustomELresolverisnotoften
needed,andwecanoftengetbywithsimplydefininganamedCDIbeanthatdoeswhatwerequire.CustomELresolverscouldcomeintoplaywhenJSFisintegratedinacompletelydifferentenvironmentthough,onewherewe’dlikeexpressionstoresolvetoacompletelydifferent(managed)beansystem.Evenso,aCDI-to-other-beans-bridgemightbeabetteroptioneventhere,butit’sperhapsgoodtoknowacustomELresolverisoneothertoolwehaveinourarsenal.
Anyway,todemonstratewhatanELresolvercandowe’llshowanexamplewherethebaseofanELexpressionisinterpretedasapattern,somethingwecan’tdodirectlywithanamedCDIbean.
ThefollowingshowsanexampleELresolver:
publicclassCustomELResolverextendsELResolver{
protectedbooleanisResolverFor(Objectbase,Object
property){
returnbase==null
&&propertyinstanceofString
&&((String)property).startsWith("dev");
}
@Override
publicObjectgetValue
(ELContextcontext,Objectbase,Objectproperty)
{
if(isResolverFor(base,property)){
context.setPropertyResolved(true);
returnproperty.toString().substring(3);
}
returnnull;
}
@Override
publicClass<?>getType
(ELContextcontext,Objectbase,Objectproperty)
{
if(isResolverFor(base,property)){
context.setPropertyResolved(true);
returnString.class;
}
returnnull;
}
@Override
publicClass<?>getCommonPropertyType
(ELContextcontext,Objectbase)
{
returnbase==null?getType(context,base,null):
null;
}
@Override
publicbooleanisReadOnly
(ELContextcontext,Objectbase,Objectproperty)
{
returntrue;
}
@Override
publicvoidsetValue
(ELContextcontext,Objectbase,Objectproperty,
Objectvalue)
{
//NOOP;
}
@Override
publicIterator<FeatureDescriptor>getFeatureDescriptors
(ELContextcontext,Objectbase)
{
returnnull;
}
}
IfwenowusethefollowingFacelet<!DOCTYPEhtml><htmllang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
>
<h:head/>
<h:body>
<p>Test:#{devThisIsDev}</p>
</h:body>
</html>
we’llsee“Test:ThisIsDev”beingprintedwhenrequestingit.What’shappeninghereisthatthecustomELresolverhandleseverynamethatstartswith“dev.”
DynamicExtensionsThepreviousexamplesweremostlyaboutregisteringtheextensionsthatweneededstatically,e.g.,byregisteringafactoryorthetypedirectlyinfaces-config.xml.Factoriesgiveusanopportunityforsomedynamicbehavior.Thatis,atthepointthefactoryiscalledwecandecidewhatnewtype(ifany)toreturn.
Forevenmoredynamicbehaviorit’sfrequentlyrequiredtobeabletodynamicallyaddthefactories,orinCDItodynamicallyaddtheproducers.CDIhasanelaborateSPI(serverproviderinterface)forthis(simplycalled“CDIExtensions”)whichare,however,somewhatoutsidethescopeofthisbook.
Forclassicfactoriesandactuallyeverythingthat’sinfaces-config.xml,there’sasomewhatlow-levelmethodtoaddthesedynamically:theApplicationConfigurationPopulator,whichwe’lldiscussnext.
APPLICATIONCONFIGURATIONPOPULATORTheApplicationConfigurationPopulatorisamechanismtoprogrammaticallyprovideanadditionalfaces-config.xmlfile,albeitbyusingtheXMLDOM(DocumentObjectModel)API(applicationprogramminginterface).ThisDOMAPIcanbeslightlyobscuretouse,andthepoweroftheApplicationConfigurationPopulatorislimitedbyitsabilitytoonlyconfigure.There’snoSPIinJSFtodirectlymodifyotherfaces-config.xmlatthislevel.
Themechanismworksbyimplementingtheabstractclassjavax.faces.application.ApplicationConfigu
rationPopulatorandputtingthefullyqualifiedclassnameofthisinaMETA-INF/services/javax.faces.application.Appli
cationConfigurationPopulatorfileofaJARlibrary.
Todemonstratethis,we’llcreateanotherversionoftheExternalContextProducerthatwedemonstratedearlier,thistimeusingthementionedApplicationConfigurationPopulator.Forthiswetakethesamecode,removethefaces-config.xmlfile,andaddtheMETA-INF/servicesentryaswellasthefollowingJavaclass:
publicclassConfigurationProvider
extendsApplicationConfigurationPopulator
{
@Override
publicvoidpopulateApplicationConfiguration(Document
document){
Stringns=
document.getDocumentElement().getNamespaceURI();
Elementfactory=document.createElementNS(ns,
"factory");
ElementexternalContextFactory=
document.createElementNS(ns,"external-context-
factory");
externalContextFactory.appendChild(
document.createTextNode(
ExternalContextProducer.class.getName()));
factory.appendChild(externalContextFactory);
document.getDocumentElement().appendChild(factory);
}
}
Acaveatisthatsincethisusesthejava.util.ServiceLoaderunderthehood,itreallyonlyworkswhenConfigurationProviderandExternalContextProducerarepackagedtogetherinanactualJARlibraryplacedintheWEB-INFliboftheWAR,insteadofjustbeingputdirectlyintheWAR.
THEAPPLICATIONMAINCLASSAbovewediscussedtheApplicationConfigurationPopulator,whichaswesawisactuallyafaces-config.xmlproviderofsorts.Thismeansitworkswithfullyqualifiedclassnamesandelementsthatarestilltextinnature.
JSFfeaturesavarietyofsomewhatmoretraditional
programmaticAPIsaswell,withperhapsthemostwell-knownofthembeingthejavax.faces.application.Applicationmainclass,whichisamongothersaholderforthesamesingletonsthatwementionedpreviouslyinthesection“ExtendingClassicalArtifacts.”Forcompletenesswe’llrepeatthislisthere.
javax.faces.application.NavigationHandler
javax.faces.application.ViewHandler
javax.faces.application.ResourceHandler
javax.faces.component.search.SearchExpressionHandler
javax.faces.flow.FlowHandler
javax.faces.application.StateManager
javax.faces.event.ActionListener
AllofthesehavecorrespondingsettersontheApplicationclass.Forinstance,thefollowingshowstheJavadocandmethoddeclarationforActionListener:/***<p>
*Setthedefault{@linkActionListener}tobe
registeredforall
*{@linkjavax.faces.component.ActionSource}
components.
*</p>
*
*@paramlistenerThenewdefault{@link
ActionListener}
*
*@throwsNullPointerException
*if<code>listener</code>is
<code>null</code>
*/
publicabstractvoidsetActionListener(ActionListener
listener);
Likewise,theApplicationclassalsohas“add”methodsfortheplug-intypeswementionedinthesection“Plug-ins.”
javax.el.ELResolver
javax.faces.el.PropertyResolver(deprecated)
javax.faces.el.VariableResolver(deprecated)
javax.faces.component.serarch.SearchKeywordResolver
AdifficultywithusingtheApplicationclasstosetthesesingletonsis,firstofall,thatit’stimingsensitive.Thismeanswecanonlysetsuchclassesfromacertainpoint,whichisobviouslynotbeforethepointthattheApplicationitselfisavailable,andforsomesingletonsnotuntilthefirstrequestisserviced.Thisfirstrequestisasomewhatdifficultpointtotrack.
Specifically,theresourcehandler,viewhandler,flowhandler,andstatehandler,uhhh,statemanager,can’tbesetanymoreafterthefirstrequest,whiletheELresolverandsearchkeywordresolvercan’tbeaddedeitheraftersaidfirstrequest.
Todemonstratethiswe’lladdthecustomELresolveragainthatwedemonstratedabove,butinamoredynamicwaynow.Todothis,weremovetheELresolverfromourfaces-config.xmlfileandaddasystemlistenerinstead.Ourfaces-config.xmlfilethenlooksasfollows:<application>
<system-event-listener>
<system-event-listener-class>
com.example.project.ELResolverInstaller
</system-event-listener-class>
<system-event-class>
javax.faces.event.PostConstructApplicationEv
ent
</system-event-class>
</system-event-listener>
</application>
Indeed,thisdoesn’tgetridoftheXMLand,infact,it’sevenmoreXML,butreducingorgettingridofXMLisnotthemainpointhere,whichistheabilitytoregistertheELresolverinamoredynamicway.
Thesystemeventlistenerthatwejustregisteredherelooksasfollows:
publicclassELResolverInstallerimplements
SystemEventListener{
@Override
publicbooleanisListenerForSource(Objectsource){
returnsourceinstanceofApplication;
}
@Override
publicvoidprocessEvent(SystemEventevent){
Applicationapplication=(Application)
event.getSource();
application.addELResolver(newCustomELResolver());
}
}
Whatwehavehereisasystemeventlistenerthatlistenstothe
PostConstructApplicationEvent.Thisisgenerallyagoodmomenttoaddplug-insliketheELresolver.TheApplicationinstanceisguaranteedtobeavailableatthispoint,andrequestprocessinghasn’tstartedyet,sowe’resurelyintimebeforethefirstrequesthasbeenhandled.
LocalExtensionandWrappingInsomecases,wedon’twanttooverride,say,aviewhandlerglobally,butonlyforalocalinvocationof,typically,amethodinacomponent.JSFgathersforthisbypassingontheFacesContext,whichcomponentstouseasthemainentrypointfromwhichtogetprettymuchallotherthings.ComponentsareforJSF2.3notCDIartifactsorotherwiseinjectable,sotheCDIapproachforthemomentdoesn’tholdforthem.
Localextensioncanthenbedonebywrappingthefacescontextandpassingthatwrappedcontexttothenextlayer.NotethattheServletspecusesthesamepatternwheretheHttpServletRequestandHttpServletResponsecanbewrappedbyafilterandpassedontothenextfilter,whichcanwrapitagainandpassittoitsnextfilter,etc.
Toillustratethis,supposethatforacertaincomponentwe’dliketoaugmenttheactionURLgenerationusedby,amongothers,formcomponentsinsuchawaythat“?foo=bar”isaddedtothisURL.IfwedothisbygloballyoverridingtheviewhandlerallcomponentsandothercodeusingtheviewhandlerwouldgettoseethisURL,whileherewe’donlywantthisforthisveryspecificcomponent.
Toachievejustthis,weusewrappinghereasillustratedinthefollowingcomponent:
@FacesComponent(createTag=true)
publicclassCustomFormextendsHtmlForm{
@Override
publicvoidencodeBegin(FacesContextcontext)throws
IOException{
super.encodeBegin(newActionURLDecorator(context));
}
}
ImplementingthewrapperwithoutsupportfromJSFwouldbeasomewhattedioustask,tosaytheleast,asthepathfromFacesContexttoViewHandlerisafewcallsdeepandtheclassesinvolvedhavealargeamountofmethods.LuckilyJSFgreatlyeasesthistaskbyprovidingwrappersformostofitsimportantartifacts,withaneasy-to-useconstructor.
Thepatternusedhereisthatthetop-levelclass(ActionURLDecoratorfromtheexampleabove)inheritsfromFacesContextWrapperandpushestheoriginalfacescontexttoitssuperclass.Thenthepathofintermediateobjectsisimplementedbyoverridingtheinitialmethodinthechain(getApplication()here),andreturningawrapperforthereturntypeofthatmethod,withthesuperversionofitpassedintoitsconstructor.Thiswrapperthendoesthesamethingforthenextmethodinthechain,untilthefinalmethodisreachedforwhichcustombehaviorisrequired.
Thefollowinggivesanexampleofthis:publicclassActionURLDecoratorextendsFacesContextWrapper{
publicActionURLDecorator(FacesContextcontext){
super(context);
}
@Override
publicApplicationgetApplication(){
returnnew
ApplicationWrapper(super.getApplication()){
@Override
publicViewHandlergetViewHandler(){
returnnew
ViewHandlerWrapper(super.getViewHandler()){
@Override
publicStringgetActionURL
(FacesContextcontext,String
viewId)
{
Stringurl=
super.getActionURL(context,viewId);
returnurl+"?foo=bar";
}
};
}
};
}
}
WiththesetwoclassespresentintheJSFapplication,nowconsiderthefollowingFaceletthatmakesuseofourcustomformcomponent:
<!DOCTYPEhtml>
<htmllang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:test="http://xmlns.jcp.org/jsf/component"
>
<h:head/>
<h:body>
<test:customFormaction="index">
<h:commandButtonaction="index"value="GoIndex"
/>
</test:customForm>
</h:body>
</html>
RememberthatnoXMLregistrationisneededforthecustomcomponentwhenit’sannotatedwith@FacesComponent(createTag=true),andthatitsXMLnamespacedefaultstohttp://xmlns.jcp.org/jsf/componentanditscomponenttagnametothesimpleclassname.(SeealsoChapter11.)IfwerequesttheviewcorrespondingtothisFaceletandpressthebutton,we’dindeedsee“?foo=bar”appearingaftertheURL,meaningthatourlocalextensionoftheviewhandlerviaachainofwrappershasworkedcorrectly.
IntrospectionAnimportantaspectofbeingabletoextendaframeworkcorrectlyisnotonlytobeabletoutilizeextensionpointsbutalsotobeabletointrospecttheframeworkandqueryitforwhatartifactsorresourcesithasavailable.
OneoftheplaceswhereJSFprovidessupportforintrospectionisitsabilitytorevealwhichviewresourcesarepresentinthesystem.RememberthatinJSF,viewslike,forexample,Faceletsareabstractedbehindtheviewhandler,whichinturnmanagesoneormoreviewdeclarationlanguage
(VDL)instances.AVDL,alsocalledatemplatingengine,hastheabilitytoreaditsviewsviatheresourcehandler.Aswe’veseeninthischapter,allthesethingscanbeaugmentedorevenfullyreplaced.
Thisspecificallymeansthatviewscancomefromanywhere(e.g.,fromthefilesystem(mosttypical))butcanalsobegeneratedinmemory,beloadedfromadatabaseorfetchedoverthenetwork,andmuchmore.Also,thesimplephysicalfiletologicallyviewnamemappingsuchasthatusedbyFaceletsdoesn’thavetoholdforotherviewdeclarationlanguagesatall.
Togetherthismeansthatwithoutanexplicitintrospectionmechanismwheretheviewhandler,VDL,andresourcehandlercanbeaskedwhichviews/resourcestheyhaveavailable,wewouldnotbeabletoreliablyobtainafulllistofviews.
Suchlistofviewsisneeded,forexample,whenwewanttoutilizeso-calledextensionlessURLs,whichareURLswithoutanyextensionsuchas.xhtmlor.jsf,andwithoutanyextrapathmappingpresentsuchasfaces*.LackinganyhintinsidetheURLitself,theServletcontainerontopofwhichJSFworkshastohavesomeotherwayofknowingthatacertainrequesthastoberoutedtothefacesservlet.
AparticularlyelegantwaytodothisisbyutilizingServlet’s“exactmapping”feature,whichisavariantofURLmappingwhereanexactnameinsteadofapatternismappedtoagivenservlet,whichinthiscasewouldbethefacesservlet.SincetheServletspechasanAPIfordynamicallyaddingServletmappings,andJSFhasanAPItodynamicalintrospectwhichviewsareavailable,weprettymuchonly
havetocombinethesetwotoimplementextensionlessURLs.Thefollowingshowsanexampleofhowtodothis:
@WebListener
publicclassExtensionLessURLsimplements
ServletContextListener{
@Override
publicvoidcontextInitialized(ServletContextEvent
event){
FacesContextfacesContext=
FacesContext.getCurrentInstance();
event.getServletContext()
.getServletRegistrations()
.values()
.stream()
.filter(servlet->servlet
.getClassName().equals(FacesServlet.clas
s.getName()))
.findAny()
.ifPresent(facesServlet->facesContext
.getApplication()
.getViewHandler()
.getViews(facesContext,"/",
ViewVisitOption.RETURN_AS_MINIMAL_IM
PLICIT_OUTCOME)
.forEach(view->
facesServlet.addMapping(view)));
}
}
Whathappenshereisthatwefirsttrytofindthefacesservlet,whichincidentallyisanotherexampleofintrospection,this
timeintheServletspec.Iffound,weasktheviewhandlerforallviews.Asmentionedabove,thiswillinternallyintrospectalltheavailableviewdeclarationinstances,whichinturnmayintrospecttheresourcehandler.The“RETURN_AS_MINIMAL_IMPLICIT_OUTCOME”parameterisusedtomakesureallviewsarereturnedintheirminimalformwithoutanyfileextensionsorothermarkersappended.Thisisthesameformthatcanbereturnedfrom,forexample,actionmethodsorusedwiththeactionattributeofcommandcomponents.
Havingobtainedthestreamofviewsintherightformat,wedirectlyaddeachofthemasexactmappingtothefacesservletthatwefoundearlier.
Forexample,supposewehaveaFaceletviewinthewebrootinafolder/foonamedbar.xhtml.ThengetViews()willreturnastreamwiththestring“foobar”.Whenthefacesservletismappedtothis“foobar”,andassumingtheJSFapplicationisdeployedtothecontextpath/testondomainlocalhostport8080,wecanrequesthttp://localhost:8080/testfoobartoseetherenderedresponseofthatFacelet.
Notethateventhoughit’srelativelysimpletoachieveextensionlessURLsinJSFthisway,it’sstillsomewhattedioustohavetodothisforeveryapplication.It’sexpectedthatanextrevisionofJSFwillsupportthisviaasinglesetting.
Index
A1. Actionlistenermethod2. ADFFaces3. Ajax
1. action2. applyrequestvaluesphase3. behaviorlistenermethods4. ClientBehaviorHolder5. executeandrenderattributes6. <f:ajax>tag7. @formkeyword8. <h:form>9. implementingclass10. invokeapplicationphase11. javax.faces.ViewState12. JSFlifecycle13. lifecycle14. messagecomponent15. navigationin16. on[event]attributes17. renderresponsephase18. supportingeventtypes19. valueChange
4. Ajaxexceptionhandling
1. Ajaxrequest
2. application-widecustomizations3. businesslogic4. ExceptionHandler5. handleAjaxException()6. HTTPresponse7. JavaScriptalert8. Productionstage9. UIViewRootinstance10. unhandledexceptionevents
5. AlexanderSmirnov’sTelamonframework6. Applicationprogramminginterface(API)7. Applicationservers8. Authenticationmechanism
1. AuthenticationMechanismDefinitionannotation2. caller-initiatedauthentication
See Caller-initiatedauthentication
1. CustomFORM2. @CustomFormAuthenticationMechanismDefinitionannotation3. @FacesConfigannotation4. FORM5. customJSFcode
1. actionmethod2. backingbean3. continueAuthentication()method4. loginpage
6. loginToContinueattribute
B1. Backingbeans
1. class
1. editing2. JBossToolsplug-in
2. JSFvendedtypes,injecting3. layers4. managedbean
See Managedbeans
1. MVCframework2. namingconventions
1. BeanManager#fireEvent()method2. BeanValidationAPI
1. commaseparatedstring2. contextparameter3. customconstraintannotation4. custommessages5. groupsattribute6. inJavacode7. JPA-managed8. NotNull.class9. UIInputcomponents10. validationerror11. validationGroupsattribute12. web.xml
3. Bindingattribute4. Byrolesecurityconstraints
C1. Cachebusting
2. Caller-initiatedauthentication
1. continueAuthentication()method2. Flash3. login-to-continueservice4. SecurityContext#authenticate()method5. SUCCESScase
3. CDI-centricapproach
1. backingbean2. getPreviousMap()method3. RequestParameterMapProducerclass
4. Classicalapproach
1. CDIversion2. externalcontextfactory3. Facelet4. factories5. replace/augment
5. Commandcomponents
1. actionlistenermethod2. argumentpassingapproach3. bindingattribute4. FontAwesomeicon5. JavaScriptwithhard-codedvariables6. loadsandrendersdatatable7. managedbeanactionmethod8. Mojarraspecificcase9. namespacedfunction10. paramsproperty11. passadditionalrequest12. targetmethod13. UXconsensus
6. Componentsystemevents
1. javax.faces.event.ComponentSystemEventabstractclass2. javax.faces.event.ComponentSystemEventListenerinterface3. onload()method4. @PostConstruct-likebehavior5. PostConstructViewMapEvent6. PreDestroyViewMapEvent7. PreInvokeApplicationEvent8. Rendererclass9. subscribelisteners10. UIComponent#subscribeToEvent()11. YourComponent12. YourListenerclass
7. Componenttree
1. coretags2. #{dynamicForm}3. HTML
See HTMLcomponents
1. HTTPrequest2. JSTLCoreTags3. lifecycle
1. invokeapplicationphase2. processvalidationsphase3. requestvaluesphase4. restoreviewphase
4. modelvaluesphase5. phaseevents6. PostAddToViewEvent7. PreRenderViewEvent8. renderresponsephase9. steps10. TextField#populate()11. XML
1. Compositecomponents
1. Ajaxlistener2. backingbean3. backingcomponent4. caveat5. clientID6. defaultevent7. encodeBegin()method8. eventattribute9. getSubmittedValue()method10. implementation11. interface12. JSF2.013. JSFphases14. LocalDateproperty15. NamingContainer16. recursive17. space-separatedcollection18. UIComponentinstance19. UIInputsuperclass20. XMLnamespace
2. Container-initiatedauthentication3. Contextpath4. Converters
1. beanproperty2. custom
1. baseentityservice2. converterIdattribute3. emptystring4. equals()andhashCode()methods5. forClassconverterforjava.lang.String6. genericconverter7. getId()8. GETrequestparameter9. LocalDateConverter10. managedattribute
11. NumberConverter12. Object#equals()method13. plainJavacode14. ProductConverter15. Productentity16. productID17. usecase18. webapplication
3. EL4. <f:convertDateTime>
1. backingbean2. Chromebrowser3. HTML5dateandtimeinputs4. ISO8601format5. supportedvalues
5. <f:convertNumber>
1. currencysignpattern2. NumberFormatinstance3. standardnumberformatpattern4. tags
6. interface7. ValueHoldercomponents
5. Coretags
1. JSTL
See JSTLCoreTags
1. standardize
1. Cross-siterequestforgery(CSRF)
1. bankURL2. bar.xhtml3. faces-config.xml4. HTTPheaders5. maliciousrequests6. protection
2. Cross-sitescripting(XSS)
1. backingbean2. commoncontext3. Faceletand<h:outputText>component4. ServletContextListener5. sessionIDcookie6. URLs
3. Customcomponents
1. componentfamily2. componenttype3. customtaghandlers4. distributableJAR,packaging5. existingcomponent,extending6. existingrenderer,extending7. newcomponentandrenderer
1. backwardcompatibility2. DataModelabstractclass3. EL4. encodeBegin()method5. encodeChildren()method6. encodeEnd()method7. @FacesComponent8. @FacesDataModelannotation9. officialcomponentattribute10. UIComponentsubclass11. UIDatasubclass12. valueattribute13. XMLnamespace
8. renderertype9. resourcedependencies
D1. Databaseidentitystore2. Datasource3. Doublesubmit
E1. EarlyAccessSpecification2. Eclipse
1. configuresettings2. Faceletsfile3. GlassFish
1. location2. Payaraservername3. selecttools
4. install5. installJBossTools6. JavaEEAPI7. Mavenproject
1. createsimpleproject2. GAVin3. JavaEE84. JBossToolsplug-in5. JPAfacetconfiguration6. JSFcapabilitiesconfiguration7. Markersview
8. ModifyFacetedProjectwizard9. Oxygen210. pom.xmlfile11. ProjectExplorerview12. selecting13. ServletAPI14. settings15. yellowwarningbar
8. PATH9. ProjectExplorerview10. serversviewof11. workbench
3. EJBExceptionhandling4. E-mailaddresscolumn5. E-mail-basedsignup6. EnterpriseJavaBeans(EJB)7. Exadel8. ExadelVisualComponentPlatform9. ExpressionLanguage(EL)10. Extensions
1. ApplicationConfigurationPopulator2. applicationmainclass
1. ActionListener2. eventlistener3. faces-config.xml4. plug-ins
3. CDI-centricapproach
See CDI-centricapproach
1. classicalapproach
See Classicalapproach
1. customformcomponent2. dynamic3. introspection
1. extensionlessURLs2. VDL
4. localextension5. plug-ins
1. ELresolver2. factory-liketypes
6. wrapping
1. ExternalContext#encodeWebsocketURL()method
F1. Facelets2. FaceletsfitsJSFlikeaglove(article)3. Faceletstemplating
1. compositecomponents
See Compositecomponents
1. implicitELobjects2. SPA3. tagfiles
1. boilerplatecode2. client3. customizing4. duplicatecomponentID5. HTML5inputfields
6. implementation7. insideLabel8. mastertemplatefiles9. <method-signature>10. <required>property11. taglibfile12. <ui:include>13. view-scopedmodel
4. templatecompositions
1. client2. compiler3. finalHTMLoutput4. getContextPath()method5. mastertemplatefile6. xmlnsattribute
5. templatedecorations6. XHTML
1. HTML52. JurassicIE63. SAXparser4. webdevelopers
1. FacesServlet2. File-basedinputcomponent3. FORMauthenticationmechanism
G1. GETforms
1. <f:metadata>2. invokeapplicationphase
3. requestparametermap4. searchform5. submittedvalue6. UIViewAction7. UIViewParameter8. usingtemplating
2. GlassFish
1. location2. Payaraservername3. selecttools
H1. H2database
1. configuredatasource2. createEJBservice3. HelloWorld
1. gettersandsetters2. savemessage
4. JPA
1. configuration2. createentity
5. pom.xml
2. HelloWorld
1. <application>element2. bracenotation3. changeactivelocale
4. commonkeys5. commonprefix6. config.xmlfile7. createbackingbeanclass8. Faceletsfile9. Faceletstemplate10. gettersandsetters11. HTTPAccept-Languageheader12. labelattribute13. langattribute14. localeproperty15. localizeenums16. mapkeys17. <message-bundle>entry18. newmessage19. overridedefaultmessage20. page-specificentries21. parametersresource22. Payaraserver
1. AddandRemovewizard2. automaticpublish3. inChromebrowser4. contextpath5. root6. startserver7. web.xml
23. ResourceBundle
1. database-based2. HTMLin
24. savemessage25. XHTMLtags
3. Hiddeninputfield4. HTMLcomponents
1. input2. JSFpage3. output4. Sincecolumn5. tag6. UIComponentsuperclass7. valuetypecolumn8. Viewpagesource
I1. ICEbrowserbeans2. ICEsoft3. Identitystore
1. application-specificuserdata2. authenticationmechanism3. database4. groupsandroles5. SQLstatement
4. Immediateattribute5. Implicitnavigation6. ImprovingJSFbyDumpingJSP(article)7. Inputcomponents
1. file-based
See File-basedinputcomponent
1. HTML2. text-based
See Text-basedinputcomponents
1. InternetAssignedNumbersAuthority(IANA)2. Invokeapplicationphase
3. IOExceptionhandling
J,K1. JACC2. JASPIC3. JavaEE84. JavaEEAPI5. JavaEESecurity
1. API2. customprincipal3. excludedconstraints4. identitystore
See Identitystore
1. JACC2. JASPIC3. logout4. remembermeservice5. renderedattribute
1. finer-grainedname2. hasAccessToWebResource()3. implicitobject#{request}4. “**”role5. SecurityContext6. utilitymethods7. web.xml
6. byroleconstraints7. Servletspec8. sourceexposureprotection
1. compositecomponents2. FaceletsandJSPfiles
3. FacesServlet4. frontcontroller5. preventionmethods6. *.xhtml
9. SPIs10. uncheckedconstraints
1. JavaNamingandDirectoryInterface(JNDI)2. JavaOne20153. JavaPersistenceAPI(JPA)
1. configuration2. createentity3. entities4. facetconfiguration5. implementation
4. JavaSEJDK5. JavaServerFaces(JSF)
1. ApacheStruts2. conflicts3. developer4. 1.2EG5. ExpressionLanguage6. ICEsoft7. managedbeans8. OmniFaceslibrary9. OurFaces10. PrimeFaces11. viewhandler
6. JavaServerPages(JSP)7. JBossTools8. JSF2.0
1. CDIspec2. Facelets
3. goals4. javax.faces.bean.RequestScopedannotation5. PrimeFaces6. TheTrap7. viewscope
9. JSF2.210. JSF2.311. JSPStandardTagLibrary(JSTL)12. JSR-12713. JSTLCoreTags
1. backingbeanproperty2. calculatesuminloop3. componenttree4. ELscope5. Facelets6. IllegalStateException7. input.xhtmltagfile8. itementity9. namespaceURI10. specification11. viewbuildtime12. viewrendertime
L1. Labelcomponent
M1. Managedbeans
1. CDI
2. eagerinitialization3. ELcontext4. initializationanddestruction5. JavaEE66. JSF2.07. JSFdeveloper8. scopes
1. @ApplicationScoped2. tochoose3. @ConversationScoped4. @Dependent5. @FlashScoped6. @FlowScoped7. @RequestScoped8. @SessionScoped9. @ViewScoped
2. Messagecomponents3. Message-drivenbean(MDB)4. Modelentity5. Modelvaluesphase6. Model-view-controller(MVC)7. Mojarra
1. <f:websocket>2. specificcase
8. MyFaces
N1. Navigation
O1. OmniFaceslibrary2. Opensourceimplementations3. OurFaceslibrary4. Outputcomponents
1. dataiterationcomponent
1. add/removerows2. dynamiccolumns3. editable4. ELvalueexpression5. listofproducts6. Productentity7. selectrowsin<h:dataTable>8. valueattributeofUIData9. varattribute
2. document-based3. navigation-based4. panel-based
1. <h:panelGrid>2. inlineelements3. iterationindex4. JSFcomponent5. JSTL6. <ui:instructions>7. userprofile
5. pass-throughelements6. resourcecomponents
See Resourcecomponents
1. text-based
1. Facelets2. <h:outputText>3. JSF4. malicioususer5. managedbeanproperty6. Markdowninterface7. MarkdownListenerentitylistener8. Messageentity9. predefinedhuman-friendlymarkupformat
1. Oxygen2
P,Q1. Pass-throughelement2. PatentpendingDirect-to-DOM™3. Payara
1. GlassFish2. installing3. JAVA_HOME4. version
4. @Phoneconstraint,custom5. Portlet-basedwebapplications6. Post-Redirect-Getpattern7. PrimeFaceslibrary8. Processvalidationsphase9. PushContextinterface
R1. Referenceimplementation(RI)2. Remembermeservice
1. authenticationmechanism2. CustomFormAuthenticationMechanism3. HttpAuthenticationMechanismWrapper4. @RememberMeannotation5. token
3. Renderedattribute
1. finer-grainedname2. hasAccessToWebResource()3. implicitobject#{request}4. “**”role5. SecurityContext6. utilitymethods7. web.xml
4. Renderresponsephase5. Requestscope6. Requestvaluesphase7. Resourcecomponents
1. advantages2. bsf3. cachebusting4. classpath5. createResource()method6. DynamicResourceListener7. FacesServlet8. <h:outputScript>9. JSFcomponents10. JSF2.3webapplication11. nameattribute12. OmniFaces13. physicalresourcefiles14. PrimeFaces15. rendererclass16. renderingorder17. @ResourceDependencyannotation18. ResourceHandler19. SystemEventListener
20. webapplication
8. Restoreviewphase9. RichFaces
S1. Searchexpressions
1. absolutehierarchicalIDs2. relativelocalIDs3. searchkeywords
See Searchkeywords
1. Searchform2. Searchkeywords
1. custom2. standard
3. Selectioncomponents
1. demonstratedapproach2. HTMLmarkup3. JSF2.24. JSF2.35. newgroupattribute6. UISelectBooleancomponent7. UISelectManycomponent
4. SelectItemtags
1. countrymodelentities2. ELproperties3. <f:selectItem>
4. <h:selectOneMenu>5. itemLabelattribute6. itemValueattribute7. mapkey8. noSelectionOptionattribute9. SelectItemGroup
5. Serverproviderinterfaces(SPIs)6. Servletcontainers7. Servletspec8. Sessionscope9. SinglePageApplication(SPA)10. SkinServlet11. someLongRunningProcess()method12. Soteria13. Spring1.0.MyFaces14. Statelessforms15. Strutswebframework
T1. Text-basedinputcomponents
1. basicusageexample2. Chrome3. hiddeninputfield4. idattribute5. invokeapplicationphase6. Portlet-basedwebapplications7. processvalidationsphase8. UIInput#decode()method9. UIInput#updateModel()method10. UIInput#validate()method
2. TheTrap
U1. UIInputsuperclass2. UnifiedExpressionLanguage(UEL)3. Userinterface(UI)4. UserInterfaceXML(UIX)
V1. Validators
1. BeanValidationAPI
1. commaseparatedstring2. contextparameter3. customconstraintannotation4. groupsattribute5. inJavacode6. JPA-managed7. NotNull.class8. UIInputcomponents9. validationerror10. validationGroupsattribute11. web.xml
2. convertsubmittedvaluetostring3. e-mail-basedsignup4. minimumandmaximumattributes5. providedbyJSF6. requiredattribute
2. Viewbuildtime3. Viewdeclarationlanguage(VDL)4. ViewExpiredExceptionhandling5. ViewHandler#getWebsocketURL()method
6. Viewrendertime7. Viewscope8. Viewstate
W1. Webfragment2. Webparametertampering3. WebSocketpush
1. channeldesignhints2. configuration3. f:websocketimplementation4. one-timepush5. scopesandusers6. sessionandviewexpiration,detecting7. site-widepushnotifications8. statefulUIupdates9. trackofactivesockets10. usage
X,Y,Z1. XHTML2. XSS
See Cross-sitescripting(XSS