Archív

Autorův archiv

Datové schránky - první zpráva

Minulý týden, jsme se stěhovali do nových prostor ve Strašnicích. Mimo jiné bylo třeba nahlásit na všemožných úřadech změnu naší korespondenční adresy. V případě finančního úřadu bylo možné změnu nahlásit pomocí Datových schránek. V tomto článku se chci podělit o svoji první zkušenost s použitím služby Datových schránek.

Přihlášení do Datových schránek proběhlo bez větších problémů. Chvíli mi trvalo najít stránku s formulářem pro přihlášení, čekal bych do očí bijící odkaz někde na oficiálních informačních stránkách Datových schránek www.datoveschranky.info. Dalším drobným zádrhelem byla instalace kořenových certifikátů Postsignum. Nakonec jsem našel potřebné certifikáty zde: http://vca.postsignum.cz/…horities.php.

Po přihlášení bylo poměrně jasné, co dál. Stisknul jsem tlačítko Nová zpráva. V prvním kroku jsem byl požádán o vyplnění, komu chci zprávu poslat. Formulář pro vyhledávání příjemců se ale choval dost zvláštně. Potřeboval jsem zprávu poslat na Finanční úřad v Jindřichově Hradci. Zkusil jsem tedy nejprve zadat do políčka Název organizace výraz „financni urad“. Výsledkem bylo toto:

„financni urad“: nic nenalezeno

„financni urad“: nic nenalezeno

Nic nebylo nalezeno. Mým druhým pokusem bylo použít diakritiku, tedy „finanční úřad“:

„finanční úřad“: příliž mnoho výsledků

„finanční úřad“: příliž mnoho výsledků

Hmm, tady mi svitla naděje, ale stále žádný výsledek. Zkusil jsem tedy přednější výraz „finanční úřad jindřichův hradec“ a … zase nic. Posledním pokusem bylo vyplnění „finanční úřad“ do políčka Název organizace a „jindřichův hradec“ do políčka Obec. Stále bez výsledku. Nakonec jsem se uchýlil k vyhledání IČO finančního úřadu na jiných stránkách a poté vyhledání příjemce podle IČO.

Samotnou zprávu nenapíšete bez nainstalování pluginu 602 XML Filler. 602 XML Filler je nástroj pro vyplňování elektronických formulářů, proč ale nestačí obyčejný HTML formulář?

Nová zpráva

Nová zpráva

Finančnímu úřadu jsem potřeboval sdělit naši novou korespondenční adresu. Ve formuláři ale úplně chyběla možnost přímo zadat text zprávy. Po několika minutách zkoumání jsem musel zprávu napsat ve Wordu a tento dokument přiložit k datové zprávě.

Psaní zprávy mi trvalo bez mála hodinu. Jaké jsou vaše zkušenosti?

Jan Šmuk Internet obecně

Facebook Connect - připojte se, prosím

Podle statistik již téměř 2 miliony čechů má svůj účet na Facebooku. Je tedy logické, že se na Facebook soustředí pozornost více a více společností, které se snaží tohoto ohromného počtu uživatelů využít. Díky skvělému programovému rozhraní lze Facebook integrovat s firemními weby nebo e-shopy. Integrace s Facebookem vám umožní např. zobrazovat obsah Facebooku na vašich stránkách nebo naopak, publikovat na Facebooku váš obsah a přilákat tím vaše potenciální zákazníky. V ItPlace jsme integrovali e-shop www.cockovnik.cz s Facebookem a rádi se o své zkušenosti podělíme.

V tomto článku si ukážeme, jak umožnit vašim uživatelům přihlásit se do vaší webové aplikace pomocí Facebook Connect.

Cílem je vytvořit stránku na web serveru běžícím na vašem počítači, která bude obsahovat tlačítko pro přihlášení pomocí Facebooku. Po stisknutí tlačítka budete požádáni o přihlášení na Facebook (pokud zrovna nejste přihlášení) a po úspěšném přihlášení na stránce zobrazíme, kdo je přihlášen.

Vytvoření Facebook aplikace

Aby mohly vaše stránky komunikovat s Facebookem, musíte nejprve na stránkách Facebooku vytvořit Facebook aplikaci. K tomu si musíte do svého účtu nainstalovat aplikaci Vývojář, která slouží k administraci vašich Facebook aplikací.

Aplikaci vývojář nainstalujete klepnutím na tento odkaz: http://www.facebook.com/developers. Po přihlášení budete požádáni o povolení přístupu aplikace Vývojář k vašemu účtu. Klikněte na tlačítko „Povolit“.

Aplikace Vývojář vypadá takto:

Aplikace Vývojář

Aplikace Vývojář

Stránka obsahuje mimo jiné seznam vašich Facebook aplikací a tlačítko pro vytvoření nové aplikace. Nyní klikněte na „Vytvoření nové aplikace“.

Nová aplikace

Nová aplikace

Stačí zvolit vhodné jméno pro aplikaci, souhlasit s podmínkami a stisknout „Vytvoření aplikace“. Pokud vše proběhlo správně, jste nyní v administraci vaší Facebook aplikace. Zkopírujte si API klíč a API secret, pomocí těchto údajů se budou všechna volání Facebook API z vašich stránek identifikovat.

Příprava testovací stránky

Facebook API je založené na REST webových službách, nezáleží tedy na platformě ani použitém programovacím jazyce. Vytvoření dotazu na Facebook API a zpracování výsledku volání lze dělat manuálně, ale vývojáři Facebooku poskytují také velmi šikovné knihovny v několika jazycích. V tomto článku použijeme jazyk PHP a příslušnou knihovnu.

Zde: http://svn.facebook.com/…tform.tar.gz si stáhněte knihovnu pro práci s Facebook API v PHP. Z archívu rozbalte adresář php do adresáře <APPROOT>/facebook-client, kde <APPROOT> je kořenový adresář vaší testovací web aplikace.

V našem testu budeme používat Facebook javascript knihovnu, která komunikuje s Facebookem formou AJAX volání. Internetové prohlížeče zakazují komunikaci mezi různými doménami. Naštěstí existuje způsob, jak tento zákaz obejít. Vytvořte soubor <APPROOT>/xd_re­ceiver.htm s následujícím obsahem:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
  <title>Cross-Domain Receiver Page</title>
</head>
<body>
  <script
src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.js?2" type="text/javascript"></script>
</body>
</html>

Facebook aplikace musí vědět, kde jsou vaše stránky umístěny. Toto nastavení najdete v administraci Facebook aplikace v záložce Připojit. Do políčka Connect URL vepište kořenovou adresu vašich stránek. Například:

Umístění stránek

Umístění stránek

V tomto případě je tedy soubor xd_receiver.htm přístupný na adrese: http//localhost/fb-test/xd_recei­ver.htm.

Vytvořte soubor <APPROOT>/login.php s následujícím obsahem:

<?php
$apiKey = .... vaše API key;
$apiSecret = .... vaše API secret;
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
  <title>Login</title>
</head>
<body>
<script
src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php/cs_CZ" type="text/javascript"></script>
<script>
        function facebookUserLoggedIn() {
                alert('OK!');
        }
</script>

<p>
  <a href="#" onclick="FB.Connect.requireSession(facebookUserLoggedIn); return false;" >
     <img id="fb_login_image" src="http://wiki.developers.facebook.com/images/f/f5/Connect_white_large_long.gif" />
  </a>
</p>

<script type="text/javascript">
FB_RequireFeatures(["XFBML"], function() {
        FB.Facebook.init("<?=$apiKey?>", "xd_receiver.htm");
});
</script>
</body>
</html>

V tomto kódu jsou tyto důležité části:

  1. konfigurace apiKey a apiSecret,
  2. načtení javascriptové knihovny,
  3. call-back funkce facebookUserLog­gedIn(), která se automaticky zavolá po úspěšném přihlášení na Facebook.
  4. Tlačítko pro přihlášení pomocí Facebooku. Všimněte si parametru volání funkce FB.Connect.re­quireSession(fa­cebookUserLog­gedIn). Ano, facebookUserLog­gedIn je reference na call-back funkci.
  5. Inicializace javascriptového Facebook klienta.

Struktura vaší aplikace je tedy:

<APPROOT>
   |- facebook-client
   |    |- json-wrapper
   |    |    |- .....
   |    |- facebook.php
   |    |- facebook_mobile.php
   |    |- facebookapi_php5_restlib.php
   |- xd_receiver.htm
   |- login.php

Nyní jsme připraveni na první test! V prohlížeči zadejte adresu: http://localhost/…st/login.php. Zobrazí se prázdná stránka s tradičním modrým tlačítkem „Facebook Connect“. Po stisknutí tlačítka se otevře pop-up okno s výzvou pro přihlášení do Facebooku. Po úspěšném přihlášení se zobrazí javascriptová hláška „OK!“.

Kdo tam?

Funguje to, ale jak zjistit, kdo se to vlastně přihlásil? K tomu nám poslouží PHP knihovna pro práci s Facebookem. Upravte soubor <APPROOT>/login.php tímto způsobem:

<?php
$apiKey = .... vaše API key;
$apiSecret = .... vaše API secret;

include_once "facebook_client/facebook.php";

$fb = new Facebook($apiKey, $apiSecret);
$fbUserId = $fb->user;

if($fbUserId != null) {
  echo "prihlaseny uzivatel: $fbUserId<br/>";
}
?>
...

Pokud není přihlášen žádný Facebook uživatel, má proměnná $fbUserId hodnotu null. Pokud je někdo přihlášen, je v proměnné $fbUserId identifikátor Facebook uživatele.

Toto ID lze použít pro spárování uživatele ve vaší databázi s Facebook uživatelem. Po přihlášení pomocí Facebook Connect se podíváme, zda v naší databázi existuje uživatel s tímto Facebook ID. Pokud ano, pak uživatele přihlásíme. Pokud ne, uživatele zaregistrujeme a přihlásíme.

Je také snadné změnit chování stránky login.php pro případ, že je uživatel již přihlášen. Například už nemusíme zobrazovat tlačítko pro přihlášení.

Ještě upravíme call-back funkci tak, aby se stránka automaticky po přihlášení aktualizovala:

...
<script>
        function facebookUserLoggedIn() {
                window.location='/fb-test/login.php';
        }
</script>
...

Závěr

Ukázali jsme si na jednoduchém příkladě, začít integrovat váš web s Facebookem a použít Facebook pro přihlašování uživatelů. O dalších možnostech Facebook API, jako jsou

  • zjištění informací o uživatelích,
  • poslání příspěvků na zeď uživatele,
  • vytvoření fotoalba a vložení fotografie a
  • použití komentářů,

se budete moci dočíst v dalších článcích na našem blogu.

Odkazy

Jan Šmuk Facebook , ,

Tomcat - Sdílený back-end

Téměř všechny příklady a tutoriály, jak konfigurovat Tomcat popisují jen ty nejjednodušší případy a aplikace. My jsme potřebovali Tomcat nakonfigurovat tak, aby několik web aplikací mohlo sdílet jediný modul obsahující servisní a DAO vrstvy. V tomto článku vám ukážu, jak na to.

Struktura naší aplikace

Deployment diagram

Deployment diagram

Front-end je tvořen několika webovými aplikacemi. V našem případě jsme měli pro každý výstupní formát speciální web aplikaci (WML, HTML). Tyto aplikace sdílí společný back-end, který obsahuje Spring application context a v něm nakonfigurovanou servisní a DAO vrstvu. Service locator pouze zabaluje Spring application context a webové aplikace si jej mohou najít v JNDI.

Proč je výhodou mít jediný společný back-end? V našem případě back-end obsahuje většinu business logiky, která je všem web aplikacím společná. Jedná se například o persistenci, cache, scheduler, logování statistik, atd. Další výhodou je, že můžete modifikovat web aplikaci bez nutnosti restartování back-endu, což se při vývoji hodí, protože back-end může startovat dost dlouho (v jednom z našich projektů back-end startoval půl minuty).

Testovací aplikace

Připravil jsem archív obsahující testovací aplikaci, skládající se z front-endu, back-endu a konfigurace tomcatu. Stáhněte si tomcat-example.zip a rozbalte někam na disk. Abyste mohli tuto aplikaci zkompilovat a spustit, musíte mít následující:

Po rozbalení archívu tomcat-example.zip byste měli mít na disku tuto strukturu:

───tomcat-example
   │   build.xml
   │
   ├───core
   │   │   .classpath
   │   │   .project
   │   │
   │   └───src
   │       └───itplace
   │           └───example
   │               └───core
   │                       HelloService.java
   │                       HelloServiceImpl.java
   │                       ServiceLocator.java
   │                       ServiceLocatorFactory.java
   │                       ServiceLocatorImpl.java
   │
   ├───tomcat
   │   │   run.cmd
   │   │
   │   └───conf
   │           catalina.policy
   │           catalina.properties
   │           context.xml
   │           logging.properties
   │           server.xml
   │           tomcat-users.xml
   │           web.xml
   │
   └───webapp1
       │   .classpath
       │   .project
       │
       ├───src
       │   └───itplace
       │       └───example
       │           └───webapp1
       │                   HelloServlet.java
       │
       └───webapp
           └───WEB-INF
               │   web.xml
               │
               └───classes
  • tomcat-example/core obsahuje třídy back-endu
  • tomcat-example/webapp1 je projekt obsahující web aplikaci.
  • tomcat-exmple/tomcat obsahuje konfiguraci Tomcatu a dávku run.cmd, která nastartuje nakonfigurovaný tomcat.

Spuštěním příkazu ant v adresáři tomcat-example dojde ke zkompilování obou projektů core a webapp1. Výsledek kompilace se uloží do tomcat-example/core/bin resp. tomcat-example/webap­p1/webapp/WEB-INF/classes.

Projekty core a webapp1 si můžete naimportovat do Eclipse, budete ale potřebovat říct Eclipse, kde má hledat knihovny serveru Tomcat.

Konfigurace Tomcatu

Nejprve prozkoumáme adresář tomcat-example/tomcat. Tento adresář neobsahuje instalaci Tomcatu ale pouze jeho konfiguraci. Pro spuštění naší aplikace je potřeba:

Nakonfigurovat classpath společnou všem aplikacím (common loader)

Podívejte se do souboru tomcat-example/tomcat/con­f/catalina.pro­perties. Na konci řádku 47 najdete toto:

common.loader= ...... ${catalina.base}/../core/bin

Proměnná ${catalina.base} je nastavená do adresáře tomcat-example/tomcat. Tomcat tedy do classpath společné všem web aplikacím i serveru samotného přidá třídy našeho back-endu.

Nakonfigurovat ServiceLocator

Třídu ServiceLocatorImpl mužete najít v projektu tomcat-example/core:

package itplace.example.core;

public class ServiceLocatorImpl implements ServiceLocator {
    public HelloService getHelloService() {
        return new HelloServiceImpl();
    }
}

Vidíte, že tato implementace pouze vytváří novou instanci service HelloServiceImpl. Na tomto místě v reálném projektu může být použit například Spring a jeho BeanFactory.

Jak tedy přinutit Tomcat, aby při startu vytvořil jedinou instanci třídy ServiceLocatorImpl? Podívejte se do souboru tomcat-example/tomcat/con­f/server.xml. Na řádku 40 najdete tento kousek xml:

<Resource name="serviceLocator" auth="Container"
      type="itplace.example.core.ServiceLocator"
      description=""
      factory="itplace.example.core.ServiceLocatorFactory"
/>

Pomocí tagu Resource deklarujeme JNDI resource typu itplace.example.core.ServiceLocator a jména serviceLocator. Tomcat pro jeho vytvoření použije továrnu itplace.example.core.ServiceLocatorFactory, která vypadá takto:

package itplace.example.core;

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;

public class ServiceLocatorFactory implements ObjectFactory {

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
            Hashtable<?, ?> environment) throws Exception {
        System.out.println("creating new service locator");
        return new ServiceLocatorImpl();
    }
}

Vidíte, že tovární metoda getObjectInstance jednoduše vytváří novou instanci třídy ServiceLocatorImpl.

Zpřístupnit ServiceLocator webovým aplikacím

Tomcat již tedy inicializuje náš service locator při startu. Nyní je potřeba říct, jak k němu webové aplikace mohou přistupovat. Tuto konfiguraci najdete v souboru tomcat-example/tomcat/con­f/context.xml na řádku 36:

<ResourceLink name="serviceLocator" global="serviceLocator"
    type="itplace.example.core.ServiceLocator" />

Tento kousek xml, již jen propojuje globální kontext serveru s kontextem webových aplikací. Nyní mohou web aplikace získat referenci na náš ServiceLocator standardním vyhledáním v JNDI. Podívejte se, jak toto vyhledání funguje v servletu HelloServlet, který najdete v projektu tomcat-example/webapp1:

....
Context initialContext;
try {
    initialContext = new InitialContext();
    ServiceLocator serviceLocator =
        (ServiceLocator) initialContext.lookup("java:comp/env/serviceLocator");
    PrintWriter out = response.getWriter();
    String message =
        serviceLocator.getHelloService().getHelloMessage("ITPlace");
    out.println("HelloServlet: " + message);
} catch (NamingException e) {
    throw new ServletException(e);
}
....

Referenci na náš service locator získáme vyhledáním java:comp/env/serviceLocator v JNDI. Tuto část je pak dobré přesunout do některé statické metody, aby jste nemuseli tento kód opakovat v každém servletu.

Spuštění naší aplikace

To je celá věda, nyní můžete aplikaci spustit:

  • nejprve vše zkompilujte – v adresáři tomcat-example spusťte příkaz ant.
  • v adresáři tomcat-example/tomcat spusťte dávku run.cmd.
  • otevřete svůj internetový prohlížeč a jděte na: http://localhost/…HelloServlet

Pokud vše proběhlo správně, měli byste vidět hlášku: HelloServlet: HelloServiceImpl says: hello ITPlace !!!.

Závěr

Ikdyž je Tomcat pouze servlet container, dají se s ním dělat psí kusy. Je ale na pováženou jestli není lepší pro běh aplikací podobných naší testovací použít některý z aplikačních serverů. Pokud vaše aplikace nepotřebuje vlastnosti a funkcionality aplikačního serveru a vystačí si se Springem (jako to bylo v našem případě), myslím, že je Tomcat díky své jednoduchosti dobrá volba.

Odkazy

Jan Šmuk Nezařazené

VisualVM - vítězství v soutěži

15.08.2009

Náš článek VisualVM – tool for profiling Java applications zvítězil v soutěži Java VisualVM Blogging Contest.

Článek popisuje, jak jsem postupoval při odhalování slabých míst ve hře Java Dash. Neobsahuje ale řešení těchto problémů, což je velká škoda. Slibuji, až bude čas, ještě se k tomu vrátím :)

Ale i přesto byl tento článek vybrán jako jeden z nejlepších v poměrně silné konkurenci.

Odkazy

Jan Šmuk Java, Performance

VisualVM - tool for profiling Java applications

VisualVM is tool for profiling Java applications made by Sun. Today I would like to write about my experience with it.

Some time ago I was experimenting with game programming in Java. I wrote clone of famous Boulder Dash game. When playing the game on different machines I found out that it eats terrible amount of CPU time. What? The original was running nicely on 8bits ZX Spectrum ;). I started looking into the code and optimize it. Some hours later the performance got better but still not good enough. I came to the point where I could
not proceed any further. All my algorithms looked nice and clean. I delayed the solution and left it as it was.

Recently I have heard about VisualVM and I decided to give it a try.

Making it run

Installation was smooth. I just downloaded the zip archive from https://visualvm.dev.java.net/, extracted it and run. The first run of VisualVM resulted into warning message: Local Applications Cannot Be Detected. But the issue is nicely documented directly on VisualVM site and the solution took me only few seconds (see https://visualvm.dev.java.net/…hooting.html#… for details).

Finding a big spender

After I started-up the game i was able to see it as local java application in VisualVM applications panel to the left:

VisualVM Monitor

VisualVM Monitor

As you can see it is using up to 80% of my 2.4GHz Sempron which is quite a lot. I was going to find out what is eating so much performance so i clicked on Profiler tab and opened the settings by checking the checkbox to the right:

VisualVM Profiler

VisualVM Profiler

At first I left all settings as set by default except Start profiling from classes, where I put com.spekacek.boulder.**. Then I started up CPU profiling. Then I was playing the game for few moments – profiling slows everything down so it was not a big fun. Then I hit Snapshot button and I got following report:

VisualVM Profiler snapshot

VisualVM Profiler snapshot

In CPU snapshot view you can see how much time did it take to process each method. In the case of my game the obvious winner is GamePanel.paintLevelData() method. It was called 3858 times and sum of the times spent calling it is 38865 miliseconds which is 70.2% of time in AWT-EventQueue-1 thread. Look at Self time item right beneath GamePanel.paintLevelData(). This is time spend calling not instrumented methods – methods which don't match with the settings we did before starting the profiling.

To be able to look more deeply into what is going on in GamePanel.paintLevelData() I started-up new profiling session. this time I set Start profiling from classes to com.spekacek.boulder.view.GamePanel and cleared everything in Do not profile classes setting – I want to profile everything starting from com.spekacek.boulder.view.GamePanel and I don't want any exclusions. Then I started-up the CPU profiling again and after a while of playing the game I made another snapshot. This is what I got:

VisualVM Profiler snapshot

VisualVM Profiler snapshot

Now you can see that inside GamePanel.paintLevelData() method the biggest spender is SunGraphics2D.drawImage() method. It was called 313218 times and sum of the times spent calling it is 21622 miliseconds.

So now I know where the problem lies and I can focus myself on fixing it. I know that I have to paint game levels faster. I can try either to reduce number of SunGraphics2D.drawImage() calls or find a faster alternative. But this is not the topic of this article.

Conclusion

I like this tool. It is very simple an intuitive – not very different from highly advanced JProfiler. It can do much more than I described in this humble article including memory profiling, heap viewer, etc. In short it can help you to find weak spots in your application for free. Good job guys! :)

Some links

Jan Šmuk Performance

Effective search under a subtree

Almost every blogging system supports assigning articles into categories and subcategories. Listing articles which belong to a subtree of categories may be quite expensive operation especially if the number of articles is very high. In this article I would like to describe one of possible solutions.

Category Tree

Tree of article categories could look something like:

  • Music
    • Rock
    • Pop
    • Jazz
  • Sport
    • Snowboarding
    • Skiing
      • Cross-country skiing
      • Downhill skiing
    • Kiteboarding
  • Traveling
    • Adventure
    • Leisure

The most simple way of assigning an article to a category would be to give each category an id (e.g. Music=1, Rock=2, Pop=3, Jazz=4, Sport=5,…) and add categoryId column into Article database table. Then if we wanted to get all articles from category Rock we could do it simply by running something like:

select * from Article where categoryId=2

But getting all articles from all subtree under Music category will be more complicated, we have to make list of category ids from all Music subtree and query something like:

select * from Article where categoryId in (1,2,3,4)

Imagine much larger category tree, the number of categories in a subtree may be high. We wanted something more efficient and still we would like to be able to add new categories into a tree.

Limited tree

In our project the efficiency was really top priority, therefore we were able to accept slight limitations to our category tree which could give us better performance:

  • Maximum tree depth is defined once and then constant. Let's name this parameter maxLevels.
  • Maximum number of children a parent may have is defined once and then constant. This parameter can be called maxChildren.

Limited tree evaluation

Position of every tree node can be described by path to it from the tree root. Look at following tree:

  • Music {1}
    • Rock {1,1}
    • Pop {1,2}
    • Jazz {1,3}
  • Sport {2}
    • Snowboarding {2,1}
    • Skiing {2,2}
      • Cross-country skiing {2,2,1}
      • Downhill skiing {2,2,2}
    • Kiteboarding {2,3}
  • Traveling {3}
    • Adventure {3,1}
    • Leisure {3,2}

Path can have maximally maxLevels items and each item in path is in range from 1 to maxChildren. For simplification we filled paths shorter then maxLevels by 0s. So for example if maxLevels = 6 then path to Pop node is {1,2,0,0,0,0}. Then we can express a path as int array of size maxLevels.

Now for each node we can calculate a single integral value using this equation (the operator ^ means power):

value(path) = path[0]*(maxChildren+1)^(maxLevels-1)
 + path[1]*(maxChildren+1)^(maxLevels-2)
 + path[2]*(maxChildren+1)^(maxLevels-3)
 + ...
 + path[maxLevels-1]*(maxChildren+1)

Does this equation resemble something to you? Yes you have seen it before, it is equation for conversion a number from numeric system with base (maxLevels+1) into decimal system.

Into Article table we add new column categoryValue into which we will put the calculated value based on a category an article belongs to.

Example

Now why we did all this? Let me show you on example. The tree we will use looks the same as above. At first we have to define maxLevels and maxChildren parameters. Let's define maxLevels=4 and maxChildren=7 (When evaluating tree nodes we will therefore convert number from octal system into decimal.)

We have written a new article and we want to assign it to category Skiing (path is {2,2} which is equal to {2,2,0,0}). So we can calculate the categoryValue column value using equation defined above:

value(Skiing {2,2,0,0}) = 2*8^3 + 2*8^2 + 0*8^1 + 0*8^0 = 1024 + 128 = 1152

Then let's say we want to list all articles from category Sport {2} and below. It means everything between Sport {2} inclusive and Traveling {3} (which is next sibling to Sport) exclusive. So we calculate lower and upper limits:

lower = value(Sport {2,0,0,0}) = 2*8^3 + 0*8^2 + 0*8^1 + 0*8^0 = 1024
upper = value(Traveling {3,0,0,0}) = 3*8^3 + 0*8^2 + 0*8^1 + 0*8^0 = 1536

The sql to get all articles from under the Sport node is then:

select * from Article where categoryValue >= :lower and categoryValue < :upper

It looks more complicated then it really is. For testing we use maxChildren=9 because then paths form direcly decimal numbers. For example when maxLevels=5 then:

value(2,3,4) = value(2,3,4,0,0) = 23400

Real life

Tree parameters are limited by type we store calculated value in. We used long java type which is 64 bits type. We set maxLevels=6 and maxChildren=10. It is sufficient for our needs and very fast.

Jan Šmuk Programming

Jak si JBPM rozumí se Springem

09.04.2009

V posledním článku Jak jsme použili JBPM jsem popsal, jak a proč jsme použili JBPM. Dnes se podělím o naše zkušenosti s integrací se Springem.

Spring a JBPM se v jednom z projektů, kde jsme oba použili, stýkají ve dvou místech:

  1. Konfigurace – můžeme například potřebovat, aby JBPM použil pro svůj běh již existující nakonfigurovanou hibernate session factory, kterou používá i zbytek naší aplikace.
  2. Spouštění akcí – chceme, aby akce byly součástí našeho spring kontextu.

Spring Modules

Začali jsme tím, že jsme použili springmodules-jbpm31, který již řeší oba výše zmíněné body. Takto může vypadat kousek definice spring kontextu:

...
<!-- jBPM configuration -->
<bean id="jbpmConfiguration"
    class="org.springmodules.workflow.jbpm31.LocalJbpmConfigurationFactoryBean">
  <property name="sessionFactory" ref="sessionFactory" />
  <property name="configuration" value="classpath:jbpm.cfg.xml" />
  <property name="createSchema" value="false" />
  <property name="processDefinitionsResources">
    <list>
      <value>classpath:createArticle.xml</value>
      <value>classpath:updateArticle.xml</value>
    </list>
  </property>
</bean>
...

Jak springmodules řeší druhý bod, tedy spouštění akcí? Podlě mě ne příliš šikovně. Představme si akci, která posílá uživateli email. Chceme použít Ioc k nastavení reference na objekt, který bude obstarávat posílání emailů. Springmodules toto řeší použitím bean proxy, které řekneme id našeho action handleru ve spring kontextu. Deklarace takové akce pak vypadá nějak takto:

...
<action name="myAction" config-type="bean"
   class="org.springmodules.workflow.jbpm31.JbpmHandlerProxy">
 <targetBean>sendEmailActionHandler</targetBean>
 <factoryKey>jbpmConfiguration</factoryKey>
</action>
...

Všimněte si atributu config-type. Pokud je jeho hodnota bean, pak JBPM při vykonávání akce vytváří novou instanci třídy specifikované v atributu class. Poté nastavuje této instanci její vlastnosti na základě jmen a obsahů vnořených tagů. Tedy ve výše uvedeném případě je vytvořena nová instance třídy org.springmodules.workflow.jbpm31.JbpmHandlerProxy a jsou na ni zavolány metody setTargetBean("sendEmailActionHandler") a setFactoryKey("jbpmConfiguration"), což jsou vlastnosti nutné k fungování této proxy.

Malý trik

Zdá se vám tento způsob složitý? Mi se zdál složitý až zbytečně moc. Jednak takto sestavené akce hyzdí definici procesu. Dále jste si jistě všimli, že zde chybí definice těla posílaného emailu a adresa příjemce. Nenašel jsem lepší způsob, jak mít tyto dvě hodnoty přímo v definici procesu, než je ukládat jako procesní proměnné. Proto jsem hledal způsob, jak být schopen definovat akci ve stylu:

...
<action name="myAction" config-type="bean"
   class="itplace.jbpm.SendEmailActionHandler">
 <emailAddress>recipient@server.com</emailAddress>
 <emailBody>hello</factoryKey>
</action>
...

Samotná akce pak bude vypadat takto:

package itplace.jbpm;

import org.jbpm.graph.def.ActionHandler;
import org.jbpm.graph.exe.ExecutionContext;

public class SendEmailActionHandler implements ActionHandler {
  private EmailSender emailSender;
  private String emailAddress;
  private String emailBody;

  public void execute(ExecutionContext executionContext) throws Exception {
    emailSender.send(emailAddress, emailBody);
  }

  public void setEmailSender(EmailSender emailSender) {
    this.emailSender = emailSender;
  }

  public void setEmailAddress(String emailAddress) {
    this.emailAddress = emailAddress;
  }

  public void setEmailBody(String emailBody) {
    this.emailBody= emailBody;
  }

}

K identifikací našich akci ve spring kontextu jsme použili konvenci pojmenovávání:

  • pokud je hodnota atributu class například itplace.jbpm.SendEmailActionHandler, pak budeme ve spring kontextu hledat bean s id sendEmailActionHandler – tedy jednoduché jméno třídy s malým písmenkem na začátku. Definice naši akce ve spring kontextu tedy bude:
...
<bean id="sendEmailActionHandler" class="itplace.jbpm.SendEmailActionHandler"
    singleton="true">
  <property name="emailSender" ref="emailSender"/>
</bean>
...

Nyní potřebujeme JBPM donutit, aby nevytvářelo novou instanci třídy akce, ale aby se použil bean definovaný ve spring kontextu s identifikací odpovídající konvenci. Poté už JBPM může standardně nastavit vlastnosti emailAddress a emailBody.

JBPM si ve třídě Delegation drží statickou mapu objektů typu Instantiator. Instantiator je zodpovědný za vytváření instancí akcí. Klíčem v této mapě je String a JBPM v této mapě hledá Instantiator podle obsahu atributu config-type. Standardně je pod klíčem bean v mapě uložený BeanInstantiator. Mým cílem bylo tento rozšířit.

Nejprve jsem si vytvořil třídu, která mi umožní obecně manipulovat s mapou instantiatorů:

package itplace.jbpm;

import java.util.Map;
import org.jbpm.instantiation.Delegation;
import org.jbpm.instantiation.Instantiator;

public class AdditionalInstantiatorsDeclaration extends Delegation {
  public void setInstantiators(Map<String, Instantiator> instantiators) {
    instantiatorCache.putAll(instantiators);
  }
}

instantiatorCache je ona zmíněná statická mapa, do které nyní můžeme přidat libovolný instantiator pod libovolným jménem. Náš nový instantiator vypadá nějak takto:

package itplace.jbpm;

import org.jbpm.instantiation.BeanInstantiator;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

public class SpringInstantiator extends BeanInstantiator
      implements BeanFactoryAware {
    private BeanFactory beanFactory;

    protected Object newInstance(Class clazz) {
        String beanName = clazz.getSimpleName();
        beanName = beanName.substring(0, 1).toLowerCase()
          + beanName.substring(1);
        if (beanFactory.containsBean(beanName)) {
            return beanFactory.getBean(beanName);
        }

        return super.newInstance(clazz);
    }

    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }
}

SpringInstantiator implementuje rozhraní BeanFactoryAware, takže se spring postará o vložení bean factory. A pak jsme rozšířili standardní BeanInstantiator a změnili chování metody newInstance, která nyní nejprve hledá v bean factory bean podle konvence a pokud jej nenajde pouze vytvoří novou instanci.

Do spring kontextu je tedy zapotřebí přidat následující:

...
<bean id="additionalInstantiatorsDeclaration"
    class="itplace.jbpm.AdditionalInstantiatorsDeclaration">
  <property name="instantiators">
    <map>
      <entry>
        <key><value>bean</value></key>
        <bean class="itplace.jbpm.SpringInstantiator" />
      </entry>
    </map>
  </property>
</bean>
...

Je ještě vhodné říct springu, že JBPM je závislý na beanu additionalInstantiatorsDeclaration, stačí přidat depends-on atribut do JBPM konfigurace:

...
<!-- jBPM configuration -->
<bean id="jbpmConfiguration" depends-on="additionalInstantiatorsDeclaration"
    class="org.springmodules.workflow.jbpm31.LocalJbpmConfigurationFactoryBean">
...

Závěr

Viděli jste, jak jednoduše lze JBPM integrovat se Springem. Také jste si možná udělali obrázek o zdařilosti a praktičnosti spring jbpm modulu. Způsob, který jsem vám předvedl, naprosto vyhovuje našim požadavkům a funguje opravdu velmi dobře.

Zdroje:

Jan Šmuk Java , ,

Jak jsme použili JBPM

29.03.2009

V tomto článku bych se chtěl podělit o praktické zkušenosti s JBPM. Článek neobsahuje všechny informace ani výčet všech vlastností JBPM, mým cílem je vám ukázat, jak jsme JBPM použili v jednom z našich projektů.

Zadání

Představte si následující požadavek na váš nový blogovací systém:

Uživatel přidává nový článek. Na základě konfigurace se provede jedna z těchto variant:

  • Bez moderace: Článek je ihned vytvořen a zveřejněn.
  • Moderace apriori: Článek je vytvořen, poslán k ověření moderátorem, pokud jej moderátor akceptuje, je zveřejněn.
  • Moderace aposteriori: Článek je vytvořen a zveřejněn, poslán k ověření moderátorem, pokud jej moderátor odmítne, je označen jako neveřejný.

Analýza

Jaké operace nad článkem tedy náš systém musí podporovat?

  • Vytvořit neveřejný článek;
  • zveřejnit článek;
  • zneveřejnit článek;
  • smazat článek;
  • poslat článek ke schválení moderací.

Z těchto operací nyní můžeme sestavit kteroukoli z variant. Nejjednodušší je v našem případě varianta bez moderace, my se však zaměříme na variantu moderace apriori. Konfigurace našeho systému musí definovat, která z variant se provede, tedy které operace, v jakém pořadí a za jakých podmínek se provedou – jinými slovy konfigurace musí definovat proces vytvoření článku. Samotná moderace může nějakou chvíli trvat, bude tedy třeba provádění procesu po odeslání článku k moderaci přerušit a pokračovat až moderátor o článku rozhodne. Variantu moderace apriori můžeme znázornit jako stavový diagram:

Moderace apriori

Schéma moderace apriori

Návrh

Jak funguje moderace? Poslání článku k moderaci si můžeme představit jako přidání článku do fronty. Moderátor tento článek z fronty vybere a rozhodne. Je tedy zřejmé že náš proces musíme rozdělit do dvou asynchronních částí, provádění bude probíhat ve dvou vláknech:

Vlákno 1:

  1. vytvoř neveřejný článek
  2. pošli článek k moderaci
  3. konec vlákna

Vlákno 2:

  1. vyber článek z fronty
  2. moderátor rozhodne, jestli akceptovat či odmítnout
  3. pokud moderátor akceptuje, zveřejni článek
  4. konec vlákna

Jak budeme operace spouštět? Jak si zapamatovat, kde jsme v provádění procesu skončili? Jak pak pokračovat v provádění existujícího procesu? Jedna z možných odpovědí na tyto otázky je použít JBPM. Co JBPM nabízí?

  • JBPM je nástroj pro spouštění business procesů.
  • Pomocí jazyka JPDL (Jboss Process Definition Language) lze snadno a přehledně definovat graf procesu.
  • Definice procesů lze verzovat. To v praxi znamená, že pokud změníme definici procesu, právě probíhající instance tohoto procesu to neovlivní.
  • Jsou podporovány i takzvané wait-states, uzly ve kterých se provádění procesu zastaví.
  • JBPM obsahuje podporu pro persistenci procesů. Stav právě probíhajících procesů je uložen v databázi.
  • Instance procesu může používat procesní proměnné které jsou také ukládány spolu se stavem v databázi.
  • Na přechody i na uzly lze navázat akce.
  • Snadná integrace se Springem.
  • Nástroj pro grafický návrh procesů.

Nechci tady popisovat všechny vlastnosti JBPM ani JPDL, na stránkách Jboss můžete najít výbornou dokumentaci. Dejme se tedy rovnou do implementace.

Implementace

Ukažme si, jak bude vypadat definice procesu moderace apriori v jazyce JPDL, jednotlivé části si pak vysvětlíme:

<process-definition
    name="createArticle" xmlns="urn:jbpm.org:jpdl-3.2">

  <start-state name="start state">
    <transition to="create invisible article"/>
  </start-state>

  <node name="create invisible article">
    <event type="node-enter">
      <action config-type="bean"
          class="blog.handler.CreateInvisibleArticle">
      </action>
    </event>
    <transition to="send to moderation"/>
  </node>

  <node name="send to moderation">
    <event type="node-enter">
      <action config-type="bean"
          class="blog.handler.SendToModeration">
      </action>
    </event>
    <transition to="wait for moderation"/>
  </node>

  <state name="wait for moderation">
    <transition to="process decision"/>
  </state>

  <decision name="process decision">
    <transition to="make visible">
      <condition>#{decision.accept == true}</condition>
    </transition>
    <transition to="end">
      <condition>#{decision.accept == false}</condition>
    </transition>
  </decision>

  <node name="make visible">
    <event type="node-enter">
      <action config-type="bean"
          class="blog.handler.MakeVisible">
      </action>
    </event>
    <transition to="end"/>
  </node>

  <end-state name="end" />

</process-definition>

Takže JPDL není nic jiného než XML které popisuje graf business procesu. Vidíme, že se tento graf skládá z uzlů, přechodů a akcí. Speciálním typem uzlu je stav. V našem procesu máme hned tři stavy: „start state“, „wait for moderation“ a „end“. Provádění procesu, se v těchto uzlech zastaví. Jak bude vypadat kód, který vytvoří novou instanci procesu a spustí ji?

public void createArticle(Article article) {
  JbpmContext context = jbpmConfiguration.createJbpmContext();
  try {
    ProcessInstance instance = context.newProcessInstance("createArticle");
    instance.getContextInstance().setTransientVariable("article", article);
    instance.signal();
    System.out.prntln("stopped in state: "+instance.getRootToken().getNode()
        .getName());
  } finally {
    context.close();
  }
}

Zde již počítáme s tím že máme připravenou konfiguraci JBPM v proměnné jbpmConfiguration. Nová instance procesu, kterou zde vytváříme se nachází ve stavu „start state“. Akce, které se budou v průběhu provádění instance procesu spouštět, budou potřebovat vědět jaký článek vytváříme. Proto nastavíme proměnnou „article“. Tato proměnná může být transientní, protože nepotřebujeme aby její hodnotu JBPM ukládalo v databázi.

Zavoláním instance.signal() se spustí samotné provádění. Provádění probíhá synchronně, tedy ve stejném vlákně a když se dostane do prvního stavu, v našem případě „wait for moderation“, pak probíhání skončí a tím i provádění metody signal(). Naše metoda vypíše na standartní výstup řádek:

stopped in state: wait for moderation

Teď se podíváme, jak funguje spouštění akcí. Akci lze pověsit na událost. V našem případě na událost typu „node-enter“. Akce musí implementovat rozhraní org.jbpm.graph.def.ActionHandler. JBPM vytvoří novou instanci třídy uvedené v atributu class tagu action a zavolá metodu execute. Implementace třídy blog.handler.CreateInvisibleArticle může vypadat třeba takto:

package blog.handler;

import blog.Article;
import org.jbpm.graph.def.ActionHandler;

public class CreateInvisibleArticle implements ActionHandler {
    public void execute(ExecutionContext executionContext) throws Exception {
        Article article = executionContext.getContextInstance()
            .getTransientVariable("article");
        article.setVisible(false);
        getArticleDao().saveArticle(article);
    }
    ...
}

Tato implementace počítá s tím, že instance procesu obsahuje transientní proměnnou „article“ a jednoduše volá DAO vrstvu, aby článek uložila. Jak bude naimplementovaná metoda getArticleDao() není tématem tohoto článku. K tomuto problému se vrátím v jíném článku, ve kterém se budu věnovat integraci JBPM se Springem.

Podobně bude vypadat implementace blog.handler.MakeVisible:

package blog.handler;

import blog.Article;
import org.jbpm.graph.def.ActionHandler;

public class MakeVisible implements ActionHandler {
    public void execute(ExecutionContext executionContext) throws Exception {
        Article article = executionContext.getContextInstance()
            .getTransientVariable("article");
        article.setVisible(true);
        getArticleDao().updateArticle(article);
    }
    ...
}

Zbývá nám poslední akce blog.handler.SendToModeration:

package blog.handler;

import blog.Article;
import org.jbpm.graph.def.ActionHandler;

public class SendToModeration implements ActionHandler {
    public void execute(ExecutionContext executionContext) throws Exception {
        Article article = executionContext.getContextInstance()
            .getTransientVariable("article");
        long processInstanceId = executionContext.getProcessInstance().getId();
        getModerationQueue().add(article, processInstanceId);
    }
    ...
}

Zajímavá je zde lokální proměnná processInstanceId, k tomu abychom po moderátorově rozhodnutí mohli v provádění procesu pokračovat, musíme vědět ke které instanci procesu se moderovaný článek vztahuje. Proto do moderační fronty posíláme i id instance procesu. Toto id pak využijeme pro získání existující instance procesu. Následující metoda je zavolána když moderátor akceptuje článek:

...
public void acceptArticle(Article article, long processInstanceId) {
  JbpmContext context = jbpmConfiguration.createJbpmContext();
  try {
    ProcessInstance instance = context.getProcessInstance(processInstanceId);
    System.out.println("currently in state: "+instance.getRootToken().getNode()
        .getName());
    instance.getContextInstance().setTransientVariable("article", article);
    Decision decision = new Decision();
    decision.setAccept(true);
    instance.getContextInstance().setTransientVariable("decision", decision);
    instance.signal();
    System.out.println("stopped in state: "+instance.getRootToken().getNode()
        .getName());
  } finally {
    context.close();
  }
}
...

Nejprve si vyžádáme instanci procesu podle processInstanceId, stejně jako v metodě, createArticle musíme nastavit transientní proměnnou „article“, aby další akce měly přístup k článku. Dále nastavíme transientní proměnnou „decision“, která obsahuje informaci o tom, jak moderátor rozhodl. Poté se zavolá metoda signal(), čímž dojde k pokračování instance procesu. Metoda acceptArticle vypíše na standardní výstup:

currently in state: wait for moderation
stopped in state: end

Proměnná decision se používá při rozhodování, který přechod použít z uzlu „process decision“:

<decision name="process decision">
  <transition to="make visible">
    <condition>#{decision.accept == true}</condition>
  </transition>
  <transition to="end">
    <condition>#{decision.accept == false}</condition>
  </transition>
</decision>

JBPM v uzlu typu „decision“ vybere první přechod, jehož podmínka je splněna. Pokud není žádná podmínka splněná, vybere se první definovaný přechod.

Závěr

Viděli jste, jak jsme postupovali a jak a proč jsme se rozhodli pro JBPM. Zatím to vypadá, že jsme zvolili správně. S dostatečně velikou množinou akcí, lze pohou změnou definic procesů měnit chování našeho systému.

Zdroje

Jan Šmuk Java ,