Dienstag, 31. August 2010

Know the API and do it right

Wesentlich für die Programmierung von Java-Anwendungen sind gute Java API Kenntnisse. Die Java API beinhaltet reichhaltige Funktionalitäten zur Verarbeitung und Speicherung von Daten. Neben den viel diskutierten Java Collections sind in der Java API bereits Musterimplementierungen vorhanden.

Das Wissen über diese Muster und deren Anwendung führt zu robusten und wartungsfreundlichen Applikationen. Bevor man selbst ein Muster programmiert, ist es deshalb ratsam, die Java API zu konsultieren, um zu evaluieren, ob nicht schon die gewünschte Musterimplementierung in der API vorhanden ist.

In diesem Blogbeitrag wird anhand des Observer-Musters eine Eigenimplementierung und die Java API Version des Musters vorgestellt. Das Observer-Muster definiert eine 1-n Beziehung zwischen Objektinstanzen. Das Observer-Muster setzt sich aus einem Subjekt und den Beobachtern zusammen. Das Subjekt ist das Objekt, dass seine Zustandsänderungen den registrierten Beobachtern mitteilt. Für die Implementierung des Musters werden die folgenden zwei Schnittstellen und deren Implementierungen benötigt.

Subjekt - Schnittstelle

public interface Subject {

    public void registerObserver(Observer observer);
    public void registerObservers(Observer ... observers);
    public void removeObserver(Observer observer);
    public void removeObservers();
    public void notifyObservers(Object state);  
}

Beobachter - Schnittstelle

public interface Observer {

    public void update(Object state);
    public Object getState();
}

Implementierung des Subjektes:

import java.util.ArrayList;
import java.util.List;

public class SubjectBean implements Subject {

    private List<Observer> observers = new ArrayList<Observer>();  
  
    @Override
    public void notifyObservers(Object state) {
     
        for(Observer observer : observers) {
            observer.update(state);
        }      
    }

    @Override
    public void removeObservers() {
     
        observers.clear();
    }
  
    @Override
    public void removeObserver(Observer observer) {
     
        final int index = observers.indexOf(observer);
        if(index >= 0) {
            observers.remove(index);
        }      
    }

    @Override
    public void registerObserver(Observer observer) {
     
        observers.add(observer);      
    }
  
    @Override
    public void registerObservers(Observer ... observerEllipse) {
     
        for(Observer observer: observerEllipse) {
            observers.add(observer);
        }
    }      
}

Implementierung des Beobachters:

public class ObserverBean implements Observer {
  
    private Object state;
  
    @Override
    public void update(Object state) {
      
        this.state = state;
    }
  
    @Override
    public Object getState() {
      
        return state;
    }
}

Unit-Tests des Musters:

import static org.junit.Assert.assertEquals;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class ObserverTest {

    private static final String SUBJECT_STATE_MESSAGE = "new-state";
    private static final int COUNT_OBSERVERS = 2;
 
    private Subject subject;
    private Observer[] observers;
    
    @Before
    public void setUp() {
    
        subject = new SubjectBean();
        observers = createObservers(COUNT_OBSERVERS);
        subject.registerObservers(observers);
    }

    @After
    public void tearDown() {
    
        subject.removeObservers();
    }
 
    @Test
    public void testBroadcast() {
           
        subject.notifyObservers(SUBJECT_STATE_MESSAGE);
    
        assertObserverStates();
    }
 
    private void assertObserverStates() {
      
        for(int i = 0; i < observers.length; i++) {        
        
            assertEquals(SUBJECT_STATE_MESSAGE,
                    ((ObserverBean)observers[i]).getState());
        }
    }
 
    private Observer[] createObservers(int countObserver) {
    
        final Observer[] observerArray = new Observer[countObserver];
        for(int i = 0; i < observerArray.length; i++) {
        
            observerArray[i] = new ObserverBean();
        }
    
        return observerArray;
    }    
}

Die Eigenentwicklung des Observer-Musters ist ausprogrammiert und rudimentär getestet. Zum Vergleich nachfolgend die Java API Implementierung des Observer-Musters.

Implementierung des Subjektes:

import java.util.Observable;

public class SubjectBean extends Observable {

    public void notifyObservers(String state) {
      
        super.setChanged();
        super.notifyObservers(state);
    }
}

Implementierung  des Beobachters:

import java.util.Observable;
import java.util.Observer;

public class ObserverBean implements Observer {

    private Object state;
   
    @Override
    public void update(Observable observer, Object state) {
       
        this.state = state;       
    }       
   
    public Object getState() {
       
        return state;
    }
}

Unit-Tests des Musters:

import static org.junit.Assert.assertEquals;
import java.util.Observable;
import java.util.Observer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class ObserverTest {

    private static final String SUBJECT_STATE_MESSAGE = "new-state";
    private static final int COUNT_OBSERVERS = 2;
   
    private Observable subject;
    private Observer[] observerArray;
   
    @Before
    public void setUp() {
       
        subject = new SubjectBean();
        createObservers(subject, COUNT_OBSERVERS);       
    }

    @After
    public void tearDown() {
       
        subject.deleteObservers();
    }
   
    @Test
    public void testBroadcast() {
       
        ((SubjectBean)subject).notifyObservers(SUBJECT_STATE_MESSAGE);
       
        assertObserverStates();
    }
   
    private void assertObserverStates() {
       
        for(int i = 0; i < observerArray.length; i++) {           
           
            assertEquals(SUBJECT_STATE_MESSAGE,
                    ((ObserverBean)observerArray[i]).getState());
        }
    }
   
    private void createObservers(Observable subject, int countObserver) {
       
        observerArray = new Observer[countObserver];
       
        for(int i = 0; i < countObserver; i++) {
   
            observerArray[i] = new ObserverBean();
            subject.addObserver(observerArray[i]);
        }       
    }
}

Vergleicht man die beiden Implementierungen, fällt sofort auf, dass bei der Eigenentwicklung sehr viel mehr Quellcode erstellt worden ist. Bei der Java API Implementierung des Observer-Musters ist sicherlich unschön, dass der Status als "Object" Typ behandelt wird. Der Gewinn an Flexibilität wird durch die mangelnde Typsicherheit aufgewogen. Bei der Eigenentwicklung des Observer-Musters ist der Typ des Statusobjektes frei wählbar.

Die Java API Implementierung des Observer-Musters ist synchronisiert und damit auch in nebenläufigen Umgebungen einsetzbar. Die Eigenentwicklung des Observer-Musters ist zunächst noch nicht synchronisiert. Dort liegt die Schwachstelle und eine mögliche Fehlerquelle der Implementierung in nebenläufigen Umgebungen. Die Eigenentwicklung mit einem Lock zu sichern ist nicht empfehlenswert. Es bietet sich vielmehr eine threadsichere Liste an. Die "ArrayList" in der Subjektimplementierung ist deshalb durch eine threadsichere Liste auszutauschen. Als threadsichere Variante einer Liste für nebenläufige Umgebungen bietet sich die CopyOnWriteArrayList an.

Auszug aus der angepassten Subjektimplementierung:

public class SubjectBean implements Subject {
    ...
  
    private List<Observer> observers = new ArrayList<Observer>();  
    private List<Observer> observers = new CopyOnWriteArrayList<Observer>();

    ...  
}

Der Blogbeitrag veranschaulicht, dass ein Programmierer mit guten Java API Kenntnissen weniger Quellcode erstellt, auf Basis von getestetem Quellcode arbeitet und deswegen in der Lage ist, robustere und wartungsfreundlichere Java-Applikationen zu implementieren.


Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.

Montag, 30. August 2010

Some more about writing code

Die Prinzipien und Praktiken für Clean Code Developer sind schon lange bekannt und gut formuliert. Beispiele für "Code Smells" sind nicht nur von Robert C. Martin in seinem Buch Clean Code, sondern auch von Martin Fowler und Kent Beck in ihrer Literatur formuliert worden. Die vielen Beispiele der Literatur beinhalten Code Styles, Refactorings und Best Practices, um "Code Smells" wie den folgenden zu vermeiden:

// a loop construct
do while(l < 1)
  System.out.println("l is " + l);
while(l > 1);

Warum riecht der Quellcode? Sicherlich ist der Kommentar unvorteilhaft, dass es sich um ein Schleifenkonstrukt handelt, ist offensichtlich. Die Formatierung ist schlecht gewählt und die Klammersetzung wurde ausgelassen. Obwohl es sich nur um ein paar Zeilen Quellcode handelt, ist der Programmablauf nicht sofort ersichtlich. Bessere Formatierung und die Klammersetzung würden den Programmablauf des Schleifenkonstrukts sofort ersichtlich werden lassen.

Wesentlich für die Maxime von CCD ist, dass man sich als Clean Code Developer auch als Autor versteht, der sauberen und leicht verständlichen Quellcode für andere Autoren schreibt. Darüber dürfte in der CCD-Community Einigkeit herrschen.

CCD beschäftigt sich mit Prinzipien für höhere Softwarequalität und propagiert Kohäsion, Kapselung und lose Kopplung. Klassen wie die nachfolgende mit geringer Kohäsion stinken, wenn man CCD und objektorientierten Paradigmen folgt.

public class LowCohesion {

    void takeOrders() {}
    void servesFood() {}
    void cleanDesk() {}
   
    void takeOrdersAndServesFoodAndCleanDesk() {
       
        takeOrders();
        servesFood();
        cleanDesk();
    }
}

Ein wichtiger Punkt, der weniger stark betrachtet wird, ist die Korrektheit von Programmen  für eine definierte Systemumgebung. Systemumgebungen wie Java EE Applikationsserver beinhalten sehr viele Verträge, die bei der Erstellung von Java Applikationen einzuhalten sind. Die Spezifikationen des Java Community Processes (JCP) sind voll von Einzelheiten über Verträge, die zwingend bei der Applikationsentwicklung einzuhalten sind. JCP-Spezifikationen beschreiben Best Practices und Antipatterns für die Implementierung von Java-Software.

Die Programmiersprache Java beinhaltet selbst wiederum sehr viele Verträge. Man denke an den Equals- und HashCode-Vertrag, der zwingend im Rahmen der Java Collections einzuhalten ist. Die Verträge im Java-Umfeld sind häufig stark zwingend ausgelegt, sodass festgelegte Schritte bei der Programmierung eines Algorithmus ausgeführt werden müssen.Typisches Beispiel ist die Suche in einer Collection mit "BinarySearch", die nur dann ein definiertes Ergebnis liefert, falls die Collection, auf deren Basis gesucht wird, zuvor sortiert worden ist.Die Sortierung einer Datenmenge unterliegt wiederum einem durch die Programmiersprache Java festgelegten Sortierkriterium für die einzelnen Elemente der Datenmenge.

Hinzu kommt die Nebenläufigkeit in Mehrbenutzerumgebungen, die wiederum die Wahl der korrekten Collection und die angemessene Synchronisation von Threads erfordert. Korrekte Programme bekommt man nicht geschenkt, sondern sie basieren auf tiefer Erfahrung in einer konkreten Systemumgebung. Die Vielfalt der Fehlermöglichkeiten reicht bei falsch erstellten nebenläufigen Programmen von keiner Synchronisation bis hin zum Deadlock. Zwischenzustände, die ein Programm sporadisch korrekt erscheinen lassen, sind möglich, äußern sich aber mit zunehmender oder abnehmender Last fehlerhaft. Probleme, die durch die Nebenläufigkeit auftreten, sind durch ihre sporadische Natur häufig schwer zu debuggen.

Java Boardmittel zur Synchronisation von Programmen, wie beispielsweise synchronized, wait, sleep, yield und join sind sicherlich schwierig anzuwenden. Die Schöpfer von Java haben diesen Schwachpunkt erkannt und seit Java 5 die Java Concurrent Utils (java.util.concurrent) als Antwort parat.

Die Java-API selbst hält viele Fallstricke bereit, die nicht alle durch einen Kompilierfehler angezeigt werden. Solche Fehler treten vielmehr zur Laufzeit auf und führen zu Ausnahmen, die vom Java Laufzeitsystem geworfen werden. Die Java-API verhält sich auch nicht immer in der Form, wie man es erwarten würde, sodass das Schreiben von Unit-Tests ratsam ist. Nachfolgend ein paar einfache Beispiele, die zum Nachdenken anregen sollen.

Beispiel - Integer-Datentyp und Vergleichsoperator:

 @Test
public void testCompareSuccessul() {
    
    Integer firstInt = 45;
    Integer secondInt = 45;

    assertEquals(true, firstInt == secondInt);
}

@Test
public void testCompareFailed() {

    Integer firstInt = 145;
    Integer secondInt = 145;

    assertEquals(true, firstInt == secondInt);
}

Beispiel - Inkrementoperator und Returnwert:

private int getIntValue() {
        
  int intValue = 1;
  
  return(intValue++);

@Test
 public void testReturnValueSuccessful() {
                   
     assertEquals(1, getIntValue());
 }

@Test
 public void testReturnValueFailed() {
                   
     assertEquals(2, getIntValue());
 }

Beispiel - Downcast:

class CleanCodeDeveloper {}
class JavaDeveloper extends CleanCodeDeveloper {}


@Test
 public void testDowncastFailed() {
        
    CleanCodeDeveloper cleanCodeDeveloper = new CleanCodeDeveloper();
    JavaDeveloper javaDeveloper = (JavaDeveloper) cleanCodeDeveloper;

}

Beispiel - Arrays:

class CleanCodeDeveloper {}
class JavaDeveloper extends CleanCodeDeveloper {}


@Test
public void testArraysAreNotTypeSafe()  {

    CleanCodeDeveloper[] cleanCodeDeveloper = new JavaDeveloper[1];               
    cleanCodeDeveloper[0] = new CleanCodeDeveloper();                        
}

Die Liste der Laufzeitprobleme könnte noch sehr viel länger werden und veranschaulicht ein paar einfache Verträge der Programmiersprache Java. Sollte deshalb nicht auch ein CCD-Entwickler eine hohe Kohäsivität seiner Programmierskills anstreben, um optimal in einer Programmiersprache und Programmierumgebung agieren zu können?

Ich denke, dass man diese Frage mit "Ja" beantwortet. TDD und Eclipse Plugins wie PMD mildern diese Anforderung zwar ab, weil Unit-Tests ein Sicherheitsnetz bilden und PMD Programmfehler vorausschauend erkennt, aber dennoch halte ich die Fahne für die hohe Kohäsivität von Programmierskills nach oben.

WSDL First for Interoperability

Einen JAX-WS Web Service mit dem Java First Ansatz und entsprechenden Annotationen zu programmieren ist einfach. Der Java First Ansatz hat allerdings eine entscheidende Schwachstelle: Interoperabilität. Diese Schwachstelle liegt beim WSDL First Ansatz nicht vor. Beim WSDL First Ansatz ist ein XML-Schema und eine WSDL-Datei die gemeinsame Schnittstelle und das Typsystem zum Austauschen von Nachrichten zwischen den Services. Deshalb ist dieser Ansatz typsicherer und besser für die Kommunikation in heterogenen Systemumgebungen geeignet.

Beim WSDL First Ansatz erstellt man zunächst ein XML-Schema und eine WSDL-Datei und generiert danach mit "wsimport" das Service Endpoint Interface (SEI), welches die Basis für die Web Service Implementierung ist. In dem Blogbeitrag wird ein einfacher Web Service mit dem WSDL First Ansatz auf Basis des folgenden Schemas und WSDL-Datei erstellt.

XML-Schema des Blog Web Services - blog.xsd

XML-Schema des Blog Web Services

Graphische Darstellung und WSDL des Blog Web Services - blog.wsdl


Der Eintrag <PORT> in der WSDL ist durch den konfigurierten Port des Applikationsservers zu ersetzen, auf dem der Web Service deployed werden soll.

Die Web Service Artefakte generiert man mit Hilfe von "wsimport".

wsimport -keep blog.wsdl

Aus den generierten Web Service Artefakten entnimmt man das Service Endpoint Interface (SEI) aus dem man die Service Endpoint Implementierung (SIB) erstellt. Die Service Endpoint Implementierung entspricht dem funktionalen Web Service der in einem EJB-Projekt erstellt und in einem EAR als EJB-JAR deployed wird.

Service Endpoint Implementierung die im Package ccd.jee.ws.soap.blog zu hinterlegen ist:

import javax.ejb.Stateless;
import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;

@WebService(name = "BlogEntryWebService",
                        targetNamespace = "http://blog.soap.ws.jee.ccd/")
@XmlSeeAlso({
ObjectFactory.class
})
@Stateless
public class BlogEntryBean {

  @WebMethod
  @WebResult
  @RequestWrapper(localName = "getBlogEntry",
                               targetNamespace = "http://blog.soap.ws.jee.ccd/",
                               className = "ccd.jee.ws.soap.blog.GetBlogEntry")
  @ResponseWrapper(localName = "getBlogEntryResponse",
                                 targetNamespace = "http://blog.soap.ws.jee.ccd/",
                                 className = "ccd.jee.ws.soap.blog.GetBlogEntryResponse")
   public String getBlogEntry(@WebParam(name = "id", targetNamespace = "")  int id) {

         printRequest(id);

        GetBlogEntryResponse response = new ObjectFactory().createGetBlogEntryResponse();
        response.setReturn("getBlogEntry - response");

        return response.getReturn();
    }

    @WebMethod
    @Oneway
    @RequestWrapper(localName = "storeBlogEntry",
                                 targetNamespace = "http://blog.soap.ws.jee.ccd/",
                                 className = "ccd.jee.ws.soap.blog.StoreBlogEntry")
     public void storeBlogEntry(@WebParam(name = "title", targetNamespace = "") String title,
                                           @WebParam(name = "entry", targetNamespace = "") String entry) {

         printRequest(title, entry);
     }

     private void printRequest(Integer id) {

        System.out.printf( "web service request - getBlogEntryById - id : %d", id);
    }

    private void printRequest(String title, String entry) {

        System.out.printf( "web service request - storeBlogEntry - title: %s / entry: %s", title, entry);
    }
}

Die beim "wsimport" Durchlauf mit dem Service Endpoint Interface generierten Artefakte kopiert man in eine eigenes JAR-Projekt in das dort erstellte Package: ccd.jee.ws.soap.blog und bindet das JAR-Projekt mit dem EJB-JAR über die Java EE Module Dependencies im EJB-JAR Projekt. Nachdem die Kompilierfehler im EJB-JAR Projekt durch das Binden beseitigt sind, kann der Blog Web Service deployed werden. Als Deploymentumgebung eignet sich der JBoss AS 6 (Milestone 3).

Nach dem Deployment des Web Services kann über die JBoss Service View die WSDL-Referenz erfragt werden. Auf Basis der WSDL-Referenz erzeugt man nun mit "wsimport" die clientseitige Service-Schnittstelle.

wsimport -keep  http://localhost:<PORT>/WsdlSoapBlogProjectEJB/BlogEntryWebService?wsdl

 Der Eintrag <PORT> beim "wsimport" Aufruf ist entsprechend des lokal eingestellten Ports des Apllikationsservers, auf dem der Blog Web Service läuft, zu ersetzen.

Den neu generierte Service Client zur Ansprache des Web Services kopiert man nun in das zuvor angelegte JAR-Projekt in das Package ccd.jee.ws.soap.blog und erstellt darauf folgend ein weiteres Java Projekt zum Testen des Web Services. 

In dem Java Projekt legt man ein Package und folgende Testklasse an:

import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;

public class TestWebService {

    private BlogEntryBeanService service;
    private BlogEntryWebService port;

    @Before
    public void setUp() {

        service = new BlogEntryBeanService();
        port = service.getBlogEntryWebServicePort();
    }

    @Test
    public void testStoreBlogEntry() {

        port.storeBlogEntry("SOAP", "SOAP is very nice");
    }

    @Test
    public void testGetBlogEntry() {

        final String response = port.getBlogEntry(1);
        assertEquals("getBlogEntry - response", response);
    }
}

Damit der Test ausgeführt werden kann, ist die JAR-Datei mit den Web Service Arteakten in den Build Pfad des Testprojektes aufzunehmen.Wenn alles gut verlaufen ist, sollte nun der JUnit-Testbalken grün sein und in der Konsole des Applikationsservers sollten zwei Meldungen erscheinen (storeBlogEntry und getBlogEntry Requests).

Der Blog Entry Web Service ist bewusst einfach gehalten worden und verwendet deshalb auch kein Backend. Das wesentliche Fazit der Erstellung des Blog Web Services ist, dass man bei der Implementierung des Services "wsimport" zweimal aufruft. Einmal zum Erstellen des Service Endpoint Interfaces, damit die Service Endpoint Implementierung auf Basis der generierten Schnittstelle erfolgen kann und nach dem Deployment des Web Services wird "wsimport" nochmals mit der deployten WSDL-Referenz aufgerufen, um die clientseitige Service Schnittstelle zu generieren, die als Proxy für einen Web Service Client dient.


Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.

Samstag, 28. August 2010

SOAP in fifteen minutes

JAX-WS und der I-Stack unterstützen optimal die Programmierung von SOAP-basierten Java Web Services. Die Entwicklung eines Web Services mit Hilfe von Annotationen ist spielend leicht. Ist der Web Service programmiert kann auf Basis der WSDL mit "wsimport" der clientseitige Quellcode zum Ansprechen des Web Services erzeugt werden.

Die I-Stack Technologien sind integraler Bestandteil von Java SE 6, sodass JAXP, JAXB 2.0 und JAX-WS inklusive der Werkzeuge zum Generieren von Quellcode und Schemata (wsimport und wsgen) auch ohne Applikationsserver in Java SE 6 Umgebungen anwendbar sind.

Wie der Titel des Blogeintrages bereits verspricht, ist ein einfacher SOAP-basierter Web Service als Full Stack Java EE Anwendung in fünfzehn Minuten programmiert. Als Basis für die Programmierung dient das JBoss Developer Studio 3.0 GA und der JBoss AS 6 (Milestone 3). 

Der SOAP-basierte Web Service Endpoint, der nachfolgend programmiert wird, beinhaltet eine Operation zum Anlegen und Lesen eines Blogeintrages.

Zunächst ist ein Java EE Projekt anzulegen (EAR inklusive eines EJB-JAR) mit dem Namen "SoapBlogProject". Im Verzeichnis /ejbModule/META-INF ist eine persistence.xml zu hinterlegen, die auf eine konfigurierte Datenquelle verweist. Danach sind, wie nachfolgend beschrieben, schrittweise die folgenden Packages und Java Klassen anzulegen.

Anlegen der Klasse BlogEntry im Package ccd.jee.ws.soap.domain

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class BlogEntry {

    @Id
    @GeneratedValue
    private int id;
   
    private String title;
    private String entry;
   
    public int getId() {
        return id;
    }
   
    public String getTitle() {
        return title;
    }
   
    public String getEntry() {
        return entry;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public void setEntry(String entry) {
        this.entry = entry;
    }

    @Override
    public String toString() {
        return "BlogEntry [entry=" + entry + ", title=" + title + "]";
    }
}

Anlegen der lokalen Schnittstelle BlogServiceDao im Package ccd.jee.ws.soap.domain.dao

import javax.ejb.Local;
import ccd.jee.ws.soap.domain.BlogEntry;

@Local
public interface BlogServiceDao {

    public void store(String title, String entry);
    public BlogEntry getById(Integer id);
}

Anlegen des DAOs BlogServiceDaoBean im Package ccd.jee.ws.soap.domain.dao

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import ccd.jee.ws.soap.domain.BlogEntry;

@Stateless
public class BlogServiceDaoBean implements BlogServiceDao {

   @PersistenceContext
   private EntityManager em;

   public void store(String title, String entry) {

       BlogEntry blogEntry = new BlogEntry();
       blogEntry.setTitle(title);
       blogEntry.setEntry(entry);

       em.persist(blogEntry);
    }

    public BlogEntry getById(Integer id) {

       return em.find(BlogEntry.class, id);
    }
}

Anlegen der Klasse BlogEntryService im Package ccd.jee.ws.soap.service

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import ccd.jee.ws.soap.domain.BlogEntry;
import ccd.jee.ws.soap.domain.dao.BlogServiceDao;

@WebService(name = "BlogEntry",
            serviceName = "BlogEntryService",
            targetNamespace= "http://service.soap.ws.jee.ccd")           
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT,
             use = SOAPBinding.Use.LITERAL,
             parameterStyle = SOAPBinding.ParameterStyle.WRAPPED)
@Stateless           
public class BlogEntryService {

    @EJB private BlogServiceDao blogServiceDao;
  
    @WebMethod(operationName = "getBlogEntry")
    @WebResult(name = "BlogEntryResponse",
            targetNamespace = "service.soap.ws.jee.ccd")             
    public BlogEntry getBlogEntry(
            @WebParam(name = "id",
                        targetNamespace= "http://service.soap.ws.jee.ccd",
                        mode = WebParam.Mode.IN) Integer id) {

        return blogServiceDao.getById(id);
    }
      
    @WebMethod
    @Oneway
    public void storeBlogEntry(           
            @WebParam(name = "title",
                      targetNamespace= "http://service.soap.ws.jee.ccd",
                      mode = WebParam.Mode.IN) String title,
                    
            @WebParam(name = "entry",
                              targetNamespace= "http://service.soap.ws.jee.ccd",
                              mode = WebParam.Mode.IN) String entry) {
      
        blogServiceDao.store(title, entry);
    }
}

Das war es schon! Die Klasse BlogEntryService beinhaltet den SOAP-basierten Web Service, der den Document/Wrapped Parameterstyle verwendet. Dieser Style ist zwar nicht WS-I Basic Profile konform, hat aber den wesentlichen Vorteil, wie ein RPC basierter Web Service im SOAP-Body aufzutreten.

Die Operation des Web Service Endpoints zum Anlegen eines Blogeintrages ist als OneWay-Operation ausgelegt. Für OneWay-Operationen wird keine SOAP-Response erzeugt. Die Operation zum Lesen eines Blogeintrages liefert einen Blogeintrag mit den beiden Attributen Titel und Eintrag als SOAP-Response zurück.

Der Web Service kann nun auf dem JBoss AS 6 (Milestone 3) deployed werden. Ist das Deployment erfolgreich verlaufen, schaltet man einen Browser auf und lässt sich die Liste der deployten Web Services anzeigen:  

http://localhost:<PORT>/jbossws/services

Web Service Eintrag


Die WSDL des Web Services schaut man sich durch die folgende Eingabe in der Browser Adresszeile an:

http://localhost:<PORT>/SoapBlogProjectEJB/BlogEntry?wsdl

Der Eintrag <PORT> ist durch den lokal konfigurierten Port des JBoss AS zu ersetzen.

Zum Testen des Web Services verwendet man den Web Service Explorer des JBoss Developer Studios 3.0. In der Open WSDL Ansicht gibt man die obenstehende WSDL-Referenz ein.Nach Betätigung der Go-Schaltfläche wird der Web Service mit seinen beiden Operationen angezeigt. Zunächst wählt man die Operation "storeBlogEntry" aus und definiert zwei Parameter mit der jeweiligen Add-Schaltfläche. Nach der Parameterdefinition betätigt man wieder die Go-Schaltfläche. Der Web Service wird ausgeführt und der Blogeintrag gespeichert.

Web Services Explorer: storeBlogEntry

Den angelegten Blogeintrag kann man mit einem ID-Parameter abfragen. Dazu verwendet man wiederum den Web Services Explorer und die Operation "getBlogEntry". Die nachfolgende Abbildung zeigt den SOAP-Request und die dazugehörige SOAP-Response der "getBlogEntry" Operation.

Request-/Response-Message der Operation "getBlogEntry"

Der Web Service ist nun programmiert und rudimentär getestet. SOAP Web Services sind mit dem Java First Ansatz zügig umsetzbar. SOAP ist nach wie vor das geeignete Mittel um Interoperabilitätsprobleme zu lösen, weil mit dem WSDL First Ansatz, XML Schemata und der Standardisierung durch das WS-I Basic Profile eine stabile Umgebung für den Austausch von Daten in unterschiedliche Betriebssysteme und Programmierumgebungen vorhanden ist.


Der Rechtshinweis des Java Blog für Clean Code Developer
ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.

Freitag, 27. August 2010

Two out of the crowd

Entwurfsmustern begegnet man häufig bei der Erstellung von Programmen. Entwurfsmuster werden gerne als Schablone für Problemlösungen herangezogen und standardisieren quasi die Programmierung allgemeiner Algorithmen für die Erstellung von Software.

Entwurfsmuster sind Teil der gemeinsamen Sprache unter Entwicklern. Es ist offensichtlich einfacher, einem Entwickler zuzurufen, er solle doch bitte eine Facade programmieren, als mühsam erklären zu müssen, welche Art von Komponente er erstellen soll. Zu dem gemeinsamen Verständnis über die Quellcodequalität gehört deshalb auch ein gemeinsamer Sprachschatz, um klar und deutlich miteinander kommunizieren zu können.

Ein kleiner Teilausschnitt aus dem Katalog der Entwurfsmuster sind die Erzeugungsmuster mit denen Objekte instanziiert werden können. Zu den Erzeugungsmustern gehört die Klasse der Fabriken (Factory Patterns). Fabriken werden immer dann eingesetzt, wenn Objekte auf Basis einer allgemeinen Schnittstelle erstellt werden sollen und dabei auch noch die Konfigurierbarkeit eine Rolle spielt. Zwei Muster aus dem Katalog der Erzeugungsmuster werden nachfolgend im Quellcode veranschaulicht.

Fabrikmuster (Factory Pattern)

Das Fabrikmuster der nachfolgenden Implementierung nutzt die Möglichkeiten des Enumerationstyps, der ab Java 5 ein Sprachelement von Java ist und bei der Implementierung des Musters eine interessante Alternative zu den herkömmlich bekannten Fabrikimplementierungen bietet.

Generische Schnittstelle des Fabrikmusters:

interface Factory<T> {
   
    T instance();
}

Eine Fabrik für Integer-Listen:

import java.util.ArrayList;
import java.util.List;

public enum IntListFactory implements Factory<List<Integer>> {

    NEW {
   
        public List<Integer> instance() {
           
            return(new ArrayList<Integer>());
        }
    };   
}

Eine Fabrik für String-Listen:

import java.util.ArrayList;
import java.util.List;

public enum StringListFactory implements Factory<List<String>> {

    NEW {
   
        public List<String> instance() {
           
            return(new ArrayList<String>());
        }
    };   
}

JUnit-Test des Fabrikmusters:

import static org.junit.Assert.assertEquals;
import java.util.List;
import org.junit.Test;

public class TestFactory {

    private static final int INDEX_OF_FIRST_LIST_ENTRY = 0;
    private static final int INT_TEST_VALUE = 25;
    private static final String STR_TEST_VALUE = "strValue";
   
    @Test
    public void testStringListFactory() {
       
        final List<String> list = StringListFactory.NEW.instance();
        list.add(STR_TEST_VALUE);
       
        assertEquals(STR_TEST_VALUE, list.get(INDEX_OF_FIRST_LIST_ENTRY));       
    }
   
    @Test
    public void testIntListFactory() {
       
        final List<Integer> list = IntListFactory.NEW.instance();
        list.add(INT_TEST_VALUE);
       
        assertEquals(INT_TEST_VALUE, list.get(0).intValue());       
    }
}

Erbauer (Builder Pattern)

Das Erbauermuster bietet sich immer dann an, falls Objekte mit sehr langen oder unterschiedlichen Parameterlisten erzeugt werden sollen. Der wesentliche Vorteil des Erbauers ist die flexible Konfigurierbarkeit eines Objektes bei seiner Instanziierung.

Erbauer Implementierung:

public class Builder {

    private final int intValue;
    private final String strValue;
           
    public Builder(InnerBuilder builder) {
       
        this.intValue = builder.intValue;
        this.strValue = builder.strValue;
    }

    public int getIntValue() {
       
        return intValue;
    }

    public String getStrValue() {
       
        return strValue;
    }
   
    static class InnerBuilder {
       
        private static final int BUILDER_DEFAULT_INT = 0;
        private static final String BUILDER_DEFAULT_STRING = "";
       
        private int intValue = BUILDER_DEFAULT_INT;       
        private String strValue = BUILDER_DEFAULT_STRING;
       
        public InnerBuilder intValue(final int intValueParam) {           
           
            this.intValue = intValueParam;           
            return(this);
        }
       
        public InnerBuilder strValue(final String strValueParam) {
           
            this.strValue = strValueParam;           
            return(this);
        }
       
        public Builder build() {
           
            return(new Builder(this));
        }
    }
}

JUnit-Test des Erbauers:

import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class TestBuilder {

    private static final int INT_TEST_VALUE = 25;
    private static final String STR_TEST_VALUE = "strValue";

    @Test
    public void testBuilderWithIntAndStringParameter() {
      
        final Builder builder = new Builder.InnerBuilder().
                                    intValue(25).strValue(STR_TEST_VALUE).build();
      
        assertEquals(INT_TEST_VALUE, builder.getIntValue());
        assertEquals(STR_TEST_VALUE, builder.getStrValue());
    }
   
    @Test
    public void testBuilderWithIntParameter() {
      
        final Builder builder = new Builder.InnerBuilder().intValue(25).build();
      
        assertEquals(25, builder.getIntValue());      
    }
   
    @Test
    public void testBuilderWithStringParameter() {
      
        final Builder builder = new Builder.InnerBuilder().strValue(STR_TEST_VALUE).build();
      
        assertEquals(STR_TEST_VALUE, builder.getStrValue());
    }
   
    @Test
    public void testBuilderWithNoParameter() {
      
        final Builder builder = new Builder.InnerBuilder().build();
      
        assertEquals(0, builder.getIntValue());  
        assertEquals("", builder.getStrValue());
    }
}


Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.

Donnerstag, 26. August 2010

Time for REST or work as usual?

Im Java EE Web Services Umfeld hat sich besonders in Bezug auf RESTful Web Services sehr viel getan. Java EE 5 platziert REST neben SOAP basierten Web Services ohne die Tiefe der Entwicklung von SOAP und dem I-Stack erreicht zu haben.

REST ist eine interessante Web Services Technologie, die altbewährte Konzepte des WWW und das Transportprotokoll HTTP nutzt. REST Web Services sind leichtgewichtig und unabhängig von XML, sodass auch einfache Texte, JSON-Daten und die vielfältigen anderen MIME-Typen als Payload ohne den Overhead eines XML-spezifischen SOAP-Envelopes transportiert werden können.

Das Formular eines einfachen HTML-Dokumentes, eine AJAX oder DWR Routine, eine Business Komponente oder ein Rich Client können als REST-Clients fungieren. REST Web Services sind, wie gewöhnliche HTML-Seiten, mit einer URI adressierbar, vor haltbar wie HTML-Seiten (Caching), mit einer einheitlichen Schnittstelle (GET, POST, PUT, DELETE) versehen und statuslos. REST nutzt alle diejenigen Konzepte die das WWW erfolgreich gemacht haben.

RESTful Web Services in Java EE 5 sind mit @WebServiceProvider zu annotieren und müssen die Provider-Schnittstelle implementieren.Clientseitig kann der RESTful Web Service mit der generischen Dispatch-Schnittstelle aufgerufen werden. Eine standardisierte REST-Programmierschnittstelle wie für SOAP-basierte Web Services (JAX-WS) ist in Java EE 5 nicht enthalten.

Exemplarisches Beispiel eines Session Beans als RESTful Web Service Endpoint in Java EE 5:

@Stateless
@WebServiceProvider
@ServiceMode(value = Service.Mode.PAYLOAD)
@BindingType(value = HTTPBinding.HTTP_BINDING)
public class WSRestBean implements Provider<Source> {
   
    enum HTTP_VERB {
       
        GET("GET","Payload for GET-Response"),
        POST("POST","Payload for POST-Response"),
        DELETE("DELETE","Payload for DELETE-Response"),
        PUT("PUT","Payload for PUT-Response");
       
        private final String name;
        private final String response;
       
        private HTTP_VERB(String name, String response) {
           
            this.name = name;
            this.response = response;               
        }
    }
       
    @Resource
    private WebServiceContext wsCtx;
   
    @Override
    public Source invoke(Source source) {
       
        final String httpVerb = getHttpVerbFromMessageContext();
       
        if(httpVerb.equals(HTTP_VERB.GET.name)) {
                       
            return buildResponseStreamSource(HTTP_VERB.GET);
        }
        else if(httpVerb.equals(HTTP_VERB.POST.name)) {
                       
            return buildResponseStreamSource(HTTP_VERB.POST);
           
        }       
        else if(httpVerb.equals(HTTP_VERB.DELETE.name)) {
                       
            return buildResponseStreamSource(HTTP_VERB.DELETE);
        }
        else if(httpVerb.equals(HTTP_VERB.PUT.name)) {
                       
            return buildResponseStreamSource(HTTP_VERB.PUT);
        }
       
        throw new HTTPException(405);
    }
   
    private String getHttpVerbFromMessageContext() {
      
        final MessageContext ctx = wsCtx.getMessageContext();
        String httpVerb = (String) ctx.get(MessageContext.HTTP_REQUEST_METHOD);      
        httpVerb = httpVerb.trim().toUpperCase();
      
        return httpVerb;
    }
 
    private StreamSource buildResponseStreamSource(HTTP_VERB httpVerb) {
       
        printMethod(httpVerb);

        final String xmlMessage = getXmlResponseMessageByHttpVerb(httpVerb);       
        final ByteArrayInputStream stream = new ByteArrayInputStream(xmlMessage.getBytes());
   
        return new StreamSource(stream);
     }

    private String getXmlResponseMessageByHttpVerb(HTTP_VERB httpVerb) {
               
        switch(httpVerb) {
       
          case GET:
             
              return getXmlResponseMessage(HTTP_VERB.GET);
             
          case POST:
             
              return getXmlResponseMessage(HTTP_VERB.POST);
             
          case DELETE:
             
              return getXmlResponseMessage(HTTP_VERB.DELETE);
             
          case PUT:
             
              return getXmlResponseMessage(HTTP_VERB.PUT);             
        }
       
        throw new HTTPException(405);
    }

    private String getXmlResponseMessage(HTTP_VERB httpVerb) {
       
        return "<rest:response xmlns:rest='urn:rest'>"
             + httpVerb.response
             + "</rest:response>";
    }
   
    private void printMethod(HTTP_VERB httpVerb) {
       
        System.out.println("REST Service received - " + httpVerb.name);       
    }
}

Der Einfachheit und Leistungsfähigkeit von RESTful Web Services ist es zu verdanken, dass sich neben JAX-WS in Java EE 6 ein weiterer Standard JAX-RS gesellt. Die JAX-RS Implementierung RESTEasy, die von Bill Burke entwickelt wurde, ist Teil des JBoss Stacks. Bill Burke hat im Rahmen der JAX-RS Expert Group die JAX-RS Spezifikation mitentwickelt.

Sein Buch RESTful Java with JAX-RS beschreibt detailliert die JAX-RS Schnittstelle und Programmierung von RESTful Web Services mit der Programmiersprache Java. Das Buch beinhaltet auch eine Art Workbook in dem die JAX-RS Beispiele der RESTEasy Distribution im Detail erläutert  werden. Entwickler, die sich für JAX-RS und RESTful Web Services interessieren sollten sich die RESTEasy Beispiele und Dokumentation ansehen. Das Buch von Bill Burke ist als zusätzliche Referenz geeignet und ausnahmslos zu empfehlen.

Durch JAX-RS wird die Entwicklung von RESTful Web Services durch die standardisierte Programmierschnittstelle nochmals erleichtert.Deshalb soll in dem Blogbeitrag ein JAX-RS WebService auf Basis von RESTEasy programmiert werden.

Als Programmierumgebung dient JBoss Developer Studio 3.0 GA, der JBoss AS 5.1 GA und die RESTEasy 1.2 GA (resteasy-jaxrs-1.2.GA-all.zip) Distribution. Im JBoss ist eine Datasource zu einer installierten relationalen Datenbank anzulegen. 

Implementiert wird ein RESTful Web Service, der Daten über ein einfaches HTML-Frontend in eine Datenbank schreiben und Daten lesen kann.Die Funktionalität des REST Web Services soll ein rudimentärer Blog sein.

Beim erste Schritt der Implementierung ist ein Workspace mit dem Namen "blog" anzulegen. Im Workspace wird ein "Enterprise Application Project" mit dem Namen Blog-Project angelegt.Das Projekt mündet in eine EAR-Datei, die zwei Teilprojekte "BlogProjectEJB" (JAR) und "BlogProjectWeb" (WAR) beinhaltet.

Die nächsten Konfigurationsschritte beinhalten, dass in das META-INF Verzeichnis des Projektes "BlogProjectEJB" die Datei persistence.xml hinterlegt wird.

Der Aufbau der persistence.xml sieht folgendermaßen aus:

<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
       <persistence-unit name="EmployeeService">
           <jta-data-source>java:/DataSourceName</jta-data-source>
           <properties>
               <property name="hibernate.hbm2ddl.auto"
                   value="create-drop" />
           </properties>
       </persistence-unit>  
</persistence>

Der DataSourceName muß mit der konfigurierten Datasource im Applikationsserver übereinstimmen. Das konfigurierte Hibernate-Property besagt, dass die DB-Struktur aus den Entitäten erzeugt wird. Dabei wird die Datenbankstruktur bei jedem Serverstart angelegt, bereits gespeicherte Daten bleiben deshalb nicht persistent erhalten.Obwohl wir JPA zum DB-Zugriff verwenden ist das Hibernate-Property korrekt, weil Hibernate der JPA-Provider des JBoss AS ist.

In das WEB-INF/lib Verzeichnis des "BlogProjectWeb" Projektes sind aus dem /lib Verzeichnis der RESTEasy Distribution alle JARs zu kopieren. Danach ändert man die web.xml.

Die web.xml muss folgende RESTEasy-Einträge beinhalten:

<web-app>
    <context-param>
        <param-name>resteasy.servlet.mapping.prefix</param-name>
        <param-value>/rest</param-value>
    </context-param>
    <servlet>
        <servlet-name>Resteasy</servlet-name>
        <servlet-class>
            org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
        </servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>ccd.jee.ws.rest.application.ServiceApplication
            </param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>Resteasy</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>
</web-app>

Im Quellordner des Projektes "BlogProjectWeb" legt man die Datei jndi.properties an. 

java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=jnp\://localhost\:1099

Die Einträge der Datei jndi.properties werden für das Lookup der Web Service Facade (BlogServiceBean) im EJB-Container benötigt. Der RESTful Web Service läuft im Web-Container und greift über die Remote-Schnittstelle auf die Web Service Facade in der EJB-Schicht zu. Die strikte Trennung des Web Service von  der Facade hat den Vorteil, dass man den Web Service (bei entsprechendem Bedarf ) später auch in einem externen Web-Container laufen lassen kann.

Im Quellordner des Projektes "BlogProjectWeb" wird nun das Package ccd.jee.ws.rest.service mit der Klasse BlogService angelegt.

Danach legt man das Package ccd.jee.ws.rest.application an und erzeugt in dem Package die Klasse ServiceApplication.

Die Klasse ServiceApplication hat folgenden Inhalt:

import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.core.Application;
import ccd.jee.ws.rest.service.BlogService;

public class ServiceApplication extends Application {

    private Set<Object> singletons = new HashSet<Object>();
    private Set<Class<?>> empty = new HashSet<Class<?>>();

    public ServiceApplication() {
        singletons.add(new BlogService());
    }

    @Override
    public Set<Class<?>> getClasses() {
        return empty;
    }

    @Override
    public Set<Object> getSingletons() {
        return singletons;
    }
}

Jetzt wechselt man in den Quellordner (ejbModule) des Projektes "BlogProjectEJB" und legt das Package ccd.jee.ws.domain an. In dem Package erzeugt man die Entity-Klasse BlogEntry.

Die Klasse BlogEntry hat folgenden Aufbau:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class BlogEntry {

    @Id
    @GeneratedValue
    private int id;
  
    private String title;
    private String entry;
  
    public int getId() {
        return id;
    }
  
    public String getTitle() {
        return title;
    }
  
    public String getEntry() {
        return entry;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public void setEntry(String entry) {
        this.entry = entry;
    }

    @Override
    public String toString() {
        return "BlogEntry [entry=" + entry + ", title=" + title + "]";
    }
}

Nun legt man das Package ccd.jee.ws.domain.dao an und erzeugt in dem Package die lokale Schnittstelle und die Klasse eines einfachen Data Access Objects (DAO).

Die lokale Schnittstelle des DAOs hat folgenden Aufbau:

import javax.ejb.Local;
import ccd.jee.ws.domain.BlogEntry;

@Local
public interface BlogServiceDao {

    public void store(String title, String entry);
    public BlogEntry getById(Integer id);
}

Das DAO hat folgenden Aufbau:

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import ccd.jee.ws.domain.BlogEntry;

@Stateless
public class BlogServiceDaoBean implements BlogServiceDao {

   @PersistenceContext
   private EntityManager em;

   public void store(String title, String entry) {

       BlogEntry blogEntry = new BlogEntry();
       blogEntry.setTitle(title);
       blogEntry.setEntry(entry);

       em.persist(blogEntry);
    }

    public BlogEntry getById(Integer id) {

       return em.find(BlogEntry.class, id);
    }
}

Das einfache Domain-Modell wurde angelegt, sodass nun ein weiteres Package im Quellordner mit dem Namen ccd.jee.ws.session anzulegen ist. Im soeben neu angelegten Package legt man die Remote-Schnittstelle  BlogServiceRemote an.

Remote-Schnittstelle des Blog Service (die auch als Facade Interface dient):

import javax.ejb.Remote;
import org.jboss.ejb3.annotation.RemoteBinding;

@Remote
@RemoteBinding(jndiBinding = "ejb/BlogService")
public interface BlogServiceRemote {

    public String getBlogEntryById(Integer id);
    public void storeBlogEntry(String title, String entry);
}

Das statuslose Session Bean legt man im Package ccd.jee.ws.session an:

import javax.ejb.EJB;
import javax.ejb.Stateless;
import ccd.jee.ws.domain.BlogEntry;
import ccd.jee.ws.domain.dao.BlogServiceDao;

@Stateless
public class BlogServiceBean implements BlogServiceRemote {

    @EJB private BlogServiceDao blogServiceDao;
   
    public String getBlogEntryById(Integer id) {
       
        final BlogEntry blogEntry = blogServiceDao.getById(id);
       
        return blogEntry.toString();
    }

    public void storeBlogEntry(String title, String entry) {
       
        blogServiceDao.store(title, entry);       
    }
}

Die Quellen, die im EJB-Container laufen sollen, sind nun erzeugt, sodass wir nun wieder zurück zum Projekt "BlogProjectWeb" gehen können. Dort legen wir zunächst ein Package ccd.jee.ws.rest.helper für Helper-Klassen an. Die Helper Klassen kümmern sich um das Lookup der entfernten Schnittstelle des statuslosen Session Beans, welches im EJB-Container läuft. Nachfolgend sind die Helper-Klassen gelistet, die zu erstellen sind.

BlogServiceReferenceException

public class BlogServiceReferenceException extends RuntimeException {

    public BlogServiceReferenceException(Exception ex) {
        super(ex);
    }
}

BlogServiceReference

import javax.naming.InitialContext;
import javax.naming.NamingException;
import ccd.jee.ws.session.BlogServiceRemote;

class BlogServiceReference {
   
    private final InitialContext context;
   
    BlogServiceReference() {
       
        try {
           
            context = new InitialContext();
           
        }
        catch (NamingException ex) {
           
            throw new BlogServiceReferenceException(ex);
        }
    }
   
    public BlogServiceRemote getRemoteBlogService() {
       
        try {
           
            return (BlogServiceRemote) context.lookup("ejb/BlogService");
        }
        catch (NamingException ex) {
           
            throw new BlogServiceReferenceException(ex);
        }
    }
}

BlogServiceLocator

import ccd.jee.ws.session.BlogServiceRemote;

public class BlogServiceLocator {
   
    private BlogServiceLocator() {}
   
    private static final class DemandHolder {
       
        private static final BlogServiceReference resource = new BlogServiceReference();
    }
   
    public static BlogServiceRemote getInstance() {
       
        return(DemandHolder.resource.getRemoteBlogService());
    }
}

Damit die Remote-Schnittstelle im Projekt "BlogProjectWeb" gefunden werden kann, ist das Projekt "BlogProjectEJB" in den Java EE Module Dependencies (Alt-Enter) bei den JEE Modules einzubinden (Häkchen bei JAR/Module setzen). Normalerweise würde die Remote-Schnittstelle und Helper-Klassen in einem eigenen JAR untergebracht werden, der einfachheithalber wird allerdings auf diesen Schritt verzichtet.

Zum Abschluss ist noch folgender Quellcode in der angelegten BlogService Klasse zu erstellen:

import java.net.URI;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import ccd.jee.ws.rest.helper.BlogServiceLocator;
import ccd.jee.ws.session.BlogServiceRemote;

@Path("/blog")
public class BlogService {
   
    @GET
    @Produces("text/html")
    @Path("{id}")
    public Response getBlogEntry(@PathParam("id") Integer id) {
       
        BlogServiceRemote blogService = BlogServiceLocator.getInstance();
        final String blogEntry = blogService.getBlogEntryById(id);
       
        return Response.created(URI.create("/blog/")).
                status(200).entity(blogEntry).build();           
    }
   
    @POST
    @Produces("text/html")
    public Response putBlogEntry(@FormParam("title") String title,
                                                 @FormParam("entry") String entry) {
       
        BlogServiceRemote blogService = BlogServiceLocator.getInstance();
        blogService.storeBlogEntry(title, entry);
       
        return Response.created(URI.create("/blog/")).status(200).
                entity("Der Blog-Eintrag ist erfolgreich angelegt worden!").build();
    }
}

Als letzter Schritt ist noch eine einfache HTML-Datei mit folgendem Aufbau zu erstellen:

<html>
<head><title>REST-Blog</title></head>
<body>
<form action="/BlogProjectWeb/rest/blog" method = "post">
<table>
<tr>
<td style="FONT-WEIGHT: bold;"><u style="BACKGROUND-COLOR: #ff8000;">REST-Blog</u>
</td>
<td style="BACKGROUND-COLOR: #ff8000; COLOR: #ff8000;">
</td>
</tr>
<tr>
<td>Blogtitel:</td>
<td><input type = "text" name = "title" style=" width : 457px;"/>
</td>
</tr>
<tr>
<td>Blogeintrag:</td><td><textarea name = "entry" style=" width : 457px;"></textarea>
</td>
</tr>
<tr>
<td></td>
<td style=" width : 141px;"><input type = "submit" value = "Blogeintrag senden" style=" height : 24px;"/>
</td>
</tr>
</table>
</form>
</body>
</html>

Die HTML-Datei ist im Projekt "BlogProjectWeb"in den WebContent-Ordner zu legen.

Der Blog Service ist eine Full Stack Java EE Anwendung und kann nun in die JBoss AS 5.1 Laufzeitumgebung deployed werden. Wenn alles glatt gelaufen ist, startet der JBoss AS ohne Deployment-Fehler. Die Anwendung wartet nun darauf verwendet zu werden. Nachfolgend ist die Anwendung im Browser-Frontend zu sehen.

HTML-Formular des Blog Services zum Anlegen eines Blogeintrages
Abfrage eines Blogeintrages mit der URI: http://localhost:<PORT>/BlogProjectWeb/rest/blog/1
Der Eintrag <PORT> ist entsprechend den lokalen Porteinstellungen des JBoss AS zu ändern!

Projektstruktur im JBoss Developer Studio 3.0

































Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.

Mittwoch, 25. August 2010

Rock SOLID oder SOLID Rocks

SOLID ist ein künstliches Kurzwort von Robert C. Martin. SOLID steht für die fünf Basispatterns der objektorientierten Programmierung und Designs. Die Patterns in ihrer Kombination erlauben es wartbare und nachhaltige Software zu schreiben. SOLID wird häufig beim Refactoring eingesetzt um "Code Smells" zu entfernen.In der Designphase hingegen wird SOLID beim Klassendesign als Richtlinie verwendet.

Single Responsibility Principle (SRP)

Eine Klasse sollte nur eine einzige Zuständigkeit und damit verbunden auch nur einen einzigen Änderungsgrund haben. Robert C. Martin hat diesen Term basierend auf dem Prinzip der Kohäsion von Tom DeMarco formuliert und beschrieben.

Die Kohäsion einer Klasse ist ein interessanter Aspekt und bedarf näherer Betrachtung. Eine Klasse besitzt eine hohe Kohäsion durch einen hohen Grad der Spezialisierung. Monsterklassen, denen man sporadisch begegnet, sind oftmals zur zentralen Schnittstelle mit wenig Funktionalität und häufig nur zum Lookup von Ressourcen geworden.

Monsterklassen, die als Vermittler und Verteiler zum Ansprechen unterschiedlichster Ressourcen fungieren, sind definitiv mit schwacher Kohäsion ausgestattet. Diese Klassen arbeiten perfekt in ihrer gewohnten Umgebung , weil die Menge der Funktionalitäten leicht vom Entwickler genutzt werden kann. Im Zuge der Weiterentwicklung, Modularisierung und Wiederverwendung sind solche Klassen allerdings grauenvoll und bei entsprechender Komplexität kaum zu refaktorisieren. Das unüberschaubare Netz dieser Klassen breitet sich von einer zentralen Stelle bis in den letzten Winkel eines Softwareproduktes aus.

Die Kohäsion einer Klasse bezieht sich nicht nur auf die reinen Funktionalitäten in Form von Methoden, sondern auch auf die Objektdaten ausgedrückt durch Attribute. Übel sind schwach kohesive Datenstrukturen, die gegen das Prinzip der Kapselung verstoßen und deswegen auch noch öffentlich zugänglich sind.

Open Closed Principle (OCP)

Eine Klasse sollte offen für Erweiterungen, aber geschlossen für Modifikationen sein. In der OOP wird dieses Prinzip durch Vererbung und Polymorphie erfüllt. Das Prinzip ist von Dr. Bertrand Meyer in seinem Buch Object Oriented Software Construction formuliert und publiziert worden..

Die Grundidee des Prinzips ist, dass sobald eine Klasse fertiggestellt ist, diese nur noch im Fehlerfall korrigiert wird. Neue und zu ändernde Funktionalitäten sind in einer abgeleiteten Klasse zu deklarieren. Abgeleitete Klassen nutzen dabei die Funktionalitäten der Basisklassen durch Vererbung wodurch die saubere Wiederverwendung von Quellcode ohne Duplizierung erreicht wird.

Die Ableitung von Klassen und das Nutzen der objektorientierten Möglichkeiten durch Patterns wie beispielsweise Template Method und Hooks erlaubt es, dass nicht nur die abgeleitete Klasse Funktionalitäten der Basisklasse verwendet, sondern auch das die Basisklasse Funktionalitäten nutzt, die erst in der abgeleiteten Klasse formuliert werden.

Exemplarisches Beispiel für Template Method:

abstract class TemplateMethod {

    final void calculate() {
   
        abstractOperation();
        operation();       
        hook();
    }
   
    /*
     * overridden in subclass
     */
    abstract void abstractOperation();
   
    void operation() {
        // implementation
    }
   
    void hook() {
        // implementation in subclass
    }
}

public class ConcreteTemplateMethod extends TemplateMethod {

    @Override
    void abstractOperation() {
        // implementation
    }

    @Override
    void hook() {
        // implementation of the hook
    }
}

Liskov Substitution Principle (LSP)

Jede Erweiterung einer Klasse soll die Basisklasse vollständig ersetzen. Eine abgeleitete Klasse darf sich nicht anders als seine Basisklasse verhalten und in keinem Fall die Sicht auf die Basisklasse einschränken oder gar verdecken. Das Prinzip wurde von Barbara Liskov und Jeannette Wing formuliert. In diesem Prinzip spielt die Erwartungshaltung eine Rolle. Grundsätzlich sollte sich eine Klasse so verhalten wie ein Entwickler das erwarten würde. Im Zuge von Vererbungshierarchien kann es dabei unter Umständen zu Abweichungen kommen.

Ein einfaches Beispiel ist die Fehlerbehandlung. Wirft eine Methode der Basisklasse eine überprüfte Ausnahme, muss die überschreibende Methode in der abgeleiteten Klasse dieselbe Ausnahme werfen. In manchen Fällen ist das Liskov Prinzip nur sehr schwer einzuhalten, sodass anstelle von Vererbung die Delegierung angewendet wird.

Das Delegieren trägt zur losen Kopplung bei, lässt Klassen leicht verständlich erscheinen, fördert die Erweiterbarkeit und minimiert Seiteneffekte. Häufig sind lose gekoppelte Klassen die zwingende Voraussetzung für TDD.

 Exemplarisches Beispiel für eine Delegierung:

interface IToDelegate {

    public void delegateMethod();
}

class ToDelegate implements IToDelegate {
   
    public void delegateMethod() {
        // implementation
    }
}

public class DelegateTo implements IToDelegate {

    private final IToDelegate delegate = new ToDelegate();
   
    @Override
    public void delegateMethod() {
        delegate.delegateMethod();       
    }
}

Interface Segregation Principle (ISP)

In einer Schnittstelle sollen nur die Methoden eingefügt werden, die auch tatsächlich Verwendung finden. Überflüssige Methoden verkomplizieren und überladen eine Schnittstelle. Das Prinzip ist von Robert C. Martin formuliert worden. Wesentlich ist das öffentliche Schnittstellen nach ihrer Publizierung nicht mehr verändert werden dürfen, sodass die Menge von Methoden, die nicht verwendet werden, für alle Ewigkeit in der öffentlichen Schnittstelle verbleiben.

Schnittstellen sollten mit Bedacht implementiert werden. Es ist nicht zielführend die Schnittstelle einer Collection-Klasse zu implementieren, wenn dabei viele Methoden implementiert werden müssen, für die es eigentlich keinen Verwender gibt.

Es ist ratsam Schnittstellen nicht grundsätzlich zu verwenden. Die minimale Voraussetzung einer Schnittstelle sollte sein, dass es zwei unterschiedliche Implementierungen für die Schnittstelle gibt.

Dependency Inversion Principle (DIP)

Das Abhängigkeitsinversionsprinzip schreibt vor, das Klassen möglichst nicht von konkreten Implementierungen anderer Klassen, sondern von deren Schnittstellen abhängig sein sollen. Der Vorteil dabei ist, dass man die Implementierung bei Bedarf ändern kann ohne einen Seiteneffekt hervorzurufen, der durch viele undurchschaubare Abhängigkeiten entstehen kann. Dependency Inversion fördert deshalb deutlich die Wartbarkeit einer Software.

Robert C. Martin hat das Dependency Inversion Principle in zahlreichen Publikationen beschrieben, sodass es heute vielfältig Anwendung findet. Dependency Inversion führt zu lose gekoppelten Komponenten. Module eines höheren Levels sollen dabei nicht von Moduln eines niedrigeren Levels anbhängig sein. Abstraktionen sollen nicht von Details abhängen und umgekehrt sollen Details nicht von einer Abstraktion abhängig sein.

Dependency Inversion drückt sich auch im Adapter-Pattern aus, welches sich sehr gut zur Adaption unterschiedlicher Schnittstellen eignet.

Exemplarisches Beispiel für einen Adapter:

interface Enumeration {

    public boolean hasMoreElements();
    public Object nextElement();
}

interface Iterator {

    public boolean hasNext();
    public Object next();
    public void remove();
}

public final class EnumerationIteratorAdapter implements Iterator {

    private final Enumeration enumeration;
       
    public EnumerationIteratorAdapter(final Enumeration enumeration) {   
        this.enumeration = enumeration;
    }

    @Override
    public boolean hasNext() {       
        return(enumeration.hasMoreElements());
    }

    @Override
    public Object next() {
        return(enumeration.nextElement());
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }
}


Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.