This blog has moved to
http://blog.matthias-reining.com

Die bestehenden Artikel bleiben vorerst alle bei blogspot. Neue Artikel veröffentliche ich allerdings nur noch auf http://blog.matthias-reining.com

Donnerstag, 30. Oktober 2008

EJB3 und ServiceLocator

Leider benötige ich, anders als hier im Web oft beschreiben, im Zusammenspiel zwischen Stateless SessionBeans (SLSB) und meinem Web Frontend das ServiceLocator Pattern (Plattform JBoss5). Aus einem Servlet (managed container) kann man zwar ganz komfortable die Annotation @EJB aufrufen, aus einem einfachen POJO allerdings leider nicht… (wäre mal ein ganz nettes Feature für die nächste Generation ;-) )

Problem: Aufruf meiner SLSB aus meiner Struts2 Action.

Nachdem die Action nicht vom Webcontainer verwaltet wird kann mit @EJB auch die SLSB nicht injiziert werden. Also muss doch das gute alte ServiceLocator Pattern her. Seit EJB2 hat sich hier allerdings einiges getan (Generics, HomeInterface, …), so dass ein einfaches C&P von der Pattern Page nicht wirklich toll passt (http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html).

Klar, jetzt kommt eigentlich Spring zum Einsatz. Aber… ich will in diesem Projekt einfach kein Spring benutzen ;-)

Mit folgendem Code komme ich ganz gut zu recht:

ServiceLocator Klasse:

import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class ServiceLocator {

    private static ServiceLocator instance;
    InitialContext context;
    private ServiceLocator() {
        try {
            Properties p = new Properties();
            p.put(Context.INITIAL_CONTEXT_FACTORY,
                    "org.jnp.interfaces.NamingContextFactory");
            p.put(Context.URL_PKG_PREFIXES,
                    "org.jboss.naming:org.jnp.interfaces");
            p.put(Context.PROVIDER_URL, "jnp://localhost:1099");
            context = new InitialContext(p);
        } catch (NamingException e) {
            throw new ServiceLocatorException(e);
        }
    }

    public static ServiceLocator getInstance() {
        if (instance == null)
            instance = new ServiceLocator();
        return instance;
    }

    @SuppressWarnings("unchecked")
    public <T> T getService(Class<T> clazz) {
        try {
            // TODO Name des ear-Files (MrBlue) zur Laufzeit ermitteln
            String lookupName = "MrBlue/" + clazz.getSimpleName()
                    + "Bean/local";
            T lookup = (T) context.lookup(lookupName);
            return lookup;
        } catch (ClassCastException e) {
            throw new ServiceLocatorException(e);
        } catch (NamingException e) {
            throw new ServiceLocatorException(e);
        }
    }

}

Aufruf aus der Struts Action:

public String execute() {        
        AccountService accountService = ServiceLocator.getInstance()
                .getService(AccountService.class);
        accountService.ping();

        return SUCCESS;
    }

Caching wird hier Clientseitig nicht verwendet. Übernimmt hier ja quasi der EJB-Container.

Der ServiceLocator hier geht davon aus, dass bei der Klassenbezeichnung der SessionBeans eine Konvention eingehalten wird (wie eigentlich üblich…)

Local Interface: <name>

Remote Interface: <name>Remote ß hier im ServiceLocator noch nicht implementiert (geht ja bei einer Webapplikation hier im ersten Schritt ja auch nur um local Calls).

Session Bean: <name>Bean


 

Samstag, 4. Oktober 2008

Javascript und Mathematik (oder Fließkommaarithmetik)

Ausgerechnet heute, am Feiertag (03. Oktober), hat doch in einem Projekt der Kunde ein böses Problem entdeckt… Die Applikation funktioniert nicht mehr! (zumindest ein ganz spezieller Fall) normal ;-)

In der entsprechenden Applikation (Webapplikation) werden im Frontend in Javascript einige Berechnungen durchgeführt (eher trivial, dafür allerdings ziemlich viel). Berechnungen in Javascript? Spart den Serverrroundtrip, der per AJAX notwendig wäre… Zur Kontrolle wird aber natürlich nochmal alles im Backend in Java mit BigDecimal nachgerechnet. …und da passen die Ergebnisse jetzt leider nicht zusammen. Die Applikation wirft einen Fehler und funktioniert nicht mehr.

Die Berechnung

function roundNumber(value, maxDecimalPlaces ) {
var e = Math.pow(10, maxDecimalPlaces);
return (Math.round(value * e) / e);
}

var x = 234.7;
var factor = 0.95;
var result = roundNumber( x * factor, 2 );
alert(result);

Das Ergebnis ist 222,96
Eigentlich hätte man aber ein leicht anderes Ergebnis erwartet… sauber gerechnet ist x*factor=222,965. Kaufmännische gerundet sollte das Ergebnis 222,97 sein.

Das Problem liegt natürlich an Fließkommazahlen und deren Rundungsfehler. Vergisst man nur mal schnell wenn man ein "bischen" in JavaScript hackt.

Das Ergebnis in Javascript für x * factor:

234.7 * 0.95 = 222.96499999999997

Und somit wird hier natürlich anschließend auch falsch gerundet.

Abhilfe schafft eine modifizierte round Methode:

function roundNumber(value, maxDecimalPlaces ) {
var e = Math.pow(10, maxDecimalPlaces);
//fliesskommaaritmetic
value = value + 0.0000000000001;
value = (Math.round(value * e) / e);
return value;
}

http://de.selfhtml.org/javascript/objekte/math.htm#round

http://www.galileocomputing.de/openbook/javainsel7/javainsel_02_004.htm#mjf9723d99079baea563298aa74cd42cb9