2010. november 28., vasárnap

Liferay Service Builder

A Liferay az egyedi portletek fejlesztését saját MVC keretrendszerével kívánja élvezetesebbé tenni, az un. Liferay MVC-vel. A keretrendszer legizgalmasabb része a Model-t előállító Service Builder, ennek bemutatására teszek most egy kísérletet. Felépítését tekintve az adatbázis réteg fölött Hibernate biztosítja az entitások adatbázissal való megfeleltetését. A Hibernatet a Liferay saját perzisztencia rétege burkolja be. A perzisztencia réteggel a Servicek tartják a kapcsolatot, melyek lehetnek lokálisak és távoliak egyaránt, és általuk szerezhetünk referenciát Entitásainkra, melyeket a Liferay analógia szerint Modeleknek hívunk. A könnyebb megértést segíti a mellékelt ábra.
Első lépésként, ha még nem tettük volna, hozzunk létre egy New -> Liferay Plug-in Projectet kedvenc Eclipse fejlesztői környezetünkben. Eclipshez létezik egy Liferay IDE nevű plugin, ami azért nagyban megkönnyíti a fejlesztést, természetesen a Liferay SDK-t is használhatjuk, én a pluginra támaszkodom. Következő dolog amit létre kell hoznunk, az egy New -> Liferay Service. A beállító-panelen állítsuk be a Plug-in Projectet, Package path paraméternek adjuk meg a service réteg ős-csomagjának nevét, ez tetszőleges, Namespace paraméternek a tábla prefixet tudjuk megadni, és végül saját nevünket. A Namespace paraméter üresen is hagyható jelentősége annyi, hogy mivel az érintett táblákat a Liferay  alap értelmezetten saját táblái mellé hozza létre könnyebb átlátni, ha prefixeljük a táblaneveket. Ha végeztünk találunk egy service.xml állományt a docroot/WEB-INF mappában. Dolgunk mindössze annyi, hogy XML formában rögzítsük Modelljeink struktúráját.
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 6.0.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_6_0_0.dtd">
<service-builder package-path="hu.jpattern.model">
 <author>mhmxs</author>
 <namespace>library</namespace>
 <entity name="Book" local-service="true" remote-service="false">
  <column name="bookId" type="long" primary="true" />
  <column name="title" type="String" />
  <column name="author" type="Collection" entity="Author" mapping-key="authorId" />
  <column name="description" type="String" />
  <column name="price" type="long" />
  
  <column name="groupId" type="long" />
  <column name="companyId" type="long" />
  
  <order by="asd">
   <order-column name="title" />
   <order-column name="price" order-by="desc" />
  </order>
  
  <finder return-type="Collection" name="title">
   <finder-column name="title" comparator="LIKE" />
  </finder>
 </entity>
 <entity name="Author" local-service="true" remote-service="false">
  <column name="authorId" type="long" primary="true" />
  <column name="name" type="String" />
  
  <column name="groupId" type="long" />
  <column name="companyId" type="long" />
 </entity>
</service-builder>
Az XMl-ben az entity tag írja le az entitásokat. Először is egy kötelező név attribútumot kell megadnunk, majd a szerviz hívására vonatkozóakat. A példában csak a lokális Servicekkel foglalkozok, de tudni érdemes, hogy létezik remote-service attribútum is, alapértelmezett true!!, mely SOAP szolgáltatásokat valósít meg a Liferayyel együtt szállított Axison keresztül. Következnek a mezőleírások, melyek típusa primitív, egyszerű adat típus (String), vagy Date lehet. A primary="true" paraméterrel a PK-t határozhatjuk meg, értelemszerűen entitásonként legfeljebb 1-et. Lehetőségünk van entitások közötti kapcsolat létrehozására is, erre példa a Book author mezője, ahol a kapcsolat típusát, módját kell megadni, és az összekapcsolás kulcsát. A mapping-key OTM (@OneToMany) a mapping-table pedig MTM (@ManyToMany) kapcsolatot hoz létre entitásaink között. Liferay "poliszi", hogy nem használnak idegen kulcsokat az adatbázisban, minden kapcsolatot Java kódból kell kezelni, ezért a Service Builder sem támogatja ezt a dolgot. Személy szerint nekem ez roppant unszimpatikus, hiszen rengeteg hibát rejt magában a koncepció, a fejlesztők emberek, és óhatatlan, hogy bonyolultabb adat-struktúra esetén valami mégis kimarad a kódból. Az egyetlen "elfogadható" érv a Liferay döntése mellett, hogy így tudják biztosítani a minél szélesebb adatbázis-paletta támogatását. Érdemes felvenni a groupId és companyId mezőket, mert a Liferay ezen mezők szerint fogja tudni összekötni entitásunkat saját jogosultság-kezelő rendszerével. A Service Builder lehetőséget ad egy rendezési elv meghatározására az order mező megadásával, példánkban cím szerint ASC, és ár szerint DESC rendezést valósítunk meg. Sajnos order-ből egyetlen egy szerepelhet.  Kereséseket könnyítendő meghatározhatunk előre generált kereső metódusokat a finder mezővel, ez eléggé hasznos, ugyanis tetszőleges számú lehet, és rögtön megadható a comperatorlt, =, !=, <, <=, >, >=,  és LIKE lehet, értelemszerűen a = az alapértelmezett.  Bár a példában nem szerepel, létezik egy tx-required mező, melynek szöveges tartalmában kell azon metódusokat felsorolni, amikre szeretnénk tranzakciókezelést használni. Ennek értéke alapértelmezetten add*, check*, clear*,
delete*, set*, és update*, így az alap Service metódusok le is vannak vele fedve. További lehetőségekről a liferay-service-builder_6_0_0.dtdből tájékozódhatunk :). Végezetül a service.xml szerkesztőjében nyomjuk meg a Build services gombot, aminek hatására az IDE legenerálja a szükséges osztályokat.
A létrejött struktúrát megvizsgálva az alábbi osztályok "érdekesek" számunkra:
  • hu.jpattern.model.service.base.xxxLocalServiceBaseImpl (xxx az adott entitás osztály) ez az osztály tartalmazza az alap szerviz metódusainkat. Ezt az osztályt ne módosítsuk!
  • hu.jpattern.model.service.impl.xxxLocalServiceImpl osztályban tudjuk megvalósítani saját szerviz metódusainkat, melyek nincsenek benne a xxxLocalServiceBaseImpl osztályban. Fontos tudni, hogy az ebben az osztályba írt publikus metódusokat a Service Builder beleteszi az interfészbe következő buildeléskor.
  • hu.jpattern.model.model.impl.xxxModelImpl az alap entitásunkat reprezentálja. Ezt az osztályt ne módosítsuk, és ne szerezzünk rá referenciát közvetlenül!
  • hu.jpattern.model.model.impl.xxxImpl tartalmazza az entitás egyéb, általunk megírt metódusait. Ezt az osztályt szintén beolvasztja a következő build futtatásakor a Liferay.
  • Egy állományt emelnék még ki a többi közül, mégpedig a docroot/WEB-INF/lib könyvtárban létrejött ?.service.jar-t, mely az interfészeket és a szerviz "végleges" osztályait tartalmazza. Ezekkel a fejlesztőnek nem kell foglalkoznia.
Használata pofon egyszerű, az xxxLocalServiceUtil osztály statikus metódusain keresztül érhetjük el a Service réteget.
BookLocalServiceUtil.getBooksCount();