30 marca 2008

SCJP - Typy wyliczeniowe

Typy wyliczeniowe, czyli streszczenie sekcji "Declaring Enums" – kontynuacja działań zapowiedzianych w artykule "Przygotowania do SCJP czas zacząć". Znowu przyjdzie mi jednak czytać specyfikację języka Java, jako że informacje w Książce są mocno nie kompletne.

Typy wyliczeniowe deklarujemy za pomocą słówka kluczowego enum bezpośrednio w pliku, w klasie, interfejsie albo wewnątrz innego typu wyliczeniowego, ale nie w metodzie. Typ wyliczeniowy, oprócz tego, że definiuje stałe wyliczeniowe może także zawierać konstruktory, zmienne i metody oraz klasy wewnętrzne i inne typy wyliczeniowe – prawie jak klasa, ale prawie robi ogromną różnicę.

Zacznijmy od stałych wyliczeniowych. Poniżej przykład deklaracji typu wyliczeniowego, który zawiera trzy takie stałe.

enum Size {
SMALL, LARGE, HUGE
}

Na tego typu deklaracjach kończy się wiedza wielu programistów, a to dopiero początek. Zacznijmy powoli, od modyfikatorów, jakich możemy użyć w deklaracji typu wyliczeniowego – dla deklaracji bezpośrednio w pliku są to tylko public oraz strictfp, a dla deklaracji wewnątrz innych typów dodatkowo private, protected i static. Ich znaczenie jest takie jak w przypadku klas i opisałem je w artykule "SCJP - Modyfikatory dla deklaracji klas". Idźmy dalej. Poniżej przykład deklaracji zawierającej stałe wyliczeniowe, metodę, zmienną i konstruktor. Zwróćmy uwagę na średnik po deklaracjach stałych wyliczeniowych – jest obowiązkowy, jeśli po tych deklaracjach znajduje się coś jeszcze. Zapamiętajmy też, że stałe wyliczeniowe muszą być zadeklarowane jako pierwsze, dopiero potem, po średniku następują kolejne deklaracje!

enum Size {
SMALL(1), LARGE(2), HUGE(3);

Size(int size) {
this.size = size;
}

public int getSize() {
return size;
}

private int size;
}

To, czym typy wyliczeniowe różnią się zdecydowanie od klas jest, że mogą one zawierać jedynie konstruktory prywatne. Nie jest możliwe utworzenie instancji typu wyliczeniowego w inny sposób, jak tylko poprzez deklarację stałej wyliczeniowej. Tak, stałe wyliczeniowe są takiego typu, w jakim je zadeklarowano i są to jedyne instancje tego typu. W powyższym przykładzie stałe Size.SMALL, Size.LARGE i Size.HUGE są zatem typu Size i są to jedyne instancje tego typu jakie kiedykolwiek będą występowały w przyrodzie, tj. w JVM – innych utworzyć się nie da, także używając refleksji czy operacji clone(). Oznacza to między innymi, że możemy używać operatora == zamiast operacji equals(). Mimo, że tak czy inaczej konstruktory typów wyliczeniowych są prywatne, możemy oznaczyć je słówkiem kluczowym private explicite. Jest to jedyny modyfikator, jakiego możemy użyć w deklaracji takiego konstruktora.

Zerknijmy teraz ponownie na deklarację stałych wyliczeniowych w powyższym przykładzie. Są to stałe i do dobrych praktyk należy, aby ich nazwy były pisane samymi wielkimi literami, ale nie jest to wymóg bezwzględny, może to być dowolny poprawny identyfikator – co to znaczy poprawny opisałem w artykule "SCJP - Poprawne identyfikatory i konwencje nazewnicze". Tuż po nazwie stałej możemy podać parametry dla wywołania konstruktora. Jeśli argumentów nie podano – tj. użyto samej nazwy, np. SMALL – to dla utworzenia instancji (stałej) wywoływany jest konstruktor bez-argumentowy. Tak jak w przypadku klas, możemy zdefiniować i użyć dowolny inny konstruktor.

Typy wyliczeniowe mogą implementować interfejsy, ale nie mogą dziedziczyć z innych typów wyliczeniowych. Istnieje jednak sposób na przedefiniowanie metod dla wybranych stałych wyliczeniowych, zupełnie jakby były one podtypami zawierającego typu wyliczeniowego. Po deklaracji stałej i jej ewentualnych parametrach dla konstruktora można umieścić ciało klasy anonimowej, która dla tej jednej instancji określi typ na bazie typu zawierającego. Spójrzmy na poniższy przykład.

enum Size {
SMALL {
public String getDescription() {
return "Taki calkiem maly";
}
},

LARGE {
public String getDescription() {
return "Dosyc duzy";
}
};

public abstract String getDescription();
}

Mimo, że nie możemy zadeklarować, że dany typ wyliczeniowy dziedziczy z innego typu, to jak najbardziej typy wyliczeniowe również należą do wspólnej hierarchii z klasą Object w korzeniu. Każdy typ wyliczeniowy E dziedziczy bowiem implicite z typu Enum<E>, a ten z typu Object. Oprócz metod odziedziczonych z Enum<E> każdy typ wyliczeniowy E ma zdefiniowane metody ‘public static E[] values()’ oraz ‘public static E valueOf(String name)’. Metoda values() zwraca tablicę stałych wyliczeniowych zdefiniowanych w typie E a metoda valueOf() służy do konwersji z typu String – zwraca instancję typu E (jedną ze stałych wyliczeniowych) o podanej nazwie.

Jeśli chodzi o deklaracje metod, to dozwolone są wszystkie te modyfikatory, co dla deklaracji w klasach, z jednym tylko obostrzeniem dla modyfikatora abstract – jeśli w typie wyliczeniowym zadeklarowano metodę abstrakcyjną, to musi być ona zdefiniowana przez każdą ze stałych wyliczeniowych w tym typie. Musi przy tym być zadeklarowana co najmniej jedna taka stała. Zmienne obowiązują te same zasady, co zmienne w klasach.

Było ciężko, ale się udało – zakończyliśmy niniejszym lekturę pierwszego rozdziału referowanej książki "SCJP Sun Certified Programmer for Java 5 Study Guide (Exam 310-055)", a to już mały sukces.

25 marca 2008

Adnotacje kontra XML

Byłem jakiś czas temu uczestnikiem dyskusji, a może nawet sporu, na temat tytułowy, czyli adnotacje czy XML? Koronnym argumentem tych, którzy optują za XML’em jest, że można dokonywać zmian bez rekompilacji kodu, ale ja powiadam, co z tego? Nie jest to argument sam w sobie, trzeba się jeszcze zastanowić właśnie nad tym - co z tego, że można? Co z tego, że w przypadku użycia adnotacji nie mogę dokonywać zmian bez rekompilacji kodu. Czy rzeczywiście jest to argument i czy zawsze? Nie twierdzę, że adnotacje są zawsze „lepsze”, ale często są łatwiejsze w użyciu, podczas gdy nie ma powodów by ich nie używać. Jak na wpis, którego celem jest polecenie lektury innego wpisu robi się już dużo tekstu, więc przechodzę do sedna - polecam lekturę artykułu "Annotations vs. XML" i czekam na komentarze.

22 marca 2008

SCJP - Deklaracja zmiennych i stałych

Deklaracja zmiennych i stałych, czyli streszczenie sekcji "Variable Declarations" – kontynuacja działań zapowiedzianych w artykule "Przygotowania do SCJP czas zacząć".

Java definiuje dwa rodzaje zmiennych: zmienne typu prostego (ang. primitives) i zmienne będące referencjami (ang. reference variables). Jest 8 typów prostych, są to: char, boolean, byte, short, int, long, double oraz float. Referencje są de facto wskaźnikami na obiekty, ale nie na wszystkie obiekty a tylko na te zgodne z typem referencji. Jeśli więc zadeklarujemy referencję na obiekty klasy A, to może ona wskazywać tylko na obiekty klasy A oraz na obiekty klas pochodnych. W języku Java – w przeciwieństwie do np. C++ – wszystkie klasy uczestniczą jednak we wspólnej hierarchii dziedziczenia z klasą Object w korzeniu, tak więc możemy zadeklarować referencję do obiektów klasy Object, która będzie mogła wskazywać na obiekt dowolnego typu.

Zmienna typu boolean może przyjmować jedną z dwu wartości: ‘true’ albo ‘false’. Już to pisałem, ale przypomnę, że Java jest językiem tzw. case-sensitive, a więc ‘true’ to co innego niż ‘TRUE’ i co innego niż ‘True’ a wartością zmiennej typu boolean może być tylko ‘true’ albo ‘false’. Typ znakowy char reprezentowany jest na dwu bajtach i odpowiada jednemu znakowi UTF-16. Zmienne typów numerycznych zakodowane są bitowo w tak zwanej notacji uzupełniającej do dwu z najbardziej znaczącym bitem oznaczającym znak liczby (0 to plus a 1 to minus). Poniższa tabelka pokazuje liczbę bajtów użytą do reprezentacji poszczególnych typów.

byte

short

int

long

float

double

1

2

4

8

4

8


Zmienne można deklarować w czterech różnych kontekstach. Zmienne (nie statyczne) zadeklarowane w klasie to zmienne instancyjne. Jeśli do zmiennej zadeklarowanej w klasie dodamy modyfikator static to staje się ona zmienną klasową. W ciele metody możemy zadeklarować zmienną lokalną. Ostatnim sposobem na deklarowanie zmiennych są parametry metod. W zależności od tego, z którą z powyższych deklaracji mamy do czynienia możliwe jest zastosowanie innego zestawu modyfikatorów. Przypomnę jeszcze, że możemy zadeklarować (i zainicjalizować) kilka zmiennych w jednej deklaracji, poprawne są więc deklaracje tej postaci:


int a = 0, b, c = 2;

Integer i = 5, j;


Przyjrzyjmy się teraz bliżej zmiennym instancyjnym. Są to te zmienne, które zostały zadeklarowane bezpośrednio w klasie i nie zostały oznaczone jako static. Zmienne instancyjne są inicjalizowane na domyślne wartości w chwili tworzenia instancji (a zmienne klasowe w momencie wczytywania klasy), tak więc nie muszą być inicjalizowane explicite. Zmienne te mogą mieć każdy z czterech zakresów widoczności, a więc dozwolony jest każdy z modyfikatorów: public, protected i private. Modyfikator widoczności może być również pominięty i wtedy obowiązywał będzie zakres domyślny. Zakresy widoczności działają analogicznie jak dla metod, co opisałem w artykule "SCJP - Modyfikatory widoczności w deklaracji metod i zmiennych". Ze zmiennej takiej możemy zrobić stałą dodając modyfikator final. Możemy też użyć słówka kluczowego transient oraz volatile, o których za chwilkę. Legalne jest także użycie względem zmiennej zadeklarowanej w klasie słówka kluczowego static, ale oznacza ono właśnie tyle, że nie jest to zmienna instancyjna tylko klasowa. Zmienne klasowe mogą być oznaczone tymi samymi modyfikatorami, co zmienne instancyjne.

Modyfikator transient jest znaczący w kontekście serializacji. Zmienne instancyjne oznaczone jako transient są ignorowane, tj. nie są serializowane i ich stan nie jest odtwarzany przy deserializacji. Modyfikator volatile jest istotny w kontekście programów współbieżnych a jego znaczenie wymaga dobrego zrozumienia zagadnień współbieżności - zadanie to odkładamy na później. Póki co zapamiętajmy tylko, że modyfikator volatile nie może być użyty w parze z modyfikatorem final, innymi słowy - stała nie może być volatile.

Zmienne lokalne, to zmienne zadeklarowane w ciele metod. W przeciwieństwie do zmiennych instancyjnych nie są one inicjalizowane na domyślne wartości, tak więc to programista ma obowiązek przypisać jakąś wartość zanim zmienna zostanie użyta. Zmienne lokalne mogą być oznaczone jedynie modyfikatorem final, co ma ten skutek, że wartość takiej zmiennej nie będzie mogła być zmieniona – będzie to stała. Zmienne będące parametrami metody również mogą być oznaczone tylko jako final. Ze zmiennymi lokalnymi wiąże się jeszcze jedno zagadnienie - przysłanianie zmiennych. Jeśli parametr metody, albo zmienna lokalna ma tą samą nazwę (typ nie ma znaczenia), co zmienna zadeklarowana w klasie, to zmienna zadeklarowana w klasie jest przysłonięta, tj. nazwa odnosi się do zmiennej lokalnej lub parametru metody, a odwołanie się do zmiennej zadeklarowanej w klasie wymaga użycia operatora ‘this’. Poniżej przykład ilustrujący to zagadnienie – efektem uruchomienia metody jest wypisanie cyfry 5 a następnie napisu "Ola ma kota".

public class TestClass {
String var = "Ola ma kota";

void test() {
int var = 5;

System.out.println(var);
System.out.println(this.var);
}
}

Pewnym szczególnym przypadkiem zmiennych są zmienne typu tablicowego (ang. array). Tablice mogą być zarówno jedno jak i wielo wymiarowe. Deklarując tablicę, która będzie zawierała elementy danego typu dodajemy po nazwie typu albo po nazwie zmiennej nawiasy kwadratowe ‘[]’, przy czym zdecydowanie zaleca się umieszczać je po nazwie typu. Poniżej kilka przykładów poprawnych deklaracji tablic, przy czym pierwsze dwie deklaracje są zgodne z zaleceniami a kolejne, mimo że poprawne stanowią przykład deklaracji nie eleganckich.

String[] names;

boolean[] [] allowances;

int codes[];

String[] names[];

Dla tych, którzy zanim zaczęli swą przygodę z Javą, programowali w takich językach jak C czy C++ powiem jeszcze wprost, że w Javie nie jest poprawnym określenie rozmiaru tablicy w deklaracji, nie jest np. poprawną deklaracja ‘int[2] codes’. Deklaracja jest tylko deklaracją - pamięć na tablicę jest alokowana dopiero wówczas, gdy tworzony jest obiekt tablicy i to wówczas istotny jest jej rozmiar.

18 marca 2008

JAX-WS i Maven 2 w podejściu Contract First Development

Serwisy sieciowe, WebService’y, po przejściu fali marketingu i wyczerpaniu się na slajdach prezentacji odnajdują wreszcie swe nisze i zaczynają zyskiwać znaczenie praktyczne. Jedno jest pewne - o WebService’ach już nie tylko się mówi, coraz częściej przychodzi je zaimplementować. I pojawia się pytanie - jak to robić, żeby było – nie powiem, że łatwo jak na prezentacjach – ale chociaż znośnie. Próbowałem już wielu możliwości i śmiało mogę stwierdzić, że nie ma to jak JAX-WS (zwłaszcza 2.1) w parze z Maven 2. WebService’y z użyciem JAX-WS można implementować na dwa sposoby: zaczynając od klas Javy z odpowiednimi adnotacjami i na ich podstawie generując WSDL, albo na odwrót, zaczynając od WSDL’a i generując klasy. WSDL jest kontraktem, definicją interfejsu serwisu a podejście, w którym zaczynamy od wygenerowania klas na podstawie otrzymanego lub uprzednio napisanego WSDL’a nazywa się stąd, z angielskiego, Contract First Development. Tym właśnie stylem tworzenia serwisów sieciowych zajmę się dzisiaj.

Zakładamy więc, że mamy definicję serwisu w postaci pliku WSDL i naszym zadaniem jest implementacja takiego serwisu. Nasz WSDL (plik math.wsdl), definiujący operację dodawania wygląda następująco:

<definitions targetNamespace="http://jaxws.centric.pl/math"
xmlns:tns="http://jaxws.centric.pl/math"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:math="http://jaxws.centric.pl/math/domain">

<types>
<xsd:schema>
<xsd:import namespace="http://jaxws.centric.pl/math/domain"
schemaLocation="math.xsd"/>
</xsd:schema>
</types>

<message name="addRequestMsg">
<part name="addRequest" element="math:addRequest"/>
</message>

<message name="addResponseMsg">
<part name="addResponse" element="math:addResponse"/>
</message>

<portType name="mathService">
<operation name="add">
<input message="tns:addRequestMsg"/>
<output message="tns:addResponseMsg"/>
</operation>
</portType>

<binding name="soapBinding" type="tns:mathService">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"
style="document"/>
<operation name="add">
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>

<service name="mathServiceProxy">
<port name="mathService" binding="tns:soapBinding">
<soap:address location="http://localhost:8080/mathService"/>
</port>
</service>
</definitions>

Fakt, plik jest dosyć długi, ale tak to już być musi, a doskonałą przecież wymówką jest, że język WSDL nie był tworzony z myślą o tym by być czytelnym dla ludzi. Co więcej, kto zerknął na powyższego WSDL’a zauważył, że brakuje w nim definicji typów. Owszem, zostały one zdefiniowane w osobnym pliku XML Schema a w WSDL’u tylko ten plik wskazujemy. Oto więc brakująca definicja typów (plik math.xsd):

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://jaxws.centric.pl/math/domain"
xmlns:tns="http://jaxws.centric.pl/math/domain"
elementFormDefault="qualified">

<xsd:element name="addRequest" type="tns:addRequestType"/>

<xsd:complexType name="addRequestType">
<xsd:sequence>
<xsd:element name="a" type="xsd:double" />
<xsd:element name="b" type="xsd:double" />
</xsd:sequence>
</xsd:complexType>

<xsd:element name="addResponse" type="tns:addResponseType"/>

<xsd:complexType name="addResponseType">
<xsd:sequence>
<xsd:element name="c" type="xsd:double" />
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

No dobrze, mamy już kontrakt, zatem przystępujemy do generowania klas. W tym celu utworzymy projekt Maven 2. Nie chcę zbyt dużo czasu poświęcić na podstawy Maven’a więc powiem krótko - tworzymy projekt główny, tj. typu pom o nazwie math i konfigurujemy go w następujący sposób (plik pom.xml):

<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.centric.jaxws</groupId>
<artifactId>math</artifactId>
<packaging>pom</packaging>
<version>1.1</version>
<name>math</name>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>RELEASE</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>RELEASE</version>
<configuration>
<encoding>US-ASCII</encoding>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>RELEASE</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
<modules>
<module>math-schema</module>
<module>math-ws</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<artifactId>math-schema</artifactId>
<groupId>pl.centric.jaxws</groupId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<pluginRepositories>
<pluginRepository>
<id>maven2-repository.dev.java.net</id>
<url>http://download.java.net/maven/2/</url>
</pluginRepository>
</pluginRepositories>
<repositories>
<repository>
<id>maven-repository.dev.java.net</id>
<url>http://download.java.net/maven/1/</url>
<layout>legacy</layout>
</repository>
<repository>
<id>maven2-repository.dev.java.net</id>
<url>http://download.java.net/maven/2/</url>
</repository>
</repositories>
</project>

Teraz tworzymy projekt potomny typu jar o nazwie math-schema. Jedynym zadaniem tego podprojektu będzie generowanie na podstawie WSDL’a i XML Schema’y odpowiednich klas, kompilowanie ich i pakowanie do postaci pliku .jar. Plik pom.xml tego projektu poniżej:

<project>
<parent>
<artifactId>math</artifactId>
<groupId>pl.centric.jaxws</groupId>
<version>1.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>pl.centric.jaxws</groupId>
<artifactId>math-schema</artifactId>
<name>math-schema</name>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>wsimport</goal>
</goals>
</execution>
</executions>
<configuration>
<xdebug>true</xdebug>
<verbose>true</verbose>
<target>2.0</target>
</configuration>
<dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-tools</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.1.3</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

Kluczowe jest tutaj wykorzystanie wtyczki (ang. plugin) jaxws-maven-plugin - https://jax-ws-commons.dev.java.net/jaxws-maven-plugin/, to ona generuje klasy i w jej dokumentacji powinniśmy szukać informacji, gdy chcemy zmodyfikować standardowe ustawienia. W katalogu src projektu math-schema tworzymy podkatalog wsdl i kopiujemy tam pliki WSDL i XML Schema, czyli w naszym przypadku math.wsdl i math.xsd. Jest to domyślna lokalizacja, w której wtyczka jaxws-maven-plugin poszukuje definicji serwisów, dla których ma wygenerować klasy.

Utwórzmy teraz podprojekt typu war o nazwie math-ws. Będzie to projekt, w którym zaimplementujemy nasz serwis. Oto plik pom.xml dla tego projektu:

<project>
<parent>
<artifactId>math</artifactId>
<groupId>pl.centric.jaxws</groupId>
<version>1.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>pl.centric.jaxws</groupId>
<artifactId>math-ws</artifactId>
<name>math-ws</name>
<packaging>war</packaging>
<dependencies>
<dependency>
<artifactId>math-schema</artifactId>
<groupId>pl.centric.jaxws</groupId>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.1.3</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

Ponieważ mamy do czynienia bądź, co bądź z aplikacją internetową (ang. web application) nie obędzie się bez pliku web.xml, oto on:

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

<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" />

I wreszcie implementacja serwisu, tj. interfejsu wygenerowanego przez narzędzie jaxws-maven-plugin. Oto ona:

package pl.centric.jaxws.math.service;

import javax.jws.WebService;

import pl.centric.jaxws.math.MathService;
import pl.centric.jaxws.math.domain.AddRequestType;
import pl.centric.jaxws.math.domain.AddResponseType;

@WebService(endpointInterface="pl.centric.jaxws.math.MathService")
public class MathServiceBean implements MathService {

public AddResponseType add(AddRequestType addRequestType) {
AddResponseType addResponseType = new AddResponseType();

addResponseType.setC(addRequestType.getA() + addRequestType.getB());

return addResponseType;
}
}

Budujemy projekt i przystępujemy do testów. Zaczynamy od serwera GlassFish V2. Do testów używam narzędzia soapUI - http://www.soapui.org/. Prosty test pokazuje, że działa.




Ale na tym nie koniec, sprawdzamy jeszcze czy zadziała na innych serwerach. W końcu się przekonałem i wziąłem do ręki Apache Geronimo - http://geronimo.apache.org/ - w wersji 2.1. Działa bez zarzutu a serwer robi dobre wrażenie, zwłaszcza szybkość działania konsoli. Co prawda dostajemy ostrzeżenie, że nie znaleziono pliku geronimo-web.xml i może to skutkować błędami, ale nic w tym dziwnego jako że rzeczywiście takiego pliku nie ma. Błędów też nie ma. Na koniec sprawdzam jeszcze na serwerze BEA WebLogic Server 10.2. Działa i tu, co nie koniecznie musiało mieć miejsce. O ile moje doświadczenia z GlassFish’em są takie, że łyknie wszystko, co tylko można to z WLS’em jest już zupełnie na odwrót – wszystko musi być idealnie. Jak mamy WLS’a to mamy też konsolę do testowania WebService’ów, zamiast soapUI używam więc tejże konsoli. Wygląda to tak jak poniżej.