From a17c3fff503a78018563cf7ab78dad983d11b62f Mon Sep 17 00:00:00 2001 From: Marius Bogoevici Date: Wed, 16 Mar 2016 21:40:58 -0400 Subject: [PATCH] Documentation * Introductory and concepts section * Programming model * General Binding Properties + Rabbit * Added kafka options * Documentation revisions --- .../src/main/asciidoc/images/SCSt-groups.png | Bin 0 -> 17392 bytes .../asciidoc/images/SCSt-partitioning.png | Bin 0 -> 18068 bytes .../src/main/asciidoc/images/SCSt-sensors.png | Bin 0 -> 16910 bytes .../main/asciidoc/images/SCSt-with-binder.png | Bin 0 -> 18899 bytes .../spring-cloud-stream-overview.adoc | 1054 ++++++++++++----- 5 files changed, 784 insertions(+), 270 deletions(-) create mode 100644 spring-cloud-stream-docs/src/main/asciidoc/images/SCSt-groups.png create mode 100644 spring-cloud-stream-docs/src/main/asciidoc/images/SCSt-partitioning.png create mode 100644 spring-cloud-stream-docs/src/main/asciidoc/images/SCSt-sensors.png create mode 100644 spring-cloud-stream-docs/src/main/asciidoc/images/SCSt-with-binder.png diff --git a/spring-cloud-stream-docs/src/main/asciidoc/images/SCSt-groups.png b/spring-cloud-stream-docs/src/main/asciidoc/images/SCSt-groups.png new file mode 100644 index 0000000000000000000000000000000000000000..931ac9727a6f7984d5f931a46a815034d1c83af6 GIT binary patch literal 17392 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

5}#RqxVs7h`ZB?z;F&XNYU8t1n(S>v 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 literal 0 HcmV?d00001 diff --git a/spring-cloud-stream-docs/src/main/asciidoc/images/SCSt-partitioning.png b/spring-cloud-stream-docs/src/main/asciidoc/images/SCSt-partitioning.png new file mode 100644 index 0000000000000000000000000000000000000000..833d2ac66002952e3e5728aeb7389cac1cf2d0cf GIT binary patch literal 18068 zcmdtKbx<8a_xFi&akt*C%6R(1PE>cg1bv_cL*AS1mC$l&-;7d z-Kzb2t2R|s=u1z}%;__yd(P*4ZxwqWGE;oG&xyGbtow4-@p$X2nP7XT5K~P z_z&7uT}B*wX_z?>XdpSs>bOEdq2NG%prNvI2!RfBA2qezw3QV3%^dB)CgzT&7GN)X zC!jYJl%N+s@Tt92_h_ z4;EK%2R9Qh76(`A{|xfK#*wseHFNpsiAtn6Sm*8dqBI8+ewDZhlHy_1WDt1B?R@H@fRp8vPc{`YzQ(_huv z(ajOCg3CuUIR`fj7vNww6UfyGbNu({|MwpM&#_coK3V{G{dzR}e~`dv#Uj6ZO3Hg z6K+X;H=FIy`tf_?eCtlUz{uwiMB?}Jux~0nOqXI8cO2}!#kJ(tNgLYQ5MRz;p3je~ z+Q6u76=aRCvzP%2n%{jWi~%p()sMd|C%m*uf{(=Ii}?3gzXiAyjNDFvt`pFnXIw;}GiWY-48V15Tv^exCMybqfP3 z%-uGH%|hMRR!WR$;`wmDhz|y&bAbZT)e}iy69v*Tc7b@{kAE`fub1W)1#|@m#n`;I z6v2QO(h!il=+8UMuJaKoM{3jyhey1P~+Xe?Nf4zIQA0W*zKY@SR zc8RP3e`4M_@oKro^X(`880TK0pSo!NrLOR9EZzI?OT^c-82J5c$BsDe<-?m5$Lb>{ z@Ti6@=Jd59_zWLhGIz{;+2} zt*^!_V1^q=^)vGO(~qg|)D1t{!@i94YSb=}rh=EtMK>b3|JAeRLsS*tn{*e@r28RfgL_L5WRv80<@Wjg2L`I`#0{gT-&*}S zXER2=f6{+X2tS-KRkdsn)8*&b>9*Rk*+9&l4HA61n&q?{y8sJZeFih4=a+ZK?e(|+ zoe*&U{p)&OFCnQv$8|zS!?&i`+2*b9S22iJm^tRWY(|e!z!^>u;WDTwHZY0(6R-1u&&#*RD#I! zKbEG&!d#;FF@DXQ+h-%~+j!Mpipaol};TrtdO*rb`&1+hOJC z^(W#%x-T7wEI@SdyIu2i8&)jF}GpPHX#_JiXeuU=n%NLhS z9S-4#-MqFL+T+gsakWUXw$gnK(Qm&q*Zj7Vy3wpynwImon|GC|l#IN#!UA6&Hm2PM z^2pw2KDjS`XtyMV;lAr1#T)!AOy0p)TjnE%=fm=p9sF9N?De5?}OZiNxM0^-^PUqbc`DuMj<6FGL0wn$f?0ym!qs3PEzk2+pjEU zi`&F*=FABt{3~d~DIfC24%&{sW*06uuXzxoP>O#R#2x(LRA>NP*U{Mv#O#l^{9TcRsNkG%H35)y&8< ztvL^pvM5LcL8=Y}5{#hqpUqzX)jv^Gm-5@fm=d_7pThIG92O(lMvr(8PO?*RJnI=( z(<*|0B!wA4pXuzp)j0X*x^ep%8TzG|%U^-p2iW zII%;<4;PXV&yK&j$JL#N=m$Lf>YW$MPTB~{DEj9auI~#cM(D^_;6C6BEi?zdfR>=M3>_ver6=9 z>$7I`xJ}@a=YOMPf==qy)&+yYd#ul#q^bDzR?4XiFqKznu19jz=oc$&%f90xpJ|3eGnK?=ncu~5V;(d6RdxU0$1-EnE|cfbZC|L7YDZkpSl z+;hLuD+FRY;Nl;lYNhqC4qyHyyg2E`_?Z)&1^^!Ya#)nWD970_uWKuGx0@nE>i&zT z^G=C1UhkuCIpM9^n*a0P`~}wK(_iUVL@mH#tj=n5Ka)<9?|V`^82zX4kJz*LjgV{u zT`8-}XT=Vd+rWqBKx$>@uk~KIj)4y=bXJPfwiHuHFhgKs7A{R%Q;8YzU#k-{dose8 zSc+VxHWEiO(l|z@Nh>~v-g}>A`QDfMeK3q+X(FVZ0s_l#Y%AAQ=RyB%ocu(~&Y1Lt z0-P}e8*-VK2$4rI?}$KlsCpG82O>gmNqwSDmB9PiR5@>>wmxiymd1^Mho5Ke)e7(k z7v#IXP!uXI1lp+2j=w%1HoE`lL1x-~bMo)4xAwF@#S}Np2C&FfZUL(#^Bjf9l5O?R zvmXa}=K2bPku632#kBF_l0|qaqVdl20pI3E<%$=f?FX9R%^9 z{~Q)tji@uc@>K8(6^s8C#|>kr9QmWZbzNYO4hQArM4~QUvSYaNlvS?wOSegI|oIbRDZEE>4 z^L_Xh*eF>6Q}jgKXWe{4SbQ}^=bRLU;M zXM#jF9S}k0LyyqD7_dJclli33{K4Q}F;nG5n(dNB0wbJSWc-HCnp*KkeqemuIO)!pHx3{JmNE2>az@SI$J1#}X|!vCFD!+W@81qDV6!FBB&Bd-uH!S{8twYT?ikP(&B7)%;8;hy>mS43poQx z7dEYKyJf7{q)hJid~g?^rVV6MbDV&X4X z=s2}6n4yL)#mXxd+FP1YmE81c433Vl!MD)r*68KhXpzU0a-cu+BI(}@Ek5+QL@9=1 z`Z`r%smROSr1r;j$&@i)oj0-%TYlbTl;ktkS25nK-I?H)&0ub!rsj9J_|)Nsv#CBT zc!s{Gjm~FHFjod+OnY7mfBs>Y)hi=5QOi-27rtDbJ&e4mw@*Q9r3;f^?7m*8l z`2Ez`Tc!;9UbUux2{YQTE*!7B&jcwbYSL|TWMNKnhW0bx7#x)gFO_s1Tw)ktkn>0n zQq8UQztNgRDsZFzXkCosO1Z1EiE6*7Qyl`8WbZOb7KCtpokaY3xG>|MZ}+CR$)Q%?*7UK#Z2l`H0w)B1f`^={~L>^dv#%#Bn*9 zo@5>M>71O8dOYAVhWe9(PRSF@39VBB7118!$+sbB1iPwiU-EUhxY~b#+YT{kE$5fDjh8M(l3S?6jI^wyW)+sf>_`Mi`{b$7*m6lj zAF8FJlMyDxKgl6Y^Yu$O^ACGR;N$vcxFsKf3wyP_24aaM8&EN^;Pbq!n?WA^zmpuI zM2(IZ4D)=J2+O32ocYb8lQY+;gyz%Ci1T5({cmq|I8UtX%>UF1{dQC-n&IDK$Qs2B z#ZOX-Od$WlsL@mTlUX^{AH?6|)E1tVICjyoW5Hv;++gbcQRaPFe;sYE9t|A5@zVq~CvDWzEg}*;yRM&vh2E7hKwJHAf4Uv#K9z>yaLRjvZ67d?x(+)5&}M-GbK$ zbKVlJ`Jlw}hZCQVA2Qs1*;6=w9%`n7gZq+8otX<3_EOEi;_S7BIc`RFA*T&9d`#w! zGYY69#t#YOgSA~Cktv~d=!aopM?Gr;X&KEhGgU^JXIH|0E%HCI41LpRBd5&qbHc5? zkKsP6PJ~E(*?4ow#Y?0H$R2DwH zdy47TKsECazh3VG5&8N#Eu%G=mEYaW#;}OX{%-gnO)}`(_NN&(cumR;3O2E@*~~yf ztA-VWZQ)9X%NzNyB$poKW#wJsHYJN>>*lsTX5^yc9!}#ptUW(f^fW#x)2{C*O+~Ri zWBD`QgTIwit5J04$1!gUBx8)oq2HGYn$4nyE8Hofs{O`Irrn?_-#0m?li_F5 zQio>GE&FCI>3sk19!SHIlHQsoyE5wJtDM9)E*Ll%_0XF5Obknm^XFO(z{!ayW$2KwL^Ey;9y zKcb4n4C+SN`oQpz6D;QunDL?=J7_G^rl`Jb-CiG_u?6@LH?nqqDlaUjU&f)6=HhYi2`Ta~k+i@tC%8)x{GJ9JXNzFsTkf^WJMdPJmg1RZDh;GNT zl56A?p(bRTu!mvY*Fn^^)x-$`7hC;g&Q=y9G&jnPx7QwN?yG^FqoouIP+F88(Xqsy z6uQlU$L*woVGBa_E9m= zLCk`+fMTW6tqTpdvg3x=C+;9ZdmPRIc7Kj!MKu=LM=JjNpNtj#c|y@TzYi()X$ht< z^P(gUd*WLuiOM&>%XEH;q?cBFu^g@l`VcY8R$r=L5EvzJ-kuV@5rMWaIY)&S(#k-D za8F-wC#WfgIOQ>MJr`v z6!LnN>UKz@HNH}d*8(K#a>x|!hS@0zEDy6Zf1AH;@udXE^a&9h%LFs!J{do|6vOEV z9_$3T5!laDu*qINb=>}B_0W9x&(m$#+Ca2w|GjRCW40|%z*m+F5Gl+5L&_U zAq-3i%?6EGjy23p71k#*)NCzBrH&nTE}5FWH4ie0QE3}wFAV=2jkkOp)tpITDd}S1 zVR(^JTWmx!FAAf^v`1g1@9N+m3dVPF8Ct2a8gm4GwC}b;+w7D$J!MA!sWgU*~ja!tNXHF=c-7D_Pi*O zOau+vFO%A;7}&QPUnShDXEA>^y_lbB zC9ZNSkpUJRVCtr7ObGCoJEWy9ztIBAbC=FijKc*Td^?Y0;qI!3i z5-=>PdKk6MHWs7&`B;>la|Tw__+odZ7#l-5R2DG=3)NKprN9J+=$|OksYArMa$LjP zjg-rwV=LZYe7U9>sD(=rpu~BnZ8Tyxk5q}rH);8)V*n$v1QMC`Bs$$HBT$92Z{Xg5 zv$Hs!T4;J3i=#|f!snaDr1>mGGDH!CRc#o~!NLXehUqLqfdi<&9loxRNKnDMV6dBI zPL<4m!@nD;Zv9<;?d+i*w2$_cBi?5GAlZ%cFz*=C_3uzKrEEN(P%{DARkWzU=+_0| z!e=N5cmdVW`PdKDAxW^67HS~NFYSgVWvMCuGOo?=Vi3>b<@POf6s>gYbqgU zk;z`8oSLXRs@f}i#{hEM95{x-faq z;>A7>yz*n8<0oW;fZa>Yv?hgWf~eERbw+A_$gy()<5C0Xegf}yq?@t_>srI4^dI7F z%OlZ5!&ckUs)Q7J1H~VZ{6&+7@CYhd&`tXI<{-Y1M-YS)i5|B^FNB8{_nGmw24G0x zpG43sr>fu1)ry~`_(<($d7nmSnO3YQy{7+BmmI?C;r(aPSJAy0Tegy@2pn!V?XP( zd1Jly`_HjhN5b*q8pdx)1#3S)5xK(#oneB`+5}elE8g=j%=(599^g7;&w0y~!k9GN z9R{A-8o7d+aXJKGspM4{VSch^%aI1S8O8wiYmAH?b{DSQ0$9v0dEa2^c+{-=<#H#G z9S`zH5@S z)FJ@;K?o6jKh#|0b$ZhAub*{Zapgx6gWwhSid~UtZ@#fF+FuJYpTjTT_DqBB0A^Mc z$1NX}Z_`RVsc^FlBa75@5o7dNNC_kOX+r3B)ooN~3~f}>5@1TGgh5}}r`LdN4OA_IsMKD*Xf}mM^h=walMaD>hYPMZn6c^PsumK8_~6% z6MH-xey&x!5TusLSY1ECe;pTVuY7Yzx!Lh=I|hSq7gHb`Bj6&0W^Gv1$u21>3K0fR zvVgHC1zLidyNK-08>3~znnaLx)eXF{%_yj+_x{L%s*+;ihb{bv`fk-s zT_Revafqj9%P*5ERpfp;A>g{g2+y@u!Ikrmf_rTmx&$xqy7D*aV6BnQv=NTvK!w#I zYDmM|#;r$(B4GJrX_ONLp6s_JK}f!wWP$FkFYpdyexMqcgIEuS1#4@z++|URFWSt z)j5q`X(6U?0wnPwNIkUw1`uc)Lr?cE?N_&7j-W?58uGrfuud#OjX8@>6Vm9LOqUb~ zPcF?mM>>(sS%GrAFo3=mkOQ>@XNcwR9aFHAr6@ui($)-OKTm2_NrgEr7uC3V zcEM;ls_M488#IcdXd4~=^}g+P-8ZaF9zWBahq_1{> zgORis{AM?#S0Ao)bsTQF%=LOsOXFjc8e6pD8yExTY&l|Ew^amyK(9c3JD@7*9(64+0x^@m(BCUrUZX3Qd22+#2jgPWg3@5n%wbgjkMeld@6{ zJHxiNU=;XdY~4J(0ZmERyM(?Exl*lnEIKv80 zYktxj!oZYN_j(LSs!S|uTB{Iq%f^%R0}kbs-;$zcMfxL-GB5PceflW{YP0tX;3czQJ%M`kEu>0>KW5}V0csJ+ zc2*O)O76aq|9n;Irz77}K~*?P4xlP^-QyCh!H)rl667<%X6$e|V&hLFhpxLxGB-1J zzew@nF4k620{&)iFat*G<=B@aFGU*xSOn`3;*~DOSK7D*$x#lq0zMcVlYg0$ewF}@ z@VtV1u-*3@CuUgBE$e3W+d@I-d;oxj4zu9Vkwv;eQ;U&D4_58J;shI1M7HU=BShR( z@V_$v0acO5CHQ3*Y80Td*$K~XcCZ}DJ)CyFJewOXrX&Ph74@41=Uy;0fYu*@B7#$E zr!j?`*rLXGo9o6Jdy2Ca#<8D|r;dR*O&4cv=M#KMghFtZV;N$q(w4kF8pK_(IV??Q zt;anz-bP{_**lz(Z4*8a_S$*TgK{ttN@db6X2{HIIxkuDiM9Pg9GrqvW}-}e|6Tx= z3?)rmu;cP`^;qf6<8^f)US}dRZGs>+bvP8>s%5r4w`t0oT{ml=uu+F~GuxO*x#Rt!il&j8d=%URLS%ns#neksq$ zb^P5#80N{vCs=ge-(^LN!k21q3>S`TP%zgo!g)Z~`h~8J1ZIT48EG~RZ}zPW;P1%# zsKLjLCNPJeTYw@7((*1`$8-Y^6BDX?ZtR4w2d;R(OC>q}0<{y_??0AOA{=Kk(hfF9 zxXdkB10V`q(rsN#3OAw~ae8H;WXgNBsJun5k?_eAQo3R$^&#;1<$(UzW2yCD80ThM zm=d!xF~`b+gIW!P3rktnPk8oi7~AIz8effVAXp5N0bWQ^k+81V z6U|BW-U=f~&fOY$Di*o^rjn#Zvvcg|f2;?rY;sjhU%J(m-!V239X(UlJYJ6KY2`MF z$e#Ho`nyN>)}%T{nb^+J)^n?8m}EM^4@MPp>>v9Z8!l7>@m)pu&fKP4wr#KA=-g{i zfa5eR#D*0pXcBq;lK_-ANifEyI|GwpkMpPg$c3xqoW+wHEN$tN|r9z_JLbsNp<4Yv`sY z2ceQdcL$UCU36pkZNg4i(_+Y;*`54e|C@JvaWcMByKrT1WEE#Q2VYyW_T|c32KEU=wQacbQmZ<0@;Q! z@&AW-e^lx~)4US*KsmY4;Hng?mE{r>{RQ*k7XL$AEjfxkRy4AipWXa^Y@;D(LFYX zT%OzZHacOc&dLeK=>DuE`X+Hd0lu@?c!+<6*yO=N(e==GmhHEGo&*iPK!wi+uqsbK zz1K2m{QA^_3O9fQZ4Ru-lTs2&?=r*-H6S&Gge7i~7jZ_!KQ8P1vlq44^_Bdq6qm?v7(?)2Ac#6N4VjjLq;x65% zk)yX-?g;xV)GSKz3{d5^KD#?o;N5_$@UBYoKd>9|?u@+YyIugWd4N39(YP+H^CdDR zp_6k~A`eS~#CpmCI?8f(P>aO}j~>{Hj6hXf6-`~A&1p?9dJEf%_S;h}19!YRc1w8y zZJP7lkEOZu%Gsx>KY&*cc(RL4?I{iWC8G$J7Sh#LrVs#uJ2%Nq2cx7v)h76lY2b&& zm1Vi|E-QH?Ns{wZF@CXBbmP80hOGhE>uJkQ{JUPv6_1k79}3qM?(T{inNpH5lqUJ* zXf^ySsWfQvilsWg;d63ED`92N+EgADs-%++bv$qdJ3Gur*9zyz_E7n*IvJyiTAa(Q znB7}fxRp{Y*6p&A%l{_c6j}f=!N{0wE~|&Qsk&k3ato_@8^LTK?ZWaENc18UGUMRI zKayd|ierxqQ=QJg4T@5+tb3NWA{`xc2t8R@;JS`v7dQ9U37fETmZc7>a}}>7$57TM z0_RH7(kw@5(+PZx`3o^2P>V;s%1(`Mqk>QJ4ZgXBYX=p0J)_KbZSE@}q&ek|rP#Pu zadqO0RXOb(NO7i4)xYb{znu~g-HaSRg7-l{xEOQPs%7{J(ouOob`Bm_jI+s7f|1}7 zKrr!j_Ll0LKg){KMZ==tHhB-ce2yDDFg7eIgGuj$Ua|LGjH#$W9D7q ztuGG@Pe>+=tTbfRXf&1#9=-954f}$1IS;9+B0h$tNgqlg7*dZ#6C^d0_Vyb|azjfp#TEPJT+d0C z$1IuN!@LO419_W^UHY15`R5n+5lQCvNtrOqgk#$=ta+ocem(ySV^=>S>N%o%NeI>x zMYY9>{*Gba$06A$r1~SvIQUDswvZ(LUs_JDrCinB=FktIl2;q&A9iF-!k-NwATvu* zxmWbq$fZR_%RT4>WrQp51$?9tdPclmqy9vyNyi}J1O9n6gZ?{KNlg;x3sAEm1fpfD zr#}KJH)G`szYIt8poAlz19fvF6j`_$i?l9(Q!i}TFB^ldJ-jn12bn$l@w8IAf-S^G zGJ#~$lo%rAsu$Sv+xN@i+wv7tv+dB}t?f@EuG?jSFoa+(rh^qv1zWa*Sc%}iPuoEa z1bb9UZ=otrDp|^@P91oaM^RZnTQ|cci~O5gF7bjU^CpBBr39 z`LgkLhW564uE!I{@DzY6*5Wi~Ya9n6y-mDAK-fmDyQC3XR2uSv@y(HLJxRDn%&LWQ zHzv$zGrQJRL+r^M$Z^V<+U}IlL$vmeN7d?<_k)pN7EE(}S@t5cY!4I?M$`#W-VW+T z52j;UD3+q_bZs7~+dfO-NNk^OPe14u4Zyt`KWiar1895CW&>Wlf5|b zvHO@<#v}49DelqvXLb8y9AdF?bo(QoPB`IM_+9qr;{*@Md70B>&iHOT7)lbIzV&X1 zP=b)^LLNG`yH#Zwq-RxnFOM9J^jxBV5vHtVlioyP8dPTvwT?;^{eZhYw~Tdr;^6zX zrwK99Syq&=EBdF-rTSk70K4`NC_SZjtWKe`c9i_}srBOz{$du4>U6k?<+r`TG;gVr z{x-fvJ1^;WIYziO0O!5TWW5`==0a*AE^mJ+xymqj<<+6J;qKRYHFVtxoXC~6!&?wq z-3G;+iQ`4*YJl#J8miGT)<7xiiS^CL*``&>Kzhg_arK2>>VffbT;jVN7s(1G>$8n~ ztK!`?r;TAip~vp*^`2uko%p4K=oYMEj=GWUqU#~c6C-a7#m18lJxwY1HDN=5JKOFl zRq8N(ne zP60F0UL?v#)T30JKH)0(sGU}^%1EwNOdsf0DUo}sR2pRMj}?OMV9TQKr>Y~TJm}7oq8&lG(L=?yy)p> z^LAOvUvw7w8@3~wSp&%k+?dn1SS(Bwl3T!)Jx5Qi49&}sdBiefMrxudkgJ<`t_)NH`PJ!o5XZ?TvCuKU;dv|l z=^{0r2R^Qd+j!0sNIbI9w%`A*IA*HFfPS~KV%lfbFKg+wVUi+shR0QlN3TA^}Am$=?oX3-BZ1cHXw`PS;t(HdzpLjlTKwv zTeLbG^^tC5LDNOj7=Cm&l}j$00a+gTmK6a-Kx@8=FJ3-RQy?mhF#M#AlrZ}@A7jj;W$9Mt@$4oCscx6!&MnU zLIe3n<@0YxWS;zGbZ{=uEd*@>DdgWMwJ_2PFOgQzgg6>;rAx0r2ZB_V2g{x1Cbiq) z$6%tTS|oQNHQaOn@CH#Ag-5PvYn5)XL3gcu<(|@C1$(l1IxC;FO*u4!R{wc?`DG}L z-rVOP%SLywJ2A0@oN2kq+KTQ>; zo!{w&?#mexxU(2=%QBznXIMX5X{dCF83!ZBL{i}F#Cj-ro!7JuuQ1_Bk03Ew3nV$7 zaDSF7Tsy&QKSk{5)dc%zfkwkn*%94}ui+ zzmpd+wKi`cEThN|PeE5;THY_b4xStuv^EWp(~8bjx5i1QW#=+!&PYF^D~oT=pzQ40 z*7h24g1~TAm*}%Kw*2?8U>n6e{p6Be<(LD7-7##OQ6MmW>u)ncOJw{;0HQXDlT&=zX1V{vxSub=zu3sKUc}PD(_U>xfcAzyA)E zy~M?5v9=YN4a6MR2uH(<&C?KkTG*f)tj!_it<>V(Z}@wZ$>X`vgeed>?l8{67pwit zh2YMTDmmcSXF_|tuuNQjtTiKx&+nD_k7x#B#@MIEP!l2Zaeu zXnjP_Mj9_s9SKgUV*5}_CvB5t`GShLM7^CV^&~yG1Vh#5G09c-A4VSL6)NcEE_p$P z+}^#pn8E15pHwy4{C30bv*KICJGmnt4!la)f1g($-#Xic`t!mgzn4klZR`5Ddekx} zN22aV3Q7%z`W}HL%p%w*TO#Pc<%j}1oL`M ztW%DD5`0HQF_}F%smOlzg|g1CZ%1x(Ynt)>;@VSxWoQ044<-1X`mJ$R*7h7@p7Y<} zm6&a5KfOi>lmzJq`AAEp0M>N;vb{x16n*2vzLfbq*PxPNlmUKA76o+czyi`&s6Or; z=UV#;)idi54+4V1xlx}EQ!#qn3Qw~Jh$c+ZGE)|Icjj{^jgR$l$n6XL=6-zguKy2F z*VBM5@Tw){f6qDGVuu5*5Ne^0F1Kv?eZOBYz+gYr+Er%RbE^$xQhk(TMH%^QJ zM7wR<7-gwjjeg^LdU=c1MVlqDmV(58h;G-&9#@f8rTTa7rJ^I$2@n&x9wv;(t1$c9 zk{}AXFQNtSZr3hR*l_>mtcs<@oA`%#`K2!7y8sDprIbD0{{5(#U{HdWG6|Be;#al4 zx0+x(gA@`ry!p*taonGt(V`DC^{XyVb;Hf_DyVhUGl)h>2zrpp$*LhmQ$ifdPov5m zu=MZnl)%zqJ^vUC)MKbu1C*~Vx^)Aq90(dHJaGZ302wr~a17&Ou#frjSdiiP*B>3G ziRV6w<=GOe-I$-*ig_i=W%(5E*| zIJ<3>%qjW91~_UrW(0dmvar}r5y#M->p=O35C)dYT8; zcV};`4jUW9P%qcswbN>uk3}RX$3=S#;Kce8*1G=*#i~0=_Kn+z33{gJTI<#!GI3gc z*SftO{d2nArwp9EqcO1-rHLu7bP2l1-23GNB;+jf?mHgNdR2LnhyjAh*Bkdi8aGG` zOw34xbw(wx?x_$(6wFHgGP$p&5b|(_z8-HY^mmLir%n*B7A%FYd~JXo3;J+Wyil0K z)RaTILyX554%NDINYsj7n@qJXErSv9i&(7Sn~ic?BQWjM#%-*z{3t%dyv5cwESTE2 zEYjnm1Zr5~xeJ6nPp)9D_5&Byh;n zgXYw1I-}tv`gY%9z^#!?M674LB^8#G#wQ@PrOc^Y4>x%&z&U9iTy*YjHGD$+Gd;L; z*#XU>=1I^Mf6hF#neQp(?^n?rCg=(}s65e8EVl-sP;FsM?dr-Ljm4)L1}*TUhU8@)&(Y=v1WU{B{2%jU0+Ok8FC0Y1f|A{N|G>>&LE&gw@?*3 zh7hk=eqJ|jhK=&+4Hae1Hu2H5kA2s2&Mg{E_1a$(BZy*S&i5^Q;p?fEMJa|ni7BL# zUR&i601f^vltQQmxeXY5;7o03iG{MStvLyx>vf)7EdA?ca06^@4MA3o=C$>k66oqP zNh6K>dd0-R6+6Hd(__E3u7R!wKdrE-ks-z}Km){`&Cu$&uR6p6AE0a1I%gEtt15E} z0S1r~gnS%9YyLG*CS4A?+IGrM@+oA`6fJlI0_r7zy8R6R))>W2qwmp^0V+R$H+a%V zkiBY(k*TM${8V!JuK^uY*#&O7FxO#s&F`YvKfu^sHI%>~s{sKXkeQ+=0(#O zh}0O((W@7rf6ZeYqYWCgBdb7qyu%w1O2J&g#Jx_2yi5Vh<@W_hsu@rdQX+~b1C+hK zB;D}MyJi9=e^H4w3P>1m`wR&K|IP~5B%VagE`mfqV_CC2faCK>{D5*?0t2^cV555v zyvv9HiA7*#vD_G+NX|v_KL@v+%!p(Tr!r{t5Q@y}m4Y^#4FQduvaQtJ2-OtPUDXMf zsqw4N#1g}Nf1kb+E<48aI@wKKiX8I+=_0w^kgvXOz=#ppMS7JGee zR3W-vUCC|!|34o1=1D9s@c9mX#{Qq4X%ua1_6rOOGX#ctXz>85*Et#E`QI4q(!A)P z%7z(v0wX`?tRo;(?F?Xc%ES6`?(7`CgPeK+xOsr)0v=b{)~d#YStbnQJhNF8MF|B3 zrwI8j0Ke<}>BYDU$PDf#N&woqC5Ybm#E3$GF6D36x4~rE$nY@?3>q`05Aol)7Bu;H((w0z1vHto(J#~4amH3 zP2jX)7Qh-FJxFw802(0%MuaEOPn@~~0OrGH2!{LyAgVg|S!e^air>uQ`7D8wzV_W@ z1@1-Tkc0fdfLPJkZ}C(B=qBqg<=1tkk`+sbR4Zu#N?psQt#IOwQ+r2&=YKoH+WHVx z@X#M18O-n#5ZlU=xJ`+bxnhMtzA*q`nNPqo!Vd@-he}b_+-8-kf8HzuD#st%%rik< zPC&iRt&>o-GYQM@_S2@7x6Ag=C@c+_znM8rYJr$fvwZ3@lFDGtka5OrAZLNQZW_aa zeVQhL9^xH%Ah3~n1}so)#el<;%^>oCd!I**o-hCHBy`@5JA#!LX=~+1DA8EgeE?19 z?j-=Whci_~o*MAg114zbRV-Qf=&*r>4G5LKhh$4$Xc==9Rd*0Jp}evOD50}m68E>i z)8%>f>;Ygrg%cP$z37)PET2rUppe{TOeU}^3`YUNckgxkfI|a`cN$PnQW?h;h!7S^ zIm%qG*b}T&$RsN+0I*|exCrZ?q5K__=t76|ipoj;E z2(``t{dDCM0FkS{H!c4Lgv=X(FHfAvW6D*X&pvkl7)oR@2eMIcKs0{$rOO~iXDgbi z>K}l1)Et$wxlaLqsDif6wr`@;NVogI| zFrcMZS$&sA_W^sx4gpaMC{k+Hckai#O2H7i!eqi7pu2PM-v(r!Qrj`ff9^Swa6qUi zfVco-G$q=(BeZu{l77nRUdfH;00OJ{NC7J>Vp$KAqdu&fKK>`bvB6LZ5^Y5Y+-p#? z-2uoGOYr(o% xNND^T6XPJOBk1$ce>C`)kO=wz?SU__KO)Zv9H58qAYTj;B@ zmLoM|74Qe$&rn?nePM(r9Q=dhtzqGZhK5IS`-_fNP(%q1n7wCg?r*N6E#>It!DsK} z<>1U0 zpZ@n;{`-|Y{r>Y<-~st>zu_0;6X5@^zQL(7x2;mjULM}Q&VGKNe>qW^e~$b=wf*<& z{Cm8A7zN*Zj+&nS&c5JefBW0JlN0*S+5hiH{9pUh^S$Q`p8B7&1^;vQ|Je4Q z<7N17AN+q9h<``8d$zzYZuBLqek!bfGMCYXiz&O2{MxTzYtA)NDEiG!78@ zu9S&0ro9KpJNc~FY$+kxAr9D*2UCUcVF&PaQ~RNbKVuq61VqyIWPflr9z`d_+lQeG@bk={|Tn9Efyyr5lov*cMuCsniAF@@pkP`Q&D%XEW_Swg0 zI@0HBnd0vAQ_uBEOlnN84u%+L@uxFoAV{3zQruSdr~bqek8Fb%bbP|OINu`n+mBa= zo^-FKN^g89H0$tnexjLFgb^Y7V6G_aVtaKkWoNn~uK(^tC49<>k@Jj+N!aFOcfs%P z>8~OBNA3gc??RFkW?!20c7&Y#R81oH`1YoyZJ;@)pb}&9CH-S`zvx`suzZ6t;c_NY zl8A>pbE2M$K}Q=OW|#*CIKMxcp&LlbdLZE+w^Jn4fg$8Wdw-KJs>=5IAkIuf502)S_9r?k zUL$bHf`&uC6ph>d@9*wzS5j3DLZ~^l^b!i=`toQ}qK_;7znx5_R%-I~Z23^2GVKqp zwhnoxlA(XJ!XPs;Anm}U8UjPF2XA{HS92Nxp?ZRvR~$4e!$m?nl@xY+xLH)yVNNpO zE~so{xKQwYT=Yha`ZQ8H#2Ux8@%5CiB5UZ#4H!y$p`G3?>N#@QplVw zMftQn_?(K?A!qW^cx0Agl2JL9a&!Vso#C-R7(n@pt%~^9#|6`{VF^n$GuvySA8lSg zz=W9@*q*7vBVXM5xj}Y!a-MVIAf&0@JK-lF`~@yAF^yk~kP-zzmqYV*i> z%%`AJUe9hHcw02@$)%%o{E&*oZbLui^7iEPL4`ac9lTa423Jf*O?@<%|Hs?F-)-MR z@M!d>Rn{^SU~Wxm2`%LDGGNA~g@&H5=SB>t2rXlRL9r>#%N7x!*Xy{G!;_y~MdVs0 zY5}|Zj*wpFtnTfeZ%*Qit|L+VKNlxEo8$S)$KT!tcAtKEFe!f%nlI%1^K*TPfTAis zHRwpOCf(^s`KH$Bd1@wZj;dL$`SP*mxa|7xp#j0ir_X0t8E>87{fsqG?e>YMXEz^g5J|43F+Y)+`OK1? z*A3Ii-s1nRr2~UfqBlmNnWZ+dK`&Lo+qUh@E`Y-XAaAI9nn&&LbnjX06Ca){ za+{iKVmD4rgB5Oa`{GR=)-)&~$-%Av|JXS4S-ciw8)no6KVlKc8{^Al*HBWqouq$O zzeN7}+V)6IH+n^RsiwXQ#18XNd$LhXFkLB7D>qgko`Cf}z+l5DtB5wP_?^U%i75cA zAh$2Cx5x8^WtrKP4j2U-2a{bs6uhS}Ewh5By(hdqv(t65I8#q9e@uhRtd2IECq^49 z;?MU-za#PZmNw>OG+|x6M>sy881Y?kJT;oNzp|tfKuk zq87OYE$vNLs85%xrrX`C&9w=+2jb8&A?48#%6~4?PA9#E#y@V$Eia3aBAz1XD@UdD zKKbO0f$|f9doz_Cew&_)?LI@PbRd%5(G;>!%eum>-DpZh&9y%GTvxyxWOL(yU94P? z4y`e(zg~(WIxI}@FncDer0`PZ32NM1$g1}O#1_{e#;hwR5bHI_d%XuJq+#l^Y!1c_A@-bAUS8_6)jrNAB$U8iM&WK81#QzU_jA~DG5xh}79 zHM}NpH~V(!;ga(VlP7>^8B^}frP76%5~~bVCVvLQ#3e1g`Mucjkv#FUVU^Z`nPd|# zx+b=T$+z?v5AgKSW>HuKxD}bIgp?B>=2j+%-;(DU8v2%Je!0rHVXNwcp!CQ{pzQZCG0a8!u8BA#pS=?6_%_88d&jRRbSp zy4^&pn@o%u;WS{~{;xHX?TJX|FmY>fnAyr@lE!=`C>nHUbU~x#U#53QYefj=d+&pL3$=qGAHe@}% zyyovZizJB_z5l8Ge4{Xllrxfdl}PSvsq62au;g2CSsVfTKIl>6>hqGYpFg_8{ky_1 z4~D?J=C^)Bs;uTbnzj5r3&hAmJN+dRvtmc44*ULCI_Urr=?~KWTRoT`y}GYXK|q%n zvugAF*7f)=z3(vNfaTkOUl>F(4fj5>;WL3O;8o!7?_j^KEj#^LQx8%ezq_sOoCf78 z6>2F0&#+qLZ!SshV_@N*ZB;z630lUK5o~hn`)cc}EB5{IA8Wn_*}uEZJD*-S$}bO- z5%BQ4&o|ir+57hEOUo**`-H{qmI`u+@S_jnf*<`q8K_(Pj<8nlR5G{4!8D}y;p4QG zTbcf3U&OD-{gm-2_&Tpnzv{`JeCmzD_WQekxIXr~`zDOZ`wwE}K>SB&)ZhB<>+;`? ztf609J!0p;j^;ZqIJ8#!cXF)3zTfnf<->0eKNHYNWrab;?B+fU@poS~d3 zfU9|r@wR=cweUM!8_9~)bAKF19TRz$u1&F0ZB(e|Er1oM(D3A=7IhNYBmP!g&V!|PyNF(n{$ zHAP%#U|HI23YiD#W*eA$b@y|vMHF~VUlxtJeAl%u*y+d^YO~*g@Oi9|2XT)~wJ%q> z6e_;?@q_3(`Q5iS5C4K_*qAV(dI(au3^MW29Ns%Wx-Nd!Y`>0n+E;h)ovyVI-YWda zQ7GupnfV^Z7mwA;`ES2C(NzuDU^f7B6<@C`IsHrM_z=ky|U5FJ%%0c!uCL zL`DzYfqp>Z5tspe=;tcFQ|fkty_CQFR9C?wzFB=DzogzkthAzrp0L&1&z&(r`?pGfDv*ho6CffY?+k8+8Xx^pEHE+_iAjj7YD6maT<+(b{@;=wV-_Sf%^!PSP8An zY;~1%o7=b=jkzbYcmyTmGz?eqS3!$)dn0(QHsQ6<#Oiru6?*Vqfcla^x-_$JS8&Wu z>_D&jYC`(A3=0LWLlF9PLnlaZ?*^n%Q0GMYq;XTm`GjAcd?qDO#G*$awCim^4!=Z} z(J@sfKSeN({pFyaNWRl;nkku3@bF`bN3+Xi_~8gU&LHZEfuq#?e4}G<3|-N~ISV!^ zVLTV~!N(Wd&-)4CyHuMcVHfu((WPMKXnycPKjM&|zlWe=-KP#@n{>DhIDU&{IZ#AK zX|7V4Rfq3qD?frJGz-=XpCLL|&1rcMc04Z+q6zZ#jy5MKIrNrR7;_b z*&>v-%fKg5{JsAz@JBD<))5>2PpLbj63B^97>`-#T=o___A#~T%c-NMkvaWzMau{N zU21Qs2JUMUcJE_Cv79iyzOFkK@(PgB%4Dnw-Q@YIyNTk8? zpb}oH4u`<7MLo2n8aYxMkPxrpv5r6RQjp$$RB}{0`sM6v%j52L| zX6tL-J7Lt3){W~3Dp;k?-{8xipR;&~r!+=+qNQ|p3LBoPZ*X5?uLXou1XKRTME??V@08dBXAWuw^bo6VOK_+oy^=Q0d4bIVOns4R9@&nRm%vGD8@q=PL2 zI!ttgsTS5W;7?aGx(}(mdZtrYUg%^`oIUh&w#H%tNhTfBNLo(kCTY><^e#$9A!nvh z%OUc;8b+xGURS?A#;HoyZFa4ODksg4+-w|w ztJBrr9{>54T*M<)Fx-IQbgvy1tQytBT`Z{agjO=c({I5!x6DNyQ-lBA^~Da39*@dn zm&4kx6%gb-7r5ZoK8^{~OmFj~ToP{D2Rsfu#I(IysDn^s7o2xha}eA|X)h-r*A)Z%q9Pg96!T`vC4wso{!0>%DY@)v@qfFBrwfzc6MR$f zy68H%gUUA{(l`BmJnTg@752v<-&8Ka`mNw|;d(_G`we0QGE4;iBCQf`f={D#Uto+C z0X_Mdh~_st-%9+R&`Qu4jS)@^wOWW^!()7Lk4zcHa*NjvDH9{%FdVRFGrrLHzc=Pv zr|&wzbkXI&4!7i_CaH-Q(QZmkTw24SqyU4$Bb+FxG0a(eL6Nf2N5JF-n@(;&Y0;*ADVj;+NLdO&`aD0B^MB$n9Es+LfRFcZh!ZZTY?njeU zdZiHBdsga~Dd8C6+!v|swqxY0?il+XswoAt`sO$dC9x|BJxmy}I155w@7to8E3ylY zP?moy%n|@Ey>Lx}ZNv&3M({d5z-3i9KjP!wQq)g!Xkz_>RT-ENZ`gH9hKM`asM`HT zJC&(bWEu9QBZYw>;09FFVv_Lhi9KZfLwMxH-zb3*ZI zKbC=6$N6#ve$+h$?P6-YbxOjf)&#%kiByVKOVgLeIIFstc;suXWZHc$>Kz!yT8&X6 z##nP*0($Wsf-cy*$Q~;pio{Ar2J|}_CmOpI4y@Us_Im`%>51Ol)d zg_83f{qmx(VzlAAPw~Pw*#x#5Td01$SjKe1O&ux2x6nTA2pb>_#k%ZnO%POl@P{Hp zh9+gcEq7g~M0$0%#n>*AW*haW>awnQS66Sh=ktYn`oz2KjaJn?grX73Fy$QLN|G;q zR*;Qk9lSE$SI>&i3u|R7Q%o3RYnY&~EQ*!kpP@T5>#ZKul~{((^p8p$Iqg|E=^$dL zDEM|@$cCw#j*EnKvc16NMCt&+^1IHS@2wy1jvdz*H%YKIV%Tz*Y)pKp$`3m3G0VHw zOn$88J~~6(FCSES%@AXHNwjzYn670d`n{29)P~7AiX|wLYd+HZauyMpcHh9Jg^B%X z^j%{)e{8_OtbUgxAMdbD=04UxjV_hMDueyYAZgR~ELgNK2GpolRiCqdFJBp-YWG=f zCrh%hDq1Gv5rocXXxw1`8Yp+w=V{>x=i;{Iz26;l7#*1__U7^5(;x`9b_NV3TtqRR zSxK_Cb>ARtPWO^`^eLUd+lq(fyC=6gFI6@ZuXGBRstK1#% zfNA#u4lzt=*uslQzQtdTFSV#npy>GA-X+=Wl`HI#!G-@IZ8>Yk_k=SsoiiVppbf!C zQ_4h`-8~N2z4grN`mi&AIwJHGxbxANP?!Z^J9yB8LI)XYu4=Yfk`zHkcZLMCStgM)V3-o zs<^+=E|H_dE{yS5LX#RGgqU-VX6V7Z>n>wyJWK0+zv_UTRKol55M5y2>)&s7>WXC_ z&DB{yx;#60pGfxLd*}IRHg}5i=aEeIHjl5Ti++>NZK5UZLOUPCf3T1Tn*0Gs69Csq znWOPpGh12Twc)>eZ;AZ8ZgW4GD*x>n34!Q!qXptSgC`=GPS*V#3S4j)_u)1#g-dOt zw(HNG9t@(&{;zM8$A+)WI+>WKQm_fAo#b2G7c$%((;^=rQ)Lb{JS$pD>e^RuwLkBF zsk)OZFkDn)izJ86vNsv+U{HdNYe(AeURtz#w2@_4#!(@ zWhx<6VG>>ejfJOOmxp&U`d?P*WF2h`-ZHGKOPw#SoMH-V{jD%UQi^+i4_ZwL{2t8i65sI5D*^fg0SnkaTYb`Rg+Aft7WJgSm(mkD z1+xh|U-$Q#gN?uGzdS$ENxzfDF%7`^apakR=u&PA&T)FOtBi6izM@Z0i=|b0zw9R- zj=VzA_B&XjENxz8MlccoIxSt_-_&gRXhqA;8H345s&-l8BWbu6i^j|)eC;!L>*BLx z;uyl_R|G=OyqU{alympyUeOd_raFFpbvIX6w{@EI@E(B5Ah&b!LziQ{lj9U=SDQ$J zsjC=t89YVJWBGa*zF8jP*D*L#btgs7GHk?T*WX%>WFcE+7i!f#Q7BH-i{E&EJWo)- zz!3=#1%vOt$KO01xI5p=1TR$Ew?F^$tkCn8p$d&Avm=#fqbwd@m>2Bh@_DZNbYF#N zTn%wQa+LX^F9wfSaqqlizYos>TdhRKb%M6FR6AejeW>&45V0VM28*(3<$|`urGZ3+ zEFYk28qGaij1sGIaMfR?>!}v6mQh{$3NEV)Hw6B+NmO=R&DBxS#@22VcXpywE>7X& zACG*czC~RwU-fN>?EJVHqt!#iZ3?Y&Yc~Gw6S<3O#gx0++bQIVXWZiu3p@r9k$Asb zB-v6D@Z}y4v)?lRwLn--@L6d4 z72F7NXJcHODpNimknMv;(aD^AGQy`=EE|@A-vBvb#U+ZGH!2B(N(~tw{*(Um=pE}B z&MMkV-(JEgyoLuo$xvh2XVGrfoH6gQSL%kJ=hIE4-~m!t*u6QR!%4gvlAHgantPpHUCfI$|-*VWgGa-JT%AgL= zdkM0`Zn$%C*fweOi`mx_Lkkwr4<-G0+v_F4;;Ul-feFAl4|PWgAHim%U$g8ngVw_@K>6@dk~Mt@~^x={40FCPSL$L*jqN zM}RO>^`bC1nL2j+O)qAQ&eg?*9D4>bG}?j@NR)8#E=EKGSP`q?i0f-% zYtrY_KMM4!i2&~|MVQ}+!k6y3j_%-vkf9Y078PnQT~%`>#NhY!`=_WT-ZZdf7IC%<9UDe zXe*Nd2~7nZi>m+GRAuhWk9|o%$5@~QUNo>IOfX!%(#cY_^MBOIL}#b{{o?57CkQ2> z0~?;ddrc)$hD4QElnLztwl+5pB1gu&Pyt<$a-z{ZJP*<|4zR1oW0}B8!r;l~0{+Nt z-rVKCJdUbs`BY4+DFj#ng-cwOr9l|pv|@N#ZeZ=4PWF@C_lo`I);(|Af%HjwY76ng<8!fuTxXuYR7cRlR~d&vu|Y zUs`H}Dc_N%plL_M`OvpIbg5}z3*7T86>ncvSWy^VnysbxlcLk(Xb>0~g}gzN{#0RV z_cS3*n^xk$1&W(t*PCI2uMBT#Hlo@dFE;ZeK1?(&-KuP}I=F)ki`8A6pHZ5i;4w!% zPbXx89~@vJmJpp#(gKnmK~+*0u8J zrfMwO9D+*E1n1F1_;82oNm%`z04x6Rq7qyx^a~r_8qY|a1r~1Kyb9$dAe5Zr{Ef*5 zQ|{pg5Fn@Xx0wp=WTo$F`5IR2RfsB#vsgqaNJbxT;qwG;Ap}|tTiq9k-&VRuIIbj! zvOH_Vr(qMoees~KgDqeH8T!F4APqkh1DnvP5k;^`E2o&|$c;_{9TUwct8J%>=vnHG zGQg5#n5(q*n}9{QyG4~r`Wh-c9bi9RGp3m5%@qSG{mKMW+V$(* zMI5|{TgFCNWB*vdwH(8;UQfarN@DC6^En>fo%j8DoDd*vt5D;7wLr1zE@o=*ew*?_ zUi_o)cZ5kF&{PuW(-HBqzr{QS=qyGFwVfN@rnBdfRajM#qNl!VuvyZM#AG%L=#hh{ zJ)%jn=GDMH747RyP@^Z5qm+IyueCb$+Am2*&pf7 zWBP1njGbwMFBu#tE`2OWBAWWQ5i0J{1~lEKa#9iYJqOp^6HZfdz;6#-TbkoUSo9Cl z6Fq-e@%fcw=HboGHQSINvCH;%QSIcdi!1?%y{cU8dSbO3;e@XSkzVV&rZ~}+2bMP0 z>4Cq0*0IbOHn?ku)DFkBSoL~k)(zjuro7ZLCX7Mp0iNs(`Z@yv=~eG}N>>U?-|M+a zg!(=B`2OaTQ9-$-vDWg3q@aINeD6=WQANR3j5nl=C~K_G7?zw`QnOC8<%TKVb;qms z4EAoGgGRKG?s=q35OV!K!8N&2~10#+r#A^3$wvrCzZ z0xuWuEVWsZgBs#*?2%lW6;SZkA=%&DjvZFs~TXAG%}D zNco9ku7~$3m6T6^LIua)c}c$Rx<2@$e8mCV>6>{}1TkFWP6HjxzlYHfy(-L=QwK&2cF3|ZtM9K1ggR}n=&xthR&x^sd*+>iIaZ#) zufYU|J@-d>T#9*o$@BPh+3NYtvJ+Xpa>4~IN5I&nON*@E28gyZhJ0}BWgV()^DP@Sy-cixDA9XT-ujGUPK)B9o|tAip7Q>x8lWz?_PuBxi& zXq2=xS_K)mDU9>xA4%&<&zRFi7$|8@e!psVm5|tg#gSZ^N`eo&+$vlhrzQc6X&8V4 zqV?WVg*gd>uNSQTa=nEQM}UzhlsIOBHc^4E-RmA_Tf^2A00zaT4M9K_lEK$vpSwo+ zpuR}>=ehw}!OpOJ*+-|Z?|tk#TfxI8pRG1n-6-?|0{QUTNTy(@4A{;Jzw9sfgVc13 z&-%@haQO8ldr7G8&fKf#PXnlPQh(09O4|GiGSN>)df)#1X$H9yg-sCps@s3WpvL6E z>A^dE9hJ&)gt1FtjWYnN3Q%_$m}@AtEZV*8fh=J8+O7BVkMNtvebG1rU)~1tpKmWT zy8=Oa{|3M+^(XZ20AvI}jNlILQ>lk9ZM(b5p4l?O386DQbW=pP+Lw`@?8E(~UjI99 z4uD8Z)CAO#nR3;nt$i%#z4OPwkO1g26&u$N1*&%a5(4q_z$F0$vE^={@`C(kAE?!j zH=hP>eLYh+Z~1H!g_lez2+0uyh5{+!Yv|7Rchc|P0P!q^T9jJKe+#5tfqRQ1?8QL- zI7kuy?)J5n_neVsgZOlJA?gO`QyNw%m^i2XbrsXxL~L=WLDH zCRytKdamuQY)7fmAHo${cJ+g(q@C}nuI``MVaF%3vHHLUK}5>2|6?VW$HEt|1rk9T z>G%NWU$w8MuAUV6O+*xq7mELG8sobQ;Qh_j)zU3t;OPD9qnET=RB3V^OFLagoFSR59-qv{QtWFfvA8M`<2&AG>2(Y(C8C>0C&qS% ztVFBpMquZoU64V_6mPH0cLDU*k6$}PUZ^AxyBLcv6@wAf*q9em%0~I4YEy4oJ-(j( z0t2Fg2_HYp!-?_fw|%Dj6c_vaXi|QAHjX}o<9h$;ZT3-(p)H`zE4(e>84Z@Qpv>^u z{@pq&$*_E8Kv8dOKQD4Ayg>}1q;2rZHo|QN(pbf_0epq@_ZO_ zhDJg1`yKumBP1SD=$38?cieCvOs1N+YqAffPQSRv7bjXaDPr`)PymYPA0Hd2gz&k& z1=yg>s9K+K4I+I;67-|%F*r6V!Q{2u^h=W(AgF%Zn7u3*`=*huNl>8MJ`O0WWjqeL zzrY8B&)Ehac7x8m&{BEW4Mf2ltgUl3EAgmq^ejahS%%DD35Z`%boix#;Ebe!;FQ;g z{-s;TkY80~GaH!5@4)DjIokvOPeSAh{9^*N=L111aNjgWAY_LVf>q-cnDra%dc%h) zoY5hK@13CO&{Pa=`#Dry8JL7%T{o)$b)8WG`E(cU7qS`S7&CcR<8Lip(-f*H&C@+i z)oA$hXWQ_1z>+rjJm_FWn@KH|c7z&gon=eT%x&_L)T0R}ID*5Qgc4nrRuI~xfv!!f zPfN)LCIor>kB6VF*#wq>Iyv$CRonMO-hifoQP!Q(hd)0lilDFsEtV+C(6ZNnJZRMw z8l1^}g&X0GT=JC4F3yp9paE2LrmOrBnJBT*KHx%W^p3CF15_rut#;J3Jk=Y-#IG~G zdKXcIhx|9AgO}s;Pr?%Pi?Ju!0_5I^ZMYjwe zkx2uxUP@SHK3$q{i1dT&t<&+)2wvr_OE|5AQ-R)bfP;_Dqg}gUx_BBMZt8>OLa#1+ zei8@F@t7EGN?aU9xgtQ#>=TCZib{}DHFK%$JPB=l^vCm*4*o6V*)U^38k3!>7(aBT zR*U7XH~Ji@`*q&yyMg%FR}0w`@OS$^o>WDNM9{>rW@1)i3ffK?@OZ0(0qOd3G%W7!)_+SdV-L z6Z;KX5PF}+LX-33v`Z3>XKZ*lsAn7_$h4H1h~A`iwPL3RBP&L@5Ok(vic*NfgqAUF z`!=aEjw(uaVC;WBkd+MAfJLW!`u z-;998(*CAu0lPN`d(I+7I)0y1&mvX<6>^6wjfhk_o)3Aq%tFp?By+PoiBCwqxgu&r z9d;H47*TnN&3xQAWEw@o$-o|4s>T|XEeDT6@%9=D_r@>SDo)x#x2-n?4{faf`5ZKH;Tdy{Z^;B3ZeZGzBI+#{M@8e8jG0fWG6S$^qrjeg z=eb`3p$_*`F_O$pe185^GOf_26~K5O$?6L?Q_Y|0WlL7gPa`vZuSo}4mYE)2u__ev zXn2{bl5&q^<16C^rU+rcT_0YuC(UJcbIu#%`L(8pNNEZA5niaTDzYdOGx0vZqFoIG z`J5kbXuE7jRb-VGU$@g3v-m>H+Xk+u?6+B%U8y4bB%ht9$;xMv2BBP-7_)`F;Cc7< zv%&`Slwmu*;;}^~&dp+GxaBrlWxEI2EA;N-iX0y(FF$gVGQ-6qa;#oY{>tC)j`bj! zI+ZR0VxL3U?iyqL*ecg<&r=H#Nu@>p3fJt*T&=}N_ed;m&)f(j2{R|5*KVT$eXwW? zDHL9*F9rW_i%+Tj`b*d&WTYre`%bg%D&V0X1K*Bc3e)5iGkhXDX3DH^6LZZIZ|~J| zKmrhlylGOZp3uz3^PCRzRBL?e5qPh-od(9e*NgcqKJq$Dq^MJLEW;q97V|uh$22}#trkZe+893 ziXO>;ppx#r`NpWb4g7C+g_0$L3twAiwFOz;$7!n$TIdCe$r2_=L0U9N({Z8gK?Tc@ z*G7smngtW+XkT@#9-a5$@i&|Z7wiHT4=S5oyIVE9N$$P@d198-;p{C4@b>w#1z2LE z0xyL4t(qe4CZweDtKsc7FcvBQ9yauZs-qq(jqLIBb$saz_ES+gdP$zm&BJ+^+^yQt zjNuaadIIdE&pGOuXi|EBk!`Y;(Jdr;D|DqlSWApQPj4~)YrU*vlCP~>Wx%ahv$eKTX_K*_gGv8|VHHrjBSY(KG1f=qFdwyMI6Cr} z`!2J7gbF3(^%%YfdccR*eZfP~0P2WRf<9caYvs$#PrHHO!}Rj4nN2Wjvx1$hFK`#d zV}Y&z`3K1zL|*hOs}3P9&D9s=&bsTn3CIAYr-y`6RRtD}4pMJ{PvxWkUo|3y=M{S+F&8|a?lgy8T|iL+V(;b8 zmM~nck_XcUj<-KPE%yDJ&@=H=+V9o+!Riou4#D2xM#uw_+$0vTrZTzdw!ZpHN)FO3 zEE=8w-K-HcN z*h7hmciziNd}WO6i=l;PL90Z}Jy~cJcW1#A3mER~Qqx~z@qJVGeF8tg`+M*v6$i+y zqA~7L8=KfH4Q_`69}|yl7ww~EIw}8n;l#-ECJ8=E{{%rIv;gB z+%fHvN*S8c*tho_)`nxpVt6c3rUF=i=qE5On137SRU-Ge0s9d!^WECGU$FYHYv=X6 zhF~kzG;W|CjpguuC=}mRQW!j6?vMLQ9+R@L_wB86L=ATX?g4;d8(QdvuctOMgTazA zZS%A)w=3NTW~SGlI8Hp;zQrzC)tK^j&Al*UuYWNmpS5@+Tu2vswjB3~k4;=HnNo2` z&8`OcXP?MlYjiYDc7Rke2>r5{x@rP-2;TZbB}l>isngHp<1FbsSWgRBPTK2+p8?(G zoESL5nv0yzyzBHxs`GV#Ze$B z5Pd*S8$nO#sO0e8ohMQo$X750FOgIb=s&q}?*VTgOHw+sy05bsI%Q&-(2}}HAFwxO zhEuaAa~42^R|sgt@>CdSz=Qg(kIDkydnF97!6VO5ImKm?BM9h_p}?obd$-k}Fep~( zI?P_Ikzx2f;nJpA|6xhc3QndQus7PxK04hMtjEWjKcW|LgV&E=4NJ;=bs>6&^04Jt z!g%{0jfIdaFHtcHMbmGi5)819N7k_vB&IV?e}E$wvA6Xu{?VzunEKC8q}gNc(`y6x zuadDlZ9t-^K_C|AqLvU8Ph^O}9DP1;_1bFFBTAA^4QkgD7_!o}_hv`nDn5#aRpV)$ zCnWUd>JR>PJXU9c^e$!MIJ;2)u-D>chKGCqw3+L8o?>qY+vm_PEe|NHaL}Va!*InW zqd5&LVX>~fqKBG(qMx6p!7V4jTbmD6JwSh4s7&*IJ3gWlCx_|{Mq>I7K;WTSvgRk^ zwtxV|#2X^PC?3S-@EAy?lNORj>GV8#wfUaHb`kghwySdC=6NMd^EBb+#mf-H^wWB8kxEAMypu}Ve#Lfmk`t<8P@}+kx3%^h_jAAE zX&Ne757JFFPMAm7Q^`yFZ2wkx9e^xHW9_A$$g#lw&Puck!9G9^znYj?lO1T*-%~_; z@%05>LvWrTU+tx-@CG zpozI*GwEbWWvOq^UgdBa4TcDX16OObN52NgX{L|Bu=?5Bj%d_FYz?2|{rD3bo5tCD zj3`@kal#;v<4#07HX*Hq|CWAIlW^i9x+0nlYHQ*Z!T5HbQK{D$x1;VWMH~B-gPY|z zhCGM>gd{H_niK8Ms04$sy-!AY+>!T5oiOFz_jiw&ol75hW5uf5*?rk2Sr&UI`zA)+ zFCso3r`tt9+KxD8BVeLPvUNW`V4So_d_ba`Npwm-Im@|%68nOYoXwXmok{7Zs5Q)6 z8Y4q2quKt?c)o`&C1vvWp|gfCcX`&;QWNjH$p%m%QH6KvX7U#$aX6eI5wMHbngtBF zec?)UDNUIj7z@e}XDs_6_GdD;R^y$zw@e%ow#zEoB1ImQr_uVm?#mPVBX5QC==6is z9!g1)or2xCOhxfa+-j4lyN~_&rlG+6<=fv~NqeYwSk->N%yty8lz9h?KU@qWM4;vr z3O>VZ#upezejo3=2t33|vIW}5U5)B3;DTFcm6*!kp84c!vx4!J-1hN_35NiyVtDVn zS)d%-Tc@$;yUxtsZS&NjpTf(4+H2p9i%JKYp5kv* z7;e(x)=hg`#zXiT<`!}nE*=Sy+pv!>*K986!G+vc%~(qH3W~6vzGN>AywQE{Sm&8B zIxEjxjNrywi!iYuk0YQ5ES1n<;Ygl^h20%02YTD(CIV^^1u zZNuf%>GJW>zC1z4@{d(`2HmaYR9I6p2crUa{y-1M2}%o3;18e=F2osp)og*+#^*!oA-!0A2qy; zFd)xJG?LFYv-(1x$o1(i0)3cWD>{SqPNed{^G=%u9h70FJ&~YTy}-fQTc+RNm#j1g zGKp^b%>&YYo~FMkcjh>(Lq)e()nV#ALNi+vz8jbp5Soq72xo&TanlKGe`)ogc`Lm@ zl2b;uF*;^ahp&pXUI_3oD09Z%R$A=$x8|Pge|^b9HV4p+y~Di@ruq>jhaRpRIE)dn zl#I2;WSTIRSU^KMv~&LNH%d#}nb~p>&9KDgdNuaQ`28^&RMX_;5|iV+p#CQ}q@7)J zH%)BfXbNWR(Yy{y{jg3ZJA8fL;NFX;L6%w4(o4I6ho@gZ8eS*sl8!6NRV1aEZOoP* zDzQ~v|6>qB+2hoe-nplyDnpIF>+QHA`9&TIGVgmY2HGu2g%jtATB`-9?6ltw5xxMX z;f9ct`vTa(^)Rf&RFKxrB0yRznB#Pq@=M&3of>+Rlpe!LtwWF(*S-skPNtrhyj*+% z3Ueufl0nHO?}z(dr}Zs$meO4`)u^5uy+6NiaL|O&TB_A);b5nxe?#JFaBhd~lxx9YrX>Q3uS{v?Onh!;W&Sm3+0Rm}&CUVY zV){F=r@`J_nqj*254Sf3;Rn3zS{g7Y1Nxts!KGY9Nm7VX4+)@_Wg-kzq7^7Xu^UX8 z_a**qqf(vpK9m`OfLKJLDx-K_xRDvb+L`rxWHqp2mpt_VgRfIFB3)y(I6eHdgvy-M z*#dSJcZ+Ms&a4W5sWgJ3u?e$|fBEyG%orNa_T`3t!se>?fWr^}%M}L*@(w1ruE`4U z>G!VhEuMOej+^Y=lEE3d_M5mRGBFByw~rWsR!LFHg2s&E1358Te$8d!U2nq zR(o3k3B2PHXTQM4mAFJ=aXVjcX?#)RXWIF|3$hzcqPrxn{qR{6EP|Br7MOO19B)O; zp?;9svo|Rroo->}+1}gVsZu|yZwY09_wX%RUPve*$zaj>bd?bPX;KKI8ge`{25wfE z$ObixK>e*#;fb$gvY$@IMsGD6BGI;wMzO=%n=vvh8XaQT6B0DK;I|bjM`Sh;F$x*C zRdOt-L_TOAATkt#ej2hOAeu~|{_KCU5w0Jg4|rR^jBmEN-}1_UhxVwAH``(8b_8T4 zsNk``TqZMwIf+2fov{<_arr6bLfcbl4(?yA27148gIIBfUEp#;Au$To^NOWuCN7}F zk?~drc%AhV_?&129R@%RiGZu|zcnPjmJ4fZV1sxJ3MznfYFMt40KDWaUO$STegejZ zuYl+V+3MGp2Y~={*IP6Xo+tXStW1>1-vAJ{_~zjb*u7&DSca)1typCqUmSaNoj-G% zF8@#<+V4L~VH+ywHdT7d1Ay8qVEjkl%hZ+P9B3IaiC$>)FdjKK`1OJ~64oOJ`w z6rjO8W-4(OHvzg11(|tC$WKmlBLCZJrDP$RI8EzrCFB4MY9=MI*S-pfUW{Y>&!7rL zc#{qA`^q<7)8+30bvep@Awa?5O>{wKX5|9}21h}%54ONcbQUc%bDl^w3%q%D_8mZE zU|Ru%tSBhq0T{F1`Ykz=2J}oEIDWzhp!lo`u7Ia~UgVp|IBX`Xgj~G6Y^kf|RtKv+ zu%mY}-Si_YTix%;TZ6iWakkq!oSn=XN8roU=dfs68w{s+W1_nRB_ZHC&T6RIM)!p# zaJ3d-d5LG~AWnY{hl$^gC8!2-FH;w_OTF-IBQAy!o{}_2f!RY7zC-m(p{u9atlkDu z(vTf}jGmM)=0RN$1Hr@vRQJxsPQB|J1j4Xh33&{I=F+XaQt6r-P$5S`V38C343Vh_8{jODAmRLoikCqT zOa4A_5_85)6v0$L0e>2-2&k8p=M`y6$WWM5-X1cPtdtlhVhO0S@S->Ry3q;+wK|Pcwb$B9=bU4Xv18O#<ol`(SfSrSj zor~)wDDl$M&&A8a_oa&`&A%%7k9wqSJgqz&+`JrIT_~UHwXk&c_7b6{es1VL|NZMd zy&P=+M@ufA|Lzv(Ap7%Q*g4ra*#EO`@Tlg5VX!Nb8y!NtqQ13c_y@%(n8T>pOh|GC8fT$h@MgAM5QKTmW1`|1CB@89Kx z*`GW9Uk2h|qx{cZFwCMT!tDP!Wuhnzn_SCKP~uPu(h}Of(0}s$dUU^E4%c&9E2dhH zXj?NXL>n`uCJ)FgbG=i_?&qD_!iiBDGiKwmWlgVJzr>A6MTm?jk+gNWx;m{hZ1tS< zob=1z?ffzol<)n>KPBi?|K)OJ^~I9xM*HfBri`4c&gc>VCS> zv8t}6GDo@&>pAPvo;`3tB7#|=GJhQGU~57lg;}8lm+$KuV@dv3?>qNOUZ{asCr z&Q@X89wsV;N__~4<4rIAVcTZn@0uo6OT09)?tr^LpAtyh2gi8ZMR{RcW73ooZT1AVj5RU0uv=(N)=+%BQWqu(C^V^ z5MS?p8)EjFNswF)Jjjd=O;D?6=um1F{J>iVs)e5lD<;Cvv<9=65hGsgDNjzJL+>{4 z2nLIh9QvGn(8`_!%sV9sxSnn!`T^cPR<7q>>69o30hEcBrJQ?$EpEybzUnycdN1Hf zA7}Rdb)&-aXK$t2E>uFc`?YY)>+wMZ(%0%ueJKX@zc)N_g5$Vn7Dmj`!jT> zIwQs?s5?*K#tT6&C~C*P?untq7fQU3VBcVp@W^{@7$=HcE`(j!y~h^nI_|o=W?A}H zB(=HP&RDplnkQJ)9ul8Q2c@0z6Qxd;$u{0ctVDBPI$IN0-}TGIb^j-$>h6a>t~?P> zfuARA)TXjH!>{n!J5MA(?cJR3)^{9zerec@WxJ!+ZG1YO#c9>E1s1^k*!|z{YUre| zmYhE=M|eJ5{0J{8n`aHZqrrHX3V!UrsVvWD?&5N}*DPsGg{c~8-O3P|7D%_aKU;lz zgk(eNyKZBLe!KU=6E6Axt~E#8&2g#|c)Z@Eczei+AR3Q!xnPA^Oq#OQ+xA^mD1kyO z|HKYr^2YD}ba+v*+-@pA_2*}Ar=uayDG{Hhudg6ql zN-~7ur9>=kyB%7J#;@n)&UI682Zp#C?8Jxr^C>opoVDMD;TV>`Im(j#|L*k?$-%WP zR_tD_2G^fS+wrXiG#SraS|tkDZYJ=Rd20vthQTxM;H(CmqeH-YZ{gB2^|Kjr!D-?s zrahuzhrc*p6^ln}C->hg%JW(A`_uA+9%CcevC6J50{NaH^G)sNgDUIb{Za@mGL*aH zq1w<%GHeS^wzITb<;B5Vc;T_OLP*3+WNvzce;p{d(;= zEkpRyv{N7M=O`*HV^DE&%} zK@*i!R6`zSK57e^9oEpPh8kIf?ZDC9<)3jnPqlAtVy#@Azw0{MdaYfAgrCm9va?)V zV)hluj#78V@@46i1qT@Wm%p0Sg&bV>J_K-+LdU|bhzBcQ-qsqn$R?4i$j*knOk68? zx@Wk0#V6*X>JRPbLhFwxN}ena$Iu0a8iqvm9RVkg=kG$Ed_2a5Ewo7$zGZ0xeo=+M z(?P<~=wtrdy${5K&@kB4%_TG9sfMkuV{ExLX7Qx>f;1-F-DY!dpU>M_l&8LktAQBY z=^^>dFt@H|pPmrtSvf^ysK^q>xMPWf(N?%DtR1*n#@$}}xyxQ2aCsHx2dWv>b)SZ?4wZQ+6Vj z3}QZ)+^ym{eqMfyr?3#=dxh{etWs_0P;pt6r>CFJLl~@%GJ1Siqj~o|bic7X^%5o>D-C*C$$R=a{c<`!Itx zx8$@R>|xc!fB&d2FuwH|dc9F?7_~|BQ~9q1(!$i9XOWVZvJPu)qe!F=-NL?dHF9q?%JH@Q1!=zuvL8*6Ud88Ne zft|zN$xU{&0vw0s7u3Bah&ox9GvymT=8eNSN1(4B1p$}-o!;<`ud z-S-}B(1<>0q2K+A4&)EV2FT%9n2_S&l_ka6S>JmKbU*dMX1-%aWPA2D7~Ixw+!k#h z(|*c!Z6@*y6Lgq-#;|Zj@7^EGNKg7J_$Jx10ds~MZ+e3@uys^AbIp6G!=91Fl?_V3 zMPQqE_AezEu_YNFrS-rjDCOVONtSxoLEd%p$fdzR#&!jscEz3y*s0onxwB!Rq`yb0 z<=sNqN!7r#BDh+kiuc@K?%+!y3<^z7Mm`Hk4PZwmTR;qf#)hR3K??SgC5Q~gpo^bb zLE|YnrB1k>2PNVZBH`LRx_neJ5)kW*@B#Quv9LKHnX67@$~q^++`~tk|4PrBOucztRdv ztKv&j zOLX$ur7h*Sz45V{9L_Aegs2x;?gYxcXZM))nM#>rM2Vb3RQ}W@)cJOkVF#SveLSzY z_DZ7oF%Uwa(QEpk=v5;^w=GQz&|WBGm&ewc`Z)^KWVpw0^ABu45P; z*G5OopxZ;CggX%$*^W&%@(yPd4UH>Xp{2jN600Y5(5>DM;j`-*5-mQT)pG8e!->jr zu|;615NVgfkB6`z?CYnQ!h9ZwDSBPlip2R43}qVb0Iy=nlgOT?v;#xrbYe||vGE7l zbjJG!2~Ql>()omqwcL0YfvfDQG2kvBY`ZX4fbDs-?eK7$Pp3KkGH37TTGImv-6-tEoI)a zvBI&X>Tsl|kw7K;DEsQk7!ACg>C`NNNEQ;vBU8WxkWt8NA`J{S5U@$Ta#W&?=EL@r zRp@l4WwD=y{v^*&qmc4jj`lQyGFBoh{06(2A!T=)urLZ)5s@N*<5(?})Gg%z9Pl~h z@dnW88=d^&vH>EvsiV0S)LkJY%G;T160z-AgKcv?-3?IC?+{tpXK}bYD&fDiakszC zKEZXKNWzgAsNRqm`;gH?Wg4$d{wVya-;yV}jb87;FN@n6br;1GNn9UugHnl~H^2YZ z2g-DdxRt|{hB2*k?SofmD~#q~HxxbpxA&iO*{e?2;iA5oM=Se3h)dwwN57BM&!Xl> z#r5q^oXXG>)WfU_DVHwx)W?qT5b-rU2m@I@XuG529;@?FoMp-j7> zOFJ;FzH=FiLK7SEuu@Q+NvW}%O`Lc8CD$X3Yf0;w5bMiPoo)D6F9KAw*92w0f>h^%BYP zd<19&Gi4%dT`YyjZQ@CecQ}dQ>>Of(3YFeXJ`}>y!Bc-ZZV5s(n-CD9G4@~VbM3uy z3ZqgdN6HFjN(yS$C4wo94!MD79^zN0{m-{+)!6s(S1xuayzYIX^v-;Vm7f+gWAkji znJwUa(cg)go>HJb7WTYfI-(d*YMYBc z0*-@R8A+-y*Y^wV_o^*Bozi->b4`Kcq^69m^C_|Vr?y%)I~RWzH0YIg8-w_G8H@){Pes62z|09F52HE&}$HbtFvk7T*bT$doV1r$f6*6va69SKe{=H==E*F#;(|!Zy}=pdOSOr>0xrLFsd_H$3Y|y+Oe#d zAC5x+t)Bly{&wBB>%k_|#sdR7JpJ0(`PKD z`lxY?;tuB{l0=(jJR!hy{lDegmobDOv0l-ixh~wWow;IF|yqq_%%L~U8)pxAp zlD+eVg)AI|&P*4E-)0 ze`J3>2lkj%i2#pqUutuI3p`(lSr9Q+t5P?X{&f98rn|G5-!^{!LVfq+ZC%IFEeM!B zG3^!>B*GCgYFeLxtv{bugFs~bX2Aln(z-x25Ge&iU@N&d9I@c-Q9JSclJ-k0IMyJ8 zp^#nHqx`o=4&TeZf(UI11n%}3n?t{VZEk}VdUH6^V*z^BzE>Paz;4P4^XJzmidV0R zq^dw9<^^yCvA^G>b|1(c0U|F2e|hD?RvcY)b0Fnkl>#%H<6fIcJQe4eDV*7 zGyz6qQ_=TFB=|8P_4AuQisB#5dts`kyMu%I>srA}yzu%af%j#U{{7#*>CHIyE-YuO z;Y2#-BCb3kpR%9N9jpDcQU%W>F+0?gKt0?jR;8vjnjx zsfd5`3FxdqADO0_;C{(hzEv`MsagOP#?7x6`ClI1?3WkBmgG4hKnwCe78;(yh?WI` zE`eB%Ew3!_>EUYWkMJn;dD(lHDxGSAt0t(sjW~8qHJ7yyNuoJLTB}`wddBWD_6pnc zbd@&anbM5buV*W!qd-XQwl1mpysWq;a|Lj7NH4%Dio8A!eu_gi0_Y|{R~(CB@5h;| zYq;Duq*KX*t}V8R5k*dVU||u^3Pm-?z^g75NxcIApqc-E>C#kPD?mLcKH7fGK_}+2 zUh4@Zb#6|{eD1E6TIZ0!Zf^eqB03|5Sdj4IvQ+0fh-?0E!SYO|``2~d9snF<`x#dO z&{JJQ9}3zc)@Gy2^5gnG;Mz?vT_bBG9F{~??7@qz!{<1$07rBeV64Q$RFEGa z00m%Exxzv1Gx*vU0oIdlD&q@R@AXJRkC@;hN-o72fci+`6vZ-QZ-Ay{4@)7YC@0HG zmFFWVG?6hKivVznv7tO+pRmB4#!637EK2X=(4sw^_SKZp%;NPf%f{a`dmSL%{o}qj z#2x_O8{}ooOJ4w%*&!qyHcDgYq!1k%jNJ`;9!-C zASMxNe@{<`Da?Xywv9dNxd_60_B++R@4*HPJq%T9egVRYh*y2(f5DJbl*M}Q&&cfY z&g+L$Y1ZBRybe>BHyx5FMtRp~k95WS5%Vz°DGR+bsvb*2Ql|1)1Th9#fwhk@@7 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 ids8|2gBI4Fpl@|x=~`AM>z}{0q#&azT_> for more information on the available binder properties you can configure. You are also able to configure middleware specific properties, see <> for more information. + +Spring Cloud Stream will automatically detect and use a binder that is found on the classpath, so you can easily use different types of middleware with the same code, just by including a different binder at build time. +For more complex use cases, Spring Cloud Stream also provides the ability of packaging multiple binders within the same application and choosing what type of binder should be used at runtime, and even if multiple binders should be used at runtime for different channels. + + +==== Fat JAR + +Spring Cloud Stream applications can be run in standalone mode from your IDE for testing. To run in production you can create an executable (or "fat") JAR using the standard Spring Boot tooling provided for Maven or Gradle. + +=== Persistent publish subscribe and consumer groups + +Communication between different applications follows a publish-subscribe pattern, with data being broadcast through shared topics. +This can be seen in the following picture, which shows a typical deployment for a set of interacting Spring Cloud Stream applications. + +.Spring Cloud Stream Application topologies +image::SCSt-with-binder.png[width=300,scaledwidth="50%"] + +Data reported by sensors to an HTTP endpoint is sent to a common destination named `raw-sensor-data`, from where it is independently processed by a microservice that computes time windowed averages, as well as by a microservice that ingests the raw data into HDFS. +In order to do so, both applications will declare the topic as their input at runtime. +The publish-subscribe communication model reduces the complexity of both the producer and the consumer, and allows adding new applications to the topology without disrupting the existing flow. +For example, downstream from the average calculator we can have a component that calculates the highest temperature values in order to display and monitor them. +Later on, we can add an application that interprets the very same flow of averages for fault detection. +The fact that all the communication is done through shared topics rather than point to point queues reduces the coupling between microservices. + +While the concept of publish-subscribe messaging is not new, Spring Cloud Stream takes the extra step of making it an opinionated choice for its application model. +It also makes it easy for users to work with it across different platform by using the native support of the middleware. + +[[consumer-groups]] +==== Consumer Groups +While the publish subscribe model ensures that it is easy to connect multiple application by sharing a topic, it is equally important to be able to scale up by creating multiple instances of a given application. +When doing so, the different instances would find themselves in a competing consumer relationship with each other: only one of the instances is expected to handle the message. +Spring Cloud Stream models this behavior through the concept of a consumer group, which is similar to (and inspired by) the notion of consumer groups in Kafka. +Each consumer binding can specify a group name such as `spring.cloud.stream.bindings.input.group=hdfsWrite` or `spring.cloud.stream.bindings.input.group=average`, as shown in the picture. +All groups that subscribe to a given destination will receive a copy of the published data, but only one member of the group will receive a given message from that destination. +By default, when a group is not specified, Spring Cloud Stream assigns the application to an anonymous, independent, single-member consumer group that will be in a publish-subscribe relationship with all the other consumer groups. + +.Spring Cloud Stream Consumer Groups +image::SCSt-groups.png[width=300,scaledwidth="50%"] + +[[durability]] +==== Durability + +Consistent with the opinionated application model of Spring Cloud Stream, consumer group subscriptions are durable. +This is to say that the binder implementation will ensure that group subscriptions are persistent and, once at least one subscription for a group has been created, that group will receive messages, even if they are sent while all the applications of the group were stopped. +Anonymous subscriptions are non-durable by nature. For some binder implementations (e.g. Rabbit) it is possible to have non-durable group subscriptions. + +In general, it is preferable to always specify a consumer group when binding an application to a given destination. +When scaling up a Spring Cloud Stream application, a consumer group must be specified for each of its input bindings, in order to prevent its instances from receiving duplicate messages (unless that behavior is desired, which is a less common use case). + +[[partitioning]] +=== Partitioning + +Spring Cloud Stream provides support for partitioning data between multiple instances of a given application. +In a partitioned scenario, one or more producer application instances will send data to multiple consumer application instances, ensuring that data with common characteristics is processed by the same consumer instance. +The physical communication medium (e.g. the broker topic) is viewed as structured into multiple partitions. +This happens regardless of whether the broker type is naturally partitioned (e.g. Kafka) or not (e.g. Rabbit), Spring Cloud Stream provides a common abstraction for implementing partitioned processing use cases in a uniform fashion. + +.Spring Cloud Stream Partitioning +image::SCSt-partitioning.png[width=300,scaledwidth="50%"] + +Partitioning is a critical concept in stateful processing, where ensuring that all the related data is processed together is critical for either performance or consistency. +For example, in the time-windowed average calculation example, it is important that measurements from the same sensor land in the same application instance. + +Setting up a partitioned processing scenario requires configuring both the data producing and the data consuming end. + +== Programming model + +This section will describe the programming model of Spring Cloud Stream, which consists from a number of predefined annotations that can be used to declare bound inputs and output channels, as well as how to listen to them. + +=== Declaring and binding channels + +==== Triggering binding via `@EnableBinding` + +A Spring application becomes a Spring Cloud Stream application when the `@EnableBinding` annotation is applied to one of its configuration classes. `@EnableBinding` itself is meta-annotated with `@Configuration`, and triggers the configuration of Spring Cloud Stream infrastructure as follows: + +[source,java] +---- +... +@Import(...) +@Configuration +@EnableIntegration +public @interface EnableBinding { + ... + Class[] value() default {}; +} +---- + +`@EnableBinding` can be parameterized with one or more interface classes, containing methods that represent bindable components (typically message channels). + +NOTE: As of version 1.0, the only supported bindable component is the Spring Messaging `MessageChannel` and its extensions `SubscribableChannel` and `PollableChannel`. +It is intended for future versions to extend support to other types of components, using the same mechanism. In this documentation, we will continue to refer to channels. + +==== `@Input` and `@Output` + +A Spring Cloud Stream application can have an arbitrary number of input and output channels defined as `@Input` and `@Output` methods in an interface, as follows: +[source,java] +---- +public interface Barista { + + @Input + SubscribableChannel orders(); + + @Output + MessageChannel hotDrinks(); + + @Output + MessageChannel coldDrinks(); +} +---- + +Using this interface as a parameter to `@EnableBinding`, as in the following example, will trigger the creation of three bound channels named `orders`, `hotDrinks` and `coldDrinks` respectively. + +[source,java] +---- +@EnableBinding(Barista.class) +public class CafeConfiguration { + + ... +} +---- + +===== Customizing channel names + +Both @Input and @Output allow specifying a customized name for the channel, as follows: + +[source,java] +---- +public interface Barista { + ... + @Input("inboundOrders") + SubscribableChannel orders(); +} +---- +In this case, the name of the bound channel being created will be `inboundOrders`. + +===== `Source`, `Sink`, and `Processor` + +For ease of addressing the most common use cases that involve either an input or an output channel, or both, out of the box Spring Cloud Stream provides three predefined interfaces. + +`Source` can be used for applications that have a single outbound channel. + +[source,java] +---- +public interface Source { + + String OUTPUT = "output"; + + @Output(Source.OUTPUT) + MessageChannel output(); + +} +---- + +`Sink` can be used for applications that have a single inbound channel. + +[source,java] +---- +public interface Sink { + + String INPUT = "input"; + + @Input(Sink.INPUT) + SubscribableChannel input(); + +} +---- + +`Processor` can be used for applications that have both an inbound and an outbound channel. + +[source,java] +---- +public interface Processor extends Source, Sink { +} +---- + +There is no special handling for either of these interfaces in Spring Cloud Stream, besides of the fact that they are provided out of the box. + +==== Accessing bound channels + +===== Injecting the bound interfaces + +For each of the bound interfaces, Spring Cloud Stream will generate a bean that implements it, and for which invoking an `@Input` or `@Output` annotated method will return the bound channel. +For example, the bean in the following example will send a message on the output channel every time its `hello` method is invoked, using the injected `Source` bean, and invoking `output()` to retrieve the target channel. + +[source,java] +---- +@Component +public class SendingBean { + + private Source source; + + @Autowired + public SendingBean(Source source) { + this.source = source; + } + + public void sayHello(String name) { + source.output().send(MessageBuilder.withPayload(body).build()); + } +} +---- + +===== Injecting channels directly + +Bound channels can be also injected directly. For example: + +[source, java] +---- +@Component +public class SendingBean { + + private MessageChannel output; + + @Autowired + public SendingBean(MessageChannel output) { + this.output = output; + } + + public void sayHello(String name) { + output.send(MessageBuilder.withPayload(body).build()); + } +} +---- + +Note that if the name of the channel is customized on the declaring annotation, that name should be used instead of the method name. Considering this declaration: + +[source,java] +---- +public interface CustomSource { + ... + @Output("customOutput") + MessageChannel output(); +} +---- + +The channel will be injected as follows: + +[source, java] +---- +@Component +public class SendingBean { + + @Autowired + private MessageChannel output; + + @Autowired @Qualifier("customOutput") + public SendingBean(MessageChannel output) { + this.output = output; + } + + public void sayHello(String name) { + customOutput.send(MessageBuilder.withPayload(body).build()); + } +} +---- + +==== Programming model + +Spring Cloud Stream allows you to write applications by either using Spring Integration annotations or Spring Cloud Stream's `@StreamListener` annotation which is modeled after other Spring Messaging annotations (e.g. `@MessageMapping`, `@JmsListener`, `@RabbitListener`, etc.) but add content type management and type coercion features. + +===== Native Spring Integration support + +Due to the fact that Spring Cloud Stream is Spring Integration based, it completely inherits its foundation and infrastructure, as well as the component. For example, the output channel of a `Source` can be attached to a `MessageSource`, as follows: + +[source, java] +---- @EnableBinding(Source.class) public class TimerSource { @@ -44,274 +380,65 @@ public class TimerSource { } ---- -`@EnableBinding` is parameterized by one or more interfaces (in this case a single `Source` interface), which declares -input and/or output channels. The interfaces `Source`, `Sink` and `Processor` are provided off the shelf, but you can -define others. Here's the definition of `Source`: +Or, the channels of a processor can be used in a transformer, as follows: [source,java] ---- -public interface Source { - String OUTPUT = "output"; - - @Output(Source.OUTPUT) - MessageChannel output(); +@EnableBinding(Processor.class) +public class TransformProcessor { + @Transformer(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) + public Object transform(String message) { + return message.toUpper(); + } } ---- -The `@Output` annotation is used to identify output channels (messages leaving the app), and `@Input` is used to -identify input channels (messages entering the app). It is optionally parameterized by a channel name - if the name is -not provided the method name is used instead. An implementation of the interface is created for you and can be used in -the application context by autowiring it, e.g. into a test case: +===== @StreamListener for automatic content type handling + +Complementary to the Spring Integration support, Spring Cloud Stream provides a `@StreamListener` annotation of its own modeled by the other similar Spring Messaging annotations (e.g. `@MessageMapping`, `@JmsListener`, `@RabbitListener`, etc.). +It provides a simpler model for handling inbound messages, especially for dealing with use cases that involve content type management and type coercion. +Spring Cloud Stream provides an extensible `MessageConverter` mechanism for handling data conversion by bound channels and, in this case, for dispatching to `@StreamListener` annotated methods. + +For example, an application that processes external `Vote` events can be declared as follows: [source,java] ---- -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = StreamApplication.class) -@WebAppConfiguration -@DirtiesContext -public class StreamApplicationTests { +@EnableBinding(Sink.class) +public class VoteHandler { @Autowired - private Source source + VotingService votingService; - @Test - public void contextLoads() { - assertNotNull(this.source.output()); - } + @StreamListener(Sink.INPUT) + public void handle(Vote vote) { + votingService.record(vote); + } } ---- -NOTE: In this case there is only one `Source` in the application context so there is no need to qualify it when it is -autowired. If there is ambiguity, e.g. if you are composing one application from some others, you can use the -`@Bindings` qualifier to inject a specific channel set. The `@Bindings` qualifier takes a parameter which is the class -that carries the `@EnableBinding` annotation (in this case the `TimerSource`). +The distinction between this approach and a Spring Integration `@ServiceActivator` becomes relevant if one considers an inbound `Message` with a `String` payload and a `contentType` header of `application/json`. +For `@StreamListener`, the `MessageConverter` mechanism will use the `contentType` header to parse the `String` into a `Vote` object. -==== Multiple Input or Output Channels +Just as with the other Spring Messaging methods, method arguments can be annotated with `@Payload`, `@Headers` and `@Header`. +For methods that return data, `@SendTo` must be used for specifying the output binding destination for data returned by the methods as follows: -A stream app can have multiple input or output channels defined as `@Input` and `@Output` methods in an interface. -Instead of just one channel named "input" or "output", you can add multiple `MessageChannel` methods annotated with -`@Input` or `@Output`, and their names will be converted to external destination names on the broker. It is common to -specify the channel names at runtime in order to have multiple applications communicate over well known destination -names. Channel names can be specified as properties that consist of the channel names prefixed with -`spring.cloud.stream.bindings` (e.g. `spring.cloud.stream.bindings.input` or `spring.cloud.stream.bindings.output`). -These properties can be specified though environment variables, the application YAML file, or any of the other -mechanisms supported by Spring Boot. - -For example, you can have two `MessageChannels` called "default" and "tap" in an application with -`spring.cloud.stream.bindings.default.destination=foo` and `spring.cloud.stream.bindings.tap.destination=bar`, -and the result is 2 bindings to an external broker with destinations called "foo" and "bar". - -==== Inter-app Communication - -While Spring Cloud Stream makes it easy for individual boot apps to connect to messaging systems, the typical scenario -for Spring Cloud Stream is the creation of multi-app pipelines, where microservice apps are sending data to each other. -This can be achieved by correlating the input and output destinations of adjacent apps, as in the following example. - -Supposing that the design calls for the `time-source` app to send data to the `log-sink` app, we will use a -common destination named `ticktock` for bindings within both apps. `time-source` will set -`spring.cloud.stream.bindings.output.destination=ticktock`, and `log-sink` will set -`spring.cloud.stream.bindings.input.destination=ticktock`. - -==== Consumer Group Support - -Spring Cloud Stream is a library focusing on building message-driven microservices, and more specifically stream -processing applications. In such scenarios, communication between different logical applications follows a -publish-subscribe pattern, with data being broadcast through a shared topic, but at the same time, it is important to -be able to scale up by creating multiple instances of a given application, which are in a competing consumer -relationship with each other. - -Spring Cloud Stream models this behavior through the concept of a consumer group, which is similar to the notion of -consumer groups in Kafka. Each consumer binding can specify a group name such as -`spring.cloud.stream.bindings.input.group=foo` (the actual name of the binding may vary). Each consumer group bound to -a given destination will receive a copy of the published data, but within the group, only one application will receive -each specific message. - -If no consumer group is specified for a given binding, then the binding is treated as if belonging to an anonymous, -independent, single-member consumer group. Otherwise said, if no consumer group is specified for a binding, it will be -in a publish-subscribe relationship with any other consumer groups. - -In general, it is preferable to always specify a consumer group when binding an application to a given destination. -When scaling up a Spring Cloud Stream application, a consumer group must be specified for each of its input bindings, -in order to prevent its instances from receiving duplicate messages (unless that behavior is desired, which is a less -common use case). - -NOTE: This feature has been introduced since version 1.0.0.M4. - -==== Instance Index and Instance Count - -When scaling up Spring Cloud Stream applications, each instance can receive information about how many other instances -of the same application exist and what its own instance index is. This is done through the -`spring.cloud.stream.instanceCount` and `spring.cloud.stream.instanceIndex` properties. For example, if there are 3 -instances of the HDFS sink application, all three will have `spring.cloud.stream.instanceCount` set to 3, and the -applications will have `spring.cloud.stream.instanceIndex` set to 0, 1 and 2, respectively. When Spring Cloud Stream -applications are deployed via Spring Cloud Data Flow, these properties are configured automatically, but when Spring -Cloud Stream applications are launched independently, these properties must be set correctly. By default -`spring.cloud.stream.instanceCount` is 1, and `spring.cloud.stream.instanceIndex` is 0. - -Setting up the two properties correctly on scale up scenarios is important for addressing partitioning behavior in -general (see below), and they are always required by certain types of binders (e.g. the Kafka binder) in order to -ensure that data is split correctly across multiple consumer instances. - -==== Advanced Binding Properties - -The input and output destination names are the primary properties to set in order to have Spring Cloud Stream -applications communicate with each other as their channels are bound to an external message broker automatically. -However, there are a number of scenarios where it is required to configure other attributes besides the destination -name. This is done using the following naming scheme: -`spring.cloud.stream.bindings..=`. The `destination` attribute is one such -example: `spring.cloud.stream.bindings.input.destination=foo`. A shorthand equivalent can be used as follows: -`spring.cloud.stream.bindings.input=foo`, but that shorthand can only be used only when there are no other attributes -to set on the binding. In other words, -`spring.cloud.stream.bindings.input.destination=foo`,`spring.cloud.stream.bindings.input.partitioned=true` is a valid -setup, whereas `spring.cloud.stream.bindings.input=foo`,`spring.cloud.stream.bindings.input.partitioned=true` is not. - -===== Partitioning - -Spring Cloud Stream provides support for partitioning data between multiple instances of a given application. In a -partitioned scenario, one or more producer apps will send data to one or more consumer apps, ensuring that data with -common characteristics is processed by the same consumer instance. The physical communication medium (i.e. the broker -topic or queue) is viewed as structured into multiple partitions. Regardless of whether the broker type is naturally -partitioned (e.g. Kafka) or not (e.g. Rabbit), Spring Cloud Stream provides a common abstraction for implementing -partitioned processing use cases in a uniform fashion. - -Setting up a partitioned processing scenario requires configuring both the data producing and the data consuming end. - -====== Configuring Output Bindings for Partitioning - -An output binding is configured to send partitioned data, by setting one and only one of its `partitionKeyExpression` -or `partitionKeyExtractorClass` properties, as well as its `partitionCount` property. For example, setting -`spring.cloud.stream.bindings.output.partitionKeyExpression=payload.id`,`spring.cloud.stream.bindings.output.partitionCount=5` -is a valid and typical configuration. - -Based on this configuration, the data will be sent to the target partition using the following logic. A partition key's -value is calculated for each message sent to a partitioned output channel based on the `partitionKeyExpression`. The -`partitionKeyExpression` is a SpEL expression that is evaluated against the outbound message for extracting the -partitioning key. If a SpEL expression is not sufficient for your needs, you can instead calculate the partition key -value by setting the property `partitionKeyExtractorClass`. This class must implement the interface -`org.springframework.cloud.stream.binder.PartitionKeyExtractorStrategy`. While, in general, the SpEL expression should -suffice, more complex cases may use the custom implementation strategy. - -Once the message key is calculated, the partition selection process will determine the target partition as a value -between `0` and `partitionCount - 1`. The default calculation, applicable in most scenarios is based on the formula -`key.hashCode() % partitionCount`. This can be customized on the binding, either by setting a SpEL expression to be -evaluated against the key via the `partitionSelectorExpression` property, or by setting a -`org.springframework.cloud.stream.binder.PartitionSelectorStrategy` implementation via the `partitionSelectorClass` -property. - -Additional properties can be configured for more advanced scenarios, as described in the following section. - -====== Configuring Input Bindings for Partitioning - -An input binding is configured to receive partitioned data by setting its `partitioned` property, as well as the -instance index and instance count properties on the app itself, as follows: -`spring.cloud.stream.bindings.input.partitioned=true`,`spring.cloud.stream.instanceIndex=3`,`spring.cloud.stream.instanceCount=5`. -The instance count value represents the total number of app instances between which the data needs to be partitioned, -whereas instance index must be a unique value across the multiple instances, between `0` and `instanceCount - 1`. The -instance index helps each app instance to identify the unique partition (or in the case of Kafka, the partition set) -from which it receives data. It is important that both values are set correctly in order to ensure that all the data is -consumed, and that the app instances receive mutually exclusive datasets. - -While setting up multiple instances for partitioned data processing may be complex in the standalone case, Spring Cloud -Data Flow can simplify the process significantly, by populating both the input and output values correctly, as well as -relying on the runtime infrastructure to provide information about the instance index and instance count. - -=== Binder Selection - -Spring Cloud Stream relies on implementations of the Binder SPI to perform the task of connecting channels to message -brokers. Each Binder implementation typically connects to one type of messaging system. Spring Cloud Stream provides -out of the box binders for Kafka, RabbitMQ and Redis. - -====== Classpath Detection - -By default, Spring Cloud Stream relies on Spring Boot's auto-configuration to configure the binding process. If a -single binder implementation is found on the classpath, Spring Cloud Stream will use it automatically. So, for example, -a Spring Cloud Stream project that aims to bind only to RabbitMQ can simply add the following dependency: - -[source,xml] +[source,java] ---- - - org.springframework.cloud - spring-cloud-stream-binder-rabbit - +@EnableBinding(Processor.class) +public class TransformProcessor { + + @Autowired + VotingService votingService; + + @StreamListener(Processor.INPUT) + @SendTo(Processor.OUTPUT) + public VoteResult handle(Vote vote) { + return votingService.record(vote); + } +} ---- -====== Multiple Binders on the Classpath - -When multiple binders are present on the classpath, the application must indicate which binder is to be used for each -channel binding. Each binder configuration contains a `META-INF/spring.binders`, which is a simple properties file: - -[source] ----- -rabbit:\ -org.springframework.cloud.stream.binder.rabbit.config.RabbitServiceAutoConfiguration ----- - -Similar files exist for the other binder implementations (i.e. Kafka and Redis), and it is expected that custom binder -implementations will provide them, too. The key represents an identifying name for the binder implementation, whereas -the value is a comma-separated list of configuration classes that contain one and only one bean definition of the type -`org.springframework.cloud.stream.binder.Binder`. - -Selecting the binder can be done globally by either using the `spring.cloud.stream.defaultBinder` property, e.g. -`spring.cloud.stream.defaultBinder=rabbit`, or by individually configuring them on each channel binding. - -For instance, a processor app that reads from Kafka and writes to Rabbit can specify the following configuration: -`spring.cloud.stream.bindings.input.binder=kafka`,`spring.cloud.stream.bindings.output.binder=rabbit`. - -====== Connecting to Multiple Systems - -By default, binders share the Spring Boot auto-configuration of the application and create one instance of each binder -found on the classpath. In scenarios where an application should connect to more than one broker of the same type, -Spring Cloud Stream allows you to specify multiple binder configurations, with different environment settings. Please -note that turning on explicit binder configuration will disable the default binder configuration process altogether, so -all the binders in use must be included in the configuration. - -For example, this is the typical configuration for a processor that connects to two RabbitMQ broker instances: - -[source,yml] ----- -spring: - cloud: - stream: - bindings: - input: - destination: foo - binder: rabbit1 - output: - destination: bar - binder: rabbit2 - binders: - rabbit1: - type: rabbit - environment: - spring: - rabbitmq: - host: - rabbit2: - type: rabbit - environment: - spring: - rabbitmq: - host: ----- - - - -=== Managed vs Standalone - -Code using the Spring Cloud Stream library can be deployed as a standalone application or be used as a Spring Cloud -Data Flow module. In standalone mode, your application will run happily as a service or in any PaaS (Cloud Foundry, -Heroku, Azure, etc.). Spring Cloud Data Flow helps orchestrate the communication between instances, so the aspects of -configuration that deal with application interconnection will be configured transparently. - -==== Fat JAR - -You can run in standalone mode from your IDE for testing. To run in production you can create an executable (or "fat") -JAR using the standard Spring Boot tooling provided for Maven or Gradle. - -==== Health Indicator - -Spring Cloud Stream provides a health indicator for the binders, registered under the name of `binders`. It can be -enabled or disabled using the `management.health.binders.enabled` property. +NOTE: Content type headers can be either set by external applications in the case of Rabbit MQ, and they are supported as part of an extended internal protocol by Spring Cloud Stream for any type of transport (even the ones that do not support headers normally, like Kafka). === Binder SPI @@ -356,18 +483,405 @@ The RabbitMQ Binder implementation maps the destination to a `TopicExchange`, an will be bound to that `TopicExchange`. Each consumer instance that binds will trigger creation of a corresponding RabbitMQ `Consumer` instance for its group’s `Queue`. -==== Redis Binder +== Configuration options -.Redis Binder -image::redis-binder.png[width=300,scaledwidth="50%"] +Spring Cloud Stream supports general configuration options, as well as configuration for bindings and binders. Some binders allow additional properties for the bindings, supporting middleware-specific features. -NOTE: we recommend only using the Redis Binder for development +All configuration options can be provided to Spring Cloud Stream applications via all the mechanisms supported by Spring Boot: application arguments, environment variables, YML files etc. -The Redis Binder creates a `LIST` (which performs the role of a queue) for each consumer group. A consumer binding will -trigger `BRPOP` operations on its group's `LIST`. A producer binding will consult a `ZSET` to determine what groups -currently have active consumers, and then for each message being sent, an `LPUSH` operation will be executed on each of -those group's `LISTs`. +==== Spring Cloud Stream Properties -=== Samples +spring.cloud.stream.instanceCount:: + The number of deployed instances of the same application. Must be set for partitioning and with Kafka. Default value is `1`. +spring.cloud.stream.instanceIndex:: + The instance index of the application, a number from `0` to `instanceCount`-1. Used for partitioning and with Kafka. Automatically set in Cloud Foundry to match the instance index of the application. +spring.cloud.stream.dynamicDestinations:: + A list of destinations that can be bound dynamically, for example in a dynamic routing scenario. Only listed destinations can be bound if set. Default empty, allowing any destination to be bound. +spring.cloud.stream.defaultBinder:: + The default binder to use, if there are multiple binders configured. See <>. -For Spring Cloud Stream samples, please refer: https://github.com/spring-cloud/spring-cloud-stream-samples \ No newline at end of file +[[binding-properties]] +=== Binding properties + +Binding properties are supplied using the format `spring.cloud.stream.bindings..=`.`` represents the name of the channel being configured, e.g. `output` for a `Source`. +In what follows, we will indicate where the `spring.cloud.stream.bindings..` prefix is omitted and focus just on the property name, with the understanding that the prefix will be included at runtime. + +==== Properties for the use of Spring Cloud Stream + +The following binding properties are available for both input and output bindings and +must be prefixed with `spring.cloud.stream.bindings..` . + +destination:: + The target destination of channel on the bound middleware, e.g. Rabbit MQ exchange or + Kafka topic. If not set, the channel name will be used instead. +group:: + The consumer group of the channel. This property applies only to inbound bindings. + By default it is null, and indicates an anonymous consumer. See <>. +contentType:: + The content type of the channel. By default it is `null` and no type + coercion is performed. See <>. +binder:: + The binder used by this binding. By default, it is set to `null` and will + use the default binder, if one exists. See <> for details. + +==== Consumer properties + +The following binding properties are available for input bindings only and must be prefixed with `spring.cloud.stream.bindings..consumer`: + +concurrency:: + The concurrency of the inbound consumer. By default, set to `1`. +partitioned:: + Must be set to `true` if the consumer is receiving data from a partitioned + producer. By default it is set to `false`. +maxAttempts:: + The number of attempts of re-processing an inbound message. Default '3'. (Ignored by Kafka, currently). +backOffInitialInterval:: + The backoff initial interval on retry. Default `1000`.(Ignored by Kafka, currently). +backOffMaxInterval:: + The maximum backoff interval. Default `10000`.(Ignored by Kafka, currently). +backOffMultiplier:: + The backoff multiplier. Default `2.0`. + +==== Producer properties + +The following binding properties are available for output bindings only and must be prefixed with `spring.cloud.stream.bindings..producer`: + +partitionKeyExpression:: + A SpEL expression for partitioning outbound data. Default: `null`. If either this property is set or + `partitionKeyExtractorClass` is present, outbound data on this channel will be partitioned, + and `partitionCount` must be set to a value larger than 1 to be effective. + The two options are mutually exclusive. See <>. +partitionKeyExtractorClass:: + A `PartitionKeyExtractorStrategy` implementation. Default: `null`. If either this property is set or + `partitionKeyExpression` is present, outbound data on this channel will be partitioned, + and `partitionCount` must be set to a value larger than 1 to be effective. + The two options are mutually exclusive. See <>. +partitionSelectorClass:: + A `PartitionSelectorStrategy` implementation. Default `null`. Mutually exclusive with + `partitionSelectorExpression`. If none is set, the partition will be selected as the + `hashCode(key) % partitionCount`, where `key` is computed via either `partitionKeyExpression` + or `partitionKeyExtractorClass`. +partitionSelectorExpression:: + A SpEL expression for customizing partition selection. Default `null`. Mutually exclusive with + `partitionSelectorClass`. If none is set, the partition will be selected as the + `hashCode(key) % partitionCount`, where `key` is computed via either `partitionKeyExpression` + or `partitionKeyExtractorClass`. +partitionCount:: + The number of target partitions for the data, if partitioning is enabled. Default `1`. Must be + set to a value higher than `1` if the producer is partitioned. On Kafka it is interpreted as a + hint, and the larger of this and the partition count of the target topic will be used instead. +requiredGroups:: + A comma separated list of groups that the producer must ensure message delivery even if they + start after it has been created (e.g. by pre-creating durable queues in Rabbit MQ). + +[[binder-specific-configuration]] +== Binder-specific configuration + +This captures the binder, consumer and producer properties that are specific for several binder +implementations. + +=== Rabbit-specific settings + +==== Rabbit MQ Binder properties + +The binder supports the all Spring Boot properties for Rabbit MQ configuration. + +In addition to that, it also supports the following properties: + +spring.cloud.stream.binder.rabbit.addresses:: + A comma-separated list of RabbitMQ server addresses (used only for clustering and in conjunction with `nodes`). Default empty. +spring.cloud.stream.binder.rabbit.adminAddresses. Default empty. + A comma-separated list of RabbitMQ management plugin URLs - only used when nodes contains more than one entry. Entries in this list must correspond to the corresponding entry in addresses. Default empty. +spring.cloud.stream.binder.rabbit.nodes:: + A comma-separated list of RabbitMQ node names; when more than one entry, used to locate the server address where a queue is located. Entries in this list must correspond to the corresponding entry in addresses. Default empty. +spring.cloud.stream.binder.rabbit.username:: + The user name. Default `null`. +spring.cloud.stream.binder.rabbit.password:: + The password. Default `null`. +spring.cloud.stream.binder.rabbit.vhost:: + The virtual host. Default `null`. +spring.cloud.stream.binder.rabbit.useSSL:: + True if Rabbit MQ should use SSL. +spring.cloud.stream.binder.rabbit.sslPropertiesLocation:: + The location of the SSL properties file, when certificate exchange is used. +spring.cloud.stream.binder.rabbit.compressionLevel:: + Compression level for compressed bindings. Defaults to `1` (BEST_LEVEL). See `java.util.zip.Deflater`. + +==== Rabbit MQ Consumer Properties + +The following properties are available for Rabbit consumers only and +must be prefixed with `spring.cloud.stream.bindings..` + +acknowledgeMode:: + The acknowledge mode. Default `AUTO`. +autoBindDlq:: + Whether to automatically declare the DLQ and bind it to the binder DLX. Default `false`. +durableSubscription:: + Whether subscription should be durable. Only effective if `group` is also set. Default `true`. +maxConcurrency: + Default `1`. +prefetch: + Prefetch count. Default `1`. +prefix:: + A prefix to be added to the name of the `destination` and queues. Default "". +requeueRejected:: + Whether delivery failures should be requeued. Default `true`. +requestHeaderPatterns:: + The request headers to be transported. Default `[STANDARD_REQUEST_HEADERS,'*']`. +replyHeaderPatterns:: + The reply headers to be transported. Default `[STANDARD_REQUEST_HEADERS,'*']` +republishToDlq:: + By default, failed messages after retries are exhausted are rejected. If a dead-letter queue (DLQ) is configured, rabbitmq will route the failed message (unchanged) to the DLQ. Setting this property to true instructs the bus to republish failed messages to the DLQ, with additional headers, including the exception message and stack trace from the cause of the final failure. +transacted:: + Whether to use transacted channels. Default `false`. +txSize:: + The number of deliveries between acks. Default `1`. + +==== Rabbit Producer Properties + +The following properties are available for Rabbit producers only and +must be prefixed with `spring.cloud.stream.bindings..` + +autoBindDlq:: + Whether to automatically declare the DLQ and bind it to the binder DLX. Default `false`. +batchingEnabled:: + True to enable message batching by producers. Default `false`. +batchSize:: + The number of message to buffer when batching is enabled. Default `100`. +batchBufferLimit:: + Default `10000`. +batchTimeout:: + Default `5000`. +compress:: + Whether data should be compressed when sent. Default `false`. +deliveryMode:: + Delivery mode. Default `PERSISTENT`. +prefix:: + A prefix to be added to the name of the `destination` exchange. Default "". +requestHeaderPatterns:: + The request headers to be transported. Default `[STANDARD_REQUEST_HEADERS,'*']`. +replyHeaderPatterns:: + The reply headers to be transported. Default `[STANDARD_REQUEST_HEADERS,'*']` + +=== Kafka-specific settings + +==== Kafka binder properties + +spring.cloud.stream.binder.kafka.brokers:: + A list of brokers that the Kafka binder will connect to. Default `localhost`. +spring.cloud.stream.binder.kafka.defaultBrokerPort:: + The list of brokers allows to specify hosts with or without port information, i.e. `host1,host2:port2`. This configuration sets the default port when no port is configured in the broker list. Default `9092`. +spring.cloud.stream.binder.kafka.zkNodes:: + A list of Zookeeper nodes for the Kafka binder to connect to. Default `localhost`. +spring.cloud.stream.binder.kafka.defaultZkPort:: + The list of Zookeeper nodes allows to specify hosts with or without port information, i.e. `host1,host2:port2`. This configuration sets the default port when no port is configured in the node list. Default `2181`. +spring.cloud.stream.binder.kafka.headers:: + The list of custom that will be transported by the binder. Default empty. +spring.cloud.stream.binder.kafka.offsetUpdateTimeWindow:: + The frequency in milliseconds with which offsets are saved. Ignored if `0`. Default `10000`. +spring.cloud.stream.binder.kafka.offsetUpdateCount:: + The frequency in number of updates, which which consumed offsets are persisted. Ignored if `0`. Default `0`. Mutually exclusive with `offsetUpdateTimeWindow`. +spring.cloud.stream.binder.kafka.requiredAcks:: + The number of required acks on the broker. + +==== Kafka Consumer Properties + +The following properties are available for Kafka consumers only and +must be prefixed with `spring.cloud.stream.bindings..` + +autoCommitOffset:: + True to autocommit offsets when a message has been processed. If set to false, an `Acknowledgment` header will be available in the message headers for late acknowledgment. Default `true`. +mode:: + When set to `raw`, will disable header parsing on input. Useful when inbound data is coming from outside Spring Cloud Stream applications. Default `embeddedHeaders`. +resetOffsets:: + True to reset offsets on the consumer to the value provided by `startOffset`. Default `false`. +startOffset:: + The starting offset for new groups or when `resetOffsets` is `true`. Allowed values: `earliest`,`latest`. Defaults to null (equivalent to earliest). +minPartitionCount:: + The minimum number of partitions expected by the consumer if it creates the consumed topic automatically. Defaults to `1`. + +==== Kafka Producer Properties + +The following properties are available for Kafka producers only and +must be prefixed with `spring.cloud.stream.bindings..` + +bufferSize:: + This is an upper limit of how much data the Kafka Producer will attempt to batch before sending – specified in bytes. Default `16384`. +sync:: + Whether the producer is synchronous. Defaults to `false`. +batchTimeout:: + How long will the producer wait before sending in order to allow more messages to get accumulated in the same batch. Normally the producer will not wait at all, and simply send all the messages that accumulated while the previous send was in progress. A non-zero value may increase throughput at the expense of latency. Default `0`. +mode:: + When set to `raw`, disable header propagation on output. Useful when producing data for non-Spring Cloud Stream applications. Default `embeddedHeaders`. + +== Binder detection + +Spring Cloud Stream relies on implementations of the Binder SPI to perform the task of connecting channels to message +brokers. Each Binder implementation typically connects to one type of messaging system. Spring Cloud Stream provides +out of the box binders for Kafka, RabbitMQ and Redis. + +=== Classpath Detection + +By default, Spring Cloud Stream relies on Spring Boot's auto-configuration to configure the binding process. If a +single binder implementation is found on the classpath, Spring Cloud Stream will use it automatically. So, for example, +a Spring Cloud Stream project that aims to bind only to RabbitMQ can simply add the following dependency: + +[source,xml] +---- + + org.springframework.cloud + spring-cloud-stream-binder-rabbit + +---- + +[[multiple-binders]] +=== Multiple Binders on the Classpath + +When multiple binders are present on the classpath, the application must indicate which binder is to be used for each channel binding. Each binder configuration contains a `META-INF/spring.binders`, which is a simple properties file: + +[source] +---- +rabbit:\ +org.springframework.cloud.stream.binder.rabbit.config.RabbitServiceAutoConfiguration +---- + +Similar files exist for the other binder implementations (e.g. Kafka), and it is expected that custom binder +implementations will provide them, too. The key represents an identifying name for the binder implementation, whereas +the value is a comma-separated list of configuration classes that contain one and only one bean definition of the type +`org.springframework.cloud.stream.binder.Binder`. + +Selecting the binder can be done globally by either using the `spring.cloud.stream.defaultBinder` property, e.g. +`spring.cloud.stream.defaultBinder=rabbit`, or by individually configuring them on each channel binding. + +For instance, a processor app that reads from Kafka and writes to Rabbit can specify the following configuration: +`spring.cloud.stream.bindings.input.binder=kafka`,`spring.cloud.stream.bindings.output.binder=rabbit`. + +=== Connecting to Multiple Systems + +By default, binders share the Spring Boot auto-configuration of the application and create one instance of each binder +found on the classpath. In scenarios where an application should connect to more than one broker of the same type, +Spring Cloud Stream allows you to specify multiple binder configurations, with different environment settings. Please +note that turning on explicit binder configuration will disable the default binder configuration process altogether, so +all the binders in use must be included in the configuration. + +For example, this is the typical configuration for a processor that connects to two RabbitMQ broker instances: + +[source,yml] +---- +spring: + cloud: + stream: + bindings: + input: + destination: foo + binder: rabbit1 + output: + destination: bar + binder: rabbit2 + binders: + rabbit1: + type: rabbit + environment: + spring: + rabbitmq: + host: + rabbit2: + type: rabbit + environment: + spring: + rabbitmq: + host: +---- + +[[contenttypemanagement]] +== Content Type and Transformation + +Spring Cloud Stream allows to propagate information about the content type of the messages it produces by attaching by default a `contentType` header to outbound messages. +For middleware that does not directly support headers, Spring Cloud Stream provides its own mechanism of wrapping outbound messages in an envelope of its own, automatically. +For middleware that does support headers, Spring Cloud Stream applications may receive messages with a given content type from non-Spring Cloud Stream applications. + +Spring Cloud Stream can handle messages based on this information in two ways: + +* through its `contentType` settings on inbound and outbound channels; +* through its argument mapping done for `@StreamListener`-annotated methods. + +=== Type converting message channels + +=== @StreamListener and conversion + +== Inter-app Communication + +=== Connecting multiple application instances + +While Spring Cloud Stream makes it easy for individual boot apps to connect to messaging systems, the typical scenario for Spring Cloud Stream is the creation of multi-app pipelines, where microservice apps are sending data to each other. +This can be achieved by correlating the input and output destinations of adjacent apps, as in the following example. + +Supposing that the design calls for the `time-source` app to send data to the `log-sink` app, we will use a +common destination named `ticktock` for bindings within both apps. `time-source` will set +`spring.cloud.stream.bindings.output.destination=ticktock`, and `log-sink` will set +`spring.cloud.stream.bindings.input.destination=ticktock`. + +=== Instance Index and Instance Count + +When scaling up Spring Cloud Stream applications, each instance can receive information about how many other instances +of the same application exist and what its own instance index is. This is done through the +`spring.cloud.stream.instanceCount` and `spring.cloud.stream.instanceIndex` properties. For example, if there are 3 +instances of the HDFS sink application, all three will have `spring.cloud.stream.instanceCount` set to 3, and the +applications will have `spring.cloud.stream.instanceIndex` set to 0, 1 and 2, respectively. When Spring Cloud Stream +applications are deployed via Spring Cloud Data Flow, these properties are configured automatically, but when Spring +Cloud Stream applications are launched independently, these properties must be set correctly. By default +`spring.cloud.stream.instanceCount` is 1, and `spring.cloud.stream.instanceIndex` is 0. + +Setting up the two properties correctly on scale up scenarios is important for addressing partitioning behavior in +general (see below), and they are always required by certain types of binders (e.g. the Kafka binder) in order to +ensure that data is split correctly across multiple consumer instances. + +=== Partitioning + +==== Configuring Output Bindings for Partitioning + +An output binding is configured to send partitioned data, by setting one and only one of its `partitionKeyExpression` +or `partitionKeyExtractorClass` properties, as well as its `partitionCount` property. For example, setting +`spring.cloud.stream.bindings.output.partitionKeyExpression=payload.id`,`spring.cloud.stream.bindings.output.partitionCount=5` +is a valid and typical configuration. + +Based on this configuration, the data will be sent to the target partition using the following logic. A partition key's +value is calculated for each message sent to a partitioned output channel based on the `partitionKeyExpression`. The +`partitionKeyExpression` is a SpEL expression that is evaluated against the outbound message for extracting the +partitioning key. If a SpEL expression is not sufficient for your needs, you can instead calculate the partition key +value by setting the property `partitionKeyExtractorClass`. This class must implement the interface +`org.springframework.cloud.stream.binder.PartitionKeyExtractorStrategy`. While, in general, the SpEL expression should +suffice, more complex cases may use the custom implementation strategy. + +Once the message key is calculated, the partition selection process will determine the target partition as a value +between `0` and `partitionCount - 1`. The default calculation, applicable in most scenarios is based on the formula +`key.hashCode() % partitionCount`. This can be customized on the binding, either by setting a SpEL expression to be +evaluated against the key via the `partitionSelectorExpression` property, or by setting a +`org.springframework.cloud.stream.binder.PartitionSelectorStrategy` implementation via the `partitionSelectorClass` +property. + +Additional properties can be configured for more advanced scenarios, as described in the following section. + +===== Configuring Input Bindings for Partitioning + +An input binding is configured to receive partitioned data by setting its `partitioned` property, as well as the +instance index and instance count properties on the app itself, as follows: +`spring.cloud.stream.bindings.input.partitioned=true`,`spring.cloud.stream.instanceIndex=3`,`spring.cloud.stream.instanceCount=5`. +The instance count value represents the total number of app instances between which the data needs to be partitioned, +whereas instance index must be a unique value across the multiple instances, between `0` and `instanceCount - 1`. The +instance index helps each app instance to identify the unique partition (or in the case of Kafka, the partition set) +from which it receives data. It is important that both values are set correctly in order to ensure that all the data is +consumed, and that the app instances receive mutually exclusive datasets. + +While setting up multiple instances for partitioned data processing may be complex in the standalone case, Spring Cloud +Data Flow can simplify the process significantly, by populating both the input and output values correctly, as well as +relying on the runtime infrastructure to provide information about the instance index and instance count. + +== Health Indicator + +Spring Cloud Stream provides a health indicator for the binders, registered under the name of `binders`. It can be +enabled or disabled using the `management.health.binders.enabled` property. + +== Samples + +For Spring Cloud Stream samples, please refer: https://github.com/spring-cloud/spring-cloud-stream-samples