A következő címkéjű bejegyzések mutatása: OSGi. Összes bejegyzés megjelenítése
A következő címkéjű bejegyzések mutatása: OSGi. Összes bejegyzés megjelenítése

2011. szeptember 24., szombat

Liferay Portál OSGi bundle támogatással

Liferay és OSGi témák már többször is előkerültek, de most egy olyan egyedülálló kombináció bemutatását tűztem ki célul, mely egyesíti a két platform minden előnyét. A bemutatott módszer aktív fejlesztés alatt áll, éles használatát nem ajánlom senkinek. A fejlesztés Raymond Augé keze alatt történik, jelenleg a teljes kódbázis branch repóból érhető el. A cikk megírását az érdeklődés ihlette, és a remény, hogy másoknak is megtetszik a kezdeményezés, és programozók tucatjai állnak be Ray mögé, hogy minél előbb core feature legyen a dolog.

Első lépésként szükségünk lesz a módosított Liferay forrására, amit itt tudunk beszerezni. A letöltött forrást egyszerűen tömörítsük ki valahová. A fordításhoz egy Apache Antot is be kell üzemelnünk, ennek lépéseit nem részletezném. A kibontott Liferay gyökérében találunk egy app.server.properties állományt, melyben tudjuk konfigurálni, hogy milyen konténert szeretnénk használni. Amennyiben valami "különleges" igény nem szól közbe, én az Apache Tomcat verziót preferálom, ez egyébként az alapértelmezett. A properties fájlban 7.0.21-es verzió van beállítva, letöltés után a Liferay forrásunkkal párhuzamosan lévő bundles könyvtárba csomagoljuk is ki. Vagy az app.server.properties-ben írjuk át az útvonalat, vagy az apache-tomcat-7.0.21 könyvtárat nevezzük át tomcat-7.0.21-re.

A fordításhoz Antunkat is meg kell kicsit piszkálni. Először is az ecj.jar-t be kell másolni az Ant libjei közé (vagy szimlinkelni, vagy egyéb úton a classpath-ra tenni). Az ecj.jar beszerezhető a Plugin SDK lib könyvtárából. Be kell állítanunk az Ant memóriahasználatát is az alábbi módon.

ANT_OPTS="-Xmx1024m -XX:MaxPermSize=256m"
export ANT_OPTS
Nincs más hátra, mint elindítani a fordítást a Liferay gyökérkönyvtárában.
ant all

A fordítás befejeztével a ../bundles/tomcat-7.0.21/bin/startup.sh parancs futtatásával kelthetjük életre friss Liferayünket. Miután elindult, a Control Panel Server szekcióban találunk egy OSGi Admin menüpontot. Belépve láthatjuk, ha minden jól ment, hogy van egy OSGi System Bundle nevű batyu, ami aktív, tehát az OSGi konténer fut rendben.Az általam próbált verzióba Eclipse Equinox 3.7 volt integrálva, Ray elmondása szerint minden >=4.1-es implementációval változtatás nélkül működik.

Amit a rendszer tud jelenlegi állás szerint:

  • Az adminisztrátor portleten keresztül lehetséges OSGi batyuk hozzáadása, eltávolítása, frissítése, elindítása, újraindítása, és leállítása.
  • Egy saját deploy listenerrel a deploy könyvtárba másolt batyukat a Liferay megpróbálja telepíteni és elindítani.

Most, hogy működik a portálunk, nincs más dolgunk, mint nekiállni első OSGi Servletünk megalkotásához. A batyu készítésről itt írtam részletesen, így az alapokba nem is mennék bele. Hozzunk létre egy OSGi Plugin Projectet. A MANIFEST.MF állomány tartalma legyen a következő:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: OSGiOnLiferay
Bundle-SymbolicName: OSGiOnLiferay;singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: javax.servlet;version="2.5.0",
 javax.servlet.http;version="2.5.0",
 org.eclipse.equinox.http;resolution:=optional,
 org.osgi.framework;version="1.6.0"

Hozzunk létre a gyökér-könyvtárban egy plugin.xml-t, amelybe regisztráljuk a Servletünket.

   
   

Végezetül írjunk egy egyszerű Servletet.
public class TestServlet extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException {
  PrintWriter out = resp.getWriter();
  out.print("Java'nother blog");
  out.close();
 }
}
A plugin buildelése után egyszerűen másoljuk be a kapott JAR-t a deploy könyvtárba. A Liferay logot nézve az alábbi sor jelöli a sikeres telepítést.
12:14:29,798 INFO  [AutoDeployDir:167] Processing sample.jar
Az admin felületre látogatva remélhetőleg meg is találjuk batyunkat az OSGi Admin felületen, nincs más dolgunk, mint aktiválni.

És itt jön a szomorú, ám reményteli vég. Mint említettem eléggé fejlesztési fázisban van a projekt, ami sajnálatosan azt is jelenti, hogy ezen a ponton nem tudunk túljutni. Ray a közeljövőben publikál egy birdge Servletet, aminek a segítségével a Liferay a HTTP kéréseket az OSGi HttpService rétegén keresztül bármely, a keretrendszerbe regisztrált Servletnek vagy JSPnek továbbítani tudja. Tervezi a Plugin SDK bővítését is, hogy OSGi batyukat lehessen vele kényelmesen fejleszteni. Véleményem szerint ígéretes a kezdeményezés, hiszen a Liferay egy jól gyúrható platform, az OSGi, pedig égy végtelenül rugalmas keretrendszer.

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.
    

2010. július 10., szombat

OSGi szárnypróbálgatás

Bár biztosan sokan ismeritek, de legalább hallottatok az Open Services Gateway Initiative-ról, ismertebb nevén az OSGi-ről, gondoltam én is kísérletet teszek rendszer a bemutatására. A témában kutakodva azonban találtam egy rövid írást Paller Gábor kollégától, melyben olyan érthetően megfogalmazza a technológia lényegét, hogy bizton állíthatom nekem sem sikerülne jobban, ezért meg sem próbálom. Az elméleti alapok elsajátítása után a gyakorlati megvalósításra szeretném helyezni a hangsúlyt.
OSGi pluginek fejlesztésére Eclipse IDE-t választottam, mely amellett, hogy ismeri a MANIFES.MF állomány függőségeit kezelni, rendelkezik beépített OSGi konténerrel (Equinox) is. Szerencsére a hazai bloggerek már több ízben is feszegették a témát, ennek köszönhetően pl. a jTechnics hasábjain olvashatunk a különféle konténer-implementációkról.
Megvalósítandó feladatként készítsünk egy bundlet, amely kiajánl egy szolgáltatást, amit egy másik bundeből meghívunk.

A szerviz:

  • Első lépésként hozzunk létre az Eclipsben egy "Plug-in Project"-et service néven, és az "on OSGi framework" opciónál állítsuk be az Equinox-ot.
  • Hozzuk létre a service.SimpleService interfészt:
    package service;
    
    public interface SimpleService {
     public void echo(Object message);
    }
    
  • Majd hozzuk létre service.SimpleServiceImpl néven az szolgáltatás implementációját:
    package service;
    
    public class SimpleServiceImpl implements SimpleService {
    
     @Override
     public void echo(Object message) {
      System.out.println(message);
     }
    }
    
  • Szerkesszük a service.Activator osztályt a következőre:
    package service;
    
    import org.osgi.framework.BundleActivator;
    import org.osgi.framework.BundleContext;
    import org.osgi.util.tracker.ServiceTracker;
    
    public class Activator implements BundleActivator {
    
     @Override
     public void start(BundleContext context) throws Exception {
      context.registerService(SimpleService.class.getName(), new SimpleServiceImpl(), new java.util.Hashtable());
      
      //Check service
      ServiceTracker serviceTracker = new ServiceTracker(context, SimpleService.class.getName(), null);
      serviceTracker.open();
      
      SimpleService simpleLogService = (SimpleService) serviceTracker.getService();
      if(simpleLogService != null)
       System.out.println("Service started.");
      else
       System.out.println("Service init failed!");
      
      serviceTracker.close();
     }
    
     @Override
     public void stop(BundleContext context) throws Exception {  
      System.out.println("Service stopped.");
     }
    }
    
  • A META-INF/MANIFEST.MF állomány szerkesztésével készíthetjük fel bundlet a konténerrel való együttműködésre. Eclipsben az Import-Package szerkesztésével tudjuk a "Plug-in Dependencies"-eket szerkeszteni, az IDE automatikusan importálja az itt megadott csomagokat. Fontos továbbá, hogy az Export-Packagenél kiajánljuk a service csomagot, mert ellenkező esetben a kliens oldalon "class SimpleServiceImpl cannot be cast to class SimpleService" exceptiont fogunk kapni.
    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: Service
    Bundle-SymbolicName: service
    Bundle-Version: 1.0.0
    Bundle-Activator: service.Activator
    Bundle-RequiredExecutionEnvironment: JavaSE-1.6
    Import-Package: org.osgi.framework;version="1.3.0",org.osgi.util.tracker;version="1.3.1"
    Export-Package: service;version="1.0.0"
    
  • A MANIFEST.MF állomány->jobbklikk->Run As->Run Configuration ablakban tudjuk beállítani a konténert a teszteléshez.
  • Fordításhoz vagy írunk saját ant/maven scriptet, vagy Exportáljuk a bundlet JAR formájában. Szeretném megjegyezni, hogy az Eclipse alapértelmezetten generálja a MANIFEST.MF állományt a jar-ba, viszont ebben az esetben a konténer nem fog tudni mit kezdeni a bundleval, ezért a "Use existing manifest from workspace" opció alatt (utolsó lépés) adjuk meg a megfelelő állományt.

A kliens:

  • Ismét egy hasonlóan paraméterezett "Plug-in Projct"-re lesz szükségünk, de ezúttal caller néven.
  • A caller.Activate osztályt hozzuk az alábbi formára:
    package caller;
    
    import org.osgi.framework.BundleActivator;
    import org.osgi.framework.BundleContext;
    import org.osgi.framework.ServiceReference;
    
    import service.SimpleService;
    
    public class Activator implements BundleActivator {
    
     @Override
     public void start(BundleContext context) throws Exception {
      //Another way to connect a service.
      ServiceReference reference = context.getServiceReference(SimpleService.class.getName());
      SimpleService service = (SimpleService) context.getService(reference);
      
      if (service != null)
       service.echo("Service called.");
      else
       System.out.println("Service call error!");
     }
    
     @Override
     public void stop(BundleContext context) throws Exception {
     }
    }
    
  • A service.SimpleService interfészt mindenképpen tegyük elérhetővé ebben a projektben, az egyszerűség kedvéért hozzuk létre az objektumot.
  • A META-INF/MANIFEST.MF állomány tartalma legyen a következő:
    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: Caller
    Bundle-SymbolicName: caller
    Bundle-Version: 1.0.0
    Bundle-Activator: caller.Activator
    Bundle-RequiredExecutionEnvironment: JavaSE-1.6
    Import-Package: org.osgi.framework;version="1.3.0",service;version="1.0.0"
    
    Fontos, hogy a szervizben exportált service csomagot itt importáljuk.
  • A szerviznél leírtakkal azonos módon tesztelhetjük és fordíthatjuk a bundlet (nekem voltak problémák, amikor az IDE-ből próbáltam tesztelni a két bundlet együtt).

A konténer:

Utolsó egységként a konténert kell megismernünk, melynek vezérlése eltérő a különböző implementációk esetén. Az Equinoxot választva az alábbiakat kell tennünk.
  • Szerezzük be a nekünk megfelelő verziót, jelenleg a 3.6-os a legfrissebb.
  • A `java -jar org.eclipse.osgi_3.6.0(.*?).jar -console` parancs futtatásával indíthatjuk el a konténert.
  • Installáljuk a bundlekat:
    osgi> install file:///path/to/file/service.jar
    Bundle id is 6
    
    osgi> install file:///path/to/file/caller.jar
    Bundle id is 7
    
    Eltávolítani az uninstall paranccsal, frissíteni pedig az updatetel lehet.
  • Az ss paranccsal lekérdezhetjük a telepített bundlek állapotát, mindkettőnek INSTALLED állapotban kell lennie:
    osgi> ss
    
    Framework is launched.
    
    id      State       Bundle
    0       ACTIVE      org.eclipse.osgi_3.6.0.v20100517
    6       INSTALLED   service_1.0.0.qualifier
    7       INSTALLED   caller_1.0.0.qualifier
    
  • Utolsó lépésként indítsuk el mindkét bundlet:
    osgi> start service
    Service started.
    
    osgi> start caller
    Service called.
    
    A bundle neve helyett használhatjuk az azonosítóját.
  • A stop paranccsal állítjuk le a bundlekat:
    osgi> stop service
    Service stopped.