Category: magico mondo de Java

Axis howTo

wsSalve, ultimamente ho avuto a che fare con lo sviluppo di web services, tecnologia molto interessante che permette di far dialogare sistemi sviluppati con diverse tecnologie…si pensi ad esempio, ad un back-end sviluppato in python e il front-end sviluppato in Java che effettua interrogazioni. Attraverso l’uso dei web services, è possibile realizzare una tale l’interazione. Sostanzialmente un web service, è un servizio lato server, che può essere invocato passando all’occorrenza dei parametri, e che può restituire un risultato. In Java, un modo abbastanza diffuso, è quello di utilizzare Axis, una libreria che permette lo sviluppo di web service, molto potente.Possono essere sviluppati utilizzando due modalità: contract-first, contract-last. La prima non prevede alcuna definizione del servizio, basta cioè sviluppare la classe che implementa il servizio ed esporla verso l’esterno. La seconda modalità, contract-last, prevede invece la scrittura di un file xml, chiamato web-service deployment descriptor (WSDD), che elenca al suo interno quali metodi della classe saranno esposti all’esterno, eventuale autenticazione per l’utilizzo del servizio ed eventuale mapping di tipi complessi. Una semplice richiesta per utilizzare un servizio consiste nella scrittura di un client che, invoca opportunamente un web service attraverso l’utilizzo delle librerie di Axis. Il compito delle librerie è quello di tradurre la richiesta in stream xml, il quale viene inviato al server utilizzando il protocollo HTTP come protocollo di trasporto. Lato server, lo stream xml viene ricevuto, tradotto e quindi eseguito il metodo. Dall’altra parte, l’eventuale risultato viene tradotto anch’esso in flusso XML e quindi inviato al client, che si occuperà di tradurlo. Vediamo di seguito come configurare Axis per fare in modo di eseguire in semplice web service, sia lato client che lato server. Bisogna innanzitutto scaricare axis da qui. Dopo aver decompresso il pacchetto, è possibile testare le applicazioni di default che sono contenuto all’interno, copiando il contenuto della directory webapps all’interno della directory webapps del vostro servlet-container preferito, nell’esempio consideriamo che sia tomcat, versione 5.5.25. Quindi collegandoci all’indirizzo http://localhost:8080/axis, potrete vedere in cosa consiste l’applicazione, che mostra anche un esempio di file WSDD. Per poter testare un web service invocandolo, è necessario perfezionare qualche dettaglio dell’installazione. Definiamo alcune variabili d’ambiente che memorizzano i path di installazione delle librerie di axis:

1
2
3
4
5
6
set AXIS_HOME=/opt/axis
set AXIS_LIB=$AXIS_HOME/lib
set AXISCLASSPATH=$AXIS_LIB/axis.jar:$AXIS_LIB/commons-discovery.jar:
$AXIS_LIB/commons-logging.jar:$AXIS_LIB/jaxrpc.jar:$AXIS_LIB/saaj.jar:
$AXIS_LIB/log4j-1.2.8.jar:$AXIS_LIB/xml-apis.jar:$AXIS_LIB/xercesImpl.jar
export AXIS_HOME; export AXIS_LIB; export AXISCLASSPATH

E’ chiaro dall’esempio che il sistema operativo di riferimento per i test è linux. Quindi definiamo le variabili d’ambiente fissandole sui valori dei path di axis, considerando di averle installate in /opt/axis. Fatto ciò, selezioniamo un esempio, dalla directory samples di axis, axis/samples/stock e prendiamo il descrittore del servizio per effettuare il deploy. Per effettuare il deploy sarà necessario invocare il web service Administrator integrato in Axis che realizza il deploy in Axis-engine del descrittore selezionato. Supponendo di posizionarci nella directory dove riesiede il file deploy.wsdd, l’invocazione per effettuare il deploy è la seguente:

1
2
java -cp $AXISCLASSPATH org.apache.axis.client.AdminClient
-lhttp://localhost:8080/axis/services/AdminService deploy.wsdd

se ottenete un errore del tipo ClassNotFoundException, probabilmente avete dei problemi con la definizione delle varibili d’ambiente per axis, quindi verificare che sia corretta. Se siete stati spinti dalla curiosità, probabilmente avrete dato un occhiata al contenuto del file wsdd, e quindi avrete intuito che viene richiesta l’autenticazione per accedere al servizio. Volendo testare a questo punto il web service, quello che dobbiamo fare è:

1
2
java -cp $AXISCLASSPATH samples.stock.GetQuote
-lhttp://localhost:8080/axis/servlet/AxisServlet -uuser1 -wpass1 XXX

che invoca il servizio chiamato GetQuote passando come parametro XXX in aggiunta a user e password. Il risultato che otteniamo, se tutto procede correttamente, è: "55.25". Quindi abbiamo visto come fare il deploy di un web service all’interno dell’engine di axis e come invocarlo da linea di comando. Vediamo ora, più o meno sinteticamente, come creare rispettivamente il lato server e quello client di un web service. Il nostro ambiente di sviluppo sarà eclipse J2EE, all’interno del quale cominceremo con lo sviluppare una "Dynamic web application" chiamata devmeWS. La nostra applicazione sarà veramente semplice ed elencherà i post presenti sul sito. Definiamo la struttura del nostro value object, ovvero del bean che memorizzerà le informazioni sui post, molto semplicemente si ha:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class PostVO implements Serializable {
    private String titolo;
    private String descrizione;
    public PostVO() {}
 
    public int getTitolo() {
        return titolo;
    }
 
    public void setTitolo(String titolo) {
        this.titolo = titolo;
    }
 
    public int getDescrizione() {
        return descrizionr;
    }
 
    public void setDescrizione(String descrizione) {
        this.descrizione = descrizione;
    }
}

Il nostro VO ha 2 campi che sono il titolo e la descrizione del post, con i relativi getter e setter. La logica banale del nostro web service è la seguente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CatalogoWS {
 
    public CatalogoWS() { }
 
    public Vector getListaPost() {
        Vector lista = new Vector();
        // post1
        PostVO post1 = new PostVO();
        post1.setTitolo("Spring Web Flow, DWR: perfect combination !");
        post1.setDescrizione("L'integrazione delle tecnologie più diffuse....");
        // post2
        PostVO post2 = new PostVO();
        post2.setTitolo("Il grosso grasso JAR....");
        post2.setDescrizione("Come si esegue un ricco jar");
        return lista;
     }
// web service descriptor deployment
<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns="http://xml.apache.org/axis/wsdd/"
            xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
    <service name="urn:catalogoWS" provider="java:RPC" style="rpc" use="  encoded">
        <parameter name="className" value="it.devme.ws.CatalogoWS"/>
        <parameter name="allowedMethods" value="getListaPost"/>
        <parameter name="scope" value="Session"/>
        <beanMapping qname="myNS:PostVO" xmlns:myNS="urn:catalogoWS"  languageSpecificType="java:it.devme.vo.ProdottoVO"/>
    </service>
</deployment>

Sostanzialmente il web service, costruisce una lista di oggetto Post e la restituisce al client che ne visualizzerà il contenuto. Dobbiamo costruire a mano il nostro wsdd, anche questo molto banale che definisce il nostro web service, il riferimento alla classe che lo implementa ed il/i metodi che sono esposti verso l’esterno. Osserviamo che abbiamo definito anche un nostro tipo di dato custom, PostVO mappato su un namespace di esempio chiamato myNS. La definizione di tale tipo permette di poter serializzare/deserializzare il risultato fornito dal server o la richiesta inviata dal client, in xml, in modo tale da poter essere letto o scritto all’occorrenza. Più semplicemente, quando un web service invia un risultato, se questi è di un qualche tipo primitivo, viene serializzato in un tag xml che lo contiene. Se il risultato è di tipo più complesso, ad esempio PostVO, dovrà anch’esso essere serializzato in una qualche forma tale per cui sarà poi possibile la de-serializzazione client-side. Queste 2 operazioni, serializzazione/deserializzazione, sono possibili grazie al mapping nel file descrittore dei tipi predefiniti. Il client si basa anch’esso sulle librerie di axis, che permettono di invocare il metodo remoto e manipolare il risultato.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Client {
 
    public static void main(String[] args) {
        try {
            Call call = (Call) new Service().createCall();
            call.setTargetEndpointAddress(new URL("http://localhost:8080/productWS/services"));
            QName qnameProd = new QName("urn:catalogoWS", "PostVO");
            Class classeProd = PostVO.class;
            call.registerTypeMapping(classeProd, qnameProd, BeanSerializerFactory.class,
            //richiamo il metodo getListaPost
            call.setOperationName(new QName("urn:catalogoWS", "getListaPost"));
            Object rispostaWS = call.invoke(new Object[]{});
            Vector lista = (Vector) rispostaWS;
            System.out.println("Il catalogo comprende:");
            Iterator iter = lista.iterator();
            PostVO item = null;
            while (iter.hasNext()) {
                item = (PostVO) iter.next();
                visualizza(item);
            }
        } catch (Exception ex) {
            System.out.println("Si &egrave; verificata l'eccezione: " + ex.toString());
        }
    }
}

Il client è un’applicazione classe Java semplice che usa le librerie di axis per effettuare l’invocazione del metodo remoto. Viene fissato il tipo di dato custom e mappato sulla relativa classe che lo implementa, che ovviamente, è necessaria anche sul lato client. Viene impostato il nome del metodo da invocare, come nome dell’operazione da svolgere ed infine viene effettuata l’invocazione, la quale se non ci sono errori, restituisce un vettore di oggetti Post che vengono visualizzati all’interno del ciclo seguente la chiamata. Da osservare una cosa molto importante, e cioè che affinché tutto funzioni, sarà necessario installare il servizio creato, all’interno di axis engine che provvederà quindi a renderlo disponibile.

1
2
java -cp $AXISCLASSPATH org.apache.axis.client.AdminClient
-lhttp://localhost:8080/axis/services/AdminService deploy.wsdd

Spero di essermi ricordato tutto correttamente e senza errori, era da un pò di tempo che volevo pubblicare un post del genere, ma solo oggi sono riuscito a ritagliare in tempo per farlo. Sono andato a mente spero di non aver ricordato male. Qui trovate l’esempio di cui sopra, client e server implementati. Buone vacanze.

Array & Java Reflection

In questo periodo si lavora abbastanza…si programma come al solito usando Java…il mio linguaggio di programmazione di riferimento. Ho avuto a che fare con la riflessione, approcci comuni quali richiamare dei metodi su oggetti, prelevarne i valori delle variabili membro, etc…il tutto ovviamente fatto a run-time. Ho letto un piccolo hint su come recuperare l’istanza di una classe di un Array usando la riflessione in Java. Ricordiamo come si recupera l’istanza di una classe usando la riflessione:

  • Class.forName("it.devme.MYClass"), per ottenere l’istanza dal nome della classe
  • MYClass.class, per ottenere l’istanza dal tipo
  • Integer.TYPE, per ottenre l’istanza da qualunque tipo primitivo

E nel caso di un array ??
Come suggerito nell’articolo, un array è anch’esso un oggetto e quindi possiede una classe, ma con i metodi suggeriti sopra non è possibile ottenerne l’istanza a runtime. Un modo per fare ciò è il seguente:

  • Class.forName("[C"), per un array di caratteri. Si specifica come nome [C che corrisponde al nome della rappresentazione data dal linguaggio agli array di caratteri. Se vi capita di fare debug con un IDE a caso, ad esempio eclipse, vi accorgerete che la rappresentazione di un oggetto array di caratteri data dal linguaggio Java inizia con [C
  • Class.forName("[Ljava.lang.String;"), per un array di stringhe.

L'articolo si conclude con un suggerimento dato da un guru di Java, Tim Eck, che suggerisce di usare semplicemente l'istruzione:

char[].class

per ottenere l’istanza di una classe di array di caratteri.

Il grosso grasso JAR….

jarSalve…non sono morto, è che sto lavorando abbastanza e quindi trovare il tempo per parlare di cose interessanti, anche se mi piace molto, è difficile !!! ma poi con chi sto parlando visto che siete sempre così pochi ?? [crisi esistenziale]. Ciò detto, volevo rendervi partecipe di questa mia piccola scoperta…che poi scoperta in realtà non è, ma dal momento che a lavorare ci si imbatte nei problemi, questa volta la soluzione volevo condividerla…sai mai che a qualcuno possa tornare utile. Dunque il problema è il seguente: creare un jar contenente al suo interno altri jar, ovvero le librerie, che possa funzionare senza dover specificare nulla nel classpath.
Sostanzialmente, dato il jar devme.jar voglio lanciarlo usando il comando:

1
 java -jar devme.jar

senza preoccuparmi di altro.
Cercando su gooogle ho trovato un articolo della IBM che mi ha illuminato sul fare alcuni esperimenti, e quindi sul risolvere il problema…vediamo assieme.
Il classloader di java, per gli amici sun.misc.Launcher$AppClassLoader, che viene richiamato al lancio del comando java -jar, è a conocenza di 2 cose: 

  • Carica classi/risorse che compaiono nella root del JAR.
  • Carica classi/risorse che compaione nell’attributo Class-Path del file MANIFEST.MF.

Inoltre, ignora qualunque valore della variabile d’ambiente CLASSPATH o argomento fornito da riga di comando -cp, usato per specificare il classpath. Dulcis in fundo, si fa per dire, non sa come caricare classi/risorse all’interno di JAR presenti all’interno del jar da eseguire. Per cominciare creaimo un singolo JAR, che sarà il nostro eseguibile e che quindi chiamiamo main.jar. Supponiamo di avere una classe entry-point it.devme.main.Main e assumiamo che dipenda da 2 classi: it.devme.a.A all’interno del jar a.jar e it.devme.b.B all’interno del jar b.jar.

main.jar | it/devme/main/Main.class | it/devme/a/A.class | it/devme/b/B.class

Questo approccio ha delle limitazioni tali da suggerire l’utilizzo di un altro metodo. una di queste è che l’informazioni sulla provenienza originale della classi A.class e B.class viene persa. Un altra più importante è la seguente:
se a.jar e b.jar contengono una risorsa con lo stesso nome, quale scelgo? Cambiamo strada. Un altro approccio è quello di modificare il MANIFEST.MF a manoni, cercando di comporre quello di main.jar in modo che avesse visibiltà degli altri jar. Ma l’unica cosa che si riesce a fare è quella di porli nel filesystem a fianco di main.jar che è esattamente la cosa che si voleva evitare.

Per tagliare la testa al toro, il suggerimento dato è quello di scrivere un class loader personalizzato, in modo da caricare le classi che servono dall’interno di un JAR. Si tenga presente che scrivere un class loader personalizzato non è un operazione da prendere alla leggera, dal momento che questa ha un impatto molto profondo con il resto dell’applicazione, dal momento che si preoccupa di caricare le classi e di rispondere agli errori quando questi si verificano. Il concetto di class loader va oltre lo scopo di questo post, per cui ulteriori dettagli non verranno trattati. Tenendo presente la struttura del nostro jar:

one-jar.jar | META-INF/MANIFEST.MF | main/main.jar | lib/a.jar | lib/b.jar

proviamo a scrivere il nostro class-loader. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
com/simontuffs/onejar/JarClassLoader.java
protected URL findResource(String $resource) {
    try {
        // resolve($resource) returns the name of a
        // resource in the
        // byteCode Map if it is known to this
        // classloader.
        String resource = resolve($resource);
        if (resource != null) {
            // We know how to handle it.
            return new URL(Handler.PROTOCOL + ":" + resource);
        }
        return null;
    } catch (MalformedURLException mux) {
        WARNING("unable to locate " + $resource);
    }
    return null;
}

Si noti subito che per il recupero di una classe si utilizza un URL con rispettivo protocollo che permette di identificare una risorsa. Il protocollo in questo caso è un protocollo custom, che comincia con il prefisso onejar:. Di seguito abbiamo l’handler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
com/simontuffs/onejar/Handler.java
package com.simontuffs.onejar;
...
public class Handler extends URLStreamHandler {
/**
* This protocol name must match the
* name of the package in which this class
* lives.
*/
   public static String PROTOCOL = "onejar";
   protected int len = PROTOCOL.length()+1;
   protected URLConnection openConnection(URL u) throws IOException {
       final String resource = u.toString().substring(len);
       return new URLConnection(u) {
 
           public void connect() {}
 
           public InputStream getInputStream() {
               // Use the Boot classloader to get the resource.
               // is only one per one-jar.
               JarClassLoader cl = Boot.getClassLoader();
               return cl.getByteStream(resource);
           }
       };
   }
}

Il nostro class loader andrà inserito nel MANIFEST.MF/Main-Class attribute. Verrà creato un nuovo bootstrap della main class, com.simontuffs.onejar.Boot, la quale è specificata come Main-Class attribute. La nuova classe creerà una nuova istanza del JarClassLoader userà il nuovo loader, per caricare it.devme.main.Main.class usando la riflessione per invocare il metodo main(). Finito di leggere l’articolo IBM che è possibile trovare qui, o forse nel mentre della lettura non ricordo, ho notato l’indirizzo di questo meraviglioso, risolvi problemi, plugin per eclipse….che realizza né più né meno la tecnica sopra descritta per produrre un jar eseguibile con l’accesso a librerie al suo interno. Il plugin lo trovate qui…è ancora una pre-release alpha, ma fino ad ora non ho avuto alcun problema nell’utilizzo.

JavaFX…installa e via !!!

Come argomento del periodo, oltre a Symbian, mi piace trattare quello di JavaFX…il nuovo linguaggio che permette di creare applicazioni in modo rapido..prendendo spunto da un articolo trovato in rete, ho voluto testare alcuni componenti che permettono lo sviluppo di applicazioni JavaFX. In particolare, mi riferisco all’installazione del plugin di JavaFX per eclipse e il test di un semplicissimo programmino. La versione di eclipse è la 3.2.2 -attualmente esiste una versione più recente che è Europa-, e il JDK è 1.5. Si parte. Dal menù Help->Software Updates->Manage Configurations aggiungete una nuova entry per il download del plugin, e come indirizzo usate questo:
http://download.java.net/general/openjfx/plugins/eclipse/site.xml

eclipse1

 

Selezionato il nodo JavaFx e procedete con l’installazione.

 

eclipse2

 

Al termine del download installate tutto e terminate.

 

eclipse4

 

Benissimo, a questo punto cominciamo il lavoro divertente. Creiamo un nuovo progetto Java (Java Project) che non ha niente a che fare con JavaFX, almeno per ora. Anche perchè, se guardate bene, non riuscirete a trovare alcun progetto JavaFX nel menù di creazione dei nuovi progetti. Fatto questo, tasto destro sul nome del progetto, New->Others e selezionato JavaFX File.

 

eclipse6

 

Scegliamo il nome del file, ad esempio devme.fx, e notiamo che il file appena creato ci viene aperto in edit-mode. Da qui possiamo scrivere il nostro codice, io ad esempio, ho costruito un piccolo programmino che crea un Frame dentro il quale compare un messaggio, come segue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import javafx.ui.*;
    Frame {
        title: "Hello World - DevME - "
        width: 300
        height: 100
        content: Box {
            content:
            [Label {
                text: "Hello World - DevMe"
                toolTipText: "Tool tip"
                font: Font {
                    size: 18
                }
                border: EmptyBorder {
                    top: 10
                    left: 10
                }
                background: Color {
                    blue: 255
                    green: 255
                    red: 255
                }
            }]
        }
        visible: true
    }

Dopo di che, possiamo eseguire la nostra applicazione per vedere se fa quello che desideriamo. Per eseguire un’applicazione JavaFX dovremmo creare una nuova Launch Configuration dal menù Run di eclipse, in particolare sarà un nuovo RUN JavaFX Application.

 

eclipse7

 

Nella dialog specifichiamo il nome del progetto nel tab Main e non dimentichiamoci di aggiungere nel tab Argument, il nome del file contenente il codice FX, nel nostro caso devme (senza estensione).

 

eclipse11

 

L’aggiunta dell’argomento da passare al programma è necessaria dal momento che la configurazione creata possiede come Main Class la shell di FX e questo implica che per ogni file da eseguire si dovrà creare una configurazione di run ad-hoc. Ed infine ecco quello che otteniamo se lanciamo il programma.

 

eclipse10

 

Il plugin che ho testato non fa una piega, funziona perfettamente. L’unica cosa che il context-assist non è così performante come funziona in Java classic, e magari il fatto di dover creare una run configruation per ogni file del progetto potrebbe essere una seccatura. L’articolo che ho letto parlava di NetBeans come IDE di riferimento, e apparentemente si riusciva a fare tutto quello che abbiamo fatto noi nel nostro esempio.

Overloading del costruttore – REVIEWED -

Eh sì, devo proprio dirlo. Ho sbagliato !!! Non che non mi capiti mai, ma questa volta l’ho anche scritto sul mio blog, quindi DEVO riparare e salvare quello che ancora rimane della mia reputazione. L’errore è stato nel post in cui si parlava dell’overloading del costruttore, rimetto di seguito il giochino che avevo pubblicato:

1
2
3
4
5
6
7
8
public class OverloadResolver {
    public OverloadResolver(Object param) {
        System.out.println("Construttore con parametro Object");
    }
    public OverloadResolver(Object[] param) {
        System.out.println("Costruttore con parametro Object[]");
    }
}

La domanda era: "Qualcuno sa cosa succede se faccio questa chiamata:"

1
2
3
4
5
......
public static void main(String[] args) {
    OverloadResolver or = new OverloadResolver(null);
}
......

Ingenuamente mi son fidato. Nel senso che ho considerato buona la risposta che ho trovato nell’articolo che ho letto in internet, (in realtà volevo anche rispondere ora che so la risposta corretta, ma non riesco più a trovarlo, poi vi dico se lo trovo) e quindi non mi sono posto il problema di testarlo. Quando ieri, un caro vecchio amico, mi ha fatto gentilmente notare, e per gentilmente intendo a modo suo, che la risposta da me data non era quella giusta, in quanto diffidando dalla risposta ha testato l’esempio, e il risultato non era quello che avevo dato. Quindi, eseguendo l’esempio, il risultato è che viene richiamata la funzione:

1
2
3
public OverloadResolver(Object[] param) {
    System.out.println("Costruttore con parametro Object[]");
}

Perchè ? Il motivo lo incollo, così come l’ho trovato in internet, ovviamente sul sito della sun:

So the third rule is to choose the most "specific" method. The rule is: if any method still under consideration has parameter types that are assignable to another method that’s also still in play, then the other method is removed from consideration. This process is repeated until no other method can be eliminated. If the result is a single "most specific" method, then that method is called. If there’s more than one method left, the call is ambiguous. Suppose that you have the methods: f(float) f(double) In this case, the parameter types for the first method are assignable to the parameter types of the second method, that is, it’s legal to say: double = float through a widening primitive conversion. By contrast, saying: float = double is not valid without a cast. Based on this third rule, f(double) is removed from the set of possible methods to call, and therefore f(float) is called.

Morale della favola? La prossima volta non fidatevi degli articoli trovati in rete senza prima averli provato. Ciauu

 

You need to log in to vote

The blog owner requires users to be logged in to be able to vote for this post.

Alternatively, if you do not have an account yet you can create one here.

Powered by Vote It Up

WordPress Themes