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.

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;
}

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.
© The Useful Pot To Keep Things In
Maira Gall