czwartek, 22 października 2009

Oracle + daty + JDBC

Ostatnio musiałem się zmierzyć z problemem związanym z obsługą dat w ORACLE 10. Wprowadzenie do tego problemu można znaleźć tutaj. W dużym skrócie: w przypadku insert do kolumny DATE wartości typu java.sql.Timestamp, czyli z poziomu Hibernate zapisujemy property typu java.sql.Timestamp lub java.util.Calendar/java.util.Date z annotacją @Temporal(TemporalType.TIMESTAMP) ), do bazy trafia zapisana przez nas data z czasem, ale bez milisekund - co jest zrozumiałe, bo DATE nie trzyma milisekund. Jeśli jednak spróbujemy odczytać zapisaną przez nas uprzednio krotkę, podając w warunku where zapisaną przed chwilą wartość - wynik jest pusty!
Pewnym popularnym rozwiązaniem, jest ustawienie specjalnego property dla ORACLE 9/10 JDBC driver: oracle.jdbc.V8Compatible=true. Konsekwencje użycia tego ustawienia można znaleźć tu.
Wyżej wymieniony problem można samemu zdiagnozować za pomocą testu.
Środowisko testowe korzysta z Oracle 10 g (10.2.0), JDBC Driver 10.2.0.4 a same testy są uruchamiane w JUNIT 4 za pomocą Spring TestContext Framework.
Encja, która będzie służyć do testów:

@Entity
@Table(name = "TestTable")
@SequenceGenerator(name = "seq_id", sequenceName = "SQ_TT")
public class TestEntity {
@Id
@GeneratedValue(generator = "seq_id")
Long i;

@Column
String state;

@Column(columnDefinition = "date")
@Temporal(TemporalType.TIMESTAMP)
Date d;

public TestEntity() {
}

public TestEntity( String state, Date d ) {
this.state = state;
this.d = d;
}

Test ukazujący w/w problem

@Test
public void test1(){
TestEntity t = new TestEntity("foo",new Date());
sessionFactory.getCurrentSession().save( t );
sessionFactory.getCurrentSession().flush();
sessionFactory.getCurrentSession().clear();
t = (TestEntity) sessionFactory.getCurrentSession().createQuery( "from TestEntity where d=?").setTimestamp( 0, t.getD()).uniqueResult();
assertThat( t, notNullValue() );
}

Test ten kończy się failure.
Można to łatwo poprawić, gdy "wyzerujemy" milisekundy

TestEntity t = new TestEntity("foo",new Date());
sessionFactory.getCurrentSession().save( t );
sessionFactory.getCurrentSession().flush();
sessionFactory.getCurrentSession().clear();
Calendar cal = Calendar.getInstance();
cal.setTime( t.getD() );
cal.set( Calendar.MILLISECOND, 0 );
t = (TestEntity) sessionFactory.getCurrentSession().createQuery( " from TestEntity where d=?").setTimestamp( 0, cal.getTime()).uniqueResult();
assertThat( t, notNullValue() );

Wykonanie tego testu kończy się sukcesem.
Na tym niestety problem się nie kończy. Pomimo tego, że udało nam się uzyskać poprawny wynik, to potrzeba "zerowania" milisekund jest strasznie upierdliwe, ale co gorsze zapytanie takie nie wykorzystują indeksu, który byłyby założone na kolumnie typu DATE. Można się o tym przekonać wykonując następujące polecenia (użytkownik system) :

select sql_id, child_number,sql_text from V$SQL where sql_text like '%from TestEntity%' order by last_load_time desc;

W wyniku dostaniemy sql_id oraz child_number, obie wartości służą jako input dla polecenia:

SELECT * FROM table(DBMS_XPLAN.display_cursor('<sql_id >',<child_number>));

W przypadku gdy mielibyśmy stworzony indeks np. na 2 kolumnach (jedna z nich będzie typu DATE), to zapytania z warunkiem where, które teoretycznie mogłyby wykorzystywać indeks (, np status=? and d>=? and d mogą się bardzo długo wykonywać.
Jeśli nawet ORACLE użyje indeksu to może zrobić to bardzo nieefektywnie.
Jest to spowodowane tym, że ORACLE będzie "promował" daty w indeksie do typu TIMESTAMP. Taka "promocja" powoduję, że przeglądnięcie indeksu jest nieefektywne. Widać to w planie zapytania:
|*  6 |      INDEX RANGE SCAN         | MOJ_INDEX |   741 |       | |                                                                                                                                                                            
--------------------------------------------------------------------------------------------------------

6 - access("state"=:3)
filter((INTERNAL_FUNCTION("D")>=:1 AND INTERNAL_FUNCTION("D")<:2))

"Promocja" typu jest prezentowana jako INTERNAL_FUNCTION
Rozwiązaniem problemu (brak użycia indeksu oraz potrzeba "zerowania" milisekund) byłoby "wypchnięcie" do bazy z poziomu JDBC wartości typu DATE a nie TIMESTAMP.
Można to zrobić poprzez:
  • używanie funkcji to_date - wymaga to ręcznego wprowadzania daty w postaci String

    @Test
    public void testToDate(){
    TestEntity t = new TestEntity("foo",new Date());
    sessionFactory.getCurrentSession().save( t );
    sessionFactory.getCurrentSession().flush();
    sessionFactory.getCurrentSession().clear();
    String dateInStringFormat = createDateFormat( "yyyy-MM-dd HH:mm:ss" ).format( t.getD() );
    t = (TestEntity) sessionFactory.getCurrentSession().createQuery( " from TestEntity where d=to_date(?,'YYYY-MM-DD HH24:MI:SS')").setString( 0, dateInStringFormat ).uniqueResult();
    assertThat( t, notNullValue() );
    }
  • użycie funkcji cast as
    - cast z TIMESTAMP na DATE nie tylko "rzutuje" jeden typ na drugi, ale także dokonuję zaokrąglenia:

    @Test
    public void testCastMilis500andAbove(){
    Calendar calendar = Calendar.getInstance();
    calendar.setTime( new Date() );
    calendar.set( Calendar.MILLISECOND, 500 );
    TestEntity t = new TestEntity("foo",calendar.getTime());
    sessionFactory.getCurrentSession().save( t );
    sessionFactory.getCurrentSession().flush();
    sessionFactory.getCurrentSession().clear();
    t = (TestEntity) sessionFactory.getCurrentSession().createQuery( " from TestEntity where d= cast (? as date)").setTimestamp( 0, calendar.getTime()).uniqueResult();
    assertThat( t, nullValue() );
    }

    @Test
    public void testCastMillisBelow500(){
    Calendar calendar = Calendar.getInstance();
    calendar.setTime( new Date() );
    calendar.set( Calendar.MILLISECOND, 499 );
    TestEntity t = new TestEntity("foo",calendar.getTime());
    sessionFactory.getCurrentSession().save( t );
    sessionFactory.getCurrentSession().flush();
    sessionFactory.getCurrentSession().clear();
    t = (TestEntity) sessionFactory.getCurrentSession().createQuery( " from TestEntity where d= cast (? as date)").setTimestamp( 0, calendar.getTime()).uniqueResult();
    assertThat( t, notNullValue() );
    }
    Nie objedzie się w takim przypadku od "zerowania" milisekund. W przypadku Criteria API nie wiem w jaki sposób out-of -box używać wywołań funkcji. Chyba trzeba by stworzyć własnego Criterion
  • bezpośrednie użycie typu oracle.sql.DATE:

    @Test
    public void test3ImplicitOracleDateJDBC() throws SQLException{
    TestEntity t = new TestEntity("foo",new Date());
    sessionFactory.getCurrentSession().save( t );
    sessionFactory.getCurrentSession().flush();
    sessionFactory.getCurrentSession().clear();
    SimpleJdbcTemplate jdbcTemplate = new SimpleJdbcTemplate(dataSource);
    Timestamp ts = new Timestamp(t.getD().getTime());
    oracle.sql.DATE d = new oracle.sql.DATE(ts);
    long queryForLong = jdbcTemplate.queryForLong( "select count(* ) from TestTable where d=?", d );
    assertThat( queryForLong, equalTo( 1l ) );
    }

    Skuteczne, ale niewygodne. W przypadku HQL trzeba by pewnie stworzyć custom type, który byłby użyty przy bindowaniu parametrów przy pomocy wywołania

    Query setParameter(int position, Object val, Type type);

    W przypadku użycia Criteria API chyba najwygodniejsze byłoby stworzenie własnego Criterion
  • przy użyciu driveraJDBC w wersji 11.1.0.7.0 zadziała taka konstrukcja:

    @Test
    public void testImplicitType() throws SQLException{
    TestEntity t = new TestEntity("foo",new Date());
    sessionFactory.getCurrentSession().save( t );
    sessionFactory.getCurrentSession().flush();
    sessionFactory.getCurrentSession().clear();
    Connection connection = sessionFactory.getCurrentSession().connection();
    PreparedStatement preparedStatement = connection.prepareStatement( "select count(*) from TestTable where d=?");
    preparedStatement.setObject( 1,new Timestamp(t.getD().getTime()) ,java.sql.Types.DATE );
    assertThat( preparedStatement.executeUpdate(), equalTo( 1 ));
    }
  • Oracle JDBC 9/10 (dla 9 nie sprawdzałem) driver undocumented "feature":

    @Test
    public void testSetTimeMilis500andAbove(){
    Calendar calendar = Calendar.getInstance();
    calendar.setTime( new Date() );
    calendar.set( Calendar.MILLISECOND, 500 );
    TestEntity t = new TestEntity("foo",calendar.getTime());
    sessionFactory.getCurrentSession().save( t );
    sessionFactory.getCurrentSession().flush();
    sessionFactory.getCurrentSession().clear();
    t = (TestEntity) sessionFactory.getCurrentSession().createQuery( " from TestEntity where d= ? ").setTime( 0, calendar.getTime()).uniqueResult();
    assertThat( t, notNullValue() );
    }

    @Test
    public void testSetTimeMilisBelow500(){
    Calendar calendar = Calendar.getInstance();
    calendar.setTime( new Date() );
    calendar.set( Calendar.MILLISECOND, 499 );
    TestEntity t = new TestEntity("foo",calendar.getTime());
    sessionFactory.getCurrentSession().save( t );
    sessionFactory.getCurrentSession().flush();
    sessionFactory.getCurrentSession().clear();
    t = (TestEntity) sessionFactory.getCurrentSession().createQuery( " from TestEntity where d=?").setTime( 0, calendar.getTime()).uniqueResult();
    assertThat( t, notNullValue() );;
    }

    Zgodnie z dokumentacją setTime() powinno zostać skonwertowane na TIME, ale konwersja jest robiona na DATE! Dla Criteria API trzeba by stworzyć własne Criterion, aby móc wymusić na Hibernate użycie setTime() - Hibernate out-of-box decyduje jaką metodę wywołac na PreparedStatement na podstawie typu/annotacji property na której ustawiamy warunek.

sobota, 17 października 2009

JDD 09

“Jak usprawnić model domeny wykorzystując jBPM?”

Pierwsza prezentacja dotyczyła jBPM. Dotychczas nie miałem do czynienia z takiego typu narzędziami i uważałem je bardziej za sztukę dla sztuki, które tak na prawdę nadają się tylko na prezentację. Pomimo uprzedzeń, prezentacje uważam za bardzo udaną, zaczęła się od wyjaśnienia znaczenia podstawowych, groźnych skrótów: BPM, BPEL, PDL. Po tym nastąpiło przedstawienie konkretnego narzędzia: JBoss jBPM. Parę rzeczy wyglądało na prawdę obiecująco: procesy można modelować w przeglądarce i pod Eclipse. Szczególnie ta pierwsza opcja może być ciekawa dla analityków - "wyklikają" proces w przeglądarce a później developer może zaimportować go w Eclipse. Sam jBPM może być uruchamiany w trybie standalone lub embedded. Moduł jBPM Console pozwala monitorować i śledzić wykonanie proces w postaci wykresów/tabelek i innych wodotrysków w przeglądarce. Zachwyciłoby to nie jednego managera. Dodatkowo jBPM Console pozwala w łatwy sposób na tworzenie prototypów aplikacji wykorzystujących jBPM, np. zasymulowanie wysłania SMS można zrealizować poprzez utworzenie prostego formularza HTML, którego wysłanie wygeneruje input dla kolejnego etapu procesu.


“Sztuka messagingu"

Pierwsza prezentacja jednej z gwiazd JDD Marka Richardsa, autora Java Message Service. Mark starał się przekonać nas (i mnie osobiście przekonał), że pomimo tego, że JMS API nie zmieniło się od 2002, JMS jest cały czas bardzo użyteczną technologią, a jej umiejętne wykorzystanie pozwala nam tworzyć wydajne/skalowalne/niezawodne i potencjalnie heterogeniczne systemy. Tu znajduje się zapowiedz prezentacji Marka, która pasuję bardzo do obu jego prezentacji na JDD. Na początku prezentacji wiało trochę nudą, i nie pomogło nawet odebranie wiadomości w Groovym w typowym przykładzie send/receive. O wiele ciekawsze okazało się omówienie podstawowych przypadków użycia JMS:

  • integracja poprzez JMS pomiędzy aplikacjami/modułami napisanymi w potencjalnie różnych językach (nie chodzi to bynajmniej o wspomniane powyżej Groovy), np JAVA i .NET
  • skalowalność - tu szczególnie leży siła rozwiązań opartych na JMS. W przypadku komunikacji 2 komponentów: pierwszy wysyła komunikat do kolejki, a drugi komponent przetwarza ten komunikat. Dzięki takiemu podejściu można bardzo swobodnie i łatwo sterować ilością komponentów (listnerów) przetwarzających komunikaty
  • asynchroniczność - możliwość asynchonicznego powiadamiania zainteresowanych komponentów

Dalej pojawiło się parę slajdów związanych z 2 modelami JMS: point-to-point or publish-subscibe, dostępne typy wiadomości (tu pojawiła się uwaga: aby zapewnić interoperability można zapomnieć o ObjectMessage) oraz budowa wiadomości. Mark zwrócił także dodatkowo na ważną cechę związaną z używaniem JMS API: w odróżnieniu od JDBC, transakcyjna jest obiekt session a nie connection. Zazwyczaj optymalnym rozwiązaniem ma być użycie pojedynczego connection i puli obiektów session. Po prezentacji rozmawiałem z Markiem i uświadomił mnie ,że 1 MessageListener= 1 session = 1 TCP connection (to ostanie sam zamierzam sprawdzić ). Oznacza to, że nie można przesadzać z liczbą równolegle odpalonych listnerów - Mark stwierdził. że dla mocno obciążonych systemów ma byc to około 20. Jeśli chodzi o technikalia to polecił używanie Jencks wraz z ActiveMQ lub CachingConnectionFactory.
Bardzo podobał mi się slajdy przedstawiające różne considerations związane z wykorzystywaniem JMS:

  • trwałość komunikatów - wiadomości mają byc domyślnie persystentne co oznaczą, że są trzymane w persystentnym storage. Ma to znaczący wpływ na wydajność - przedstawił wykres prezentujący 4 000 persystentych komunikatów na sekundę do 11 000 nie persystentnych. JMSDeliveryMode można ustawić na MessageProducer lub bezpośrednio na Message
  • użycie pojedynczej kolejki dla całego przetwarzania - architektura w której to wszystkie komponenty wrzucają swoje komunikaty do pojedynczej kolejki. Wiadomości z kolejki są odczytywane przez pojedynczy komponent pełniący rolę routera, który na podstawie "magicznego dyskryminatora" przechowanego w property wiadomości (a nie w treści wiadomości) deleguję wywołanie (wywołuję metodę) odpowiedniego komponentu. Problem w takim podejściu polega na tym, że pewne komponenty (potencjalnie o różnym znaczeniu) mogą wrzucać z różną częstotliwością swoje wiadomości (potencjalnie o różnej charakterystyce przetwarzania) do wspólnej kolejki. Może to bardzo negatywnie wpływać na response time oraz dodatkowo utrudni skalowania takiego systemu - nie za wiele pomogą tutaj triki z ustawieniami różnych priorytetów dla wiadomości, tym bardziej, że nie można wywłaszczać aktualnie przetwarzanych komunikatów. Idąc dalej, Mark stwierdził, że nie jest niczym egzotycznym definiowanie więcej niż 1 kolejki dla tych samych wiadomości - by zapewnić QoS


Na końcu tej części stanowczo stwierdził, że JMS bardzo dobrze się nadaje gdy mówimy o technologiach związanych z integracją szczególnie pod kątem interoperability. Stwierdził, że dla rozwiązań działających w środowisku "inside firewall" JMS nadaję się o wiele bardziej niż web services, głównie ze względu na większą możliwość tuningu, niezawodność i mniejszą złożoność tworzenia takich systemów (?).
Na końcu pojawiła się wzmianka o REST w świecie JMS i pewna technlogiczno-syntaktyczna niespójność: co powinna robić metoda GET ? czy ma pobierać wiadomość z kolejki ? A jeśli tak to pobranie wiadomości jednocześnie usuwa ją z kolejki, a przecież wywołanie GET powinno być safe. Z tego co zapamiętałem to Active MQ oraz Websphere MQ mają posiadać pseudo REST API do swoich systemów

“Obsługa sytuacji wyjątkowych w systemach budowanych w technologii JEE”

Prezentacja sponsora konferencji, na szczęści wyglądało to duże lepiej niż rok temu. Zaprezentowany materiał całkiem fajny, oparty na doświadczeniu, ale trochę mało świeży, szczególnie gdy ktoś ma do czynienia z lekkimi frameworkami, które stawiają na unchecked exceptions. Oprócz podstawowych informacji o tym jakie wyjątki logować, w jaki sposób logować, co logować itd mnie bardziej zainteresowały następujące zagadnienia:

  • transakcyjność a pamięć podręczna - w sytuacji gdy nasz cache nie jest transakcyjny (czyli zawsze, chyba że się mylę ) to trzeba zadbać aby w przypadku rollback wycofać to co w ramach transakcji zmodyfikowaliśmy w cache. Sprawa wydaję się w ogólności dość skomplikowana , a z tego co się orientuję to w hibernate 2nd level cache potrafi sobie z czymś takim radzić out-of-box
  • transakcyjność a sesja - w przypadku gdy nasze przetwarzanie na serwerze zakończy się wyjątkiem - sesja może być niespójna - wyjątek mógł polecieć po tym jak coś z sesji zmodyfikowaliśmy. Rozwiązanie ma być oparte o filtr opakowywujący oryginalną sesje w wrapper, dostępny dla aplikacje, a faktyczny zapis do sesji jest realizowany w tym samym filtrze po zakończeniu przetwarzania
  • W przypadku standardowego logowania SQLException stacktrace/message wyjątku nie zawierają ani SQLState ani ErrorCode
  • W przypadku ServletException nie jest logowany faktyczny powód wystąpienia, który można zobaczyć dobierając się do rootCause
  • naruszenie więzów integralności na bazie nie koniecznie jest błędem typu unrecover , a może być po prostu błędem biznesowym - próba stworzenia 2 userów o tym samym login. który to musi być unikalny. W takim przypadku można taki wyjątek złapać i poprawnie obsłużyć.

“Asynchroniczność, współbieżność i rozproszone przetwarzanie w Java EE – przykłady z użyciem technologii middleware Oracle: WebLogic Server, EclipseLink/TopLink JPA i Coherence"

Prezentacja Waldka Kota miała pierwotnie składać się z 2 części: omówienie WorkManager API a później przedstawienie Oracle Coherence. Jednak ze względu na to, że widownia miała problemy z zebraniem się z obiadu oraz dużą liczbą pytań o WorkManager API część druga w ogóle się nie odbyła, a Waldek obiecał, że opiszę to co miał przygotowanie na temat Oracle Coherence na swoim blogu. Prezentacja zaczęła się od przypomnienia/uświadomienia czemu nie powinno się tworzyć wątków w środowisku JEE. Z WorkManager API miałem styczność jakiś czas temu przy tworzeniu aplikacji JEE uruchamianych na "ulubionym" IBM Websphere. Pierwotnie API to powstała w ramach JSR 236, które aktualnie zostało zaniechane i z tego co opowiadał Waldek są jakieś zawirowania w tej sprawie w związku z JEE 6. Do tego czasu Websphere(min 6.0, w wersji 5.0 ma podobny mechanizm Asynchronous Beans)oraz Weblogic (min 9.0) wspierają CommonJ Timer and Work Manager for Application Servers (oprócz Work Manager API mamy do dyspozycji Timer API). Jest to na prawdę silna broń, ale nie do końca wierzę aby nadawała się do tworzenia aplikacji batchowych. Niespecjalnie wgłębiałem się w dostępne opcje konfiguracyjne WorkManager na Websphere, ale to co pokazywał Waldek na przykładzie serwera Weblogic robi naprawdę wrażenie: przypięcie WorkManager do wielu aplikacji, pojedynczej aplikacji, servletu, ziarna EJB, czy nawet metody EJB, określanie parametrów definiujących min/max ilość wątków, ustawienie stuck time i jeszcze pewnie parę innych.
Co ważne podkreślenia WorkManager API uruchamia zadania w kontekście JEE: same zadanie jest wykonywane w kontekście (security, classpath, naming) w jakim zostało ono zlecone do wykonania.
Specyfikacja wspomina także o Remoteable WorkManager i RemoteWorkItem - delegowanie zadań do zdalnych węzłów w klastrze. Websphere nie wspiera takiej funkcjonalności, jestem ciekaw jak to jest w Weblogic.


Efektywne przeglądy kodu dla developerów Java używających metodologii z rodziny agile

Bardzo ciekawa prezentacja, na temat code review. Wojtek na podstawie własnych doświadczeń, przestawił nie tylko na czym polega nowoczesny proces code review, ale co najważniejsze wiele wskazówek związanych z wprowadzaniem code review do organizacji - nic na siłę, metoda małych kroczków, trzeba dostosowac proces do zespołu (zmotywowany i samo-organizujący się zespół :) ), sposób wybieranie reviewerów. Pomimo wielu zasadniczych zalet: mentoring mniej doświadczonych/nowych członków zespołu, budowanie bazy wiedzy, zwiększenie collective ownership kodu, ewentualnie wykrywanie błędów, wprowadzenie code review niestety daje mało mierzalne rezultaty - co często może powodować opór managementu. Code review nie powinno w żadnym przypadku zastępowac statycznej analizy kodu, które to powinna odbywać sie przed review w celu "wyczyszczenia" kodu z naruszeń. Code review, które miałoby się odbywać "tradycyjnie" w postaci spotkania, na którym obecny miałby być cały zespól przeglądający wydrukowany kod, ma być mało skuteczne i problematyczne w sensie logistycznym, o wiele lepiej wykorzystać do tego narzędzia ,np. Crucible. Przyznam, że narzędzie zrobiło na mnie duże wrażenie - wszystko wyglądało bardzo intuicyjnie i prosto, a jednocześnie dobrze dostosowane na potrzeby developerów. Bardzo ciekawie wyglądało porównanie code review i pair programming- obie techniki dotyczą pracy 2 osób nad kawałkiem kodu, jednak tak na prawdę więcej je od siebie różni niż łączy... Z projektów darmowych podobno warto sprawdzić: rietveld, reviewClipse a sam kiedyś instalowałem jupiter


“Testowanie z Groovy”

Prezentacja kolejnej z gwiazd JDD Scotta Davisa. Prezentacja mnie rozczarowała, po standardowej opowieści o tym, że na JVM można uruchamiać aplikacje napisane w: JRuby, JavaScript, JPython,Java FX, Scala oraz kilu żartach prowadzącego, zostało kilkanaście minut na wpomnienie o tym czym jest BDD i pokazanie trywialnego kodu w Groovy z wykorzystaniem paradygmatu: given... : when... : then ... and ...


Architektura Resource-Oriented (ROA) i REST

ostatnia prezentacja JDD. Tym razem Scott opowiadał o REST i ROA. Zaczęło sie od typowych żartów na temat SOAP, które miało umożliwiać szybkie i wygodne tworzenie interoperable web services. Po tym jak wylano wiadro pomyj na SOAP, Scott pokazał z jaką łatwością konsumuje się REST webservices z poziomu Groovy, szczególnie gdy korzystamy z XMLSlurper. Po przedstawieniu podstawowych pojęć związanych z REST, Scott przedstawił wiele przykładów wykorzystania ROA: Ebay, Amazon, Twitter, Yahoo, GData. Szczególnie polecał przyjrzenie się Google Calendar Data API, które uważa za modelowe przykład wykorzystania ROA, szczególnie gdy sami będziemy projektować REST based systems.

poniedziałek, 12 października 2009

Dostęp do Websphere Remote EJB 2.1 z JAVA SE

Potrzebowałem stworzyć małą aplikację J2SE do wołania zdalnych sesyjnych bezstanowych EJB uruchomionych na Websphere 6.1.0.19. W mojej poprzedniej pracy juz robiłem coś takiego, ale wtedy aplikacja działała na SUN JRE. Tym razem podstawowym wymaganiem było użycie IBM JRE. Skoro serwera aplikacyjny oraz JRE są od tego samego dostawcy to sprawa powinna być prosta.... tymbardziej, że aplikacja była trywialnana a dodatkowo nie miałem potrzeby korzystać z żadnych z enterprise features jak bezpieczeństwo czy transakcyjność.
Dodatkowo EAR zawierający EJB, do którego trzeba się było dobierać nie był mojego autorstwa i nie dostarczono dla niego jara klienckiego - ze stubami, ale te można samemu wygenerować za pomocą ejbdeploy.
Za wszelką cene chciałem uniknąć sytuacji, w której to musiałbym uruchamiać moją aplikację za pomocą client container albo wykonywać jakies inne, dziwne kroki w fazie developmentu/deploymentu.
Po przeczytaniu dokumentacji wyglądało to wszystko extra skomplikowanie. Tymbardziej, że Weblogic już od dawna udostępniał swoim użytkownikom lekkiego client.jar (czy jakoś tak) do takich celów.
Czemu prosta rzecz nie może byc prosta?
Po wielu próbach i błędach udało mi się w końcu przygotować aplikację. Oprócz jara ze stubami, classpath zawiera następujące pozycje:

ecore-2.3.0-v200706262000.jar
emf-2.1.0.jar
com.ibm.ws.webservices.thinclient_6.1.0.jar
com.ibm.ws.runtime_6.1.0.jar


ecore-2.3.0-v200706262000.jar jest do ściągnięcia z mvnrepository, pozostałe 3 są skopiowane z WebSphere.
Dodatkowo bardzo ważna jest kolejność na classpath: com.ibm.ws.webservices.thinclient_6.1.0.jar musi byc przed com.ibm.ws.runtime_6.1.0.jar, ze względu na to, że klasa com.ibm.ejs.ras.Tr znajduję się w tych obu jarach.

Odwołanie do EJB jest realizowane w następujacy sposób:

Context initialContext = new InitialContext();
StringBuilder builder = new StringBuilder("corbaname:iiop:");
builder.append(serverIp);
builder.append(":");
builder.append(serverPort);
builder.append("#");
builder.append(ejbJNDIName);
Object lookUpResult = initialContext.lookup(builder.toString());
HomeInterface home = (HomeInterface) javax.rmi.PortableRemoteObject.narrow(
lookUpResult, HomeInterface.class);

serverIp - adres serwera na którym jest zdeployowany EJB
serverPort - port do komunikacji RMI (domyslnie 2809)
ejbJNDIName - nazwa JNDI zdalnego EJB

Dodatkowo dla serwera, którego EJB będziemy wywoływać należy dodać parę:adres ip, nazwa serwera do etc\hosts.

Jedyne co mi się nie podoba w przygotowanym przeze mnie rozwiązaniu to potrzeba umieszczenia na classpath w/w bibliotek z Websphere (sam com.ibm.ws.runtime_6.1.0.jar zajmuję prawie 60 MB).
Podobno w Websphere 7.0 został przygotowany jar kliencki, ale osobiście nie mam jak na razie dostępu do tej wersji.

UPDATE: przeglądając materiały związane z classloadingiem dla WAS 6.1 natknąłem się na info dotyczące "thin client library" dla WAS 6.1. Składają się na to 2 jary: com.ibm.ws.admin.client_6.1.0.jar (34 Kb) oraz com.ibm.ws.webservices.thinclient_6.1.0.jar (15 Kb) siedzące w <INSTALL_ROOT>/runtimes. Po usunięciu z classpath jarów wymienionych na początku posta (ecore-2.3.0-v200706262000.jar, emf-2.1.0.jar, com.ibm.ws.webservices.thinclient_6.1.0.jar, com.ibm.ws.runtime_6.1.0.jar) i podegraniu na ich miejsce tych z <INSTALL_ROOT>/runtimes wszystko działa bez zmian.