Loading ...

Creare un web service REST in Java con JAX-WS

Creare un web service REST in Java con JAX-WS

Per una delle aziende per cui lavoro, ho creato un REST web service in Java, che si connette al database Oracle, ed espone i dati in formato JSON.

Partendo da questo esempio reale, vi mostro come creare un web service REST in Java, usando JAX-WS!

Questo è quello che useremo nel progetto:

  • Maven -> per gestire il progetto e le dipendenze
  • Grizzly -> come HTTP server
  • Jersey -> framework open source per la creazione di servizi RESTful in Java
  • OJDBC -> per la connessione al db Oracle; se avete un altro tipo di db, potete usare JDBC o un altro driver appropriato

Quindi, una volta creato il progetto con Maven, aggiungete questo al vostro pom.xml:

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.glassfish.jersey</groupId>
                <artifactId>jersey-bom</artifactId>
                <version>${jersey.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-grizzly2-http</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-jackson</artifactId>
        </dependency>
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>11.2.0.3</version>
        </dependency>
    </dependencies>

    <repositories>
        <repository>
            <id>codelds</id>
            <url>https://code.lds.org/nexus/content/groups/main-repo</url>
        </repository>
    </repositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <inherited>true</inherited>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>com.cimoda.rest.Main</mainClass>
                </configuration>
            </plugin>
            
        </plugins>
    </build>
    <properties>
        <jersey.version>2.17</jersey.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

Andiamo a descrivere la struttura del progetto (che voi potete ovviamente modificare come volete); io ho quattro package:

  • com.cimoda.pojo -> qui tengo tutte le classi POJO
  • com.cimoda.queries -> qui tengo le classi per la connessione al database e l'esecuzione delle query
  • com.cimoda.service -> qui ci sono le varie classi che identificano gli endpoint da interrogare (ad esempio http://INDIRIZZO_IP/api/estrazione_1)
  • com.cimoda.rest -> contiene il main e altre varie classi di utilità

Partiamo da una classe POJO, cioè dalla cosa più semplice:

package com.cimoda.pojo;

public class Zona {

    private String zona;
    private String descrizione;

    public String getZona() {
        return zona;
    }

    public void setZona(String zona) {
        this.zona = zona;
    }

    public String getDescrizione() {
        return descrizione;
    }

    public void setDescrizione(String descrizione) {
        this.descrizione = descrizione;
    }

}

Nulla di particolarmente complesso, quindi sorvolerei sulle spiegazioni.

Passiamo invece alla relativa query:

package com.cimoda.queries;

import com.cimoda.pojo.Zona;
import com.cimoda.rest.DBManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import org.glassfish.jersey.server.internal.process.MappableException;

public class ZonaQueries {

    public ArrayList<Zona> getAll() throws ClassNotFoundException, SQLException, MappableException {
        ArrayList<Zona> list = new ArrayList<>();
        String query = "SELECT ZONA, DESCR32 FROM ZONA";
        Connection conn = DBManager.getInstance().getConnection();
        try (PreparedStatement pstmt = conn.prepareStatement(query)) {
            ResultSet rs = pstmt.executeQuery();
            while (rs.next()) {
                Zona zona = new Zona();
                zona.setZona(rs.getString("ZONA"));
                zona.setDescrizione(rs.getString("DESCR32"));
                list.add(zona);
            }
        }
        return list;
    }
}

La classe DBManager la vedremo in seguito; sostanzialmente è una classe Singleton che si occupa della connessione al db.

Comunque, qui eseguiamo una query ed andiamo a riempire un ArrayList di oggetti Zona.

Il relativo endpoint sarà una cosa del genere:

package com.cimoda.service;

import com.cimoda.pojo.Zona;
import com.cimoda.queries.ZonaQueries;
import com.cimoda.rest.JsonError;
import com.cimoda.rest.NotFoundException;
import java.sql.SQLException;
import java.util.ArrayList;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("zona")
public class ZonaService {

    @GET
    @Path("/zone/")
    @Produces(MediaType.APPLICATION_JSON)
    public ArrayList getAll() {
        ZonaQueries query = new ZonaQueries();
        ArrayList<Zona> list = null;
        try {
            list = query.getAll();
        } catch (ClassNotFoundException | SQLException ex) {
            throw new NotFoundException(new JsonError("Errore", ex.getMessage()));
        }
        return list;

        // http://192.168.1.30:8080/cr/zona/zone/ --> INDIRIZZO DA INTERROGARE
    }
}

L'indirizzo da interrogare, commentato, dipenderà dalle impostazioni messe nel main (che vedremo dopo).

Anche JsonError è una classe che vedremo in seguito, insieme a NotFoundException.

Qui non passiamo parametri al path, ed indichiamo di produrre un JSON.

Se avessimo dovuto passare un parametro tramite query-string, avremmo fatto una cosa del genere:

package com.cimoda.service;

@Path("zona")
public class ZonaService {

    @GET
    @Path("/zone/{VALORE}")
    @Produces(MediaType.APPLICATION_JSON)
    public ArrayList getAll(@PathParam("VALORE") String from) {
        ZonaQueries query = new ZonaQueries();
        ArrayList<Zona> list = null;
        try {
            list = query.getAll();
        } catch (ClassNotFoundException | SQLException ex) {
            throw new NotFoundException(new JsonError("Errore", ex.getMessage()));
        }
        return list;

        // http://192.168.1.30:8080/cr/zona/zone/VALORE
    }
}

Vi ho modificato anche l'url commentato.

Bene, siamo quasi arrivati; ci mancano quattro classi usate precedentemente, e stanno tutte nel package com.cimoda.rest.

Cominciamo con la connessione al db:

package com.cimoda.rest;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBManager {

    private static DBManager instance = null;
    private static Connection conn = null;

    private DBManager() {
    }

    public static DBManager getInstance() {
        return (instance == null) ? (instance = new DBManager()) : instance;
    }

    public Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName("oracle.jdbc.driver.OracleDriver");
        conn = DriverManager.getConnection("jdbc:oracle:thin:@INDIRIZZO:1521:DB_NAME", "USER", "PASSWORD");
        return conn;
    }
}

Ripeto, se usate un altro database, vi basta usare il driver e la stringa di connessione appropriate; le modifiche non sono tante.

Questa la classe relativa a NotFoundException:

package com.cimoda.rest;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

public class NotFoundException extends WebApplicationException {

    /**
     * Create a HTTP 404 (Not Found) exception.
     */
    public NotFoundException() {
        super(Response.status(Response.Status.NOT_FOUND).
type(MediaType.TEXT_PLAIN).build());
    }

    /**
     * Create a HTTP 404 (Not Found) exception.
     *
     * @param message the String that is the entity of the 404 response.
     */
    public NotFoundException(String message) {
        super(Response.status(Response.Status.NOT_FOUND).
                entity(message).type(MediaType.TEXT_PLAIN).build());
    }

    public NotFoundException(JsonError jse) {
        super(Response.status(Response.Status.NOT_FOUND).
                entity(jse).type(MediaType.APPLICATION_JSON).build());
    }

}

Classe generica per intercettare le eccezioni.

Questa invece la classe relativa agli errori JSON (che a volte possono indicare un'errata query):

package com.cimoda.rest;

public class JsonError {

    private String type;
    private String message;

    public JsonError(String type, String message) {
        this.type = type;
        this.message = message;
    }

    public String getType() {
        return this.type;
    }

    public String getMessage() {
        return this.message;
    }
}

Simile alle classi POJO in sostanza.

Infine il main:

package com.cimoda.rest;

import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import java.io.IOException;
import java.net.URI;

public class Main {

    public static final String BASE_URI_MIO = "http://192.168.1.30:8080/cr/";

    public static HttpServer startServer() {
        final ResourceConfig rc = new ResourceConfig().packages("com.cimoda.service");
        return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI_MIO), rc);
    }

    public static void main(String[] args) throws IOException {
        final HttpServer server = startServer();
        System.out.println(String.format("L'applicazione è in funzione sull'indirizzo %sapplication.wadlnDai invio per stopparla...", BASE_URI));
        System.in.read();
        server.shutdownNow();
    }
}

Qui avviamo Grizzly, impostando l'indirizzo di ascolto, e il package dove ci sono gli endpoint.

 

Abbiamo visto parecchie cose, e non mi sono soffermato più di tanto su ogni argomento; mi ci sarebbe servito un libro.

Inoltre sono partito dalla cosa più facile, anche se come studio vi consiglio di partire dal main (che è la prima cosa che scriverete in questo caso).

Di spunti ne avete parecchi, quindi divertitevi.

Enjoy!