2008-02-18

Spring, CXF i Eclipse

Popełniłem pierwszy w życiu web service. Jak można przeczytać w tytule są to komponenty Springowe udostępniane jako usługi sieciowe za pomocą CXF. Projekt stworzony w Eclipse kompiluje się i działa bez problemu, ale Eclipse'owemu edytorowi XMLa nie podoba się definicja kontekstu Spring:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd"%gt;

<jaxws:endpoint id="webServiceEndpoint"
address="/test"
implementorClass="moja.klasa.WebService">

... implementacja usługi ...
</jaxws:endpoint>

</beans>
W zaznaczonym miejscu sygnalizowany jest błąd:
The matching wildcard is strict, but no declaration can be found for element 'jaxws:endpoint'.
Skoro Eclipse nie może znaleźć deklaracji, to trzeba mu powiedzieć gdzie jej szukać. Z menu wybieramy Window / Preferences, potem Web and XML / XML Catalog, zaznaczmy User Specified Entries, klikamy przycisk Add... i wpisujemy następujące wartości:

Location: jar:file:/lokalna/ścieżka/modules/cxf-rt-frontend-jaxws-<wersja>.jar!/schemas/jaxws.xsd
Key Type: Schema Location
Key: http://cxf.apache.org/schemas/jaxws.xsd

Wybranie wartości Schema Location w polu Key Type jest możliwe dopiero po wpisaniu poprawnej wartości w pole Location.

Po wykonaniu tej operacji dotychczasowy błąd zniknie, ale pojawi się nowy:
Referenced file contains errors (jar:file:/ścieżka/do/bibliotek/cxf-<wersja>.jar).
Problemem jest teraz brak definicji, która jest importowana przez jaxws.xsd. W taki sam sposób jak poprzednio dodajemy drugi wpis:

Location: jar:file:/lokalna/ścieżka/modules/cxf-common-utilities-<wersja>.jar!/schemas/configuration/cxf-beans.xsd
Key Type: Schema Location
Key: http://cxf.apache.org/schemas/configuration/cxf-beans.xsd

i tym razem jest to już wystarczające.

Opisany sposób jest oczywiście uniwersalny - można tak dodać w Eclipse dowolne brakujące definicje.
Share:

2008-02-10

Migracja z EJB 2.1 Entity Beans do Hibernate

Niedawno stanąłem przed koniecznością przepisania warstwy dostępu do danych z Entity Beans na Hibernate. Baza danych nie mogła być zmieniana, więc nowe klasy mapowane przez Hibernate musiały mieć dokładnie te same atrybuty i powiązania co dotychczasowe Entity Beans oraz musiały radzić sobie z typami danych w istniejących tabelach. Wszystko szło gładko do momentu kiedy trafiłem na atrybut mapowany na kolumnę typu BLOB (poniżej notacja XDocletowa):

/**
* @ejb.persistence column-name="NAZWA" jdbc-type="BLOB"
*/
public abstract String getNazwa();

Atrybut jest typu String, więc spodziewałem się że w bazie będą zapisane po prostu ciągi bajtów uzyskane metodą getBytes(). Jednak po sprawdzeniu istniejących wartości okazało się, że są to zserializowane obiekty i to nie klasy String, ale org.jboss.invocation.MarshalledValue (aplikacja działa na JBossie). Konieczne było więc napisanie odpowiedniej implementacji Hibernate'owego interfejsu UserType. Pomocny okazał się - jakże by inaczej - Spring, dostarczając bazowej klasy AbstractLobType:

public class MarshalledStringType extends AbstractLobType {
private static final int[] TYPES = { Types.BLOB };

protected Object nullSafeGetInternal(
ResultSet rs, String[] names,
Object owner, LobHandler lobHandler)
throws SQLException, IOException, HibernateException {

if (names.length != 1) {
throw new HibernateException("invalid column names");
}
Blob blob = rs.getBlob(names[0]);
try {
ObjectInputStream ois = new ObjectInputStream(blob.getBinaryStream());
MarshalledValue mv = (MarshalledValue) ois.readObject();
return mv.get();
} catch (IOException e) {
throw new HibernateException(e);
} catch (ClassNotFoundException e) {
throw new HibernateException(e);
}
}

protected void nullSafeSetInternal(
PreparedStatement ps, int index, Object value, LobCreator lobCreator)
throws SQLException, IOException, HibernateException {

MarshalledValue mv = new MarshalledValue(value);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(mv);
oos.close();
baos.close();
lobCreator.setBlobAsBytes(ps, index, baos.toByteArray());
}

public Class returnedClass() {
return String.class;
}

public int[] sqlTypes() {
return TYPES;
}
}

Aby nowy typ był widoczny dla Hibernate wystarczy jedna adnotacja, najlepiej w pliku package-info.java w pakiecie nadrzędnym dla klas modelu danych:

@org.hibernate.annotations.TypeDefs({
@org.hibernate.annotations.TypeDef(
name = "marshalledString",
typeClass = MarshalledStringType.class
)
})
package pakiet.nadrzedny.modelu.danych;

Od tego momentu marshalledString może być używany tak samo jak wszystkie standardowe typy dostępne w Hibernate:

@Basic
@Type(type = "marshalledString")
@Column(name = "NAZWA")
public String getNazwa() {
return nazwa;
}
Share:

2008-02-08

Problem z adnotacją @Autowire w Springu 2.5.1

Bug ujawnia się dość specyficznej sytuacji: jeżeli używamy automatycznego rozwiązywania zależności (autowiring) opartego na adnotacjach, mamy komponent nie będący singletonem i mający przynajmniej jeden atrybut będący kolekcją. W poniższym przykładzie:
public class TestBean {
   private List<TestDep> deps;

   @Autowired
   public void setTestDeps(List<TestDep> deps) {
       this.deps = deps;
   }
}


public class TestDep {
}


<beans>
   <context:annotation-config/>

   <bean id="testBean" class="test.TestBean" scope="prototype"/>

   <bean id="testDep" class="test.TestDep"/>
</beans>


public class Test {
   public static void main(String[] args) {
       ClassPathXmlApplicationContext ctx = ...
       ctx.getBean("testBean");
       ctx.getBean("testBean");
   }
}
pierwsze wywołanie ctx.getBean("testBean") zadziała poprawnie, ale drugie (i każde następne) zgłosi wyjątek IllegalArgumentException: argument type mismatch. Problemy powoduje kod odpowiedzialny za cache'owanie referencji do komponentów w klasie wewnętrznej AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement. Wpisałem zgłoszenie do JIRY (numer SPR-4438) i po 39 minutach (!) błąd został poprawiony, a poprawka włączona do wersji 2.5.2. Rewelacja. [aktualizacja 2008.03.04] Wersja 2.5.2 została wydana wczoraj i AutowiredAnnotationBeanPostProcessor działa w niej poprawnie.
Share:

2008-01-17

Znacznik <script> w IE7

Straciłem ładnych parę godzin szukając przyczyny dlaczego aplikacja GWT jest niewidoczna w IE7. Działała bez problemu w Firefoksie i Safari, a w Internet Explorerze wyświetlana była pusta strona. Ponieważ to GWT, to najpierw sprawdziłem kod w Javie, potem wygenerowane JavaScripty - nic podejrzanego nie było widać. Ostatecznie okazało się że problem tkwi w stronie, na której aplikacja jest umieszczona. IE7 poniższy zapis traktuje jako błędny i ignoruje go:
<script language="javascript" src="gwt.js"/>
Wystarczy dodać znacznik zamykający:
<script language="javascript" src="gwt.js"></script>
i skrypt jest poprawnie wczytywany. Po tej historii sprawdziłem stronę walidatorem W3C i rzeczywiście:
The sequence <FOO /> can be interpreted in at least two different ways, depending on the DOCTYPE of the document. For HMTL 4.01 Strict, the '/' terminates the tag <FOO (with an implied '>'). However, since many browsers don't interpret it this way, even in the presence of an HMTL 4.01 Strict DOCTYPE, it is best to avoid it completely in pure HTML documents and reserve its use solely for those written in XHTML.
Share:

2008-01-10

Groovy i Hibernate w jednym stali domu

Postanowiłem w pewnej Bardzo Dużej Aplikacji Webowej podnieść wersję Groovy z 1.0 na 1.5. Bo w 1.5 są adnotacje, typy generyczne, typy wyliczeniowe oraz jakże mile widziane w naszej korporacji "significant performance gains".

Sama zmiana wersji jest banalna. Projekt jest budowany Mavenem, więc zmieniamy w pom.xml wersję z 1.0 na 1.5.1 i przy okazji identyfikator grupy z groovy na org.codehaus.groovy (reorganizacja w centralnym repozytorium). Potem mvn install i za chwilę mamy gotową aplikację. Aplikację, która nie chce się deployować.

Okazuje się, że Groovy 1.5, a konkretnie znajdująca się w jego zależnościach biblioteka ASM 2.2 wywołuje konflikt z cglib 2.1_3. Cglib jest wykorzystywana przez Hibernate, a w rzeczonej aplikacji Hibernate jest kluczową technologią. Po zmianie ASM na wersję 1.5.3 działającą z cglibem przestaje działać Groovy, więc nie tędy droga. Rozwiązaniem jest użycie wersji cglib-nodep, która nie wymaga ASMa (a dokładnie klasy ASMowe, przeniesione do innych pakietów, ma włączone do swojego kodu).

Pewnym problemem jest Maven - artefakty cglib 2.1.3, asm 1.5.3 i asm-attrs 1.5.3 są w zależnościach Hibernate. O ile zmianę wersji ASMa możemy wymusić za pomocą dependency management:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>asm</groupId>
<artifactId>asm-attrs</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
</dependencyManagement>

to zmiana cglib na cglib-nodep wymaga wykluczenia cglib z zależności Hibernate i dodania cglib-nodep jako nowej zależności:
<dependencies>
<dependency>
<groupid>org.hibernate</groupid>
<artifactid>hibernate</artifactid>
<exclusions>
<exclusion>
<groupid>cglib</groupid>
<artifactid>cglib</artifactid>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupid>cglib</groupid>
<artifactid>cglib-nodep</artifactid>
<version>2.1_3</version>
</dependency>
</dependencies>

Nie jest to rozwiązanie do końca eleganckie. Konieczna jest modyfikacja (i późniejsze pilnowanie) zależności Hibernate we wszystkich projektach wchodzących w skład aplikacji. Jednak w następnej wersji (3.2.6) problem ma być załatwiony - Hibernate będzie wykorzystywać cglib 2.2 i ASM 2.2 (zgłoszenie HHH-2875).

[aktualizacja 2008.03.03]

8. lutego została wydana wersja 3.2.6, jednak nie ma w niej zapowiadanej poprawki - została przesunięta na wersję 3.3, czyli bliżej nieokreśloną przyszłość.
Share: