čtvrtek 31. května 2012

RESTful webová služba 1. díl

  1. Nainstaluji si server glassfish. Ten je možné zdarma získat např. zde. Stačit vám bude ale i Tomcat nebo jiný servletový kontejner.
  2. Vytvořím si v Netbeans novou webovou aplikaci:
  3. V tomto okně je třeba dát Add a následně vybrat nainstalovaný servletový kontejner.
  4. Nyní je třeba nakonfigurovat aplikaci. To je možné dvěma způsoby:

    1.  Prostřednictvím souboru web.xml
    2.  <?xml version="1.0" encoding="UTF-8"?>  
       <web-app 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_3_0.xsd" version="3.0">  
         <servlet>  
           <servlet-name>JerseyServlet</servlet-name>  
           <servlet-class>  
             com.sun.jersey.spi.container.servlet.ServletContainer  
           </servlet-class>  
           <load-on-startup>1</load-on-startup>  
         </servlet>  
         <servlet-mapping>  
           <servlet-name>JerseyServlet</servlet-name>  
           <url-pattern>/resources/*</url-pattern>  
         </servlet-mapping>  
       </web-app>  
      

    3. Pomocí anotace @ApplicationPath
    4. 1:  package biz.prodejna.examples.rest;  
      2:    
      3:  import javax.ws.rs.ApplicationPath;  
      4:  import javax.ws.rs.core.Application;  
      5:    
      6:  @ApplicationPath("resources")  
      7:  public class JaxRsConfig extends Application {  
      8:  }  
      

  5. A nyní konečně mohu napsat nějakou svoji třídu.
  6. RESTful webovou službu mohu volat jako bezparametrickou nebo s parametry. V druhém případě mám v podstatě 3 možnosti, jak parametry vkládat:
    • Jako součást URL. Příklad: http://localhost:8080/example-rest/resources/names/jmeno/prijmeni
    • Jako parametry metody GET. Příklad: http://localhost:8080/example-rest/resources/names?jmeno=Adam&prijmeni=Oliva
    • V těle HTTP requestu. Zde mohou být parametry naformátovány v libovolném MIME typu.

    V tomto bodě ukáži pouze první případ. Další případy si nechám do některého z dalších dílů.

    1:  package biz.prodejna.examples.rest;  
    2:    
    3:  import javax.ws.rs.GET;  
    4:  import javax.ws.rs.Path;  
    5:  import javax.ws.rs.PathParam;  
    6:  import javax.ws.rs.Produces;  
    7:    
    8:  @Path("names")  
    9:  public class Names {  
    10:    
    11:    @GET  
    12:    @Path("{first}/{last}")  
    13:    @Produces("text/plain")  
    14:    public String getFullName(  
    15:        @PathParam("first") String firstName, @PathParam("last") String lastName) {  
    16:      return firstName + " " + lastName;  
    17:    }  
    18:  }  
    

    Vysvětlím jednotlivé anotace:
    @Path u třídy udává, pod jakou URL bude služba dostupná.
    @GET definuje, jaká bude použita HTTP metoda.
    @Path u metody definuje, jaké bude pořadí parametrů. Nebo přesněji: jaký tvar bude mít zbytek URL.
    @Produces udává MIME typ v těle HTTP odpovědi.
    @PathParam mapuje parametry metody na části v anotaci @Path

  7. Nyní je už možné aplikaci deployovat na server a zkusit ji zavolat např. z webového prohlížeče: http://localhost:8080/example-rest/resources/names/Adam/Oliva

  8. Proč URL vypadá právě takhle? Vysvětlím:
    example-rest -název aplikace
    resources -definováno ve web.xml nebo anotací @ApplicationPath
    names -definováno anotací třídy @Path
Pokud máte zájem o pokračování tohoto blogu, informujte mě o tom. Co lze očekávat příště:
  • služba vracející odpověď jako xml
  • služba očekávající parametry jako parametry URL
  • tvorba klienta s využitím implementace Jersey.
  • služba očekávající na vstupu XML dokument.

pátek 30. března 2012

GlassFish V3 admin console se načítá příliš dlouho

Pokud máte nainstalovaný GlassFish V3 za proxy a chcete se přihlásit do administrátorské konzole, nejspíš musíte čekat na úvodní obrazovku dosti dlouhou dobu. V logu je pak výpis podobný tomuto:
 admin console: initSessionAttributes()  
 Cannot refresh Catalog : Connection timed out  
Tohoto neduhu se lze zbavit následujícím postupem:

1) Do souboru %GLASSFISH_HOME/glassfish/domains/domain1/domain.xml přidejte následující jvm-option:
 <jvm-options>-Dcom.sun.enterprise.tools.admingui.NO_NETWORK=true</jvm-options> 

2) Odstraňte (raději si ho někam zálohujte) update tool jar z umístění %GLASSFISH_HOME/glassfish/modules/console-updatecenter-plugin.jar

3) Pokud máte čistou instalaci, můžete si dovolit odstranit složky %GLASSFISH_HOME/glassfish/domains/domain1/osgi-cache a %GLASSFISH_HOME/glassfish/domains/domain1/generated. Postup mi fungoval i bez tohoto bodu.

4) %GLASSFISH_HOME/bin/asadmin [re]start-domain

čtvrtek 8. března 2012

EJB jar včetně závislostí

Potřeboval jsem vytvořit EJB jar v němž měly být obsaženy také všechny závislosti. V mém případě MyBatis a Spring Framework. Pro buildování používám maven. Do výsledného jaru samozřejmě nelze zabalit přímo další jary, je třeba je nejprv rozbalit. To vyřešil mavenovský plugin maven-dependency-plugin přidaný do pom.xml. Zajímavá je v tomto místě především jeho část execution:
 <execution>  
   <id>unpack-dependencies</id>  
   <phase>process-classes</phase>  
   <goals>  
     <goal>unpack-dependencies</goal>  
   </goals>  
   <configuration>  
     <!--Definuji co se ma rozbalit do vysledneho jaru-->  
     <includeGroupIds>org.springframework,org.mybatis</includeGroupIds>  
     <outputDirectory>${project.build.directory}/classes</outputDirectory>  
   </configuration>  
 </execution>  
Aby byly přibalené závislosti také v classpath, vypadala definice pluginu maven-ejb-plugin následovně:
 <plugin>  
   <groupId>org.apache.maven.plugins</groupId>  
   <artifactId>maven-ejb-plugin</artifactId>  
   <version>2.3</version>  
   <configuration>  
     <archive>  
       <manifest>  
         <addClasspath>true</addClasspath>  
       </manifest>  
     </archive>  
     <ejbVersion>3.1</ejbVersion>  
   </configuration>  
 </plugin>  
Výsledkem celého snažení ovšem byla nicneříkající chyba:
Caused by: org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 9 in XML document from URL [file:.../META-INF/applicationContext.xml] is invalid; nested exception is org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'beans'.
Naštěstí jsem narazil na blogspot, který tento problém řeší následovně: "stačí" ze všech použitých springových jarů vybrat soubory META-INF/spring.handlers a META-INF/spring.schemas a sloučit je pouze do dvou. Tyto dva dát do adresáře META-INF a celé to zabalit do jaru. Výsledný jar, pak umístit do lib aplikačního serveru. V případě použití novější verze springu může být třeba tyto soubory doeditovat.

čtvrtek 12. ledna 2012

SSL na více virtuálních serverech v rámci jednoho serveru glassfish

Poblém, který následující řádky řeší je přístup přes https na více domén (druhého řádu) v rámci jednoho glassfishe, kdy domény jsou definovány v rámci virtuálního serveru. Používat více serverových certifikátů na jednom glassfishi v rámci jedné domény možné není. Více o problému zde.

Uvedený problém je řešitelný vydáním certifikátu odpovídající standardu RFC-2818 (http://www.ietf.org/rfc/rfc2818.txt) následujícím způsobem:
V CN bude název jednoho z požadovaných názvů virtuálního serveru. Ostatní názvy virtuálních serverů budou v položce "Alternativní jména subjektu certifikátu" (SubjectAltName), kde je možné umístit jednu nebo více položek dNSName případně iPAddress nebo uniformResourceIdentifier. Tomuto podobných řešení existuje více (např. využití regulárního výrazu v CN), uvedené má ovšem nejlepší interoperabilitu (http://wiki.cacert.org/VhostTaskForce#Interoperability_Test).
Podle zmíněného standardu je možné v dNSName využívat i hvězdičkovou konvenci. Navíc definuje, že CN je použito jako identita až v případě, že není definováno rozšíření subjectAltName typu dNSName. Používání CN je zastaralé a RFC-2818 namísto toho certifikačním autoritám doporučuje používat dNSName.

pátek 1. července 2011

SSL certifikát pro více subdomén spravovaných jedním serverem glassfish

Případ, kdy je více domén 3.řádu (k jedné doméně 2. řádu) obsluhováno jedním serverem je celkem častý. Pokud tyto domény využívají SSL je zde možnost využít  certifikát vystavený pro všechny domény třetího řádu (*.mojedomena.com)
Popíši teď postup pro případ, kdy si vytvořím certifikát podepsaný sám sebou. Budu předpokládat, že alias certifikátu nastavený v http listeneru serveru glassfish se jmenuje s1as. V Admin consoli je toto nastavení v Configuration - Network Config - Network Listeners - http-listener-nazev - záložka SSL - Certificate NickName. Všechni níže uváděné příkazy jsou prováděny v
1) Nejprve smažu dosud používaný privátní klíč:
 keytool -delete -alias s1as -keystore keystore.jks -storepass changeit  
2) Smažu certifikát:
 keytool -delete -alias s1as -keystore cacerts.jks -storepass changeit  
3) Vytvořím privátní klíč:
 keytool -genkey -alias s1as -keypass changeit -storepass changeit -keystore keystore.jks  
4) Předchozí příkaz je interaktivní. Je nutné na otázku "What is your first and last name?" napsat "*.mojedomena.com" další odpovědi již nejsou tolik významené.
5) Exportuji do souboru certifikát:
 keytool -export -alias s1as -storepass changeit -file cert.cer -keystore keystore.jks  
6) Importuji certifikát do cacerts:
 keytool -import -v -trustcacerts -alias s1as -file servercpost.cer -keystore cacerts.jks -keypass changeit -storepass changeit  

Hotovo, nyní by již měl glassfish po restartu na příchozí https požadavky využít nový "hvězdičkový" certifikát.

úterý 7. června 2011

Zkušenost s Českou spořitelnou

U ČS jsem měl hypotéku. Nebyla z nejvýhodnjších a tak jsem se rozhodl refinancovat ji za pomoci jiného finančního ústavu. Asi měsíc před koncem fixace jsem se tedy chtěl spojit s ČS a záležitost probrat. Zkoušel jsem tedy zavolat své osobní finančí poradkyni, která mi byla přidělena. Číslo neexistovalo. Zavolal jsem tedy na ústřednu pobočky ČS, kde poradkyně sídlila. Paní na ústředně mi sdělila, že tato zaměstnankyně u nich již nepracuje. Chtěl jsem tedy někoho jiného, kdo by byl schopen můj požadavek konzultovat. Byl jsem přepojen na jinou poradkyni. Rozhovor s ní, po vysvětlení detailů, vypadal asi následovně:
- Ale ke mě nepatříte, to musíte řešit s někým jiným.
- Tak pak nechápu proč mě přepojili na vás.
- Protože jsem tady jediná, kdo aspoň trochu rozumí hypotékám.
(Pozn.: podotýkám, že jde o pobočku ve městě, které má s okolím cca 70 000 obyvatel)
- A ke komu tedy patřím?
- Já se vám podívám a zavolám vám zpět.
Opravdu zavolala a sdělila mi jméno včetně kontaktu na dalšího člověka. To bylo naposledy, kdy mi z ČS někdo opravdu zavolal, poté co mi to slíbil.
Na uvedeném kontaktu jsem si domluvil schůzku. Vysvětlil o co mi jde. Dostal jsem nabídku na úroky, které mi nabídla konkurence (snížení oproti původní nabídce asi o 1%) a to bez dalších podmínek typu "nechci slevu zadarmo" (maximální možná splátka při konci fixace, minimální doba splácení atp.). Nechtěl jsem však již dál platit hypotéku z běžného účtu u ČS, ani jsem nechtěl platit poplatek za změnu tohoto účtu. Poplatek za změnu by činil 500 kč, přestože mě bývalá poradkyně tvrdila, že je to za 5000 kč. Poradcem mi bylo tedy nabídnuto, že by mi z běžného účtu u ČS nebyly účtovány poplatky, abych ho dále využíval. I toto jsem odmítl, protože mě už nebavilo se s ČS dále handrkovat o jednotlivé "slevy", jejichž výsledkem je cena jako u konkurence.
Také jsem si poradci postěžoval na předchozí poradkyni, u které jsem chtěl zrušit svoji debetní kartu, protože jsem nechtěl za ní platit poplatek 200 kč ročně, když ji stejně nepoužívám. Poradkyně mi řekla, že když si ji žena (majitel účtu) neaktivoval, tak mě poplatek strhávat nebudou. Informace se neukázala pravdivou, když mi byl poplatek na začátku roku opět stržen. Na to mi poradce poradil, abych napsal stížnoust a poplatek mi bude vrácen.
Část hypotéky jsem zaplatil ze svého. Potřeboval jsem však potvrzení pro novou banku, že do ČS peníze opravdu dorazili. Osobní poradce mi sdělil, že neví, jestli nějaké takové potvrzení je vůbec možné vydat. Za týden ve středu však měl mít z dovolené v práci opět kolegyni (asi ta, co jediná na pobočce aspoň trochu rozumí hypotékám), která bude vědět, zda je možné toto vydat a pak mi zavolá. Asi týden poté, kdy již měla kolegyně nastoupit do pracovního procesu, jsem poradci volal, jak to vypadá s tím potvrzením, když se podle slibu neozval on mě. Říkal, že ve čtvrtek to bude. Ve čtvrtek jsem tedy přišel na pobočku a potvrzení opravdu bylo.
Tím jsem měl hypotéku vyřešenou a nastal problém se zrušením účtu, kterýmžto vlastníkem je moje žena. Ta si naivně zašla na pobočku, vzala lístek a čekala až na ni příjde řada. Číslo se objevilo s tím, že musí na přepážku o patro víš. Než tam došla bylo tam již jiné číslo. Další číslo si tedy vzala již na vyšším patře a čekala znovu. Když se na ní dostala řada, paní u přepážky ji řekla, že stiskla nesprávné tlačítko, že s jejím požadavkem musí k jiné přepážce a rozloučila se s ní. Šla tedy k tiskárně čísel znovu, stiskla doporučené tlačítko a čekala opět. Již byla u správné přepážky. Nikoli však pro ní.
(Možná si teď říkáte, že moje žena je asi úplně blbá, když není schopná stisknout ve správném patře správné tlačítko. To je ale v rozporu s mými zkušenostmi a také s tím, že byla schopná bez problémů absolvovat vysokou školu technického směru, kde byl odpad studentů přes 80%) Paní u další přepážky jí sdělila, že jí byl přidělen osobního poradce a toto má řešit u něj, nejprve pracovnice zkoušela poradci zavolat, ale nedovolala se. Dala tedy manželce kontak na tohoto osobního poradce, aby si s ním dohodla schůzku a po 1,5 hodině čekání a tisknutí lístků ji poslala domů s nepořízenou.
Poradci jsem poslal mail s tím, že bychom si chtěli domluvit schůzku kvůli rušení účtu a informaci o možnosti připojištění dítěte. Poradce navrhl termín, já ho potvrdil. V domluvený termín jsme se se ženou dostavili na pobočku a ...poradce přítomen nebyl. Zeptal jsem se tedy na něho jeho kolegyně od vedlejšího stolu. Ta mi řekla, že jsem si s ním měl domluvit schůzku. Já odvětil, že tu jsem měl domluvenou na teď. Řekla, ať chvíli počkáme a někam odešla. Pak přišla s úsměvem na tváři a říkala, že to s námi tedy vyřídí ona. Začala mluvit o připojištění dítěte (takže věděla, kvůli čemu jsme přišli). Připojištění jsme nakonec podepsali. Pak nám najednou řekla nashledanou. Tomu jsem se podivil, protože obvykle se ptají jestli potřebujeme ještě něco. Moje žena odvětila, že ale ještě chtěla zrušit běžný účet. Paní na to: "Ale to není jen tak, to se musí připravit a já na vás nemám už teď čas." Nakonec řekla, že dá vědět našemu poradci, aby se nám ozval. Pak jsme odešli. Poradce se samozřejmě neozval.

středa 6. dubna 2011

JAX-WS enterprise application client důvěřující všem serverovým certifikátům

Nedávno jsem vytvořil webovou službu využívající SSL a k ní enterprise aplikačního klienta pro server glassfish za použití JAX-WS. Bylo třeba aby tato dvojice byla spustitelná i na jiném počítači. Tedy klient musel akceptovat i jiné, v době vývoje neznámé, serverové certifikáty. Jedním z možných řešení je vytvořit klienta tak, aby automaticky důvěřoval všem serverovým certifikátům ať obsahují cokoli. Upozorňuji že toto řešení v žádném případě není vhodné pro produkční prostředí.
Aby klient důvěřoval všem certifikátům je třeba vytvořit následující třídu:
1:  import javax.net.ssl.X509TrustManager;  
2:  public class TrustEverythingTrustManager implements X509TrustManager {  
3:      public java.security.cert.X509Certificate[] getAcceptedIssuers() {  
4:        return null;  
5:      }  
6:      public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {  }  
7:      public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {  }  
8:    }  

Navíc WSDL, jehož kopie je i lokálně na straně klienta, obsahuje následující adresu: <soap:address location="https://localhost:8181/services/testPort"/>. Pokud server vrátí certifikát jehož CN bude jiné než localhost, což s největší pravděpodobností bude, nastane následující vyjímka:
 java.lang.reflect.InvocationTargetException  
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  
     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)  
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)  
     at java.lang.reflect.Method.invoke(Method.java:597)  
     at org.glassfish.appclient.client.acc.AppClientContainer.launch(AppClientContainer.java:424)  
     at org.glassfish.appclient.client.AppClientFacade.main(AppClientFacade.java:134)  
 Caused by: com.sun.xml.ws.client.ClientTransportException: HTTP transport error: java.io.IOException: HTTPS hostname wrong: should be <localhost>  
     at com.sun.xml.ws.transport.http.client.HttpClientTransport.getOutput(HttpClientTransport.java:135)  
     at com.sun.xml.ws.transport.http.client.HttpTransportPipe.process(HttpTransportPipe.java:163)  
     at com.sun.xml.ws.transport.http.client.HttpTransportPipe.processRequest(HttpTransportPipe.java:95)  
     at com.sun.xml.ws.transport.DeferredTransportPipe.processRequest(DeferredTransportPipe.java:105)  
     at com.sun.xml.ws.api.pipe.Fiber.__doRun(Fiber.java:629)  
     at com.sun.xml.ws.api.pipe.Fiber._doRun(Fiber.java:588)  
     at com.sun.xml.ws.api.pipe.Fiber.doRun(Fiber.java:573)  
     at com.sun.xml.ws.api.pipe.Fiber.runSync(Fiber.java:470)  
     at com.sun.xml.ws.api.pipe.helper.AbstractTubeImpl.process(AbstractTubeImpl.java:112)  
     at com.sun.enterprise.security.webservices.ClientSecurityPipe.processSecureRequest(ClientSecurityPipe.java:192)  
     at com.sun.enterprise.security.webservices.ClientSecurityPipe.process(ClientSecurityPipe.java:180)  
     at com.sun.xml.ws.api.pipe.helper.PipeAdapter.processRequest(PipeAdapter.java:115)  
     at com.sun.xml.ws.api.pipe.Fiber.__doRun(Fiber.java:629)  
     at com.sun.xml.ws.api.pipe.Fiber._doRun(Fiber.java:588)  
     at com.sun.xml.ws.api.pipe.Fiber.doRun(Fiber.java:573)  
     at com.sun.xml.ws.api.pipe.Fiber.runSync(Fiber.java:470)  
     at com.sun.xml.ws.client.Stub.process(Stub.java:319)  
     at com.sun.xml.ws.client.sei.SEIStub.doProcess(SEIStub.java:157)  
     at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:109)  
     at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:89)  
     at com.sun.xml.ws.client.sei.SEIStub.invoke(SEIStub.java:140)  
     at $Proxy46.testOperation(Unknown Source)  
     at testwsappclient.Main.main(Main.java:62)  
     ... 6 more  
 Caused by: java.io.IOException: HTTPS hostname wrong: should be <localhost>  
     at sun.net.www.protocol.https.HttpsClient.checkURLSpoofing(HttpsClient.java:524)  
     at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:448)  
     at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:166)  
     at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1014)  
     at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:230)  
     at com.sun.xml.ws.transport.http.client.HttpClientTransport.getOutput(HttpClientTransport.java:123)  
     ... 28 more  

Pro tento případ potřebujeme vytvořit další třídu:
1:  import javax.net.ssl.HostnameVerifier;  
2:  import javax.net.ssl.SSLSession;  
3:  public class VerifyEverythingHostnameVerifier implements HostnameVerifier {  
4:    public boolean verify(String string, SSLSession sslSession) {  
5:      return true;  
6:    }  
7:  }  

A nyní obě třídy využijeme v kódu klienta:
1:  public class Main {  
2:    @WebServiceRef(wsdlLocation = "META-INF/test.wsdl")  
3:    private static TestService service;  
4:    public static void main(String[] args) {  
5:      Main main = new Main();  
6:      TestPortType port = service.getTestPort();  
7:      ((BindingProvider) port).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "user");  
8:      ((BindingProvider) port).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "password");  
9:      HostnameVerifier hostNameVerifier = new VerifyEverythingHostnameVerifier();  
10:      ((BindingProvider) port).getRequestContext().put("com.sun.xml.ws.transport.https.client.hostname.verifier", hostNameVerifier);  
11:      SSLContext sslContext;  
12:      try {  
13:        TrustManager[] trustManager = new TrustManager[]{new TrustEverythingTrustManager()};  
14:        sslContext = SSLContext.getInstance("SSL");  
15:        sslContext.init(null, trustManager, new java.security.SecureRandom());  
16:        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());  
17:      } catch (NoSuchAlgorithmException ex) {  
18:        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);  
19:      } catch (KeyManagementException ex) {  
20:        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);  
21:      }  
22:      ObjectFactory of = new ObjectFactory();  
23:      TestOperationRequest req = of.createTestOperationRequest();  
24:      req.setFirstName("Fist");  
25:      req.setLastName("Last");  
26:      try {  
27:        String back = port.testOperation(req);  
28:        System.out.println(back);  
29:      } catch (TestOperationFault ex) {  
30:        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);  
31:      }  
32:    }  
33:  }  

Obdobný problém u REST služby je řešen zde.