March 27, 2008

Frühlingsgesichter - JSF und Spring

Spring-Beans lassen sich leicht in JSF-Seiten verwenden.

Im folgenden Beispiel soll der customerController nicht als normale Managed Bean sondern als Spring-Bean definiert werden:
<h:inputText value="#{customerController.name}"/>
1. spring.jar

Kopieren in den Ordner WEB-INF/lib

2. web.xml

In der web.xml den Pfad zur Spring-Konfiguration festlegen, beispielsweise:
<web-app>
   <context-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>classpath:config/applicationContext.xml</param-value>
   </context-param>

  <listener>
     <listener-class>
        org.springframework.web.context.ContextLoaderListener
     </listener-class>
  </listener>

  (...)

</web-app>

3. faces-config.xml

Der DelegatingVariableResolver dient zur Auflösung der in den JSF-Seiten benutzten Beans:
<faces-config>
  <application>
     <variable-resolver>
        org.springframework.web.jsf.DelegatingVariableResolver
     </variable-resolver>
  </application>

  (...)

</faces-config>

4. Beans für den Application Context

Die JSF-eigene Definition als Managed Bean entfällt - stattdessen wird eine Spring-Bean konfiguriert:
<beans (...) >

 (...)

 <bean id="customerController"
      class="whatever.package.CustomerController"
      scope="request">
  </bean>

</beans>

March 22, 2008

JSF und die Keksfabrik

Eine kurze Übersicht über die Behandlung von Cookies unter Java und JSF:

Erzeugen:
public void bakeCookie(FacesContext ctx) {
  Cookie cookie = new Cookie("tastyCookie", "mySecretCookieData");
  cookie.setMaxAge(604800); // Haltbarkeit in Sekunden

  HttpServletResponse response = (HttpServletResponse) ctx.getExternalContext().getResponse();
  response.addCookie(cookie);             
}
Die eigentlichen Daten sollten selbstverständlich verschlüsselt sein. Weiterhin sollte man in der Verwendung von Sonderzeichen Vorsicht walten lassen - diese könnten je nach Browser zu eigentümlichen Verhalten führen (richtig, genau der Browser).

Auslesen:
public boolean tasteCookie(FacesContext ctx) {
  Map cookieMap = ctx.getExternalContext().getRequestCookieMap();
  Cookie cookie = (Cookie)cookieMap.get("tastyCookie");
  if (cookie != null && cookie.getValue().length() > 0) {
     // enjoy cookie
     String val = cookie.getValue();
  }
}
Entsorgen:
public void eatCookie(FacesContext ctx) {
  Cookie cookie = new Cookie("tastyCookie");
  cookie.setMaxAge(0);

  HttpServletResponse response = (HttpServletResponse)ctx.getExternalContext().getResponse();
  response.addCookie(cookie);      
}

JSF und die wesentlichen Bestandteile einer Bohne

Benötigt man für jedes Feld einer JSF-Seite wirklich ein weitgehend sinnfreies Getter/Setter-Paar in der zugehörigen Managed-Bean ?
Nein - denn JSF ist konfigurierbar und Java bietet das Reflection-Package...

In der faces.config.xml kann der zu verwendende ELResolver geändert werden:

<application>  
  <el-resolver>
     com.whatever.package.FieldResolver
  </el-resolver>     
</application>
Die Methoden des EL-Resolvers werden dann analog dem folgenden Beispiel für getValue implementiert.
Zuerst wird weiterhin versucht, einen passenden Getter zu finden. Das ist wichtig, um Sonderfälle, die ohne Getter nicht auskommen, behandeln zu können. Wurde keine Methode gefunden, wird nach einem entsprechenden Class-Field gesucht. Nur bei letztendlichem Erfolg wird die Anfrage als bearbeitet markiert (context.setPropertyResolved(true)) - andernfalls werden implizit die übrigen EL-Resolver aufgerufen.
Da dies alles nur Sinn macht, wenn es überhaupt um Objekt-Properties geht, werden nur Ausdrücke mit einem Basis-Objekt bearbeitet (base != null).
public class FieldResolver extends ELResolver {
    private static final Object[] sNoArgs = new Object[0];
    private static final Class[] sNoClassArgs = new Class[0];

    public Object getValue(ELContext context, Object base, Object property) throws NullPointerException, PropertyNotFoundException, ELException {
        String name = property.toString();

        if (base != null) {
            // 1. try getter
            try {
                String getterName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
                Method getter = base.getClass().getMethod(getterName, sNoClassArgs);
                context.setPropertyResolved(true);
                return getter.invoke(base, sNoArgs);
            }
            catch (NoSuchMethodException ne) {
                // no error so far           
            }
            catch (Exception ex) {
                throw new RuntimeException(ex);
            }

            // 2. direct field access
            try {
                Field field = getField(base, property);
                if (field != null) {
                    context.setPropertyResolved(true);
                    return field.get(base);
                }
            }
            catch (Exception e) {
                String message = "Error accessing property '" + name + "' in bean of type " + base.getClass().getName();
                throw new PropertyNotFoundException(message, e);
            }
        }

        return null;
    }
...
}

JSF und die Frage wie es losgeht

Wer sich erstmals mit den Java Server Faces befasst und/oder von Struts umsteigt, steht wahrscheinlich vor der Frage, wie und wann man am besten einen UI-Controller bzw. die zugrundeliegende(n) Managed Beans initialisiert, sprich, die benötigten Daten lädt. Denn alle darzustellenden Felder werden nur lose an beliebige Beans gebunden. Abgesehen von weiterführenden Themen wie Servlet Filtern oder Phase Listenern gibt es keine vordefinierten Einsprungmethoden.
Eine simple Lösung macht sich die Möglichkeit der Expression Language (EL) zunutze, komplexere und hierarchische Property-Ausdrücke aufzulösen.

Anstatt beispielsweise den Kundennamen wie folgt zu binden,
<h:inputText
    id="name" value="#{customerController.name}">
</h:inputText>

setzt man ein Formbean-Object davor:
<h:inputText
    id="name" value="#{customerController.formBean.name}">
</h:inputText>

Die Formbean wird als normale Java-Bean implementiert:
public class CustomerBean {
   private String name;
   public void setName(String name) {
      this.name = name; 
   }
   public String getName() {
      return name;
   }
}

Der Übersichtlichkeit kommt dies natürlich auch zugute, da der UI-Controller nicht mehr mit Gettern und Settern für alle Felder überschwemmt wird.

Im Controller gibt es nun eine zentrale Stelle für die Initialisierung:
public class CustomerController {
   public Object getFormBean() {
      if (formBean == null) {
         formBean = new CustomerBean();    
      
       // load form data, set form properties, etc.
       ... 
      }
   
      return formBean; 
   }  
   
   private CustomerBean formBean; 
}


Sobald erstmalig der Wert eines Feldes angefordert wird, wird die Formbean erzeugt - bei allen folgenden Property-Zugriffen wird die Initialisierung dann übersprungen.

March 20, 2008

JavaDB in 5 Schritten

Für einen ersten Test mit der JavaDB (früher Apache Derby, davor IBM sowie Cloudscape) habe ich ein bestehendes Oracle/Postgres-Schema mit etwa 60 Tabellen übertragen.
Das ging erfreulich unkompliziert. Ebenso der anschließende Zugriff über Hibernate.
Angepasst werden mussten lediglich:
  • die ID-Vergabe aus Hibernate (statt Sequenzen nun Identity-Felder, durch Ableiten der SQL-Dialekte kann weiterhin mit bestehenden Entity-Annotations gearbeitet werden).
  • die JavaDB mag in Scripten keine NULL-Keywords hinter den Columns (NOT NULL fkt. selbstverständlich)

1. Der Server kann durch die mitgelieferten Batchdateien gestartet werden. Das Setzen der Umgebungsvariablen DERBY_HOME ist gar nicht nötig.
Im Falle von Windows:
startNetworkServer.bat im Verzeichnis javadb\bin

2. Anlegen einer Datenbank mit dem Kommandozeilentool "ij":
connect 'jdbc:derby://localhost:1527/c:/JavaDB/db/myDB;create=true';

3. Beispiel zum Ausführen eines Scripts über ij:
run 'c:\JavaDB\createDatabase.sql';

4. JDBC-Connections
Der Treiber für den Client/Server-Betrieb ist org.apache.derby.jdbc.ClientDriver, für den Embedded-Mode org.apache.derby.jdbc.EmbeddedDriver.
Für User + Passwort interessiert sich die JavaDB offenbar erst nach entsprechender Konfiguration.

5. Shutdown
Im Embedded-Mode erfolgt ein ordnungsgemäßes Herunterfahren durch Anfordern einer Connection:
DriverManager.getConnection("jdbc:derby:;shutdown=true");
Eigentümlicherweise bedankt sich die JavaDB dabei standardmäßig mit einer Exception (Error-Code 50000).

Weiteres:
  • Für den Embedded-Mode ist die Datenbank sofort nach Anfordern einer Connection über den DriverManager betriebsbereit.
  • Ohne weitere Änderungen werden nur lokale Verbindungen akzeptiert. Dies kann durch Ergänzen der Batchdatei mit folgendem Startparameter geändert werden:
  • (... ) org.apache.derby.drda.NetworkServerControl start -h 0.0.0.0
  • In URLs für den Embedded-Treiber entfällt die Host-Angabe (z.B.: jdbc:derby:c:/JavaDB/db/myDB)
  • Hilfestellung gibt ij durch das help-Kommando

Schließlich zur interessanten Frage: was ist mit der Performance ?
Nun, der Mythos von der Langsamkeit von Java wird mit einer in Java geschriebenen Datenbank sicherlich auch nicht aussterben.
Wie auch immer, der erste Eindruck ist gut (einige Suchanfragen über 10000 Artikelsätze mit Joins und Subselects).
Besonders interessant ist auf jeden Fall der Embedded-Mode für unkomplizierte Auslieferungen als Teil von Datenbank-Anwendungen oder für einen autarken JUnit-Testbetrieb.