wBlec<}?#_{bEIU*2M*Jy~~_(b8srL+lc6@Yk2{9zgQ zoR*_?40f+GA|PbP9`@Icis_cTYz3Zz0)ykyU>Ei=@uLanYl~}9)Z6u*)Rqs^3bR#P zQA!|v`_kaxQa9n9;N!i%yD;X2-;VaX;6~5>$|h;7esRQOWL9@D8;b!=X=qp zw@bQfja aR7O78^5|!mi(M2FBRP z4}%nS1H_?+QsOlkQZXpwt|Y{SHDx~bVxA3(<>;tqw2(b#%N0%9>m;{i&F@P6h}IV( zHQ>Xo{31iXBQjC&elT7V7 %5sJwAxn7*SSI6SdEcrPt}`ZX1;STO4xc(b+x%g%3b5! z++t)^fk6^3Q4uTSYQ_(g@1RE%=dDTm9((oFN7}eR)Q}0>+AvU^d=Q91IuI;+ZnKhg z>o$^;S5<$Nw2%0R`Bqk`mtt}RMUP})RG9o?>~mn+Ptd^R^3`38u*0i1ZZ I7=Ves<-z&E&+DPd=e4*Pi;B8$bkf}zLqzmD@L-e! z)ngUn4l4e^U*Ti0xj{9}5z8jwKuS?GVAr`XqZg{C^U`NO`RFhr Kt9gz|y6cLW9F2-CwCW@7T!pz-bt_y6A{R zeE=(eE*c@>s-Ajal-}Hj +SurystxDxtDm%zaLu0#_s<8$rYx-M zJMaqII6TM`ks73yEDKlq?SB}2ZHWA;$*hPRbz}S~mBduxy{Zv~Gi338*wDP={*6u^ zIk+(@jXX}Sld+ju?krg<4W)gF7B!W4Ur?V%9?sF(vt3_C&SHjMNDrCQA*O_`&JB2S zax-|fdg8&! a{J~RR3y?-!l77bSppepxwEjBcJ?^g88y^ Fn2!t_1{5 zY{EZ1Pj(Q%a=3o)dIW8GA2|yNt9w6CeunJYq3!(F)Q;JaO;3IM2JZTODX?HU)Z>PV zLOZfpTEJX&VUiDpgdb!F5QD8_{bOM{{+s)9c;oKaAImvtw@9Swb!II|+H^zY&%(R< zd0`|f4dD0DyG1F34T2}Bt2pgr>8(htmzay%hlAk`Q^gN4!<>&T9<93PA3?A*fE4Gh zdn@Lq?}OL0>E!%-v(MTlA-hGaxt`=LL!&!UCo`dYm-jKEq%tmQ_~#wICK%sG_$UgO zQ-2@%^L-L`QNz7@{e8ET;j&!@GBzmLvK&sx))??K6PG*HtIMr>SYCC!S$3uY;Pdl* zByUMbpVF36Y?2Ya-2aV81GwcwXW9YtkoNH7b|2{5OK8!cBqrH##o2?HHe|;wnFCIv z<4*jwRUvAk&+h(~v~t8w#;cy-)PFgLjYddJlINQm%0we{8oMfA^>LOsmyCu B<{iLc$go&l zR)iQyxv@dy%b_<;dS28zvcGQJ6~~Sv3mn}M^osj#z9TfZyWseAMAc@?Xa%2`bTyB! zIWi(lv%xUeejBgsL8S!oarT^-UeXo}uW7X&mb~#Bz=y)zD?U59#JW>jGvI4OI*cvi z_eXXju$*zVL0TKy=wB)7Ul|_6*T(FLN%d%fZ;|8AZ*`&lQkzW_M9vSPyG8W45#&kb z7!(QGLeVeF(xf6({0!jTH^81jd!&?UV$ewc0J-GkKfMs! z0xLuhV|mT2cF+Zvj&d^&&nk5>kNgLyDES$}&o?>1W)Ff?sE^&a4%UW5^gyqOu(`|| zk$t21Y&=P%c1cA=4kmKGjKMFRtakyqHwn0KY5fi<>;&&~U06Jx)(x}rFZo8{@~UyJ z;-0ZL<8I3yhONJyW>nQTazwLl;Sd}N^vA&F>aA6YCcHa5X2^q$*MIXqoS<@P+N!0~ z1?#Ge&OK%$`>CwewZj>~`uoJ`<(n_u+oIjgFuZ7r)=#LzjA$EbRon+5eq;3*r38j& zl@q|gfwbwWd6E{7cz Ys^$!?bx(4XLldOh zNFS>5O-rxYDz~1yEeBZwlMQv9ssJRbCZ#51kXW3;*=t=q8wqAQF_CJ%Y2V_917p-g z^?K4+f*=us#boWvHX7ai`mE!i-DH|u{@-%RXKC1H)ErrC#I$yFiRvx`kzy3E8?|;J zsZK^6>%5WH5Te>Z6ojY@W5B#MlIe&wr$|$37D*f=f*>rvwylLu^s5;P%z$hnlOHEu zO8P!&mG04P8CmNl3j}cz2Ttc8-3*hNa}yB%4o`;!1bL#VmAa49G7-HgzVxbFM+^6E zwUqp{@ vOr`UEx0Ely$;
mUD<4N(}u}3K^nm000pFzf0U95ijl9#^qZS3 zB ~L&%SPSa{V%Lbv`Q%iTR$!X*)u{f{WMYKiMwVMK0W zGI2`&L1f1~IwpvD2&EM>z-L9_Uq2y4Fw(P@0o+rW7qWZyEcxm5B}5(`VAav!AL6Q- z7mhmAO8L$n2ADy_ukyNh9HRP2uS`w-Z`+e_honms=A90J7sg+sx?;bhL$$}!Qx yzfu1HW2?*~WuQ<+2gt&Te5NT2-|DF%O-SJrM z>9Ho6<-o0eTnbxby8sN2sOf$ayZ*CGlfF}G(FZ@v_N!r!e$q8gy{Z0PrVaaII|vu5 zxC39%&>u{aDN|hZIgef#qMbe1x6ig*k&Z+hD~VC)J>_azMBn6zntu@1ziMo`>~N|p zFt(j#s6v^MuN<3K7U;a$A#!aT=6F)`qP@qX<+r18yj6fUs|>7WiqbkNZ*2bjr qc)>2sy^`?wio
=;L^J8kh@)2ltF^dFmJ zK;`BJuWNiQAeU<_mdkO9CU?eJ%Cu3;Q<_SLh50LM#_u_?EN0IoxTZ#)FV8&JJg%1s zGRsQcT2^wOddi;>EQagMya+W*q(XG?%V}AeD@4|RE>D((X6qSvth$*(?N8tHr!6(Y z|7G>)hVqR)b2&Z|y{HanX|LXL&G$C6d8GG389DkeSzq*FGvLYmQ`(`G4sLeMwd+ej z`wE}oW+$I1>i2ANSN4yriy2huO(d{ZZJ}&hCE`Us28hPfH>wwSM1uMUnm=3ra}S5@ zTkf{m(0-y4E;{~s0=bOjI8LnXcnzXY8??Ia^gcIcPBO2}FX7(<3x8})gNF946uJ|= zGGJUtb_AZm-*T5k$g^22b;Z9p!@dqR7gELvG?#O9*!0PL8y!6j*7qkrUM>0T-I4P3 ztJtp}xWB^bYAl{JVF(4aTI)iD&A_bt#P3|~f-n|Ce Y%$9 zm4aL1GU5E+xh3DS_t6^I_MnvfFU||~OqAW8xhr4T{nxI)&mENFj$j^zK2ju1my9qP zrg8fi9SrYJk1}~dl$`PVVO}RTJS494j1O!o7ZUz)+n(YB*)wo1&gW4t-f#?&vu$F~ zYMzC${EmGb$2k;qYW?sW9E=a9}9MNA|DUsa%YNa`eZsT%ufmmtAV({YUHFj`D{C5U(fK z+665b&`|x9#-n)gSjGn=%lx|JlS(JmjZWc~i(@*)qf7}Z=#K3&0tnm;*Ghi@m2{iF z3Oh57AgAu+wbN3p0XXq|^b(PdeV4)tr>npweUOE+Kk<0`cJT4 )0*@*)j`OgVdynI%FfM1+ 4!ug3!c)NP&+hIixT=tEbHk|_>_~hlIFcp5G4#CbS+C =o|2H}Yy-aBDIQjqhkOE$xVrJ^+?B=d_2rgupi{+pC_|xp!Hz zSCM-Lh1 i@Ny`FRHFI`3P*^Nc^O%Dt8Sw+lrUg7?R zCm)Ff(^JM6NHt-Wiprbcsd@{N5iTBC6y}A*-0J4AHr$WqBotkGEb2}4)WiZr@U;$y zVvWBx{4`LL1(I-Z$UIMvi)*L)RomS9P~>eBd95Fz{;m4w)zvTm9Mzu?;KFg%nN%Z) zVGGmrsY - Ptgm7PX5@9_fIk-6;_fc?5|>qz72KkJ)054zOhG;{A)2wI^-F==pB~ zTY7oHWCG*ii%Wr%a8)OP?^hFJ4z|kQ&omFq0iBvTQFHeu2dnSf0e#mgf1=O5=_A?& z0q;SGiV}?qtSEG83u+^|kyi$9HjDQh_e1N)B?@`961r>uqC;X+Eub*)4aRY8stwKQ zK&SwIX;pT%<3^Lq!b)du=Ro^uC;q~JfF79+xPPThdHWowzQE3AFty>Dx^uz2Y#J#3 z2k
x}Bi`{1G^J^p_6$-`#wPyv{U{~5LZRtFCz z+l{+A$QrtP3rib)dsB)NMOHlD?Ri;hMa0B|Q$|lo6phdM{esXCJ(et%t{8U=q`6vh zQB1L!(*$}K-sGto8yi*H4WIvjcEXIDVTDiUN*}7O!lp|m)byxEb)5!}C#|dAC8n#S zkM9@zrx+>;;og7B07aL~@0{zc znw{qf+~gWP@ohr%I4oDhqc}9Q->jK6KF!d5AxEunp)rSk#QWxg_8Axe*1xi+o)f+| z_z&h1YLzBrmkFykK3MmKU6$k+mz4H=gw$MU+Xkc4D%^b)o+*eu0~j#_bnA9i3-g*U zc{hIpcD__LS8)84IVq?G!-1lIQ786F=H~kIw4?WS-SZ|;RA3i CW|bN<3wt}nqTNXHaxlv1N_qB|H#;-mNfy4N zw4AzB+eow!zh1LyQ0>poQD39Qt@YjsBpwDw53y&X7PTrro*{$fVCh^Mb)K~_O1_{+ zEoOMQsEN{~`b}2OcEB4mJOeM?gXe(2!9P!*`(*8==s#i%5G(#xlGn0sJc=&@d(yVf zKlcPAs?>$mQRMoW4kY0IDp*WovUAM#aDmYcHeBz~c%ID)tez{63@hKZ4Z{?GoPUN) zTc>M#mY=imL5YVD{Mvo8O0F9tr55j&UY9B{>5pPN+RKZLePR7A6@U2zK!6p1mxPv8 zZvnaJFz)=F En)%$`qzn)otabm;;%Y6abO?SY5 zs#`7I)SydD%7mD)D@P8(M**5s^Ou7GL;l^{fL{fAtpErXL4~l#qFjQ@B>JV25z@5Q zDe*+zE)Z*LgMk1i7anDM#D&ZyQ69xVC0x`nLqrWP8*U2JxFO>~u)aB9qV}p*^MG8R zema|X_vO_*e65%df!v6iF1U$}#Pj&-;uv?SA;Tlc@horUNSdYR_JN3rp3*vAgCS;C zIqV~Ypet^I-w$QUkrjS$tXD%E j9JhK3uxDr#a9 z&8pe mE#CEXLblm8kXfc+zN2bK+mGQ$QpP2b 4(|F_?F~c(d ?M5{D;+h5>me)H8(H`+x$a^XYMWcg_JUaOb} j9&-&4_dy#?> 7*Vxj-Gx?D( z5C}wSRoYF&GPoD|v*%9_Ak-eqZ>av6yH}Es*B9a?v9aBeFq{2pK=o&S54XB&$(uDn zeozwm$kI(3?mGyS2NECRT*c$5$*`dc=E>pirtqfOZ{g?;dYRFDo`U)hWb&xjLQNrf zob%%KBlb-&3r1VTFnQ};F^pfXzxlhRayIx6ON>OGjt4Mklb ygn3$iddC7rn z$8{~%>r@tq ;;(j#zn%e~>%Ku8b?_=3`_$eUEmd(P|Ap@Yf?fY7e=fa1yH*N7Du(8IT zrAW}?4UNn1Q%>2T56YqB6q)}5$=ecfd^X(Mjjj$l96_xt=pv(tSuqc&tw^ WJQuB>=2?Ep2p9S8vF?C>ROz%@#Gc@dl}?V^S@6*G^-Lzv^O|8MjA&WE{t zR~V**?}88%QB9Fgom$aRaUa0>&$^8IB?wu19IJDwQpS*M* fO%wN`*(xc5)+`bq=%e9@>0Iam)mxd99az;f8Jj!F@7Fc_7T zmmmnDOH1TYI!psSTrdC_O323R^*l80%F3r1T*PIK+AV_0`%+YCtIx*~j+@IU<|3a9 zuD&6TXc(9Cx(MA(7DDtanc~YfS5+vWO~_9C) UnF6!uEao+0dbYx1>}iMSJ|Equc0J)FA0 zPc><0x$SjsnJ(LH=PtqU3EAH=5CdXn=6=6ko3~-8Ors5yv-q?az+2GRKr*ERweW{; zN5{zVE%`rq5%nmlO4;0VX%q#}kXc=^7!m^(U!_Jyh<#CkyUJ;n_)e%70^h>lw(`rk zta~ee46tsMd75}f#`v{q8vNJ-fuOZ^v`m#tb24$rg?_AqXg9XQOj?u-en9I4o2JgP zSo46u@va~1T*^GR%U~$?5JZjp>%MvvC0IvMWXsy|MeYvN!Cgh{VGs^c0^T#YvgwTI zvlYvT5^<+ijh9d6m9;H|GkNH`#PM1H8qBjx)~$e)kSkiT>cKh2n$Tfg0Zw87br-{T z4@6ZT8#!us>G&j6HBp}s#j^x{EJPbsoA+&LJ=It~$@Lw&ASA*LCT|~#%KecqxasP; zO7WhwqtbRh61W*wr=dt=<8@N E)@k8Z&6@iiRnJy~zt52@4W`*lY@~{?w?Z9{h7m{ |WQTklm3^L;Cruvw#1jh6VoS$Yu}8HvsBCGTde zLd|16ue;{iMNt8aCr5uSGg938bmL|nh1uR&XZ_3xH4%hi2YvFO+-ouDROoN_cCTD^ z{u_sR4(Q`Pf( AOz*)v>GLBRFW;i4O&<7BKxfpEfs`aXJLb?aLrBxYYo0G=_4J}SGrl|8|9BW)hDR2E6 zK6mQ!d90OjU_#>P8h$dGb9RuhZ!1AGEbYm(RAe%v{J#(zK0!XD5iHlmmB)ou;aIKJ zX%~y5k|Fh*-qH}_2RHYp78T!1Vaz=h&x2jji*KNAOEaUW7Q(Ytz6+70wrYRVqlg_n z{NSDlmCW@&ZK};V(W+pwctg5dUjX1>*8jr=#Ut+DSpcv_XLG0tkd7Rt6rFGLu)E-T zb8;ps?}DV;> #By+@>i6;2#ozb%O9hTFFb2|hvpQ2Zsb_{hZ?r#sEq z!x41KQO*V+1eAimN!BTtep9@|>T)H!$ FAxJbXZU3 zdno#fcqqDFvW~3Nc2n$3@M@hYn2%D&C9F92Y)3yb?zo#K=MTYh<|J`*X2-fs5wU8x zs-Y;ft2EPXi Bdpy2Gd_mfJp@MgL-6S+n+!?6y#7 8#?atO`B|GRY=6)uBMi9W_!X=yuFgL``k)W0wZi%;hclTB=lsEvSD>p(u$M z%XPMUlc8zkKbp^|GB_*>4DBbNlul3ZWZgycDXu29IE1^Blnd@biBAS{l9DffR_pQ9 zd4hgcd*kL>j(&R`)V!NhxrkrtED0d%J{rh###vF*{5R2>2GqY@i{4k;kEspw%NlW; z>LSz(xWKs;lsn3NQr{&xQb(+VNL_f=pjSK-1FceHYce_lsnjp;&nyl;eQ09 | z=~=@SVUsf=nxpbn^dcKl#KP`}l3kLB=Obt~a^lecY}Li^Spn@lxMGr>xPZ+j&*gwJ z*}lqQ-~91)d{HpQ`=q;`MZ$m?*D`L2Q5JeQq 9M#CXS5U#Ta9Ol*C5L z{x}6ePM%UyC1c%9(Oe$K#52#@Sd6%W4!NK2)c?x5Fap)z(L(dm12V1-KFthbU}G!Z zwR^|{BS*CTI8Y(QwA4woyrf)-xYQgxZFQ26GOvWbJ%dw?z>)sYso}lMTG!NAngrrc zh &8ENAPW5SblA!Ds8I{#y&?$GG8HT)1Tn{Ize-S$WM5AyPG0z-JeXSu2jD@x5GE( zYRP?ifu*1f#(Xhy?%WSEmGndcqn)9NkcO&gjBr)`9(;JXo^0@EL)?(NprU_KrCrkL z3^CC|{{4^4vD?OIcgwWC-mc= %^C3Y4EwA1LQi4OPCW4)c7m z&|=|3#$5F-tGZU@%tOPizJ!#xd_zW5b&AkVo(i23hGmz%N?@_9MstWw(@x82=|?fy zQ)KZrxb#l)u@|2<8}YXS?3f%-otYbU4M^yO7fVV!AUbnWJoM>3yG*gZD=i+L#2ZSc z8TE@lM5APAb~Pc}(jS~Rb4XTmN+6*GY9O y(HD6SDP+Opc4?DG6UWB;vTZyi-p;V&sCL{0pMX^712`hd)s5{6 z#dY-*`?sP4Xp%CC5LtT_L}9`GG72N=UtG+==KvR?GEZw!KYq@P-;8m4ne>#wT?&NN zSVfgUipnp;S=b=;11oM-jQ=b3O2WXai$F6F&KiBOY7qB ^ZqI92) z_O@kiSAruybmbYo#&|8U{{VcSConad-NSam5vsa=i}{m^2fr*xg-7GeKiJWX&b`hl zL8FP6U^CLT1fPl1G!;AMIeIH_)Vimq#lYfkir1!J3~<>^o^I#;ry}etr>vZ{nNu?L zNVf+#KFLUaAPf1Em#Bdn<=2gH!`JY&{w!A2N#F5PGJeC%CN9Rtv`1HU!|Rj3lxPvB zUlh!j1TT3cgEO#V4&KcLicVIX=wz6Kj}rN>E29&WEm&fFx>I($oh+71*nsXZ{az2d zIy+;vp4& jYct1p9(wnI1P8v km3EgUya%D+4M zXDv^3vlGbZ6)XDBqu jo~$61C}@xg Qj%K=a=Lvl$Ww`bX|Ie1{jq4L78@^j&)7Gs?HgB#x$q?f5Pk zn;Y?n#YwO&PvcF3XzB-)Yco{b=BT4?2Du3SDHxKycIDm!*`K@le2uhEO1uM-_7O Ki9 z7Nh@e6C;u1DiDMxk{)dYtuYF+Z}i(QTUfd5O0}*zI7 (VM!9{m&!*nz=HD zbwrl@#;Al?ogw!VxXwAm!Cxyrm1~i!N7OE#Hw?qQk>@$}CP!P4gL7s!QrXYG_q}~5 zT-I+2y|rX^jBJo}8ckEL447(rAAE)0(v2YEW$0_E-W#7)iuoR=hv=Gec6{Tw1sfcF z*~x(L)YRp4FcfqzzHK?YFf;gt^>X>2Od(6yemUWUP_STO5sUFEamXui+cn{OUR@_I z&~co6c@9{&%XL>`YJRj5oa@lkIIq6~ZIbNxpL}5PFU6UYSWF~Vuf^Nfp#3iblIG?f zebpisW~>j(a>wxxr*&suuXCkFT-O(8-xHfd-7V4XEb-QeC0~iJ%lOVqpA^i-7={MV z@=z^ !}4eQNCb`G2HW zzc7555L(JnolUL%*F6P-re&fZg$JS>-FT9JG-gW^&IS5~1k-y!UCQsE%(Sr1%K&fM zabbe5kN8}n#>K4uQ*Fqq%^BT@0h%ITd0Y`v=gIW0g*fC&RtM?A^2Sg|a*|Wa-Odm9 zs*A#O1S}$NC@Mqt2Iuz*vVlydOuv15RvSMeWprC=++@7h+9h;fNasmsNKb==7!K G5(Du&9+ 1aXq~NVA>n}cGd%U<6?5ID{P3SbNC*c0ulPyyrgSVf6iqb3IGpDWO4X>?4s~xot z11XjqXh^2NtWKBtc>hXgt>1U{9cv*^)y_9w)HJ JnA6f>ip#nIAmc8Lg3!QF* zolQm6OAS7L#v=SvPrV#vLixa19mClJFs# CW`OE zr50M;r&E!I<*$=gxYS-o+UK{YS1Ex$R5HpHL;Z&c$pr *~d~hR{PR=o8djb_bSDdh0p*oMakDu+b7>lyaC~P%RsGTk5eD-K3=?DSnU- z3CY;Jgia?cgn^P1UF%K+Rv)LgF@51|Y5J&T*kw^T+;P5#YZuajQeun>OdZ|lc7Eb& z_LwVAwLlLkBMG U2X`f~u0*)~UaJqJ3znrU7>>v@2J11FjuSbU)*(95u%E`DW)Mh(W#m0x8n zWL(Ey b1ia8!H E>GDzs`nC)g@cyjG6}1hjQ~cV>Fo( zj$Nh4w_Roz)`Yu(C-em?1_P!Kq1Z&(X-4AwE)rAoB`6aWxXh7|ZIJ4rdQ6`{6$*@u z-7hN#mN z$zRe52>xjB7!q_zRY^1_@O7(BYNtc0-!yCp9`}w|$BJ>TkzuRg(-1I;bQN*tAHE9L zoy5&09qK;p8k)Y|P!;1a3-ABo8i2>Whj5`tq5=3Kkn1A0H4(N#j6@FN9tW3Z!Uh($ zkz>7R!IV;+CW Zgq>m@tOjsx+X7( zeR3Q~*fn~O2^=y=aeHxbDPmrgo2^DTzq;Zj)YGHoR!c?_aUDf0?}lIMBcdUvTGC7T zMDY=`YpK!7&tA-+ErawWbM+KtU7R>2b1;ZE4>jr$pQo81RJs!d_achBYK}!nGri2v z djje$(rKeCf>Aq;+LVqVNWTjXfnQtu4a=&}ED zTGz;W%_8rHp TZ6u~Ffu zJ2KWm8>U4|IzO;$BsiQdd;2XHsD(5yM8YK02`nAdc&SO!r#?$oc^OfP3TcY{U>F0V z%i${ lT(wtR$e)Rzh{k5u8FP_&hlk64O0vk-jp&8M?n-{ z)|>j<)8{B2s6?;nTxz{C*}Ghx&qKgh#K4l8%+0s`Mohua%_8x3_~lfk6_(_S&uXR_ zfuGMC@_E3XKnQKW=1nprzV?&$!mdI$&c{NrgJc{Ro5N-EZ{9Xz`qmED&+b<_>Wm}h z0d_KpEj6RH$9g)R%Io{Pf`FYdu;)PgZ!8-ap{|b7sWf^gWcK!KzX;dGqcwz}l)O^s z``x3+rch%cB0XUDuQ2dZCS&w(?3!P`C@wBtIEzrGV<1iSY^E5S{`Ll+7BOx;Nm_Xg zQ!9RAMJ1!0nHo>r`RxbEC=F9HeqtphDBG#SHJFq`M04_=#M;0ib=)PpUl7zD-%qB6 zQ9Z$V&rFOP+D@R#6IEmD#V;(^m{=2{l=FKj-AFJp1d@lz)hs6P=lq#%#Z7D(F4mul z3sDIl>-goEDphq)!%~quXl)Pjxm?toGF?5rw>BDMX<_T9-%@e8Ksv*SI?5E1h!E91 zx6I0!4&;Opik$flWR&Y8oIN`*8bfJ`F>J?;y-=|yYO=YB7%0MBNIF^Fb`fa0gcVxs zL|;W+`G7Ic=v9wSU;0+EhESUtJzgKad%}*9m+~jEMry#zC6n)o^OeQBQ1vcH>zs~$ zSZWc4wt_P|o9_PP%p40={q_Jd)=wj_zIbF>L`uMc72o7jzc0qdGJhUq{X4b426Bdg zK|;0MZCK ml#cypn7THH+A8u7Uv15Ar(U0;vco%)8@6WQ`jGpWUa08!B+KLoeHQ_K zTy#mL qXwG{MjrP6sW zTs=wXh{pdYvXsfe>kgv}oR=5zhSVHEud$GoRU#bN_5dE@h6e7ZelxD!6786H`dKgv z=_1oiBljsbVHv>`oUeoBvTrUZ&`0a|X&HapMXs0oa)E9o+)XC{iCejQ%ea>H9tXaV z>>+#B=%&c=+Q0=@kPZ2F>G=QE*jGly(RAzL9^45*0!(lX3GTrM0t9z=$RLBe2TO3b zpn>4-&fxAA+$Ff5A$dPJ_ue1(Pp{S8Rl9aq_nPY2kMxA`!$i;nm1LHEpHZ#?C1M*t z_Ha?)4?F&-&ew}1n}Vdj%NiOd?@mcq4Y&E!jqLsFQHo7%kHSb{fNTRQ6KS~2Ki7=T zLo7tE&BasAz(4Xq@aMpcjrg6VfUY-!602pd#~G^ta^XKjBKZ@kC5j3 LoMEkVgGAj@>q(a?1l>8wCa4=2)qvx!f@m?5%@#b(^^gQCnWo%N)|vAYcUSXRn| z479N-pG7IL%yEYm6nJ!ofTyzLo%6Bw&_kA$#G#QO>S3F4yX~;AUsK>zTb8{C$9fz! zC3&Vw6%HGD(_i#NZSH%T`F;?ozPS5v2f `gF d}jIOYm@2K6VzqvZvy+9v?FNaK`7Hz M+u=T#k^ZVoMP>IwO2vrBwN;sx zw}DFiot7SF!w;i#3fad+3ErUZW9mRX)%>M^x(ruvOUe3jbG}WayXo#do65pcOi8O$ z1EyX|y1)O93fF$EodQ8y$lBTOX^9H2r}_Tfg&W9 1*9y&&%D~Q~_k>6o(7jh+kuhz>O*73qj3#MYC3|CLdQ$PdA(@iUzCd zW#7n%$e``*g)o&yKu#p4off5+W|>W0 vfBDPehq62UC zQnzhKyk#ZAdKwmcEBB1SmVx?$_e5*+l#|f#rQKB`F_G4{zHoK;KV|u!6f7cetA*2F z&&&b&mGsrPJYqVCjIsa4#m0eX+8+<;8y@_r(X%}MQ=_-Z(uy3x*eC0u%V-LZQTc3% zq&-tSwtO_?M)5;V8-dTZfgQ%d5{9MR0)5^DL6+p{?i=!u@XLJBO=mt2g&|rZ0krhi zNjrahaZVnmA=$TDeqT7}5^R0$b`!d_v)9+c*ff^@NK1DectCE0B2l%&MDJOLXF+TU zNj2RhI)Cf@GWqU;y~nKnHNL+-7nGmOHD6d&`tpBF>!sx+&>XP10ac$!1j?Nto>d;a zWZx22sm!`gVEk4prgghr###&e^i`<*eQGUQHPOnMBBtle%)ju(Pc!x9^L%f3ooc6; z3oe>u#&-|NWQAS^H#+>LaA~74u6?h2)={3t7hsG8MzXI)fetdEsdJ7QMdB!`&d5_w zxj9X;zI_6KceB59Nleu>)Nx)$V|-w?D;oT%X^-0mrG1Yu_LOA R>r+ZV(X@6CZr}u&SK|Ew^F}I^KFca-30S{M^JWf0Kcl;f|yqtql_-{^N_zI;c5H zh5M(yfX@;E-PpIr+NOR}B&@N08vWE3945nw!2DBPL#l^PW^y=vjQ;%k8)Npy`peC9 zoky~^u^Fn?AeQ#YHC6^=&5LQe;;h9?uoo!=lHkU s^;Ck#Uirm4}z`nb&C(7@6Yk=N$B-kJUL6cd~oW7_=YgN-M-pinI{kuaBP}X zkL=$>ECT>=q!0^_ND*(~$G%@1TrWJ*&br0TB857u1h?!;moU^dNN#3=s#V{xp4jz- zvGTX=M02dO$>m1@uk1RLB4r1G MrCg+^{!i-T_35acwoFgvQMmyv0~gY>7*=&%GFB)lHD;YS$V#&)(hd{ z1=GAM_*d0`V?jL6{iqox1$a*aC}%cVbl$haY%$I|`D9wabCOLmE&VfwQOG6qHZ5*M z@W(GT!s}f|<~VJTK4U;O3ww--ms#QRHXZK27>h;I{119lZ&cMPP0M>Q2iIF_pq4r3 zafj?%Htts}7MT6ApQFT3zIii=;u6#Co=SIgJm{m2lOBm;#|kh}NvsS_o%GJ(;tRZP zP^sEYV=10Fx{fm%Xy=<_@BfQE4)R^arNQzHc8rn|9SxMVv1ec7U_EVMele0F`HM3& zMgz^M0mu3>njEGH&_c!qIwp&+CDRZvThQd1?6mB2My?fgKG|7JsTfu~#gk+G#;g9N z5HkJC1B+%d>=*J!2QAWLqeahz_vW?y LF9pjtT>Rrp8t&OVIcrfo^NGkNw5X1Chuj^E;vt+%U@O+G0ga6HQ)hI4T=hg{>K0 zEQDk4B*bYwd`bI3>6DL(&GAtWxRO0=yqDRM+JIq68>I+)u{jxR8k3J%GyvgT!WIKq zHkk!PYlY~`*GkvY*viOaJ>`{f?j0@i2dGn$%9)54zljxj>x%O8Ga7{hsKCn%ynxMK zv+)6aRY_sl&_jlw^=a5nf0DBe3$d}R;?3PzbI1Fv(blPFAq%^+F~hnuA>!wmmkbh8 zQkYYllMc1>?ji7VV{o x34NifgFIfaW_9DKH+-Y4AfXl%)$kBU0L2P-Ibm^&kG zdu%16S}vTh4H+FJl(5bH9$6B7wG!PI=Jtjd&MN@K#GK&5*WeS4Am^9RhjPQqJ4cS1 zbWQ#9kC30Pgvmwi^>$F@kp>Nfe$~RlRmqYu$2=e)Z~0CZ4{WIHdBF8Oo_3E0`6|nw zM9tADYz!_yAGUoA4k79((;QaRnF`y7n90X5Iv)C`t 2AxK zbC(%zSk)imMN1V2ilyN;8RsPv1)?_)v*OT>)k!+d5I-_nQOE5lu=5>WL`A;yu{f}x z2AO-O-B>?4K|R>4RNoJg$*<^F%mT5H_w6<#KO^)&+ohVIUb{HD50q+=l2ius?Ep(# zNM-p(i!()L;wm^AbRO*Bc(mk=AkA(HVB%B@x);15|ICc@MTH|zh1IRt&;+jIXFP?L z2IAbgxpW`I_N8@0e4}wW?IJt!+g@^PDqpLn^Tda_8zZF0%pXOI5fss9Y5+&EpEapX zh>#hoZTmWN*hhU>-!ZuRS5L6e?VNu$;`_@{?^`UX0rt4j1Qu)VfB>3I2u5`TE?9+T zDW ciY1HQhs&VVJ^tKx;?L-9~M<&)Io@?_Fd-w z!4C}rp_(EhDubph_{QTI)Jr%^tZvDw`q?4t3(;*#lrcCzKg5tRI611n!4Lk4ErL|` zEdTicMsIh|BJoZLp9f}zknsB6D{7K(MoEjIG *p6+y{=WZuLNggjB!o zb@!6=N~O+|vz?E*7_1%`G^8Yd{b+F?)8-}WGKckThr1tx6ZJ#1H-HT|Fr EJCm@hQvCk6^sY(rr!cfkdtf 1T<98_ypt zhn3?T?kA=d8_7#V^#QgWOE|zBwte|Yw8}@^9}yvTT~xSNnQ1LuN)+XiNtLjPu{6P^ zLdXm|SzL-uHQbNAWQ`Y9FLZT`;^2YR1IVQ=EX-rI*iC%|(c{=qm7gUj4Gg&(r4jYx zS81(`nJ{FHUtM2&`tkdtoP2tMp12ba1gDShhb$MLeE?~Jr`h%kd1`G(YA98ACnZyw zh|lq&A< dSgXouzi&15Eou@ofjX6UYj!sO02Pn%|~4h9K}M7g=SB7~8Wt#wR8nW1#B= zz%|Vqj^(?d%77vNVjud=oJC0a{efJ2Pp+;()Xm@>@9mH^8fFA@jaF~hOKr=ZO2Dx3 z;V@PS9<*gBzYt&CrZHwb9;WV>r-#>zgKO2>@@yRVv1l6A_7@S`FAOXqL-68`q4B`Z z|67v7HHOO5{7r?mq-zdz6+B8b T2uQMKRUQr(OFU!nGr NwIs{ZU_(WL?l=%Va%5;bLHN_aoqq0EL#4-OxT&q}`PRv18StoR2mQR!;9*gT}8 zqE);z1WUdIvxa`Y5O+P=Y0)bal+HSGV*2T^^)viTE#}aMaM%oJ@J`UJ$>hc2)IWYD zYhSM~0$tWfNT#;aQZ1)EYCPx$1706+I6Qad#xiPZdpI$1PmxA(wovYmh#Fwc{p&*l z>*Yz6?(*S-4bQJy4+eP&UJ|U9Mb#s*GI#QA4(>zlvu03>hIDZ++1hwsEExq(Hg563 zBK087=q$DZ|LYvLzAyLcAIorQqh9IyhiX}{bTvnw#WWtf- 9yj<-kyr0@|O<^aZ zYaP)Jkxdb`^(g5e&+=9S8%J^=8H|VBr@Qn>UR3)u+A%aq)$f>X^K `GM-Hj9@+G*DWw$aU8`(XUWK1$RBJ0$qz$Z25NfNs;( zyV|&ENK_kIHCeY>#5EZi4-0Qpw_+ 4{o+C(t` zmMvZ@A+_seRzL5zVZAPDlwIz;nZEnZ+;09622t1?86xr-_A#S+3f5Tq5v1aeAtH zz{#0}lTDx%2?FV;e8r_hc2e>!(r0Dz7t?T$W8l5Xg{OJ+y8uazf0_HBaAX=CZr__) z?vZuLJ8?zF&`QSD@ve9)Rut{nrKT5n+b|9+pjsa|6~gKWUD@lgDJ!o~+7{(VJZUf2 z(A>y~zONazJKo3*+tuqyQ<8m4NJO+I{I5oVsau|zL8-Q~2WcJRk!Izt*F){(!J5zc zFmD*sP9`!Wa`VO<(Y<#EOfQBvd}JtBF=C!1zT-hL%gc)wSyYkqFUJ!uB=tX?XM|55 zE3K(KF8{*-iQ)qBTV?gkj`*&~$$R?O6-Lrs)yOIgDYuN@=kKu$d3>t{&)ctkvbM0G zLm(N2&J$&FVWA!NyQz;$9+8{X8H|91Evbo-Nk%I|k?34fD j8ziM8r 60XMw+or@r;B36LQQ3(Q|R!po$ki?udy!qmV>$FdMVQj^QQDeLsMT?mwhFpo~>_ zw9g7cLPz5ms2aipK7jgRZ1Qg0eu^*m{MTVsFyPAk?aRo)-M7WN%7Y#Y(|b$flM;-0 zX^3|82hAh_*m&WdBXiXC!x-(wQ;GT=UEP>pI7QVM0(UrYH7|gK!E3sgMt8citSSs$ z2u3SsHByIxeD5%uu$oZui1?U>QFQ1DvInHhxe2afD$&&5H R;@GE!bamXpYt`nEVy~lHMw#!|0P$(vUZRbkf^`tzDx*NnB;?9 z9bA`Be7}%iN9~xa8p85AE6vqGqOsyjeB~u@Zn1A3uxlcr_5GnnUSlgEaXHizI5;QR zO_0S{E$T>udYI0~Twh@AIgyw({vHZUB0gmstG+4yuCYPx@t##VIiuPWjV$ZOmoKA; z*(P|Ha;D7LjNeJ-A&_LwEOSq-QVbU)&A<8ILG$Mj14L8Yo;lPge3saeDZ$qHAS$Mp zzE}BHMpf;4XH3>rX#MaxcMLyfCHV(>kbG%kMx|#MtEigH^Ayg{@|9ofs&(zr`!&Zv z=i0Ycf*MtuSJ4nU6_KO4R?v$x5O)U xN`P-wDXvIOpL^7zzB>7b(p e5)p`@`{TRsSQoIJ!xt7bTmq2C|*o=Nt4A6t?+n$#-*hQk{i^J+wQp6v11d z3cUVg0TLew3%+uy )s;LhJ(sb*Oq4Uwvitu~G@_7EvN?roI|! zj6-rr3U-D9VL4d~qS$ rPhfJE+aNj94kny)x?sUEBD&` z-uf+RFmg^tD%1Y{h2Zsf`z7`7& L6c39<20!k^`iR z8k`QQV3A&e05+O%33b%MwLyF)tl$jl?Ku0LMi`5J-6J|X%I6(MfYrt4W(8qGkC0Tg zj_kAFeFW?D7B`a_q0hTlmJ8LA%Jv*F{0$n=dBJbq46u`LHv>ly^tsaVyA7bCJ^9RFk3I4LNl&PN_}!1m*L~4Ry_`{p5Kb4@G`!j?0A>MzkygoXPsj;tuG--pXw7V zTYkc$JW$()uGAqEtf>2|vbP>ZU%)eTzYz?=1qhfH1^S$)mvRUnBb41NRm3Lso>o2j zd8Q#P+KtIYef#^KTqn-C^mLLTT^TyGj@a|Xi#zW0Ae2+1QF%)WuN@o2@o;V@Bpau7 zysjmd+81gylA>??W`h&x$}G%g1s3c`+xqM6v%4rn^2-oe-}7Jz-__;a0SksS=U6gT zvZ#<&Sk%|$&I3jP!( lJ25-%v0XUK&C^qhkL1| zFB)%=2haEa08iAHhTPkoyNf&)_4B5VKr=OfUg#cI)KByTIVG3xBb|5NvQJRh$svNK z?c1Iu$Dn1(n8wTs6qz&>;}Rm~ChzR@a<1VmZ5e84Oe{VKLGM-8k!NS?>y9+N6ME@( z70v#YWDvqTA1K6`YwZ*98ZdZW3kLb~Xh7AOh9&HS-I2}BXM0OQ1)2IlcJ}DhorOkk z(pww^v@LwR*AUdLApvWBfh>JsIeToXJ|x@-Dm3E&D6-`z63YV1#;w&A&O)``Br3~c z&;bglNY8CW5ioO}TPLULFGRCPc$RLFdZ`Y=5CegEhlfySR59YFZEh$Or_yE*g0sHb zMmld6YfoM4Z~?&$7*UY1IKo0lra+3tTaEHG=6W9a)IgkvL_q;MNGh=sFg!h+%S#m6 zqdO6leQ{pydp@b|>&Md_EV7Akz1}m12cnK5%jrjDit=780h75913=>1#Hvthh+*gb zB_zV>(3dzwJ#ly?`5#^nM%@X{L(y6#WcUG8rl(Va
%F5OxC!Txk3E8wpC~APot@hXePH}L&^8N#D}eAoZSs~G z-3}J^n_-4eahnxH7z`YF+WXhMU7Q-`zs9Qy4>MM lA_Ek7M0%G6?ZPLE2@0cxPN8up~?S6&7!3Fqsof>ah(UV zn=SwzN*S aZRZ|nPqvpyfS6;0=>iJC)#cBI)fj 2fk>d<+f>mOVZ=K?ydJblO$warC`xE*i*)3VO18oqVx|GLfTs#;TN$6 z{SD(g>^cw&?f>{NRJHZ(viBl2$nyO$${A>1tUl*XJxb+mO?{(nXEH93v3`{xve0(& zep3DV;2*8U(YDfspsmrj-(Y9>?t=lFHt!7urn*;jfWJ5k;=c`Z{()JFb7oX)GpElO zAm3~4m>%q39t}ghS7BqKm)2Z{(0hZ9R-y5%{lEPNk#1Yt0pM>n?$rMcsyJCIn>bJT z#W;r9NQ$JZp_q&O%WBgr<-brBI`%tw@GHxGkIBJ2V3yf*h13mjfnYdx;Rp>}Q13qQ z!`k#Oc7mgglQJx{c7=<+tEWa^DSFOy`^5Dw;b6q}r)RwxQ3Gta40K}u8s} D; zSmU0iT9O|41-0^XuS~!E
cbUTb>`|giA zy?+pmUk4?`BA`+lUxRVmBpN0{+s3!rcI2r7Jeu(Z_DI!GC =vAZbLl1YNlk@xE@ z$Zs $os*iy86aa4rAm?fsC2y&}ELUR0X7KuJMyqv~HLp4tO(HQooP z`f6-FU*M-5OGLdjkApL%0-=C377y%f(P#rb63h$E|8njnHaeJmHUs8*-K>_u5tJJC z<-*q2nb>qn@~totU%;QQ4*c5`TUvt$$FJL2%j`Qd$`EuXjlZvTcQ3pOF^h+u5fFgG z-qWP~UXXDlr_^}50woJ^mY+G{J`bcz{d%Vs=cNNoLFw-O@KbAZ e*5A8!0Yrb0ZikDgrzd%M&>GP^d(lHS;4zXsF0*P%gr$e_wF6y)V(*EU7YF zDxYzr#;%$wM}xg|9t!BE6K)pMc3b%F9$C=%9ERsKTsX}L)3;r}ES3L+4}~pgobW_) z{~XAhZpoJfuJRH1{xo>oa@LfKUw@HB$ZKYd8Lc;ReyQ`Ghxo=Vy3U6I@Eb*fZ;PB+ zctqXoZrZVhp5Jcs#hw{ECvvgFLL=V_N;ArLuT@*;-)NrMh~`;SH6{0;$09L2{-h*$ ztYU)d*9u&B@FpbJn;Xy= ~kIm 9P1J{X4r@H8VdvCaq&Za@ij(@6k{3Kcy9Y93o4Czp0i4 z6bY>ff$~s_8)F*V?|WJ=w;NP(TFqhmYsBu*azEX7r)f>xgFBCByozT{OfjtmPCt5i zsv(Pv!+}1C#0hO*OQ2(a{e2Luw&mlbf0j(=r84-&oHSeMJ{vKF^kB2xN7X9kUA#hu z$2HS)A0ArZQLVD^93Qd%*H<>5fZaL~rM?KTS1IJFAsD7IPG$`R_fb%&M1mevA z!Nzc(6!LGB>OiUgoQ}u)Mrl7_Q6%Fnn0iiO5TQp=*rPkcOU~Q7f=k+tH!t*xo4T`& zIsf&fiLkAg{>_Uz&(&20z2>Xx-lXF@0YF)&TCnJ5^JDxdO8RK7WN{hQ9AZx^-;~%v z{OJ8jVH(Y+csUGK3y(O$T!Mb-Ywkekrxj3{?~^oT%>}iq0*;J>k5_V<3jzU??iP1C zOYw@{3I)Nmju#_`jM_fcb2@h}D_QIhnI|?f>v_|-nzx)&@uP-y=BPthSFE`dN$#S} z2{lBYn6e(z6|rP@_Q(z!7knA=t$I~$wJ!^G6&7oyU#{nk KPdbVp9k#Eg zJ@3j!k=>6Ug;6N5KrIm%t+qK6SKQ>PDZ4cK1>>Go8^H3uM?ROfY9OQVo|!=)D=mK1 zVR7ZWIl=S`k|uw|m1Bh2_B@{AW>uLI-FHnx*7NvLt+24q$~6JQuGgFvkiK8pv|N1r z-m$On(iKN?4{70;v+@9wCa5Jf v6S9XL(`fo zok%;mFJL3Pv(XNQKL(mFSmQ#&F4fWN_3WZ9-MPj^vLLX^cQ9q$y35G!+^8*pOW}2Z zyU{Bt{T1W54Y_j^Wl2?fJh=#(qV$<9b5te3fjH$v44Em0kDp@_U{G@oCbHuCA=8Y3 zkW7^lT!MG5sL^cwg5;WosUH~2^Hb-o(W8?+H*Me-B|l3FcU4Qtm2O~!WPT#8?Uq;a z36Q?DfZsnlKhE+D?)J8pg8e#5{h1_&GE6lzH03)CYRtg|k_yifPm{IL4X3Jtk}%D4 z02*Y;F$VU}G5C}Q-#q7i<_{t-W2Fn*&Y|y|8~yoerTl*Xt9yh}^Mv;0Difl3m@T~u zQm)+Q8l4_2F5Mj9MFA*T;R3iWcT9sQ9!VIw8|P=DXk9e=9K7^-^cW=QT^t?|Q|^RT zfuzwwUNV0xQA3Ln!KwW_0^}nz``ouc=}T1jy`w6ZtJ5zgS>SJQ)P)ii7cvGMZ^9=3 z=E+k;%cAA>0vZspVp-a>|A*#xMH2kaRWd4z_-}-hZ)oo1_S2wGGg^#)4Bkqa+>M81 zyHi@dAu?kW+|wsM1^VClxyv$d)qPZU6wMlz2|}?7hJaLG Fpl=TpKk!{h3dQt zeM=B0Tl% 7U*y^;tDpkjXpsDZ-<+$ae zjere7dGbZ`kZ3*|j!9tTqI3i(jIBM! ivOQoEl-KoRC*)#wK{BY5 z;x}-6&{bydd6j|7PIdc`?1{+PE2U6bN+~bQ&o@*LE}TnXQe)_2}*6sS9- z%2LlSwMK8?V>iN2dXN3eBao72rj~n$ixt2jzyr0|{ob53g>#?Cyx~yE!Hg?zisE4? mii dQK2C{%#cZrJhJMovMp^ )n?|%TN#?Fxd literal 0 HcmV?d00001 diff --git a/thread-specific-storage/etc/thread-specific-storage.urm.puml b/thread-specific-storage/etc/thread-specific-storage.urm.puml new file mode 100644 index 000000000000..8b95fce260de --- /dev/null +++ b/thread-specific-storage/etc/thread-specific-storage.urm.puml @@ -0,0 +1,37 @@ +@startuml +!theme plain +top to bottom direction +class UserContext { + - userId : Long + + UserContext(userId : Long) + + getUserId() : Long + + setUserId(userId : Long) : void +} +class UserContextProxy { + - {static} userContextHolder : ThreadLocal + + {static} set(context : UserContext) : void + + {static} get() : UserContext + + {static} clear() : void +} +class RequestHandler { + - contextProxy : UserContextProxy + - token : String + + RequestHandler(contextProxy : UserContextProxy, token : String) + + process() : void + - parseToken(token : String) : Long +} +class APP { + + {static} main(args : String[]) : void +} +class Thread { + + start() : void +} + +UserContextProxy ..> UserContext : manages +RequestHandler ..> UserContextProxy : uses +RequestHandler ..> UserContext : creates +APP ..> UserContextProxy : creates +APP ..> RequestHandler : creates +APP ..> Thread : starts +Thread ..> RequestHandler : executes +@enduml \ No newline at end of file diff --git a/thread-specific-storage/pom.xml b/thread-specific-storage/pom.xml new file mode 100644 index 000000000000..6e54c3fc406f --- /dev/null +++ b/thread-specific-storage/pom.xml @@ -0,0 +1,83 @@ + + + + + diff --git a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java new file mode 100644 index 000000000000..882852ef4c49 --- /dev/null +++ b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java @@ -0,0 +1,40 @@ +package com.iluwatar.threadspecificstorage; + +/** + * Application entry point demonstrating the Thread-Specific Storage pattern. + * + *4.0.0 + ++ + +com.iluwatar +java-design-patterns +1.26.0-SNAPSHOT +thread-specific-storage + ++ + ++ +org.slf4j +slf4j-api ++ +ch.qos.logback +logback-classic ++ +org.junit.jupiter +junit-jupiter-engine +test ++ +org.mockito +mockito-core +test ++ ++ ++ +org.apache.maven.plugins +maven-assembly-plugin ++ ++ ++ ++ ++ +com.iluwatar.threadspecificstorage.App +This example simulates concurrent request processing for multiple users. + * Each request carries a user token, and user-specific context is managed + * transparently using thread-specific storage.
+ */ +public class App { + + /** + * Runs the Thread-Specific Storage pattern demonstration. + * + * @param args command-line arguments (not used) + */ + public static void main(String[] args) { + // Initialize components + UserContextProxy proxy = new UserContextProxy(); + + // Simulate concurrent requests from multiple users + for (int i = 1; i <= 5; i++) { + // Simulate tokens for different users + String token = "token::" + (i % 3 + 1); // 3 distinct users + + new Thread(() -> { + // Simulate request processing flow + RequestHandler handler = new RequestHandler(proxy, token); + handler.process(); + }).start(); + + // Slightly stagger request times + try { + Thread.sleep(50); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} diff --git a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java new file mode 100644 index 000000000000..6be42db5f62a --- /dev/null +++ b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java @@ -0,0 +1,58 @@ +package com.iluwatar.threadspecificstorage; + +import java.util.Random; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * Application Thread + * + *Each instance simulates a request-processing thread that uses + * the Thread-Specific Object Proxy to access Thread-Specific Object. + */ +@AllArgsConstructor +@Slf4j +public class RequestHandler { + private final UserContextProxy contextProxy; + private final String token; + + /** + * Simulated business process: + * 1. Parse userId from token ("Token::userId"). + * 2. Store userId in thread-local storage. + * 3. Later, retrieve userId and use it for business logic. + * 4. Finally, clear thread-local to prevent memory leak. + */ + public void process() { + LOGGER.info("Start handling request with token: {}", token); + + try { + // Step 1: Parse token to get userId + Long userId = parseToken(token); + + // Step 2: Save userId in ThreadLocal storage + contextProxy.set(new UserContext(userId)); + + // Simulate delay between stages of request handling + Thread.sleep(200); + + // Step 3: Retrieve userId later in the request flow + Long retrievedId = contextProxy.get().getUserId(); + Random random = new Random(); + String accountInfo = retrievedId + "'s account: " + random.nextInt(400); + LOGGER.info(accountInfo); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + // Step 4: Clear ThreadLocal to avoid potential memory leaks + contextProxy.clear(); + } + } + + private Long parseToken(String token) { + // token format: "Token::1234" + String[] parts = token.split("::"); + return (parts.length == 2) ? Long.parseLong(parts[1]) : -1L; + } +} diff --git a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/UserContext.java b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/UserContext.java new file mode 100644 index 000000000000..51399a1a0c72 --- /dev/null +++ b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/UserContext.java @@ -0,0 +1,15 @@ +package com.iluwatar.threadspecificstorage; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * Thread-Specific Object + * + *
Provides a service or data that is only accessible via a particular thread. + */ +@Data +@AllArgsConstructor +public class UserContext { + private Long userId; +} diff --git a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/UserContextProxy.java b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/UserContextProxy.java new file mode 100644 index 000000000000..ca5e15e93919 --- /dev/null +++ b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/UserContextProxy.java @@ -0,0 +1,35 @@ +package com.iluwatar.threadspecificstorage; + +/** + * Thread-Specific Object Proxy + * + *
The Thread-Specific Object Proxy acts as an intermediary, + * enabling application thread to access and manipulate thread-specific objects simply and securely. + */ +public class UserContextProxy { + /** + * Underlying TSObjectCollection (ThreadLocalMap) managed by JVM.This ThreadLocal acts as the Key for the map.So That there is also no key factory. + */ + private static final ThreadLocal
userContextHolder = new ThreadLocal (); + + /** + * Set UserContext for the current thread. + */ + public static void set(UserContext context) { + userContextHolder.set(context); + } + + /** + * Get UserContext for the current thread. + */ + public static UserContext get() { + return userContextHolder.get(); + } + + /** + * Clear UserContext to prevent potential memory leaks. + */ + public static void clear() { + userContextHolder.remove(); + } +} diff --git a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/AppTest.java b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/AppTest.java new file mode 100644 index 000000000000..0a01ee3be2ba --- /dev/null +++ b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/AppTest.java @@ -0,0 +1,38 @@ +package com.iluwatar.threadspecificstorage; + +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests for APP class + */ +class AppTest { + @Test + void testMainMethod() { + // Capture system output + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outContent)); + + // Run the main method + App.main(new String[]{}); + + // Give some time for threads to execute + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + // Verify output contains expected log messages + String output = outContent.toString(); + assertTrue(output.contains("Start handling request with token"), + "Should contain request handling start messages"); + + // Restore system output + System.setOut(System.out); + } +} \ No newline at end of file diff --git a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/RequestHandlerTest.java b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/RequestHandlerTest.java new file mode 100644 index 000000000000..6afbaf7f0178 --- /dev/null +++ b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/RequestHandlerTest.java @@ -0,0 +1,34 @@ +package com.iluwatar.threadspecificstorage; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class RequestHandlerTest { + + @Test + void process_shouldStoreAndClearUserContext() { + // Given - a real UserContextProxy + UserContextProxy proxy = new UserContextProxy(); + RequestHandler handler = new RequestHandler(proxy, "token::123"); + + // When - process the request + handler.process(); + + // Then - after processing, ThreadLocal should be cleared + assertNull(proxy.get(), "ThreadLocal should be cleared after process()"); + } + + @Test + void process_withInvalidToken_shouldSetUserIdToMinusOne() { + // Given - a real UserContextProxy + UserContextProxy proxy = new UserContextProxy(); + RequestHandler handler = new RequestHandler(proxy, "invalid-token"); + + // When - process the request + handler.process(); + + // Then - after processing, ThreadLocal should be cleared + assertNull(proxy.get(), "ThreadLocal should be cleared even for invalid token"); + } +} diff --git a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/UserContextProxyTest.java b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/UserContextProxyTest.java new file mode 100644 index 000000000000..56eb44ad4d15 --- /dev/null +++ b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/UserContextProxyTest.java @@ -0,0 +1,68 @@ +package com.iluwatar.threadspecificstorage; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for UserContextProxy class + */ +class UserContextProxyTest { + + private UserContext userContext; + + @BeforeEach + void setUp() { + userContext = new UserContext(123L); + } + + @AfterEach + void tearDown() { + UserContextProxy.clear(); + } + + @Test + void testSetAndGetContext() { + UserContextProxy.set(userContext); + UserContext retrievedContext = UserContextProxy.get(); + assertNotNull(retrievedContext, "Retrieved context should not be null"); + assertEquals(userContext.getUserId(), retrievedContext.getUserId(), + "Retrieved context should have the same userId"); + } + + @Test + void testGetContextWhenNotSet() { + UserContext retrievedContext = UserContextProxy.get(); + assertNull(retrievedContext, "Context should be null when not set"); + } + + @Test + void testClearContext() { + UserContextProxy.set(userContext); + UserContextProxy.clear(); + UserContext retrievedContext = UserContextProxy.get(); + assertNull(retrievedContext, "Context should be null after clearing"); + } + + @Test + void testThreadIsolation() throws InterruptedException { + UserContext context1 = new UserContext(123L); + UserContext context2 = new UserContext(456L); + UserContextProxy.set(context1); + // Create another thread to set different context + Thread thread = new Thread(() -> { + UserContextProxy.set(context2); + UserContext threadContext = UserContextProxy.get(); + assertNotNull(threadContext); + assertEquals(456L, threadContext.getUserId()); + }); + thread.start(); + thread.join(); + // Main thread context should remain unchanged + UserContext mainThreadContext = UserContextProxy.get(); + assertNotNull(mainThreadContext); + assertEquals(123L, mainThreadContext.getUserId()); + } +} \ No newline at end of file diff --git a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/UserContextTest.java b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/UserContextTest.java new file mode 100644 index 000000000000..636e8bfcdc2a --- /dev/null +++ b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/UserContextTest.java @@ -0,0 +1,51 @@ +package com.iluwatar.threadspecificstorage; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for UserContext class + */ +class UserContextTest { + + @Test + void testConstructorAndGetUserId() { + Long userId = 123L; + UserContext context = new UserContext(userId); + + assertEquals(userId, context.getUserId(), "UserId should match the one provided in constructor"); + } + + @Test + void testSetUserId() { + UserContext context = new UserContext(123L); + Long newUserId = 456L; + + context.setUserId(newUserId); + + assertEquals(newUserId, context.getUserId(), "UserId should be updated"); + } + + @Test + void testToString() { + Long userId = 123L; + UserContext context = new UserContext(userId); + + String expected = "UserContext(userId=" + userId + ")"; + assertEquals(expected, context.toString(), "toString should return expected format"); + } + + @Test + void testEqualsAndHashCode() { + Long userId = 123L; + UserContext context1 = new UserContext(userId); + UserContext context2 = new UserContext(userId); + UserContext context3 = new UserContext(456L); + + assertEquals(context1, context2, "Objects with same userId should be equal"); + assertEquals(context1.hashCode(), context2.hashCode(), "Objects with same userId should have same hashCode"); + + assertNotEquals(context1, context3, "Objects with different userId should not be equal"); + assertNotEquals(context1.hashCode(), context3.hashCode(), "Objects with different userId should have different hashCode"); + } +} \ No newline at end of file From 5d8581ac319bd95311148b25aa53ab62fa88c1cd Mon Sep 17 00:00:00 2001 From: CMD137 <2992456841@qq.com> Date: Fri, 23 Jan 2026 00:42:25 +0800 Subject: [PATCH 2/7] fixed:pom.xml Run 'mvn spotless:apply' to fix these violations. --- pom.xml | 2 +- .../iluwatar/threadspecificstorage/App.java | 17 +++++------ .../threadspecificstorage/RequestHandler.java | 12 ++++---- .../UserContextProxy.java | 19 +++++-------- .../threadspecificstorage/AppTest.java | 16 +++++------ .../RequestHandlerTest.java | 4 +-- .../UserContextProxyTest.java | 28 ++++++++++--------- .../UserContextTest.java | 22 +++++++++------ 8 files changed, 60 insertions(+), 60 deletions(-) diff --git a/pom.xml b/pom.xml index 05884a780537..92b5da5460ce 100644 --- a/pom.xml +++ b/pom.xml @@ -231,7 +231,7 @@ table-module template-method templateview -thread-pool-executor +thread-pool-executor thread-specific-storage throttling tolerant-reader diff --git a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java index 882852ef4c49..8d784787eede 100644 --- a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java +++ b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java @@ -3,9 +3,8 @@ /** * Application entry point demonstrating the Thread-Specific Storage pattern. * - *This example simulates concurrent request processing for multiple users. - * Each request carries a user token, and user-specific context is managed - * transparently using thread-specific storage.
+ *This example simulates concurrent request processing for multiple users. Each request carries + * a user token, and user-specific context is managed transparently using thread-specific storage. */ public class App { @@ -23,11 +22,13 @@ public static void main(String[] args) { // Simulate tokens for different users String token = "token::" + (i % 3 + 1); // 3 distinct users - new Thread(() -> { - // Simulate request processing flow - RequestHandler handler = new RequestHandler(proxy, token); - handler.process(); - }).start(); + new Thread( + () -> { + // Simulate request processing flow + RequestHandler handler = new RequestHandler(proxy, token); + handler.process(); + }) + .start(); // Slightly stagger request times try { diff --git a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java index 6be42db5f62a..eca0001a0e51 100644 --- a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java +++ b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java @@ -7,8 +7,8 @@ /** * Application Thread * - *
Each instance simulates a request-processing thread that uses - * the Thread-Specific Object Proxy to access Thread-Specific Object. + *
Each instance simulates a request-processing thread that uses the Thread-Specific Object Proxy + * to access Thread-Specific Object. */ @AllArgsConstructor @Slf4j @@ -17,11 +17,9 @@ public class RequestHandler { private final String token; /** - * Simulated business process: - * 1. Parse userId from token ("Token::userId"). - * 2. Store userId in thread-local storage. - * 3. Later, retrieve userId and use it for business logic. - * 4. Finally, clear thread-local to prevent memory leak. + * Simulated business process: 1. Parse userId from token ("Token::userId"). 2. Store userId in + * thread-local storage. 3. Later, retrieve userId and use it for business logic. 4. Finally, + * clear thread-local to prevent memory leak. */ public void process() { LOGGER.info("Start handling request with token: {}", token); diff --git a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/UserContextProxy.java b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/UserContextProxy.java index ca5e15e93919..998fc514bbf4 100644 --- a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/UserContextProxy.java +++ b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/UserContextProxy.java @@ -3,32 +3,27 @@ /** * Thread-Specific Object Proxy * - *
The Thread-Specific Object Proxy acts as an intermediary, - * enabling application thread to access and manipulate thread-specific objects simply and securely. + *
The Thread-Specific Object Proxy acts as an intermediary, enabling application thread to + * access and manipulate thread-specific objects simply and securely. */ public class UserContextProxy { /** - * Underlying TSObjectCollection (ThreadLocalMap) managed by JVM.This ThreadLocal acts as the Key for the map.So That there is also no key factory. + * Underlying TSObjectCollection (ThreadLocalMap) managed by JVM.This ThreadLocal acts as the Key + * for the map.So That there is also no key factory. */ private static final ThreadLocal
userContextHolder = new ThreadLocal (); - /** - * Set UserContext for the current thread. - */ + /** Set UserContext for the current thread. */ public static void set(UserContext context) { userContextHolder.set(context); } - /** - * Get UserContext for the current thread. - */ + /** Get UserContext for the current thread. */ public static UserContext get() { return userContextHolder.get(); } - /** - * Clear UserContext to prevent potential memory leaks. - */ + /** Clear UserContext to prevent potential memory leaks. */ public static void clear() { userContextHolder.remove(); } diff --git a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/AppTest.java b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/AppTest.java index 0a01ee3be2ba..143c57541b49 100644 --- a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/AppTest.java +++ b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/AppTest.java @@ -1,15 +1,12 @@ package com.iluwatar.threadspecificstorage; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayOutputStream; import java.io.PrintStream; +import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Tests for APP class - */ +/** Tests for APP class */ class AppTest { @Test void testMainMethod() { @@ -18,7 +15,7 @@ void testMainMethod() { System.setOut(new PrintStream(outContent)); // Run the main method - App.main(new String[]{}); + App.main(new String[] {}); // Give some time for threads to execute try { @@ -29,10 +26,11 @@ void testMainMethod() { // Verify output contains expected log messages String output = outContent.toString(); - assertTrue(output.contains("Start handling request with token"), + assertTrue( + output.contains("Start handling request with token"), "Should contain request handling start messages"); // Restore system output System.setOut(System.out); } -} \ No newline at end of file +} diff --git a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/RequestHandlerTest.java b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/RequestHandlerTest.java index 6afbaf7f0178..e58cbb4a5661 100644 --- a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/RequestHandlerTest.java +++ b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/RequestHandlerTest.java @@ -1,9 +1,9 @@ package com.iluwatar.threadspecificstorage; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + class RequestHandlerTest { @Test diff --git a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/UserContextProxyTest.java b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/UserContextProxyTest.java index 56eb44ad4d15..fd8cfbd6b7aa 100644 --- a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/UserContextProxyTest.java +++ b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/UserContextProxyTest.java @@ -1,14 +1,12 @@ package com.iluwatar.threadspecificstorage; +import static org.junit.jupiter.api.Assertions.*; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests for UserContextProxy class - */ +/** Tests for UserContextProxy class */ class UserContextProxyTest { private UserContext userContext; @@ -28,7 +26,9 @@ void testSetAndGetContext() { UserContextProxy.set(userContext); UserContext retrievedContext = UserContextProxy.get(); assertNotNull(retrievedContext, "Retrieved context should not be null"); - assertEquals(userContext.getUserId(), retrievedContext.getUserId(), + assertEquals( + userContext.getUserId(), + retrievedContext.getUserId(), "Retrieved context should have the same userId"); } @@ -52,12 +52,14 @@ void testThreadIsolation() throws InterruptedException { UserContext context2 = new UserContext(456L); UserContextProxy.set(context1); // Create another thread to set different context - Thread thread = new Thread(() -> { - UserContextProxy.set(context2); - UserContext threadContext = UserContextProxy.get(); - assertNotNull(threadContext); - assertEquals(456L, threadContext.getUserId()); - }); + Thread thread = + new Thread( + () -> { + UserContextProxy.set(context2); + UserContext threadContext = UserContextProxy.get(); + assertNotNull(threadContext); + assertEquals(456L, threadContext.getUserId()); + }); thread.start(); thread.join(); // Main thread context should remain unchanged @@ -65,4 +67,4 @@ void testThreadIsolation() throws InterruptedException { assertNotNull(mainThreadContext); assertEquals(123L, mainThreadContext.getUserId()); } -} \ No newline at end of file +} diff --git a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/UserContextTest.java b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/UserContextTest.java index 636e8bfcdc2a..3a7864c9e92e 100644 --- a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/UserContextTest.java +++ b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/UserContextTest.java @@ -1,11 +1,10 @@ package com.iluwatar.threadspecificstorage; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; -/** - * Tests for UserContext class - */ +import org.junit.jupiter.api.Test; + +/** Tests for UserContext class */ class UserContextTest { @Test @@ -13,7 +12,8 @@ void testConstructorAndGetUserId() { Long userId = 123L; UserContext context = new UserContext(userId); - assertEquals(userId, context.getUserId(), "UserId should match the one provided in constructor"); + assertEquals( + userId, context.getUserId(), "UserId should match the one provided in constructor"); } @Test @@ -43,9 +43,15 @@ void testEqualsAndHashCode() { UserContext context3 = new UserContext(456L); assertEquals(context1, context2, "Objects with same userId should be equal"); - assertEquals(context1.hashCode(), context2.hashCode(), "Objects with same userId should have same hashCode"); + assertEquals( + context1.hashCode(), + context2.hashCode(), + "Objects with same userId should have same hashCode"); assertNotEquals(context1, context3, "Objects with different userId should not be equal"); - assertNotEquals(context1.hashCode(), context3.hashCode(), "Objects with different userId should have different hashCode"); + assertNotEquals( + context1.hashCode(), + context3.hashCode(), + "Objects with different userId should have different hashCode"); } -} \ No newline at end of file +} From 34cc03f6bb576f4cd466743f05c0f94417d122bb Mon Sep 17 00:00:00 2001 From: CMD137 <2992456841@qq.com> Date: Fri, 23 Jan 2026 01:30:20 +0800 Subject: [PATCH 3/7] fix: address Sonar security hotspots for Random usage and e.printStackTrace() --- .../main/java/com/iluwatar/threadspecificstorage/App.java | 5 ++++- .../com/iluwatar/threadspecificstorage/RequestHandler.java | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java index 8d784787eede..2de8c837dd90 100644 --- a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java +++ b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java @@ -1,11 +1,14 @@ package com.iluwatar.threadspecificstorage; +import lombok.extern.slf4j.Slf4j; + /** * Application entry point demonstrating the Thread-Specific Storage pattern. * * This example simulates concurrent request processing for multiple users. Each request carries * a user token, and user-specific context is managed transparently using thread-specific storage. */ +@Slf4j public class App { /** @@ -34,7 +37,7 @@ public static void main(String[] args) { try { Thread.sleep(50); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.warn("Sleep interrupted", e); } } } diff --git a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java index eca0001a0e51..3aa2aa75b913 100644 --- a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java +++ b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java @@ -1,5 +1,6 @@ package com.iluwatar.threadspecificstorage; +import java.security.SecureRandom; import java.util.Random; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -36,7 +37,7 @@ public void process() { // Step 3: Retrieve userId later in the request flow Long retrievedId = contextProxy.get().getUserId(); - Random random = new Random(); + SecureRandom random = new SecureRandom(); String accountInfo = retrievedId + "'s account: " + random.nextInt(400); LOGGER.info(accountInfo); From 106f06d8080aba527063a48c19a894b96d2f6dd4 Mon Sep 17 00:00:00 2001 From: CMD137 <2992456841@qq.com> Date: Fri, 23 Jan 2026 01:52:19 +0800 Subject: [PATCH 4/7] fixed: Run 'mvn spotless:apply'(remove unused import) --- .../java/com/iluwatar/threadspecificstorage/RequestHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java index 3aa2aa75b913..719c663c0641 100644 --- a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java +++ b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java @@ -1,7 +1,6 @@ package com.iluwatar.threadspecificstorage; import java.security.SecureRandom; -import java.util.Random; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; From d3a8041057948fc91ee5eb339b1defc5babd3b32 Mon Sep 17 00:00:00 2001 From: CMD137 <2992456841@qq.com> Date: Fri, 23 Jan 2026 11:27:24 +0800 Subject: [PATCH 5/7] fixed: Refactor Thread-Specific Storage pattern implementation to address Sonar issues: remove unnecessary instantiation, add private constructor, use diamond operator, and update tests. --- thread-specific-storage/pom.xml | 6 ++++++ .../com/iluwatar/threadspecificstorage/App.java | 6 ++---- .../threadspecificstorage/RequestHandler.java | 13 +++++++------ .../threadspecificstorage/UserContextProxy.java | 5 ++++- .../iluwatar/threadspecificstorage/AppTest.java | 12 +++++++----- .../threadspecificstorage/RequestHandlerTest.java | 14 ++++++-------- 6 files changed, 32 insertions(+), 24 deletions(-) diff --git a/thread-specific-storage/pom.xml b/thread-specific-storage/pom.xml index 6e54c3fc406f..1af91514369b 100644 --- a/thread-specific-storage/pom.xml +++ b/thread-specific-storage/pom.xml @@ -59,6 +59,12 @@
mockito-core test ++ org.awaitility +awaitility +4.2.1 +test +diff --git a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java index 2de8c837dd90..a84388acfe08 100644 --- a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java +++ b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/App.java @@ -17,9 +17,6 @@ public class App { * @param args command-line arguments (not used) */ public static void main(String[] args) { - // Initialize components - UserContextProxy proxy = new UserContextProxy(); - // Simulate concurrent requests from multiple users for (int i = 1; i <= 5; i++) { // Simulate tokens for different users @@ -28,7 +25,7 @@ public static void main(String[] args) { new Thread( () -> { // Simulate request processing flow - RequestHandler handler = new RequestHandler(proxy, token); + RequestHandler handler = new RequestHandler(token); handler.process(); }) .start(); @@ -37,6 +34,7 @@ public static void main(String[] args) { try { Thread.sleep(50); } catch (InterruptedException e) { + Thread.currentThread().interrupt(); LOGGER.warn("Sleep interrupted", e); } } diff --git a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java index 719c663c0641..d5f2cca2830d 100644 --- a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java +++ b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/RequestHandler.java @@ -1,7 +1,6 @@ package com.iluwatar.threadspecificstorage; import java.security.SecureRandom; -import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; /** @@ -10,12 +9,14 @@ * Each instance simulates a request-processing thread that uses the Thread-Specific Object Proxy * to access Thread-Specific Object. */ -@AllArgsConstructor @Slf4j public class RequestHandler { - private final UserContextProxy contextProxy; private final String token; + public RequestHandler(String token) { + this.token = token; + } + /** * Simulated business process: 1. Parse userId from token ("Token::userId"). 2. Store userId in * thread-local storage. 3. Later, retrieve userId and use it for business logic. 4. Finally, @@ -29,13 +30,13 @@ public void process() { Long userId = parseToken(token); // Step 2: Save userId in ThreadLocal storage - contextProxy.set(new UserContext(userId)); + UserContextProxy.set(new UserContext(userId)); // Simulate delay between stages of request handling Thread.sleep(200); // Step 3: Retrieve userId later in the request flow - Long retrievedId = contextProxy.get().getUserId(); + Long retrievedId = UserContextProxy.get().getUserId(); SecureRandom random = new SecureRandom(); String accountInfo = retrievedId + "'s account: " + random.nextInt(400); LOGGER.info(accountInfo); @@ -44,7 +45,7 @@ public void process() { Thread.currentThread().interrupt(); } finally { // Step 4: Clear ThreadLocal to avoid potential memory leaks - contextProxy.clear(); + UserContextProxy.clear(); } } diff --git a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/UserContextProxy.java b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/UserContextProxy.java index 998fc514bbf4..468e554b0bf6 100644 --- a/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/UserContextProxy.java +++ b/thread-specific-storage/src/main/java/com/iluwatar/threadspecificstorage/UserContextProxy.java @@ -11,7 +11,10 @@ public class UserContextProxy { * Underlying TSObjectCollection (ThreadLocalMap) managed by JVM.This ThreadLocal acts as the Key * for the map.So That there is also no key factory. */ - private static final ThreadLocal
userContextHolder = new ThreadLocal (); + private static final ThreadLocal userContextHolder = new ThreadLocal<>(); + + /** Private constructor to prevent instantiation of this utility class. */ + private UserContextProxy() {} /** Set UserContext for the current thread. */ public static void set(UserContext context) { diff --git a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/AppTest.java b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/AppTest.java index 143c57541b49..775ded85b8ea 100644 --- a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/AppTest.java +++ b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/AppTest.java @@ -1,5 +1,8 @@ package com.iluwatar.threadspecificstorage; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayOutputStream; @@ -18,11 +21,10 @@ void testMainMethod() { App.main(new String[] {}); // Give some time for threads to execute - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } + await() + .atMost(5, SECONDS) + .pollInterval(100, MILLISECONDS) + .until(() -> outContent.toString().contains("Start handling request with token")); // Verify output contains expected log messages String output = outContent.toString(); diff --git a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/RequestHandlerTest.java b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/RequestHandlerTest.java index e58cbb4a5661..e5f5968e7794 100644 --- a/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/RequestHandlerTest.java +++ b/thread-specific-storage/src/test/java/com/iluwatar/threadspecificstorage/RequestHandlerTest.java @@ -8,27 +8,25 @@ class RequestHandlerTest { @Test void process_shouldStoreAndClearUserContext() { - // Given - a real UserContextProxy - UserContextProxy proxy = new UserContextProxy(); - RequestHandler handler = new RequestHandler(proxy, "token::123"); + // Given - a request handler without proxy parameter + RequestHandler handler = new RequestHandler("token::123"); // When - process the request handler.process(); // Then - after processing, ThreadLocal should be cleared - assertNull(proxy.get(), "ThreadLocal should be cleared after process()"); + assertNull(UserContextProxy.get(), "ThreadLocal should be cleared after process()"); } @Test void process_withInvalidToken_shouldSetUserIdToMinusOne() { - // Given - a real UserContextProxy - UserContextProxy proxy = new UserContextProxy(); - RequestHandler handler = new RequestHandler(proxy, "invalid-token"); + // Given - a request handler without proxy parameter + RequestHandler handler = new RequestHandler("invalid-token"); // When - process the request handler.process(); // Then - after processing, ThreadLocal should be cleared - assertNull(proxy.get(), "ThreadLocal should be cleared even for invalid token"); + assertNull(UserContextProxy.get(), "ThreadLocal should be cleared even for invalid token"); } } From 1bc4eddd836b9b814429af738d9bbe555bdee954 Mon Sep 17 00:00:00 2001 From: CMD137 <2992456841@qq.com> Date: Fri, 23 Jan 2026 12:20:07 +0800 Subject: [PATCH 6/7] fixed: update uml and README.md --- thread-specific-storage/README.md | 25 +++--- .../etc/ThreadSpecificStorageUML.png | Bin 28087 -> 24939 bytes .../etc/thread-specific-storage.urm.puml | 72 +++++++++--------- 3 files changed, 53 insertions(+), 44 deletions(-) diff --git a/thread-specific-storage/README.md b/thread-specific-storage/README.md index a08b1f6af199..e3109595fd84 100644 --- a/thread-specific-storage/README.md +++ b/thread-specific-storage/README.md @@ -63,9 +63,6 @@ of the user context data. @Slf4j public class APP { public static void main(String[] args) { - // Initialize components - UserContextProxy proxy = new UserContextProxy(); - // Simulate concurrent requests from multiple users for (int i = 1; i <= 5; i++) { // Simulate tokens for different users @@ -73,7 +70,7 @@ public class APP { new Thread(() -> { // Simulate request processing flow - RequestHandler handler = new RequestHandler(proxy, token); + RequestHandler handler = new RequestHandler(token); handler.process(); }).start(); @@ -93,9 +90,12 @@ Here's how the request handler processes each request: ```java @Slf4j public class RequestHandler { - private final UserContextProxy contextProxy; private final String token; + public RequestHandler(String token) { + this.token = token; + } + public void process() { LOGGER.info("Start handling request with token: {}", token); @@ -104,13 +104,13 @@ public class RequestHandler { Long userId = parseToken(token); // Step 2: Save userId in ThreadLocal storage - contextProxy.set(new UserContext(userId)); + UserContextProxy.set(new UserContext(userId)); // Simulate delay between stages of request handling Thread.sleep(200); // Step 3: Retrieve userId later in the request flow - Long retrievedId = contextProxy.get().getUserId(); + Long retrievedId = UserContextProxy.get().getUserId(); Random random = new Random(); String accountInfo = retrievedId + "'s account: " + random.nextInt(400); LOGGER.info(accountInfo); @@ -119,7 +119,7 @@ public class RequestHandler { Thread.currentThread().interrupt(); } finally { // Step 4: Clear ThreadLocal to avoid potential memory leaks - contextProxy.clear(); + UserContextProxy.clear(); } } @@ -133,7 +133,12 @@ The UserContextProxy acts as a thread-safe accessor to the ThreadLocal storage: public class UserContextProxy { // Underlying TSObjectCollection (ThreadLocalMap) managed by JVM. // This ThreadLocal acts as the Key for the map. - private static final ThreadLocal userContextHolder = new ThreadLocal (); + private static final ThreadLocal userContextHolder = new ThreadLocal<>(); + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private UserContextProxy() {} /** Set UserContext for the current thread */ public static void set(UserContext context) { @@ -219,4 +224,4 @@ Start handling request with token: token::2 * [Pattern-Oriented Software Architecture, Volume 2: Patterns for Concurrent and Networked Objects](https://www.amazon.com/Pattern-Oriented-Software-Architecture-Concurrent-Networked/dp/0471606952) * [Java Documentation for ThreadLocal](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ThreadLocal.html) -* [Java Concurrency in Practice](https://jcip.net/) by Brian Goetz +* [Java Concurrency in Practice](https://jcip.net/) by Brian Goetz \ No newline at end of file diff --git a/thread-specific-storage/etc/ThreadSpecificStorageUML.png b/thread-specific-storage/etc/ThreadSpecificStorageUML.png index 1d2e0d27d7702005daeb4607bcec352f8feddb5e..d4957dd1cc2a3a488979328616ec1671a2050675 100644 GIT binary patch literal 24939 zcmafabyOTn_$3 v8sWAftIz7`TNho!p!~iXGhD=zSgESt`@Z(7Ihw0 zR#vuO>>QjOEq!fl8hq^=0~{S4-96mh{5{;`y&Ri@+*%_%JUqNo{Cv{_{W605z6E-> z$A*T62BZfEWrT)hMTB&R1au}vMMXts$He6&M3%(HRwPFC<;8smr>3SRR-~j %7eVj3`*d7CkOCh` zj4#e&z_K+B%!vti 66Y3=e8nCxYe-- qfxM zxr}Us>mv~|q9N6=f_}m{BZP#c$oHyX7C>@w*fsoky*#q-I1%!^eA#>Au*ynEvh!1% z70Z$r55hYA84@Bw$~kHx#>%=G^!|t3C|Itbv?-o(IK{W3)SVsQAD7v?3HZv(zBm5y zpql-&g%IxC8zeF{fc+5F=o$_OR_eolAuQDY#uIc$X{_KjFDS$50U~@sc9jNf-Y+4~ z>&r0mfbNB}&q)vSD944@U*2AcTuD@slmG@EEJ&`(nAArX8`ow >FBH6 zPk+ot|Mh9naRnnBxNOnIJbi|9+YHx@<;41=W#iN!ROcc>7gfnEi*na=x@Z(I2Qh`d z`U^-oi{QQc$q5MoizpI^st2>gr8Ta1b9d~hoF|p;lD2nNUmKLCywW(dD&G6BqlEhu z_9~iR$AqeR^Qug+9Q_$f4<)IbUK47mlqjKRL~}`7=+%;_SNfdgN|OQ2PIMne|Ci2} ze2rsq$0cdy zGF!c7BCH waO4{b?spk?_4| G8*QP*S|R9!ue2Ze)fK8Fd`NeVUhgx6AFkgF0* =G9f`MSe|an?=ox-uiv`zth@ot*HymAB70) zcJ19fFpihUiWVl*@WT54-CIB2L6|D6xeQCuVUil`6i4!WBik@xM0>?fI4El!1B z@;V^@G}_?nYY^As%DUWW>%$hz<;Z80;Gvb$?>}FVeu6oJH(e%S>M?%QcjYIVR%YCp zIx7Y5)EiN0si86sufUDypwObV^tzg?X;l<1bm-*>esJ&d32Jv=w5*f6?Nveb7kvSh zpi;YCyLDZ= t;5vdQ2xm zT~=$Ou4iEOn3WlE!{Tw#z*eIWlP-J5T)Wv1MwqMgpvnK(f6T=VZr-|c?^^Uv< 0^1qkt-Kd$v1visjCi2ZYW-4l7_iRCF3J z^HKPjn^+32oPA~h7`}P@SPx`jtNZ^14%fm9|Ie#3I*Ij)a^b`DSy9{HF4dplUpBr5 zdq -)V#uS!Rdje3!d@z-=5(p57>53pf?j zV<@9u)+MW`tiHd-qdlYac1NtncgUAV1}c$EUj6up@U6vl&R +E^xYwzCV5e|QFX@8iB= z3`{#v>fUGLr2Eh@ERQjVbefn9^MKpfl6((793>cSjo^&pptP(W#BRez(o 0R>DhliIXHtgWOXtw;2jKr4~3>=r4=}_>CDxL 5JL)I{# QgIe4h7s%e<{UV%SUZJY#)n;mTeJn(X!vYhwXRh7`D|GYY(M zXTJ1 }zmE%S zbrf$Zf7}M>OZM6y>vl`+*eN1>V<2eyDvI-Tr+LlZdvYX|7JX8=_Wg;wPPNj&K4dBu zxUY&y43rG~ClsFKfZLI7F3J@!4pAv#?Ic;P0f#$k*_!i2G9T`*|4sBiiU0TYOl<(R zTe* oa}Mr*dmw*3kuU=Z&=P|__%_jLu{38Q*Vt7o`!}za+74V *E_ZT3uA z%yuW|EAowWioi8_)O4Hr(>99Qjo0WGb}Zytes-N?D2fb+pOeqDUks1#TExA=e#mtL zt&zOrba3|;a_c_v@8{o_5~;Ao2Xq{>dBxt*AFW+8&mPbr*BI0P{-m~%S2uY$3WT1@ zbF+ChY1t5B3x29ro20!asBG@8t|X{n)I+gKHnF4q1Gnht5aPyXlRoWF@U3EBc%v R%n^(7bPbY3UxqV$Qp`_PZx-Iel}b+4uOf z)S)Y6DTce1NGHEJJS+o!ZHr3L`Kg6wpBnP*itOK-2E*s*vUF4>W?qNRc`05=%g9Ul z9Q<}SDz9*`7$H(q X}2%ybS`A2UU{Usa6q*3H=F9aI=LChp_iJEvD(XqPrrdMr9A3?}Nqm zw0RkQHBm`QnHz3F2sO$!Xw1{4D}39FDY)6+$A|5%Won1$xC*jaz0NEO@Wawm8)pDr zbMy+toSqjb{?X80P0yh12w3(%wC8DdJ@oOb*IMOVD%RqMVfU~rJQ-j0?RhXN57k_! z^tvhIDjAoKgCh;2Ie40emBo-m+o80#+e^=2?zi5-ZCS?UA7{S)u*9+~NM@6PE(OTQ zu)h9u^Sk}=Qv=sXjnZ*y>Qmg>6f;aPv6Es{vv`4-wD6F>smdIm1qxQ@&*L})?ys_A z2jBYy7+i5Aw03O#DVfzYv*s9eO<{G^09ZX?K-~u<9&{^iD)c%payL>f-KbBu8dybl z`4or`CtE`zHK&Y^XE$a(N?v5N;QQBdn{}HFsg6$e?;p?o(GB=+=&-xPfVED^`E%^Z z4au2+2G*J02WtO{QtN%sk~ WVS7HgE>mpt$V zPvf8eT=Ux$<6Bf#Gb=_kJ1cd|<@ n)w3InbT`upF`RP)XXe+67V+=J$9L4UL(zn*#$D_J zY6xaHA-`vvI>2M0>_l$Ar21A>@kDocmj`S~I8nFO5?tQWvvP+-M$JfU@z<5rF5uX7 zc0=OvN?{GIS9F&SiND5mgwqD}No;knwx6Ml^@y(x0(y nQf#46b zk$uF0dCU?tWU44Xi%bS_K~|2C11#}Kle5)VhhEekJaO?1A=V_|V`n-o3+hI N_H-)E7^)p>SFI|&}5*(>+?o&EEgbv(6xXPVub2fl!vVi2i zKHxLq&x=p^{s^wEv9Xhq?^D5ZZ?HSF6}`Qa7 8|0YutqECQ$3$Get@FhYr%$aCyk!`@v@ad+S zIUZHJ<7#`erfL}BUf)S=lC&j6mq5os#ztpwR+B(KdK>15@6gU`2nI64=~3u0+d#tI z 2@9Td?1RRbh`E=|N*u9fBHp6$%xJp&wAB zheA>xfrKV?Rbn~!hhm+}RU-Ii&R;N~%66OESWff8Mx0him^aVYSZtbuJpr@R;W^r} zS20NPdGSU4K2o$pv!KY0_7Iwdyc5-hU-eiIpW`rnOe7CLH+yU>!hQHk@Ud%>mhX8` zu43m08r8l!AP_B$-o^-mt6YXKH)`7`v>$-d-l{?p6gX1~^vbRPw6JVL9h$PRJOT#c zer$8MB7VK5`)hXq1|Jj#M@`ur3WVUHi=^L)jM$|z!#He1Kgu{_NsJzlKg r!-Z%<)-<0pvmzmGN%KeP|hx2k1wFH>!#*0!SjM zVw5EM421Gj9MWV>`x9$1C27F-X|HF|tR(7Hn|sBPU@Q>fMJ4|aGrW)~5(F?B=-$Wh zA2X^}7E6Kze6D-Ako*4F@7T=3%!C6wgGZtD0x+?!q<1&}iTPzjaIYlf)Ll5iX*j4B z0Xs#D`JSt77kUGS8}KGGaf-efFE|X43KEi-ku? Z%q1&v zw>>k(TF!V#$W|(q{#Id7AeR7qCI0%kgBWM>D3Uyyh@k9GpDvSrEp&PmA)#vlA`9dB zv+6O rlShu0M0ZzQ*#6A1Yh z61~5BZt4F(gqsZ}@DO*H5seL80T-ZhrYf7Q|K43kEnR2I4ZnO=U# R1H zGHnR1>~g%K7FS puO?8eY~2jBR4c2(%hoz+jEo&XGg`ZNgU~6jlINh1`T@W %hr(Sh&0T-A?WCdByDuM{&o2l5X4P~1LZMA*Zu`)t6Nl5*KdR+DecX_shD}Y zHj9}*`55Ydndu)R8=hlN*s5@W|7AcGQUR{p7&AAphepFD474B33y4tWciw-49BKM# zM5x~~*u8A*SD0?n7*oNHi? cAws}}PcGc0Wm7so%i 0F3Pb-% zor1}_cn2%$kPc@=`8ms7*OnI^gx-k@CGBA6s3^o;X76z4$ny^ohv70o1=3YfUuNah zXZ2S$Im=zV`;KQHujFOtyC(Es3|Q#{t>$X(_&%_ot=_!A4FMYDqJC0oTy2r9j{TAm zpZAqJ#CxIg|7K~4P>FDO J5vk(dzfaCIr zZn;?LN+)zWaf@tt8+%GH%@Y^n&UEfR+?@&aGoADHIVflNf&Z;+7%uKCSPTXz%+Qg8t`b-*NJM +?}(BgM1MEh57nP162d+6=Q zw7Je(7prbje%AfFbVk-X_ L)oaK~tz>U&JwqnVi*gXGw&4GOy8F5=hf(1Pz#k z>PoM^-8%~{y>zc YBa3wBU7#0Vh1XmR=c}gX=#g0fc=s^fk{Thb==Fj=e9R8>#mX;ps!GG#q!EKVN7i z9)2PT`K8(*2m#4N4f#Z@cRd;c(~!nAVwITkC= 4 vARFr?Y!Jx+K4s6m374 zx`5sAjiF~eUs%Urd;K@Z^b*4SFjv_I>qX*v`U}LwXAOA&)W}+)ZE0B0^|SGH7nT2} z#uf!G&*kni+X<|sBn-(QV%xr^-p|y)W48xDzFXnGK@Cz%kT?XlfV=9`uEwsQ0-?p2 zI=sK+=4X -9En<@g`veytk2lgZt8fnD|j2deJ4KN%1oUPWAv1*b$`aTNnY^q45J90Z1}nJ z)Yy3AE;i1&CyGC@C^t{o+fxi~)hh6yDLj{vM(OPuTYX~ckw~e2h9!ik439Lqk;%Iz z523}Ho%J@~;AHiB;{_)$S^Rbl797 }cE-8OzvIi5g z_0@x-4}c!B1i8e(eCqNBwlg#ON^#33+xb-_^kYOlzwh+wBdHo#?4BMgb9IWHO2t@- z)o86HIWBco9r8JQR6+SbpXF{1w=5qCROPYUAm+$gn(bvwH?v5mCGgu~Jx8%ez2~a) zBk`tBvIeq$Xe80xoAGOhffwKFfE*1SIZm=)rRC8sO?3m|H5~sZh<8-T;Pu68)&UES z#p=HDarA a1(m>H+Tfd$*0Qf_D-#WE1N5&ax@NOt%o9G#(g4f_%Rm692iKxGny z0$flA@v~O4+lv>k2t{G`l&yL;wG@Az!XLY0q7IDEgYGUJj$f5JT)#QQxWNIlsEn8% zo^`-&X<=|c=r<0_`DjTDVLnXBdbMDeoBQiT2tG_$hV^p4Y@T2zsC`+$L$Uk~*Tn$^ z jF9Vm~EkG=OZnLwk&4m9}}gopeRelM(0yAe;$?4oV7 zKcVmbw9!_n^&^bh-`=l9_Uq`>n%MaHr1L|%&>*(eY7UaNzx9^J!%XJ`t9E)cYU_1T z=fh1Uu4xc6G;lXmwvJ3OXF2h1X7DhGqWV=a3<1d#6EjO|BavN1F#)KOWRWJbM +U )~0=l%B?d61{$-)x?lgB|KH;el1@ZUsUQnA3@s zhPr-KK3mDZvFuhk9=;lvm@Wk>pq=V}(A1i^J3T>|_&gN-L+HG|7RSqrR @lYpbCCg!sKAJUzuB;*cfN|Hr_+r;r+bD;$<^j%x@4ybF%M)E&PUFXL zp-0pZ2=2J?bo%OkO^=8z5G?cjn}Y1$+mXPs_`ZyNEx?#yaLx8F079WDsCQNnk-)ND z+!)#_Do@hb9e9mw{oOe#WV#V(6e%`Uz(klsPUgZG72_kBCo0t2N{cDB1P+jl3S##T zFFfrLooNN8@M0MB|Fpvr-v9@|%H_aMZA%wNz7q$_as*hlt6@a4SmJx&fG1Q+0rh*? z_tvmpGa nL+vh5^Qq6rORTvUfxSUmLo|Hqdc@%!XHt@P-~ereFlu rxs86>|BF*bQWGF%F$|Mj!2?~xZbFhXA)0iylSM{Sh&OS)Kfdi`i z8(oIAsq#OP%UKkhwu4v$fW5C~oiy!w^u3$rWlwfmF{Fq(*thq`<_wgAxT0$|J0|d; zfEQ^eVhwJAK*n|~so2W+IHVQBhg7bd2vZy8l>FIFdDSR2I-V#*u%oy+CLbSt4VH{& z(gZvITDcioMgPIhw@xz`j&3x;pN61yciHvA5 6z%d6J+`lLx_@1!DSoL)wF zZBXJ-=0nD}D!*AA#haUt<|^EHx<1lS^+2KD_JSQPtZ2jUO59t0`k5A%Ue8_Af-<(C zb!_Q`{&%{AD1ZMLza!s+y_wVA{Kgc~D-DJynF8$4U&9kLw=Tg&1;I!qd>6H|x &jCr|#TncH^DbxQKUb2+YWcd^Hb?3mfh5KcW zW5>0jw`dR~*L7aRIvs`CU5l)}T1BIs*}(98w{@#wZ3|m5(`~gS _nC{wJp)8oN9C@mzIjsqy!EB!=SB |egtK?;6%tnyr!S7!R6DKZnv zj3b|oZZmhIN&dp%0<+CK2yy$ByO;oaH7Ru6;_?fvZ_-6ldX9mk+tbJe#DL={PCtGf z@`}1aDiyLX?3=<{duQtF&eLI1G!C(E18rgYmsPTN{u$D7nFF%vFGNknT~AtX18O1s z9ujvZg~;S0h-n1b2FV^QMAUs4SI(>Sr?P?WnH v^3-vMmWTYhl=HO_!3% zv77KbyBD6@EL`jfX$@pUa=UmQQt*n;EDMbV5A-V x_AaJL@I8!WU`jh@$>=75XK_Os`!-NbPM<5++8%mdmkq}}c( zs2gY56G`v8hduqYCK@itq!k%qxZFaqmcAlH1&%kKv@UPgeadeGt1}^LpWk>dTl|tp zGXf`U8Wy!O2oQ@|SkV^~{OB?2O*~lFLOS&&=M~jff{3T3Tlz@m8`JR{-y$E98Zui? z!$>54rj0Z;bTuOwmM!473}wkm)ZiJ|HcDCIQUu)p3^!ffi_UN``$tHn=7+m^*vC7z zwY^trNh^4b9iLqOv6hCX({I_|#ck?_+?+Npy?QeLp&2;-Bf16d#qA%Q+#!uOUy7!K z?M!E*ghDjBY-!MDuN`a0;R^BPuX;MXy73%ZzsuwY&~-k7B8q_O6@qg`V^KZ-NJ#Nl zT=+*|h~<@Z#yu<&QL+}a(hvs})hTi`flmXn{$EA(uPQ3_ |p} 9$clR+qTd-g>Xp+`q@iM7)qWyW%JUud7ZR2u%5RVb$q%`xR zuY0e{=g3{br})l%5$XF=c9mE;H}l@nkJ(F1+}V+whtTY#`CUD~?5PjjZ(eWXgVML0 z)U*5w$7Q@T<3^k77Ar_mQSjCqonxKhk>6bcpZ;iHP0XLdrp8q?2=?AT;%KCvNA^XX z{_(EwyP3T@<3MWun7% )O`Aa28%B+%PY#MnOQr8=1CN#uZTFu!tS%arLFU zd(|RSOaSpoc@3qsMG3q5OE6PUrwxnb6z{}$6@Lrb_>%Nu01GOv1@}(RPskkZ+V8A! zGa@Nv; O#U$ECGw zQ>@}Uf|x;^QvW2Q8allebv_EdZrIsCTa>!mb?qMIg;C?Y7MSxc2!OxCz#$#8S^d4Y z{(2h=ipZh}<&YLU`eRe+x97<&(7R&!>up1E@*%Oy^Op|vD#6r`luSBp+WE9I ?9RnKX62|5{+&0t1YG41vyUuxBEM`(8T3tdpu^P)vtPIJ z*DAv25P2S|)t{A%1D%Qz?#X^#ic#y2=}maroZh#I*D;Pynv^R+oA$G{t+I(vI@W7n zqk_`V2Kw3h5oos_C*f%h5(FspWaQeWV;q$}8JnzAgTKiluT%elo86Ag$@;!ky_ElJ zmVyy}6rQFe 59wtwYTD|Kv!YBR^<~qL3UWp;32v*}AZ`rxl=B zMWhp;(o2bP>`QUn`nZ)QlMO!w?`}@I$J{^8v!~Jjod9i)vBNM^jdm@`A>2;nnkx{c zw#STE$XA`Z=glaHR|sQm>yYh!t2Q&pXY6zEytE|9hocZ3af0)x{p)#*BdI(BE)|6y z3zeRSR9gadKYM>WQpn?lS9Zv^?CUMNcc70Wskdr&8HKvdJ~_tLKbp%0HVZ*22|=2I z$DNh%kmC8akBz= 6{3XwsZQXu-bZ^ry9Hg9M_wwiQ;8BpLOb&_Uwpt>TqJY=tk+;&vPqZLn!>f z8b83TvK?kj@-4u$0-;S98GeW4CXd7LNVMF@AkL_WL0i!#_p)J(I(6yKV51oFg8k}O z5`KN_?Twu8U=^a=wHEUjauYV)-Wg6#33?EVCIU;NLY*7BA|c EnrW`R*&nU$f9pWl*P%?$8MxU zo7=1e@Qh>wfW&Ns?b{_Q?GvBk7HWZ2q{8!tD|dyJl2ivsPMw9X^FN{m(XkOvR_2UJ zjGF}+BSLbPuiZ^L#biO7h$xb@_a<@=5B1bZVf1D(I>x>?<3ILD4$66!oVTrj+@V^y zTgk*kL=x1=T{FFVZ-COHF4n5|r 6jo77yy#(;IAA67M`1vAt zn 5E^2$o=yA%6(Zp;l3Z zj0Mub{tLE5;d?@{YI+uH@U~3-?|II%=DGf66C3)3r;>A|ph#%emLa4RT?;ufE%o=# zz7N{WF63^Gu!?b7mRyL=C!=|Z!9 x8V*){p2MPUjB{Rp{RpXzy_sfnc=8 zP!_ewY5fAqugA0;ofCFQ5B*h{t2GRntD6#LIum-lWS*I>$;ZRCqLwuMOe}PT3q*?2 zCMD-pAc>9)#(kV>iNJhCJiJ^%s= kx-h0{EG;;C$OxlDg&06F zC>4nx?sRA(7-UeP@1*o%AAG7RN^H`?hZEX=m6y!XEMIewkOMcFtjZWhUY~kHXhL5~ zbF&e9S{9`yw(*0~*8`=ZU49g0cf=&0(jZ!C7O&8%;7hsku&h!vHv!z;!z#f+)-gh4 zHW0E2C8F#IeB5~kW)fpa24*6Z$_TlN477^GggZWQ02H}I!3gpde(JHB0qw%HEqL^o zT1K!a^%s#C2{MF_eHg@QVwmb;%Rp5KL~WR;ZfKQr;n#BrK)pmk!ZEv8T?{~+9dv#R zFcziu_=cc867gOSMv$Ci3yuOZLZn9M;e%3 j7~a6PM{Dj#?L-W>}HSO;Ycd^52K3CM;7j4f8G z-@5N*+ae>BYe+snC{eg*5(ko7jzXR=Py5Sh$};ly(|yIKTTwe-BKml|Q6MO3nSj8G z8OmTL;h})=6UP^}K!tw8%CLm6!8eFf)>`hTS{aY3SqS45R>QP|cnl%gQ<51gU4QF7 zJw&L^gVGb*ANY;Wht#qQnSzprmbd}r_3(i5Y)b53YAlq1KyugbnnvD;F~|(>0b9cO z`)SUma{2x^-8e1XAsLm!)<7T%or=4tqB?r0RYTBNm;ZAlt!y~HhpDfeP`L`pfOFLP zMy+BRb+5}txFy;m%R4H(yE{4=(~V43-A0}*;zWLbP`m4y%HvVfh-B_Ym2C?Eu k6B!U z7n4V~Sw{srP%Vg3MfK*N62_bKXcwW8^O-;g$^kejQdcikdkKfy2 q=lV13| zaJB1Iloas}?r4TOrEX#^&gHFx<^g @sXo* zpmddO0YaTI%m*r`y3%r0?+2SjgDp%hNjxM9{%+i7+Rvbj-k!qAZChe2Khap1sYucT z>G6JweGKr7aP{w39s(z-gWBAwr^p+H8^RaBr$P3zCBTsh;aSkokB8EFD@i~SFshed zKH2i?FXocw ;cn1<6l_&U~y}(O{IQHWw?-cH&C`v^DQAhhZ z{N=vx{iqgY5*6ZQFz0&&dcDSp)EcaQ5+w*`-4o_UtxiVabWnMSJb@B)Ops`c8^7H9 zZY2_e^sMruA4MokebI$KCMN?x9_}?_zBx0%_r$*`#Q}ltO9xJ3eHSW>ZhWXTAHmC< z)a`x)PR81I_N&}nzJceVcC_Y5IWV9c_oI^<4R{2BkH1z4yIJ}JA6e!lyg}D~6;~Nq z+sUl?=ix3+LpWXbZZhaNp{;%m;gL_L&g?YA_FGKUSJ($iiQdLp!Y# giHRCq_6Ga(wd1i?`$ )k_<0oW@{@i0g=EJ*od4DAydpe4A0qF2-r!R9;OVjNT>E~7h~AI4f8QO` z2AlOxrb5WX?$zkz3wdQBC$x9c{T|JDXG;5E%#QTCy107M4O6PN>i+JOF+{gXJc-`# zdHj}|504)Ycx_!I*mPzSd>l&dL+ii1u6fap-+d|wGr&_acZ^QrT(!{sM)UczOhZAT z;zb1=KFMg!1s}`)8Hw(~srmJ1(JA7O@z?( ryM6&X?0)JCa6fJBkpdNZOrEQ*GEHdBxg z2l*ISl~LlQxt_hh3kK6lfYUU&dARo-sNY}TMGihub`VSpIMz4?V)=87G&^XX*X3=T zg7BDqs3;XULh cnXwrQirk6XGtgtZo6j}6C0 zg}9gs{jzTb&`lVFnSo*1=9hKKL$gGR5?jJ`csEW|<7jRs`K*c(PGz$ZjS8s8>?N_l z5q9@NAqLn@iPWr!?VWa*!|$q4O6ui-K(Aj3ltHQ}Ic2KQpktk|#{xjoZ6w_SZs@P_ zDHVWI-MxfY_>P7lz&pm9mrkTg_RO)fBAOib17biGhE;Bvsw=3tS45P(G*VGLv*ah; z0*<5{G9bnZHoNSS< j+Qd6raa@r?8mU%5x=M)Z%uEV@JtHVGHjc*t #PZ>fW z2PKfhz_-lP9o&w!+nhwTb0Cc +Dqp+;vK&ZhoEEzB)r zyGA-+SVC)>!QI~8s?7hizW-BJ;iB4M{*lz@(~aBOG|7C;EtToO*LwLCcTm~4V%&Mm z!S)L&Z%e-senY;*x+!-eu{El$f;3>9Mxjs=j}zD04 jzGjdQ0{W_g52e3$~a2T zJ7RAmJco0SdA3%DijR42058J^-KMd>BNP;PV1+Y3Hp7lBa2bhovf|40K-x!?Q$}GW zgGgj@+I&raE(xw}0{1uj^A(>ff4}}Bxyn5EOK%avvSYAYJDi|!h>K0&bXcm3xI6s0 z-BnucS7DagT|58YQm=V9Oq${FMrKAk$0yZ}Cq3i7w{-$%=t=Ich(+HyF*F2N^DKC>g;DBxi>KHL7o;G-aFY37?Rr^{+zR)FjVkY$<24cWta?E z>3{b*7tT;jl8Ur59a=94mg+*eI#!=jZ%*0D9F7jdtdV-!9ft3SHa7&>