From 09bb543f5f43a30f61a6f3bff981b3bd5476750e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20J=2E=20Garc=C3=ADa?= Date: Sat, 10 Aug 2019 18:35:36 -0300 Subject: [PATCH 1/4] A Classes and documentation for the Domain Model --- ANSWERS.md | 35 ++++++ DomainModel.png | Bin 0 -> 59901 bytes cars/.gitignore | 1 + .../com/mooveit/cars/domain/AbstractSpec.java | 102 ++++++++++++++++++ .../com/mooveit/cars/domain/BaseEntity.java | 28 +++++ .../java/com/mooveit/cars/domain/Brand.java | 57 ++++++++++ .../java/com/mooveit/cars/domain/Engine.java | 59 ++++++++++ .../com/mooveit/cars/domain/EngineType.java | 7 ++ .../com/mooveit/cars/domain/Ingestion.java | 87 +++++++++++++++ .../com/mooveit/cars/domain/Modification.java | 39 +++++++ .../mooveit/cars/domain/Specification.java | 88 +++++++++++++++ .../java/com/mooveit/cars/domain/Wheel.java | 48 +++++++++ 12 files changed, 551 insertions(+) create mode 100644 DomainModel.png create mode 100644 cars/src/main/java/com/mooveit/cars/domain/AbstractSpec.java create mode 100644 cars/src/main/java/com/mooveit/cars/domain/BaseEntity.java create mode 100644 cars/src/main/java/com/mooveit/cars/domain/Brand.java create mode 100644 cars/src/main/java/com/mooveit/cars/domain/Engine.java create mode 100644 cars/src/main/java/com/mooveit/cars/domain/EngineType.java create mode 100644 cars/src/main/java/com/mooveit/cars/domain/Ingestion.java create mode 100644 cars/src/main/java/com/mooveit/cars/domain/Modification.java create mode 100644 cars/src/main/java/com/mooveit/cars/domain/Specification.java create mode 100644 cars/src/main/java/com/mooveit/cars/domain/Wheel.java diff --git a/ANSWERS.md b/ANSWERS.md index 9cd3f0d..cda9811 100644 --- a/ANSWERS.md +++ b/ANSWERS.md @@ -2,6 +2,41 @@ ## A - The entities +Below a short explanation about the decision taken regarding domain model design. + +![Domain Model](DomainModel.png) + +### Brand + +The service will support model specifications from several brands/factories and each of these brands will have a +unique list of car specifications that they sell. + +Each `Brand` will contain a `name` and their list of `Specification`s. + +### Specification and Modification +Based on the car models structure and data provided into the sample file, I've identified a hierarchy of entities based +on commons attributes as `name`, `Engine` and `Wheel`. + +An `Specification`s represent the base setup for a set of `Modifications`s that define small variants from the +original spec. + +> *NOTE : Some decisions were taken with the goal of design a normalized Domain Model and use some advance technics as +> using hierarchies and JPA, but if the domain model is so simple as the sample data, maybe a unique `Specification` +>entity containing all the data is much simpler for maintenance.* + +### Engine + +Based on the provided sample data the same `Engine` information could be used on several `Specifications`, so I've +decided to create just one engine per _power:type_ tuple. This means that many `Specification` instances could be pointed to +the same `Engine` if `power` and `type` attributes are equals. + +Also, I've modeled `EngineType` as an _Enum_. The pros are that this simplifies the string typos and keeps integrity in the domain model. The cons are that every time that a new engine type appears, a new version of the service must be deployed. An improvement is to transform this _Enum_ in a _Value Object_ list stored into the DataBase. + +### Wheel + +I've taken similar decisions for `Wheel` entities. They are unique id _size:type_ tuple is equaled. +In this case, I keep the type as a `String` + ## B - Ingest the data ## C - Expose data with a RESTful API diff --git a/DomainModel.png b/DomainModel.png new file mode 100644 index 0000000000000000000000000000000000000000..c3652653e281f27adcb90d22bcba6e5e0837b024 GIT binary patch literal 59901 zcmeFZXIN8RwhY=hq)*8Nl=F0nZ@d{CYyUjCX$ZA%WnYU&Zhcm#`OK8eH`G z&yV4LSn=TCeChcAU;n>BbjS!_nisozaNIyL9GtALn9xTmrJ}`7zTB5_p70wQ@pN>d ztOTx+%HZI<#v6gph|7uiT?}P_cdkib2ru5c1kH9M)lj8-z^P+r2JrB(xZ;?wP3`Ghqhq%Q&~Oi;-5KY-#hU{$7l z9Hn>A36P|IaDnx68+88T(ll+)hk;p#U${9-fqJ~(@bsEsKf$;c6ugi`XT%sME|xt3 zJ^sa=VHxmnCYU7?7yaWGkuMs-5yg^UfXhL@2o3rH$wkn3*rFTBD&Qm!5<9r5-0Fpt+y*E9LuD=9_csLn&u2 zC60gSy;2C2A6*tZ5R)uA$rLnw4rSLkmt~k{?v1QY+CR{jVr{$(IY{bMOPV;{2KbVl*Xs%m6Y!Waj?|_bV{>{w@&R>0h{z3H+@$m&X80 z7@0$PP+ngt9^&gS^`rAf7xdckRJMDezi?_ePJJu$`xg)b6Hx2O9n)uPYK)FhvjjnM zz89pfi#+0gndi!@%k{cw(nj7|u@RfCCaC0D^uB4T;OgDmm;S{bcJkR}%bS*E^5czr zid9R1W?VXo@@)!iHg10vEjR;cCR_Vr0dnw=S6a-0tf$V%4ygNQN|Q(6sn*Q}P1i(M zCi9wwirolp4t|-8TLI7uZ72?o7~8x@*Ld|paU~C+^Uri%TijMvX^Q#K#qQO7ncQBHKSN}M`5~!J^&G)7taKoRc zzLr55AoCo9fZt2OAmIBS4`p8h6mG}cl{Ff2e)hMNYlY{-sOUkiyq`d`dgU>s1C4(GnH7{NXkt6QmR>-fRl@%+^XD z+masLq?jxY_!8f|+V1;Rl=D1Fc^#CBP~TkN4sGtNbACeu33v@q+?;)nL;E3A#~Jis9Z97D|HVmU}Y0Rv4Sfm(C(x!@n6eCsRT%)F2?h<3vi?-aNr26I`W;##fgttWlakbsTB_ zi=6E8yBzNa5G}p<|j@o3=+(tqpOw}ij6>`>bFO*IizX!d2#RgrW^)GCYsJaq@ z&z$S2A@KSq*Jm0xDE=?pt562@hpD()$_Y~?DBPWB{i4Pdlc}E@i+^(S+eW;6x6QuK|kibK){3y z3Cdb)O7u=6#tq2!Yp!4=zzJ1-5dHBhA43o;nuS4m%W2PCVGom`84qX25Gt983gY0n zQ@&iU7kAx9C#=Y9?4jNBz>vw6ln$3i75__+$)gaZO%)xwEC$ogw&cI;TqsM4#r3dmh6g%T+=Gvu*5tVQ*+TBu$Oc>MLhZ2T7nn&1MTIuapNCHBKj-6&>tZU$YG^ zkY&a#q~AbX*p>Tu5zrM~URua}rda}f@H6tJ@xUFeo8x@?Lpr*_jg!;L)@uvFh-wD< zQAweRcCT6Hd{^QL8m+4`%R%0}jQ5_e#L-Qd^DjAx>HOlzP&9@Y4$dn=MIdT6b#722 z`D6ID^V9ZHL-JKvGF!3ig}P?0K;NAw2DH?iL-4oD_?L~mwU_kezSV5@gGO|eex^?1 zv(43MNB8Ok3TQgE-a$^xCv_~{$ z0Xn*Y?YC+f2LYt_3EfF_Z&eY6$EBggoYSexzf_h zEgptOqjPibefMmgd)eR1gW6*wN+;TH&r%K*;&VW7eIsfSL|48!LG{AI*l=y}D_Yxl zb5?6MyFiqEciViyIHWgpb^X3PF6Fdy^7HkyA{%n6d0y||L^}n`GtR(LE^!&u7wk*E zPIYBOI4mOS!(2*}%@o-z?XW)czm0B3#L-Y=W)5V=G~AXd(`3Z(EbhiAk>0Ph5xtUP zJwTf=llW>U+M&U57U%Mx`^#tu%{KzsCynz#JCzO}L8AUqg#K49P&NXS7IwYnpJ2xV zcaqP?{Qfdl_jJ|Ze2nkaPW}0q?_b6$1y2x8SbP<9Xj9-1_B14ATwOrbC0V&w&2^jKZnl?`PJQ5EUI* zRI7h~z!Q)UH?w;p+}`eF%{DnXQH`&oTV)w=GR$7n-sve=q8 z{`R)$%0W*Piw$2V*ER+*D!04vy^eY_q5oA(j?lO2;E;ANFG~M8XAO~lgVS?FXzerO-x4lIBmK3`%m$Yxz8%WKh74N8+q!YZ2HbRL;ZziyG>m1DSPX? zi8bhGQ`K%rw93FkAF)J%5Ru#bISLU@_N}_kpU!TuFj0}6#oFa4Owmf6nlvDPj1{_P z#$e6&$8}d5DnQh~VcjJrQ^K;TJ9}Ct+uIsxKL$Ygs1*ghp+oiE!3dykHW>D5on*%G zFk%#OxjGX=J(4Ki%Ygzr%aVgx=*0rz0p-CejQ7bY%G!dUMf}0dDjuP5`=P-@()#6) zNgnd=y73B#&P`OlE1b=2>%)Jr>&Gr%)-Ul4i#jqc@%GFPn$~u->@Fg*Tfy-0mRh(l z>oFnub@c*;x0AB8srHp>0xxrO{JwL*x>A2;**21P3C!mJ6FuCA*-|@qkLUChvYN`= zA{9Ya?QW%#=JsWd3(dE3gyZ&(7bze63mYrVQZ-FKGC2L{&DZ+at(jvGj()cygG|6U z1cRCHlJ-#*p(Xag?-o}_syNMaFtPO!h2-jz|pV{KXPH%nYi}4Hr z<#IbXh1zR{+A;fCv0IN4==F+xCa6CLq9$;&z&(TBRfyed{;dHt`_AmL%)H>it5RSx zY@TGQ*21RQZC~4nuBr7i9j;lm{lUp;-;sfCc6nieI=>s&@~kK$_RU19#Nyp- zovg^^De{bw{u(QFWUT1@bj=U+u@H@f%|*DcA9$^OWSp&(=Vt{p08s+0xo$;wFK~IK zBjn`iW54Hdn~o*G#Tbd^NdymU=4vi53)qeKUQ_ApZSJYhBfRi04{mF+L4B15I1@Z2 z#ctkqxlg8_WH3k@1bE;fUG?m>!3%Ye_Ji~a{#g5W5n>0S{E<-HvDm5%SMIEl+Y-|A z@*;Z(0jZrCCYnkzUaT^zu|)SJD>&FuyoQyJmF}Ux)XRIE7PSVv<+D1d`oM+wx`Te< zCOFvyT%m7^fS3(WXEF6Ld3Fr>G~Wr3eGwdHQ&3r$`3d%U+7WM&%=v)~@}xyc(yq*s z!x~}&r&AvCL6DEtJPKS5J$O>%&%JxqW7T0~KWt@bHoi!?ZB#rjl5xk16-&op)yW{p zDj2-%*&NmO;t{X(o@D=`iJ?%!QI-B4=TR8gd}HbJaU@G9=Y90}7ob(2vCnTJNPo8N z=(ljqQtC;MFpI;8)09~T6GN`bRL}<|*lW!$Swxq3HgiC6Um@`K3lDu*({rrA5yp%8 zi9MPcYByvB1wVLeMdG-5Y%p z+CDLrA^cdqSX^1f3zXUO;`&yyV@i!q_uXG6A%1Y1+(@*<-NS4za^rR8%eW$wki`aF zy+_ImwRf%;L(Hhhhe80s<~+)>icyDhQ?8yQj!`A7PZ_-vnIh8oo}S%6;Js46G3)RRAg0;?p(hc~bKQn2@NWcJj1lYx zO$~`q48!G0-rMZ>j>~@SC%u7ITYvm+BhTa7$bEeC$bg9e$nL{eoU^q@mZwz9?L=7` zWn1a1cx+bC93wKF_>P&l2kI(N`FIyNlj}0d6|F|x{2ocA$M=xMGFG`K=I9-j6`@f( zea?5-e6RMm?@xcZ#!t-&N+yJRdvKYbH`tj`%~L5XcuK<3Ws?y-;xNO`vWdP{BT)v% z+#*=L5x-GPd+dcxNjZ&$vMeHMab1ZOboO*uSPFR>c zAOLzVxO@UwsMqzLkAXOq3jSV?eh%2Tv#rKR+nzhVB3v}a#8^k3ZTW~or6 z($rE&t@1X1o$-pZnjMT>fHwC7{8nA!Ri(17p=303WW}EN^fiWJ$15ZkkOBEiO^EPz z;ZX^FV|(BS_VxW99``Hzh=d>^FIcu_9C9H9cI(sCK)7VK>XAxqkopB z4R;<6p6sWnc2Y9tvwuwLQ+V`hSeJ%ychkU9|->?!FzN5at<5 z^#eD|cOa01FLJ8PEO!(l-UV0Hj+{fp?RL6cI)~A0Hl{A^|MYxhQ0r&5a_MC@uxe_O z#>bPl#Uld<365J_dC-M>bL@fi_$4{gR!oMN*cQ@i@w>})Q?EKCUt?1pu~9&I;%>yX z;hcxMJ5G)B9@m&VbjRwhFZ~AXj~|qGSF}l>d~yXjA=pDB-{{`J>lJ`paeHR+;nLZI z>7WksyRwNDPe2l1mF1XbYfWnp?+&;;dDMjHhjHtsAMFmkfZqB@bsj>+QZ36cHxL(Y z!WAEUsJlSrH|m8Xw;cc6d-KON?A#2()!cy^i@-6ZT(^$xJ8Ew;fn%h9AEwdx3cRg6 z?e)*Y2c$!E&9;fY!fD@pok`HOLENA3S(f^`Vdupc94HY%2jJD5-SURwrM#Qw@ge&I z+w7`y27}vUb%G#TNow=Fro}@H$wR$5vlDvS-tyo?wl8|0{)qIkyFRA|}@*peHtKY9s4bk%Lat!ENeeCMKJAeDq z^B|F4!%OFttN>h?JEBeu|vr~X6b-vlQ7MGO`^UwFWDE|lWilmk{aCZ{T(Bqh~P5ks`Z z$XRT8`=4Grm``aH`~8xFx@`%L_b|M%LyM_(LOCn0US;0)>p$QNq!zRNw%>!n730VR z?hl}%{Nh9I7xwjVA&~1Lc^a!S9FuAZa+xZJ_sT?V&l@EFE(8hqY!0w8ZoBabXd}It zaT<3LQgnvxC^YUTIF|q)qYax;58_MQPk|Z0|1>w5?ct&6ac`=t7Q{HB#XuI47a;Va zLhBVvXuKRhh#MXBAt8r@Lvj0ngYe^TwwK7;GLyTn zh_%6r^jpk{Tc7L}=cH11wN^x?ozdxY_9hqFR1~1>cngT>BQ3aK9Io)p{W5%ureLct?yQKt@B6UKiT9$IaB6xY$C>!#wLv(6 zb9F{UKL3YQ={Cvm`(>=^$nERxLAT@mLv`SgFBT7mOMvoGWVyg;Aq|OK=7J^SpH{{< z6{uT}i=f%wFn@Zlw^2pCJEc;b2dfGMLF}Q1G6zlDU(&I&@((kXo(&8b1^m3lz}nnM zl~v&LGIS4eDqN{yqAtd}xVMIp)ML2Ny+@6h8ojChtX8d!T-Ix^L>CQ(L!B^_bi^GQ z&9pT%MAd%o9149IKP^Hw>XEM|lO>@9vcqEX|e6Ri8 z{%W<1BL|NVNb6!!$CZ;?bGP5Eq3A$!ZpRvDp4H7lhh<7pc~p@3y}G1q;`V3wVWbWK z-s3GNxx7gc+X>)jB!H!48t{5Dt*|L0b>&94d6W*Q>Z2vLQNQ{jz+Q-oZz?phyH#;J z=^=xa#1+I>DybG>@gV+AC0Nfkit5Gt8@4A8(AIp16vj=lc}gd_tU*rJkd0SX<|i_< zt2FPFRAXpqsw#`GTfi^;%91(a>qmamgm$9i(Hxp3GZ*`?Qpy0K>7gRO6aZ`pUcmwZ zQrqA~2l?p7_ZU$H@YL4~`|YqVw*!`g9RJOwC=_hYgTNcip6 z>2KA+<0gsNRrzOK5|TR6I+e*r2_~~>GAA#C9LLNc;WplyTryc|(AuzX4sXFP7;Ji` zS6&IV$-aF<*2kcvPDHkXF2e(0CH<=pg4*_m-rD|A#<-;td=f7Ba#`GTUany{!4j++Q+B~eX9UUc~QauA;gekyuUbS20$ zpBHYf>0K}w=IZL0!)5}w8P0TsXR&mp^jEJ1;feLwpML&wyP55(N-v)fs2fQT=gm4N zefHvE3Es#8iFkA^epQNol2yEX>asXA0G54^3ah-}6_7Xwi5!SLwYFc=aZ^fLTWumNlgfXs`66QiG%B};(Pi2iL| zz)I|`K|*u>2kOmLZII8qWxN1zYYf|MpE(1xk^kZzA-*O}e;b`{?iXng5d-5~;7h^Fg}3rPv<8MZR^HSHnfAv@&g=Yu z!#-uo##x^YHcAC+xn6@I0mI)UMTx0ot^9{lBIH4q(V-ftbiw!s_r522)dAjtC&UMAuBbv|8k7_XOpre!aw^iy^{dW7$&?X_)JwVuRfaisZL z5Vkh%AOBWRxvGKRlqJT=TC0#O{OM#xv;`Z7?a$y80uj`ve4x&20e#z88oi$g4OOe2 zO0``x-Mf0N2mci{a0&GKx`Abq`XMny8MM}`Oo4DZGgM|Tazc@|@b%3O@H*VhUXxDg zp;NXWaf#e+eg`IbKOb&g7V+6oXczD(u8ldszLlH&Yq3gUNvNt0qFy89+gyE-wq!pW zQ}ic(lkm&`wt!<7)J7Z;*?v#8@NF3Y@}%!&^O9?bYF<+w>&>`=Tf0Jj=X!Yy4K$Mw zWhGD4!PVJ$|E@a8ouLaQ8S9)0v@Ot-(BCBkuDjo6ZRUytG-Ag@a^ckVcle)2yAp!^P%i@|?K z!~ctMC6W6-NbWzF`zSmJ7@#GZA^b1n<$sD3|05p%PmA*Z2SVxpLoBEHc96Qsivs^G zQ1~tD=JPHP+5dy_i|jFc=!f;=xWwWx2b^D%WtT{;Ab}|6+he)NGKgwJi&alOZEA&x zzmPmmvyP##CqFCZKK@%vd zxcMiQ{H;GLws{%Apbaandc0R$iuGvT~GxPHD!wS zxAr8L+n3&5foh~nUU->5hRBvvhCq%FT!>>-9!B!}?JFg~`)l?4^6Ku7pTDYlIw>Or z!$Xg!#Fj9{@(6mNIgA600-+qQQqMEsAgdRP?o96@MitS_i&L$zFr(Bi=w+gLY!?$O zyHr*OBhjVlNhMg-Clc2EC@j7zBQ59lIM$^lc0RRSVv-A*9qT9w+Ouej22XF^?uz{l zH#Gw~|Mpz_*mEW@l`%m=JG$STQhh(w>Ve$(t;#Zi!31miL_%1Izg-G@&f;Wci~*?{ z<=M10cF}zOu{1XWn?t)Gz~y;*k7-vhagWMoM8Yga62ClKn@ob!`LlHh`@MqjQ0&Qs zA73WI?gXK}Tp#D(fAdFw9fD^Wd}=;8OKX-Wa_5!N@MZtk&75P(rW87=6{f zxC6_6ECXTo96Pd;O0lFa)$4>}5KgVhA8 zqiUb$w7~s8WBD4C5CRebpW#j!U}sVVO?hyazSuf3Z^8<_-0DcxL^sPmmcBiO7x{t0 zDQv|;VooL+U#F;_uF8&O;he2PpwdJ;bG`y*1mwI8%vn0iIojxpLu4M$=WbQk?iPgH zvD>ZA_;`V661}6>4TokU-9kB0vvvfL`@XqgQdk+OGIfb(Dup-kCQyc73dNfF_M~pR zvypJS;Jhx>pNLS`6Sf{vrsHu`^Wb`hKuTgd4MFZid9LN1y=LT{^xnX&b z|1eth3PpP=A~M-BN9b@|yQcZqLa!84H(b+W^8m^ z!=aA`+HQNJ>U6ntffxsrze0Yp_bp?dHZ0j+Brqhe0FWT~xu&b2%ZHP9(xKUDLc=-E zlEag9Qum?cwts~>yCNffUqu%&aU!Px+EFD#m5P^u%U^+)Wv*AZc+UhB;~sqWy)6BL z&cS}Ar>wu56AM-pO}WPL?BpG=KT9Ahy5Pid)jP6Ld6_QIW90b~k^46{gm?-alWPFs zuBPNmTWGeENiz~Y@zM7S0`s+dQ{@F+YS5<{jGn>)qQ&^7Bs&M)Spl$336v1|wmQ)N z7srl$+Nw5uUS@~XfqjXHNN0lt$}xzU;woNqZp_#I+jp0Cz3n~`7gE`Klv%N0qoZ#& zO`V}&_8UW|O*^0C^2uhZtC)1{b<*Fc0J1W|Qxq3v42Ru5`^9 zE%aCqlqbHW&t!Z@#rH7@HQ#!7+LmdP(+h{2L;G6eSGAlLi^FqMz&OluR_AWoG`PoP@Gi)u?u-B z-`Z_>q;S*ZJF~oo>Akv&&>Yjn=Fltqp9N%8Q1HmaLrTR{cpW%_5Ky!^{F$P@H!AV6 z2=k|gG)HA$y7k4U8$T3gtuC}%&CIX$d)OG`;7AT2vqEg4#2e==G~D?|SgrBF1{;!%&%R zCQ5|z3>|eU0VRD!!*k^H@!8kHxy!y8kekZ!t@eWGv2G3pC?Paw7F!J}dCrTb&<|fl zL(Ld=PpOMul3vXq;K|W`f5#Gvi)G6a{p?Ml7@kbqaFlW5(pe`B@UF{Xvne7dNv!Q# zfP5#3EoCdsXLMfRuH@^nAuyV`%Fb1Wfm-Bjh(47LW#{ke+2w?!r)RxY;hTP-%LQN^ zqYVb9JW)f46MtHuSwufj+oHN!PTLw$Z;xtR{g14os*@6MKc9Ul610F;1tQHh)kUVno!``7Qwb>lYn^8PSAix2C#^&a0l*O z;e0Adu%$m)2pk?MU$;Ut+Y>KTJt*+Z<{?xTx{QA4mjRI^F;jG=PkV>1c48P@Jsic> zi06W;wVyn1Pyn?S)TC4h)^BnpF> zZ}YAtoKOKYBFT>&2s5IN3CqS=!a@?N7s5X#WPTGbqEx8dJaqgs>ybpn*if?Jic0fa ze0GknJq^IZVqi%9A#dh2gcH&8m7wKG>GdDc1~<+f0=B5;_L=GI(yL8G6A;6stS>So z_^BbzCl}b?vr`G)8H8&RD1*SNmwj;NnmC9|Od-2%JYq(-H=2agT1Dyv*N&ZcH=ZkO z?@`HZeSO|7i~jgI#^uM;lTYkjRxZ$33Z~b`HO$^2NYw3)6Npi2Fw+go@efO(9G>nq zCQ!-x?q`V5en@t8b~N>JJBvHJ&-1v2$~uHXD0wT)fTF2&853sagDCC)OiswRX5f;` zo~d{P?NoI14mg4ek?={I2vkUn*x*w7^hOclPQuquS^IWbkp_Z$2V2PgJ`7hc`@v%M zJ*w&10~dpw;jopiHX2iXt|gyR{14{Xy>{X|H|l2jg+Nyjm$GP&hVaoi9auEN(Ck~$ z0edip2R!j6kOw^Zaq23iQ6D~cItzy!V*P`Zsz9-(lBv%P>|U{}$av9ZyoV|Y&u0sP z0eV@L9w_)kc1H_mVf29R8v#wX<*Uc#Qd9CzKu;*7OpY!O!apLid{40*g;WI3wb&HW z_hixGVPoGRW_Jqp6>9a@I`#xhxi#pGOH7T*A(U6<+AklEEE|6-swaq4O^9FxCs5Cvgrxr z^DNTPwdrVxK(%Ye{xaHKs`RySdANmm5Tj@Ykd`Qm3x?f#%WXq#^We;8!2mK5GczbR zHTt>d5pyF0Eh&CPY{#vMB@%_wq{_y@Uf9XVWEN+jP&atxa) zqgH}h8r{*`=$qB@mBl^psalzOHShN`_V(QW)q1&*{kmLXP)`Ctrdce4fv^Vv{dG1ukOhR_S% ze*;8p743kS@}_o+c`Dx&7{sr|I2V72I4 zPm`D0b;@Q48255JuJd`J;zA$2O2GvzWRq?0Z^asoXf_&AHmea^Q+P%?AGRHGca)Aa z3cXj@?c_$#r8Vz70POnkKD3F`G35PsO0?LnBtx?8SaMr2K}83Jn^6iye%2??qFgNw zOG=0AUd0L76rG_=O`($eTWVf(t^^KO(XYiWQjBx6Yhqf)^DO7?#U6mTkF}x4^zu3bL+&2)9r()+oOX_zN8xF#qt~#z;`8u| z#;MOz!S?!)Vr{-R;32q7)6~@1ApD^-Y8<7nKxpGdB6p9)STq%$s6svPXt%m*_C_i$rMW$FduEiQ$iLx-!GJ3=b(9Z{emNA zm@{5iApfc(WKTETyXI4F7B_KxkBN9;(oBmh_X4%Jvg)PA%p}^Z7cUQgM8l>-R%CcTB|q?J!s8mU9yYlG~Q=2%D|3_*q=RN5m4_v4bC|SA+eqE@9l@ zC`hKpNNHOHpuEvUH#wmwND_OGx}`XcpU(c7lz=5k&8uo1?Ql8Hk|R!XP0hS2uC;>h zCWSt4_4y-U^TUtT_uqCL*rJzvpGE4sZ5yHh)gFS&9K?dTWA!4kAxBe*FLtuQRg<(9 znZDrGh~lA|IQd`-zIq5DWM22&cdfC(97%i){5=@>n$3YCjWLDKg{t#)24XqBKUK2G zAngkAEMLywzsd%#bpO16sg%j-aeMx%%uj5bd-FQthDgbnFq*WTc2tIn8lCd>U1HQi z7Ovx14C1sw*XUsrCWvz5lPZ;%n>D)jzHVxQZBzFBuGe+W*a(nf2T@W^Z@#ulAG^8( zPU4(UrJ6%mHDXN-Wz*A8J)T$em&eA(H$i}(e^p-ih*gq zhgyLZ>tbzeFa}R=Ip`r&?}1iU%vY~sU{)eampx6A&CIpa9JbUYbOeXy$#5AWbIeZB zxwqa)zOgU{|U7XF(jrP+h7C>d@RQD1S^SC;=_{F zO!BtxZDdJXc*VOd2a$}@>RKrthU@G;T`-yjH05rERwa}Xu=%uEB2;)pT=fvjpaVw} z79cKcnD4$j)-b;h0+0pT@p)z=w0Nun0G>JKq^g5&ThJtPe$c5{oAAxpG7BeeggR5$ zBP%bSmxl5-T;Gb7H6-q!znE|>+H-8xOL*ELl@cXEf|s$*ty~xM-tu54C}mna6D*k5 z_X(m?b-X(AZE>Vbgh%pO^u$4Q5If#TW5d_)IJ5VEsYOS^ky-gXt`LUbXkY2Nf%j>E zt}0YSq}jRSHl)g;m+Jd?rLn_My;rJny$$ER7<3r75fkyL!~yP`*T{PoI`u zW%;>%uU|brKXE%vU4ZVbeKYf9TchvFOVm749yFpkq<{Lw8p0WIn##y2Un~i1dDf6$QM=86NgSdn5hg`qDfMySeP@yRyNg)cF zrysFfXACY(Snp_rJI0hcjTLCV?ymj(_Q=rqSEg)_P+pFQMPrxwj*!Vfy@gpkRzX?!=_Oq z?x7*q)n`Yu)8^s5#%v$Dl#=oauXl872=#bp#1PkC6+epjV zLIb)!ba^|NAm0`|P0lDO3-iW2Tw9iLc%Kh7+Y2-6=C5;bdI-Gm$T3ZsI*r$k$3T;d z%3Y=Xx%fp|d3N3|NeIi76bm_$9iH*bYaa*q;bAchJBpp{+2 z3B7H06*rS%l#707IJ{r7^pIxTD!VwlB}j)d_}*53JMylcAY7@!=6#_EIilk3#VQrs zRFg0}&2eci379>wQ@VkEl?FSkPw(9`m$lSLwBJ_J$JYEL2hqJC*j>)7M#F%5uxf zG~RE?6aiTf{PJGHC+1bkMX^VW_k*{?#O&uwjHuZ)-58=cm$vBZAM6YH_=3-+>yD1t zIlkRMh~2)^wEMif7Hf)RFHk|g`9~#rX z&uGh;;bdR1GAUnJ^BQsAVmM4y26?9GIFTquiAGcHe8VR|V~eptr`UR}bzS^$NZ9lH zb_hBOiQc{3Dtc9CC!;-EJkXL7E9()sv+nUN2CbPDnUZGK_$UkA;2-z?M9^OzUclhV zk)I@iMKj(!%P|-O90GVy4}thd^6A>ZlL^{<$!}sn1fq9`-#Dux%i>fQy}Z~eVyf@e zTVH`at@YafPr`%cu$Q4PM}0WH3=?!mV|1~fq9vOzBuM^w(t_h4Lu&I%kWqP=xGu8f z?*t4WcwQHYa!~)oqk9CKatm-|8C%q38xG${F>=*W|FomB;12^r%{3T@&ur zHbAbC;Z^Ui!y!M@&wj*F;50M-NypA#&YB`{(C1lR4AL6&eJd7`QJOhwwI(7r{uPcn*4ZiOBwz`A(6Y)sTAAP za?n2x?Mm5A4YI8}>;ht_UfdO9hXrcs(xj&227(`_{cg03s#I0OVHa@C%vS6=Qyk$^Js^vIgm%3ugPF^T2-0`&94fER>m{TZsC z&g-$OA!!r8S*WPkaboM-s{z|fd;sUjM`c6J3^cync$_MpzSfBvO8W_DeOcMwgnN?Ey2<_o1P;&VTaI3@vu&IEvG%bPul=p} z-8-fpStj-YnxPU$Xb?8*|3YQY4*L$xPDTgKl*;v47C+tZN!Oo+nE8~gx}9ijzwo%4 z2TY5)ER5aymh2)xF!eA8xQ1>zG7B3F;9&P}wgei$7WrQ7 z5WL2B)cQ~>O&I9Fiy@i=mbfye)oBuzn4E!vD{PQbJ^Zim=PX^e>Tnl zp4%79rPS7^P9XT6W)t>AuYT?7O#B$chKQjF&R9#>^?o>|m=bj{QSm z{N+Rn1kHwRvw=71<_BZ+6c$c>NgtUjGd_*vXDcWnNah_lELM5>_*zxx4Gn<@n&xZPRG*Z5 zzs{#DtJYMs{nmyt!0o(QY9(y;eg(?35|4=N`cNIKWU8LT)jQ*$TrABQoxikd#t64M z(pax~dq?zRxcr@P9?1L**684M^m|2%@%P7IPIm#hb#b|ZFOoBks2ycd9~6wNlm^nw zAOZOT%N>br*0KsASJ+P7iWVgtwRtLuw@I}{Bo_%ofUlbAc6saVg8&;_OD zgNljSJ0bRnTga-;q)089l7ZCUx}8~0#7R0wXw@yo+M^{lVu%Fzc*JYxZCs9VRARO$ zN-JjWBS#Cd-18g~j9(EH@=MX6V_eF#PBa&P=RzvFC-t%t{{6Sr)q{W3hvw3tzqNGV z%!|R6xAl)}CNtAm+0#8=ArRuI^}-sB>{KcAfyOZuyRcee_g?Mqpr0KPW3C@7y;odh zP-elMBS+ql*m-X<2URV!S>#4UYRzqNX>SMF)GsT$$fb5KbsB%qKpNAFNT0k}c0Q(V z6s<8;*>RgXzyP1eng1ZUwu^Sp268cIu32Jg1fIiMkN}i4q<2J;!q!20rG0I+uKB_t zP-6F6DknTY3j-G@mX_akWH~d&CoGGTD3!E(lI8QRrTlu_0$)Xj&T+&IO*N z+jFzrWX-%XoC21@e;MZBy$$D9AVgGNyNyOlZv%!!3327J8le2RfZ^3H0Gl<0&TS|%x+ z%St~NcdFjQYP-qoC12SmJ9uyY!t@iu0Hw*}&ql(XOM#BA%b1|GVfB@%v9;$4HbN0c zV&~d-Dhiq3z-r&F;jYr0j^bM|Nth#Rm{ne3L}J?HS_!ynqrcB`c)(Y~IKQ@Jw5D`ef)%{8CH^#ZEWrG;C{Mcl#D^aW(78MUVj~?ybI*%ai5=VautPu$U|d$AVcb z@3mBA(?HD>dg{)4q1no7eF`LjsBQ8j(L1iL66t^Sz(3v4<%(CA$%=!qy-1qtl4Wig z_e%CK5{~&DprZaj@S1POkLI73u4{foPdq)MO#QrIpadH)@Mv!LLrOI07Px8%^vAAT zU$n%8zl~yN1G=?Ew}TnUlQxv+WLQS6J6Wf0O$|$U!{%du*PmaG0giu2u}hD<9E6LD z#bWotiF}tS6&qvYzwO*quG75Xa#~N%XJQ{+MV$$(CvBOX2QIS``7hkg<*K|MbS5Vt zVwEZD6(jy@9c-jGLp!nbKUC!`WFM=bmSHxUy&H;KjEa@}vS zlHU+`h7LE1E)v2fAh=W~>rH-s<+MsCu}x{WikjQXooo*FS(NWiWB|n!iPo^2?r8?$ zoFL^@DSn9)J&;uKs}nl~wQ6U`QKFU2o*_j z`j^a$V46X(oZ(tR?AjSiF)p6)Tr2}PuckoLsdYVUg9|}7yZm0tQKRb$A!^$hVv%NU z!DrN>L?o|Ml%J((qVePA-BpLCO{Cyu4s68X&JN2Ww&v7P5gw_H9Psj!_yaScIE|q%q6v>4QbC1oFlfd<)c8kA!-qn=~iYcc&^3y(0 z6T27h&WRjUD8}P?s#R<96LC0z|Lxc*uE;{M=@+rxY|uTyuzS z`G~yn$`U(lcl*x|@bU-d$b0^nU?uHU+3}hke+Kl6ry>RYX+vPC|0ujoe%cSJ8OQ4; zsg3x1XxkQIybo8d!N#wg_x~QhdSs1xnu6tn9@WzQod7l_&kf*^;Lp==`AaWu{C&)hX>t>uIc^VR=KCV?*Vx?G zO-HqA7a)*5DL93Vy>H+6m+)L))|ocl4!$a?Cx)B^xlUY6Ucev5yf}1qX-M1$l83Xs z&Eg%tC&{;w6c#YJE;_EjW#6*Vv^ZSnd+S|Hl=1d^nk3A{7*>Nz??|d2j zLmD;#MVUYug290wkI6Y;X2A|u^szyn+`W%qDLr0SbPO-Du#^X2Lp2`VoAS|t5eg@0 z%_zp4ce}EIq@di;<~x(Dim3L$`|=8M3_mSna`eR4TQwY{ox9u5V<%_}I>)w`vuhls z4TT2MfBhV?)Py~)excWfCD?{h@vm4J=psF0LED<8yo+r>>Jc58H04r%i@1Mi?RYKb zOunxz2bQH(rjRFkuiiecT#i|Jkbf>jmFcMY=Vt>AoRhd>?Jp$PIyxoah@^3<%KP2D z2(D}c+0?qFGj9f|eTbY-xT!|iQ$O{wnhhAbz=YU3BO^smuRV$H)|?MHV!7cH?D{ zNh(Oh%aw2=T4uRMf?`KPWTt}cQ5jpGVlvKZkW{Yp>95a zXdjRboW7Uf*&J&SQ*#|Z64A1@FNGWGDcme1M27vG#jiHoMV@!Q=npl5pX%9>ik3$W zEijnp0keXGBt$F{cLEf!pG9Hx3{disdKZp!{qG1xbUjH4J6RS2Te@2XX&4j+5Cj3~7`hRV?(UJ05NYWiQK_Mo z5|B7I7?00){+#bz=Q`K-b zAEhtFC&mxsXa4&zQ4nm^+oSZl!WzgG?C#{W(J+W2?4g?(jHfqw6j!K3Kda#QZnpxQ zkHh*p>VH9mOL-$csWf{dM()3`&P|GAY<;uzad^R(QI)W)|mawu*Dt-B-iZUCaZapHWIJKb)c85mOVBdZsO6nY#Q4aPv{Qec9LL@5@@Y8#YyWx4)vV@EP@A=sK%5IVjrB z8Bw#Z@mu&eIc+#Icn0ZlkSsoYvsmQE_3zI*f?2xoWQ(GhFkAoq6`RXDY;a_)>|8hb zuP?s*ZQe(|CxQL+K&*a7$pX>z{0wvg^?LwM6$Tg=6#)?(^<}flr7z(B;K%o95GW7P zme*u)NREt&u%>lMZb7$B6jWa8M)`FPs$`AS%8cYQ+w?S(U&GM8cnXYql-Cy15I2IxOd;hf2qz@UH zco6 zjq~Xz>R;!rG&`}0CEb|&UMyl`Clbyx@|5o(@ce%Qm9!zRzSe5yfuBdK7LHQBTm~6F z*kYRi)usvuuE(%sQD8yQyKwp@WExGt(;v+|=#{3U{L!}Gd#=&B)%M2fxKg+ZhZ8hq zys{9YG`J1iW$AB>G{D=(ABas9PQlisQ$^B6PG%_-o;ltDu)xNwPe^+WF%1r+gQAxL z;IaO=7npKgW@2N0AJ=hZxx$Ghrh_}e51`Mgm(B-7(!==h4cuY;$_~#p`;SQZ_kq&< zZaOt7ayQMRmANr7e?fQ|qB7v}?pJWluNCUgDIKIq#d>xDxYDk$zq%%`5V9MVL1c=d zA?<01TC1D*IM5^Lqpk_d?&#q+P@%za&Ff-s5FAzSnfA%8J%|nBwpKZ9s#o4#baJ`B zskiJL!4rMr_cgwDs7F`;X}&4|k}tj&SABQhzFm#>8t$J_d^=U=0?8i#;9gC(HK#-{ z#P{aM_*Un01KADv#nv<>I@>-kJ4;aY*Zk9$zo23gSNWVlbC$(NIQ*Pj*jFFr%Xdrd zeE5fq%9xh|(o!KGS$l0c=w%NVfMZ-PChkf-2_VB}n?3jL%1YsQwZlO3J8*xw_-hze zBU|XA;gM~fWXO7*h=z1p7473UyRHG(8VaeVaiEK|uL?nRt?#YpxpEcxzTl4ps(;kO z^bY^9PxOvc|GN)_scASOOT&d(68sitM>%s`?@TmraJ;y}!HT41lbtoAB3VgJq(+c^ zMtji|>iaxaFe5Ni4|fGip4G>0Sd8psoAV(J&1)dHO%{RF*~7Rf&@@iWZt|aD5Lv!^ z%lszp8FNd*$B{Xy&{>7opof7Cc@?oA*033z)nGdDbL8USHVFI-Yrww|?3p-H2vDIrg} zT>5+7hr_ea@zv=LBR;j76DCP>gIZ$htsH5nUr`XrZZ+O!Q&y|&el(JoOS;2HxI@Qo zoA%*{)-rvuFtM8JgLbWh;V5iM56V z?#bkxq|%f7NWm%E0p*M4)9GICVnid{wG1BUwu@1W$*+f~oE+ z>Ws5tsI_5A3UjNrc9*)?6Hv!OF9kw3XnN*BdU>`CEk(fqk3bo`>Tz{HCO7Z9**R?O zs4nLN-Hh(xP$?&+{I-uh3iciti`dx6ZW+P7M>uUlA!&+?4VD(H3a{B+b4;dXTQ<=# z5h!e@OObN1PvrGKN`r-Yts^&YazZ-?7K*7J%+&Ykd>^_;-QRA#0e>_WznZN+FV>9g z9=~m8Hf!sZ`*8Y&=&{of@e%GYEcl6)zGfD|DJCsMls+qcov3~sA3lt0wXK7R#5DNK zY#&2c_$E@WB?5#?Rhuwt95G`X)^452HV_PTB8=f_SP zgO7zdV#BP10d%7)EtuP|7M`2Ac><=+c zwZ&pN_iEm)2zAg8Vj?5QP7#-MU1UC6Y?e71z zvkSnTM08UnRg&_{E%_6Sz^-U@+9gHGnXzAB5F758`y3P5bGH5RXSQn&rO=$T#P6!h zu%J{hW`b+%wf$#!xBuDk21j-Z$HURPW>6WG1|4_0oD*x<(Xw`RRf+aNKM+>e`)Ux~ z+0}`9-sy^@yL6IJwKZ-$W?mVuG)!r?{W8~?k-X6Z6el348i7!SS$=hQQ z?Jrq8`J$aqK(XbCe2V24*7G>Xek~NMWf0oArmtq%iBsp6HFo2HYCk5S*2}vm;bhE1 z6R>u{&RXor5=cZuj~q$iamskVZQfp3uYS^6_k_`H2JsI=40&JpMDM(ILJY4)3pv%< z-LWgiOjQ%6t(aZDBix_zegWCdPB?ykWH5p+JcchUOp>)RC$C?)ljv}*o#^ZxE-96I z?FXK*n&S;W(~IXz>#0J@UK!~nxIIqap7;WpK^4Pnb%HSRnc{o!M^@O0u)F=5)=tpQ z^t&>pzS^~R%zb7E4T%R`<5d++nEJ#&7ldwFuKar;>p@o5_gRxQq9h}(*@m@Ncp$2L z9T)B%)jeb}q?Q-)5C}{j&gCmfk~I1uxy)XFeU@#(#cO7-aPNeJdnEG}Xf3F8Wy0Y_ ze|h$vvN%7Tt|zy9cQItV{yT<+O4O5)%yPGz{36Wa$RG+{e-Al-+DrVs20f4D+C~=z zx*0TK9l0Qog|;R=OEuwLjnYg7Eyx1pR!CrQ{L1y=xe$I`g=?k`-Bxi-Nw#g> zUlKB}#yf^{1ZjfDh&ptFa`@MDXUM|ya)*>G$I0eHY3k2(p`bxaA z+U&b1>+=l_18VW%+dFUH0+*K%oySJ_4x89TW|Nr}%W3I%G7L48H`8Pbwxad~78)6CH03tN>=9fjlTGzsU*#*9syej^$)+OoB3{MoL` z>HdDV3}eY=nH(vy`IXQ63zOdkKg_O(ew%&(DD1zkl?+Jh#e9f)FQohU5mXQ7PU?Kh z@uXMH4T`%Z8Vca#*;bV}UaRibUWL^sT06(8$-(7|}l!y?cqco%dH7qC69 zxlaNL{4W=Ue=ei|^wTUXSg*K2!lk2~q{-`qZXZfXFJx@h z8IfT+g^w^IepabAg)i29%{S`P&(-#kb#DDR)f8SC@zM@T} z7jq2M>ImdblcJns0?NHFWK*a$oUjrakqu>SG}@Q)g)|E(T0N*V1j)7@^+c+$Q07E0 z4VA8Lz@=Way=DHRasT}6X}){UE4h3e?h-)z8IifIe~y%E%mO(P#Lkd`TxDvX*Vdruj*;oR=7?n3{4prH zGZx>RpoDJCGka1c2MQ-U?EeDMVFKn*1RAdTEKFcc&k%r`M{_*O9Y+oqdOa&4n7)-#j zdK10D@-t%c-Uo7EBWtPBezx1i|PuK&Ke ze!|Mp1T%)}?2URoert_KWT&0&1E}p{bksm^jSB;=iH$0?gc?ck3bi4RSQ*k;&fxfM z7to(UD{{FqLDCik1BLq`+-D|w1e%p_i*L{wHI}tGw?6kCqfE}+7nE5&(lOul;V3y>H6Ut7u_5-{t?$g|F}t-k{z zg5c42eUJAr-peDq32k09YJdP5R+d~28y&Rh52BnKvSB_uFA30`byVB{oju?^Cb=S@ z6LcL%@}l*2&9Jo=9E2sno_eLZR)A77@C%fjO>0{j0N~R?AVGepzSQCTT{II*+aBj( zmE}2QwPmkAC%^a3N2=jll#kjTlz3uY$%?nAD@r_2xKz%K+358m!9;irhqEzzzaVr` z?~>S$IQ1#xE`}c;r`g}vtDhY5jQ?a#?a&DZuLV)w`cx_SnnNfMG9P~rQs3NFSC`?P zLx$j0Aso(wu=n%T#brdf)r3c3z@KiTN`ob%fyIQI+K}D#0{)tli-AKw%n-Xi+)pSXu zd&hY!iod$qjzkQ@(k^ZK-6E5bGt2S}=%V%5CqBk4YBLh9w}*fnMF87U#H&Y`EmR%7$|Z1e(8f!JOo<0Vbca0Iy!GMD`~d~TGCBA z(gc*Q%hRnK+LwsZ2HFbR^oauLtw{BA>^NT3-NEiGj+y`+tD5?|w(^@cH^v&P65XYd z(-ZW)*t|1&^lWod1?7yl<3*Op)ptASg})9SajKT=N3Rvd56r)0gMUjM%i`qKa$naN z91k+@>u6<>i{1p^&*(JoyqyxKw(qKhp+p0o(<5ZRffwo7j0_qMqfbKD8lPn~uZxeE z#(4J{)PM$fa4ahzP`Bjt;zIkq*hp=xU|j^!O}8R({rjSXo`^mzuF*nI*^t3~ap!|~ z$_1&#bB@+ND`iXR1tV*N2ct0IBB?x}@j%hJBv#`BSGE^EeOG5Iq;D&g1unH zm%{Ea+uc@N7>8z8sbU3J*Q?Y)PI1ypy$>7U z0@eE9E~EJPi9oX?DV1K)1DlUR3P1lyEOoD}gd2ag6K>&%jsg@K_B~b5jf_xwKex7V~pW8c5>Z@ zZ?1^s<#F}|xjF+CcudbKV_w+JF)aF9`z8bpElAl1NjlM=tTsS@5T^}JF1ri*Mp9D6 zWxq@o33>cD8?=gRi1Vm$KokuT4#8X5($pLEJ)!`V@>df&AxHMOmgu%?uq&t zEwB!*66e_FCPnIiX_Wbm@cEgU##rq)I(jwZ_zA(2t&>=Mp=0luPT;vfw`Ajm-_J7k zz_~8jvg4RO4Szk8$^lx>X?oumwk@65KNDIF6ql}&7a4w)YG4LK$OzNjv5FV8bYF1D z6;?In-A5n?Q3t5crgKbHGHQuqEY)D%J;B=p6Zn>r-sS>n+23nEi-6SkCzLR*L+V?F zq`<5^GA7P&+>K%DU66@IG;B%8SIs z`}>%_Bk!j3o0*N$syE|GJq4AiUkaaGveeQfwmNA=+x!t{39TtBBjt(@iF&(DJD7Do z++*~gym){F*yrZqxTb6J2jV-Ea-|Ty?TsevY10o*y8g$xDXSSAhX<+Ji&FOyPxkuF z(jIdH4TqU-(l^eWFg`{gW%Wzq<9E#x>z#l(9K+E&I#x-n*AwLBafDhqLDmn!+7v5VZ{aR-A`}HSx=Taj%S4Z!5Ly?3%6^=*K$iCMlauI<%0Zo02*mV4@P zwrqn$e97)3uBqiu^RAWwJEwUb>w$x&@YK?U8rHgj&d0onLcO5e(WGFO&8=Iq?tS-a z=)Fc^+u4D0e-zaxt}@AJuC4bDRG#O6ava9;wmIb}>KST<=B=(@a&RVu2Cq>9^(5SE z1m>9q>Wb{|Sy&G`d2(oxl?J5ph`j>({&-8Ksl%%TmOYQL5cg)3B#_-U&j&Jae_L-P zENi9pqpp21+xfv2 z^J^sh3Vk^NqfPM{Bu_p!1J5==puJv_KStWOF7;9o*G|16JMzAd;+wg}Z2}|Cl~Q0M zF}B3b+rV#=y>A&Yl7kSFemij5bvIgkbLBF?Zy3p|tyrMM-6dg#^By0rxuK+JaIOd5 zM_un7aJlL0a-~%N+SW7moptYXAYH^ZW(H|8{Q1gWS<-GY-(S9g-X=N*r^eFE3$s^I z$sZxGP^c5pF(K$0=*w=25$3Ca)9TPTiEy)>Fd?GP1dl~Z$HCzp0wVNo5vAr{6ELl% z2K!NZ?-G!?(-%KBiPCblL21U8Rzjh;WH+R}kVzpb= zjYp%S;3G4iJDG+a5g?{ruZ*)-;zX0B0sFQzrDi(2>8#Bou=-jH_5B3Z6`2(o>`T?Q zciUN)%+42X2sI_p0=fq^2o7(-x!o?4GAzv6M)1!Q=P|PEqFNj;<}Y(NdR;$ziv2k8 zT!3jxpepT-mKNF0Y8)cFr+23x-yE~B`?%IE81G!@BQ@eqcXebu_~(3xNO9HEI@9I# zhroHGPxDL;9Q)^|+)VoBNSMSL0l%BO@?r(gG^Gk0!YDW2O ziUYP_-%RJ6E~01UgUm%PC_wcMsgGKTFa`-bpf$zf_Uk&`xr@-Rs}bYXzt<8X#ZPUs z3mY9GJPbys81h!B#=q3S!hfKsGflUGsJ9vJBQ!|Zts{+*rjy7wsBam|+4RTyc55DO zfS4N^%vOXQU!+a*&dFUQH8oNo{4Bqoxx5Elr$2oVYCv}94rNNmt5AglPICEtl*&-t z{sg3pEE{Y*I13zLXoCnF|6D8rLzm1>mml1N;X+yN&DgU>~rt>xjC85=r}P6$>O#H{stMf z(Hapehd;Qvt`A0TimgCrJR7W(?RNC7lHP5)jo)$HybL}y)tc}FX(dKmJ4Z?l66MJI z9UQNxE%-zntfV;fA_kBn9vD8TfAI>u?Nk=?;-!kXWFRZ9(;=y4&TaPW^J?7ptIN9z zI{n6^3E&}qPn^B)x4gX*(Jp@L|COo9MyIsVD4PSmHu!$7bqPQLAocO z`2+pklA<@E&V|uxC+$w zEUos^)KBYpxM+g&#^lw=cB0ri`rfms`74X%JslSSOhBXGCLrpH^)%0g=4n``jPtK`aXMaoz$AtH^vE#Drt>tgr znXVLh{D$y|>5u#9(kplwGG1a@4ioNdXSG&aFgo>?1Rp&x-N1{iNEZrTKJD}w!{^5R z=5SZC3g;y`F;hNXty)}ITNWs0AspabK<~kMAf?@x=9_8xV`o#IvbI#A=geIZ&w+e4 zA=jo7w7?uRt*7UOpzMjq^@S8ERt&dl2vV&%?36H&@=M?E&O3PjIgw~mi5f$aAwCfd z-Hqp!4tx5MHQ-~PN&H+Wxn3yAX~YaHV@?^+=lv!l)jD95bZqh_lTYsTiu!dh&)zLc1wwNC2&kc&;quL-of6J5tu;(VU> zhylNy=GHx$1W4B~oZAKiS%&lEqSDYI@0lXvQxQY%omaJ5X^F|wl}olsb%U{eO;2x& zX}@;N_{=itW_#@>`?rNw&e7&dZ{e=^mmY?5SUiq$NAEo?fgaI=ZwDvAzpv==rMcpW zraL8IRfSUA`Z|A~+@ac5i$A{xgPVj)*eJ%)u(v8hS2UBbP&1?>ZiUU!KEjz`s!BHU4`Pw z{d}@u)w54B-r1v2Prv+p2|Ly{-UvOXWbe09OKSPG$GV*`+_Y+NGAYUf>s7U&JhQb;aj;vDkI>W^V7Zt7g` z2BHAzxJ7($kM73fThE!Mu0LuDA6E(~V}Y7wvPTilJB zp&`sq!0u{M#O@;ZFcLEDs+x;KG(nepgH`fi4+c!%x&_yK1DCpmWgL2Dn-??4D{q?! z=T?sc#xVlrG+2N0s~@fwq+`yO>!D~VjLiBf3H6oXW(|t>stk)25`i63Fw+@X+{d6v zjtT2o_C7^uvQd+*F5&&v!aPvaoD;rMxoiUh|$Ihx1cW3$dE>F`vj&Rvb=Sz7LW|a8&fDeVyNL~P+G6Gizi~Ck?%1>2IpoU8*oVVw{-?uV1^EYjkzCOZ1O`buHs}@ z{t|8Qe4Q-M@h3_3TV_ltg(>OFMlRgPD||Wf5DKhBAOk(9tlThG&BbGYv71@W(@5D! zD6VjDLIs>(Ym}Bq9H+)_GHm;RwSv-OmXadI2S_&?@M*zL`DkoQV54EGn;$CcEcE_1 zfX~xoD_q1g^xp#SSf~89cM;<|vp}wNAo`Ei;tHhydvDF5>o96R0m>%KDz2HwOg2Gey8G1&9+D0wN!QL^5O9HqA zR97Eb6n6U+R5w#wBxTA3ESqX6LBOaQqg^!wV$*p8NL?#Qu-6~S>6Ta`MWCI4u9a(s z=Qdqz6j{pOsZ?v8>}oOAB|rc~`*~nh&Or&0hm2;z2=P3eWPSRlNx7LDL2-$*^PE@( z;`Q*Djb>rpYft0z-NpeQt6Hkv-Vi}O?H+n7@uhw|=jQ7c@oYhhb09zWFoL5$_R&Lr z$APr;Q&0V|`pnqU6y?wBG|&FHJvTY^U1{0IYR4OTj9LfS&c-s0fObo}jywI%T2W+( zXG${l>0h~$`iS;bn_Wemuw}UWTgH^c%v>!TtZl!&c|)9^onhJTCg55eGOL*i`IuTg zZ7gwLoBcW)Mu5gXqLJ>+o3P@WzI#$hI@*V3P>bQ+wAs)@1=jn@#r=kPeK7`FM}LEJ zX;`S*3kxbvZ%!;2uA@P1VpZ^bUoJ$q_4O9l(QGS&E zgTzMnLxJ(Kr`b6q4Y=Qo5*)HPQVbYr>TlHjmdZ==%f-j0Z0*tKx|R_1OBHQv1V29$ zO^J>1X*Zp?>~-s#<=+Y?yuxt~D33){ zcekpf&>+$vVZn&siD*x@QcryuCq4QZGl_mc)^Ck5y-)W|a@!cl)UMzpp_~| z4Z%htg7%675f|(D{Ka0%g`pfBe}L6n?la6EWRkJ6Iu8Mc=m57`06N~Rv&;gik))<( zB@Prgr&zU7n}^@~1EYQu>kFQ^b|)=-yvq-ah2BOoC;B~^>;dX><@rGtgG)ZonP{yA zPN(nU{Z>qWYuIIXUK_5tlViUp7FXX}Zf1X#7db`TNB7U_mnnHs6RD^I zY-g)UGInT=yX(sVas3KMY5LI8J)YkfxPoyUKlU-=oWb60T|Q@(#%aJ=3ZK@>QECNZ zw!Q-6)TIXar`~n`YB|PYCCKkPx{M3~^9FS~sr7p1faYcO!@10R^n(c!f^o^}UkOii zN^dHL>!|P4jpH}vhZ#J>u+R5WWAnVq)^16^hk^%~7@uR{w8E07Cg5&0PT^O%rpJch zcy(-@AfTI>IF|yl`+aTp>XQi3uIah!=x=?2kQ-GI=q2?!W*+NuR*~YwAzh9RP9&A* zoCem!LEOkoow{wz*<^P|X^VxPieja~Mbyh!-6tzutRrA#jM#hkEmXHQqdQ2}asuQS zU?I|6m!XWSyhz}wkx@QOXZ;+qq1lJ{uggxL5=7KJ*N=aL`kj^g zJJw}z`FAKVo!~1b&FfX567HXU)UckR6Aiok3$gjos6X1c2{%xJU8AJVua&6z_ zS1_=H#5ITZm8cTWx1GZgs!O?#Dn|ATz^}51*W1FRb7_K?DhQe#kZrFC`VZ*B5 z;B!Rb_grTZb1v>I;7a~E)_1HUJ@76zb?^VSWFY*81!45`eU-}y?Vt0_dHF_E)oc3@ zL>w$m*MzovN!sr<95p9B7>BnfA9)ru+%`z3&pWo78VwQ>*;p$1sMMG*@F*c#S(NX; z?(MXQDv)Ayua%ffM_<#uoRIP3w#&gwX0F%pgAQcSQ9v5RYEI$J-rG*$`o2P5B5#=8>8lLxOmHTJqFR`)W1d0)k<{_9gPTlE{yIgY;{nLP&zAU*HL zHats41J=~|$*W%WVbN@MesdIlJ1L4pV%w4dfov_z0N15;7Mv+GeT+MZ9+eF_el@rz z>)h@4vKl2xNlFGy6-i4hc=P_IW2b<^|M^dH%1apEi*jEi)l%E z;6^%npgaQUv)mbDcW#*jTHlgyc?7kH4VGq%>Ayv)Rsi*%ZTS|Da}c<@{R@R;?j5K{bV(K_19gy_9nbeHG68e)0XqIioN z$-}c3d1s@_oK4f=z+k)@WQs@@b~qZx-*e0Z&Fqc`dFe?H8An{6$HWxl<=$5Vt8&iz z_pjO7mHNA(O9oLB${U#0g}B^$x}t-XaM}~-zP8a&^MfI>whei4?+O>{^65#KXX29{ zYlfxfcR&ZUYrPY?G=hXb4~2KaC@F{7Se$e9H4nlQ07?MV5UjEa;Rm1ZK7d*n3_Z==%?XC~7)7~m2%PFB}$W@`*!=4$aP zCv>j(KWtsY)YMLJwi7aI;PaFs8B5I0EQc0rGA>cv{RM^O(R=#if|9)_*5!wO3tD`} zPW4^TNsXXKOxzz3FrG5}mXz<|L`C>J(3}y9k(AzE zqa;<8gA9wF8v>M=%5NN}lN>M(J4VrKiXpEyO7r#^hiOKZ-Ao%7cAe;vFQJAotF?4YX^Nv|~C_{7apRkngy!E8oY>sNB{RW+0_1Y#9Pt z#YkeG78DwH?Q~zjg_%CF0zr#E8r#Er50^Z-G>0B-3mO1Izi17CCC1iB64=rLQSg2KB6j2T80EGjl|b zCF!ny81f>!ngzb(b2>NCggPa;YATa-&9laFoG5AkMLjA)kQ^A)o3!`fiyKGU2e~=u z?t=a*mWi~sd!{(Sp;fKD*yYUTq`eQJrnWD$8<-8SfJoI(q4>$}T?yiu_)CUA;t^$P_k&vsI}FKj(pEln6Pp0=75V!DaV2zLppVnzrs*yk&+zlr ziDR@ZKftGUC6-a1FkI|K_4aRm-~iUrA8MI7?0g@!hQG@0gIYHQ$Y_+tSf8}-B)UZB zD;KT8)-?CjnrGYsU$OEr?WaS5jCcq@;U*!QOmLGEm(Alh7ruGgZzHzAbFt7l0WX>5 zQk9bY%#B|2rm3Ji^9tCn4}}3ajzYHEHknjU+#+|eyx#0rIsKwWY^}DSm!Wb=be*G(H93q7vFNwet`ySWhQlJF#9zZ)-~&c>-oQ*UKeTm%+3}tD!p125#{0-+r6KKc z+lW(%7G=}6e0-O&_?@b+)b+n$O#0?Dw$sCq-GFR+kS;+)405X1afg4pNIk#RVw3af zmXt`HftSz%-_|BUmxNd60BguTL3TZz1=DEHyFCenZhX>0mIifk1)lRMfNhRP%Y3a=m*!Xc#vZC}4~C zk_a_?TuEjdb^!)zJ%GgHx4NhOo)VgY$cpb3`m03Ox&WMLvT7h98O{lxrOMBe#ybVa zJ>?{eMP=&M@I0}c_fB1()W=|D-O9B*%LSP{Os%Sbrp}A)KH9v$K`rG!y8&okC?)}$ zOebzU3^T_20R7Nm2e$y-IG9}1Yq6}B>*J#gWl^=uC=NP2MKt$EJGPd%*MhcZMb|L7 zf0XK`R?G4tF`og7>sz@~ZF8R&<(fGS%ufJ!V6mn}JicWa56Zz(q|(hwfo!;2BlLv( zzEQo~f*v?NMFp;zHkF`aSYQYc6EQ#=Q(xQK&QM6dpI$TyC(+z?+mO#o27;DU_jRK|am)|FSm7qQeW$pSf;v?&%`P#5; z#!dEfvn4b~|K$h^*26fIo`AltW&Y286LNN*PgUSq3Gdc}AY_k#=`p{M%K)%~iB(nE zz^_jFt0Vy(CxeMCCof0vzbSBmgif9^qBKpv0a|KwcjVrGTEF=Yj^jDFuIW zySFz_lWYplo00kJ947#uAidVHJ&Dh4c!%=6&OyPGYcw~n-*o1=*O``WbCLv^g3bQ@ zWlj3lCv;MP&@J5;@IT*7LCVqM&h9=68*^VoPdJ&pg(&=b55wz{2ry26)cd_8PgFw< zl0af61(uK3cyo(v>2vj)o$)jUoyX@6QM?QETxyU57doho*e3ou-*L~2Y|!!h5?1=*LMqU}u|l2&=-)d?S26ZaA>Is! zgs2sIO`)rD50YIG$=d06syW4TRNx6TE?TiAk}&4-`m6(ak~=}kY_$XX#?m6yv0)?S zc{6^e`uH&n38|hk^&wB_l&&9_gUVh-#qV5}_yd(sC~B(`7moNdu~nF!PQFjy59*c2 z<-c*7PU*`d;!}R;;(jo>R;SulAKs9s^WjTEaf=Vohjxp$Y($r|87O(9A$r2?r|!T- zjlRsR-m~?@p5>aUHnBAasXQ>I%!-KpBKG)o1+Fx6+1Al?WS=?<|8BP1!w28T>H7SCKV1npnos6vnMe1JlY_#h4})D^Q%|AeTr+ucC04ICInDEWT&!RCi4;+& zn9uHyT>ktLlWhKy!(v}EQTasjreEO!i8TbK4fSVKnhk9d*&cR9hd<>3&WMIeMsn2k zaaWVRW~8tH8T86sf_CjY`H2k5-wQnN$4cpbg*~+PW@IDKYH$0Qkp$Mi|F0mE&dt0% z9kVc6lQ=5Ug9%-90G|CdG)~5^*W(F)iK(9`dF^%d)8<0)^9{f5g|^%YzB1W<0n1F? zDkb^hQm|j0Tk`npPfP44sMq>}e@Pc2?8fA`Od8@|zvPhQ+jrN*{dPb95`6!|x4w&d z-gJ^HjstL&D=d7uEJ!F9FNy*Z)qWnl5pES3TcW<6b2&f$>a6AEDLG5)aVLOj^dHRr zL;e4ur~jF+=-V@5l()Cg8nr4~jk_=|y(zHwE(c>56lNPXL@ll*|v7u(6NeG+L@LlFen z{MAiSnlPaN9S^=edxD92L{LtG68WEP{)4ptP}+b0-P|aE(4(A&mEeLKF<*IJz9W=} zWcp-~C-{N~mro4!EoV9B{3nVh>!^bA&G4tLRFVO&ABvIv{w*&vV38J3UA|_NX*`@s zm-Zqo-~r>K2>8D|FuD8+@J0I${{Q~u*KaHbbG9Ajbe%Tnkvj?qCP`dlCL2p7@9{PR% z^*{Vaw&qH6ZS;~w!|_2rEv3ORqgI_sAzJZ4qa{%n!gsk0KB50a-%|-s2*?9*-JWQW z#K&$SdYGQ2!KutxmP&OV1J?`fC*rmh(x+1&B)=!HFnH@~@%%mpFw@hl@-5R(>@B{C zSJFypn~Ml(11Serm4sKs&2BZ-Rr*p_tscbFt2a7$guL?MA;eJxL!mK94X|O z-PepUl?&ZI<95AC>HM87KUh(D0!(~UcT9JDQ_`L4GtX#=`=|tA^7YI{+*?Q2)Wj}X z`#d2nK{ylhJZd9rwATJ|G>q_z-+eWp6|!%?qTb3$niiNfw%1WO2V}|`s|dUI_zP(C z(K8&_$LaN;uTjmVvSD>N9zz$LvY2joE={1PWb9wB|D>`;YJb6I=SdlzTAigYEb{Lt zr5v(N5QYt3idUcMWF@mOl|h7mJfLIYfMdXcVgB*j^u~72@0V*G16ebJ;KkX7!9G zC(%|7lB?|4L9y!M`$QbVaYqIzHHkgi(Wu{E5j#v|^cI{=?jv(AjULyy4M_<>2Nx>~ zWxjfFayT&E(-RR!`y(EX5UV-vRtAUEQlz|&P3UT_vTy81{7qG2^^w-Q zTv^y*^0O!@QRb#&`1PY}(s))`B|sZJ)Ot9#AbCjq5$>&LSikU|Ks#nhuRo7th-P{|!Xf-Hmc5wEQ}B2-kq>!#=jQhiab zYiCB-lrA96ih;la`1j-NBF%OVt7zq5BGXW%vJy*(aJp;e!V#T8n<{}>m7VsAz(E%n z%aSEp)*9P7{4Jo1Q!%zE@ka6L@^F+U&z5N3ArScp0sW!U)Fn1;5~m*J2P(TP-7(V` z>Pshf0n<0fme%b(ij#B&pg%qb$;QkPc##W?VS(thG!Gd z{ldb^AEG?0Wbx3U-qS?qFD58Y%8m(tN&T(yB?kLXl-B z{2?mt&z)sap^JB@^ecUT$%Jea_)~%?HiNgnZd>zVce3xo?mr8nzO~Y*%=q;+N+mgI z>FTeaqBvk;Y&88ODom6#sdi7!-y-S%?vv&?lpPaqHBVl!i3`o#f&b_wpTlV2kZ z0Q!bpN;nU!8wYr3IMTnD_Yd*JM8(J=t^f*nNdO@l0##BTRU%8L2>zSOwf zJiG+g{V`2;hLogKVOI%Ae-%|08z<47Nb>{DYZ{&Z2f&b+$g(&d`GAz4A3GNs3n0PZ zj<)BI&i!$jQ7C?$W+;lTujJei@nFn}Gnzi&B8mQb++0EE(fyXD8_u@0Qa3QNES5|vK|weg7_qe@4SLzFsbQoD+F@-_4nhsWba;yFlKC$-)mk6nu2kalmY!$iU?d| zmrAJ>y5aaD_jSXuFSgZX4mmEEvuRX*pN|PC&oiC0riU0Frl0vpm@`HAupC3~9|W(i z!ef|}9L?&KVz(z?ZhNgpz&6x?zvAlhfFuURn57lnn8)=Na{9XxygWtNN+ixys=Uvrtv@2)mgAYJRpUVY)NC5)xAI6l zvA82PAhp8DiTgU|Yn0Q=Sj#1?4DGmDsE{D|G?|nNOeJ+rbvxIAq<|lAOFmUfgbnjW zL2iAo%swB3tG3MovMF81u``Z{QuOxVQnw!XNP@+ZLsj>JupH#o-+ap~4gvmqWdO&i z>tc&OHe@|e+;`C>68jPsNkf<&S_sNcfrs&5Zo0?mIF2Ecjs?9Hc3y_&6Q@5G?wL~J z6eshypK(OTd7Js0{(*X{^cS8Zp=vpO7!kCJxIIe>MoM9X@R(F7&Xjwhep>upZi)^r zm}3{k0noN+t#bxw&ZLL3+khs8fg~!B;&G+-EvJ(}{pb7~;+f@`2$s>H6yJpLx+a2@y8>4b_YN&qT zT>{0SR(^+%G}}mjuu;M`oPe#p6{cf@qXG{t_9;7hV^e626pL|R(Wiee4f5o3_)&_QbThx>^q_voSAVf zj>8~$yoerv27c9S3FSJDME=N$Wk`D~C0{_dz+ zB)4+W@J5&0ic&Du?H{%@4pw;Cr1j?1J$#-w8=})Px*o6C3DpzAY3IZ@Qyp^7!s5wUGy3U0Ey3TO zI*F@?wJQO3MaFNgxk?WdDe{!Mp`OT>e@{(-ORUmN#;Pld0COd(;*9fj#29Xr)L1f* zginP}EL$=WR2j9*$R+eLSUU!50dX3>-!#WYH(NE+pW%3{^#OBL?!wZ3GTmDP;4Xil z{6Aymi>vz)-p}9SLZme?*a-eGV1+efW!u5&Is6xMgp{w8lHZ+8{$kgX;K-3AD~A@U zJKu4jbW|Pe+YOjM1;O$fJZ2`4_Cl7c;H)>~R6*5np2Hm$%<_%M>S-JQFK`H_V6;N{ zAdlGGvdV}^M$h6ej9tDc5&?$uPWV5?OJAp}Kjj!u)J7E_q7);?CQoiI=}Z0hJ|n;+ zTBX?7(L^n)k2&#?EzbreRB-L6{PAiBw537TC7QF!R@!WA!V&m`jea3pIuw?j7j)WW zPKT~BD_xr{muD76e3&M4(`9_*sMA3+K%P0m0r4r#N=YqM30%#mRC0I3w%$CiCzT19 zrvs7s3-_W%;CWYSS28@vwO#U1yUE);6Qr8!kC;r$rxL~qwI2t=YS(W4sQ1vAqd;g_ zSiVyR0#vRHvGl;iCV{^&j05$DRUb$nwA=^O2XQ9KwGDn(j#dmuLnOy4)>YxfUP zD_n@>SrglS?otDXZKJb15qvqtAOVEq&(a6K<5?E$S>!ZRi5?6Wc^j~${B1=VNrfCX zI%1Zth?@u6B9?%TsQ)!}MlOg|8*`hDOd^MI(0HiY2)W&Hno?>oZymCB6%7GDcyi12 zl(PPc$^9d}zr>`g7~{1vKG`*KJ#6{DYNK>wA{kLjyM{1XDaP5ZT2X~8Xv&S>PYvLU zoTN!MHLk||7#vG2!y!N>H={R*DQZ_6KE6Mvz#?~GpCt`xS_vXkn_7)Z57c`0`Co_r zbNCWO*bMfvXl0F`5TY1UL=~*t5THGiOWDyRyPRXtPQzrQ>PB0P&x0V#O_Mq03U)c0*^>I}T{lLCH4Ne#V?b5c zPRexPifT%eG4EETP1Q-u+Rc|<5!0$~{XxvXq)y6Gkgbj;!)2d?=q>8&64VMbJnN0z zC^~!1YFC`Ks4TXc7jbC4PL+b6zP}f@+||89XKR6q~Oiv+Mp%C%~_zAk>)1I8Kb#fUvE`dCB0uE)q2S1op}81PvpFB>aeP~9(BBqJ&$NnJb)OJ? zM(q96SoF(45}F6J&8#(n%{YLr^!?te&j@PSN@1hM=it-owLmDZ)QJtxUr+*HBiwJL z=aOGqeRtKUU--cBU8md=+3Ph{|4CB2*K^roER4tsZGv`sAGpJ7A5-SThkda#M`7g^ zU7>U9LXM5{gw7^~ZZ*IEuYJH8q5F!=8noTTqqC~tVy-oRPzLrcqNcjy%y)~vEGkxG z?9d?Nc?31@E`;lIVKI}sUcG^oAk7yhmj~R7blnePO_DPQAjKOxTp$(Haw0^uFVu;>XaG<>-q z;;pzSlidLXQ6PL}_~nb6TaUO(SFn?P9uGZFd%RzGGTxcfPkk;zX=x|P%I{fnA{I4f z+=+WhIDPi4;CdEF>Z4!AiQNozXNcW^Jg`x#oH6&$R|_E* z!7HJaA=*DP#+2NlhY6zngdEkE$f@alyaG^`5#K+3Xe4?qhl_4;^zkX#if@XL?y&SpERX|HE zt(o*-D<*kZ=yP}usB&RX!RQQb#$mwX74=!}P}v4v>rG+u=IacOI6W4I>?~~dkF0E$s3$5Nh+dq`RR+>F7ud>Qcp#2TE`WdW|<9;t(D^3AXs1rKl}U( z?7JSOORR5h^QZK>M0BH<;0N&jK4IkYHG$`(G--rr`f>XNI1CZ;3|kn0(=t-? z1`&hZuh!^TaAqra9$pN)NnFoP;D2&w9q*gA4p(A6`wMhrSGQCF`55icS4&HrO*&X< zi^eUXQKQ=wHob>YgsfWrUQ~aYHcrx^x9iLTkE@2aQyj~|=;#4wjSw-8IW0e+4kVC2 zfn61hydcvM&PdyDy(mMQ1YH)f8+r2G@%?U}V2ge@2sBCRprCzx8Z#-@@k*`P7Eq&( z|Ij(!A7D8B%oz-5{K8U;Dh7$vzpR2b5YWeK;_El}`&;8&r>z)?FCVqNp)< zc#A4xmL+l~3Pfg!*!3r_;*r@|G~A!UGuPwDk&nQI#Nh3=fT! zI6ve4d~D*ERiRp7tmB)V;rThBQ08*Ui?MfsYTZ!m!qs`|plD!QT;$gsCU_r@Miiy5 zOY;c(boF%+Tb7YChCA#!U=?@TxyXEe2c^>~nI1bAbLr3`*;$k4}D$W zt&vym+aQ;8G-+F>r{d&9)!$_VCeq-2Ao^+g2%m4?ONWLaZ|Y1X7A@KB!;N9PxQ9^9 zK?T`RvXzOc`_b(35Qm0!-rf8wn;(7Aj!y#8c2CF$-QQSV!>B@t3f@k4I2l$ z+=$iB31}mknZ8#U<2U6sQrg~cDWvFfCnd)EJ~--ObHBT@)-mFNb!S%(bGu5?93sD| zk(o{2ockXAgL6B`drMViaQ@kkugfCQgXrfLIGt+3(cQ8ZO?0D@-8QsKk3XgJD%{6L zbt3N(b=BwBkypdc`3;kKtQg@%>y3B8A341p1k^p_?Q5(}A_J z7TDbEIrPDcUFQejQ9Bu)v|zS?C4$#aKz9YF7Y^ia?S26IYJm6%1qM2=-xE}voO+Di z+X%zYxc<|saLlZYNZ#+g5VJ9xp=M^bQy)31ejZ9DMD4e7WXp3wG&H-_uc>30)ctIV zn-#PpR>HS@`Q5KSu_KFJR=V$%1s`ItPjWOAiTF`%1NL6U&Pss#$SBlZ+7BauDBYJv ze;b(mHX68w2xSOGR0^@v5NIx8h1G4BFWd2Y-|7DTB-kY!VVQVgeh@IiaO(&<0Q0L+`6Kwj`PKuiZV!P&=`-IR)&pS*9 zee1v)y=Z^)N)Wkm8c|Z)LoBMltt_?D)ifWkJP=*jXluUUe9-)pp%PKIfZn;_3(~#v<2v!Xa}}?`Qq9R0 zX^p<2=$Nqacr(X8KUzXlkEPfeLd^T1!9&&jVJs(iPf`%L%(RGi*$)%M2sdp{vhOtjrHEGnP?!l7TO zgi3=g>$v^}tg0Z8Uj*2kW1(GBNy#6{5+8mi!W;EnNtKT;GC}@jasawe;dM zVEMK}+J%+c^1>zu!GL8u{unMX-EmhR_LWp0GMz(z4?oMmM|1w_&pET83oe;o0rBc< z(HtdnHonX&`4NtvV;T|^cE4c<$!vU;0Wkj)XMceK8;d~|fdX}i?$%sAzR9G0mPMs+ zCb_vn0T|7D4CEPfULb5P9{tbY1LI^xra!2#zNGShRYP^Uqb9Yv0{UyviPN>8a9{tq ztxkE8njFWU26{KPM*opGW&Z0X)CI!RTgok5Kmm_f1&{A9Nt3N#)I0maFFUu! z`@b|@_uy_V<&XppQgmbfA2dFdUz*YNXSj)}64uig+0Yy5AHEebpvj8V0XsrP7&HS7f(-m6{ zDyFkBtON?{>vKqLoRmTE}=`ZzD0tcd6*L+^v~m% zoe!E8%xqu~{c271gOJ|&(`tI=bq^(%4nbBDWU_=eyDPm*&U~l7;X<&+3_WEBDhxP958D$4K~6LzpL-~V#g$feQm5usabpB$Lj%tq%HMQ+RU z@MJ5!N&t$X_pR?tu1Ke@ZNe8F@47UErO4jTXCOLf%Pu=#Y?@jQX z5}vqNF6puLk0M7uxdcQ&d+wmQ&X&fPmx|X8R942?yp^#_d#$p_1)ckPn%lS%wm@80 z{sROx+VOt&TwF~(nV>j}Dh9dh!Y-0G=Uf!?wvVCJZr^Y{h%7rsD=w)>a~wMhlCcm8 zz5Owk`n5KuKx!_q@K6wC7cJRicOvJYuY>tYmHRp&yNC2_+2*?=l6#rEB7#x9=OOQD zo=rnR&2>d|be>S1DNZGo+^5SfIgd9*MsnTHS>kg5%2DQ%Sc;_jY4CX=sduKq^FMsNwu zISHD~=zTh^C^`71Vt@TuWDh%D_C;Q$XJRU^F8G07i)LjIbn`fVA82}~tN8lCcjAWj zQXuo6&{j1a63APWKI@S)q1S)IkfZGlnr}64e~cvuy<$Kz1_{oYYY;)C^A%f=%Iz|hwd`$C z-Via;(Hj~>ineD?aX>56+H)!N#Biy(7Ttahbc%mpa1;?Qg!|1>Z!W? z(lsLrdw@=X`I_==^2iR;xV~Iv3+E4yE_)-O=0k>yM+|N@qHEZxi?E9IWN3LZG;ZrP z|Tp6k?$jwu4lXz$5=q+gGFPlnemHCBF z3I{~z7R)##N#P^iaCkSjWbhXy`YZv1;cNsgD4&r6XwA?#hCp5KV$U{RkP(|*30n2- z?gIf2qh?@J57NJ~3Y+j$6%6chNem2hHC~s;%>ptJ4u7Fjs0f63P{2JPekg|?J8@FrJ~hP4SWn|=SV0HXm>}YqR@qQj7YsNpL=#w50ez= zcsl^PK%0yA-L8Q#8#hfXLCtk&vKFg8u~T-idU=cP2MkP7_w4t)0v!EK$39ns9^bNqiDpGqrs^d`!W}_0-2CB7;5DjCsc~PA@&H(bT@5^F4zt z<=9a(&kOaiVFxLBnQ#f^wZSFJu~Sx5O1p%zeUaPXdN-<1BeQpuu;Mhe%O)R*x0#+S z-2Z6O=B{(RMt(*^$dU6-neQ#yy>_@70^|Rb52cgZV^73#LZ+{%kkZkv1 zsQ-9b5uJQPvFW70ZWmkZHH|)AD_M4L1xX2|@~~0D^7P{3C9*$k&A=1pyiI?^y)DhE zMb-;~ELT>r?wga@R%k49N#?s1WMmUq-q5Og&~U0dW4jb^YGm&I~q6Pg*EGq z(;WfEwPK(Ix&U`?VHM3|51l&+uC{&eRwJ@VQz1pXiy|c$it+xCjIl;yy*zKXwH*TZ zwT*B8n3(Tcbx(VBA4J%Qu8<9#Gp<`+^yS$xJ#kF#$LZ(;S@v~)V6qqh>#!{!Ys{_; zAD(8mY{ffD=?U7t-u84ldTjX}?lxFcZRzU9fS6hE-muM3->XaKr~w;@9<&rW9uhiO zpw?uQfgR$T@ZT^7gct^P{&0$OU8T*3 zU(0^QV9*c5gzC1K-d9^E#V_)2e);O}(2buXYy(4kZaJA~SuI}tXM~9KvSi+f}gdJ@US1T??=_fBJ<(U z@5ipo3Tf0Y}YMHlyb3`>EjdHX>7DFI&yGv)Jc* z`}03iFO@TLF_Tt4LuuwT@7FRntxrxj@1#NutywN6NwDuZ0;OsH1;G!Uk`L?eb76+g zj>l>@2V5k^J8aN{VTF=syxm5D1xg2MKs(yvwp}x2*s@E!x3fPA%FCVk8S(treSTU- z0{0XLe_6$dOca2G^AsG7zWCa&B=o!~Vu@8f&DX?bd!R26*3Kxw4v}CEPMLmxh7p!q zk)C@fRto_$IqV<4mT}Z0(`~#c39EDa?WyoABT32ubq_y!R=3s>r%>yYzc9|RuV%IB z!Zt3hacoa^hUA3!?U`YcB6AgM*Mcm^Q1AAyB~kq@tC^L2PqeyskPAw#8rSJC+8jnu z)O@SJx!O3PRX4r*Rp`!KQ*@XW+X%;&Dv+79s2yn$Ph)uor=r&-sgKt=LSs{N z_|h^_LP>7NiIE4V>qvw%JuoQdm6Cypm+x!u%hQ_a_a!~Z*g=-=-5n_{B&?G~jxTnX zBwg*X^*W{B#Qs(*G3CiHS4P+4tY z(H${kbiFL_5S*r6*oZ806b;CZ7{BimW^EliVQ_WP33ZQ471Ps6o!!G2|6DOr(k7jn zx6t>YNp?(%{sR`(!Jcaimn)@drdYB{ZCi1v`#;Lw-%S^ed$XfOE^1lf^^K!#K?2IE zE3csLkE?1y6|x{}ssC)l|1-BTQi(yxQYh#o7esMqJM7&mCoW_6f?Mu~VeYMD${*qj zHeaudHhi^W>UNJQbmhpe^lVfmZ{Y>dznL#WW%C+{+d2rGCTD&n$Po5@PzhQ zm84d=fvpka>q6-$&os~1YK!D~LwO@YTGX$T#%lXb!;%mWN%*&20%u6k>oQqD1G6i{ zmc6zridUH-e#6{^NLGjRYFt$-*ifia(Hal}#GN-OQD#KcA-W%VASytB8jxnWBoU!D zi!Y=|8sPFI)o2)d+bjidY7+|$;EZz7oocjD$<}D}KwkZ~8s=OEB}Vc+9wAL_Q_xf` zRMy9Qg`^c}o)8_Vu1i}AFcQe=hsc&+yaf#lX)rBP#jWJuLDk%r0VBoO5W85WGjA)` zHZ~XT$8U7K5xi4C>cev&K~`56m;*v}$NFA~nImRm6*ePR+0wY!SY{`kQ@nSciw7}{ z<-G8yY!oT~DB`1vqfc(IDzYMNR+$%xrY;11420s6tL9f|wcJH>t$gBfnH*lO8S zVqmo)g~}vYLz?5eH62^3NCFmF)0xi@`-Gl0$%YXYTM!h6p2w)Y{7O^uI92$DabwrMi*Pb zfFzNil1zTr?~lwZ++foF`mYCVb{MalV9ECxQc+rm?TJEm>Vy@#MWazW^ufED01o(7 zjnUxV>sl^m^s&NrO!R#(nme4+0n zo-snk<-n8L7-q~dtvRQoCS}?_KYX6O( zF2HUn$BK>82On@_jW1-uyE=!bAGh`(;4THQr<*vDPr2=F>C#jF0fwoSS%ss)k+$QG zTu@Q6rTa~Pigpv1fFw+VUl(P`ejZRC?`X~#f2Tl}LK{3pI5)!}x+0B!S|JgSPEK3L zu5qQlu*W=FTPZYQIWax9GeTNpp5p%*aq`o6GtdNzq`1BJxXnZ#P+zQC7Pei^a$8rV z8$T270)@HnKElaZT6^&cj4@_KbMeV(eX;d@UgiaRWV?aL0kyq0F9pG#+w|~gAprwz zL0){EdPF?5`eQWq$%jsnRrh?*q9x|dd^}G^G05)^qOrVdTb1m8G#4|=kM zc}n!_e0(uUwj~qCIIZH~V@vd12+8=Hr$gGS`CKewhd`bEp^4a4n|E-xA$vuz?b!}* z!O+r7e5ewkyN4)uUZzVdy=zjJP|4y)Xd%y3mmbE6Qnuh*u9TxoPApe({)Tz&n3aVz zvOxG2752x`4o_@xyw04i6li(3_3)c7bY`=I-v)iLCjV$ z;TnhHoY!c8lW-lWu(`z!k+&h`yxBuu(*Z`C;6!w>-*C-x=Yj-p`Kl^4kZp)Fp5AE@ z2Nwod9ELG_5gxn^~%(r~PS(^+zbTm|q{pK6BmOAFtzT@@nY=80e!AlNhw zEAv<7p+{#Bj@U2{?zt1xOgiR_QkcNPb}WI?;{##k+-5ba(cMV$4k_`a9py4JT}CRn zgpw#hOb;_-R+;ct=h_RIxCdy*E%o{U|oR;bA$=2K_I>bQ{C7^nX&sx}$`+ zE88bDRYRF=3eQnQF5R-c`+N~bTeKA~pS-|Ac&G$kFbpX)XunI7d>r=hy4vav*YJjT z?fV5&!g~j?-dP|R^0Nke7LBN*$h+ihEe#;)z#K!2r3Kba=5L_uePo0uu`-g#w-WSm zMcR?Y?OtHV($j4R%C@`AKULDsTmpJ*gCVj!S5qssBBlW#?QI0KWaOJsc~fd@5>U0T_bipG?ERvS#L@ZxoV6bml=d-o5NtZGN*1*-e{;1bRB_kH6z zMvhgu1oGm1tC66sYUKSKkm(n~y`bH@RU6Pes?C*y7lJ^z!3Iqk%!0J0EwpRyEwV=i z8@wsbL6us&S7ni{wQi=MCDkp{a`j&_+eMz{N^;6pPoK01=h{1F?y!?5?b4?uO3H!* zvz(Ym+7dwP4q?BERKk7UMa$aV0Xm*Tw%KqB+(9L;4UIYPGaFJG`4SIF?mUzHJxX2O z31YmddsVy?M&zlxKsSoc0imw}0v{%ZzY6EnoK@D|b2@CTUR{1@|902bXn3{1*gn=%%F+_IIxqVNdtUF+ zHFkl2!_+-88gL5k08R!fM^*BQ`Ovg8p{$yoAM2n)$4$VgP=gpMxm? zNpXt>fp7L3;$xg*2Z&aKYHEQ<@_#+Y9+6Om=8aVI9OzT>>5O35@?NS)w0z5jzXMuz zXIjfa^bm1e>>m4hqh=UU$X}owxf*1xXgYu6H#+>G&3e0XUaZv%^E_Q32`E8bE00^5 zj@~&!yQe~PhwQ$A&OODe#?-)SXO(k~34mm93h{b!w{<4GcIuh}#YiS!VjF z_3p{pu_+1>WUwS*WMiocv)siH`Mn*=?6e3Np6O!CKWH2piy%~R%)9FQd zSaj?w#ZMs%Ml?4}ZU_<5h%-4qp0~(J4b_@tI1SqUPk*mDgd2>610t4l<5=eeM^ zjKAv!&CsMR+~m?xxr0hBv?`;gzh^OMb6D(Ev#mnWFg#o1VB;BFd}J6BmkcFRKQQbD zt3g_Bp{6**FBKhbOs~_$%0by!jMJwkm!zqfqSNKtjs(&XgA71x3txRDbJK$<@D=;PJ$(3OoD|n9A9`SO| zs~ZcVMHVDeKXHKZHYr2 zkBNiWT3OzGWU!|Z)(&{0EiO?0iuc1zjUcJ-RUAn(?t7xBdCpH&^*mf5PC>mMi_33% zY4;Zgh;T5K-glpTBNR+j^@Cn*ZJ1n&ul27%Waf4I<*G|T({9}tCMUqtqVG>QDV+Z7 zIM=~?|By|$`GZ}st>Kx=n)pM&LaD;9$t15u9pO~F^M6;f7$QYYoZ7_eFb>TL#+L)%O zsl9n)U`S|DWs11`s~;5=tFhDHcb`!XMnYek4v*Oj2%nk~Oii?tZSAepmJX|pdO=Gz z7p2)w7sLtiBVS4>V+VYM$W~eSvVbY$?NhBwB%l}tDMyO$tqnuZperP}OOtH9(u+RL zPsLb`f7}h&LQJnr54}5cWKFgz*yy)IeQOKh&{!Nbc0H`hL?%tw-dSWYT!{Qg$_);D zkNTw6rF0!}=RRh?T0>*W@KxR;FIcM~nZs(|V(QA<2H&u{oDwvMs! z`Xex<%zVi?#~V<7z(3G6eaDM8aYZgI&s@Z{2lm#vdYarce}TUsZFc_NLx&VqVc%RX zo&&#!I}bvs3b=_1X%3<+o|>_N(H0Vg>Br@)b zmk$>@R*eT^Ti!ZBzSt*oyce$(FKK=lujbhs(nW~U<;QtkIQ0xlFbZ}(4UPx3&Ls)P zI_+$X*r6)a$5EV7wUe=+%}S*zZe8Rqsl7<=sKIt`mBka8rm*M{IciL)euJ7t({|}& zzEvfal5d7L?CeG&r&rEt;~LUZvmy*Bi-y=(IqmPq8_j;R)00>2shM8!qx(V%n}?UL zbQ`PGv)I)3ks4+_>7(rXq2C^E?M%hsjCa{NI&g(mmb44ey;Z}>vEJhWsWTE!t~+RF z^nC>Gm?T}&f%1kc73oj>@Ex(o%ZqmFZ?TQ+iSIMEr0Z~?4p)gI87iIrypK^F9o_0C zw9?KAii&Qvz{*(qxi2}M@#CySLL)u2_E7@u?K3?~qE_h{xhET6NRktQG8hb8J zfEpwY8mFx>i!%f`B&KtG)6ZCGIi}8vAgX*d{rP^ke%rkAVo-(z7wL#5RAU=x_;9}D z3+7m7(YP0*e%3#TfEj4BdGxtU9bpC$Gh?2fT@{$%#30x zaT8TfbfMFlTR!{j^*$A@^G$IM1bLqaCJB$En7~2H;%IV&H*G1h|2%>E_z;L3R&yMK z@_sA-B;4EC;ZW*O@{K-ca}qH*;@Wumy5_g_n82RfPKkNht^uz%Mzr4>qJR+oDu{1g zQOYZ+*vhW zP5(;PPg@ce^|qs~%|+*Fj&8^}Y4_(8`%Z7{sb;nmPz^N?475)=`P`WV)zz+rXAocq z7ROlj{!oD)UkNlEm7YoGXz+?xIR#EEsnO2Hkp!iBrlN_u8TBm++h(?lP;}Zj&0#xZ z#6m<;lU}EKbSi4=%u}});R2*_!*Xu5#SsJ9={teM2O|^drR)B$^=@lubl;-7`O#u7 z8afXp77KJt20gDN72sf$t9!7t#FH=?X4va!pv^c3rMvp;z8z)Vket2^aRQsX#}T}r zC0s75-Nb3a?=ao30_A1*M0OFnWppv+JZgOXzTV1lA&8uYEWe@`VaKjNz5fMmOHOuk z>4m~=cKQr=R&W9;Mhn*3KJXy1rhoxo>$y*k*K67OTc)M4q$OWKYFlcOY+_Kkxx^q< zsm^|wmcwVYFjID7Q&DowFT2^%JtsgV?K{|^@7LCce{_*MbRmXXvKa%!z7^$8aEl(` z5mj+XdGYN8FA?zgh-o^IVX~r_b;>ZDu#f3tmh9CGj1;az*h}x|mMeW}!4Y9xSpqdU zBZfv){%R35ggCn6=crxldqY*cI4Q3^6bwm-p~v&_)9x(ht9YwD>gI<5=8gP3izY{_ zljGTeF>6+yUul}XP+2!#ce@j?Y)C}$qfB;-BH|+`KwR*d=MJn@ii||IcExJ;V^xNg zCM}7-IG4&RaI}8EvaZPoc7shCIQ5^(ox+-xE$_dji0D`CJn7G|9U7n zUcsfK(MUd4kS1?<|4o-iDa>A$35<6#={Mh`-Am|4P^=&mq1M#`v5UI~>t%o64|1`_ zB-aFjOv)LT?h`fNseL1$BR0P%-6;96iqx}vjbYBj%-XLFI{0p_EwsGw*F#159nWV9 zRcDcRNlv-h2>vEa|Dn#dpPX85l{})wfHLknvm5CWv4NLXZ|YH+>R5x|l6j+|19bh| zz`5w~+qEcRUAG_O<5T(lb%d~3Hu-%079UDy7Uvc=q1AYp(SE8?2Hr#s=C+32MTtY= zgNdoQUS&VOF)3}4^@nYS0S#tpiUsmliro3K&8 zYrow^&6^}e=_O5}Tubs#zxZ{}LbX*t{6i0ywOVApbad^ac^A=v*?x^CQdfTQ*t}s& z+065JUw+8e>~1GWli=6wF1kh1!QC5tnb~h~f;v6l2_m`n*pr)#xKu8MZLiELP$ZFp z+6H!3F__vsQ%>^>cNZRI<@Q{%Z(d9o@0FeG5w3gxGpEA)tCIdfE&|&7=A2CW@w|Er zhF7wmwF*%DGx5DLEY-Ir>A$3$P_36tpUahS{sFm-^%=3X_P?I(Ig&?syzl z8_A|Eq2|5qj=SAR%~;n4WeuO*>ay|S1+$AwhEdYPr!@&>^(w7$H~v|8Pu#F2BWWvA zooY(|QqxE6{oxs7DIvrAvBR#oT?I35@ z5evodCkZ}zH%4JOqx9hn;A-NPOyScdo6Efq<41So8)&LDI5H~O(t1X9rVT<~&c7lhFg@oJZ_g-stz{98pWgr6 zO#>coY^s6g5Wwc$Uj2G_AA`*Y#kgwcPCa#Y3la?{nThvwMK1Zwll2~`TA^a5J382+ zAGQ+giDMJ$E1JD`Yb&xLu4qF-&mz4lc=%KN zUDS@?zpnWNP}Ope&(tz)QDCz`)l|5>JBE z}= z%{$EJP#m{0i3MrK?E~Fs)w?=^>U%Vz9%bwLg?;73npQ#^GXy#^0mB=XGuG0P$6!H}8s{OjrCl3(PT*@H;ka|3(jIFI-S z(ZqprreswII2LKZKf(qF*CKi>-2G_5y31j9v1WQ*j}h;i$`(5+3-hXW-@-_Bi+OaL zTUt+-KbC}8SJuf(HTR8MX`Dn%lJ@d%R?GY9Y5Ht*BRYK4C#h^pUu+U^by3u0R1u_2anLd8HI_h}(Ijm#p+qvtLKoH-?a$mp7 z{)8T?qUWjCfIIGDhO>Z|;LF&M&)T(^g#pTv zvdj_=*gSZ5sFYvo-*6pV%98l1vdJ>cPYGDKkC$n}=1qh%bs(osWs^`hd%KyQI`XX^ zM7nt7Y4k|Ow07->E{rdhMKw`F$)a>K+bb(tu%VSu@=mbD)Oykln3-tEn8Iw=-2vOQ z!^Mu>YQu$&uU7GBDQNxN6uy*uW+koop>po7)~FEG>F&^p%~%?Z=sEbt_)pzf=1dQkEl3}D z8`W$YUCMIE8~F-|Zl!s@QC>Ow3>PSXjp{xf33gi6B>#mltj(lRXGAuP+^s*~^voE66eK57<4Z z!Mo+e&OM&rb-a1eswc%4do61f$pGvAZ62j1?iC#J6 zWn<8Y?Zpl)at^e`cXHb28Oi;Ux7eI)^x~T^tGR*Gq-yB zc6K8Y1=T5m1<%7HyfO!c<3NIf>Zd||C|E3wCSOEyA}EGoJlQnk%h@x)74(W=^f;ZF zrLO>!As-Ilt)A1yHRC5|x4q+hL96kVF2A&Zem_2SnR9ht)*(<|Mg1#*#Z#b(Tc%joPA3x=>e%WlCKIS$fRhN{Zk9FKS1DS+> z?IXUaxfHAkQ0ut=Bl0INOmzBf+*%CpH$Swj{@B_Z?zOZ*{b_CQl#3dSg~hIzlD@20 zR_;t=Z#DWP(jjBfP-Hq{yVpTu%l5_j61HCxI-HYIl4KJ}-&4RZYvD}|HphAvd}kz5 z>y;n$-X%BV9R?IC!k_By)F?rpPw!aZNm!|9EG-bnzU9sAze3HeUr7tz zP``R<6O)?rc~|JjU*qqtGKd;-gLij zgS(jc^6yWm%Wm6;9@p&4wUc{%pE8rqyC|Lf175oRfj`xUD}TWL|Nj5K2M;{gLfVzV zP^_1;&w)c$_M&^9lnDh6v^g4}z|6zvZ#YFU^;fWbY2g`ISXj5o7qEdpl0Rt&3oBX- zAV#cb=5Ij%@2CIkuyJvXj%N|v+`yO(91cBPN*iu&0=Ez_gINH7u(&z7_}Mt0vT^ch yaB&N8^9wxTW#!});N-N+59Ii5fP + * The complete hierarchy is mapped into a single table to make data access more + * efficient, avoiding the inner join between tables. + */ +@Entity +@Inheritance(strategy = InheritanceType.SINGLE_TABLE) +@DiscriminatorColumn(name="spec_type") +@Table(name="specifications") +public abstract class AbstractSpec extends BaseEntity { + + /** + * Car's model name + */ + @NotNull + @Column(length = 125) + private String name; + + /** + * Year of putting into production + */ + @Column(name = "yearFrom") + private Integer from; + + /** + * Year of stopping production + */ + @Column(name = "yearTo") + private Integer to; + + @ManyToOne(cascade = CascadeType.ALL) + private Engine engine; + + @ManyToOne(cascade = CascadeType.ALL) + private Wheel wheel; + + protected AbstractSpec() { + } + + public AbstractSpec(String name, Integer from, Integer to, Engine engine, Wheel wheel) { + this.name = name; + this.from = from; + this.to = to; + this.engine = engine; + this.wheel = wheel; + } + + + public String getName() { + return name; + } + + public Integer getFrom() { + return from; + } + + public Integer getTo() { + return to; + } + + public Engine getEngine() { + return engine; + } + + public Wheel getWheel() { + return wheel; + } + + public void setName(String name) { + this.name = name; + } + + public void setFrom(Integer from) { + this.from = from; + } + + public void setTo(Integer to) { + this.to = to; + } + + public void setEngine(Engine engine) { + this.engine = engine; + } + + public void setWheel(Wheel wheel) { + this.wheel = wheel; + } + +} diff --git a/cars/src/main/java/com/mooveit/cars/domain/BaseEntity.java b/cars/src/main/java/com/mooveit/cars/domain/BaseEntity.java new file mode 100644 index 0000000..8d077ec --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/domain/BaseEntity.java @@ -0,0 +1,28 @@ +package com.mooveit.cars.domain; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +/** + * Base entity with all common state and behavior of the entity models. + */ + +@MappedSuperclass +public abstract class BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + +} diff --git a/cars/src/main/java/com/mooveit/cars/domain/Brand.java b/cars/src/main/java/com/mooveit/cars/domain/Brand.java new file mode 100644 index 0000000..5d2c7c1 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/domain/Brand.java @@ -0,0 +1,57 @@ +package com.mooveit.cars.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; + +/** + * Car's brand model. Root entity that model the complete and updated catalog + * for a specific car Brand. + */ +@Entity +@Table(name = "brand") +public class Brand extends BaseEntity { + + @NotNull + private String name; + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "brand") + private List specifications; + + protected Brand() { + super(); + }; + + public Brand(String name) { + super(); + this.name = name; + this.specifications = new ArrayList(); + } + + public void addSpecification(Specification spec) { + this.specifications.add(spec); + } + + public Stream getSpecifications() { + return specifications.stream(); + } + + protected void setSpecifications(List specifications) { + this.specifications = specifications; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/cars/src/main/java/com/mooveit/cars/domain/Engine.java b/cars/src/main/java/com/mooveit/cars/domain/Engine.java new file mode 100644 index 0000000..bf1b2a8 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/domain/Engine.java @@ -0,0 +1,59 @@ +package com.mooveit.cars.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; + +/** + * The entity to model the car's engines for each {@link AbstractSpec}. + *
+ * Engines with the same type and power will be unique. +*/ +@Entity +@Table(name = "engine") +public class Engine extends BaseEntity { + + /** + * Engine's power + */ + @NotNull + private Integer power; + + /** + * Engine's type + */ + @Enumerated(EnumType.STRING) + @Column(length = 8) + @NotNull + private EngineType type; + + protected Engine() { + super(); + } + + public Engine(Integer power, EngineType type) { + super(); + this.power = power; + this.type = type; + } + + public Integer getPower() { + return power; + } + + public EngineType getType() { + return type; + } + + public void setPower(Integer power) { + this.power = power; + } + + public void setType(EngineType type) { + this.type = type; + } + +} diff --git a/cars/src/main/java/com/mooveit/cars/domain/EngineType.java b/cars/src/main/java/com/mooveit/cars/domain/EngineType.java new file mode 100644 index 0000000..0148e8c --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/domain/EngineType.java @@ -0,0 +1,7 @@ +package com.mooveit.cars.domain; + +public enum EngineType { + HYBRID, + GAS, + ELECTRIC +} diff --git a/cars/src/main/java/com/mooveit/cars/domain/Ingestion.java b/cars/src/main/java/com/mooveit/cars/domain/Ingestion.java new file mode 100644 index 0000000..ea2a36f --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/domain/Ingestion.java @@ -0,0 +1,87 @@ +package com.mooveit.cars.domain; + +import java.util.Date; + +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; + +/** + * Register every ingestion made for a {@link Brand} from a specific `source`. + *
+ * Sources could be URIs, Paths, etc. + */ +@Entity +@Table(name = "ingestion") +public class Ingestion extends BaseEntity { + + @NotNull + @ManyToOne + private Brand brand; + + @NotNull + private Date date; + + @NotNull + private String source; + + private Long totalSpecs; + + private Long newSpecs; + + public Ingestion() { + super(); + } + + public Ingestion(Brand brand, Date date, String source, Long totalSpecs, Long newSpecs) { + super(); + this.brand = brand; + this.date = date; + this.source = source; + this.totalSpecs = totalSpecs; + this.newSpecs = newSpecs; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public Long getTotalSpecs() { + return totalSpecs; + } + + public void setTotalSpecs(Long totalSpecs) { + this.totalSpecs = totalSpecs; + } + + public Long getNewSpecs() { + return newSpecs; + } + + public void setNewSpecs(Long newSpecs) { + this.newSpecs = newSpecs; + } + + public Brand getBrand() { + return brand; + } + + public void setBrand(Brand brand) { + this.brand = brand; + } + + +} diff --git a/cars/src/main/java/com/mooveit/cars/domain/Modification.java b/cars/src/main/java/com/mooveit/cars/domain/Modification.java new file mode 100644 index 0000000..9ab7e2b --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/domain/Modification.java @@ -0,0 +1,39 @@ +package com.mooveit.cars.domain; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.validation.constraints.NotNull; + +/** + * Entity to model specific variants for a car {@link Specification}, as for example + * different {@link Engine} or {@link Wheel}.  + */ +@Entity +@DiscriminatorValue("modification") +public class Modification extends AbstractSpec { + + /** + * Specification car line + */ + @NotNull + private String line; + + protected Modification() { + super(); + } + + public Modification(@NotNull String name, @NotNull Integer from, Integer to, String line, Engine engine, + Wheel wheel) { + super(name, from, to, engine, wheel); + this.line = line; + } + + public String getLine() { + return line; + } + + public void setLine(String line) { + this.line = line; + } + +} diff --git a/cars/src/main/java/com/mooveit/cars/domain/Specification.java b/cars/src/main/java/com/mooveit/cars/domain/Specification.java new file mode 100644 index 0000000..c44bf15 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/domain/Specification.java @@ -0,0 +1,88 @@ +package com.mooveit.cars.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import javax.persistence.CascadeType; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.validation.constraints.NotNull; + +/** + * Base setup for a car model. + * + */ +@Entity +@DiscriminatorValue("specification") +public class Specification extends AbstractSpec { + + /** + * Cars model type + */ + @NotNull + private String type; + + /** + * Car model brand + */ + @ManyToOne + @NotNull + private Brand brand; + + /** + * List of modifications per each car model + */ + @OneToMany(cascade = CascadeType.ALL) + private List modifications; + + protected Specification() { + super(); + } + + public Specification(Brand brand, String name, Integer from, Integer to, String type, Engine engine, Wheel wheel) { + super(name, from, to, engine, wheel); + this.type = type; + this.modifications = new ArrayList(); + this.brand = brand; + } + + public void addModification(Modification modification) { + this.modifications.add(modification); + } + + public Modification getModification(int index) { + return this.modifications.get(index); + } + + public Stream getModifications() { + return this.modifications.stream(); + } + + public boolean hasModifications() { + return !this.modifications.isEmpty(); + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Brand getBrand() { + return brand; + } + + public void setBrand(Brand brand) { + this.brand = brand; + } + + protected void setModifications(List modifications) { + this.modifications = modifications; + } + +} diff --git a/cars/src/main/java/com/mooveit/cars/domain/Wheel.java b/cars/src/main/java/com/mooveit/cars/domain/Wheel.java new file mode 100644 index 0000000..931c162 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/domain/Wheel.java @@ -0,0 +1,48 @@ +package com.mooveit.cars.domain; + +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; + +/** + * The entity to model a car's wheels for each {@link AbstractSpec}. + *
+ * Wheels with the same `size` and `type` will be unique. +*/ +@Entity +@Table(name = "wheel") +public class Wheel extends BaseEntity { + + @NotNull + private String size; + + @NotNull + private String type; + + protected Wheel() { + super(); + } + + public Wheel(String size, String type) { + super(); + this.size = size; + this.type = type; + } + + public String getSize() { + return size; + } + + public String getType() { + return type; + } + + public void setSize(String size) { + this.size = size; + } + + public void setType(String type) { + this.type = type; + } + +} From b96f9c385ebe114c72e8cf04f66c9f3ba4a281a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20J=2E=20Garc=C3=ADa?= Date: Mon, 12 Aug 2019 18:01:36 -0300 Subject: [PATCH 2/4] B All the JPA implementation, catalog generation and ingestion strategy --- ANSWERS.md | 67 ++++- ClassDiagram.png | Bin 0 -> 108741 bytes .../mooveit/cars/domain/Specification.java | 17 +- .../mooveit/cars/ingestion/BrandBuilder.java | 29 ++ .../cars/ingestion/FordBrandBuilder.java | 260 ++++++++++++++++++ .../cars/ingestion/IngestStrategy.java | 13 + .../cars/ingestion/IngestionException.java | 15 + .../ingestion/MergeBrandsIngestStrategy.java | 53 ++++ .../cars/repositories/BrandRepository.java | 15 + .../cars/repositories/EngineRepository.java | 20 ++ .../cars/repositories/IngestionDTO.java | 6 + .../repositories/IngestionRepository.java | 19 ++ .../repositories/SpecificationRepository.java | 20 ++ .../cars/repositories/WheelRepository.java | 17 ++ .../cars/tasks/BuildersConfigurations.java | 23 ++ .../mooveit/cars/tasks/FordIngesterTask.java | 15 - .../com/mooveit/cars/tasks/IngesterTask.java | 93 +++++++ .../cars/ingestion/FordBrandBuilderTest.java | 75 +++++ .../cars/repositories/AllRepositoryTests.java | 14 + .../repositories/EngineRepositoryTest.java | 53 ++++ .../repositories/IngestionRepositoryTest.java | 66 +++++ .../SpecificationRepositoryTest.java | 92 +++++++ .../repositories/WheelRepositoryTest.java | 52 ++++ .../mooveit/cars/tasks/IngesterTaskTest.java | 95 +++++++ cars/src/test/resources/ford-test-example.xml | 59 ++++ 25 files changed, 1159 insertions(+), 29 deletions(-) create mode 100644 ClassDiagram.png create mode 100644 cars/src/main/java/com/mooveit/cars/ingestion/BrandBuilder.java create mode 100644 cars/src/main/java/com/mooveit/cars/ingestion/FordBrandBuilder.java create mode 100644 cars/src/main/java/com/mooveit/cars/ingestion/IngestStrategy.java create mode 100644 cars/src/main/java/com/mooveit/cars/ingestion/IngestionException.java create mode 100644 cars/src/main/java/com/mooveit/cars/ingestion/MergeBrandsIngestStrategy.java create mode 100644 cars/src/main/java/com/mooveit/cars/repositories/BrandRepository.java create mode 100644 cars/src/main/java/com/mooveit/cars/repositories/EngineRepository.java create mode 100644 cars/src/main/java/com/mooveit/cars/repositories/IngestionDTO.java create mode 100644 cars/src/main/java/com/mooveit/cars/repositories/IngestionRepository.java create mode 100644 cars/src/main/java/com/mooveit/cars/repositories/SpecificationRepository.java create mode 100644 cars/src/main/java/com/mooveit/cars/repositories/WheelRepository.java create mode 100644 cars/src/main/java/com/mooveit/cars/tasks/BuildersConfigurations.java delete mode 100644 cars/src/main/java/com/mooveit/cars/tasks/FordIngesterTask.java create mode 100644 cars/src/main/java/com/mooveit/cars/tasks/IngesterTask.java create mode 100644 cars/src/test/java/com/mooveit/cars/ingestion/FordBrandBuilderTest.java create mode 100644 cars/src/test/java/com/mooveit/cars/repositories/AllRepositoryTests.java create mode 100644 cars/src/test/java/com/mooveit/cars/repositories/EngineRepositoryTest.java create mode 100644 cars/src/test/java/com/mooveit/cars/repositories/IngestionRepositoryTest.java create mode 100644 cars/src/test/java/com/mooveit/cars/repositories/SpecificationRepositoryTest.java create mode 100644 cars/src/test/java/com/mooveit/cars/repositories/WheelRepositoryTest.java create mode 100644 cars/src/test/java/com/mooveit/cars/tasks/IngesterTaskTest.java create mode 100644 cars/src/test/resources/ford-test-example.xml diff --git a/ANSWERS.md b/ANSWERS.md index cda9811..803a320 100644 --- a/ANSWERS.md +++ b/ANSWERS.md @@ -9,13 +9,13 @@ Below a short explanation about the decision taken regarding domain model design ### Brand The service will support model specifications from several brands/factories and each of these brands will have a -unique list of car specifications that they sell. +unique list of car specifications that they sell. Each `Brand` will contain a `name` and their list of `Specification`s. ### Specification and Modification Based on the car models structure and data provided into the sample file, I've identified a hierarchy of entities based -on commons attributes as `name`, `Engine` and `Wheel`. +on commons attributes as `name`, `Engine` and `Wheel`. An `Specification`s represent the base setup for a set of `Modifications`s that define small variants from the original spec. @@ -28,19 +28,74 @@ original spec. Based on the provided sample data the same `Engine` information could be used on several `Specifications`, so I've decided to create just one engine per _power:type_ tuple. This means that many `Specification` instances could be pointed to -the same `Engine` if `power` and `type` attributes are equals. +the same `Engine` if `power` and `type` attributes are equals. -Also, I've modeled `EngineType` as an _Enum_. The pros are that this simplifies the string typos and keeps integrity in the domain model. The cons are that every time that a new engine type appears, a new version of the service must be deployed. An improvement is to transform this _Enum_ in a _Value Object_ list stored into the DataBase. +Also, I've modeled `EngineType` as an _Enum_. The pros are that this simplifies the string typos and keeps integrity in +the domain model. The cons are that every time that a new engine type appears, a new version of the service must be +deployed. An improvement is to transform this _Enum_ in a _Value Object_ list stored into the DataBase. ### Wheel I've taken similar decisions for `Wheel` entities. They are unique id _size:type_ tuple is equaled. -In this case, I keep the type as a `String` +In this case, I keep the type as a `String` + +### Ingestion +This entity is used to maintain a history of each `Brand`'s ingestion, registering the source, the ingestion date, +and the `Specifications` processed and added during the process. +This also allows checking the duplicated ingestion of the same source. + ## B - Ingest the data +The ingestion process is designed and implemented to accomplish with the next definitions: + +* Factories will only send catalogs for a single Brand +* Factories will send monthly files that should be ingested +* The same file should not be ingested twice but two different files from the same factory could contain the same + car model +* If the model was not previously ingested, this will be created based on the data source +* If the model was previously ingested, will be replaced with the new model specification +* The same strategy will be applied to all the different brands and the data source provided + +![Class Diagram](ClassDiagram.png) + +### Entities and Repositories - JPA + +All the _Domain Model_'s entities were annotated using JPA and the corresponding _Repository_ was implemented to keep +this complete model persistent. + +### Brand Builder +_Factory Method_ that creates `Specification` for each car model providedfor a single `Brand`, parsing and adapting +specific data sources (e.g. XML files, JSON files, web services, etc). + +A `FordBrandBuilder` was implemented to parse XML files provided from Ford brand and create the +corresponding cars `Specification`. + +A new `BrandBuilder` instance could be added into the `BuildersConfigurations` and the generated `Brand` will be +ingested using the same `IngestStrategy` for all of them. + +### Ingest Strategy +`IngestStrategy` implement the sstrategy to ingest new or existant Brands and Specifications. + +A `MergeBrandsIngestStrategy` was implemented to follow this definition: +* If the model's `Specification` was not previously ingested, this will be created based on the data source +* If the model's `Specification` was previously ingested, will be replaced with the new model specification ## C - Expose data with a RESTful API +I will take advantage of the `Spring Data Web Support` to expose all the Repositories implemented to accomplish the next requirements: + +* Get a car specification by id +```CURL``` +http://localhost:8080/specifications/{id} + +* Get all the car specifications by brand +```CURL``` +http://localhost:8080/specifications/search/findByBrandName?brand=Ford + ## D - Adding images -## E - Improvements \ No newline at end of file +## E - Improvements + +* Spring Profiles (`dev` and `prod` required) +* Security +* diff --git a/ClassDiagram.png b/ClassDiagram.png new file mode 100644 index 0000000000000000000000000000000000000000..89a846d87f4b02f7acc596cf0a80c2c0f12a2a2a GIT binary patch literal 108741 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 bxO_40-wz-`Z+0yX&^UG6?HK9ExoiIb;ga)x literal 0 HcmV?d00001 diff --git a/cars/src/main/java/com/mooveit/cars/domain/Specification.java b/cars/src/main/java/com/mooveit/cars/domain/Specification.java index c44bf15..13a68f6 100644 --- a/cars/src/main/java/com/mooveit/cars/domain/Specification.java +++ b/cars/src/main/java/com/mooveit/cars/domain/Specification.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.stream.Stream; import javax.persistence.CascadeType; import javax.persistence.DiscriminatorValue; @@ -57,13 +56,18 @@ public Modification getModification(int index) { return this.modifications.get(index); } - public Stream getModifications() { - return this.modifications.stream(); - } - public boolean hasModifications() { return !this.modifications.isEmpty(); } + + + public List getModifications() { + return modifications; + } + + public void setModifications(List modifications) { + this.modifications = modifications; + } public String getType() { return type; @@ -81,8 +85,5 @@ public void setBrand(Brand brand) { this.brand = brand; } - protected void setModifications(List modifications) { - this.modifications = modifications; - } } diff --git a/cars/src/main/java/com/mooveit/cars/ingestion/BrandBuilder.java b/cars/src/main/java/com/mooveit/cars/ingestion/BrandBuilder.java new file mode 100644 index 0000000..2fa86b7 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/ingestion/BrandBuilder.java @@ -0,0 +1,29 @@ +package com.mooveit.cars.ingestion; + +import java.util.Map; +import java.util.Set; + +import com.mooveit.cars.domain.Brand; + +public interface BrandBuilder { + + /** + * Factory method which should build a list of {@link Brand} based on + * specific data sources. + * + * @param sourceToOmit + * List of data sources that should be omitted during the Brand's + * build process. + * @return Map with a data source as key and the build {@link Brand} as + * value + */ + Map createBrands(Set sourceToOmit); + + + /** + * Return the name of the {@link Brand} that will be constructed + * @return String with the name of {@link Brand} + */ + String getBrandName(); + +} diff --git a/cars/src/main/java/com/mooveit/cars/ingestion/FordBrandBuilder.java b/cars/src/main/java/com/mooveit/cars/ingestion/FordBrandBuilder.java new file mode 100644 index 0000000..b7d9ad2 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/ingestion/FordBrandBuilder.java @@ -0,0 +1,260 @@ +package com.mooveit.cars.ingestion; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.mooveit.cars.domain.Brand; +import com.mooveit.cars.domain.Engine; +import com.mooveit.cars.domain.EngineType; +import com.mooveit.cars.domain.Modification; +import com.mooveit.cars.domain.Specification; +import com.mooveit.cars.domain.Wheel; + +/** + * Ford brand builder builds a specific {@link Brand} per each file found on the + * base folder. + * + * Sources are used to filter these files based on certain criteria, as for + * examples old, temporary or already processed files. + * + * The .xml format follow the next structure : + * + *
+ *{@code
+ * 
+ *    
+ *        
+ *        
+ *        
+ *            
+ *                
+ *                
+ *            
+ *            
+ *                
+ *                
+ *            
+ *        
+ *    
+ *    ...
+ *
+ *}
+ * 
+ */ +public class FordBrandBuilder implements BrandBuilder { + + private static final Logger log = LoggerFactory.getLogger(FordBrandBuilder.class); + + /** + * Folder to scan + */ + private String scanDirectory = "."; + + /** + * Scan folder on classloader or file system + */ + private boolean scanClassloader = true; + + public FordBrandBuilder() { + super(); + } + + public FordBrandBuilder(String scanDirectory, boolean scanClassloader) { + super(); + this.scanDirectory = scanDirectory; + this.scanClassloader = scanClassloader; + } + + /** + * Brand name + */ + @Override + public String getBrandName() { + return "Ford"; + } + + /** + * Build a {@link Brand} per each `.xml` file found in the specific + * `fordFilesDir` folder. + */ + @Override + public Map createBrands(Set sourceToOmit) { + + Map brands = new HashMap(); + try { + + List files = this.getSourceXMLFiles(sourceToOmit); + for (File file : files) { + InputStream is = new FileInputStream(file); + brands.put(file.toString(), this.createBrandFromXml(is)); + } + + } catch (Exception e) { + String errorMessage = String.format( + "There was a problem gathering files from directory %s with pattern ford-*.xml", + this.scanDirectory); + log.error(errorMessage); + throw new IngestionException(errorMessage, e); + } + + return brands; + + } + + protected List getSourceXMLFiles(Set sourceToOmit) { + + String filePattern = String.format("^ford-(.+).xml$", File.separator); + try { + // Scan directory + Path basePath = null; + if (this.scanClassloader) { + basePath = Paths.get(getClass().getClassLoader().getResource(this.scanDirectory).toURI()); + } else { + basePath = Paths.get(this.scanDirectory); + } + + // Filter all ford-*.xml files with given format not in + // `sourceToOmit` + Pattern fordXmlFilePattern = Pattern.compile(filePattern); + return Files.list(basePath).filter(p -> p.toFile().isFile()) + .filter(p -> fordXmlFilePattern.matcher(p.getFileName().toString()).matches()) + .filter(p -> !sourceToOmit.contains(p.toAbsolutePath().toString())).map(p -> p.toFile()) + .collect(Collectors.toList()); + } catch (Exception e) { + String errorMessage = String.format( + "There was a probles scanning files from directory %s with pattern `%s`", this.scanDirectory, + filePattern); + log.error(errorMessage); + throw new IngestionException(errorMessage, e); + } + } + + /** + * Build a {@link Brand} based on a XML which path is given by parameter. + * + * @param path + * Path of the XML file to be parsed + * @return {@link Brand} built based on XML file given as argument + */ + protected Brand createBrandFromXml(InputStream is) { + + try { + + log.debug(String.format("Processing Ford catalog")); + + SAXReader reader = new SAXReader(); + Document document = reader.read(is); + + // Create Ford Brand + Brand brand = new Brand("Ford"); + + log.debug("Start processing CATALOG element ..."); + Element catalog = document.getRootElement(); + if (catalog.getName().equals("CATALOG")) { + throw new Error("Root element name must be `CATALOG`"); + } + + log.debug("Start processing CATALOG/MODEL list ..."); + Iterator iterator = catalog.elementIterator("MODEL"); + while (iterator.hasNext()) { + Specification spec = this.buildSpecification(brand, (Element) iterator.next()); + brand.addSpecification(spec); + } + log.debug(String.format("Specifications : %d created", brand.getSpecifications().count())); + + return brand; + + } catch (DocumentException e) { + String errorMessage = String.format("Failed reading source InputStream"); + log.error(errorMessage); + throw new IngestionException(errorMessage, e); + } + + } + + /** + * Based on a `MODEL` element, create a {@link Specification} and all their + * {@link Modification}. + */ + private Specification buildSpecification(Brand brand, Element element) { + + String name = element.attributeValue("name"); + String type = element.attributeValue("type"); + Integer from = attributeIntValue(element, "from"); + Integer to = attributeIntValue(element, "to"); + Engine engine = this.buildEngine(element.element("ENGINE")); + Wheel wheel = this.buildWheel(element.element("WHEELS")); + + Specification spec = new Specification(brand, name, from, to, type, engine, wheel); + log.debug(String.format("Specification '%s' was created", spec.getName())); + + Element submodels = element.element("SUBMODELS"); + if (submodels != null) { + Iterator iterator = submodels.elementIterator("MODEL"); + while (iterator.hasNext()) { + Modification modif = this.buildModification((Element) iterator.next()); + spec.addModification(modif); + } + } + log.debug(String.format("Modifications : %d created", spec.getModifications().size())); + + return spec; + } + + private Modification buildModification(Element element) { + if (element == null) { + return null; + } + + String name = element.attributeValue("name"); + String line = element.attributeValue("line"); + Integer from = attributeIntValue(element, "from"); + Integer to = attributeIntValue(element, "to"); + Engine engine = this.buildEngine(element.element("ENGINE")); + Wheel wheel = this.buildWheel(element.element("WHEELS")); + + return new Modification(name, from, to, line, engine, wheel); + } + + private Wheel buildWheel(Element element) { + if (element == null) { + return null; + } + String size = element.attributeValue("size"); + String type = element.attributeValue("type"); + return new Wheel(size, type); + } + + private Engine buildEngine(Element element) { + if (element == null) { + return null; + } + Integer size = attributeIntValue(element, "power"); + EngineType type = element.attributeValue("type") == null ? null + : EngineType.valueOf(element.attributeValue("type")); + return new Engine(size, type); + } + + private Integer attributeIntValue(Element element, String attrName) { + return element.attributeValue(attrName) != null ? Integer.valueOf(element.attributeValue(attrName)) : null; + } + +} diff --git a/cars/src/main/java/com/mooveit/cars/ingestion/IngestStrategy.java b/cars/src/main/java/com/mooveit/cars/ingestion/IngestStrategy.java new file mode 100644 index 0000000..215a7b2 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/ingestion/IngestStrategy.java @@ -0,0 +1,13 @@ +package com.mooveit.cars.ingestion; + +import com.mooveit.cars.domain.Brand; +import com.mooveit.cars.domain.Ingestion; + +/** + * Strategy to ingest a new {@link Brand} + */ +public interface IngestStrategy { + + Ingestion ingest(String source, Brand brandToIngest); + +} \ No newline at end of file diff --git a/cars/src/main/java/com/mooveit/cars/ingestion/IngestionException.java b/cars/src/main/java/com/mooveit/cars/ingestion/IngestionException.java new file mode 100644 index 0000000..35cfae4 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/ingestion/IngestionException.java @@ -0,0 +1,15 @@ +package com.mooveit.cars.ingestion; + +public class IngestionException extends RuntimeException { + + private static final long serialVersionUID = 612343670266273279L; + + public IngestionException() { + super(); + } + + public IngestionException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/cars/src/main/java/com/mooveit/cars/ingestion/MergeBrandsIngestStrategy.java b/cars/src/main/java/com/mooveit/cars/ingestion/MergeBrandsIngestStrategy.java new file mode 100644 index 0000000..93a6791 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/ingestion/MergeBrandsIngestStrategy.java @@ -0,0 +1,53 @@ +package com.mooveit.cars.ingestion; + +import java.util.Date; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.mooveit.cars.domain.Brand; +import com.mooveit.cars.domain.Ingestion; +import com.mooveit.cars.domain.Specification; +import com.mooveit.cars.repositories.BrandRepository; + +/** + * This strategy takes a transient {@link Brand} and: + *
    + *
  1. If the {@link Brand} doesn't exists into the repository, save it as it is + *
  2. If the {@link Brand} exists, for each {@link Specification}: + *
  3. If {@link Specification} doesn't exists, add it to the {@link Brand} + *
  4. If {@link Specification} exists, this will be replaced for the new one + *
+ */ +@Service +public class MergeBrandsIngestStrategy implements IngestStrategy { + + @Autowired + private BrandRepository brandRepo; + + /* + * @see com.mooveit.cars.ingestion.IngestStrategy#ingest(java.lang.String, + * com.mooveit.cars.domain.Brand) + */ + @Override + public Ingestion ingest(String source, Brand brandToIngest) { + + Optional persistentBrand = brandRepo.findByName(brandToIngest.getName()); + + if (!persistentBrand.isPresent()) { + // If brand didn't exists, persist complete new brand + brandToIngest = brandRepo.save(brandToIngest); + Long totalSpecs = brandToIngest.getSpecifications().count(); + return new Ingestion(brandToIngest, new Date(), source, totalSpecs, totalSpecs); + } else { + + // TODO : If brand exists, persist new Specifications and update + // existent ones. + + } + return new Ingestion(); + + } + +} diff --git a/cars/src/main/java/com/mooveit/cars/repositories/BrandRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/BrandRepository.java new file mode 100644 index 0000000..cba2e6b --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/repositories/BrandRepository.java @@ -0,0 +1,15 @@ +package com.mooveit.cars.repositories; + +import java.util.Optional; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import com.mooveit.cars.domain.Brand; + +@Repository +public interface BrandRepository extends PagingAndSortingRepository { + + public Optional findByName(String name); + +} diff --git a/cars/src/main/java/com/mooveit/cars/repositories/EngineRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/EngineRepository.java new file mode 100644 index 0000000..ac3a1b4 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/repositories/EngineRepository.java @@ -0,0 +1,20 @@ +package com.mooveit.cars.repositories; + +import java.util.Optional; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.rest.core.annotation.RestResource; +import org.springframework.stereotype.Repository; + +import com.mooveit.cars.domain.Engine; +import com.mooveit.cars.domain.EngineType; + +@Repository +@RestResource(exported = false) +public interface EngineRepository extends PagingAndSortingRepository { + + public Optional findByPowerAndType(Integer power, EngineType type); + + public Optional findByPower(Integer power); + +} diff --git a/cars/src/main/java/com/mooveit/cars/repositories/IngestionDTO.java b/cars/src/main/java/com/mooveit/cars/repositories/IngestionDTO.java new file mode 100644 index 0000000..6afa785 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/repositories/IngestionDTO.java @@ -0,0 +1,6 @@ +package com.mooveit.cars.repositories; + +public interface IngestionDTO { + + String getSource(); +} diff --git a/cars/src/main/java/com/mooveit/cars/repositories/IngestionRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/IngestionRepository.java new file mode 100644 index 0000000..bf8de9a --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/repositories/IngestionRepository.java @@ -0,0 +1,19 @@ +package com.mooveit.cars.repositories; + +import java.util.Date; +import java.util.Optional; +import java.util.Set; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import com.mooveit.cars.domain.Ingestion; + +@Repository +public interface IngestionRepository extends CrudRepository { + + public Optional findBySourceAndDate(String source, Date date); + + public Set findAllByBrandName(String brandName); + +} diff --git a/cars/src/main/java/com/mooveit/cars/repositories/SpecificationRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/SpecificationRepository.java new file mode 100644 index 0000000..e106cf7 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/repositories/SpecificationRepository.java @@ -0,0 +1,20 @@ +package com.mooveit.cars.repositories; + +import java.util.List; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import com.mooveit.cars.domain.EngineType; +import com.mooveit.cars.domain.Specification; + +@Repository +public interface SpecificationRepository extends PagingAndSortingRepository { + + public List findByName(@Param(value = "name") String name); + + public List findByEngineType(EngineType name); + + public List findByBrandName(@Param(value = "brand") String name); +} diff --git a/cars/src/main/java/com/mooveit/cars/repositories/WheelRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/WheelRepository.java new file mode 100644 index 0000000..71214f7 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/repositories/WheelRepository.java @@ -0,0 +1,17 @@ +package com.mooveit.cars.repositories; + +import java.util.Optional; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.rest.core.annotation.RestResource; +import org.springframework.stereotype.Repository; + +import com.mooveit.cars.domain.Wheel; + +@Repository +@RestResource(exported = false) +public interface WheelRepository extends PagingAndSortingRepository { + + public Optional findBySizeAndType(String R15, String type); + +} diff --git a/cars/src/main/java/com/mooveit/cars/tasks/BuildersConfigurations.java b/cars/src/main/java/com/mooveit/cars/tasks/BuildersConfigurations.java new file mode 100644 index 0000000..b291d34 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/tasks/BuildersConfigurations.java @@ -0,0 +1,23 @@ +package com.mooveit.cars.tasks; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.mooveit.cars.ingestion.BrandBuilder; +import com.mooveit.cars.ingestion.FordBrandBuilder; + +@Configuration +public class BuildersConfigurations { + + public class CollectionConfig { + + @Bean + public List brandBuilders() { + return Arrays.asList(new FordBrandBuilder()); + } + } + +} diff --git a/cars/src/main/java/com/mooveit/cars/tasks/FordIngesterTask.java b/cars/src/main/java/com/mooveit/cars/tasks/FordIngesterTask.java deleted file mode 100644 index a04f791..0000000 --- a/cars/src/main/java/com/mooveit/cars/tasks/FordIngesterTask.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.mooveit.cars.tasks; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; - -@Slf4j -@Service -public class FordIngesterTask { - - @Scheduled(cron = "${cars.ford.ingester.runCron}") - public void ingestFile() { - log.warn("Not implemented yet."); - } -} diff --git a/cars/src/main/java/com/mooveit/cars/tasks/IngesterTask.java b/cars/src/main/java/com/mooveit/cars/tasks/IngesterTask.java new file mode 100644 index 0000000..7b97bc4 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/tasks/IngesterTask.java @@ -0,0 +1,93 @@ +package com.mooveit.cars.tasks; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import com.mooveit.cars.domain.Brand; +import com.mooveit.cars.domain.Ingestion; +import com.mooveit.cars.ingestion.BrandBuilder; +import com.mooveit.cars.ingestion.IngestStrategy; +import com.mooveit.cars.repositories.IngestionDTO; +import com.mooveit.cars.repositories.IngestionRepository; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class IngesterTask { + + private static final Logger log = LoggerFactory.getLogger(IngesterTask.class); + + @Autowired + private IngestStrategy strategy; + + @Autowired + private IngestionRepository ingestionRepo; + + @Autowired + private List brandBuilders; + + public IngesterTask() { + super(); + } + + @Scheduled(cron = "${cars.ford.ingester.runCron}") + public void ingestBrands() { + + for (BrandBuilder builder : brandBuilders) { + + String brandName = builder.getBrandName(); + log.debug("Retrieving already imported sources for brand %s", brandName); + Set ingestions = ingestionRepo.findAllByBrandName(brandName); + Set sourcesToOmit = ingestions.stream().map(idto -> idto.getSource()).collect(Collectors.toSet()); + + if (log.isDebugEnabled()) { + String omitedSources = sourcesToOmit.stream().collect(Collectors.joining(", ")); + log.debug("Creating all non imported catalogs, excluding [%d] ", omitedSources); + } + + log.debug("Creating brands for %s ", brandName); + Map brands = builder.createBrands(sourcesToOmit); + for (String source : brands.keySet()) { + + log.debug("Ingesting %s's brand from source %s", brandName, source); + Ingestion ingestion = strategy.ingest(source, brands.get(source)); + ingestionRepo.save(ingestion); + } + } + + } + + public IngestStrategy getStrategy() { + return strategy; + } + + public void setStrategy(IngestStrategy strategy) { + this.strategy = strategy; + } + + public IngestionRepository getIngestionRepo() { + return ingestionRepo; + } + + public void setIngestionRepo(IngestionRepository ingestionRepo) { + this.ingestionRepo = ingestionRepo; + } + + public List getBrandBuilders() { + return brandBuilders; + } + + public void setBrandBuilders(List brandBuilders) { + this.brandBuilders = brandBuilders; + } + +} diff --git a/cars/src/test/java/com/mooveit/cars/ingestion/FordBrandBuilderTest.java b/cars/src/test/java/com/mooveit/cars/ingestion/FordBrandBuilderTest.java new file mode 100644 index 0000000..8bfa7ab --- /dev/null +++ b/cars/src/test/java/com/mooveit/cars/ingestion/FordBrandBuilderTest.java @@ -0,0 +1,75 @@ +package com.mooveit.cars.ingestion; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.List; +import java.util.regex.Pattern; + +import org.junit.Test; + +import com.mooveit.cars.domain.Brand; +import com.mooveit.cars.domain.Engine; + +public class FordBrandBuilderTest { + + @Test + public void testCreateBrandFromXmlFile() { + + FordBrandBuilder builder = new FordBrandBuilder(); + + InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("ford-test-example.xml"); + + Brand brand = builder.createBrandFromXml(inputStream); + assertNotNull(brand); + + brand.getSpecifications().forEach(x -> assertNotNull(x.getEngine())); + brand.getSpecifications().forEach(x -> assertNotNull(x.getWheel())); + } + + @Test + public void testCreateBrandFromXmlString() { + + FordBrandBuilder builder = new FordBrandBuilder(); + + String xml = String.join("/n", + "" + "", "", + "", ""); + + InputStream inputStream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); + + Brand brand = builder.createBrandFromXml(inputStream); + assertNotNull(brand); + + Engine eng = brand.getSpecifications().findFirst().get().getEngine(); + + // Empty Engine + assertNotNull(eng); + assertNull(eng.getType()); + assertNull(eng.getPower()); + + // Null Wheel + assertNull(brand.getSpecifications().findFirst().get().getWheel()); + + } + + @Test + public void testGetSourceXMLFiles() { + + FordBrandBuilder builder = new FordBrandBuilder(); + + assertTrue(Pattern.compile(String.format("^(.+)%sford-(.+).xml$", File.separator)) + .matcher("/User/nicolas/ford-anytexthere.xml").matches()); + + List testFiles = builder.getSourceXMLFiles(new HashSet<>()); + assertTrue(testFiles.size() == 1); + + } + +} diff --git a/cars/src/test/java/com/mooveit/cars/repositories/AllRepositoryTests.java b/cars/src/test/java/com/mooveit/cars/repositories/AllRepositoryTests.java new file mode 100644 index 0000000..55a9437 --- /dev/null +++ b/cars/src/test/java/com/mooveit/cars/repositories/AllRepositoryTests.java @@ -0,0 +1,14 @@ +package com.mooveit.cars.repositories; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +@RunWith(Suite.class) +@SuiteClasses({ EngineRepositoryTest.class, + SpecificationRepositoryTest.class, + WheelRepositoryTest.class, + IngestionRepositoryTest.class}) +public class AllRepositoryTests { + +} diff --git a/cars/src/test/java/com/mooveit/cars/repositories/EngineRepositoryTest.java b/cars/src/test/java/com/mooveit/cars/repositories/EngineRepositoryTest.java new file mode 100644 index 0000000..47abd9f --- /dev/null +++ b/cars/src/test/java/com/mooveit/cars/repositories/EngineRepositoryTest.java @@ -0,0 +1,53 @@ +package com.mooveit.cars.repositories; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Optional; + +import javax.transaction.Transactional; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import com.mooveit.cars.domain.Engine; +import com.mooveit.cars.domain.EngineType; + +@RunWith(SpringRunner.class) +@SpringBootTest +@Transactional +public class EngineRepositoryTest { + + @Autowired + private EngineRepository engineRepository; + + @Test + public void testSaveAndDeleteSingleEngine() throws Exception { + + Engine savedEngine = engineRepository.save( new Engine(1400, EngineType.GAS)); + + Long entityId = savedEngine.getId(); + assertTrue(engineRepository.existsById(entityId)); + assertTrue(engineRepository.findById(entityId).isPresent()); + + engineRepository.deleteById(savedEngine.getId()); + + assertFalse(engineRepository.existsById(entityId)); + assertFalse(engineRepository.findById(entityId).isPresent()); + + } + + @Test + public void testFindBySizeAndType() throws Exception { + + engineRepository.save( new Engine(1400, EngineType.GAS)); + + Optional resultList = engineRepository.findByPower(1400); + assertTrue(resultList.isPresent()); + + } + +} diff --git a/cars/src/test/java/com/mooveit/cars/repositories/IngestionRepositoryTest.java b/cars/src/test/java/com/mooveit/cars/repositories/IngestionRepositoryTest.java new file mode 100644 index 0000000..31834e2 --- /dev/null +++ b/cars/src/test/java/com/mooveit/cars/repositories/IngestionRepositoryTest.java @@ -0,0 +1,66 @@ +package com.mooveit.cars.repositories; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Date; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.transaction.Transactional; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import com.mooveit.cars.domain.Brand; +import com.mooveit.cars.domain.Ingestion; + +@RunWith(SpringRunner.class) +@SpringBootTest +@Transactional +public class IngestionRepositoryTest { + + @Autowired + private IngestionRepository ingestionRepository; + + @Autowired + private BrandRepository brandRepository; + + @Test + public void testSaveAndDeleteSingleIngestion() throws Exception { + + Brand brand = brandRepository.save(new Brand("Ford")); + Ingestion ingestion = new Ingestion(brand, new Date(), "fileName.xml", 10L, 10L); + + Ingestion savedIng = ingestionRepository.save(ingestion); + + Long entityId = savedIng.getId(); + assertTrue(ingestionRepository.existsById(entityId)); + assertTrue(ingestionRepository.findById(entityId).isPresent()); + + ingestionRepository.delete(savedIng); + + assertFalse(ingestionRepository.existsById(entityId)); + assertFalse(ingestionRepository.findById(entityId).isPresent()); + + } + + @Test + public void testFindAllByBrandName() throws Exception { + + Brand brand = brandRepository.save(new Brand("Ford")); + Ingestion ingestion = new Ingestion(brand, new Date(), "fileName.xml", 10L, 10L); + + Ingestion savedIng = ingestionRepository.save(ingestion); + + Set ingestionSet = ingestionRepository.findAllByBrandName("Ford"); + assertFalse(ingestionSet.isEmpty()); + assertTrue(ingestionSet.stream().map(i -> i.getSource()).collect(Collectors.toSet()) + .contains(savedIng.getSource())); + + } + +} diff --git a/cars/src/test/java/com/mooveit/cars/repositories/SpecificationRepositoryTest.java b/cars/src/test/java/com/mooveit/cars/repositories/SpecificationRepositoryTest.java new file mode 100644 index 0000000..c1788dd --- /dev/null +++ b/cars/src/test/java/com/mooveit/cars/repositories/SpecificationRepositoryTest.java @@ -0,0 +1,92 @@ +package com.mooveit.cars.repositories; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import javax.transaction.Transactional; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import com.mooveit.cars.domain.Brand; +import com.mooveit.cars.domain.Engine; +import com.mooveit.cars.domain.EngineType; +import com.mooveit.cars.domain.Modification; +import com.mooveit.cars.domain.Specification; + +@RunWith(SpringRunner.class) +@SpringBootTest +@Transactional +public class SpecificationRepositoryTest { + + @Autowired + private SpecificationRepository specRepository; + + @Autowired + private BrandRepository brandRepository; + + private Specification createCarSpec(boolean withModifications) { + Brand brand = new Brand("Ford"); + brand = brandRepository.save(brand); + + Engine eng = new Engine(100, EngineType.HYBRID); + Specification spec = new Specification(brand,"Specification A", 1994, 1996, "subcompact", eng, null); + + if (withModifications){ + spec.addModification(new Modification("Modification A.1", 1900, 1950, "high-line", null, null)); + } + return specRepository.save(spec); + } + + @Test + public void testSaveAndDeleteSingleSpec() throws Exception { + Specification savedSpec = createCarSpec(false); + + Long entityId = savedSpec.getId(); + assertTrue(specRepository.existsById(entityId)); + assertTrue(specRepository.findById(entityId).isPresent()); + + specRepository.deleteById(entityId); + + assertFalse(specRepository.existsById(entityId)); + assertFalse(specRepository.findById(entityId).isPresent()); + + } + + + @Test + public void testSaveAndDeleteSpecificationWitnModifications() throws Exception { + Specification savedSpec = createCarSpec(true); + + // Check collection was persisted using cascade strategy + assertTrue(savedSpec.hasModifications()); + Modification savedModif = savedSpec.getModification(0); + assertNotNull(savedModif.getId()); + + // Check all entities are removed using cascade strategy + specRepository.deleteById(savedSpec.getId()); + assertFalse(specRepository.existsById(savedSpec.getId())); + assertFalse(specRepository.existsById(savedModif.getId())); + } + + @Test + public void testFindByName() throws Exception { + Specification savedSpec = createCarSpec(false); + + Iterable result = specRepository.findByName(savedSpec.getName()); + assertTrue(result.iterator().hasNext()); + } + + @Test + public void testFindByEngineType() throws Exception { + Specification savedSpec = createCarSpec(false); + + Iterable result = specRepository.findByEngineType(savedSpec.getEngine().getType()); + assertTrue(result.iterator().hasNext()); + } + +} diff --git a/cars/src/test/java/com/mooveit/cars/repositories/WheelRepositoryTest.java b/cars/src/test/java/com/mooveit/cars/repositories/WheelRepositoryTest.java new file mode 100644 index 0000000..4a2791c --- /dev/null +++ b/cars/src/test/java/com/mooveit/cars/repositories/WheelRepositoryTest.java @@ -0,0 +1,52 @@ +package com.mooveit.cars.repositories; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Optional; + +import javax.transaction.Transactional; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import com.mooveit.cars.domain.Wheel; + +@RunWith(SpringRunner.class) +@SpringBootTest +@Transactional +public class WheelRepositoryTest { + + @Autowired + private WheelRepository wheelRepository; + + @Test + public void testSaveAndDeleteSingleWheel() throws Exception { + + Wheel savedWheel = wheelRepository.save(new Wheel("R15", "STEEL")); + + Long entityId = savedWheel.getId(); + assertTrue(wheelRepository.existsById(entityId)); + assertTrue(wheelRepository.findById(entityId).isPresent()); + + wheelRepository.deleteById(savedWheel.getId()); + + assertFalse(wheelRepository.existsById(entityId)); + assertFalse(wheelRepository.findById(entityId).isPresent()); + + } + + @Test + public void testFindBySizeAndType() throws Exception { + + wheelRepository.save(new Wheel("R15", "STEEL")); + + Optional resultList = wheelRepository.findBySizeAndType("R15", "STEEL"); + assertTrue(resultList.isPresent()); + + } + +} diff --git a/cars/src/test/java/com/mooveit/cars/tasks/IngesterTaskTest.java b/cars/src/test/java/com/mooveit/cars/tasks/IngesterTaskTest.java new file mode 100644 index 0000000..1bb3106 --- /dev/null +++ b/cars/src/test/java/com/mooveit/cars/tasks/IngesterTaskTest.java @@ -0,0 +1,95 @@ +package com.mooveit.cars.tasks; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.eq; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.junit4.SpringRunner; + +import com.mooveit.cars.domain.Brand; +import com.mooveit.cars.domain.Ingestion; +import com.mooveit.cars.ingestion.BrandBuilder; +import com.mooveit.cars.ingestion.MergeBrandsIngestStrategy; +import com.mooveit.cars.repositories.IngestionDTO; +import com.mooveit.cars.repositories.IngestionRepository; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class IngesterTaskTest { + + @Autowired + IngesterTask task; + + @MockBean + MergeBrandsIngestStrategy strategy; + + @MockBean + IngestionRepository ingestRepository; + + @Autowired + List brandBuilders; + + + private static String testSource = "sample-catalog.xml"; + + private static String testBrandName = "Test Brand"; + + private static Brand brandMock; + + @TestConfiguration + static class TestContextConfiguration { + + @Bean + public List brandBuilders() { + + // Create Mock Builder + Map brands = new HashMap(); + brands.put(testSource, brandMock); + + BrandBuilder bb = mock(BrandBuilder.class); + when(bb.getBrandName()).thenReturn(testBrandName); + when(bb.createBrands(any())).thenReturn(brands); + + return Arrays.asList(bb); + } + } + + @Before + public void setUp(){ + + } + + @Test + public void testIngestFiles() { + + // Empty source to omit + when(ingestRepository.findAllByBrandName(testBrandName)).thenReturn(new HashSet()); + when(strategy.ingest(testSource, brandMock)).thenReturn(mock(Ingestion.class)); + + task.ingestBrands(); + + verify(ingestRepository, times(1)).findAllByBrandName(eq(testBrandName)); + verify(strategy, times(1)).ingest(eq(testSource), eq(brandMock)); + verify(ingestRepository, times(1)).save(any(Ingestion.class)); + + } + +} + diff --git a/cars/src/test/resources/ford-test-example.xml b/cars/src/test/resources/ford-test-example.xml new file mode 100644 index 0000000..1c375a6 --- /dev/null +++ b/cars/src/test/resources/ford-test-example.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From ffeffe89a5c3c8e686bfd94fed3701d4425d907e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20J=2E=20Garc=C3=ADa?= Date: Mon, 12 Aug 2019 18:26:49 -0300 Subject: [PATCH 3/4] C Enable Spring Web Support to provide HATEOS support for Brand and Specifications --- ANSWERS.md | 24 ++++++++++++------- cars/pom.xml | 11 +++++++++ .../com/mooveit/cars/CarsApplication.java | 2 ++ .../mooveit/cars/CarsApplicationTests.java | 16 ------------- 4 files changed, 28 insertions(+), 25 deletions(-) delete mode 100644 cars/src/test/java/com/mooveit/cars/CarsApplicationTests.java diff --git a/ANSWERS.md b/ANSWERS.md index 803a320..60dbb46 100644 --- a/ANSWERS.md +++ b/ANSWERS.md @@ -82,20 +82,26 @@ A `MergeBrandsIngestStrategy` was implemented to follow this definition: ## C - Expose data with a RESTful API -I will take advantage of the `Spring Data Web Support` to expose all the Repositories implemented to accomplish the next requirements: +For this project, I've enabled _Spring Data Web_ allowing to publish Repositories automatically with **HATEOS** support. +I will take advantage of this to expose the repositories of `Brand`s and `Specification`s +which allow accomplishing the next two requirements: + +### Basic Endpoints * Get a car specification by id -```CURL``` -http://localhost:8080/specifications/{id} +```curl GET http://localhost:8080/specifications/{id}``` + +* Get all the car specifications by brand +```curl GET http://localhost:8080/specifications/search/findByBrandName?brand=Ford``` + +### Spring Data Web Endpoints + +_Spring Data Web_ also will enable some endpoints that allow accessing `Brand`s and `Specification`s as for example: -* Get all the car specifications by brand -```CURL``` -http://localhost:8080/specifications/search/findByBrandName?brand=Ford +* Find `Specification`'s by name - `curl GET http://localhost:8080/specifications/search/findByName?name=Ford%20Fiesta` +* Get all `Specification`'s by `Brand`'s id - `http://localhost:8080/brands/1/specifications` ## D - Adding images ## E - Improvements -* Spring Profiles (`dev` and `prod` required) -* Security -* diff --git a/cars/pom.xml b/cars/pom.xml index 43b1e4c..05808f9 100644 --- a/cars/pom.xml +++ b/cars/pom.xml @@ -43,6 +43,17 @@ lombok true + + + org.springframework.boot + spring-boot-starter-data-rest + + + + + org.springframework.boot + spring-boot-starter-actuator + org.springframework.boot diff --git a/cars/src/main/java/com/mooveit/cars/CarsApplication.java b/cars/src/main/java/com/mooveit/cars/CarsApplication.java index 9599fc9..cd3f468 100644 --- a/cars/src/main/java/com/mooveit/cars/CarsApplication.java +++ b/cars/src/main/java/com/mooveit/cars/CarsApplication.java @@ -3,11 +3,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.data.web.config.EnableSpringDataWebSupport; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableConfigurationProperties @EnableScheduling +@EnableSpringDataWebSupport public class CarsApplication { public static void main(String[] args) { diff --git a/cars/src/test/java/com/mooveit/cars/CarsApplicationTests.java b/cars/src/test/java/com/mooveit/cars/CarsApplicationTests.java deleted file mode 100644 index 426b96d..0000000 --- a/cars/src/test/java/com/mooveit/cars/CarsApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.mooveit.cars; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class CarsApplicationTests { - - @Test - public void contextLoads() { - } - -} From d12806fadec1f2bc7f3b6857d6c6be6154a72976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20J=2E=20Garc=C3=ADa?= Date: Thu, 15 Aug 2019 15:58:42 -0300 Subject: [PATCH 4/4] D Add support for Images --- ANSWERS.md | 37 ++++++++- .../cars/controllers/FileController.java | 56 +++++++++++++ .../com/mooveit/cars/domain/AbstractSpec.java | 14 ++++ .../java/com/mooveit/cars/domain/Image.java | 83 +++++++++++++++++++ .../repositories/AbstractSpecRepository.java | 11 +++ .../mooveit/cars/services/ImageService.java | 45 ++++++++++ .../cars/services/ImageStorageException.java | 13 +++ cars/src/main/resources/application.yml | 68 +++++++++++++-- .../AbtractSpecRepositoryTest.java | 70 ++++++++++++++++ 9 files changed, 387 insertions(+), 10 deletions(-) create mode 100644 cars/src/main/java/com/mooveit/cars/controllers/FileController.java create mode 100644 cars/src/main/java/com/mooveit/cars/domain/Image.java create mode 100644 cars/src/main/java/com/mooveit/cars/repositories/AbstractSpecRepository.java create mode 100644 cars/src/main/java/com/mooveit/cars/services/ImageService.java create mode 100644 cars/src/main/java/com/mooveit/cars/services/ImageStorageException.java create mode 100644 cars/src/test/java/com/mooveit/cars/repositories/AbtractSpecRepositoryTest.java diff --git a/ANSWERS.md b/ANSWERS.md index 60dbb46..86c736e 100644 --- a/ANSWERS.md +++ b/ANSWERS.md @@ -74,7 +74,7 @@ A new `BrandBuilder` instance could be added into the `BuildersConfigurations` a ingested using the same `IngestStrategy` for all of them. ### Ingest Strategy -`IngestStrategy` implement the sstrategy to ingest new or existant Brands and Specifications. +`IngestStrategy` implement the strategy to ingest new or existant `Brand`s and `Specificatio`s. A `MergeBrandsIngestStrategy` was implemented to follow this definition: * If the model's `Specification` was not previously ingested, this will be created based on the data source @@ -103,5 +103,40 @@ _Spring Data Web_ also will enable some endpoints that allow accessing `Brand`s ## D - Adding images +### Domain Model +In order to support attach images to each car's `Specification` I've added to the domain model the `Image` entity +which contains the `data` (bytes) of the images and information extra information as `fileName` and `fileType`. + +Each `Specification` has a unique `Image` associated. + +### Service and Repository + +A `ImageService` was implemented in order to manage storage access to the image entity associated to each +`Specification`, and an `AbstractSpecRepository` was implemented to allows associating images to both car's +`Specification`s and `Modification`s. + +### Image's endpoints + +The RESTFull API of the `Specification` entity was modified adding access to the `Image`s. This two endpoint are: + +* `curl -X POST http://localhost:8080/specifications/{SpecificationId}/image -H 'content-type: multipart/form-data' +-F file=@{local-path}` + +* `curl -X GET http://localhost:8080/specifications/{SpecificationId}/image` + ## E - Improvements +Here a list of TODOs for improving the solution in order to deploy it in a production environment: + +Functional improvements +* Improve `IngestionStrategy` in order to not duplicate `Engine`s and `Wheel`s +* Add some sample `BrandBuilder` to generate `Brand`s from different data sources, as e.g. RestAPIs. +* Including _Integration Test_ for the main endpoints + +Non-functional improvements +* Add _Spring Boot Actuator_ to add production-ready features **[Done]** + - Implement custom _HealthIndicator_ for monitor `Ingestion`s **[Todo]** +* Add Spring profiles for _development (dev)_ and _Production (prod)_ environment **[Done]** +* Add _Spring Security_ to add authentication and authorization to the service **[Todo]** +* Add _Swagger_ documentation **[Todo]** +* _Docker_ support **[Todo]** diff --git a/cars/src/main/java/com/mooveit/cars/controllers/FileController.java b/cars/src/main/java/com/mooveit/cars/controllers/FileController.java new file mode 100644 index 0000000..4efcb23 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/controllers/FileController.java @@ -0,0 +1,56 @@ + +package com.mooveit.cars.controllers; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.mooveit.cars.domain.Image; +import com.mooveit.cars.services.ImageService; + +@RestController +public class FileController { + + private static final Logger log = LoggerFactory.getLogger(FileController.class); + + @Autowired + private ImageService imageService; + + @PostMapping("/specifications/{id}/image") + public Image uploadFile(@PathVariable(name = "id") Long specId, @RequestParam("file") MultipartFile file) { + + log.info(String.format("Setting image for Specification id %d", specId)); + + Image image = imageService.storeFile(specId, file); + + String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath() + .path(String.format("/specifications/%d/image", specId)).toUriString(); + image.setUrl(fileDownloadUri); + + return image; + } + + @GetMapping("/specifications/{id}/image") + public ResponseEntity downloadFile(@PathVariable(name = "id") Long specId) { + + log.info(String.format("Retrieving image for Specification id %d", specId)); + + Image image = imageService.getFile(specId); + + return ResponseEntity.ok().contentType(MediaType.parseMediaType(image.getFileType())) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + image.getFileName() + "\"") + .body(new ByteArrayResource(image.getData())); + } + +} \ No newline at end of file diff --git a/cars/src/main/java/com/mooveit/cars/domain/AbstractSpec.java b/cars/src/main/java/com/mooveit/cars/domain/AbstractSpec.java index 067beda..2a13a2b 100644 --- a/cars/src/main/java/com/mooveit/cars/domain/AbstractSpec.java +++ b/cars/src/main/java/com/mooveit/cars/domain/AbstractSpec.java @@ -7,6 +7,7 @@ import javax.persistence.Inheritance; import javax.persistence.InheritanceType; import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; import javax.persistence.Table; import javax.validation.constraints.NotNull; @@ -47,6 +48,9 @@ public abstract class AbstractSpec extends BaseEntity { @ManyToOne(cascade = CascadeType.ALL) private Wheel wheel; + @OneToOne(cascade = CascadeType.ALL) + private Image image; + protected AbstractSpec() { } @@ -98,5 +102,15 @@ public void setEngine(Engine engine) { public void setWheel(Wheel wheel) { this.wheel = wheel; } + + public Image getImage() { + return image; + } + + public void setImage(Image image) { + this.image = image; + } + + } diff --git a/cars/src/main/java/com/mooveit/cars/domain/Image.java b/cars/src/main/java/com/mooveit/cars/domain/Image.java new file mode 100644 index 0000000..3dcdbb6 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/domain/Image.java @@ -0,0 +1,83 @@ +package com.mooveit.cars.domain; + +import javax.persistence.Entity; +import javax.persistence.Lob; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.validation.constraints.NotNull; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +@Entity +@Table(name = "files") +public class Image extends BaseEntity { + + @NotNull + private String fileName; + + @NotNull + private String fileType; + + @Lob + private byte[] data; + + private Long size; + + @Transient + private String url; + + public Image() { + super(); + } + + public Image(@NotNull String fileName, @NotNull String fileType, byte[] data, Long size) { + super(); + this.fileName = fileName; + this.fileType = fileType; + this.data = data; + this.size = size; + } + + @JsonIgnore + public byte[] getData() { + return data; + } + + public void setData(byte[] data) { + this.data = data; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getFileType() { + return fileType; + } + + public void setFileType(String fileType) { + this.fileType = fileType; + } + + public Long getSize() { + return size; + } + + public void setSize(Long size) { + this.size = size; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + +} \ No newline at end of file diff --git a/cars/src/main/java/com/mooveit/cars/repositories/AbstractSpecRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/AbstractSpecRepository.java new file mode 100644 index 0000000..634b3c2 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/repositories/AbstractSpecRepository.java @@ -0,0 +1,11 @@ +package com.mooveit.cars.repositories; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import com.mooveit.cars.domain.AbstractSpec; + +@Repository +public interface AbstractSpecRepository extends PagingAndSortingRepository { + +} \ No newline at end of file diff --git a/cars/src/main/java/com/mooveit/cars/services/ImageService.java b/cars/src/main/java/com/mooveit/cars/services/ImageService.java new file mode 100644 index 0000000..01df220 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/services/ImageService.java @@ -0,0 +1,45 @@ +package com.mooveit.cars.services; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import com.mooveit.cars.domain.AbstractSpec; +import com.mooveit.cars.domain.Image; +import com.mooveit.cars.repositories.AbstractSpecRepository; + +@Service +public class ImageService { + + @Autowired + private AbstractSpecRepository abstractSpecRepository; + + public Image storeFile(Long specId, MultipartFile file) { + + // Normalize file name + String fileName = StringUtils.cleanPath(file.getOriginalFilename()); + + try { + + AbstractSpec spec = abstractSpecRepository.findById(specId).orElseThrow(() -> new ImageStorageException( + String.format("Parent specification with id %d is doesn't exists", specId))); + + Image newImage = new Image(fileName, file.getContentType(), file.getBytes(), file.getSize()); + spec.setImage(newImage); + + spec = abstractSpecRepository.save(spec); + return spec.getImage(); + + } catch (IOException ex) { + throw new ImageStorageException("Could not store image " + fileName + ". Please try again!", ex); + } + } + + public Image getFile(Long specId) { + return abstractSpecRepository.findById(specId).map(spec -> spec.getImage()) + .orElseThrow(() -> new ImageStorageException("Image not found for Specification id " + specId)); + } +} diff --git a/cars/src/main/java/com/mooveit/cars/services/ImageStorageException.java b/cars/src/main/java/com/mooveit/cars/services/ImageStorageException.java new file mode 100644 index 0000000..54d3817 --- /dev/null +++ b/cars/src/main/java/com/mooveit/cars/services/ImageStorageException.java @@ -0,0 +1,13 @@ +package com.mooveit.cars.services; + +public class ImageStorageException extends RuntimeException { + + public ImageStorageException(String message, Throwable cause) { + super(message, cause); + } + + public ImageStorageException(String message) { + super(message); + } + +} diff --git a/cars/src/main/resources/application.yml b/cars/src/main/resources/application.yml index 436f591..00064cc 100644 --- a/cars/src/main/resources/application.yml +++ b/cars/src/main/resources/application.yml @@ -1,14 +1,64 @@ -cars: - ford: - ingester: - runCron: '0 * * ? * *' #each minute - spring: + profiles.active: dev + main.allow-bean-definition-overriding : true + +--- +spring: + profiles: dev + datasource: - url: jdbc:h2:mem:carsdb + url: jdbc:h2:mem:carsdb;DB_CLOSE_ON_EXIT=FALSE driverClassName: org.h2.Driver username: sa - password: + password: null + jpa: - database-platform: 'org.hibernate.dialect.H2Dialect' - h2.console.enabled: true + database-platform: org.hibernate.dialect.H2Dialect + properties: + hibernate: + ddl-auto: create-drop + show_sql: false + use_sql_comments: false + format_sql: false + h2: + console: + enabled: true + path: /console + settings: + trace: false + web-allow-others: false + +cars: + ford: + ingester: + runCron: 0 * * * * ? + +logging: + file: logs/dev_app.log + pattern: + console: '%d %-5level %logger : %msg%n' + file: '%d %-5level [%thread] %logger : %msg%n' + level: + org.springframework.web: DEBUG + guru.springframework.controllers: DEBUG + org.hibernate: DEBUG + +--- +spring: + profiles: prod + +cars: + ford: + ingester: + runCron: 0 * * ? * * + +logging: + file: logs/dev_app.log + pattern: + console: '%d %-5level %logger : %msg%n' + file: '%d %-5level [%thread] %logger : %msg%n' + level: + org.springframework.web: WARN + guru.springframework.controllers: WARN + org.hibernate: WARN + \ No newline at end of file diff --git a/cars/src/test/java/com/mooveit/cars/repositories/AbtractSpecRepositoryTest.java b/cars/src/test/java/com/mooveit/cars/repositories/AbtractSpecRepositoryTest.java new file mode 100644 index 0000000..d3cc6ed --- /dev/null +++ b/cars/src/test/java/com/mooveit/cars/repositories/AbtractSpecRepositoryTest.java @@ -0,0 +1,70 @@ +package com.mooveit.cars.repositories; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Optional; + +import javax.transaction.Transactional; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import com.mooveit.cars.domain.AbstractSpec; +import com.mooveit.cars.domain.Brand; +import com.mooveit.cars.domain.Engine; +import com.mooveit.cars.domain.EngineType; +import com.mooveit.cars.domain.Modification; +import com.mooveit.cars.domain.Specification; + +@RunWith(SpringRunner.class) +@SpringBootTest +@Transactional +public class AbtractSpecRepositoryTest { + + @Autowired + private SpecificationRepository specRepository; + + @Autowired + private AbstractSpecRepository abstractRepository; + + @Autowired + private BrandRepository brandRepository; + + private Specification createCarSpec(boolean withModifications) { + Brand brand = new Brand("Ford"); + brand = brandRepository.save(brand); + + Engine eng = new Engine(100, EngineType.HYBRID); + Specification spec = new Specification(brand,"Specification A", 1994, 1996, "subcompact", eng, null); + + if (withModifications){ + spec.addModification(new Modification("Modification A.1", 1900, 1950, "high-line", null, null)); + } + return specRepository.save(spec); + } + + + @Test + public void testSaveAndDeleteSpecificationWitnModifications() throws Exception { + Specification savedSpec = createCarSpec(true); + + // Check collection was persisted using cascade strategy + assertTrue(savedSpec.hasModifications()); + Modification savedModif = savedSpec.getModification(0); + assertNotNull(savedModif.getId()); + + Iterable iterSpecs = abstractRepository.findAllById(Arrays.asList(savedSpec.getId(), savedModif.getId())); + for (AbstractSpec abstractSpec : iterSpecs) { + assertNotNull(abstractSpec.getId()); + } + + } + + +}