SCA z językami skryptowymi w wykonaniu Apache Tuscany, Jetty i Maven 2

Z Jacek Laskowski - Wiki Projektanta Java EE

Celem artykułu jest demonstracja możliwości Apache Tuscany jako środowiska wykonawczego SCA (Service Component Architecture), które umożliwia tworzenie SCAleń opartych o usługi napisane w językach skryptowych. Możliwość ich uruchamienia Tuscany zawdzięcza dostępnym implementacjom języków skryptowych w Javie, np. Groovy, Rhino, Jython, JRuby, jednakże z pojawieniem się Java 6 z obsługą standardu JSR 223 (Scripting for the Java Platform) należy się spodziewać, że stanie się to kanonem programistycznym i tworzenie rozwiązań z językami skryptowymi nie będzie niczym unikatowym. Poza możliwością programowania skryptowego do tworzenia usług, implementacja SCA w wykonaniu Apache Tuscany jest przede wszystkim darmowa. Mówi się również, że Apache Tuscany ma podjąć się roli platformy referencyjnej dla SCA i SDO.

Gotowy projekt do uruchomienia (cała struktura projektu wraz z niezbędnymi elementami składowymi opisanymi poniżej) można pobrać jako sca-dictionary-web.zip. Wystarczy jedynie wykonać polecenie mvn clean jetty:run-exploded, co spowoduje uruchomienie SCAlenia w środowisku serwera Jetty i wejść na stronę http://localhost:8080/sca-dictionary-web/.

Spis treści

Utworzenie projektu SCAlenia - sca-dictionary-web

Rozpoczynamy tworzenie SCAlenia od utworzenia projektu sca-dictionary-web za pomocą M2. Tworzymy aplikację internetową, więc skorzystamy z archetypu maven-archetype-webapp (domyślnie byłby to maven-archetype-quickstart, który utworzyłby jedynie bibliotekę - plik jar).

$ mvn archetype:create -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeVersion=1.0 -DgroupId=pl.jaceklaskowski.sca.dictionary -DartifactId=sca-dictionary-web
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ----------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO]    task-segment: [archetype:create] (aggregator-style)
[INFO] ----------------------------------------------------------------------------
...
[INFO] Archetype created in dir: c:\sca-dictionary-web
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 12 seconds
[INFO] Finished at: Fri May 25 01:02:12 CEST 2007
[INFO] Final Memory: 5M/254M
[INFO] ------------------------------------------------------------------------

Przechodzimy do katalogu c:/sca-dictionary-web

$ cd sca-dictionary-web

Kolejne polecenia wydawane będą właśnie z tego katalogu.

Na bazie poprzedniego artykułu - SCAlenie (kompozyt) z Apache Tuscany i Apache Maven, w którym przygotowaliśmy środowisko do rozpoznawania SCA w wydaniu Apache Tuscany i zbudowaliśmy przykładowe SCAlenie, rozbudujemy nasz aktualny projekt internetowy. Pobierzmy aplikację dostępną jako sca-dictionary.zip i rozpakujmy ją w wybranym przez siebie katalogu, np. c:/sca-dictionary. Zaporzyczymy od niego kilka klas do naszego nowego projektu.

Kopiujemy klasy należące do pakietu pl.jaceklaskowski.sca.dictionary z katalogu src/main/java aplikacji sca-dictionary z poprzedniego artykułu do katalogu src/main.

$ cp -R ../sca-dictionary/src/main/java src/main/

Podobnie postępujemy z deskryptorem SCAlenia - Dictionary.composite.

$ cp ../sca-dictionary/src/main/resources/Dictionary.composite src/main/resources/

Utworzenie konfiguracji projektu - pom.xml

Na zakończenie zestawiania projektu zmodyfikujemy plik konfiguracyjny projektu pom.xml na następującą treść.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>pl.jaceklaskowski.sca.dictionary</groupId>
  <artifactId>sca-dictionary-web</artifactId>
  <packaging>war</packaging>
  <version>1.0</version>
  <name>SCAlenie Dictionary w Aplikacji Internetowej</name>
  <url>http://www.JacekLaskowski.pl</url>
  <repositories>
    <repository>
      <id>apache.snapshot</id>
      <url>http://people.apache.org/repo/m2-snapshot-repository</url>
    </repository>
  </repositories>
  <dependencies>
    <dependency>
      <groupId>org.apache.tuscany.sca</groupId>
      <artifactId>tuscany-host-webapp</artifactId>
      <version>1.0-incubating-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>org.apache.tuscany.sca</groupId>
      <artifactId>tuscany-implementation-java-runtime</artifactId>
      <version>1.0-incubating-SNAPSHOT</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.tuscany.sca</groupId>
      <artifactId>tuscany-implementation-script</artifactId>
      <version>1.0-incubating-SNAPSHOT</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <finalName>${artifactId}</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>

Niepoprawny język.

Musisz wybrać język w następujący sposób: <source lang="html4strict">...</source>

Języki obsługiwane w podświetlaniu składni:

abap, actionscript, actionscript3, ada, apache, applescript, apt_sources, asm, asp, autoit, avisynth, bash, basic4gl, bf, bibtex, blitzbasic, bnf, boo, c, c_mac, caddcl, cadlisp, cfdg, cfm, cil, cmake, cobol, cpp, cpp-qt, csharp, css, d, dcs, delphi, diff, div, dos, dot, eiffel, email, erlang, fo, fortran, freebasic, genero, gettext, glsl, gml, gnuplot, groovy, haskell, hq9plus, html4strict, idl, ini, inno, intercal, io, java, java5, javascript, kixtart, klonec, klonecpp, latex, lisp, locobasic, lolcode, lotusformulas, lotusscript, lscript, lsl2, lua, m68k, make, matlab, mirc, modula3, mpasm, mxml, mysql, nsis, oberon2, objc, ocaml, ocaml-brief, oobas, oracle11, oracle8, pascal, per, perl, php, php-brief, pic16, pixelbender, plsql, povray, powershell, progress, prolog, properties, providex, python, qbasic, rails, rebol, reg, robots, ruby, sas, scala, scheme, scilab, sdlbasic, smalltalk, smarty, sql, tcl, teraterm, text, thinbasic, tsql, typoscript, vb, vbnet, verilog, vhdl, vim, visualfoxpro, visualprolog, whitespace, whois, winbatch, xml, xorg_conf, xpp, z80

          <target>1.5</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
        <version>6.1.3</version>
      </plugin>
    </plugins>
  </build>
</project>

Otwieramy projekt w wybranym przez siebie środowisku programistycznym (IDE), np. w przypadku NetBeans 6.0, który wspiera projekty M2 jak własne, sprowadza się to do otwarcia katalogu z projektem podczas, gdy dla Eclipse IDE należy utworzyć pliki konfiguracyjne dla projektu poleceniem mvn eclipse:eclipse. Podobnie jest w przypadku IntelliJ IDEA, gdzie korzystamy z polecenia mvn idea:idea. Wybór IDE nie ma większego znaczenia na dalszą pracę.

$ mvn eclipse:eclipse
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'eclipse'.
[INFO] ----------------------------------------------------------------------------
[INFO] Building SCAlenie Dictionary w Aplikacji Internetowej
[INFO]    task-segment: [eclipse:eclipse]
[INFO] ----------------------------------------------------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6 seconds
[INFO] Finished at: Fri May 25 01:05:16 CEST 2007
[INFO] Final Memory: 6M/254M
[INFO] ------------------------------------------------------------------------

Utworzenie deskryptora aplikacji internetowej - web.xml

Modyfikujemy deskryptor aplikacji internetowej - web.xml, aby zawierał deklarację klasy przechwytującej zdarzenie uruchomienia aplikacji w kontenerze servletów i uruchomił Tuscany. Jest to typowy sposób integracji technologii niekoniecznie stricte internetowych ze środowiskiem servletów.

Plik web.xml znajduje się w katalogu src/main/webapp/WEB-INF.

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
  <display-name>SCAlenie Dictionary w Aplikacji Internetowej</display-name>

  <listener>
    <listener-class>org.apache.tuscany.sca.webapp.TuscanyContextListener</listener-class>
  </listener>

</web-app>

Jest to jeden z dwóch opisanych w Web Application Integration story goals sposobów uruchomienia Tuscany w środowisku aplikacji internetowej.

Utworzenie deskryptora internetowego SCAlenia - sca-contribution.xml

Na koniec utworzymy plik sca-contribution.xml definiujący SCAlenie w aplikacji internetowej.

<contribution xmlns="http://www.osoa.org/xmlns/sca/1.0">
  <deployable composite="Dictionary" />
</contribution>

Plik sca-contribution.xml umieszczamy w katalogu src/main/webapp/META-INF.

Utworzenie przykładowej strony internetowej - index.jsp

Modyfikujemy przykładowy plik index.jsp, który jest faktyczną częścią aplikacji internetowej, która skorzysta ze SCAlenia na poniższy. Plik znajduje się w katalogu src/main/webapp i został automatycznie stworzony przez M2.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<%@ page import="org.apache.tuscany.sca.host.embedded.SCADomain" %>
<%@ page import="org.apache.tuscany.sca.webapp.SCADomainHelper" %>
<%@ page import="pl.jaceklaskowski.sca.dictionary.DictionaryService" %>

<%@ page contentType="text/html;charset=UTF-8" language="java"%>

<%
   SCADomain scaDomain = (SCADomain) application.getAttribute(SCADomainHelper.SCA_DOMAIN_ATTRIBUTE);
   DictionaryService dictionaryService = scaDomain.getService(DictionaryService.class, "DictionaryServiceComponent");
%>
<html>
 <head>
  <title>SCA z językami skryptowymi w wykonaniu Apache Tuscany, Jetty i Maven 2</title>
 </head>
 
 <body>
  <table>
    <tr>
      <th>Słownik</th>
      <th>Wynik tłumaczenia słowa "SCAlenie"</th>
    </tr>
    <tr>
      <td>Test słownika polsko-angielskiego</td>
      <td align="center"><%= dictionaryService.translate("SCAlenie", "pl", "en") %></td>
    </tr>
    <tr>
      <td>Test słownika angielsko-polskiego</td>
      <td align="center"><%= dictionaryService.translate("SCAlenie", "en", "pl") %></td>
    </tr>
    <tr>
      <td>Test słownika niemiecko-polskiego</td>
      <td align="center"><%= dictionaryService.translate("SCAlenie", "de", "pl") %></td>
    </tr>
  </table>
 </body>
</html>

Uruchomienie SCAlenia w aplikacji internetowej

Uruchamiamy naszą aplikację internetową korzystając z wtyczki Jetty dla Maven.

$ mvn clean jetty:run-exploded
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'jetty'.
[INFO] ----------------------------------------------------------------------------
[INFO] Building SCAlenie Dictionary w Aplikacji Internetowej
[INFO]    task-segment: [clean, jetty:run-exploded]
[INFO] ----------------------------------------------------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3 minutes 11 seconds
[INFO] Finished at: Fri May 25 01:12:48 CEST 2007
[INFO] Final Memory: 12M/254M
[INFO] ------------------------------------------------------------------------

Otwieramy stronę http://localhost:8080/sca-dictionary-web w przeglądarce i doświadczamy wspaniałego uczucia (połowicznego) sukcesu!

Grafika:sca-dictionary-web-firstrun.PNG

Krok pierwszy uruchomienia SCAlenia w środowisku aplikacji internetowej zakończony pomyślnie. Przechodzimy do kolejnego kroku, którym jest skorzystanie z języka skryptowego do utworzenia kolejnej usługi, z której skorzysta SCAlenie.

Utworzenie usługi GermanPolishDictionaryService w Groovy

Przystąpimy do utworzenia dodatkowej usługi w Groovy, która rozszerzy możliwości SCAlenia o funkcjonalność słownika niemiecko-polskiego.

Utworzenie interfejsu usługi GermanPolishDictionaryService

Interfejs usługi nie będzie niczym zaskakującym - ot, zwykły interfejs w Javie (w zasadzie kopia poprzednich)

package pl.jaceklaskowski.sca.dictionary;

public interface GermanPolishDictionaryService {
	String translate(String word);
}

Plik GermanPolishDictionaryService.java umieszczamy w katalogu src/main/java/pl/jaceklaskowski/sca/dictionary.

Utworzenie implementacji usługi GermanPolishDictionaryService

Właśnie teraz ujawni się cała siła Tuscany jako środowiska realizującego specyfikację SCA, który umożliwia budowanie SCAleń z usług implementowanych w językach skryptowych. Wybierzmy jeden - Groovy i napiszmy realizację usługi GermanPolishDictionaryService.

def translate(word) {
   return "de_" + word
}

Plik GermanPolishDictionaryServiceImpl.groovy umieszczamy w katalogu src/main/resources/pl/jaceklaskowski/sca/dictionary/groovy.

Uaktywnienie usługi GermanPolishDictionaryService

Rozpoczniemy od modyfikacji implementacji komponentu DictionaryServiceComponent, tj. modyfikacji klasy pl.jaceklaskowski.sca.dictionary.DictionaryServiceImpl (plik znajduje się w katalogu src/main/java/pl/jaceklaskowski/sca/dictionary) tak, aby zawierała poniższą treść.

package pl.jaceklaskowski.sca.dictionary;

import org.osoa.sca.annotations.Reference;

public class DictionaryServiceImpl implements DictionaryService {

    private PolishEnglishDictionaryService polishEnglishDictionaryService;

    private EnglishPolishDictionaryService englishPolishDictionaryService;

    private GermanPolishDictionaryService germanPolishDictionaryService;

    @Reference
    public void setPolishEnglishDictionaryService(PolishEnglishDictionaryService polishEnglishDictionaryService) {
        this.polishEnglishDictionaryService = polishEnglishDictionaryService;
    }

    @Reference
    public void setEnglishPolishDictionaryService(EnglishPolishDictionaryService englishPolishDictionaryService) {
        this.englishPolishDictionaryService = englishPolishDictionaryService;
    }

    @Reference
    public void setGermanPolishDictionaryService(GermanPolishDictionaryService germanPolishDictionaryService) {
        this.germanPolishDictionaryService = germanPolishDictionaryService;
    }

    public String translate(String word, String fromLanguage, String toLanguage) {
        if (fromLanguage.equals("pl") && toLanguage.equals("en")) {
            return polishEnglishDictionaryService.translate(word);
        } else if (fromLanguage.equals("en") && toLanguage.equals("pl")) {
            return englishPolishDictionaryService.translate(word);
        } else if (fromLanguage.equals("de") && toLanguage.equals("pl")) {
            return germanPolishDictionaryService.translate(word);
        }
        // no translation; pass it along
        return word;
    }
}

Jak w przypadku poprzednio wykorzystanych usług klasa komponentu deklaruje zależność (za pomocą adnotacji @Resource) od nowej usługi GermanPolishDictionaryService, a metoda translate wywołuje ją w przypadku wystąpienia odpowiednich parametrów wejściowych.

Deskryptor SCAlenia - Dictionary.composite

Do tej pory nie skorzystaliśmy z implementacji nowej usługi napisanej w języku skryptowym Groovy. Miejscem, w którym deklarujemy (zamiast oprogramowywania) zależność od usługi i jej implementacji w Groovy jest deskryptor SCAlenia - Dictionary.composite. Zmodyfikujmy plik, który utworzyliśmy na bazie poprzedniego artykułu, na następującą treść. Plik znajduje się w katalogu src/main/resources.

<?xml version="1.0" encoding="UTF-8"?>

<composite xmlns="http://www.osoa.org/xmlns/sca/1.0" name="Dictionary">

  <component name="DictionaryServiceComponent">
    <implementation.java class="pl.jaceklaskowski.sca.dictionary.DictionaryServiceImpl" />
    <reference name="polishEnglishDictionaryService" target="PolishEnglishDictionaryServiceComponent" />
    <reference name="englishPolishDictionaryService" target="EnglishPolishDictionaryServiceComponent" />
    <reference name="germanPolishDictionaryService" target="GermanPolishDictionaryServiceComponent" />
  </component>

  <component name="PolishEnglishDictionaryServiceComponent">
    <implementation.java class="pl.jaceklaskowski.sca.dictionary.EnglishPolishDictionaryServiceImpl" />
  </component>

  <component name="EnglishPolishDictionaryServiceComponent">
    <implementation.java class="pl.jaceklaskowski.sca.dictionary.PolishEnglishDictionaryServiceImpl" />
  </component>

  <component name="GermanPolishDictionaryServiceComponent">
    <implementation.script script="pl/jaceklaskowski/sca/dictionary/groovy/GermanPolishDictionaryServiceImpl.groovy"/>
  </component>

</composite>

Deklaracja nowej usługi i związanie jej z komponentem DictionaryServiceComponent odbywa się w typowy sposób (przez typowy rozumiemy naszą znajomość SCA i Tuscany z poprzedniego artykułu). Co różni tę deklarację od poprzednich jest fakt skorzystania z elementu implementation.script, w którym wskazujemy na skrypt będący implementacją usługi przy pomocy atrybutu script (poprzednio korzystaliśmy z elementu implementation.java).

Uruchomienie SCAlenia w aplikacji internetowej

Kolejny raz uruchamiamy naszą aplikację internetową.

$ mvn clean jetty:run-exploded
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'jetty'.
[INFO] ----------------------------------------------------------------------------
[INFO] Building SCAlenie Dictionary w Aplikacji Internetowej
[INFO]    task-segment: [clean, jetty:run-exploded]
[INFO] ----------------------------------------------------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 18 seconds
[INFO] Finished at: Fri May 25 01:22:56 CEST 2007
[INFO] Final Memory: 12M/254M
[INFO] ------------------------------------------------------------------------

Otwieramy stronę http://localhost:8080/sca-dictionary-web w przeglądarce i podziwiamy wykonanie usługi napisanej w Groovy!

Grafika:sca-dictionary-web-secondrun.PNG

Pytanie, które z pewnością pojawi się, to skąd wiadomo, że usługa napisana w Groovy w ogóle została uruchomiona?! Poprzednie wykonanie usługi podczas pierwszego uruchomienia aplikacji internetowej zakończyło się wyświetleniem słowa SCAlenie, gdyż słownik niemiecko-polski jeszcze nie istniał (linia u dołu). Uruchomienie drugie zwróciło już słowo de_SCAlenie, co jest wystarczającą gwarancją na uruchomienie SCAlenia z usługą w Groovy.

Osobiste