2010. február 25., csütörtök

JavaScript EE, JSON válaszol

Előző bejegyzésben bemutattam miként lehet Java környezetünket rávenni JavaScript futtatására. Folytatásban egy servlet-en keresztül rávesszük a web-konténert, hogy JSON válaszokat adjon a kérésekre.
Legelső lépésként egy hagyományos servletet kell létrehoznunk, majd a doGet metódusában megvalósítani a JavaScript futtatását.
Java kód:
Compilable scriptEngine = (Compilable) new ScriptEngineManager().getEngineByName("JavaScript");

ScriptContext scriptContext = new SimpleScriptContext();
scriptContext.setWriter(response.getWriter());
scriptContext.setAttribute("config", getServletConfig(), ScriptContext.ENGINE_SCOPE);
scriptContext.setAttribute("response", response, ScriptContext.ENGINE_SCOPE);
scriptContext.setReader(new InputStreamReader(JSONServlet.class.getResourceAsStream("json.js")));

try {
 CompiledScript script = scriptEngine.compile(scriptContext.getReader());
 script.eval(scriptContext);
} catch (ScriptException e) {}
Látható különbség, hogy ScriptEngine helyett Compilable osztályt használunk. A Compilable interfészt megvalósító értelmező annyival nyújt több szolgáltatást, hogy a lefordított JavaScripteket újra fel lehet használni újrafordítás nélkül, így segítségével gyorsító-tárazni lehet a scipteket csökkentve a szerver terhelését. Következő lépésként, mivel most nem a JavaScript visszatérési értékére van szükségünk (és azt sem szeretnénk, hogy az alapértelmezett kimenetre írjon), hanem azt szeretnénk, hogy a JavaScript közvetlenül a servlet kimenetre írjon, példányosítanunk kell egy ScriptContext objektumot, és referenciát adnunk a ServletResponse-ra. Természetesen van lehetőségünk Java objektumok átadására a JavaScriptnek, de ebben az esetben a ScriptContext osztálynak közvetlenül adhatjuk át a paramétereket a setAttribute metódus meghívásával. Ezután nincs más dolgunk, mint megadni a JavaScript fájlunkat, és futtatni a scriptet.
JavaScript kód:
response.setHeader("Cache-Control", "no-cache");
response.setContentType("application/json");

var json = {
response: {
  status: response.getStatus(),
  content_type: String(response.getContentType())
},
config: {
  servlet: String(config.getServletName())
}
};

println(json.toSource());
A scriptben jól látható miként használjuk a Java objektumokat, és végül miként írjunk a kimenetre.
A böngészőben az alábbi válasz jelenik meg:
({response:{status:200, content_type:"text/plain;charset=ISO-8859-1"}, config:{servlet:"JSONServlet"}})
Folyt köv...

2010. február 19., péntek

JavaScript EE, első felvonás

Bár a téma nem új-keletű, én most jutottam el oda, hogy legalább kipróbálás szinten foglalkozzam vele. Java SE 6-os verziója óta megtalálható benne a Mozilla Rhinó-ja, amely egy JavaScript értelmező, és segítségével a JVM-en lehetséges JavaScript kódok futtatása Java kódból. Nincs más dolgunk, mint importálni a javax.script csomagot, és a ScriptEngineManager segítségével példányosítani egy ScriptEngine értelmezőt.
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
Java kód:
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.StringBuilder;
import javax.script.*;

public class SimpleJavaScriptTest {
public static void main(String[] args) throws Exception {
//Get reference to the engine
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
//Bind some Java object to the script
Bindings vars = new SimpleBindings();
vars.put("testString", "This is a test string.");
StringBuilder stringBuilder = new StringBuilder();
vars.put("testStringBuilder", stringBuilder);
//Read the JavaScript code
Reader scriptReader = new InputStreamReader(SimpleJavaScriptTest.class.getResourceAsStream("test.js"));
try {
//Run the JavaScript
engine.eval(scriptReader, vars);

System.out.println(stringBuilder.toString());
System.out.println(vars.get("testFile").getClass());
System.out.println(vars.get("jsObject").getClass());
} finally {
scriptReader.close();
}
}
}
JavaScript kód:
function printType(obj) {
if (obj.getClass) println("Java object: " + obj.getClass().name);
else println("JS object: " + obj.toSource());
}
printType(testString);

printType(testStringBuilder);
testStringBuilder.append("This content added by JavaScript.");

var testFile = new java.io.File("/");
printType(testFile);

var jsObject = {x: 1, y: { u: 2, v: 3 }};
printType(jsObject);
A példát futtatva az alábbi eredményt kapjuk:
JS object: (new String("This is a test string."))
Java object: java.lang.StringBuilderJava object: java.io.File JS object: ({x:1, y:{u:2, v:3}})
This content added by JavaScript.
class java.io.File
class sun.org.mozilla.javascript.internal.NativeObject
Mint az jól lát-szódik szinte teljes az átjárás a két kód között, bemenő paramétert megfelelő JavaScript típusra cast-olja az értelmező, ennek sikertelensége esetén Java objektumként viselkedik, annak minden funkciójával. Továbbmenve JavaScript kódunkban is van lehetőség Java objektumok létrehozására, melyeket a script futása után elkérhetünk az értelmezőtől. Amennyiben olyan változót kapunk a scriptből, amit Java oldalon nem lehet megfeleltetni egyik osztálynak sem, akkor azzal egy dolgot tehetünk, paraméterként egy másik JavaScriptnek átadhatjuk.
Ezen a ponton merülhet fel a kérdés, hogy valójában ez mire is való? A válasz erre a kérdésre eléggé összetett, és nem is célom maradéktalanul körbejárni. Egyrészt ezzel a módszerrel tudjuk ötvözni a két nyelv tulajdonságait, tehát a Java robusztusságát a JavaScript egyszerűségével összepárosítva kiszélesedik a programozói paletta. Megjegyzem erre a célra nem csak JavaScriptet használhatunk, akár Groovie, vagy Scala kódot is integrálhatunk Java programunkba, ráadásul utóbbiak Java bytecode-ra fordulnak, ami mélyebb integrációra ad lehetőséget. Másrészt kliens oldali scriptjeinket is futtathatjuk server oldalon, ami azt jelenti, hogy nem kell két helyen, két környezetben implementálnunk az azonos működést, hanem szerver oldalon használhatjuk a kliens oldali program-kódokat. Hagyományosan egy űrlap elküldése esetén a böngészőben történik egy előellenőrzés, hogy az adatok formailag megfelelnek-e a követelménynek, ezzel csökkentve a hálózati forgalmat. Formailag helyes adatok elküldésre kerülnek, ahol szerver oldalon újra ellenőrizni kell őket, mind formailag, mind már adat-logikailag is. Mivel szerver környezetben nem a JavaScript az elterjedt programozási nyelv, a formai ellenőrzést sajnos ott is implementálni kell. Ehelyett a kliens oldali JavaScript-ünket nyugodtan felhasználhatjuk, időt, energiát, költséget, és nem utolsó sorban hiba-lehetőséget megspórolva.
Java kód:
engine.eval(scriptReader);
if (engine instanceof Invocable) {
Invocable invEngine = (Invocable) engine;
Object result = invEngine.invokeFunction("testStringLength", "valid string", 2);
System.out.println("Result: " + result + "(" + result.getClass().getName() + ")");
result = invEngine.invokeFunction("testStringLength", "invalid string", 20);
System.out.println("Result: " + result + "(" + result.getClass().getName() + ")");
} else System.out.println("NOT Invocable");
JavaScript kód:
function testStringLength(input, lenght) {
return input.length >= lenght ? true : false;
}
Eredmény:
Result: true(java.lang.Boolean)
Result: false(java.lang.Boolean)
Bár a példa nem tükrözi a technológia hasznosságát (így még talán bonyolultabb is), viszont bemutatja, hogyan tudunk JavaScript függvényeket meghívni, és azok visszatérési értékét felhasználni Java kódunkban. Folyt köv...
Aki szeretne a témáról bővebben is olvasni, az itt talál információt.

2010. február 6., szombat

@Aszinkron EJB >=3.1 hívás

Az EJB 3.1 megjelenése számos újítást és egyszerűsítést hozott a rendszerbe, a fejlesztők, mint ahogy a 2.x-ről 3.0-ra váltáskor is, igyekeztek még kényelmesebbé tenni az enterprise alkalmazások készítését. Mivel mi Glassfish alkalmazás-szervert használunk, és a kiadást követő napokban kezdtünk bele egy új projektbe, úgy határoztunk, hogy a friss és ropogós EJB-t használjuk (eddig nem bántuk meg) az implementáláshoz. Az új EJB-t tanulmányozva szembeötlött egy lehetőség, ami mellett nem tudtam szó nélkül elmenni. Úgy néz ki, hogy nem csak nekem jutott eszembe, hogy az alkalmazást, amit fejlesztek párhuzamosítsam, hanem az EJB fejlesztőinek is, ezért megalkották az aszinkron EJB hívás fogalmát. A dolog igen egyszerű, a hagyományokhoz híven @Annotációval kell megjelölni azokat a SessionBean metódusokat, amiket nem a hagyományos módon szeretnénk kiajánlani.
package sample;

import javax.ejb.Asynchronous;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;

@LocalBean
@Stateless
public class TestBean {

    @Asynchronous
    public void asyncMethod() {
     //some functionality
    }
}
A metódus meghívása a megszokott módon történik, viszont az abban elhelyezett utasítások egy új szálon fognak futni. Ez eddig egyszerű, de mi történik, ha szeretnénk, hogy a hívás valamilyen visszatérési értéket is prezentáljon? Ezt úgy tudjuk megtenni, hogy visszatérési értékként a java.util.concurrent.Future<V> interfészt jelöljük meg, és annak valamely implementációját adjuk vissza.
package sample;

import java.util.concurrent.Future;
import javax.ejb.AsyncResult;
import javax.ejb.Asynchronous;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;

@LocalBean
@Stateless
public class TestBean {

    @Asynchronous
    public Future<String> asyncMethod() {
        try { Thread.sleep(1000);
        } catch (Exception ex) {}
        return new AsyncResult<String>("finish");
    }
}
A dolog érdekessége a hívó oldalon történik, hiszen valahogy kezelni kell tudni, hogy az aszinkron hívás éppen milyen állapotban van.
Future<String> asyncResponse = testBean.asyncMethod();
//some functionality
while (!asyncResponse.isDone()) {
    try { Thread.sleep(100);
    } catch (Exception ex) {}
}

if (!asyncResponse.isCancelled()) {
    try {
        String response = asyncResponse.get();
    } catch (InterruptedException e) {
    } catch (ExecutionException e) {}
}
Láthatjuk mi módon tudjuk "összeszinkronizálni" a hívásokat a szülő szálban. Természetesen meg is lehet szakítani a kérést, a Future cancel metódusával, illetve a rendelkezésünkre állnak a hagyományos Thread notify, notifyAll, és wait metódusai.

2010. február 5., péntek

Btrace bemelegítés

Egy Sun-os konferencián hallottam először a Dtrace nevű eszközről, mely egy dinamikus nyom-követő. Sajnos használható formában csak Solaris vagy OpenSolaris platformokra érhető el, és mivel eléggé kernel szintű integrációja van a rendszerrel nehézkes a portolása. Sokáig vártam egy Linux-os verzióra, de sajnos még a mai napig sincs használható állapotban (vagy csak én nem boldogultam vele).
A Dtrace működése röviden annyi, hogy van egy saját programozási nyelve a D, és ezen a nyelven kell megírnunk a kernelnek, hogy a vizsgált alkalmazás mely állapot-változásait figyelje. A dolog szépsége, hogy az alkalmzásunkba nem kell "hook" pontokat elhelyezni, tehát a működés megfigyelése teljes mértékben kívülről történik, menet közben megváltoztatható, és nem utolsó sorban, akár éles üzemben is bevethető. A programozó döntése, hogy mit figyel, a megfigyelt eseményekről mekkora mennyiségű információt irat ki a képernyőre, vagy akár egy log fájlba.
Bár a Dtrace platform-függő, létezik egy kifejezetten Java alkalmazásokra szánt Java-s átírat, amit Btrace-nek hívnak (a honlap nem tudom meddig lesz elérhető, mert a project a Kenai forrás-megosztó oldalon van elhelyezve, amit jól megvett az Oracle és jól be is szippantja "belső" használatra). A Btrace egyébként nyílt forrású project, és jelenleg az 1.1.1-es verziónál tart.
Használat:
  • Telepítése igen egyszerű, jóformán nincs, a bináris-t ki kell csomagolni a kívánt helyre.
  • Ezután első lépésként meg kell írnunk első Btrace scriptünket, mely annyit csinál, hogy az application.Main osztálynak figyeli a someMethod metódus hívását.
    package btrace;
    import static com.sun.btrace.BTraceUtils.*;
    import com.sun.btrace.annotations.*;
    @BTrace public class ClassTracer {
       @OnMethod(clazz = "application.Main", method = "someMethod")
       public static void methodcalled() {
           println("Method called");
       }
    }
    
  • Következő lépésként a vizsgálandó alkalmazást kell elindítani, egy apró külöbséggel, hogy a JVM-et meg kell kérni, hogy HotSpot módban induljon, ennek módja az alábbi: `java -client -jar sampleapp.jar`. A jar-ba fordított programocska:
    package application;
    public class Main {
       public static void main(String[] args) {
           for (int i = 0; i < 1000; i++) {
               someMethod();
               try { Thread.sleep(100);
               } catch (Exception ex) {}
           }
       }
       public static void someMethod() {}
    }
    
  • Miután fut az alkalmzásunk egy arra alkalmas eszköz segítségével meg kell tudnunk a futó programunk azonosítóját, PID-jét, Linuxon a top parancs segítségével jutunk sikerre, bár nem ez az egyetlen mód.
  • Majd a Btracet kell ráállítani a JVM-re, hogy dolgozza fel a kapott információkat a `btrace PID ClassTracer.java` paranccsal (ha elsőre Connection refused hiba-üzenettel leáll még 1x érdemes megpróbálni futtatni, van, hogy elsőre nem indul el).
  • Nincs más dolgunk, mint élvezni a konzolt elárasztó Method called kiírásokat.
A módszer legnagyobb előnye, hogy bytecode szinten tudjuk nyomon-követni alkalmazásunkat, azt látjuk, ami a virtuális gépben ténylegesen végrehajtódik.
Még néhány lehetőség (a teljesség igénye nélkül):
  • Van mód arra, hogy a clazz vagy method @Annotáció érték helyére reguláris kifejezést illesszünk, így nem csak egy osztályt vagy metódust, tudunk lekezeleni, hanem a mintára illeszkedő összeset.
  • Mivel a callback metódusokat mi hozzuk létre, paraméterként számtalan bemeneti változó közül választhatunk, melyeket @Annotációkkal jelölhetünk. Pár példa: @ProbeClassName String probeClass paraméterrel az osztályt, @ProbeMethodName String probeMethod paraméterrel a metódust kapjuk meg. @Return Class returnClass-al megkapjuk a metódus visszatérési értékét (ehhez az @OnMethodban fel kell venni egy location=@Location(Kind.RETURN) értéket), a AnyType[] args-el az átadott argumentumokat.
  • Egy Btrace scripten belül párhuzamosan több metódussal is követhetjük az állapotot, ennek köszönhetően a programozó fantáziájára van bízva, hogy pontosan mit is szeretne megfigyelni.
  • Lehetőség van időzített ismétlődő műveletek futtatására, így képet kaphatunk a processzor terheltségéről, vagy akár a memória használatról is, ekkor az @OnMethod helyett @OnTimer(long ms) @Annotációt kell használnunk.
  • Meglévő Dtrace scriptjeinkkel is összeköthetjük Btrace scriptjeinet, bár ennek nem sok értelmét látom, hisz ahol van Dtrace ott minek használnák a Btracet.
  • Létezik egy VisualVM plugin is a Btracehez, így a scripteket a VisualVm-en belül is szerkeszthetjük akár futás-időben is. Mindennapi használatra ezt javaslom egyébként, számtalan ponton könnyíti a munkát.
A felhasználói kézikönyv szerintem magáért beszél, továbbá a példa kódok bőbeszédűek, azokból is elsajátítható az eszköz használata.
Még egy fontos jó-tulajdonsága van a Btracenek, mégpedig az, hogy mivel POJO osztályokat kell létrehoznunk, és @Annotációkat használ a sajátságos működéshez, a jar-okat importálva bármely Java képes fejlesztő-eszközhöz van támogatás, ami valljuk be elég nagy könnyebbség a fejlesztőnek.
Zár-szóként remélem kedvet kapott a kedves Olvasó, hogy kipróbálja ezt a remek eszközt, és bár az írás eléggé bevezető jellegű mégis hasznos volt.