2011. január 29., szombat

Liferay FriendyUrlMapping vs. IPC

Amikor először futottam bele a címben nevezett probléma-együttesbe, még azt gondoltam, hogy csak egyedi az eset, így a megolldást nem jegyeztem meg, és sajnos le sem. Viszont mikor másodjára is szembetalálkoztam vele, és újra végigjártam a szamárlétrát, gondoltam megkímélem az emberiséget, vagy legalább az olvasóimat ezen gyötrelmektől.
A portletek világában sajnos állandó problémát jelent a felhasználóbarát URL-ek kezelése. Ennek oka, hogy egy portletnek két fázisa létezik, az egyik a render-fázis, a másik pedig az action-fázis. Álltalános eset, hogy amíg egy portlet action-fázisban van, addig a többi render-fázisba. A böngészőből átvitt adatoknak a szerver felé tartalmazniuk kell valamiféle hivatkozást az adott portlet-példányról, hogy a portlet-konténer el tudja dönteni kinek is címezték a kérést. Paraméterben szokott szerepelni a portlet nézet beállítása (minimalizált, normál, maximalizált, exclusive, ez utóbbi liferay specifikus), valamint az életciklusra, és portlet módra (view|edit|etc) vonatkozó adat. Ennek eredménye valami ehhez hasonló URN: ?p_p_id=searchaction_WAR_fakeportletshoppingportlet&p_p_lifecycle=1&p_p_state=normal&p_p_mode=view&p_p_col_id=column-2&p_p_col_count=1&_searchaction_WAR_fakeportletshoppingportlet_javax.portlet.action=search. A probléma megoldására a Liferay többféle megoldást is kínál, de erről később. A probléma másik része, hogy a portlet-szabvány második verziója óta lehetőség van portletek közötti kommunikációra, röviden IPC-re. Az IPC lényege tömören, hogy az egyik portletünk kivált, publikál egy eseményt, míg egy vagy több másik portlet, amelyek előzőleg feliratkoztak az eseményre, elkapják azt. Az eseményben lehetőség van adatok átpasszíroázára is, így válik teljessé a kommunikáció. A dolog hátulütője, hogy kiváltani eseményt csak action fázisban lehet, és a kiváltott eseményt a másik portlet render fázisának egy bizonyos szakaszában képes elkapni, tehát érezhetően nem teljes a fejlesztői szabadság. Összegezve a problémát egy friendly url-en (továbbiakban FU) "figyelő" portletnek kell jeleznie állapotáról egy másik portletnek.

IPC kialakítása

A megvalósításhoz nincs más dolgunk, mint bejegyezni a portlet.xml-ben egy globális eseményleírást, ezzel rögzítve magát az eseményt.
<event-definition>
    <qname xmlns:x="http://jpattern.blogspot.hu/events">x:ipc.myEvent</qname>
    <value-type>java.lang.String</value-type>
</event-definition>
Ezzel művelettel a http://jpattern.blogspot.hu/events névtéren bejegyeztünk egy ipc.myEvent eseményt.
Ezután mindkét portletünket szerepkörüknek megfelelően felvértezzük a kommunikációval.
<supported-publishing-event>
    <qname xmlns:x="http://jpattern.blogspot.hu/events">x:ipc.myEvent</qname>
</supported-publishing-event>

<supported-processing-event>
    <qname xmlns:x="http://jpattern.blogspot.hu/events">x:ipc.myEvent</qname>
</supported-processing-event>
Az esemény publikálása az alábbi módon történik:
QName qName = new QName("http://jpattern.blogspot.hu/events", "ipc.myEvent");
response.setEvent(qName, "param"); //ActionResponse
Az esemény elkapásához portlet osztályunkban kell delegálnunk egy metódust:
@ProcessEvent(qname = "{http://jpattern.blogspot.hu/events}ipc.myEvent")
public void myEvent(EventRequest request, EventResponse response) {
    Event event = request.getEvent();
    String param = (String) event.getValue();
    response.setRenderParameter("param", param);
}
Ezek után a JSP-ben nincs más dolgunk mint kiszedni a requestből a paramétert:
renderRequest.getParameter("param");
Ennyi a varázslat, kipróbálásához házi feladat a portlet taglib actionUrl használata.

A FriendlyUrlMapping

Az említett több lehetőség közül nekem szimpatikusabb volt az XML szerkesztgetésnél, hogy létrehozok egy osztályt, amely beállítja a portletet az URL alapján. Az osztállyal szemben támasztott követelmény, hogy a com.liferay.portal.kernel.portlet.BaseFriendlyURLMapper osztály leszármazottja legyen.
public class MyFriendlyUrlMapper extends BaseFriendlyURLMapper {

private static final String MAPPER = "mappingstring"; //erre az URL darabra fog aktiválódni a mapper
 
@Override
public void populateParams(String friendlyURLPath,
        Map<String, String[]> params, Map<String, Object> requestContext) {
    addParameter(params, "p_p_id", getPortletId());
    addParameter(params, "p_p_lifecycle", "1"); //ez a paraméter kényszeríti action-fázisba a portletet
    addParameter(params, "p_p_mode", PortletMode.VIEW);
    addParameter(params, "p_p_state", WindowState.NORMAL);
    addParameter(getNamespace(), params, "javax.portlet.action", "actionMethod"); //Ez az action-metódus fog meghívódni
 }
 
@Override
public boolean isCheckMappingWithPrefix() {
    return false;
}
 
@Override
public String getMapping() {
    return MAPPER;
}
 
@Override
public String buildPath(LiferayPortletURL portletURL) {
    return null;
}

}
Miután elkészítettük az osztályt, be kell jegyeznünk a liferay-portlet.xml fájlban a megfelelő portletnél azt:
<friendly-url-mapper-class>com.blogger.jpattern.MyFriendlyUrlMapper</friendly-url-mapper-class>
Érdemes megjegyezni, hogy a FU kezelés a LR-ben úgy működik, hogy felveszünk egy oldalt, pl.: /oldal, amire kihelyezzük a portletet, és a LR /oldal/mappingstring URL esetén aktiválja a Mapper osztályt.
Amennyiben a portletünket render fázisban szeretnénk használni p_p_lifecycle=0, nincs más dolgunk, de mint említettem IPC esemény kiváltására csak action-fázisban van lehetőség. A LR-ben ki kell kapcsolnunk az "Auth token check"-et, ami annyit tesz, hogy minden action típusú kéréshez a LR generál egy egyedi azonosítót, és ennek hiányában nem enged hozzáférést az erőforráshoz. Nyilván FU használata esetén nem rendelkezünk ezzel az azonosítóval, ezért a portal-ext.properties fájlban vegyük fel a "auth.token.check.enabled=false" paramétert.

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();

2010. szeptember 1., szerda

Forrásgenerálás CodeModel segítségével


Minden programozó életében egyszer elérkezik a pont, amikor valamilyen előre egyeztetett séma alapján forrás-álloményokat, azaz működő Java osztályokat kell generálnia. Jelenleg is számtalan eszközzel tudunk forrást generálni, gondoljunk csak az adatbázisból létrejövő entitásokra, vagy egy WSDL alapján generált osztály-struktúrára, sőt a legtöbb IDE alapértelmezetten segítséget nyújt ezen a területen, képes konstruktort, getter-setter metódusokat, stb készíteni pár kattintással. Ha saját magunknak szeretnénk készíteni egy forrás-generátort, természetesen arra is megvan a lehetőség. A JAXB-nek van egy al-projektje, a CodeModel, aminek segítségével megoldható a probléma, ha van elég kitartásunk megérteni a mikéntjét, ugyanis dokumentálva mondhatni egyáltalán nincs az eszköz. Rövid írásom célja, hogy ízelítőt adjon a CodeModel lehetőségeiből, ezért kézenfekvőnek tűnik, hogy ismerkedés gyanánt készítsünk egy olyan generátort, ami átadott paraméter-lista alapján összeállít egy DTO osztályt. Először is szükségünk lesz a jaxb-xjc csomagra, ugyanis ez tartalmazza a szükséges osztályokat. Magam részéről a DTO típusú osztály-generátort egy saját osztályba csomagoltam.
import com.sun.codemodel.JAnnotationUse;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JDocComment;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import java.io.Serializable;
import java.util.Map;

/**
 * @author mhmxs
 */
public final class DTO {

    private final String name;
    private final Map<String, Class> parameters;

    public DTO(String name, Map<String, Class> parameters) {
        this.name = name;
        this.parameters = parameters;
    }

    public JCodeModel generateSource() throws JClassAlreadyExistsException {
        JCodeModel codeModel = new JCodeModel();

        //define class header
        JDefinedClass clazz = codeModel._class(name);
        clazz._implements(Serializable.class);
        JAnnotationUse annotation = clazz.annotate(SuppressWarnings.class);
        annotation.param("value", "serial");

        //Add Java-doc to class header
        JDocComment jDocComment = clazz.javadoc();
        jDocComment.add("Simple DTO class : " + name + "\n");
        jDocComment.add("@author mhmxs");

        //Create constructor
        JMethod constr = clazz.constructor(JMod.PUBLIC);

        //Generate getter and setter methods for all parameters
        JMethod method;
        for(String param : parameters.keySet()) {
            //Add parameter to class declaration
            clazz.field(JMod.PRIVATE, parameters.get(param), param);

            //Add parameter to constructor
            constr.param(parameters.get(param), param);
            constr.body().directStatement("this." + param + " = " + param + ";");

            String methodName = param.replaceFirst(String.valueOf(param.charAt(0)),
                String.valueOf(param.charAt(0)).toUpperCase());

            //Create getter method
            method = clazz.method(JMod.PUBLIC, parameters.get(param), "get" + methodName);
            method.body()._return(JExpr._this().ref(param));

            //Create setter method
            method = clazz.method(JMod.PUBLIC, Void.TYPE, "set" + methodName);
            method.param(parameters.get(param), param);
            method.body().directStatement("this." + param + " = " + param + ";");
        }

        return codeModel;
    }
}
Az osztály-t az alábbi módon tudjuk meghívni.
Map<String, Class> parameters = new HashMap<String, Class>();
parameters.put("parameter", String.class);
parameters.put("parameter2", Integer.class);
        
DTO dto = new DTO("a.b.Clazz", parameters);

OutputStream out = new FileOutputStream(new File("/a/b/Clazz.java"));
dto.generateSource().build( new SingleStreamCodeWriter(out));
out.close();
Végül az eredmény.
package a.b;

import java.io.Serializable;


/**
 * Simple DTO class : a.b.Clazz
 * @author mhmxs
 * 
 */
@SuppressWarnings("serial")
public class Clazz
    implements Serializable
{

    private String parameter;
    private Integer parameter2;

    public Clazz(String parameter, Integer parameter2) {
        this.parameter = parameter;
        this.parameter2 = parameter2;
    }

    public String getParameter() {
        return this.parameter;
    }

    public void setParameter(String parameter) {
        this.parameter = parameter;
    }

    public Integer getParameter2() {
        return this.parameter2;
    }

    public void setParameter2(Integer parameter2) {
        this.parameter2 = parameter2;
    }

}

2010. augusztus 19., csütörtök

META-INF könyvtár kicsit közelebbről

A META-INF könyvtáron belül található MANIFEST.MF (továbbiakban MF) állománnyal mindenki találkozik, aki Java binárist készít, vagy felhasznál egy mások által fordított Java forrást. Amikor létrehozunk egy JAR-t vagy valamely módozatát (WAR, EAR), akkor automatikusan belekerül egy példány az állományba, melynek tartalma alapértelmezetten csak a Manifest verziószámát tartalmazza "Manifest-Version: 1.0". A MF állomány JDK 1.2 verziója óta lényegesen egyszerűsödött, korára való tekintettel az ez előtti verzió nem kerül tárgyalásra. A MF állomány kulcs-érték pár alapú metaadat-tárolást valósít meg, és egy JAR-ban (WAR, EAR) csak egy példány lehet belőle, így értelem szerűen az egész JAR-ra, és a benne lévő csomagokra vonatkozó információkat is tárol. Rövid írásom célja, hogy bemutassa a lehetséges alap-beállítások egy részét. Mivel a MF funkcionalitása eléggé széleskörű, kezdve a verzió-követéstől, az elektronikus aláíráson át, a szerzői információkig, egy írásban talán össze sem lehetne foglalni az összes területet, ráadásként az egyes keretrendszerek (Spring, OSGi, stb.) is előszeretettel használják saját csomag-információk tárolására.

A JAR-ra vonatkozó paraméterek


Main-Class

Amennyiben önállóan futtatható JAR-t készítünk, a JVM-nek meg kell adnunk paraméterként, hogy mely osztály "main" metódusa a belépési pont. Mivel ezt körülményes minden indításnál megadni, ezért lehetőségünk van a MF-ben  tárolni ezt a beállítást.
Main-Class: my.package.startApplication

Class-Path

Az osztály-betöltő ha még nem töltött be egy hivatkozott osztályt, akkor az alapértelmezett útvonalakon elkezdi keresni azt. Ha szerencsések vagyunk, akkor sikerrel jár és az osztály bekerül a memóriába. Amennyiben az alapértelmezettől eltérő útvonalon található az osztály, akkor annak pontos helyet specifikálhatjuk a MF-ben. Lehetőség van több JAR-t is felsorolni szóközzel elválasztva. A fejlesztés könnyítése érdekében helyettesítő karaktereket is használhatunk az útvonal megadásakor.

Class-Path: ./lib/first.jar /usr/lib/secound.jar /usr/lib/java/*

Egyéb opcionális paraméterek

A teljeség igénye nélkül.

Archiver-Version: Plexus Archiver
Created-By: Apache Ant
Built-By: Joe
Build-Jdk: 1.6.0_04
Signature-Version: 1.0

Bejegyzésekre vonatkozó paraméterek


Name

Lehetőségünk van nevet adnia a JAR-on belül az egyes csomagoknak, osztályoknak vagy erőforrásoknak. A névadás konvenciója, hogy a név értéke a csomag esetén relatív útvonala a fájlstruktúrában, és egy lezáró "/"-karakter, osztály vagy egyéb erőforrás-fájl esetén a teljes relatív elérés. Minden további attribútum ami üres-sor nélkül a név után következik, érvényes lesz a Name-ben hivatkozott elemre.

Name: my/package/

Sealed

A névvel ellátott csomagokat opcionálisan le lehet zárni, ami azt jelenti, hogy a lezárt csomagban lévő összes definiált osztály egyazon JAR-ban található. A lezárásnak biztonsági és verziózási okai lehetnek.

Name: my/package/
Sealed: true

Package Versioning Specification

Verzió-kezelési specifikációban a csomagnak többféle attribútum is megadható opcionálisan, az egyetlen megkötés, hogy ezen beállítások mindegyikének szintén a Name attributumot kell követniük üres-sor hagyása nélkül.

Name: my/package/
Specification-Title: "MF Sample"
Specification-Version: "0.1"
Specification-Vendor: "jpattern"
Implementation-Title: "my.package"
Implementation-Version: "build1"
Implementation-Vendor: "jpattern"

Content-Type

A Content-Type segítségével az adott elem MIME tipusát határozhatjuk meg.

Name: my/package/startApplication.properties
Content-Type: text/plain

Java-Bean

A Java-Bean attribútum segítségével adható meg, hogy az adott elem Java Bean vagy sem.

Name: my/package/startApplication.class
Java-Bean: true

*-Digest

Az aláírással ellátott állományoknál kötelező elemként meg kell adni az állomány hashének base64 dekódolt reprezentációját. Az aláírás készítésről később.

Name: my/package/startApplication.class
SHA1-Digest: TD1GZt8G11dXY2p4olSZPc5Rj64=

MANIFEST.MF állomány készítése Ant taskból

Természetesen a MF-et nem kell minden alkalommal manuálisan megszerkeszteni, hanem lehetőségünk van Ant taskbol előállítani tartalmát (Mavenből szintén).

    
        
            
            
        
    

Aláírás és hitelesítés


A digitális aláírás fogalmát, és lényegét azt hiszem senkinek sem kell bemutatnom. Mivel a Java eléggé fejlett biztonsággal rendelkezik, elengedhetetlen, hogy az egyes csomagokat ne lehessen hitelesíteni. A Java a művelethez szükséges állományokat szintén a META-INF könyvtárban tárolja, tipikusan *.SF, *.DSA, *.RSA és SIG-* fájlokban. A hitelesítés menete tömören:

  • Az aláíró egy titkos kulcs segítségével aláírja a JAR-t (pontosabban minden benne lévő állományt).
  • A felhasználó a publikus kulccsal ellenőrzi az aláírást.

Aláírás készítése

  • Aláírás készítésének első lépése, hogy generálnunk kell egy privát kulcsot.
    keytool -genkey -alias jarSigner -keystore storedkeys -keypass passwd -dname "cn=jpatter" -storepass stpasswd
    Mint is csinál ez a parancs? Generál egy kulcsot jarSigner névvel, és a keypass jelszóval védetté teszi. A keystore paraméterben megadott adatbázis-állományban eltárolja a kulcsot, mely állomány ha nem létezik a keytool létrehozza azt, és a storepass-ban megadott jelszóval védi. A dname paraméter specifikál egy un. "X.500 Distinguished Name" bejegyzést, a cn paraméterben megadott egyszerűsített névvel. Az "X.500 Distinguished Name azonosítja a bejegyzéseket a X.509 hitelesítéshez, értéke tetszőleges lehet.
  • Az elkészült JAR-unk aláírásához a jarsigner eszközt tudjuk segítségül hívni.
    jarsigner -keystore storedkeys -storepass stpasswd -keypass passwd -signedjar myapp_signed.jar myapp.jar jarSigner
    A parancs lényegében kiszedi az előzőekben létrehozott kulcsot, és a bemeneti JAR állomány minden elemén elvégzi az aláírást, a végeredményt pedig a kimeneti JAR-ba teszi. A JAR-ba pillanva máris megjelent a JARSIGNE.SF és JARSIGNE.DSA, melyek közül az előbbi az egyes állományok hitelesítéséért felel, az utóbbi a publikus kulcs. A JARSIGN.SF állományt megnyitva valami hasonló tárul a szemünk elé:
    Signature-Version: 1.0
    SHA1-Digest-Manifest-Main-Attributes: FPLdz3FFeWLdgX0fUdHTqjUNkpE=
    Created-By: 1.6.0_20 (Sun Microsystems Inc.)
    SHA1-Digest-Manifest: AJwzuuyn2mg59fYB2qEhUL0PPgI=
    
    Name: my/resources/logo.png
    SHA1-Digest: oVtyix/BpiM9iq1fG/nMpy4Xy4Q=
    
    Name: my/package/startApplication.class
    SHA1-Digest: TD1GZt8G11dXY2p4olSZPc5Rj64=
    A MF állományunk tartalma is automatikusan megváltozott, az SHA1-Digest bejegyzések kerültek bele.

Aláírás hitelesítése


A hitelesítés szintén a jarsigner eszközzel történik.
jarsigner -verify myapp_signed.jar
eredményként pedig "jar verified" vagy "jar is unsigned" értékeket kaphatjuk.

2010. július 30., péntek

OSGi távoli kapcsolódás CoolRMI segítségével

Az előző bejegyzésben láthattuk miként készíthetünk OSGi keret-rendszer alá szolgáltatást, és hogyan kapcsolódhatunk ehhez a szolgáltatáshoz a keret-rendszeren belül. A mostani írás célja, hogy segédletet nyújtson távoli kapcsolat kialakítására, hiszen elengedhetetlen, hogy az OSGi konténeren kívül eső kód is használni tudja a szolgáltatásokat. Választásom a CoolRMI nevű eszközre esett, egyrészt mert magyar fejlesztés, és nyílt-forráskóddal lett publikálva, másrészt mert a környezetem ezt használja, és kipróbált alkalmazás-komponensnek minősült. A CoolRMI két részből tevődik össze. Az egyik fele az OSGi konténerben fut mint szolgáltatás, és várja a beérkező kéréseket, a másik fele pedig az alkalmazásunkban a kapcsolódásért felel.
  • Szerezzük be a CoolRMI legfrissebb verzióját.
  • Indítsuk el az OSGi konténert.
  • Installáljuk majd indítsuk el a CoolRMI-t az OSGi konténerben.
    Valami ilyesmit kell látnunk:

    osgi> ss
    
    Framework is launched.
    
    id      State       Bundle
    0       ACTIVE      org.eclipse.osgi_3.6.0.v20100517
    2       ACTIVE      com.rizsi.coolrmi_1.1.0
    
  • Hozzunk létre egy "Plug-in Project"-et az Eclipsben CoolRMI néven, majd a letöltött jar-ból a MANIFEST.MF-et és a forrás-fájlokat másoljuk a projektbe, végül pedig frissítsük a projektet. (nekem a MANIFEST.MF állományba az alábbi sort fel kellett venni: Import-Package: org.osgi.framework;version="1.3.0")
  • Nyissuk meg az előzőekben service néven futó projektünket, és a MANIFEST.MF állomány Import-Package szekcióját bővítsük a CoolRMI exportjaival.
  • Szerver oldalon a a szolgáltatás regisztrációjában van némi különbség. Jelen esetben nem az OSGi konténerbe kell regisztrálnunk a szolgáltatást, hanem a CoolRMI kontextusába, ezért az Activator.start metódusunkat az alábbira kell alakítani:

    InetSocketAddress addr = new InetSocketAddress(9001);
      
    CoolRMIServer server = new CoolRMIServer(Activator.class.getClassLoader(), addr, true);
      
    //Register a service on the id: "service"
    //This id an be used client side to find the service on this server.
    server.getServiceRegistry().addService(new CoolRMIService("service", SimpleService.class, new SimpleServiceImpl()));
      
    server.start();
    System.out.println("CoolRMI Service started.");
    
  • Fordítsuk és telepítsük a szolgáltatást.
  • Hozzunk létre egy "Java Project"-et, és importáljuk be az imént letöltött jar-t.
  • Tegyük elérhetővé a service.SimpleService interfészt, hogy a kliens programunk tudja milyen metódusokat tud meghívni.
  • A kliens osztály kódja az alábbi:

    import java.net.InetSocketAddress;
    import service.SimpleService;
    import com.rizsi.coolrmi.CoolRMIClient;
    import com.rizsi.coolrmi.ICoolRMIProxy;
    
    public class Caller {
     public static void main(String[] args) throws Exception {
      // Create a client that connects to the server on the specified address.
      CoolRMIClient client = new CoolRMIClient( Caller.class.getClassLoader(), new InetSocketAddress("localhost", 9001), true);
      
      try {
       // Get a proxy for the remote service object
       // To access the remote object the remote interface
       // and the service Id must be known
       ICoolRMIProxy remoteService = client.getService(SimpleService.class, "service");
       
       try {
        SimpleService service = (SimpleService) remoteService;
        service.echo("CoolRMI calling.");
       } finally {
        remoteService.disposeProxy();
       }
      } finally {
       client.close();
      }
     }
    }
  • Futtassuk a klienset, és ellenőrizzük a kimenetet az OSGi konzolon.

    osgi> CoolRMI calling.