This project is a simple example showing a customer chat app using JAX-RS 2.0 async client and server apis. Users can chat in secret if they desire.
This will start the web server
You must specify your first name you will use in the chat as well as a secret to use to encrypt the message. When this comes up, enter your messages.
You can also open up another window to have a private conversation.
ex15_2
|-- pom.xml
`-- src
`-- main
|-- java
| |-- ChatClient.java
| `-- com
| `-- restfully
| `-- shop
| |-- domain
| | `-- Customer.java
| `-- services
| |-- CustomerChat.java
| |-- CustomerResource.java
| `-- ShoppingApplication.java
`-- webapp
`-- WEB-INF
`-- web.xml
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>com.oreilly.rest.workbook</groupId>
<artifactId>jaxrs-2.0-workbook-pom</artifactId>
<version>1.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.oreilly.rest.workbook</groupId>
<artifactId>jaxrs-2.0-workbook-ex15_2</artifactId>
<version>2.0</version>
<packaging>war</packaging>
<name>ex15_2</name>
<description/>
<dependencies>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>3.0.12.Final-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<version>3.0.12.Final-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>async-http-servlet-3.0</artifactId>
<version>3.0.12.Final-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>jaxrs-api</artifactId>
<version>3.0.12.Final-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-servlet-initializer</artifactId>
<version>3.0.12.Final-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxb-provider</artifactId>
<version>3.0.12.Final-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>jose-jwt</artifactId>
<version>3.0.12.Final-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>ex15_2</finalName>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.0.6.v20130930</version>
<configuration>
<webApp>
<contextPath>/</contextPath>
</webApp>
<scanIntervalSeconds>10</scanIntervalSeconds>
<stopKey>foo</stopKey>
<stopPort>9999</stopPort>
<stopWait>1</stopWait>
</configuration>
<executions>
<execution>
<id>start-jetty</id>
<phase>pre-integration-test</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<scanIntervalSeconds>0</scanIntervalSeconds>
<daemon>true</daemon>
</configuration>
</execution>
<execution>
<id>stop-jetty</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>surefire-it</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<skip>false</skip>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
src/main/webapp/WEB-INF/web.xml
<?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">
</web-app>
src/main/java/ChatClient.java
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.jose.jwe.JWEBuilder;
import org.jboss.resteasy.jose.jwe.JWEInput;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.InvocationCallback;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.BufferedReader;
import java.io.InputStreamReader;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ChatClient
{
public static void main(String[] args) throws Exception
{
String name = args[0];
final String secret = args[1];
System.out.println();
System.out.println();
System.out.println();
System.out.println();
final Client client = new ResteasyClientBuilder()
.connectionPoolSize(3)
.build();
WebTarget target = client.target("http://localhost:8080/services/chat");
target.request().async().get(new InvocationCallback<Response>()
{
@Override
public void completed(Response response)
{
Link next = response.getLink("next");
String message = response.readEntity(String.class);
try
{
JWEInput encrypted = new JWEInput(message);
message = encrypted.decrypt(secret).readContent(String.class);
}
catch (Exception ignore)
{
//e.printStackTrace();
}
System.out.println();
System.out.print(message);
System.out.println();
System.out.print("> ");
client.target(next).request().async().get(this);
}
@Override
public void failed(Throwable throwable)
{
System.err.println("FAILURE!");
}
});
while (true)
{
System.out.print("> ");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String message = name + ": " + br.readLine();
String encrypted = new JWEBuilder().contentType(MediaType.TEXT_PLAIN_TYPE)
.content(message)
.dir(secret);
target.request().post(Entity.text(encrypted));
}
}
}
src/main/java/com/restfully/shop/domain/Customer.java
package com.restfully.shop.domain;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "customer")
public class Customer
{
private int id;
private String firstName;
private String lastName;
private String street;
private String city;
private String state;
private String zip;
private String country;
@XmlAttribute
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
@XmlElement(name = "first-name")
public String getFirstName()
{
return firstName;
}
public void setFirstName(String firstName)
{
this.firstName = firstName;
}
@XmlElement(name = "last-name")
public String getLastName()
{
return lastName;
}
public void setLastName(String lastName)
{
this.lastName = lastName;
}
@XmlElement
public String getStreet()
{
return street;
}
public void setStreet(String street)
{
this.street = street;
}
@XmlElement
public String getCity()
{
return city;
}
public void setCity(String city)
{
this.city = city;
}
@XmlElement
public String getState()
{
return state;
}
public void setState(String state)
{
this.state = state;
}
@XmlElement
public String getZip()
{
return zip;
}
public void setZip(String zip)
{
this.zip = zip;
}
@XmlElement
public String getCountry()
{
return country;
}
public void setCountry(String country)
{
this.country = country;
}
@Override
public String toString()
{
return "Customer{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", street='" + street + '\'' +
", city='" + city + '\'' +
", state='" + state + '\'' +
", zip='" + zip + '\'' +
", country='" + country + '\'' +
'}';
}
}
src/main/java/com/restfully/shop/services/CustomerResource.java
package com.restfully.shop.services;
import com.restfully.shop.domain.Customer;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@Path("/customers")
public class CustomerResource
{
private Map<Integer, Customer> customerDB = new ConcurrentHashMap<Integer, Customer>();
private AtomicInteger idCounter = new AtomicInteger();
public CustomerResource()
{
}
@POST
@Consumes("application/xml")
public Response createCustomer(Customer customer)
{
customer.setId(idCounter.incrementAndGet());
customerDB.put(customer.getId(), customer);
System.out.println("Created customer " + customer.getId());
return Response.created(URI.create("/customers/" + customer.getId())).build();
}
@GET
@Path("{id}")
@Produces("application/xml")
public Customer getCustomer(@PathParam("id") int id)
{
Customer customer = customerDB.get(id);
if (customer == null)
{
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return customer;
}
@PUT
@Path("{id}")
@Consumes("application/xml")
public void updateCustomer(@PathParam("id") int id, Customer update)
{
Customer current = customerDB.get(id);
if (current == null) throw new WebApplicationException(Response.Status.NOT_FOUND);
current.setFirstName(update.getFirstName());
current.setLastName(update.getLastName());
current.setStreet(update.getStreet());
current.setState(update.getState());
current.setZip(update.getZip());
current.setCountry(update.getCountry());
}
}
src/main/java/com/restfully/shop/services/CustomerChat.java
package com.restfully.shop.services;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@Path("chat")
public class CustomerChat
{
class Message
{
String id;
String message;
Message next;
}
protected AtomicLong counter = new AtomicLong(0);
protected int maxMessages = 100;
protected Message first;
protected Message last;
protected LinkedHashMap<String, Message> messages = new LinkedHashMap<String, Message>()
{
@Override
protected boolean removeEldestEntry(Map.Entry<String, Message> eldest)
{
boolean remove = size() > maxMessages;
if (remove) first = eldest.getValue().next;
return remove;
}
};
LinkedList<AsyncResponse> listeners = new LinkedList<AsyncResponse>();
ExecutorService writer = Executors.newSingleThreadExecutor();
@Context
protected UriInfo uriInfo;
@POST
@Consumes("text/plain")
public void post(final String text)
{
final UriBuilder base = uriInfo.getBaseUriBuilder();
writer.submit(new Runnable()
{
@Override
public void run()
{
synchronized (messages)
{
Message message = new Message();
message.id = Long.toString(counter.incrementAndGet());
message.message = text;
if (messages.size() == 0)
{
first = message;
}
else
{
last.next = message;
}
messages.put(message.id, message);
last = message;
for (AsyncResponse async : listeners)
{
try
{
send(base, async, message);
}
catch (Exception e)
{
e.printStackTrace();
}
}
listeners.clear();
}
}
});
}
@GET
public void get(@QueryParam("current") String next, @Suspended AsyncResponse async)
{
final UriBuilder base = uriInfo.getBaseUriBuilder();
Message message = null;
synchronized (messages)
{
Message current = messages.get(next);
if (current == null) message = first;
else message = current.next;
if (message == null) {
queue(async);
}
}
// do this outside of synchronized block to reduce lock hold time
if (message != null) send(base, async, message);
}
protected void queue(AsyncResponse async)
{
listeners.add(async);
}
protected void send(UriBuilder base, AsyncResponse async, Message message)
{
URI nextUri = base.clone().path(CustomerChat.class)
.queryParam("current", message.id).build();
Link next = Link.fromUri(nextUri).rel("next").build();
Response response = Response.ok(message.message, MediaType.TEXT_PLAIN_TYPE).links(next).build();
async.resume(response);
}
}
src/main/java/com/restfully/shop/services/ShoppingApplication.java
package com.restfully.shop.services;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;
@ApplicationPath("/services")
public class ShoppingApplication extends Application
{
private Set<Object> singletons = new HashSet<Object>();
public ShoppingApplication()
{
singletons.add(new CustomerResource());
singletons.add(new CustomerChat());
}
@Override
public Set<Object> getSingletons()
{
return singletons;
}
}