From 39ab0d61efad48fa65646a4aa829e70ed7e5b2f7 Mon Sep 17 00:00:00 2001 From: benni Date: Tue, 25 Dec 2012 16:52:37 +0100 Subject: [PATCH] release of 1.0.6.RC, fixed #14 and #11 --- README.md | 40 +++- build.gradle | 2 +- .../mbassador/1.0.6.RC/mbassador-1.0.6.RC.jar | Bin 0 -> 46135 bytes .../1.0.6.RC/mbassador-1.0.6.RC.jar.md5 | 1 + .../1.0.6.RC/mbassador-1.0.6.RC.jar.sha1 | 1 + .../mbassador/1.0.6.RC/mbassador-1.0.6.RC.pom | 58 ++++++ .../1.0.6.RC/mbassador-1.0.6.RC.pom.md5 | 1 + .../1.0.6.RC/mbassador-1.0.6.RC.pom.sha1 | 1 + maven/org/mbassy/mbassador/maven-metadata.xml | 3 +- .../mbassy/mbassador/maven-metadata.xml.md5 | 2 +- .../mbassy/mbassador/maven-metadata.xml.sha1 | 2 +- pom.xml | 2 +- .../java/org/mbassy/AbstractMessageBus.java | 38 ++-- .../java/org/mbassy/BusConfiguration.java | 1 + src/main/java/org/mbassy/IMessageBus.java | 27 ++- src/main/java/org/mbassy/MBassador.java | 44 +++-- .../java/org/mbassy/MessagePublication.java | 73 +++++++ .../java/org/mbassy/SyncAsyncPostCommand.java | 11 +- .../java/org/mbassy/listener/Listener.java | 2 + .../listener/MessageHandlerMetadata.java | 17 ++ .../listener/MessageListenerMetadata.java | 53 ++++++ .../org/mbassy/listener/MetadataReader.java | 37 ++-- .../org/mbassy/subscription/Subscription.java | 5 + .../SubscriptionDeliveryRequest.java | 28 --- src/test/java/org/mbassy/AllTests.java | 3 +- .../java/org/mbassy/MetadataReaderTest.java | 178 ++++++++++++++++++ 26 files changed, 527 insertions(+), 103 deletions(-) create mode 100644 maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.jar create mode 100644 maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.jar.md5 create mode 100644 maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.jar.sha1 create mode 100644 maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.pom create mode 100644 maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.pom.md5 create mode 100644 maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.pom.sha1 create mode 100644 src/main/java/org/mbassy/MessagePublication.java create mode 100644 src/main/java/org/mbassy/listener/MessageListenerMetadata.java delete mode 100644 src/main/java/org/mbassy/subscription/SubscriptionDeliveryRequest.java create mode 100644 src/test/java/org/mbassy/MetadataReaderTest.java diff --git a/README.md b/README.md index 4a69bca..ae47ce1 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,20 @@ Mbassador ========= Mbassador is a very light-weight message (event) bus implementation following the publish subscribe pattern. It is designed -for ease of use and aims to be feature rich, extensible while preserving resource efficiency and performance. - -It uses a specialized data structure to allow high throughput for concurrent access. +for ease of use and aims to be feature rich and extensible while preserving resource efficiency and performance. It uses a specialized +data structure to allow high throughput for concurrent access. Read this documentation to get an overview of its features and how cool this message (event) bus actually is. You can also check out the performance comparison which also contains a partial list of the features of the compared implementations. -The current version is 1.0.5.RC +The current version is 1.0.6.RC, see the release notes for more details. Table of contents: + [Features](#features) + [Usage](#usage) + [Installation](#installation) ++ [Release Notes](#releasenotes) + [Roadmap](#roadmap) + [Credits](#credits) + [Contribute](#contribute) @@ -124,26 +124,46 @@ will be done as soon as enough people use this component. Until then, the follow <dependency> <groupId>org.mbassy</groupId> <artifactId>mbassador</artifactId> - <version>1.0.0.RC</version> + <version>1.0.6.RC</version> </dependency> 3. Run mvn clean package to have maven download and install the required version into your local repository Of course you can always clone the repository and build from source +

Release Notes

+ +

1.0.6.RC

+ + + Fixed behaviour with capacity bound blocking queue such that there now are two methods to schedule a message + asynchronously. One will block until capacity becomes available, the other will timeout after a specified amount of + time. + + Added unit tests + +

1.0.5.RC

+ + + Added MessageEnvelope and @Enveloped annotation to configure handlers that might receive arbitrary message type + + Added handler configuration property to @Listener annotation to move from message filtering to more specific implementation + of this feature + +

1.0.4.RC

+ + + Introduced BusConfiguration as a central class to encapsulate configurational aspects + +

Roadmap

+ Checkout MBassador from one of the official maven repositories (as soon as the user base is big enough) + Spring integration with support for conditional message dispatch in transactional context (dispatch only after successful commit etc.). Currently in beta, see this repository -+ MessageEnvelope for each dispatch that is passed to the handler and can be used for communication between handlers -during the running dispatch

Credits

The initial inspiration for creating this component came from looking at Google Guava's event bus implementation. Since -it did not provide all the features we needed in our project, I decided to create my own implementation. When I saw that -it outperformed the Guava implementation by far, I decided to share it with the community to see if others consider it worth -a shot. +it did not provide all the features we needed in our project, I decided to create my own implementation. It matured to be +quite a feature rich and yet very efficient and performant message bus. + +I want to thank the development team from friendsurance (www.friendsurance.de) for their support and feedback on the bus +implementation and the management of friendsurance for allowing me to publish the component as an open source project.

Contribute

diff --git a/build.gradle b/build.gradle index 13cd9b5..cedc4b8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ usePlugin('java') group="org.mbassy" -version="1.0.4.RC" +version="1.0.6.RC" dependencies { addMavenRepo() diff --git a/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.jar b/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.jar new file mode 100644 index 0000000000000000000000000000000000000000..fa1d364a0bc5f5d330d4f3ed59c6dd3c77154619 GIT binary patch literal 46135 zcmagF1C(UjvMyYj=dpnA=0{ayLEy7XIwx)if-hcjIS6bJ|c5(tRm@2&nd6A=e{ z6E|mPGkaGRGgne3dK25P1&K}3Q$dwN1MEygCJ#D^g`F0XDTN%1K=1mO*H_~xH5BRx zYK9RWF}519W;$7rU))i2-x7LGfveYCq+xDrA2~GA?p$Fji<|50&t&4t%DglCby=%@w1FwR~S_uI&Dxi25077ELK&qX9auP?_#?`%a!9cff>*LL7OyX6Tc{hV#Xrj4&n{drTfo$h)OebX25WGSBY^_J zMpb=ts^w~RiKd?VM+n=TOS+caASSv+TUFSb5N=tFeEY*uG%fHif`(tuN#l!be{M-X&iXD!`nJk=uLbl z>vRl`o5%Rrwcayux-3c~Zm0vJ*_^>H@%gO zI*>s>32(<*k_`-P73k;~*H3Z;htIgD98P*DGRBk5HkhHJFv(Xp+MZ`?ef$rY<*@0+ zYLw|O_1JaSwNCDsVSdYJ*uH*9-sF?*{bu~Z3=Id5P#NWvw|@;4=X_!@eRZDP=UDPa zP67G@pu%HQ(m2#ap0G2XmS7jlf*MMUGY-`^?nx6y(E%U#8^SF>p@e?c z=@;t!7(CmWaHrA-SW{yd@Sm92MF2o$PMopNDRYis{~N*Fxrw)TzovrQgYO|joPWa3 z&;lj%xaEv~K^Mnx4~wo@;;8@6grFwYdM+WBS0%DQVLwW~QYn`g3S3 zJTgB4i)L~yjkCpq4>Hi^-h&!^{#G`u6uE{YR8Z|jhxjLrf1E;TyIQJc2)2L z8e5`g!Bq7XX5wj*sapoqT@xf=D&cw8UK0FhDYc256*<=RCXLf8_@QE{2)HeYl!NlK z6EJeOSt{M#Awdb~G?OUhpZrS!JO68({#y?tMwZ#-Ab^0h5P^Wm{{QvxzqPPMLq|nL z1MQEya}sL5kYGnjG$yO@s2I|qM#1aU~6l*Bg|aFDrxy@wq1Y-;e}5)CV``N z!E$D+in(j)%m>S*{5swQN8AL)Zwm1ElR~J57Mvqei;!84>TeqG%#Y4;@b;bCiPY?W zNxmqBd*MsrQ{lIP@t!2DJro8qrO0ZZ{Rzz+)5HdbI>k`fcUBSb9q6`eWiUO6e3F{z z=CU+D7N6AF0x>hWT`SJilhgqr32X5l9_b2KIa_;rwOZG=jW0PqQ5k#sqsrWxAipq@ zk($cg6Rt*TJw^y@H_dDThJfCMr}*r-HyYqhvU?dxaj>Qj>``wQ%>S;!B3t6jnb$0i zd0bZW(9V_n-Sotf+MJwx(pC4$t{^Qp8Z|G?*#E4|{`_M|#|yWH3|tP@-UVQZhj^?g zpE0)fB(uO#t=ZaD1NPaOT7TS;oN3D%Y59M znu(=+y^t4q#x%F^ZQp&+RJ*>$kz5WO@s9qtIjy-Fb5IdJJ_u&8m=X<6*FsutSM%x$ z%XUjoWzU9K9e6ZRTnWZJ)48uM+aZQU_z@lGyHj7EP;-(9_zp3sPyLD@L>YW{q&}z? zEfgLt6)I?(lZ5sU8c6%=Jrz)kISWIybWLl}{5=;?1Ie0b%hW0c_{|XwXBiHX79Z{w4ztmPEM%*{S-0h%7PUb6ko^)Ds!4Fk>tr)a_+wA70+H1q|vxDa$5-w3p z4uZt4fWcbJqFbl})1!&W@M)72vkr))hhK>Xu2EIPmUm(Jf_$vu*GW7Hl#j&|4iKgV`B=TnO|x5_EDo=}g)*Lu zVo0IgxtuVrpUHV^9#uQp!wU?>&Bot^YV7#|DOi%RvfcSmc0=$r_JZwLn)1EbSca{) zSeirZUi-7zP{(n5a$X6snYB!oCU$_A&y`N=&WGT~>$>ei6-C%`MRQx^^74$kku*!+ zqM$pTbCw=(ehQs(-g5XI-*8J!sc_nz+|gM?4%_n}?l@55Ml#loQvX&cu#uVFoRA;& z-)Q+c!93~LF(IGPb;$@%zBEMKq1uP5tt3&kFbAV}ZaMwr!g_YfpUq&081M?WQSb*y z&x>$1P}gU)#S?(D++jvSXY8gR^%)yMUDf1}7k1(r5X=R0&4#JvQ1ph`vV1CrbLPQAlO?tYGv_ zx?G4vFJ1o+754e{o#-+yZ&H(e|H9AqpQb*GzJTp%&iYFuCP548!&C9<=~??mp=nA>37SPlNBeNjZaXr z4r^s#k0P3)d97y$70NT4>z3v1A>?(;VU`YO**bY5q}U7w+}KV6&rPw(z`uMQ+rs+P z10r(=CBQL8$ipauf&`CY4~BkVkOv_{Z&m6QxYoTXOrkx>a2Su#B)h$%fY9*)G|qo< zZswj1$YBU}kw&-6V-eF=xLe);=2gz*`H1WNC!J4lCl!C^BTe1S>o_@oy4`_GCcgai z!x1$hs{hJWihGv|FUlYEl6s?7|70!1;S{YlJM$d*BVM7-K+5)SpKY6>TimH!f586- zEJOVlSiTKTQ7QZKL)Tvy@&DByNhvy;nOd0`xtjgAGvax4SmB3&fFOY&aEG9Ghp-oi zSRM=x&XE>}P=e2|9&8l-$QkT*_4_QYUOYsxnW7YjnBJXy=pIa(FH{wWV2@2es{am< zi$E`R=8+{5hp65CJzsdJ+BS*z9r$-1uvi6E=2#?Ih4;6X7&zuvv9q@hsmP(B)VHzM zj*eHaHz>9kIFM8FMkZ!PW=5t!AQD&-P!>?wgc+DaCZ>8|rqH43KHyR=;D1l^FZg@! z9p7>C1%JiAq8yt4>oorkf5q%woxT3$eARUvaK+I647au9%Grdk>PlPL+g(fzxhrdz zi|Prmlv=d##*e#{UC0BZ`e1renrv(nz)pfIE-Gu}1^MN17T8copfR^Qwu z1<^q0qeon=#@bOiHhM2?H^9PgtkJgLiSOxI?RZOUC)B!HH_|3();Mstwp}J&4;J~R zFE5WzxPIFo-5?nHap8GY8|byaIVJ28lGYWbUA1P*6_%Q&vf)k@ z(MH`vs`!tCbgiIXbk+w{Slx_^9vMw!7=cY0LBv$6dav7GxSa8}@Sg6;)j;==OPAa>Y#6 z`&P1dH!+bEw`f0(z~y=O--qcnWjD5o2M+TiVSp29mFIG2@gpqXl7|dA`+9&!Q;rtTpf7?q6+*|1SLmh1xt;SW5 zsD|nWcLG`6l)&2zr0%wlMx=|shAQctb=p!p-I7d`@tk8YK;llnkG%9^hZ3Unx(GYQ zO$%!(o>#6F?~c{^&|s{eA3Z9^Gf*n;6gh=M0g-VSRYxkc3+pykgf~&4UpY<~7ELLI z917_L1EQCtp_ihMkTITzGn`OqSfM>4A91$kVYbGyGi^s_VN=U$(vpf=JBm_;vwDC5 z5=6oWM54@*WNnPz<8eE2C%(D)!5yU|YI#72?B9dvS>UF)vun z(Sl7kgjh^X$in>O35gr$y80S*cL=oGGZ2%aTWLwviC1AXal5~4zxcx|R+oGyA^+3| z-{7YrqN945G8_tB1#I_DF2k-En?WDyFY(Y*a@|rbL;2SOgTLDgvM|-(%3qmg z%gScvwq_=OMcQhvR<G z@AZ8_6LhI}=0VcN;(aD#&>^r6JU5$<)g9C!1y^~t4GB$$!n+O2O0yCp_1JBK8X}?~ z>(c0P)5E9U9w7cEEiiA9X&&+y1jka^mlYl+#N>XP8S6g%&C>vPez?()pKM_$i}vUjLInKq?%I$TxtDYZz)ucKFG?k4&! z`=L>_w>e|&;jLEYQ7%Upja6;Ae7Qpq4#6dN!s_~B;-(X=b{KdV%u_waN(H))JG7Ij z8ge>T%fBo+m1lCu!{(dhSPwE<_t5Olx)Ntr;J`6!c-tm(04w0f=TohruPVpIAzW)P zLx)Fmk`vzf^v5(kqG`Vh^>mSEWq4>Gu1dEzr$q<+C`Q_xV3TjMT%F zh)DD7$LmBCa{Z>MPpZ zh|}Jy%uHTwFgSZb_McoXIiP4CIxabY-!vYuoPWUHOM95?N8c_!klUQURrxJDLWH+` zi?O|Y>(cQX+`r;LIYMsD2B-ZS@~Ko6`p`-JoMh;&I+{PbHg z{D8qOoU29^-iHr#EXyT;R*D<6A&kd-td{i!G0*N6HNvmP;e56+pl!u-rg-r}Is>C0 z4V_sOxC0w&r~5VS$864~%6+~awxbd29uLT+^eA@1ML>vh$M+z9iN}1x+EgnpA|RNy>J^N3{@~VDe%(vFv$vuWw*A+63MtbH{r6I_%Qe z_nk%l119CY!P*xw6(G!!_NnADWUs#WMgle5b_&U9oIi?4bx)WE4H!ZaK3So(eDMem zsE6uyR|tI&_h)iE9TQ9hp*NUDl|0ZbjWc%m8jH(^28?m^vp|nOVP;C6cjZP__9jRP zW8S)p{?rRxbVZAd)qkEw3RW}J1OI?*E1)=J-TgU~?cdP)8qby&aV_GA?{oE?2e~M6 z_T6$LP(F3gZySG*Q~r)1|Be=bG$JqEbvDOOIvZk=6JHds9lwkHU;_BrJBU>Bd{WLO znmcj}R%AVb9P>^1N0%klI3eM)_!owmY5Lwn>Vb{QrUFy%DR7Qzk zZG~TD6}#L#T<(@LPXxy9B_ueofFp0YE5c3$b+Q|M(3K&8dwDKXYI}whO3mBu-axo;aXJGMD^;U_wK|DUbYNhUOIDWP zAz`?WE z;*=Z12QV@MGwQ(LGo^#Tu#@6#pM+M~coil--oXn9!*PpekXA4^+eD_~>#v&2*rli8 zb_$cTz-JTJ@{8hdf#SmKWVj;kkfzjf`&$~hn8m`%##WB$;!yS*DA0r^%w=`~%LwBX zkGRCLk;uD+2X9@mJ#V&P24?27C80)(+Wm&wRZ_AO8vm=H6B01CZcfG98xS#HS89S@ zou7Q#;U1qD+fMsEpX|STeQdHnpSG)BS8EW)*SW-6W#@}3aK=BXIWf&*8>z?VV8)%@ zBfE6?yL?cN4y1+iEWz9jVOPDh4{^1K-UBSPh)pF+N2+WE+FO2g#nMap)jri0eIoqc z_GyHhcQ)*j;`fw@>MrW7XL#MEe?u|anIjEIA-dXpXjt&wP2TG~asso%v%uP>g$@Ah z=9>jiHxQga^@CsOsUdY(R)!E3z zRnE-C#mK@;*zI2~xkMY*6K%!q&y2MO$t3~<5fUsE5|m~S5|XV6baYvtAt_^$V;Jhe zl63+`@mGKxD7oKkwa#kwQQYFRF=#(8D!1Rf3t=jZTP9_L*H-b@SDbcG@>``=*{n4> z;l_)f+t`=WwRyfw`R2#H)9=4eHa~vf#WMIq_j!9cFmd1T2_Wo81zo=@1y)%f!#}e#}XX8&h{8=&b2(0i;RC*CXoSaQBV9ikxW&6FA=v! z@8L#3zh?8lD3L1R4^-Exil|eGsxH&Ns|~bGaEqG96R~GHR2Q*F6*Rnx8>$|B>UCtc zA_Es&mZC|LA3_aQ^N9z;IrNZ^= zVmtrpSG0J4lt@!_3OT`P964lvg=8JM9v47f{rl54m|qd%6i&VI)|_ViK~i@}t>fx4 zW<1`(aMyymHuylEmF-00*52;Cp^8t}0ieU4;{r|s%RMBx)5>dpO`bX|yRgBsQmUTz zkiUa2catCrz3IY@N_Z9 z5^jlATIZPkrF1FE;pM3q{rFF04ho^y;^6Y~@jZ4<#&M^|!|If?^=F%nDHZAK(e~{p zy0AxpJB`@KTDjtQEHp^JVM;)kRdhz{Krab43d_3k-Y8RP9?4=q0gYSVFD0qO`p#?@ z6E7jl-?HDrr%-9PL~YAVdJd z*6J8N#pi2w*0unX8MU`@X2dv?8#K9fbq-&`Nn~v@LK7 z*xYBw|910RZg%b)uvnv*px_%-b|uAueHK%gdjKcW+h94%BxQ9HY2?WL6_@|ro~*~H zwaKdnU!j2*91CYE^Xdn28GHP~XL5r8USxvg(Rzyw_*4N^=XdDSBaVc;WE=Pn5k1+b zk>rZY+duy6B8>GFJw~>68#}WJplMp}saFTFawhTej?+s*uN(VkqJ!($YJBo*nz752 zS>$mAGlM|-MtW1!o)(;I6A)5#-7|xJoVJCq;Ul(2%o!1ZZ)&kNIhVIVKm8hfQ07uv z*b<@6e_8HBdCQ8J!LHAns}+W zOEyvQy!QFr^8f6#LMxDdsu}alZ{gTpC?a+jpI#kodf{bYdF>%{a+;T{(3NN!qkn8) zFoo<44_i*@x-n9D+D8}BmbFUtE4@hXg6A@n&TeiZ!9qlqJxd6=NS5t@tjoHCogYJC z%~fT;t%}+r@?AyJe%iVtP`MhY>P|FWGsPzkFVRxu`@kU9h?2Fk3l+jhanE(xSR6!B`wr`4Kz<$)8GwPI%A2Xg1 zjJ^QHts=i~hPu;7L;fcV5nENrDT9A}DZm;2pi>UTHBH=Ndco9?ffR$`aEf*mfe5hh&<);(ZpvF^! z(By9^%9Q4oyt5CXA$RzdL)a5z+1L^Ajjh~UZfoZPMozI6*WnnXj91qMHBla)m{zx< zo4%Lt%WBWgsi{`PKEKiJr3@O(0_+k1N;wH>o0G)<^V;2(M$-f!>!y5^8pPY>5ym2kr|s%uc#CJI4TG9E*6?_5o?} zyMUxiLa2CgPe#^1QS+UOdZq7g!?xqc^lR$vsxe3zXZ@hd$QBZih_ePwtlf9aXxJZe zJ%ha{q%QQ@B?qK|*8P_~ahXv?MXBld1FpMUIzb3{p}PcVMWQWXKs&bJkJ)~dD_19N zkVbJ`cf%K=EGE$=>{*#k3?oV_FO(y;ek=$6acjPJKI|tDH$eH&asD6-8bK;c12?J$ z!a^q(j{`S(;WK=chkd7b;&WbT_X{&KeO?)BGuTUPn2h(l<`vHu_ZdaoWG77NI?_C^ zd=6OmGa`$wYm69<+@r~`42E-D&J2P{GdfHmGqEX_RL9>@Wa@F$%7-BqjackQ=vfU3 zajbge7C#VvErbfy7JFLv)_~7*BAnKta!TPtzS`Z{)#c8&8iHP0evaHE;@0=>#tlXD z2X)G5ZhAo<<+2VZ8PVF+-0Sx>{$el`-x5>xbX3>^r~OQj0Ep|WDf_f6${5~sgv{ts zir``tfSeI)z%eD3)`jZ(V9~-9UP}U5ed6g)v5++{c=n2y?14d zJf{agv&sxT!*}T{ad2MMUz@Pc>B^7{b1;=7n8m%giu|caJ`jfA{KdU+`D#=x_v3gN zF@FH!XDzCfqT}~hTCTa7F)sQXc8Q`4hPM#!yTeoLhe$kA$gw8}CBuuFd(N4rsov>@ zjrQCoIc{EfC^10G>P%9Db~cFx4vdthdhF7Jr%Y!UQy<51=vZZ#=oHTj_P1TeC0mLx ztZTYrR;qY#yA7~e{DN3qY#6@R{=|IH8^kedMG05s8=yV?_|+k!xlw_!0u5;BRrBkO zcBhBK;C63aOmDphK!k*!==Rg)E(w`nwdwcS!HfMQmTqsL*F2nMrQdx5>>D=7jSxQY zxMEZ?$dwfkDdNL{MFZwIS)JW152ffhMFLcU(yX9@Ad;`YzY6R1u3)zJ)bjN@Ssp@) zWXw53%jLEVtp4uj>a5TD_MGPaqVV`mr@P?LpI^O<(5vtRQKrOyL+I`sK6G&AOi)7y zSR+jEs&v05TTf$R7dw-z`+UngHrzAwE8@ZtQ|X(CY^OzreZIN#2eO`D(AquD{Rtt~ zwFcIu17orOq@gz?;utY6ExM;D^(|+I05uUW`MIMu|q_qtirmE zt0G3CGaLNefT~}F(fh^=zHmRZbj=gGa_h{lN5?OcbYWlFu!fiNbo^m{t*H3qMo4t! zz%uM^@I!ssUq8x%)tMQ8GVqM52|9civ(4le%C!BHe@Ky5Lv2Gi`I8FZBgi(40yIgd`-7#4An&Q2?5ljM2?$k}gjb(Q7qT3d;GVJRJHbnUzp}uh`=xn+Z(mlNvAG()w|X>M(?VPVo+}{qNwg@Xw4lopp&d>B;E4@ytHRoWLrTA?R$Pl=<)PPy$e+hb3#`TK`Lr-Ln`2)5YleXdHWo}40ge9|YV^s*0KC2XH?cEU@Xn3{LK?CD( zc zkb9+wbIHNye+P`0LzZuZ#2}^iK|0nj#3q)XiIm$lI>55-dz}A7JflKc2`(Rszwe)&%`%k25A;gs;Y(>RPDUQHMTp z(!C@8q(2~|7@1CPhdF!0o2X(|b%*v@3vFrsg${Xgo`aKO=ZNuZw0n}#C$cjj_8`3h zCO{035TSDToBw=nK8NKsc*MwtFQBhAR(yx$C5@bD3!jsp!#Q2*21>OSB zP&C=owXDP=z`l7~(R$2Q%)6@x8Ck9l6MBp$_ z3v-Tqblt2suIXConx^^l*($rrwsB|ln;zG5h53@m2JK=iaxTlOaxL}_u@Jp~GI?9r zr7LZrHeN@BJ5lrclwm7k^TrZ~9yt~EK=t6zB;?EmUDSomL-~|>)xdDs!<0j4z+vG9 zuE?5&N}gV*!`=o$hc_k{R~0IaOf75p=8lasxcr-6dQX`5CkeX7T2Zyt=QF-Oq&@>} zsh9Sx0Ms9We*`qR3)T8@@=jK)vzhi-zZg)WIC#YH7q5+Mb6R-lXm?6U$u4j;xx5}{XC~jDT-WUYu`7)mBMlKnXdW5UMZ4KL7C7F@4!R&` z6YDcZq+NMgi&%`RO)oK28dtiDylM)pumyx0m0PpgE+Oi|AX&57H$170bGqpQDxwb9 z(W>MDrOu0uDzmnQMWR4mhf^tV8#f-<>ab%#J+kWZSo3BlDV8y+a5|qznr>S)6>co1 zSC*Atg`E(eaj{mPlQ&p*x#*R0Ive+rcszLq?zA#33LgFJ-1pg-bTLQ%V>NMiW3i(4 zC^@}`JO)x2?etZ6r;~2B6gQLPkeX-05oykNi*h0=egh2^qs~7vFtU64jaYy7amb3f z*wQ)bZ9S!C8J*=@KJa`*NAeGG2hH>$SY-g4F-<-PnI#}_mktQn>3B?#FU?tDOO&j; zU)y{5$mKpD82i5mwYkprwA(I0>&gXK+?Y*~(piRx$Y2fRfMJuMG-6@gD)-ZLr`l`(L+wX1jldkm}JnB8hBB~L7g$$l7&6$nXwU_e=>}! zI-iU#_^?2EQzR4;#%8-l5|p4_&E>n#{vi;{dSE(~B&3+&;0EWGh!Xf2Riz<`zEb<` z_qXIg=p-&_!3=is8!iys5mGITLRQ&5ek5xp>{=%fDp?P)spcZ_4W%4i(R1K6b0&$T zH$eHZ9GoHJZ;t-Ih#1QqaU(I{gR@&!E%{nR}of}zXaYcYbCZSm>Q-VUAu z@$M6mhp>!i3%JmQB%Ray^Oe!f;Texf1^XEV^7n&T1`}kN!=4JN5w@`QUn*$*F5nY; zyf6E0H>4~Ni2vU1lPTUp8u&n)L|-NOVz_?5E@^u6f-F{0mKb(1eh0Ta-SjG3y>-pl?u9R&=82mKrwvCSZEep%GC#? zOuGGMeFAk*Ke$fSZ-^g*jf)$}Qld~|)fJmpy~?FdE#jN>An4!V*$X-jH_M(7h%}ykAA|`B z=*>BSyX0EfS7?>C+4L{1Lmxw(N`>!fFWe~A^pXG3WUr3Z3_!2k^y)?Ka_sFjR%EA2 z(7@o6>OAOx_tRO4>QtM}tF?0+$ds>UW=!_ig6L3{qvXs5VQ!}9>AZE8+Iiq?U6f{$ zWgGR)=~SXm9ZzF7XpN!UI8JMt#Mekwyfn0IYH%U3^Z~z0nAxkt<8JmWEZ|quldak% z0{4iT(B3F+Hc6v{c#N67FxeW%?D)W>iK0($vlVHEyX^dweH;k`gW+Z>+65_u5oe?} zQUA%enSLwy6OVw}QX)LY3mn$7_&cT_41|-O5Z)Iu{N%O+#3dK9#IUX;Ej~LHd@8cM zDG4O_<}u4`&VAKVSYke_j6R~ghU9mpBwD;!-5^q{*{&>k=ML=%cnQV)YH=ZUT+5G+ zr_VO-pHei%V)6>r8NC6%5m5^z>5~-G`NKf3Jab~L+3Hn?&vUCq1c4P}d@qZKM5F+} zmuKWv5yzF-9!Yi>fi?QZEvUp382UNLtPX6)OkPI|zEHEn67{=Z-C?4H2}%MaFX+IZ zajY8@#}b4{s3r#76;~pqjp3GwHw3^|C$aH`9<Vi)lP%mh?Gm#KV-F9{&gC?iMk?E%3~bTVD` zE8a9}lP{baBAo?E4V7Jjg|)Ii>g;wlW)C$YMs+#RqTo7%@djsMg;CT*b2j@_)!;e( z!?IVZQAx?bhl<(c1QA~}{I|w6eo>J`rWY-6zZ{JF7b~9iA2d$P-rdaB z!SSC}E_uU#K@bV>DJQ(yF0X@wg8-i8Xz4=9CLEDXY!8E;DBM9Qx*AV%Ds(RKeN`7+Y;g7UfVs%OvXBIadcvaiSvaOXl4=KXWF z$5qj%PjMZZZ@PXJGwE6`m?(amOC+Cm;$znBcMvLcjm2;hHxkpp!6sbYVaoenhL6`# z=C-U!29FX}Udvnf^kRdbMBGK&fXG$lWs@rhT(IIgPk}3i(qPH@2{qLw)I4qHybBm0 ziotk@EWUj6SRhDsCxWJt>W`ArhR)?Cekm@hbBdR;ZtA|bvviL#aSB{6tgufV{@E)p z_v4b(^MKUhonC=Ym%Gzv>jMOx>RbyWVzkkLY?LQ+%$8&f8%tF}{&&~O(lGQzy6XMb zFrM~_`p19;6V11-pK#~tJSMIQI^NneTqgF2v&9pNz-+h^2zFTH^Nd$aw;xN`pyM^gK>+5RFsbNs_*`!7gi>Fi+d z;N~Lv1!-)}oTcpD9lp3`U$lzgISjYC+vHh>1yG0cjhV7?-joC9l~#y=PD zQC;t6zL*XhL3gtafzw-6@v+@%P}Q`Zxi-6Alq3J#fO9)dj`=RE01PUL>)At^VjAp{ zoX@7ZUPibN{21r6j)Ftsv&Vce;tV{ed~?ji>aC@S(LSJrS53!_1pNYcz^7nv8!X zI$vgvLlVRJTe36n=%qh0I1twAZ8l3EWFQtQIYoAa7h4oeT;tNVhv`# zr^a9+?ZUHBIHe^+i)z|b`}5zLx9A6r%By@qcmxp#!bJ!(HJNb^vgv9t{A;t5ShvON z*P--a_S&7EBAhU5M}At3j=aNu;bK?VMY zM&xex4+@Z~ydgaxh{U%sT)J%CvVHwV+^QDRs}6=NT0mX?;Fnx`x$H_Zrv1?5t90`d z@up}{s&JnS-OR{?-eP(Z@9FjE7P=1u!-bJCRRm{&++21tE5l^eR)*{mLBy)%_*JgT zxx$JOdBgy?mFA63K>RuFV=3 z1Hd1h*;zVlMqBc`{3uurPHp!gU=4VUm+(p%JZ2VTku7-^i$%xSCtQt>BK<2h7oR$WJb~%#9Zz)mMTuXTzYL8>R z_qf&c0!Eo1v_FvN4-hn|^Y#`T9;^h14NcRv?149}HA+Ohcl{(Av zdcbLy8f16ofkX)Xme6{dIkvR!+(#AWvZ*u(`mPFn1-3dUxkb^z8-pUT!hjEhMsbR1 zi&Xkdlyfi5o(fDUFm9+2qx;3Q>_~4r%^w z>>_2=$xyzGUHg}3<@$$z`u}O{{^z_^iHerPmwWxg?sUM;L>kIcfJawKdbL*^QAS)` zshbEQ+58I^5!WKJjnn3kL^%Kh1$hKiAdoOD!;KhxF4(fZg~exb(tl_2;+fFk2k`3f z6ADH{vK68%63&USmldmzU4Xaj9JjkF0RGVf|GK zjH6oBeKYQh$kV`Qkb;T@vY$lp#rg^y@N#LuDdlm6aF^hr>a)`!O(AU?q5^~^QKljy zR9E3vhmmEmaA_!)|q@3NUs+^rBo>;0nB~~-cT`<*PO0MKSGG#O|iBGTL zkz6mvFu|RmV(^>tr%PdsLbeMFoI>!`9ZwX8Jo#=gLbyQ{nK2m6T!sm6)t0dB!d zgS7gLAxZ%@Xr>s=?C@D5v6FY?_KEBfhcLNT4C1a$*A)NWXbL)>LhkG{nvvi@Kznfi z95nq86XKr?iu$=a>epc+1Tb=d(2+4v4d>%B5z$~}3p!FXnzc4Pb%dDC#+a!r#DFX} zOQNNY&yNYv2k75AE7k{vt$H6@C#Nc|HTq9n*hxqr!$ZUtGyLzqCmSo70-qm89zYZg z7{S43Nal9lTqM)YuhMAhO&%Cs zC<1B7>}wW`5hfh{77X#!jM+;Fl@V9O21=t)wPW%jJ-35YJvQl9D{s**6JCC6uB$Iu zMaR{a=eVm&R++^<3*g+^YC&2d@}qWtTs;cN>B!qGEoHV20mT0q;HK@bUd}^sx-;qTcfYN1T`jy z#rHITBem?IU%DERpTWn)v2suEl^2-IaXsVs0`T!S4G?Gd41 z5&&2QVf|tJYUOz|G{?b31OI8;MTevPID9RpD@3^ye) zbvJD3y#;Uz7|v*_Bty@=Lmte%BcOm&mPG-PLZ`eEVivRjdMf>4CUx|p;3c)PWUOJF zAA6N%FZ61LX1wtOi_0^t=2q4#*#V0aIu=}VY2w;ZLZzcqHQK~WtSQe9S3{wM1nUPO z9e)sVi4$nVvk(zSB-J?J?VeB$*^~J#)6M`KT53H>r{E{kFqKok`rN$5wDt>p2K?{A zY-o<=uFJMB&9P!@<`*qEbu2b_Rx2xgbi`|TEXr)UEW_KxNUm}CnZ~@{?3SFl28iF7 z+|J2BbyWSI?b25s?2Twt#!MngKU-mEjE zNi!NA!hfK)-siuj3Fw6oejIId*m^x}fLkVkv4 zm$*Ndk8~vJ)Q(?DK`ct}Xjc*ZS}?V8F6q8Fw%y-a&OrBUIxJvMEMu2El@ zx_5L|F^QJ~#Y3hylDT}Rh!wG9N9(px77A8jHBRaXfXuQP2$53ygWkvwD&NU=PY7*d zfxX#yvv&0YUIxR%q#(qB8$iAH{eMu~{)(5*3W20qzks5_7wi20IHB^t>9BuDgT<y5^$A~o&zeQ{gIBO{d!$c5Ah=N8as-A5m#Ec0y%Gj}|_(vJ+R;fc{@V@~2 zhwT`prsqwS1kxs~SFUB|@h_D5$j{9D*a-k?kG@30VOj%1@5H+bwakE-8}30WMTRwk zcpmT!40p~*?Q$`iJV*=%2aiYRHBwAUJBxTm7;x-wT=2F}Ke;D-NJ$jfm1i!hi}cMa zgJ%4DgLRmBwA+okjeQ+M6VEd`L?JNlXvSc7s$;ljo)ks(`JHgC zWncOjyTzBtJHcvoips-VCZ=JvXanVyJc$eYPE7sfi;Ln}(UgBzPh%sy5JLE)I72w%A+RWhsE#H7~J^sZzq~op319sz9t0qn{HZ z(WIdz?lCJ7Z$pAr`%{AOEQ!D}KZX1Q)YL(QCw9f^5pd!E#$mP7*W5tz(tJzu@}oTv z0Y?WKX!CmmDtyFjqFytIJ*JUqEYOuK!01XA;%=@RmP5uU4$45{Gs8Wmk12ShT!&Bq zz>FIKCa*YigS_Ru{AGqn^Jhg#Io*RDf%5-+B$@uE&qM&T>JDS{W|BlJrChLj=nBQ4 z?E7Z66-#hu{S)Z*vGdRbJ&CFRp`Fwu^nxPE=vNAehFd&K%rUd?;3_%40a&;N+j+P)$+xoI|(-Y?uv z@D;7m{sVrO`UgB^7dM3fEQAzV^>NWiZ@(?LJVaXSgA$p51R43hif^LH(#R63;c^2+ zAU#Qr5Zvpktfu$Dm)FJ<2&_$%MMNz8{bB6_+z4yN{r9BSbc<=&>LIb4E&b-Q=~#G^ z=5_b2#90kgBAUHvGb*M;#KHC?S`z;9<~q$p3ZHYjy|Ey%y&l8ljx=CvxLfp3 zQXVfk|9^bFQ*`Is(goTb+qP}n{KvL!8{M&O8y(xWZ9D0tQH9l~j!_nn_ zbuJXmlAFn%h*dFIl^au35^lT&pcz>lp|;!E1of)d0Oq1Az-mjHI0}Nfe!Wb`&~kIG628JbOAQ^kHW|0O;5{zBAbQE=obLjX3lA1k36Pvwe@PvC%R$&moYSeN1)9Yh-to3N(i&=p| z6bJ+dDG;FKxhK!~U

Gqf_$}-dsuI!6%dx=55hqATUiB_=2cogQbZ1*Bhu@{DZFi zQ+#!dA9(Qnyi}CWOU3!mOO^b~;Q!rH9wk2t02V~4U}H2wdhp>5#Un!p???;v7gUhP zVR64&VzwBHlWe{tdbYxQ{lX9M#zaR#pAfipuRc{->_}NIG5fNvep~a)V6dO6|3tDh z(xyx{WZbnmN%6#VC<-w+&sy8qFiXteV2Z!<@PTKfMcV)NM0KD^q1~{Q>k0oXJctQn zHUrbOU4qd@A!TH%MC#??@pve*0v%U41V#I7d~%8Yw&ta2Hcw+-MpMvIC{+CNO`si^ z^D<5-DH9%F5kIG!Y zBSoGtMu9I5@{>1`lHRSwsTo7Y^f7~6eIIz;_rVT2gH+2PHmJMsq8sf0^H_f-0_c2) zHY7e-7vfJHAHx5Vg=6faZlJNb?3#NvJvt zZG?ow8N-yYC147rUx)?_QLMveNfUVPe|#gZ_80}09A{86y#anM08EdCpHY+fUR`AK zrd)b^O;x==JzTFSFPM8$-`#4H#YFmGCWdwF_lP329zk_c8tG$ZAS-ZeWQIWVC> z-A#wVJTq(}+zpHD4S)O*=pRWvBC%pj;1}OWb5SVOcIKF3;4XS>vcAS+-_q4whMj%h zVVF9~yR3JTewju(Swq|JhGXlYnYF|l%s5Ve>1gJ|Kdqxie}g*DJIA~Ko%07s>*~FmPCsJ|&wNu^tlgn? zNokTBWwn}SWw5HbY6-(N)59I_=6sGKU;~kJnS&y&YB-FXROJ(yFKNc><<$M%4W1_=+olSFmP@t`rWCd0NoD^K-~RmmkVW zwgp{FhO|Bl=s{sey74rAV1V?jYPynjf`r~|8FH$DBO4Bs_uO?ZFT>9uQM<9!1g!GC zgp@|^F;1$RXDn3<-A-Wuj%pu9( z9XXaNwU4Dg3c9KhcAd_LM+Z_1s_8lssw#mC~YK?EM5#0G2+*PgMo2ZJ#OFHZP<~WX5sth?8P1svMp5wF#7yWJ{-{ zkTPe9Lu0m=mH^eWb1tB@lZ9?fr}flgaq~pH4&6w|G4mx-I980X7X3_^WoPqV36|Lv zxo74_|6gVntfG$B5!rs{nfYfJdminG?!_t5Jj@D=dB5>vfi1XP!Y`DkA&GtvK3~dY z2@;*1KpL_nxhZQT9X_0J2MF!GKZNEO?toC)z{TsIfb?GiNf6C|bdKa|1Og3GEPPQ! zamCx-B};b5P|ZE&gpiuc_}ZgIQY|oNj^f4pqnp87+eK5bTi$3xiFN79#Jodfjp!RC zt6Z$&N5;6{qZU&eatlUfX%O9jbSxzi{S3SKpaz*pK2rRM)rg^cfW)*`yvy?UHZ5QP zEUz!_mtW!h2y|U}JyDNCpvzH@7YUP!*xvaVf%V)2M$Af<#7aMtrF~-XH5rT82t6dk zV%7){4=ISYE16Ux3Lk8M-?8Q%vG0@tS1kZ1&^fR@iUk41^4Nu3 zW_5vQh_(}9`|HeP9TwkLv&Xm<8!>FFgL4h31}XQM@Md*nyYBc|I~(^5sC1t2oU(H_ zUTf9Ua_R*!;%`LnRe5?UiW+eKh1VJg!Lx2wGrRvxApdBNMce4@M|_5khEKh;|8hg} zE{4{BI$ebw*Y~moH zO-$=bv{^*kSI3kr?q~1@+qx3e2MfVW-37kN!*G?0wmi$*o!qpz>~8PgY@bi5#^(oa ziRQme9RR=$JEVkA!I$$?T-(z}t=<>~6l?QLLyiV!C*O!{q zlIF5SW?Ml?FJ@y-M*)UUnyBQ~qTi|&E>o$2R(0lGqR=Rzgu{okja;-EDqU!nncItr z?Jdo>89#oe!dBOrc-pi}wmImD%dX#>H85jMHP>2KtC5?mTjPx|xm8vjRzfvM)dnh* z8b_KBm6}}q_+Dqg{$uAleUTOa-u0~bHqeQ&ev^Sq4t99&(of-fEt#4@sXj~9q}jIR zB4P|KWtM?w{9?-uXFYFjjufu4QQEHm@^N%dTWFe<9f5XGjn_@0j;Cmqi=$;UPNaAC zaFcMdQBm1ZJ)RMu2p#>+lSJwOD6+MG?@+PscTGbO!TF30RwZML94NaOx8hlE?Kknc ztgFp~6U2g}Ro{=&{+RRZc?NdcE;6OvQZ)>rVNC5M#vo)a**$Pv(abpqta&a|2ALER zZ>lri^7#WawEk<|Bc~f$H5Z;fHh69zA~}rKUhJx=x_kI09G>;Eo?7h_8-Ugcq2kPX zDmup-zYS6<#6!|nb#1emYX(q6EjvwKM^Y>wN0ITHvjwpS_VG`6PRVZysFb#Bm5Lj7 zoJ!+|(mE-(k(n_9Gpm_J*El|%gkHf%PKWRahfjQphe3REhl6~HjtW18Y0($?m@bV2 zZ&|YQx@jotgdChut|W2sG%9S5B$TMfG!v>Ee=)Fw&+ zedEA=*evxWV}TzA2PUx*2djUCJ#|p#a3;Rjbj3m)&FvR1dV`<{%SXhAcG}m;wOB005=cX;JfkmdSFzYvC!ez!=7W2 znYh?5wZna=1Q29g!CLBb9APsVr@D*!zEA{Gzgd+kGM74QlwElLS0Wgf*(-_tnFx}9 zmUAfoGokoLB1j_n|Lw$6m;?YbVq`Wno-Jvf!EcGioezS>Y%0zOL6l0U`o?|Y2Ugl)1G>S3?Z^ke3J+V$(>(>eF|l9hY;!SIlF^{kKxR`|0O zdxz=4;)oZRMKeXom&`nz22U7Sad;5nPU$VIRloK6Hz@*;92mrT)qV%QMN0yyAh9f;dF)9t4n07-1)@bgJ4QZ6mB%uM#EXFyOSqPH)yCam;|REj5J-&?Fwoy_-RJoc<4jg2gnuMCU|66Q4MCVh z(7?MJH*y-Or{#^;u&Ee+D2q_e{&<@7<2bUgt}e9&7Zo?_+Ipuy-{x1A3iKHrt zZNw_EfqD{=7fp=;t=qq=^ zbRMB6zxEdH2pQrv1)AA;%P+L-)PvNUQEJ4AzL6RW)%&YKq&clnDipi*WKw$<&367` z2X2*%Hby=vllcMqKl6CLrfitd&nI{Lnalszv-=Bv{UeT&C^`RIjwnjmO72e&pm6i% zd0hvTqNboIUQ;fLvWWE{k|I(()sRa3kleZntyEL}D4)dZFZ}rU&{^}xUolKum{`Dm zv9<|bjz?QwM-%72-k)Cf*}o63NDB)}4l}7LOP9Il69<$KRajyyQa(v5S6GZdc~=<- z?X(4F_Ml_k0F6^o!JNXm5ij3h#G4w6!Ufd!w<246sA6yt_G@p#YZkdCoE*=DUDzz~FGvSJ z#qe5|MAOitnL-5_R`ZS}cgvMvmbGtqAU9{oUTs-=Hsk|pH|1c?f}ZnSjFWgGngOOL zvHOm-;!o0>&9rvYP}+HGNvbW2t|AxVG+B8<388ElL)EJ;B`xBcvwqm&f^D?6q7DLp zFnlU4#qZeT3)fv7xD^pF>k*3wjbdUzX&4W?tELTX_RJx24ce!@@ zSSrA`?lZ6j80@jA210?E5FU}tFuJaCDSf}aWrK&H2BFErA*X(%W;;g9+n8&87C?GHj5#=E(DSg8#yP zo{D`0MDAwKB)4KM%8hd=bcge`uJJ>e>~hDO$%_bY7X@R64|!mkGLql2sDuj{fIu1fgd5O`rXDleo&&jtn1_T2DJw#`*+U(D{^Ox?H-lw9Z?7+um`eEDx3ZM0PVy{k$$fEr@$(X zu$#J*zjd6!#P3&2gX)QZo!qMmoIpNw14*#2)ni2zspRvZ;+(rg{l-;M=L>AYhO6Zg z!M^JXj=XEsQr{v{bcDdH&uaM%S(?3t~kDY{#7DRaE?f)_}*Ai<8G*x)QOKQCmL{igjsHFTB>W;8u$263N>~3oH>icu@=r zB3(vqSDs#uizK->t?Nw6X6uqNWMDV>@KMfZIOsxApwNPvmB{woF65cj;>4nGxZ@y? z>fyLcD(nT5B6G?4S?4;l9Yd0bZr%)N6PtLw6R&wCcgBfd3j3_o9d`)9RI7KCBf>N(BKDJg#B;m{Bg=?~?;3u4A%eUlO_M*jsIU%^A|b(FP<|hrW2}903pz9f7-&Z zXGsX5VP+_|(nKBw1hn@ZDF%b{at8@iZ?X^kDsO-}3CbV(1^9NdXA6HB`#YmISW2&v zIl`-axwKU6f+P`4UadSJRsMzs8MAlYsWHLOGP23Aq6!~p^9bgf!RY)|2)AmSR*0%(%X?RB*i z_;>rl@2{co>`w$vgB{=f zLm|RnMG@_#1j6#{#1@sUk_plD7NQi};!|XtsCn%&Fm>9ClC6cClyx#>D;NWrJrxXA zI!i6b>U2h_#bl>aEKHmg=tmA&SLL7D+cBEhloJR~+1pv<;9El(GGF5g z`^HvU0-p#F^hwjP^GaDNE>5MWI2lcC%t67J?L{;ndO5bySQXs?-kTI_ijir-Q0C|< z%eBjAdeEruReTrVT!Ik{7-5VTmfiP^z=!n$I04V_U*chz3xXe~jqPUIJIQeEta!VzC`Xnj50u_>A zG0s5H|6U%VMYv1%s`6G%Mk!J{O&RrO?#Y$~ZG%I}1W(7jXs+HPe)mZFLZ~E6qdTm% z!vfQ>*HUxu&eavsa+ zY6CTRr^VRZF7b$?C-I1+C;5oAE%Au9tuX0&4G;dP+lO=XgT$YUuyM%@%x}RzKFd?b z%m?z(Z>lb!K_A0b4lMXR-jEYhJUWuxdv%XSF>qM2=hDT=cf4`>h1aee7Z6sG^zg{+ zg@S50ByTLWOH02p@X<;EH#(S)OaOqBX`YkzmT*VpJ}y zNrfR317ohTNxoV0)VR6TGXT(3&mQ6xtx1l4*lyDI$U6tjbyPH5HhhzGF?ZxI-0Rg7 z?QJ$@Y1y(5_{<%@d+YrXB%qj)JQk$@6D|eWvMb8w%WR zjTNaF8>X&vVQM?OpqypAdstzsOIn3PXPhyUim)7P-U2#JsNm5w8SS4JEp3g89Q7^BfTNy4;A*`G^8IXBb1uLo6{JQ;y7DO8{;! zWT9l$DA`_ZSVIk`By$_ksr3qC-@dgfmT@xKhk(1+4I)VJ;eO1_^cC{&IpgoUZlUOX zFa0E~XT9Cuczb$_4vb)`_y=ZAF0g{htuXRBXR&@;bEAMUioW9$F}%ZYgG(vi2@HUZ zkOrVc5OM+hPl_yfWcE=cGl|V;ktKZ}H5^6#-=}wIG%yMpDc9iRe1WQWRQfo@Tb=OG zaRyMQC8@iv26&z<vL6iCU;Bkpe3l2o%&5r~z4t`02s!=IhEVu=c( z6$85ElxMpS6+CAb$#&myyXPN`5`UtS+#Y^E&1Xb<`uqs~(|G5=q4L;ng!L2(Ly zif%#EsZBqUk~@_}W&=#tNviQ9z7*?du z7lbd0D6OZ?0Lq1sp)+4kDZatDiEL0PN!z|7V+L!=D8X?RfKOlk<^~dfQuD?HHRkk4 zzjR}nO#4|olehcQiehJrs{$Vk)6424C9!({$M+lyPDO1_wV4Je|D)ZG3sG9Yi zRRM(}e_)YI4V0sUtxE+HbqiiYWtA3*tv2Xfnok(Pvh>?Os^Wik69^Xns)qhdVjn(% z`hOKV|9#RjCe8-N2F?bGCI-ePj(=yeQHqm)K-q^(`}tg(eo=nt4!G(s@xAXwz=w9yb~SlcD|PnpQ{4if8+Kj zHH_6bOb@*=(NwIt%ZnkhV#C{1thx`#Z^Q!9WrDps9eVU_&IA~=?+cNtLprNMw{L7k zl0D>Jc(`_IrMJ>%nL-?UzJQsDQzF`ldov&yYuq~Hv~|+)#%xN!Mds?csbS#R_isbs zg|_@IoRr^r^L3u6QGAGg6u%JD4zi`$67OS&e3u75AL0hgUvZ!W8(45f8#EcUd>EuW z)W4z)X*{%vgUGHr<}4+Ilf7dAa51TDgBd-%624#8d2gOs9A5kyCW_@_H#G9J%n;GJ zI5GGYp~f^?;aQ6wgDhwHB6TS4Nua`UQ?KAAFO+8RROjoNwwLQi*1y+q1$U%iQu;P6 zjY0KI&4=+>WEH0TgRFRU=CLVpE*Sdank)SVW`KRqHfxdg9XLXoMRz8Tu+JfxX# z-h8s%rx@NHb*Nmm11AaXHa{YPEicV$`u-1GGf$+W1qN);s5vXI*}-v zSbhp@D!CXsd)S-&rREi-B=tG&iox5Q<(dm)CxUnw)g+H0=&c|UHz1(MfY{%+Y2&2? z7D73-f%HU4qtj}z4;BEuzlOgH&nU*rj3S(e!M^R*{m^>c{;*y>t@mYOPXmfFvX~+G z8f~NR9IY@8axgG#lpz^*FcETn02i4VXcTnV=`LS@Cua&gb;H^^`nVbiGb6# zYnA}D*!gjEc@lo)BKk3iv<6L?&SYPav#-{LgXts|^g`0`iQ zA`?l=wDAtImdX*0vLXoQ?4Ou|2P3J>WHU}krIy2uYLGIg;nV6u=ah0GFxovtGYv`^ zE~~Wkf@Cvoc_!DB=^IfkER58#Yo*F>`9zF?7K@K*a3z{GGnRW*L7W@R4%>aeJjPK$ z;;GHC{XlN67n*2zMzej$Jgl`O4A&pr2qzrhRQ%$^?#mm`vhE1Q(J?!W9gTEX@+$5C zn?n$vR3T@529H;4Pm?FS+wLpws+O9)Y1&aT%+ew^Zl_xpTz^Dl_WY~UI%#UCl;1^L zjD!c=+l7#^3WKCmMkVrPP^QI zIW9>7a?jGRFP)8h4-=nlM8+`)++y1LKBpMLJzv2*Bp|#+9k))H$zt^J@_v4S+`sC- zyu21h8!QmiVW0?M_-UpK>O|LOeH!rc=ST5B4Eb-q_@B|p zCYtdR8li|B51TIluE12+TJ$r<#88lEe*N-g%ZaTat80hSo$Ld4r(pbwJxCdh=?-)2 zy@~%t6+<+X2nG_-iq`BB;><(wIw@(lP5z|<>3qMiHi*mQ*M>~moX3yBGUd#Dp(gEP zF>9)9r{6gA(Sg@_t?INHa}Wk?FR?cFhAEp|2EXlYGDxI-b6_9>zhqI6+#$F^zAXX6 zZ2cef`SULlU6S+}eg@gp&-ePj-2cD+H7625Yim2V&mVvFxhTj;0V$yHE>|2-`$S1WI67DYTeB;NY{}WQL`c%zm?+r!t>~KM@yo8?rD$YHZy;-f(Kg&#}Aw zB3qLnjD!u=1d|ABjm%UMeRjeuRHu50q)1y$4+%Q%aJcBcknmo4LRpnaGmTb8?M*(h zZwT1t{z|y6JmzIAJkqSA_DoCaFW_jlee>$adFt?NOhrBu-+mNS&~O)U%3dMJ5NIj)j4E_b4~`sO-v{A!8+)!kk?1G?6ofY{apm?z z`+5rEwKTA_GhrEVoBqJ<5LR~*22aX_w^mm{Mis2RKi*cwV?=%54qNCed zRGfkG0AN8bH&CSsvO;u~%kJwm;&4Vs^XorE-2b#~iQK=ufBl@Z8;1GcUiB}!=`WoA zci8?5udA*rA**40WEqbh)qMvNR6uCd75bv|Ly=wp6bco(6tq!yt&K!V9k#uty|$>b zb1vdjh}hKoX8`>~17AayzcBay7ulQR zZ{6EqGk%u^D}nQfEW0pzQ)3+HK?eZcz438xk0AQvr~DxXKW7k3a|KN@6XSwZ|EWQA z!_vM%49Csn?y@v$MnG(gGYZ) z4Vgn_;*7e6(adC5MdEa36H_>*K(hPH{>G*pT((MUOHZN3^HpWpX57h{iiMd_({)Q# zY!pjl`TMJ~Z5Fb7ntn39c7w|vTIcrVY}PrYWQ$af48JS`2&*9zTC*v0n8_|UunwRn4rbemWE8@*yl+Y6D{5f>@{;=Poesu#V{O6%izg|bnYLT zTpzz6jpnOft~H|9)^6fUu_PNSY>a)DF6l}zori-lfVgl;%m5{h>)!QuP@KOML&H^> z%L?LC#u&wA^l%i+L}dM~s>fHT_F*xzV0uNM@7c=M=|D7t>!vKH7(dr`Y^I??B?=@O zhv3iEGtSmw2@)4_pEx+1K)IaqJ2U;)ediQCKBnE=FXjp@w8ZUjy2vyS>PbREeC#*w zwup7Al^|-UCZiDlED8A@L!QV8&ESIkTu@2SbM11>3{>l0RZuy7?0el_EqARUZm6w6 zFQ~0y9O3ygVVMSntFL^@SBTFj-jU@@dq6=W$8_>@<$!H1>&_&n-wwf`y!AmlC^JLKwOSAAvV9)Ck@uol~%)K>1;alOh z&v!h2&i({k;sn}+p;dFfQD52wXI>>VaSwii`tL!Z@C5OKI4A#l$WQ1v9(lL;B@8Nk z)0QJk2!{!`LqH(gK`Nc;t^~9UkV2UV>%VtPi8fo~6Uc$C8tebKx9v)S1KPw~q1E64Kc z7Ll35v_^_OPATue8&Aa7=}Ww6yF?tZ6a+JhY3pxs_2z{2f;`7cs*lG^`?lM4a7H7; z9E!FZXI9>UTzKlHI8^L-SB$RYrm4onp!zR7pBc(N~py}!dJcDvF9t+B$uOsaD2(Ydj35-T=?I-Yg9%GlL?2 zR3L&S=|>o@KWnqLg-stROG}tuE5a!{mEtOCo~JVp5k|nH0+MD|_<;={tnm#2&?I0u zMmSHLNC8wYRP1-5`*s&Txse^#RaaVxO1h-(r4Du0C209v?p;IZ(3Xz;D_CB)aqa_qbpOYs2}-9)*Y<$@cc^1M?}3nFQ`Qy+OzcRs0@#Ot2r~SM+t&2` zO@EqB+deZaqW^Kw(tm9M{OhC@#$`X7tSb=huit$68Sr4VHwMbdks0H^V+p`JVkM&u z!7v-UEg65C48Y$J$MlLCF=G zddI4yRDA?ZBO|;b4yYv9BqlUvVKqs?90~OsE;D zU(j>&+s}IMI3VuH6%N)yaMT}F#iLli9F*l)yON79$MO5@kgyBM@v5$fTqc^xKO`Q|h5tA8W`lb|&I@p?se!y!G(ofzp<7N;+fcJIjBBMS);WH8F>~HIjja zhM_UEfZ;JrdsBVn2vVuI?jG1WleR;T9rUT)aZi1l}LhR zDsu7j_G&*0B{0aNPKu0z+WdOHE#1DN#NTjZJX5$2{i!9E&RpX(tS zmzp0&>A$@{B7e7HVC}#Q>JdV5pmE(tNj|8l*o~rStHJ1pQZ;f%wi_iZH=qr6*@)X~ zSHS9`4RJeg6&9B5ZwI<7u3l^UNs}0*IavWI;tJEQ&W1}TXNr%NgO$d+Ol4N5I+=xd zvQ-0U+P&Z`?$J*0WYcm1qye&{e>0mvNk30kq&221FsboWTNPWOz%Hkz71TfG&E`M# z)X^@Nt$m7XR-5$GnV?W$k)@l|w`SfT=u~mte*sb&O)mIR5DT>kD6%wDz2q{TrNXF^ z1kVeyj1%{&PqwMLRJkcmV7@T{?5$e%QrKZdlRmf@RO}6oonNLSsRTfpxYT!{wOBy1 zZTriO#e}Rl$h4c9kRF2sCroZyRZfvX?_8)`-yb+Q)ZKUDOYCTp{|?z)vkYD^a6Q)` zs1Hq&b{jlCUp%Bs238kbI?;YK1=S9$5jBoMJTFbaB#+-G;`p zHk+>#DrOQPv6XB-uvx#g*zIwCg7ytv*m~&gNY~xhIODN|iv~AXN+s#I;io*ZM&0(~ z?BK5t%Kg-6JUiy+KE9+UnBaw;xEBQ^BNW1&_=44D`G)tfZ-87A4bv5!^b?40G8?Ae zW`oRLLW6F*Z`fRl3a)!a2<^y!>0q-0J5g0+)b@zR%5< zO&~qPz=+bG=}?)Fp=pI5;1rAG2R9;Lv|>JAmIeo^E}Gw)czP$0Jo zZc7o+2Sd^vWoiel2y&G&mJ7T9TYCYZUflvNt4X4;0IVcDt3=n zE`5ipXRcI$u9@CN9Q+05_xW>=z3%XK3VYOMgklh(e9y`E8o3L;lXoEVjRabD$j@Y- zDRx~jxgg17_;qQ8C}-r^CpTW{Zyd>jLDcW;n#Sw!3)R{4p2`Q1941&tlXoui*#3B8 zlY;qF@A^Kn{ht9_LHv!_#Dk%$h_o_PGaqkY9=+|~ z!}buW-bxtG_SFT22cZfbjofQHNlVyXZXhX^1i&GO%Pj9l7<5m*M_F3^NX!5h|EYD@ zZh(ibbhHHv(Mo+$YjmoK3h;O2a15!4AJa%UOTgzHm98+bzmAKGjARBV{olJD( z_r7Z1*0(^&r?cJ}H}#{GbLeZVmtAzw!nj&_QtNjMBOwb%roLyn1u>pUb$|IcQG9TT z5x+ww$MZmUOh5%`8g2GKiJP9lG-4*|Ct7zJ38Rg~jva1r?}G_vd=l|yIv~j*OO--& zvWXm;V4yN!>qDl9@D$qePNW^7kL8PZhg2%{{>PBjA6i|@i(=z*eC`Vu>i?#W{tguX zqSgOmRn)`uP>wNugLPb!O$0!`E}Ltky7-~yD;RyB+d-8agcjOMGG4*ecXkV&nj&K1 zt+ZLY1I#I9u-U8);Weow2tY*DrZoNZ!T*_7e4I0*_pNrC-Kz#Ylz%hr(!Kk^XY1B0 zBt_Tj9qBvlV+?$Tz2XqJ4@K`Nll8z4N98>orY)W5kgIIR4s`gJE4~iBNIru704BbJ z0Do~~p3{CL9(-(~}G z)8WGj{yR=geDq{`R|cHfNY>NIyAhA^k@PP!-xGAbMLVX$vZ_6=Ss9<&;Ex>yheR>u zpubPMUt4nurCu2-4mt5riFXzo^U!}qO0?~tsjyLC#arG(Dr?W5#iPYj3u(|PLkzP- zL$}+q(G;$%{W=%f96YH$!J^szprI)OU*5Dtmsg>N&!o(4V1cxDL~^m>BaN$~RZzyb zU}+JcKx^In+x&M8ro|;m!HpoVC2}+t9$7i4EC|T3stAl{1T(RLrS^ zR)K5fCS{T<*9N79uQC!3Zd=}Z7Ls=FkT2wgjp;yd5%yZPh_yid^MW6RTA5ph0qa_| zW?O0r+UD;X*)vuYRI#GPP|I>~7FHpHhTT|Ga#E=kL>f<1H_MqVcXvK0iP+G5wsA4; zp}w`y%8X!n8wSz}jxHcw6UpcEwcNlW(HLWi%`y{K33XMRLl_NZW(hJEDT-N6`Moed z_mw%^Ko12qb6`kEBt)4G&#vDyq{??|mX90(!`JZ%kTGK?DYa@tEb|#Zq`%E!{&8p5RUiRE*2ST*oEJc*H3y1zsJ<9nNV3H}o?> zgHc)SdsFo^;-i4Lp4QUVmY-3BnwMFgE8H7wm`p_J;{KbZa?IU9&~;s_;sJt@5VKfg zDW}jfJw271xFx?(wKj>5I7fO}PnRyUL69ijt-XxxlQp6cY6qShgRewH~=T7)nRzI8)b;9j(Wv$V-Yxx+;p6@Jj{fILEQqEDhdR25a786E@JQ z%X-r(l}VJ2Kzs@F^l#>rD`DKAXZTbr{_7^M61_3k$i>R8Em*^ip|QOPYFA3Hkq%Wm z$B#k&dVAr4gY}$Jz13G%pmu!$PHdmgw!yNAb_-g9?i9uoqe zG1EGmim-*XkUd$5{EwTmO#4DXV3_qa{ZcI~zGje;=woT>i!?5ziXDSE6USNcXj4TDfjQ*Ma8V9dgOlZrO0MxBCVKQ zo9?QgYm!KydL(}*HTY)984LF=P?ue=6Al7~)HIW7-A6y2s&#e=1X{}2XcsA?lPB6*5%yf98L?S~n5L>m zf7Ylf4y4GMlE@_65#0>`9J^@(d2h@b-Nqc0^@Is#UNvFRY`+5cdwssCgqE3Xn3rL+ zA#Zi?DVGdYTouJD8b>UO2!z`_DND(8E54GCh?@O{UDQ(pmo6_dw@dHPUKXrY&Lf`9 zge#YquC#4`+5*D(mT|$rSfU8&?i42sYsn!r7iS6wGCRq@!LN zN~kH-g~;wMQEg6AT<89sR3w5lV(O1-piS$|VJz!f~7(o){Q#2kbl1$W^#WWztHS=87#BSSS7M(X7>*-)$J_I0jX9v6(sel<*_BB+`Z z&6*Wb>ETd6K6QlU+>ySD9Py6QZ?TyzMO)6I86RcEa+-qonxU(4?e0O8TBZjsutdcN zB-rypKY)gKQs?Z^0~4ULCmUdk1LE=4Yy0zd-!czKc|~>ks(6=pf)pWBrmj5rSbe`{ z=3r?;VVvH88=X(^Q>vT|8_||l2Gc-*f4Q<&U+Xy>rq(0j0c}Z-uD)2gmO60vN-GY zHHW%%60l%|`fA=|qaL-XE<8ri?k6@^y&q;8Nf?;W0t#=yi$}Ie+_G3ZK+uT0Z-?Hd zFqDwh0&Vv_#oLqfEU!qdX8`Y4(fQI2#_u>M$|tzpFXDIxKN3*~OVNEqG-AM4d++C~ zd?WYq=34FPA1;M{IKoO1j&o4#$42(YM-n6HI7HQr-Kd)2Bst(ec6GmEQSD@}hCEt9 zcx5Gi#2~~)T#`<*mS*<&kC*;R6I<*(DxM)hyHWGuqyKyl!Oh&y;PK?lTr|C3`VO>Y z*TqU@AcucNKcdYlh+C#9zo9G4zv7@NNYe&X+RB+j-H+y`_8CH!)JjIaBE5ANRxaoP z_A=IZe5X|&GhT}l)IG6O&N$M0>L@p~g|_wD+hlW{xhd4z76X$RWUFq-G( zUMzYrRW_8|#v}TcVK1HxY?78=Z|=(D1J;@c0Sb zlBh!9Cd{5WINIkFi0i~N1vD@PEleDgsbh{Se|LCaxhj7BpyGr&01K+T(lyqNj?+oD#LzC6_^J}*EzskYq07E(VWWlhbhy-<+5Fx zZ*S&k?9tG>tZ1J$VX-i1fM`#e<&)tUkml}t@xXF6&;q*9WtOP0H+jzxa>FEPX?C)<1xCy9ri-1s``8XkN=Pfpklfe|;|Da8 z)qwM0He4>X2+K6e#s)^e3?ZK?*KQ)XW)(+ePFA{1aLf=3(?`E>r9tl?P+ljyT~04UKTQo6rkb;|r70 zchb7&Qke15Vh=93I{$pgFg$@Dc2&B>Zk)hX`ZDIj{S8NmH}2A|g$KAYM5_DX2MKjV zI>g^VSz%c?VWC!YP$Y%h2+40^eQT&|#=p&53{Zfm&NnNBxNyPm=KOQD@*la??FbY- ztWQ+7^7;MOoXfva*}vsh|DyNedS!taQAF(!F+@a_+2~+~Kxd;8YFtd5BT0x%aW-pC zd&br0e#Tm)1?oBNf^=D~62xU(mjkr>rXi$y)j z)QiM7t&8nSCcCz4X!s4teafW0f!w5&0jlglDXiy|p_eM@acE(XExIeK2xry7PT>5h zY>MIj96_e$j~`F0qzY4wsz{%@{xbZWXu+!C7=YfzBNkwAMed`&MWGg5Y|Xc79cG*z zYxHJak7o{eS~KS)XIl&rbmng}(M%@y#d4yO(xV=yFWAsKB%IOh7rTxmlg(|9>xTP5 zP9VQqJBUwQ2BfmkQkMJ9>}7tnX5x~;E7J$(T01OW>Bt>S@T=er_WuCyAL?yt&36a! z^M$5=(r^Ez9sTd4{U6lZ-z`c>N}GS4?Z;+iMTN4e!w$Ny+@@x3|07sD22q@GE@U7$ zp7h09MpYrcl$ZF%$3aAx;a=t_%T&Fl_pH15*~|CT=52SNPm}p%NFNu$p)PJ!Rq=~xp~;&v10ru z$B4Wwocg9{jKuSmq8ZKMn1OeKU*QL_ZoqRi&W0i2;yl;Pt@2cL$0acOGj0VEB%IL% zQJZ1N({Sx1aOXX8MFi!YZrM5|u@8Xd!k@lE^>jyP91Z@uBsQQQF)#_UWh3Hfq)N&3;SzNa=3nuI=5}|zI$A{ z!6TMYn^}`JK^_U5+I=$-dOg8S&18_qGnoSrhf{GpOwZc#z6ApVQE}AtXwHgL^?*tJ z_ispCr|QP)S!HoBLlm@$uOzw{6_mCFny!0=3``YzH=*RNe(tpF^=^FIJ3x=AWTVeT9EAOOj5^YZBrkM6qh#zgG12 zz#0P!AD}^0Lj{(Z_z&Pp(d{Fi)RQHm{5p9^=bDV!$5@+l|EJllio;x=#!HFGUz97egl_ zM+^Hu6TS4G?MeUs@b+eR8x`nSQ+MCO_IcINHvL-XYqOZPmYu*`6d zY>@S~aM%M>6lDquDhf&pYWK0&6{MysY?xW^-BiLQXHT+zA+438_SsykwvZ01IMW~J z7#mziRS*|SCKLRKc)%D9$u=3b^LzhKW9I=+_4ogAC9=27QdaiP%!us0$=-W!8OdJB z&W>C{*+hzT6O}8YWQ!28E=tMxf9~&#dtKM}|NZp1-RtqV=lMS8e9q_GbKd8i*E`=X zg?Z9Dx& z!?xg-{F$Zj{aM;SulsXSF0P2J=n2!}mlyixCObWFySX;M`6Ph9OJ%>si6-M4c1xSx z_Ek0Y@Hi3toZf3X8oC+-Yj_{2JS`j@4dkL?2)4fC#b=)3x2}}w=J_JDi>@zRx%SZ} zo}__I2ybA+GaW~BhtK*WnJtUBsN+lKl)3%Sy`?H*&|V9J9y0=#`WULns5n}O*G2U) zbq{|o6sH!8NWJ)tvBZCoUZF4l{(Jogr6xJT=4X_#n4?&7Fbk6X5@sA75)`uZs*-nO zycgJuY;IY-k+_oa^1iHgt#iaLmlQERFAoxN2yL$GbjFj$xKZ!8`*xUq?wQl8$%3(6 z+)t`q8k=OQnk0iejA}6-YIXDzzWGr9R>9a9*T!Y~_cL=G_AaH{aZ0vKx##&j@d-ge z5uJ=y=jarau>^ke2_i~(!%^MFsm)Rjh>GcU7|kA}Yot9O1;}4G zqVwLlK+b(fNXnkIFOY(q{HDe&9a`JPQ9+Xrj;aqBm%G%V?fVb8c^LTj&_Ywc`3qt< zC@tPx1~a#aPtN9wxBS`O-Jn3jc(NcIM|Dx&ynL!CN?BBk#lGnYLn6Cq<#hJFA5Dc* zB?K3r33M;=xEpa!^ttPIqRoAp$&dOg%J*PkO$vNnX1lQ-6q;Fh+tV@c%C%J;4DhSC zdf75)BmEe~3$4=h?l>#xwx}N{TRxsOR3;Gb4|(U)EI0PCjYgF!O(=)H>vn$)q@nR0 z{j;FX@oXlp59DJM%b|Q}%%g_o{i8Fj4Hg$7zeBU@oi^?*$NTLq>Cp3P^*teK=Sfb; zx{>Ld+K-3d@z>CF+P8sgY*w%M_3|#~*u5qVHI90EY`>!Z`#%@e%Vj$g=c)4dl*RX) z>)asU2Xp8r@fy{^XI;F%nioR$S|Xk++KBxgH6y?$EK6}x12?=W(pmPoUJ`6;y1^{u zjwgfpme;}@$1tjfQM^zi9?v$n;QO?$i9xG#Trd^O5Cgt4W(L#%Op4CV!e<=!sCm_8 zcrfb6kQz9iiH$eGkIT(SL}#r$tW?R7nE=}B8uz@8wY;X+4l|$KUV=dB^Up|{aO;!)k~0G?w7x!a97qc>!V}cxY8%~(}nUk9&X-R z9v)WT8JzDQ{h^)(f6<=f}yK{3$L-M2im7EtM%JQoDq(+ z2;@sJ7dZPRhC}vap|dNENr$s*Ak8?)r1*XF4go=sbKlQLId83QibY7=WgBtEO0@qx z>X3V7T7V#R)GK1?(dueI07>#;(;qF0CD^LTHOXXsBAro~6+g9)uD0s{iipN}6< zY7y)0IVTF9XZjXy9sEEiZ7=IS2qa0pu%(qJy^C8vg8mC1OwAk{xzgDMj%Iy^*Nixoh1Q~pe4%KP<3qKfbaNetxj@{GnWhnLt6X2r%qE{gO!R*R< z>4bEs=#|&oETu2!x1)&hoAEGI&x1Z99{+z~};`9pCBV<-2O&6qZC96ou@Z>@HHHq7cP z86PZ%NWj75glifd4A<2xX~bTSkcZs1^rw%iBEPPi>?$jvy_R^_jq24yms=P7lzhg0 znm-WnR^TnHhwT}23y9a54vmi+o7pOS0v!A^iW1s3qh;jO_ltDZ5Kmr#75aqu3UXvy+}38<(aepC(7 zMWz;y$70Xz;%`=n3&l+pyJ9^fQk(XpWY5l6c*=KuaokiNw3~8&)QXa2Sl3cgSCBKL zZTPXfoqW|N2b*GNn!sRnKNTnkAmkK0ux!567Jv1A@Ob%PGrQL+Egeahy`XnWYe|OJ zN6M7M>H+u6q=!vU8P`*75`9G}Htse-D{oqw%xDqhkoPZ2fQ^P-4Y5th3==OGeF)Rh z|7NX@KbNUE&c9+s)Qa%owh+^J-Zp(C*B#y*Gt&EH4{ z^9N4rz?yi7c~eL`WY8Uhl)BPN3*6FF@>L#Rlnnj>D|LvpD%gd?VJNz63XPq5 z#XPotNd0z}5p~JhC7BXBH~MF(o>7IqAu(9d^)5oU2pNViowMC0#^^|47Tmc#?d+jm zwV2<-^QE$^j-mqZ`^CL9j>$HmYXc$NqlP;L`j`t-&Y4r+F1>%T2I6KU`&_ZPYp}$$ zBq2e~8!fc-J-26rFBp2~#?6SiE@~ptqN>ljwG95zd&SI?%np4)vi6sU3Zj&JF;@rt z!}W}BE}U1HHM^1$nmCVbu!oPC-}?G1w&kBJQkFl%q;Jf&r-U4Gr4rtHDCJ6%fGHpG zRcd7`j=bydvBo75nK?5_63H`30+Q{@Z|8s%-RTsNw;x<@lM#`%oFiMr4ZOGAY#B0k zjn41MEX7yH-)LU-W@|3Y`g7rWZpjUI2yGs1VQIc8Wl;}`iW;I*kn@8rS~5V?u}>}4P~N%#u4ki3<53y2rFy{2d7mM)$`%Fg~$rB#me;4>%Kq?9d{*rE}bkd6XlTh8OYjblC2!R+a9 zetOV30Wu7LI=OgGiHDU+)k>l#`?HZ7uSt?LeuCHtbS>f5rL-Ssmm3zY@w z$zFz@2_m!f)h2S>zN1I|F<0Dc&VQ+(t#ub|jXEq|WX!92c3R?6ll_zh1;M4PwW0RL z@T+(xPk%YJHwfFV{k^)ACco%)siT%iX~S|SqrFfEs=~vejwQlfH!tH!uci7W&%{va zWoI?Didi&6)UOa96$oyyVceE#a*XJlqU&sV+II$1Wo@%?mt94+sA{v%hB}PKWpxVI zVwIdk25Z?KwZDPf2w+=xl%c4M7F(nA-P>@Xyx>M{^#Hq@dcT=6 zpOq6Q3RGX_r0K{XdU<1v<6c*EeaBZGT8o_4P1;>tPSKeTTkB2UDJ5QMd2*zu_($*05fKm$kuMv+KyIZiv& z%LvG*dAT{;sJPkL9fe-f{s1%uGV#2Wly>AHfXPvnw;{^`GHO&|?wTNsSHUs~ju`kdpF~5+Voo)PA+Sff^)d)uC*o!qq z-U}aX`euI7Ph`cz+1;5>AiqLqmYcHNbStKCaG*2Af>om|$S6VD0h3;(%Zsx&S>axU z=3u_4(;p2RJY4ZSThZjOdsYsVW4i+BC7CfCsbnD+>`DJVy5FrGx?hb&N*-&xunFQF zd2wOEgB~)6*LexNQ;nA?-Bc>Qmzg4O=z>&cou z)lfnK{>a?{j!cHKOf7G8o!qvw7CSy%JrYe_5Kczla*+P9!>MSX+ zfIUc2t!^^}YS0ulGI;nokI|f1AKSsbhL1Buewh6A=Z}w_3ksFYmc?js?H(I7I=*XE zzzV!Z>msl)Wia?zaW?JqN~LxJ@d(>sv3o_D7y5??J-6^pZi{7v-UB9UV0J5K(+e2a z=mTndNG`2|4g=v__&GPNGd&yLlpOI&i{z?@HrJ%OyWxHCdC%WGk@Oih=CVDmpz63$&!$HY z)81e|c{e1lNbtk+SIu2@vN7_QjKy0f6dJf0pwak}IpOCFz25}ht91zaF3Lfs1Dl#a z!4Mb^{8{EprkfkJ*Z?(ljGb*ya~b!j zXDh_K&Z1pc{>NyRlb)B>)aVp4mtwZ4?J+KLY(^s$MGYbx9CO7 zm`(9id%G-M!tcM71^4SykJ^lz4!kd6f1%gc?E?{<83`2*TAKpr*?w*L(=e>I9l0Wn zPNSG}ugpB3wSR5sg$315jIhtICeC>VTjpK(EQIgdGgM<*t`%OG6>`<~uA8Ts7R>1Q_NqIjBN zbK8^UCQi_u8AY&RQE5*}>(um`=4tffiw&NvWZ(gia z{wjUP_uKj6jkMtl7^~#TlcT$jsF!}7#lo{`3F;d{SE@jtmctErAV1ttm8J?Y?4#50 zd*kUA4(Se$1}$|_=Rz3#hr!TY+k}Nl0fAJr$j77P!kq<`sXJM?4264|&?D#G{$mHy&mi$nTV`$e!QQcE){$ZGBZ=A#G#Y!MJs;>p2m`t-QSD0dafaw<;~& ze>>=`9Y(J3_>tMy;?B?BdAK1TCf9Pu6d!9_@WPIsU{EhHp}@^vIwHujU`yaLA3k)~ zf}Zt`2Qu6?_@p)kF2&(4#gJW|n^lt;t33WsWzE(WNM~s5*8|9z#^ql0sQEujyxP~E zx)zYVW)bR}_je{ZS&50d&Gbc7C20TXq!t>Q4bW!+gPfy_5cs4jt8;@>QB989#lqLd zmHTjbbn?qExd)^#yLsAixmW_S4*~y@j`3g9{VzF8HR6bXg{BTujLCK&@&N{A(FgkP zr(1>-+}&Kb{9T+obWGd^c*ufRtE$Ui3ig<1_mZvOm6E=NaW~9ACwGR~CvzydYTP&b z*JN|-Ku|gXu3hU>Qe23LoUubW0rG40p1?kb&UuSvmqs=>-ZvGn^9I|UnY+kr6n#f&)1_|UQ9AnX}-%w9Fs&WX_yev55rHF>%C{9kmEqj0^sPB$MHzHAujmrHVdT+C1RKQSy!|ukM)w{~ zo8gCCnSZb~PpnF{@k1MF8xHQN%c&#=7-Zacmx8W?;T830(?VzpF8cu=d+#j<6+9=qEZdtSo_#6Fl|b!bVlaFF{#?@Y&GQ z35Li*Y2(zp>wT0)vXTP(yF@O!t$^~M|5(0F)wA=-iwD$bg{u&|vtOtwvEt1e{x)2x zeg^%Ea}X!lpVG57^09cIs6n4?C2D9*;?lo(e+KjczJmp{%&y}y z&>d~WpP;HE`dm}E;zu8=@UWiO&lZXfNp6#Te%xH{zBZ|{O2_qF!$08fnIo6f8{XSY zB~)L#nG($HS+~BT&aD*uYn<LA5Y-vmzxb4MM174}YY^asoo&Y6Eq9VD~@LJI?n8i)XbR|NrP;_Ec;KO)#ZmzoC z4$cRs`Vpb9X+{cn1tWH#+wc$WNK-dZaczU31 z#5|%3Ukpdu0_%kc$fIZCpB>nzMpi{MfGS!m+`tewY6FMPn$yB1BU*qp3SXoUop1}X zKp&2(2>%+x?qt=Y3W5kXq5#B1L}lc#iJTe|5xE@5(f8mc_y7+FDii-&4pfghs@Q9A zGdV!aKa^%Hy}Uhv)whE~*T}I=k;{P`0tjxvM-sJxgGu*Iq`26KV3^MbIlKuRye^Fz zd|I#*L^7-iAqNbBlec9~A^*obBS+bQlj&qplm7uD$J{{7F{(fhaPFquG47#y!%^4= zL=vo*LJmCuCp}R(MnYD#J^7%Zk`RaE_*IYL5L_$>g5rp|gOw9m#~6;=)i{PjC?^V4 zV?->h(8%JwaIBfuF)VVSQ9V^KAhPf)9O$ic40zJPgeU`xoKIu{PB>OZ{}}e|Be8BBL!RbGJ$c_@Hwsx{5RSz&I)+8k8$>KIDqSo%C*S-S=V*xa&kxecT%po_ zg43`pQPYkeSwU5aFi{a?0U$Wn$Qm{G_`xOZV{$-V+^+Y$~eib4%Mwv-xDo0H$BZ0g8DU6+oa>Wb8eo=vaKUE(J-G<(mP3~=-2H*<;(wDyQ!uvpCG>KN1gIz=&X2k&*pza4`SF e|CZI`-Z>3r48XyGh9(C56C_1L`vFkU(Ebmy?Tp6& literal 0 HcmV?d00001 diff --git a/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.jar.md5 b/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.jar.md5 new file mode 100644 index 0000000..d99cfcc --- /dev/null +++ b/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.jar.md5 @@ -0,0 +1 @@ +a2ddea6754519b5b4c029f48431e0605 \ No newline at end of file diff --git a/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.jar.sha1 b/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.jar.sha1 new file mode 100644 index 0000000..1ce755e --- /dev/null +++ b/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.jar.sha1 @@ -0,0 +1 @@ +f2496df5cf69076115670219c0127968e3c7ad63 \ No newline at end of file diff --git a/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.pom b/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.pom new file mode 100644 index 0000000..946afa5 --- /dev/null +++ b/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.pom @@ -0,0 +1,58 @@ + + + 4.0.0 + org.mbassy + mbassador + 1.0.6.RC + jar + mbassador + Mbassador is a fast and flexible message bus system that follows the publish subscribe pattern + + + + UTF-8 + 1.6 + file://${project.basedir}/maven + + + + + + junit + junit + 4.10 + test + + + + + + + mbassador-github-repo + ${github.url} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${project.build.java.version} + ${project.build.java.version} + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + + + + diff --git a/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.pom.md5 b/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.pom.md5 new file mode 100644 index 0000000..ab78db3 --- /dev/null +++ b/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.pom.md5 @@ -0,0 +1 @@ +ebf2e22bfe53d858092befaa865d0bf4 \ No newline at end of file diff --git a/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.pom.sha1 b/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.pom.sha1 new file mode 100644 index 0000000..2d480c2 --- /dev/null +++ b/maven/org/mbassy/mbassador/1.0.6.RC/mbassador-1.0.6.RC.pom.sha1 @@ -0,0 +1 @@ +950ca7e831a9060060f943ca02d313f7c7f5ccbf \ No newline at end of file diff --git a/maven/org/mbassy/mbassador/maven-metadata.xml b/maven/org/mbassy/mbassador/maven-metadata.xml index 712c56f..b951c9a 100644 --- a/maven/org/mbassy/mbassador/maven-metadata.xml +++ b/maven/org/mbassy/mbassador/maven-metadata.xml @@ -11,7 +11,8 @@ 1.0.3.RC 1.0.4.RC 1.0.5.RC + 1.0.6.RC - 20121212135342 + 20121225153509 diff --git a/maven/org/mbassy/mbassador/maven-metadata.xml.md5 b/maven/org/mbassy/mbassador/maven-metadata.xml.md5 index 4ad73c9..53584d1 100644 --- a/maven/org/mbassy/mbassador/maven-metadata.xml.md5 +++ b/maven/org/mbassy/mbassador/maven-metadata.xml.md5 @@ -1 +1 @@ -e37de32d920e2182f2549b0500dc20d8 \ No newline at end of file +0cc21fcd52a994d6b22f9fe57f210143 \ No newline at end of file diff --git a/maven/org/mbassy/mbassador/maven-metadata.xml.sha1 b/maven/org/mbassy/mbassador/maven-metadata.xml.sha1 index 620a47f..ce8d7f7 100644 --- a/maven/org/mbassy/mbassador/maven-metadata.xml.sha1 +++ b/maven/org/mbassy/mbassador/maven-metadata.xml.sha1 @@ -1 +1 @@ -6b5568c98223ec0a53f7b7ede78e965ba7f0080f \ No newline at end of file +dc58ab03113b6edefe5f3bfdfc464e5c344b253a \ No newline at end of file diff --git a/pom.xml b/pom.xml index 12d88d5..946afa5 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.mbassy mbassador - 1.0.5.RC + 1.0.6.RC jar mbassador Mbassador is a fast and flexible message bus system that follows the publish subscribe pattern diff --git a/src/main/java/org/mbassy/AbstractMessageBus.java b/src/main/java/org/mbassy/AbstractMessageBus.java index 15c31bb..e9c72c6 100644 --- a/src/main/java/org/mbassy/AbstractMessageBus.java +++ b/src/main/java/org/mbassy/AbstractMessageBus.java @@ -5,7 +5,6 @@ import org.mbassy.dispatch.MessagingContext; import org.mbassy.listener.MessageHandlerMetadata; import org.mbassy.listener.MetadataReader; import org.mbassy.subscription.Subscription; -import org.mbassy.subscription.SubscriptionDeliveryRequest; import org.mbassy.subscription.SubscriptionFactory; import java.util.*; @@ -46,23 +45,19 @@ public abstract class AbstractMessageBus private final List dispatchers = new CopyOnWriteArrayList(); // all pending messages scheduled for asynchronous dispatch are queued here - private final LinkedBlockingQueue> pendingMessages; + private final BlockingQueue> pendingMessages; // this factory is used to create specialized subscriptions based on the given message handler configuration // it can be customized by implementing the getSubscriptionFactory() method private final SubscriptionFactory subscriptionFactory; - // indicates whether the shutdown method has been invoked - // -> if true, then dispatchers will have been shutdown - private final AtomicBoolean isShutDown = new AtomicBoolean(false); - public AbstractMessageBus(BusConfiguration configuration) { this.executor = configuration.getExecutor(); subscriptionFactory = configuration.getSubscriptionFactory(); this.metadataReader = configuration.getMetadataReader(); - pendingMessages = new LinkedBlockingQueue>(configuration.getMaximumNumberOfPendingMessages()); + pendingMessages = new LinkedBlockingQueue>(configuration.getMaximumNumberOfPendingMessages()); initDispatcherThreads(configuration.getNumberOfMessageDispatchers()); addErrorHandler(new IPublicationErrorHandler.ConsoleLogger()); } @@ -156,8 +151,25 @@ public abstract class AbstractMessageBus errorHandlers.add(handler); } - protected void addAsynchronousDeliveryRequest(SubscriptionDeliveryRequest request) { - pendingMessages.offer(request); + // this method enqueues a message delivery request + protected MessagePublication addAsynchronousDeliveryRequest(MessagePublication request){ + try { + pendingMessages.put(request); + return request.markScheduled(); + } catch (InterruptedException e) { + return request.setError(); + } + } + + // this method enqueues a message delivery request + protected MessagePublication addAsynchronousDeliveryRequest(MessagePublication request, long timeout, TimeUnit unit){ + try { + return pendingMessages.offer(request, timeout, unit) + ? request.markScheduled() + : request.setError(); + } catch (InterruptedException e) { + return request.setError(); + } } // obtain the set of subscriptions for the given message type @@ -169,8 +181,11 @@ public abstract class AbstractMessageBus } // TODO: get superclasses is eligible for caching for (Class eventSuperType : ReflectionUtils.getSuperclasses(messageType)) { - if (subscriptionsPerMessage.get(eventSuperType) != null) { - subscriptions.addAll(subscriptionsPerMessage.get(eventSuperType)); + Collection subs = subscriptionsPerMessage.get(eventSuperType); + if (subs != null) { + for(Subscription sub : subs){ + if(sub.handlesMessageType(messageType))subscriptions.add(sub); + } } } return subscriptions; @@ -209,7 +224,6 @@ public abstract class AbstractMessageBus dispatcher.interrupt(); } executor.shutdown(); - isShutDown.set(true); } public boolean hasPendingMessages(){ diff --git a/src/main/java/org/mbassy/BusConfiguration.java b/src/main/java/org/mbassy/BusConfiguration.java index ebe2311..8415fba 100644 --- a/src/main/java/org/mbassy/BusConfiguration.java +++ b/src/main/java/org/mbassy/BusConfiguration.java @@ -42,6 +42,7 @@ public class BusConfiguration { this.subscriptionFactory = new SubscriptionFactory(); this.executor = new ThreadPoolExecutor(10, 10, 1, TimeUnit.MINUTES, new LinkedBlockingQueue(), DaemonThreadFactory); this.metadataReader = new MetadataReader(); + } public MetadataReader getMetadataReader() { diff --git a/src/main/java/org/mbassy/IMessageBus.java b/src/main/java/org/mbassy/IMessageBus.java index d7f4c62..802e0cd 100644 --- a/src/main/java/org/mbassy/IMessageBus.java +++ b/src/main/java/org/mbassy/IMessageBus.java @@ -2,6 +2,7 @@ package org.mbassy; import java.util.Collection; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; /** * @@ -118,7 +119,7 @@ public interface IMessageBus { * Subclasses may extend this interface and add functionality, e.g. different dispatch schemes. * */ - public static interface IPostCommand{ + public static interface IPostCommand{ /** * Execute the message publication immediately. This call blocks until every matching message handler @@ -127,10 +128,28 @@ public interface IMessageBus { public void now(); /** - * Execute the message publication asynchronously. This call return immediately and all matching message handlers - * will be invoked in another thread. + * Execute the message publication asynchronously. The behaviour of this method depends on the + * configured queuing strategy: + * + * If an unbound queuing strategy is used the call returns immediately. + * If a bounded queue is used the call might block until the message can be placed in the queue. + * + * @return A message publication that can be used to access information about the state of */ - public void asynchronously(); + public MessagePublication asynchronously(); + + + /** + * Execute the message publication asynchronously. The behaviour of this method depends on the + * configured queuing strategy: + * + * If an unbound queuing strategy is used the call returns immediately. + * If a bounded queue is used the call will block until the message can be placed in the queue + * or the timeout r + * + * @return A message publication that wraps up the publication request + */ + public MessagePublication asynchronously(long timeout, TimeUnit unit); } diff --git a/src/main/java/org/mbassy/MBassador.java b/src/main/java/org/mbassy/MBassador.java index c0f3396..4a9f3ef 100644 --- a/src/main/java/org/mbassy/MBassador.java +++ b/src/main/java/org/mbassy/MBassador.java @@ -1,20 +1,26 @@ package org.mbassy; -import java.util.Collection; - import org.mbassy.subscription.Subscription; -import org.mbassy.subscription.SubscriptionDeliveryRequest; + +import java.util.Collection; +import java.util.concurrent.TimeUnit; -public class MBassador extends AbstractMessageBus>{ +public class MBassador extends AbstractMessageBus> { - public MBassador(BusConfiguration configuration){ + public MBassador(BusConfiguration configuration) { super(configuration); } - public void publishAsync(T message){ - addAsynchronousDeliveryRequest(new SubscriptionDeliveryRequest(getSubscriptionsByMessageType(message.getClass()), message)); + public MessagePublication publishAsync(T message) { + return addAsynchronousDeliveryRequest(MessagePublication.Create( + getSubscriptionsByMessageType(message.getClass()), message)); + } + + public MessagePublication publishAsync(T message, long timeout, TimeUnit unit) { + return addAsynchronousDeliveryRequest(MessagePublication.Create( + getSubscriptionsByMessageType(message.getClass()), message), timeout, unit); } @@ -24,23 +30,23 @@ public class MBassador extends AbstractMessageBus> * * @param message */ - public void publish(T message){ - try { - final Collection subscriptions = getSubscriptionsByMessageType(message.getClass()); - if(subscriptions == null){ + public void publish(T message) { + try { + final Collection subscriptions = getSubscriptionsByMessageType(message.getClass()); + if (subscriptions == null) { return; // TODO: Dead Event? } - for (Subscription subscription : subscriptions){ + for (Subscription subscription : subscriptions) { subscription.publish(message); } - } catch (Throwable e) { - handlePublicationError(new PublicationError() - .setMessage("Error during publication of message") - .setCause(e) - .setPublishedObject(message)); - } + } catch (Throwable e) { + handlePublicationError(new PublicationError() + .setMessage("Error during publication of message") + .setCause(e) + .setPublishedObject(message)); + } - } + } @Override diff --git a/src/main/java/org/mbassy/MessagePublication.java b/src/main/java/org/mbassy/MessagePublication.java new file mode 100644 index 0000000..cb32fb3 --- /dev/null +++ b/src/main/java/org/mbassy/MessagePublication.java @@ -0,0 +1,73 @@ +package org.mbassy; + +import org.mbassy.subscription.Subscription; + +import java.util.Collection; + +/** + * A message publication is created for each asynchronous message dispatch. It reflects the state + * of the corresponding message publication process, i.e. provides information whether the + * publication was successfully scheduled, is currently running etc. + * + * @author bennidi + * Date: 11/16/12 + */ +public class MessagePublication { + + public static MessagePublication Create(Collection subscriptions, T message){ + return new MessagePublication(subscriptions, message, State.Initial); + } + + private Collection subscriptions; + + private T message; + + private State state = State.Scheduled; + + private MessagePublication(Collection subscriptions, T message, State initialState) { + this.subscriptions = subscriptions; + this.message = message; + this.state = initialState; + } + + public boolean add(Subscription subscription) { + return subscriptions.add(subscription); + } + + protected void execute(){ + state = State.Running; + for(Subscription sub : subscriptions){ + sub.publish(message); + } + state = State.Finished; + } + + public boolean isFinished() { + return state.equals(State.Finished); + } + + public boolean isRunning() { + return state.equals(State.Running); + } + + public boolean isScheduled() { + return state.equals(State.Scheduled); + } + + public MessagePublication markScheduled(){ + if(!state.equals(State.Initial)) + return this; + state = State.Scheduled; + return this; + } + + public MessagePublication setError(){ + state = State.Error; + return this; + } + + private enum State{ + Initial,Scheduled,Running,Finished,Error; + } + +} diff --git a/src/main/java/org/mbassy/SyncAsyncPostCommand.java b/src/main/java/org/mbassy/SyncAsyncPostCommand.java index a750518..69cc803 100644 --- a/src/main/java/org/mbassy/SyncAsyncPostCommand.java +++ b/src/main/java/org/mbassy/SyncAsyncPostCommand.java @@ -1,5 +1,7 @@ package org.mbassy; +import java.util.concurrent.TimeUnit; + /** * This post command provides access to standard synchronous and asynchronous dispatch * @@ -22,7 +24,12 @@ public class SyncAsyncPostCommand implements IMessageBus.IPostCommand { } @Override - public void asynchronously() { - mBassador.publishAsync(message); + public MessagePublication asynchronously() { + return mBassador.publishAsync(message); + } + + @Override + public MessagePublication asynchronously(long timeout, TimeUnit unit) { + return mBassador.publishAsync(message, timeout, unit); } } diff --git a/src/main/java/org/mbassy/listener/Listener.java b/src/main/java/org/mbassy/listener/Listener.java index 09bc399..33514bc 100644 --- a/src/main/java/org/mbassy/listener/Listener.java +++ b/src/main/java/org/mbassy/listener/Listener.java @@ -21,4 +21,6 @@ public @interface Listener { int priority() default 0; + boolean handlesSubtypes() default true; + } diff --git a/src/main/java/org/mbassy/listener/MessageHandlerMetadata.java b/src/main/java/org/mbassy/listener/MessageHandlerMetadata.java index 22d9b13..023a523 100644 --- a/src/main/java/org/mbassy/listener/MessageHandlerMetadata.java +++ b/src/main/java/org/mbassy/listener/MessageHandlerMetadata.java @@ -24,6 +24,8 @@ public class MessageHandlerMetadata { private List> handledMessages = new LinkedList>(); + private boolean acceptsSubtypes = true; + public MessageHandlerMetadata(Method handler, IMessageFilter[] filter, Listener listenerConfig) { this.handler = handler; @@ -31,6 +33,7 @@ public class MessageHandlerMetadata { this.listenerConfig = listenerConfig; this.isAsynchronous = listenerConfig.dispatch().equals(Mode.Asynchronous); this.envelope = handler.getAnnotation(Enveloped.class); + this.acceptsSubtypes = listenerConfig.handlesSubtypes(); if(this.envelope != null){ for(Class messageType : envelope.messages()) handledMessages.add(messageType); @@ -69,4 +72,18 @@ public class MessageHandlerMetadata { public boolean isEnveloped() { return envelope != null; } + + public boolean handlesMessage(Class messageType){ + for(Class handledMessage : handledMessages){ + if(handledMessage.equals(messageType))return true; + if(handledMessage.isAssignableFrom(messageType) && acceptsSubtypes()) return true; + } + return false; + } + + public boolean acceptsSubtypes(){ + return acceptsSubtypes; + } + + } diff --git a/src/main/java/org/mbassy/listener/MessageListenerMetadata.java b/src/main/java/org/mbassy/listener/MessageListenerMetadata.java new file mode 100644 index 0000000..55691bd --- /dev/null +++ b/src/main/java/org/mbassy/listener/MessageListenerMetadata.java @@ -0,0 +1,53 @@ +package org.mbassy.listener; + +import org.mbassy.common.IPredicate; + +import java.util.LinkedList; +import java.util.List; + +/** + * Provides information about the message listeners of a specific class. Each message handler + * defined by the target class is represented as a single entity. + * + * + * @author bennidi + * Date: 12/16/12 + */ +public class MessageListenerMetadata { + + + public static final IPredicate ForMessage(final Class messageType){ + return new IPredicate() { + @Override + public boolean apply(MessageHandlerMetadata target) { + return target.handlesMessage(messageType); + } + }; + } + + private List handlers; + + private Class listenerDefinition; + + public MessageListenerMetadata(List handlers, Class listenerDefinition) { + this.handlers = handlers; + this.listenerDefinition = listenerDefinition; + } + + + public List getHandlers(IPredicate filter){ + List matching = new LinkedList(); + for(MessageHandlerMetadata handler : handlers){ + if(filter.apply(handler))matching.add(handler); + } + return matching; + } + + public boolean handles(Class messageType){ + return !getHandlers(ForMessage(messageType)).isEmpty(); + } + + public Class getListerDefinition(){ + return listenerDefinition; + } +} diff --git a/src/main/java/org/mbassy/listener/MetadataReader.java b/src/main/java/org/mbassy/listener/MetadataReader.java index 0ed668f..31edaa2 100644 --- a/src/main/java/org/mbassy/listener/MetadataReader.java +++ b/src/main/java/org/mbassy/listener/MetadataReader.java @@ -60,35 +60,28 @@ public class MetadataReader { // get all listeners defined by the given class (includes // listeners defined in super classes) public List getMessageHandlers(Class target) { + // get all handlers (this will include overridden handlers) List allMethods = ReflectionUtils.getMethods(AllMessageHandlers, target); - List handlers = new LinkedList(); - for(Method listener : allMethods){ - Method overriddenHandler = ReflectionUtils.getOverridingMethod(listener, target); - - if(overriddenHandler != null && isHandler(overriddenHandler)){ - handlers.add(overriddenHandler); - } - if(overriddenHandler == null){ - handlers.add(listener); + List handlers = new LinkedList(); + for(Method handler : allMethods){ + Method overriddenHandler = ReflectionUtils.getOverridingMethod(handler, target); + if(overriddenHandler == null && isValidMessageHandler(handler)){ + // add the handler only if it has not been overridden because + // either the override in the subclass deactivates the handler (by not specifying the @Listener) + // or the handler defined in the subclass is part of the list and will be processed itself + handlers.add(getHandlerMetadata(handler)); } } - handlers = ReflectionUtils.withoutOverridenSuperclassMethods(handlers); - List messageHandlers = new ArrayList(handlers.size()); - for(Method handler : handlers){ - if(isValidMessageHandler(handler)) - messageHandlers.add(getHandlerMetadata(handler)); - } - return messageHandlers; + return handlers; } - private static boolean isHandler(Method m){ - Annotation[] annotations = m.getDeclaredAnnotations(); - for(Annotation annotation : annotations){ - if(annotation.annotationType().equals(Listener.class))return true; - } - return false; + + public MessageListenerMetadata getMessageListener(Class target) { + return new MessageListenerMetadata(getMessageHandlers(target), target); } + + private boolean isValidMessageHandler(Method handler) { if (handler.getParameterTypes().length != 1) { // a messageHandler only defines one parameter (the message) diff --git a/src/main/java/org/mbassy/subscription/Subscription.java b/src/main/java/org/mbassy/subscription/Subscription.java index 84cd52a..f8bfdea 100644 --- a/src/main/java/org/mbassy/subscription/Subscription.java +++ b/src/main/java/org/mbassy/subscription/Subscription.java @@ -26,6 +26,11 @@ public class Subscription { } + public boolean handlesMessageType(Class messageType){ + return context.getHandlerMetadata().handlesMessage(messageType); + } + + public void publish(Object message){ dispatcher.dispatch(message, listeners); } diff --git a/src/main/java/org/mbassy/subscription/SubscriptionDeliveryRequest.java b/src/main/java/org/mbassy/subscription/SubscriptionDeliveryRequest.java deleted file mode 100644 index 6c93588..0000000 --- a/src/main/java/org/mbassy/subscription/SubscriptionDeliveryRequest.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.mbassy.subscription; - -import java.util.Collection; - -/** - * @author bennidi - * Date: 11/16/12 - */ -public class SubscriptionDeliveryRequest { - - private Collection subscriptions; - - private T message; - - public SubscriptionDeliveryRequest(Collection subscriptions, T message) { - this.subscriptions = subscriptions; - this.message = message; - } - - public boolean add(Subscription subscription) { - return subscriptions.add(subscription); - } - - public void execute(){ - for(Subscription sub : subscriptions) - sub.publish(message); - } -} diff --git a/src/test/java/org/mbassy/AllTests.java b/src/test/java/org/mbassy/AllTests.java index dbc6147..69ea305 100644 --- a/src/test/java/org/mbassy/AllTests.java +++ b/src/test/java/org/mbassy/AllTests.java @@ -13,7 +13,8 @@ import org.junit.runners.Suite; @Suite.SuiteClasses({ ConcurrentSetTest.class, MBassadorTest.class, - FilterTest.class + FilterTest.class, + MetadataReaderTest.class }) public class AllTests { } diff --git a/src/test/java/org/mbassy/MetadataReaderTest.java b/src/test/java/org/mbassy/MetadataReaderTest.java new file mode 100644 index 0000000..c0c1d85 --- /dev/null +++ b/src/test/java/org/mbassy/MetadataReaderTest.java @@ -0,0 +1,178 @@ +package org.mbassy; + +import org.junit.Test; +import org.mbassy.listener.Enveloped; +import org.mbassy.listener.Listener; +import org.mbassy.listener.MessageListenerMetadata; +import org.mbassy.listener.MetadataReader; +import org.mbassy.subscription.MessageEnvelope; + +import java.io.BufferedReader; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.mbassy.listener.MessageListenerMetadata.ForMessage; + +/** + * Todo: Add javadoc + * + * @author bennidi + * Date: 12/16/12 + */ +public class MetadataReaderTest extends UnitTest { + + private MetadataReader reader = new MetadataReader(); + + @Test + public void testListenerWithoutInheritance() { + MessageListenerMetadata listener = reader.getMessageListener(EventListener1.class); + ListenerValidator validator = new ListenerValidator() + .expectHandlers(2, String.class) + .expectHandlers(2, Object.class) + .expectHandlers(1, BufferedReader.class); + validator.check(listener); + } + + + @Test + public void testListenerWithInheritance() { + MessageListenerMetadata listener = reader.getMessageListener(EventListener2.class); + ListenerValidator validator = new ListenerValidator() + .expectHandlers(2, String.class) + .expectHandlers(2, Object.class) + .expectHandlers(1, BufferedReader.class); + validator.check(listener); + } + + @Test + public void testListenerWithInheritanceOverriding() { + MessageListenerMetadata listener = reader.getMessageListener(EventListener3.class); + + ListenerValidator validator = new ListenerValidator() + .expectHandlers(0, String.class) + .expectHandlers(2, Object.class) + .expectHandlers(0, BufferedReader.class); + validator.check(listener); + } + + @Test + public void testEnveloped() { + MessageListenerMetadata listener = reader.getMessageListener(EnvelopedListener.class); + ListenerValidator validator = new ListenerValidator() + .expectHandlers(1, String.class) + .expectHandlers(2, Integer.class) + .expectHandlers(2, Long.class) + .expectHandlers(1, Double.class) + .expectHandlers(1, Number.class) + .expectHandlers(0, List.class); + validator.check(listener); + } + + @Test + public void testEnvelopedSubclass() { + MessageListenerMetadata listener = reader.getMessageListener(EnvelopedListenerSubclass.class); + ListenerValidator validator = new ListenerValidator() + .expectHandlers(1, String.class) + .expectHandlers(2, Integer.class) + .expectHandlers(1, Long.class) + .expectHandlers(0, Double.class) + .expectHandlers(0, Number.class); + validator.check(listener); + } + + + private class ListenerValidator { + + private Map, Integer> handlers = new HashMap, Integer>(); + + public ListenerValidator expectHandlers(Integer count, Class messageType){ + handlers.put(messageType, count); + return this; + } + + public void check(MessageListenerMetadata listener){ + for(Map.Entry, Integer> expectedHandler: handlers.entrySet()){ + if(expectedHandler.getValue() > 0){ + assertTrue(listener.handles(expectedHandler.getKey())); + } + else{ + assertFalse(listener.handles(expectedHandler.getKey())); + } + assertEquals(expectedHandler.getValue(), listener.getHandlers(ForMessage(expectedHandler.getKey())).size()); + } + } + + } + + + + + // a simple event listener + public class EventListener1 { + + @Listener(handlesSubtypes = false) + public void handleObject(Object o) { + + } + + @Listener + public void handleAny(Object o) { + + } + + + @Listener + public void handleString(String s) { + + } + + } + + // the same handlers as its super class + public class EventListener2 extends EventListener1 {} + + public class EventListener3 extends EventListener2 { + + // narrow the handler + @Listener(handlesSubtypes = false) + public void handleAny(Object o) { + + } + + // remove this handler + public void handleString(String s) { + + } + + } + + public class EnvelopedListener{ + + + @Listener(handlesSubtypes = false) + @Enveloped(messages = {String.class, Integer.class, Long.class}) + public void handleEnveloped(MessageEnvelope o) { + + } + + @Listener + @Enveloped(messages = {Number.class}) + public void handleEnveloped2(MessageEnvelope o) { + + } + + } + + public class EnvelopedListenerSubclass extends EnvelopedListener{ + + // narrow to integer + @Listener + @Enveloped(messages = {Integer.class}) + public void handleEnveloped2(MessageEnvelope o) { + + } + + } + +}