Padrão arquitetural para acesso a informação
Protocolo cliente/servidor stateless baseado em HTTP.
O framework Jersey (JAX-RS API) permite a criação de serviços REST de forma simples usando a linguagem Java.
A abordagem consiste em adicionar anotações ao código Java (eg., @PATH, @GET, @POST, etc.).
Através da capacidade de reflexão do Java, o runtime Jersey (JAX-RS API), com base nessas anotações, produz automaticamente código servidor para as operações em questão.
O framework Jersey (JAX-RS API) requer diversas bibliotecas (jars) para o seu funcionamento.
Num projecto Java, a gestão manual dessas dependências não é trivial. Uma solução consiste em utilizar uma ferramenta como o Maven.
O Maven será a solução adoptada nesta disciplina para o efeito. Será utilizada nos materiais fornecidos para apoio às aulas práticas e desenvolvimento do projeto.
Um projecto Maven tem como base um ficheiro pom.xml que, além das dependências do projeto, também pode especificar as fases de compilação, empacotamento, instalação, etc., dos artefactos do projeto.
%%classpath add mvn
com.google.code.gson gson 2.2.4
org.glassfish.jersey.core jersey-common 2.25.1
org.glassfish.jersey.core jersey-client 2.25.1
org.glassfish.jersey.core jersey-server 2.25.1
org.glassfish.jersey.media jersey-media-json-jackson 2.25.1
org.glassfish.jersey.containers jersey-container-jdk-http 2.25.1
Pretende-se um serviço para armazenar dados, acessível através de uma interface REST.
As operações a disponibilizar são:
Cada unidade dos dados será um recurso distinto, acessível a partir de um URL, eg. http://<ip>:<port>/some-path/<id>
A interface do serviço será modelada usando as anotações Jersey (JAX-RS API), por exemplo, numa interface Java.
package api;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
@Path("/some-path")
public interface DataResources {
@POST
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
String store(byte[] data);
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
byte[] download(@PathParam("id") String id);
@PUT
@Path("/{id}")
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
void replace(@PathParam("id") String id, byte[] contents);
@DELETE
@Path("/{id}")
void delete(@PathParam("id") String id);
}
Haverá uma classe com as operações sobre os recursos indicadas na interface do serviço.
Além da lógica aplicacional inerente ao serviço, os métodos deverão incluir a deteção e sinalização de erros. Os erros são assinalados através respondendo ao pedido com um status code HTTP apropriado, (eg., 404 Not Found, quando se acede a um recurso não existe).
package impl;
import java.util.*;
import java.util.concurrent.*;
import javax.ws.rs.*;
import javax.ws.rs.core.Response.*;
public class DataResources implements api.DataResources {
private static final int ISIZE = 128;
private final Map<String, byte[]> storage;
public DataResources() {
this.storage = new ConcurrentHashMap<>(ISIZE);
}
public String store(byte[] data) {
String id = Long.toString( System.nanoTime(), 32);
if( storage.putIfAbsent(id, data) != null)
throw new WebApplicationException( Status.CONFLICT );
else
return id;
}
...
public byte[] download(String id) {
byte[] data = storage.get(id);
if( data == null )
throw new WebApplicationException( Status.NOT_FOUND );
else
return data;
}
public void replace(String id, byte[] data) {
if( storage.replace( id, data ) == null ) {
throw new WebApplicationException(Status.NOT_FOUND );
}
}
public void delete(String id) {
if( storage.remove( id ) == null ) {
throw new WebApplicationException(Status.NOT_FOUND );
}
}
}
A classe que implementa a interface não precisa de repetir as anotações.
O construtor pode receber parâmetros caso seja necessário.
import java.net.*;
import org.glassfish.jersey.server.*;
import org.glassfish.jersey.jdkhttp.*;
String URI_BASE = "http://0.0.0.0:9999/v1/";
ResourceConfig config = new ResourceConfig();
config.register( new impl.DataResources() );
JdkHttpServerFactory.createHttpServer( URI.create(URI_BASE), config);
System.err.println("Server ready....");
As operações @GET podem ser invocadas simplesmente apontando um browser para o URI do recurso.
Para as demais operações, @POST, @PUT e @DELETE pode-se continuar a usar o browser instalando uma extensão, como a </> RESTed.
É possível invocar um serviço REST a partir de Java, por recurso à vertente cliente das bibliotecas Jersey (JAX-RS API), como nos exemplos seguintes...
import java.net.URI;
import javax.ws.rs.core.*;
import javax.ws.rs.client.*;
import org.glassfish.jersey.client.*;
ClientConfig config = new ClientConfig();
Client client = ClientBuilder.newClient(config);
URI baseURI = UriBuilder.fromUri("http://localhost:9999/v1").build();
WebTarget target = client.target( baseURI );
Response response = target.path("/some-path/")
.request()
.post( Entity.entity( new byte[1024], MediaType.APPLICATION_OCTET_STREAM));
if( response.hasEntity() ) {
String id = response.readEntity(String.class);
System.out.println( "data resource id: " + id );
} else
System.err.println( response.getStatus() );
import java.net.URI;
import javax.ws.rs.core.*;
import javax.ws.rs.client.*;
import org.glassfish.jersey.client.*;
ClientConfig config = new ClientConfig();
Client client = ClientBuilder.newClient(config);
URI baseURI = UriBuilder.fromUri("http://localhost:9999/v1").build();
WebTarget target = client.target( baseURI );
Response response = target.path("/some-path/28s0pkung0")
.request()
.get();
if( response.hasEntity() ) {
byte[] data = response.readEntity(byte[].class);
System.out.println( "data resource length: " + data.length );
} else
System.err.println( response.getStatus() );
import java.net.URI;
import javax.ws.rs.core.*;
import javax.ws.rs.client.*;
import javax.ws.rs.core.Response.*;
import org.glassfish.jersey.client.*;
ClientConfig config = new ClientConfig();
Client client = ClientBuilder.newClient(config);
URI baseURI = UriBuilder.fromUri("http://localhost:9999/v1").build();
WebTarget target = client.target( baseURI );
Response response = target.path("/some-path/2486eo2pl4")
.request()
.delete();
if( response.getStatusInfo() == Status.OK ) {
System.out.println( "deleted data resource...");
} else
System.err.println( response.getStatus() );
import java.net.URI;
import javax.ws.rs.core.*;
import javax.ws.rs.client.*;
import javax.ws.rs.core.Response.*;
import org.glassfish.jersey.client.*;
ClientConfig config = new ClientConfig();
Client client = ClientBuilder.newClient(config);
URI baseURI = UriBuilder.fromUri("http://localhost:9999/v1").build();
WebTarget target = client.target( baseURI );
Response response = target.path("/some-path/24cusirnv9")
.request()
.put( Entity.entity( new byte[2048], MediaType.APPLICATION_OCTET_STREAM));
if( response.getStatusInfo() == Status.OK ) {
System.out.println( "updated data resource...");
} else
System.err.println( response.getStatus() );