Pokazywanie postów oznaczonych etykietą java. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą java. Pokaż wszystkie posty

2022-06-11

Upgrade openHABa

Niedawno postanowiłem zrobić upgrade domowego openHABa z wersji 3.1.0 na 3.2.0. Moja instalacja działa na Raspberry Pi w kontenerze Dockerowym, a ponieważ jako katalogi z konfiguracją, pluginami i danymi mam montowane katalogi hosta, to zgodnie z oficjalną instrukcją aktualizacja powinna być banalna. Trzeba zatrzymać i usunąć kontener z aktualną wersją (wszystkie istotne dane zostają na hoście) a następnie ściągnąć i uruchomić nową. Dla porządku po usunięciu kontenera skasowałem logi z userdata/logs. Obraz wersji 3.2.0-debian ściągnął się bez problemów, zaczął uruchamiać i zawiesił się. Kontener co prawda był widoczny:
$ docker ps
CONTAINER ID   IMAGE                        ... CREATED         STATUS
e2906859d033   openhab/openhab:3.2.0-debian ... 2 minutes ago   Up 2 minutes (health: starting)
w logu userdata/update.log zapisało się:
...
Replacing userdata system files with newer versions...
Clearing cache...

Performing post-update tasks for version 3.2.0:

SUCCESS: openHAB updated from 3.1.0 to 3.2.0
ale już w logu userdata/logs/openhab.log było pusto, a po kilkunastu minutach Docker jednak uznał kontener za niezdrów:
$ docker ps
CONTAINER ID   IMAGE                        ... CREATED          STATUS
e2906859d033   openhab/openhab:3.2.0-debian ... 19 minutes ago   Up 19 minutes (unhealthy)
Komenda docker stop co prawda zadziałała, ale zapisała do syslogu mało zachęcające:
Container failed to exit within 10s of signal 15 - using the force
Przyznam się, że na tym etapie zatrzymałem się na dłuższą chwilę - nie mając nic w logach ciężko było znaleźć jakiś punkt zaczepienia. W końcu uruchomiłem kontener nie jako usługę, ale po prostu w terminalu za pomocą docker run ... dodatkowo usuwając z opcji --detach aby widzieć co dokładnie się dzieje. I stała się światłość - w ostanie linii logów, po wypisaniu której proces się zawieszał, było:
OpenJDK Client VM warning: No monotonic clock was available - timed services may be 
adversely affected if the time-of-day clock changes
na co Google od razu znalazł radę na forum openHABa i na Stack Overflow. Okazało się, że winna była zbyt stara biblioteka libseccomp2 zawarta w Debianie Buster (chodzi o hosta, nie kontener). Oczywistym rozwiązaniem byłby upgrade całego systemu do Bullseye, ale trochę obawiałem się tak radykalnej operacji, szczególnie że na oficjalnej stronie Raspberry Pi jest wprost napisane "we don’t support or recommend this". Dlatego zdecydowałem się na użycie repozytorium buster-backports, w którym jest już zaktualizowana wersja libseccomp2. Aby dodać repozytorium do systemu trzeba wykonać standardową procedurę:
$ sudo apt-key adv --keyserver keyserver.ubuntu.com \
 --recv-keys 04EE7237B7D453EC 648ACFD622F3D138
$ echo 'deb http://httpredir.debian.org/debian buster-backports main contrib non-free' \
 | sudo tee -a /etc/apt/sources.list.d/debian-backports.list
a następnie, po aktualizacji listy pakietów za pomocą:
$ sudo apt update
można zainstalować nową wersję biblioteki wskazując źródło pakietu za pomocą opcji -t:
$ sudo apt install libseccomp2 -t buster-backports
Po tej operacji ponowne uruchomienie kontenera openHAB kończy się spodziewanym:
[INFO ] [org.openhab.ui.internal.UIService   ] - Started UI on port 8080
Czekam z niecierpliwością na niespodzianki w zapowiadanej na 27. czerwca wersji 3.3.0 😅

P.S.
 
O co właściwie chodzi z owym monotonicznym zegarem? Okazuje się, że zarówno openHAB jak i Java są niewinne. W Debianie i wywodzących się z niego systemach, dla architektury arm32v7 (Raspberry Pi) kombinacja Dockera starszego niż 19.03.9 i biblioteki libseccom starszej niż 2.4.2 powoduje, iż w kontenerze nie jest dostępny poprawnie działający zegar. Wpływa to na cały system, w tym nawet komendę date (przykład ze zgłoszenia w projekcie Ubuntu):
$ docker run --rm -it --entrypoint /bin/bash ubuntu:20.04
root@8543aa82ff05:/# date
Thu Feb 26 08:20:11 UTC 1970

P.P.S.

Pierwszy wpis od 2756 dni. Z jednej strony fajnie, ale jednak trochę strasznie.
Share:

2014-02-09

Lista zmodyfikowanych atrybutów w Envers

Envers to część Hibernate ORM pozwalająca zapamiętywać historię zmian utrwalanych obiektów. Wystarczy dodać do klas encji kilka adnotacji, aby po każdym zapisie do głównej tabeli tworzony był wpis w tabeli historycznej pozwalający prześledzić co i kiedy zostało zmienione. Od wersji 4.1 Envers pozwala na rejestrowanie nie tylko wartości atrybutów, ale również znaczników określających czy w danej rewizji atrybut zmienił się w stosunku do rewizji poprzedniej (bez tego trzeba by było pracowicie porównywać kolejne wpisy). Niestety nie ma możliwości wygodnego odczytania tych informacji i choć dotyczące tego zgłoszenie HHH-8058 jest w JIRA od prawie roku, to nikt nie zabrał się jego realizację. Co ciekawe Envers zwraca stosowne dane, ale tylko “na potrzeby wewnętrzne” - w publicznym API nie są one już dostępne.

Dodanie stosownej funkcji okazało się bardzo proste - wymagało jedynie zmiany w klasie AuditQueryCreator tworzącej zapytania i oczywiście implementacji samego zapytania (w dużej mierze w oparciu o istniejący kod). Poprawki dla Hibernate w wersji 4.2.x i 4.3.x dostępne są w moim forku projektu hibernate-orm w branchach 4.2.HHH-8058 i 4.3.HHH-8058 w GitHubie.

Ponieważ jednak potrzebowałem działającego rozwiązania na już, a czekanie na oficjalne zaakceptowanie (lub odrzucenie :-) moich commitów może potrwać kolejny rok, popełniłem rozszerzenie Enversa, które w ogóle nie ingeruje w oryginalny kod.

<dygresja>Przy okazji odkryłem bardzo sprytną aplikację do umieszczania na stronach WWW kodów źródłowych z Githuba - efekty poniżej.</dygresja>

Nowa fabryka AuditReaderów działa dokładnie tak jak oryginalna – jedyna różnica to typ obiektu zwracanego przez metodę get:


Tak samo interfejs ExtAuditReader różni się od oryginału tylko typem zwracanym przez metodę createQuery:


Implementacja ExtAuditReadera to formalność: zmieniamy zwracany typ i zachowujemy referencję do konfiguracji (jest w oryginalnej klasie, ale prywatna):


Powoli dochodzimy do sedna. Rozszerzenie AuditQueryCreatora dodaje nową metodę forRevisionsOfEntityAndChanges(Class c, boolean selectDeletedEntities), która tworzy zapytanie zwracające nazwy zmienionych atrybutów. Jest podobna do forRevisionsOfEntity, jednak nie ma parametru selectEntitiesOnly – nie ma sensu zwracanie samych obiektów, skoro interesują nas i obiekty i lista zmian. Tak jak w ExtAuditReaderImpl musimy sami zapamiętać konfigurację i readera - są prywatne w klasie bazowej:


I w końcu właściwa implementacja zapytania. Dziedziczymy ze standardowego zapytania RevisionsOfEntityQuery, ale zmieniamy metodę list obrabiającą wyniki zapytania. Dodajemy również metodę, która wyszuka wszystkie atrybuty mające znacznik modyfikacji ustawiony na true. Na koniec dodajemy kopię metody getRevisionNumber – jest prywatna w klasie bazowej a potrzebujemy jej w list:


Składając wszystko razem możemy zapytać Enversa o nazwy zmienionych atrybutów w następujący sposób:


Kompletny projekt zawierający powyższe klasy, gotowy do zbudowanie Mavenem można ściągnąć z GitHuba, w wersjach dla Hibernate 4.2.8.Final i 4.3.1.Final.
Share:

2014-01-02

Autoreklama

A co. Mój blog to mi wolno.

Kilka dni temu Apple był uprzejmy zaakceptować moją pierwszą sensowną aplikację działającą na iOS. Nie jest to moja pierwsza aplikacja w ogóle, ale poprzednia była średnio sensowna z racji trudnej współpracy z zamawiającym i, niech jej ziemia lekką będzie, już nie jest dostępna w App Store.

Aplikacja numer dwa służy do monitorowania maszyny wirtualnej Java za pomocą doskonałej biblioteki Jolokia. Jak na razie nie potrafi zbyt wiele (tzn. wersja iOSowa, nie sama biblioteka) - wyświetla tylko ilość zajętej pamięci i obciążenie procesora:


 ale mam ambitne plany aby dodać więcej informacji.

App Store
Share:

2011-05-17

Jeszcze raz Groovy i Spring

Jakiś czas temu miałem problem z dynamiczną rekompilacją komponentów Spring napisanych w Groovy. Wtedy przyczyną był błąd w samym Groovy, szczegóły opisałem tutaj. Po aktualizacji Groovy wszystko było ok, ale niedawno stwierdziłem że mechanizm dynamicznego przeładowywania znowu przestał działać. Ponieważ w międzyczasie wykonałem spory refactoring aplikacji, to nie bardzo wiedziałem gdzie zacząć szukać sprawcy. Uznałem, że tym razem to nie Groovy - skoro raz zrobili błąd i go poprawili, to szansa na to że znowu się pojawił jest niewielka. Po włączeniu logów na poziomie DEBUG stwierdziłem, że Spring też jest niewinny - gdy plik źródłowy Groovy był zmieniany, w logach pojawiał się spodziewany komunikat: Target refreshed successfully ... czyli skrypt był ładowany od nowa. Pomimo tego w aplikacji dalej używana była pierwotna wersja. Nie będę zanudzał Czytelnika szczegółowym opisem trudności i niebezpieczeństw, którym musiałem stawić czoła aby odkryć istotę problemu i przejdę od razu do clou tego posta. Tym razem bruździł Tomcat, a dokładnie jego ClassLoader.
Jeżeli skrypt zostanie umieszczony w aplikacji webowej w katalogu WEB-INF/classes i będziemy się do niego odwoływać w Springu tak:
<lang:defaults refresh-check-delay="1000"/>
<lang:groovy id="myBean" source="classpath:Bean.groovy"/>
... to po pierwszym wczytaniu Tomcat umieści plik Bean.groovy w cache i do restartu go nie ruszy. Co ciekawe Spring potrafi zauważyć zmianę daty ostatniej modyfikacji pliku i próbuje go ponownie wczytać, ale ClassLoader cały czas zwraca pierwotnie wczytaną wersję. Wystarczy przenieść plik do WEB-INF i zmienić wpis w konfiguracji na:
<lang:defaults refresh-check-delay="1000"/>
<lang:groovy id="myBean" source="/WEB-INF/Bean.groovy"/>
... by wszystko wróciło do normy. Spring wczytuje wtedy plik za pośrednictwem ServletContextu a nie ClassLoadera i dostaje zaktualizowaną wersję. Howgh.
Share:

2010-12-06

Użycie widoków w Hibernate

Spieszę donieść, iż błąd uniemożliwiający użycie @SecondaryTable w klasie bazowej, na który utyskiwałem w tym wpisie został w końcu poprawiony (po czterech latach od zgłoszenia!). Uaktualniłem projekt i rzeczywiście Hibernate w wersji 3.5.4 poprawnie obsługuje mapowanie bazowej encji na wiele tabel. Trafiłem jednak na kolejny problem i - w przeciwieństwie do opisywanego wcześniej - nie bardzo widzę sposób na obejście go.

Mając tabele i widok:

CREATE TABLE `TB_SOME_TABLE` (
`ID_PARENT` INT(11) NOT NULL AUTO_INCREMENT,
`READ_WRITE` TEXT,
PRIMARY KEY (`ID_PARENT`)
)

CREATE TABLE `TB_OTHER_TABLE` (
`PARENT_ID` INT(11) NOT NULL,
`SOME_DATA` TEXT
)

CREATE VIEW `VW_SOME_VIEW` AS
SELECT
`T`.`PARENT_ID` AS `PARENT_ID`,
COUNT(`T`.`PARENT_ID`) AS `READ_ONLY`
FROM
`TB_OTHER_TABLE` `T`
GROUP BY
`T`.`PARENT_ID`

i klasę:

@Entity
@Table(name = "TB_SOME_TABLE")
@SecondaryTable(
name = "VW_SOME_VIEW"
pkJoinColumns = @PrimaryKeyJoinColumn(name = "PARENT_ID")
)
public class Parent {
@Id
@Column(name = "ID_PARENT")
private Long id;

@Basic
@Column(name = "READ_WRITE, table = "TB_SOME_TABLE")
private String readWriteAtribute;

@Basic
@Column(
name = "READ_ONLY", table = "VW_SOME_VIEW",
insertable = false, updatable = false
)
private String readOnlyAttribute;

...
}

nie jesteśmy w stanie zapisać do bazy obiektu tej klasy. Pomimo, że jedyny atrybut zamapowany na widok jest tylko do odczytu (insertable i updatable równe false), to Hibernate próbuje wstawić dane do widoku, co kończy się wyjątkiem:
Unhandled exception 'JDBC exception on Hibernate data access:
SQLException for SQL [insert into VW_SOME_VIEW (PARENT_ID) values (?)];
SQL state [HY000]; error code [1471]; could not insert: [Parent]

Widać że się stara (bo w wygenerowanym SQLu nie ma kolumny READ_ONLY), ale do końca mu nie wychodzi (bo jednak próbuje wstawić do widoku wiersz nie zawierający żadnych danych). I zupełnie nie mam pomysłu co na to poradzić.
Share:

2010-08-26

Groovy 1.7.0 i Spring

W jednej z aplikacji które popełniłem, używam skryptów Groovy jak kontrolerów Spring MVC. Jest to wygodne w sytuacji, kiedy kod kontrolera często się zmienia a nie ma możliwości restartu całej aplikacji. Dzięki poniższej definicji:

komponent some.Controller będzie automatycznie przeładowywany (czyli skrypt będzie ponownie kompilowany) jeżeli plik źródłowy Script.groovy się zmieni (ale nie częściej niż podany czas - tutaj 1000 ms). Wszystko działało tak jak powinno do momentu upgrade'u Springa do wersji 3.0.0.RELEASE - wtedy aplikacja przestała zauważać zmiany w skryptach. Uznałem, że chwilowo mogę bez tego żyć a w wersji 3.0.1 pewnie poprawią. Kiedy jednak po kolejnych uaktualnieniach doszedłem do wersji 3.0.3 i dalej nie było ani trochę lepiej, zacząłem intensywniej szukać. Okazało się, że Spring jest niewinny a problemy sprawia sam Groovy, którego wersję podniosłem do 1.7.0 na fali ogólnego uaktualniania aplikacji (w zależnościach Springa jest 1.6.3). Aby dynamiczne przeładowywanie zaczęło znowu działać trzeba albo cofnąć się do wersji 1.6.x, albo użyć najnowszego Groovy (w tej chwili 1.7.4). Dla zainteresowanych zgłoszenie w JIR-ze GROOVY-3981.
Share:

2010-08-08

Google App Engine

Google App Engine (for Java) umożliwia uruchamianie aplikacji webowych na serwerach Google. Używając jednego konta można zarejestrować do 10 aplikacji, każda z nich może wykorzystywać do 1GB przestrzeni dyskowej, po 1GB transferu wychodzącego i przychodzącego oraz 6.5 godziny znormalizowanego 1) czasu procesora na dobę. I to wszystko za darmo. Całkiem nieźle, prawda? Jest jednak jedno ale: Google'owa maszyna wirtualna Javy i serwer aplikacji mają trochę ograniczeń, na które można trafić próbując uruchomić coś więcej niż "Hello World".

Nie wnikając w zbędne szczegóły: moja aplikacja miała rejestrować obiekty w bazie danych (za pośrednictwem JPA) oraz udostępniać je w postaci zserializowanej do XMLa. Trafiłem na kilka mniej lub bardziej poważnych problemów.


[1] Klasa utrwalana za pomocą JPA nie może być powiązana z kilkoma obiektami tego samego typu. W poniższej sytuacji:
@Entity
class A {
}

@Entity
class B {
@ManyToOne
A pole1;
@ManyToOne
A pole2;
}
dostaniemy wyjątek:
Error in meta-data for B.pole2: Class B has multiple relationship fields of type A: pole1
and pole2. This is not yet supported.


[2] Nie jest obsługiwana strategia dziedziczenia SINGLE_TABLE. W przykładzie:
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
class A {
}

@Entity
class B extends A {
}
dostaniemy wyjątek:
Found inheritance strategy 'SINGLE_TABLE' on B.  This strategy is not supported in this
context.

[3] Google App Engine dopuszcza jako identyfikatory obiektów typy Long, String oraz własny typ com.google.appengine.api.datastore.Key. Jednak gdy obiekt ma być powiązany z innym:
@Entity
class A {
@Id
Long id;
}

@Entity class B {
@ManyToOne
A a;
}
jego klucz nie może być typu Long. W powyższym przykładzie dostaniemy wyjątek:
Cannot have a java.lang.Long primary key and be a child object


[4] Nie jest obsługiwane zachłanne wczytywanie powiązanych obiektów. Przy mapowaniu:
@Entity
class A {
}

@Entity
class B {
@ManyToOne(fetch = FetchType.EAGER)
A a;
}
serwer zapisze do logu ostrzeżenie:
WARNING: Meta-data warning for B.a: The datastore does not support joins and therefore
cannot honor requests to eagerly load related objects.
The field will be fetched lazily on first access.
i zgodnie z jego treścią, obiekt powiązany trzeba będzie jawnie doczytać.


[5] Niedostępna jest klasa sun.reflect.ReflectionFactory, która wbrew nazwie nie jest specyficzna dla Sunowskiej implementacji JVM (jest dostępna także w maszynach wirtualnych IBMa i Apple). Co prawda nie próbowałem jej używać bezpośrednio, ale pośrednie owszem. Próba wykorzystania biblioteki XStream do serializacji obiektów do XMLa kończy się wyjątkiem:
java.lang.NoClassDefFoundError: sun.reflect.ReflectionFactory is a restricted class.


[6] W niektórych przypadkach uprawnienia nie pozwalają na akcje, które w typowych maszynach wirtualnych są dopuszczalne. Przykładem może być dziedziczenie z klasy java.io.ObjectOutputStream (wykorzystywane np. przez Apache Wicket i XStream) - App Engine zgłasza wyjątek:
java.security.AccessControlException: access denied
(java.io.SerializablePermission enableSubclassImplementation)


(*)Znormalizowana jednostka zużycia mocy obliczeniowej jest - dość mgliście - określona jako wydajność 1.2 GHz procesora z serii x86 Intela.
Share:

2010-02-19

Dlaczego trochę mniej lubię Hibernate

Używając Hibernate nigdy nie trafiłem na ewidentny bug. Owszem zdarzało się walczyć z oporem materii (głównie chodziło o zasięg sesji i LazyInitializationException), ale koniec końców okazywało się że biblioteka zachowuje się tak jak powinna się zachowywać, a błąd robiłem ja.
Dlatego, gdy po próbie zamapowania encji na dwie tabele, zobaczyłem:
org.hibernate.AssertionFailure: Table ... not found
zacząłem szukać ewentualnych literówek, brakujących adnotacji czy błędów w konfiguracji. I nic, absolutnie nic nie znalazłem. Pozostało - oczywiście - zapytać Google. Dość szybko trafiłem na zgłoszenie ANN-376 dotyczące Hibernate Annotations: nie można dodać drugiej tabeli do klasy, która jest częścią hierarchii zamapowanej z opcją InheritanceType.JOINED. A dokładnie to w sytuacji gdy mamy tabele powiązane kluczami obcymi:
CREATE TABLE A(
    INT id PRIMARY KEY NOT NULL
)

CREATE TABLE B(
    INT id NOT NULL,
    VARCHAR(100) b
)
ALTER TABLE B ADD CONSTRAINT FK1 FOREIGN KEY (id) REFERENCES A

CREATE TABLE C(
    INT id NOT NULL,
    VARCHAR(100) c
)
ALTER TABLE C ADD CONSTRAINT FK2 FOREIGN KEY (id) REFERENCES A
i próbujemy na nie zamapować klasy:
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@SecondaryTable(name = "C")
public class A {
    @Id
    Long id;

    @Column(table = "C")
    String c;
}

@Entity
public class B extends A {
    String b;
}
dostajemy wyjątek:
org.hibernate.AssertionFailure: Table C not found
Co ciekawe, wystarczy zmiana sposobu dziedziczenia (i struktury tabel) na InheritanceType.TABLE_PER_CLASS lub InheritanceType.SINGLE_TABLE i wszystko jest ok.
Niestety, w moim przypadku takie rozwiązanie było nie do przyjęcia - system już działał, w tabelach były dane i zmiana struktury bazy nie wchodziła w rachubę. Ani w wspomnianym wcześniej zgłoszeniu, ani w powiązanych z nim HHH-4240 i HHH-2053 nikt nie opisał sensownego obejścia, które nie wymagałoby modyfikacji bazy. Ostatecznie zrobiłem tak:
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class A {
    @Id
    Long id;
}

@Entity
public abstract class C extends A {
    String c;
}

@Entity
public class B extends C {
    String b;
}
Niezbyt ładne, ale skuteczne (klasa C jest abstrakcyjna aby nikomu nie przyszło do głowy tworzenie jej instancji).
Zgłoszenia w Hibernate'owej JIRZe(*) związane z tym błędem nie są przypisane do żadnej konkretnej wersji, więc będą poprawione w bliżej nieokreślonej przyszłości. A najstarsze z nich zostało zarejestrowane 20. czerwca 2006 roku.
(*) tak swoją drogą, jak należy odmieniać nazwę JIRA?
Share:

2008-12-18

Uaktualnienie Eclipse do wersji 3.4.1

Zaktualizowałem Mac OSowe Eclipse do wersji 3.4.1 (menu Help / Software Updates...). Wszystko poszło gładko, ale po restarcie środowiska zobaczyłem komunikat:

The Eclipse executable launcher was unable to locate its companion shared library.

i Eclipse kompletnie odmówił współpracy. Więcej informacji można uzyskać uruchamiając go z terminala:

dlopen(../../../plugins/org.eclipse.equinox.launcher.carbon.macosx_1.0.100.v20080509-1800, 2): image not found

Rzeczywiście takiego pliku ani katalogu nie ma, ale jest katalog o bardzo podobnej nazwie:

org.eclipse.equinox.launcher.carbon.macosx_1.0.101.R34x_v20080731

Pozostała kwestia gdzie owa ścieżka jest zaszyta. Ponieważ Eclipse odmawia posłuszeństwa zaraz po starcie, to pierwszym kandydatem jest plik eclipse.ini. I rzeczywiście, po wpisaniu nazwy istniejącego katalogu jest ok.
Share:

2008-07-04

2008-06-06

2008-03-19

Komponent Spring jako ServletFilter

Nic odkrywczego, ale filtrów nie ma w przykładach zawartych w dystrybucji Spring i nie ma o nich słowa w Reference Documentation. Pamiętam że jakiś czas temu popełniłem takowy, ale parę dni temu chcąc znowu użyć tej samej konstrukcji nie pamiętałem szczegółów. Starzeję się. Dlatego, celem utrwalenia zdobytej (ponownie) wiedzy, powstał ten wpis.

Aby możliwe było użycie komponentu Spring jako filtru musi on implementować interfejs javax.servlet.Filter i to wszystko. Potem dopisujemy go w konfiguracji XMLowej lub adnotujemy jak każdy inny bean. Cała sztuczka jest w pliku web.xml aplikacji:

<filter>
<filter-name>nazwa</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

Klasa DelegatingFilterProxy będzie delegować wywołania metody doFilter(...) do beana o takiej samej nazwie jak nazwa filtra. Jeżeli chcemy, aby filtr miał inną nazwę, to trzeba dodać parametr targetBeanName:

<filter>
<filter-name>nazwaFiltra</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>nazwaBeana</param-value>
</init-param>
</filter>

Należy pamiętać, że pomimo tego iż bean implementuje interfejs Filter, to jego metody init(...) i destroy(...) domyślnie nie są wywoływane. Aby to zmienić musimy ustawić w konfiguracji kontekstu Spring atrybut targetFilterLifecycle na true:

<bean id="nazwaBeana"
class="klasaBeana"
p:targetFilterLifecycle="true">
...
</bean>
Share:

2008-03-09

CXF nie działa ze Spring 2.5.2

A konkretnie CXF 2.0.4-incubator nie działa ze Spring Framework 2.5.2 (w wersjach wcześniejszych jest w porządku). Powodem jest zmiana sposobu konfiguracji kontekstu Spring. Dotychczas w konstruktorze
ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
parametr configLocations mógł mieć wartość null - był w takiej sytuacji zastępowany pustą tablicą. W wersji 2.5.2 nie jest już zastępowany, a w metodzie setConfigLocations (wykorzystywanej przez powyższy konstruktor) następuje próba odczytania rozmiaru tablicy i rzucany jest wyjątek NullPointerException. Błąd został zgłoszony kilka dni temu (numer SPR-4531) i jak na razie nie ma reakcji ze strony autorów - pozostaje cofnięcie się do wersji 2.5.1.

[aktualizacja 2008.03.10]


Nieoceniony Juergen Hoeller zainteresował się tym bugiem - w dzisiejszym night buildzie wersji 2.5.3 ma być poprawiony. Rewelacja po raz drugi (pierwsza była tutaj).

[aktualizacja 2008.03.12]


Nie miałem czasu aby sprawdzić od razu, dopiero dzisiaj ściągnąłem najnowszy build. Oczywiście błąd jest poprawiony.

Share:

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-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: