Restful services in Java – 360 degree of restful server

This is the blog final entry for a restful service web server.  I have attempted to create more than your typical “foo” “bar” type example program. My final blog on restful services will be a fairly complete service that could be used to provide price data supplied by several “banks” on various exchanges.

The data can be  pseudo randomly generated to be later retrieved and displayed by web browser using java script to fetch, extract and display the data from a JSON formatted string.

A real production server would save server data in a more permanent manner such as a database or even perhaps files if the data lent itself to that. I didn’t feel like creating database so I have skipped over this step by adding a restful service for generating some data which is stored in memory. This is just a “temporary” server that is available for testing and not for real permanence.

This example server has the following methods.

  • generate
  • count
  • (index).xml
  • (index).json
  • (index).text
  • fetch
  • addOne
  • retrieve
  • retrieveall

The generate method is perhaps the most important of these for testing. Simply call this restful service in the beginning to generate twenty items by default or pass in a specific count to generate that many test items.

The generated data is put into a simple hash with the key to the hash being the simple index.

To look at the data that is in the hash the count, (index).xml, (index).json or (index).text.

The fetch method is actually the other main method specifically for a web client. This web service will retrieve an array of market prices in JSON format.

In a few days I will be providing a html client that will use this server and retrieve the data in the JSON format and display that to the web page.

PriceServer.java

package com.acmesoft;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

import java.math.BigDecimal;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Iterator;

import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.WebApplicationException;

@Path("/price")
public class PriceServer
{
	// should really be a database
	private static HashMap<Integer,MarketPrice> pricedatabase = new HashMap<Integer,MarketPrice>();
	private static int databasecount = 0;

	//
	// index is the actual 0 - n-1 of the map.
	//
	private String rangeAsXML(int start, int stop)
	{
		String retval = "";
		retval += "";

		for (int idx = start; idx <= stop; idx++)
		{
			MarketPrice item = null;
			item = pricedatabase.get(idx);

			if (item != null)
				retval += item.xmlString();
		}

		retval += "";

		return retval;
	}

	//
	// index is the actual 0 - n-1 of the map.
	//
	private String rangeAsJSON(int start, int stop)
	{
		String retval = "{ \"records\": [";

		for (int idx = start; idx <= stop; idx++)
		{
			MarketPrice item = null;
			item = pricedatabase.get(idx);

			if (item != null)
			{
				if (idx != start)
					retval += ", ";
				retval += item.JSONString();
			}
		}

		retval += "] }";

		return retval;
	}

	// initialize the price cache, this is pretty much only for debugging
	// 
	// this will generate 20 items by default
	// http://localhost:8080/ThirdRestful/price/generate
	//
	// this will generate 100 items instead 
	// http://localhost:8080/ThirdRestful/price/generate?count=100
	@GET
	@Path("/generate")
	@Produces( {MediaType.APPLICATION_XML})
	public Response generate(@Context UriInfo ui, @DefaultValue("20") @QueryParam("count") int debugcount) throws URISyntaxException
	{
		String path = ui.getAbsolutePath().toString() ;
		URI uri = new URI(path);

		MarketPrice newprice = null;
		for (int idx = 0; idx < debugcount; idx++)
		{
			switch (idx % 5)
			{
			case 0:
				newprice = new MarketPrice("EUR/USD", "DBK", new BigDecimal(1.056), "DTB");
				break;
			case 1:
				newprice = new MarketPrice("GBP/EUR", "BCY", new BigDecimal(1.179), "LIFFE");
				break;
			case 2:
				newprice = new MarketPrice("USD/MXN", "C", new BigDecimal(19.1), "CME");
				break;
			case 3:
				newprice = new MarketPrice("EUR/USD", "0UB", new BigDecimal(1.059), "SOFFEX");
				break;
			case 4:
				newprice = new MarketPrice("VEF/USD", "JPM", new BigDecimal(0.1001), "NYSE");
				break;
			}
			newprice.setId(databasecount);
			pricedatabase.put(databasecount, newprice);
			databasecount++;
		}

		//return Response.created(uri).contentLocation(uri).status(Response.Status.OK).build(); 
		return Response.created(uri).contentLocation(uri).status(Response.Status.OK).entity("" + databasecount +"").build(); 
	}

	// get the count of items
	// 
	// http://localhost:8080/ThirdRestful/price/count
	@GET
	@Path("/count")
	@Produces( {MediaType.APPLICATION_XML})
	public Response count(@Context UriInfo ui) throws URISyntaxException
	{
		String retval = "" + databasecount + "";
		String path = ui.getAbsolutePath().toString() ;
		URI uri = new URI(path);

		return Response.created(uri).contentLocation(uri).status(Response.Status.OK).entity(retval).build();
	}


	// add a new price record
	// 
	// http://localhost:8181/ThirdRestful/price/addOne 
	@POST
	@Path("/addOne")
	@Produces( {MediaType.APPLICATION_XML})
	public Response addOne(	@Context UriInfo ui, MarketPrice item) throws URISyntaxException
	{
		String path = ui.getAbsolutePath().toString() ;
		URI uri = new URI(path);
		return Response.created(uri).contentLocation(uri).status(Response.Status.OK).build(); 

	}

	// returns a selection of records in xml format.
	// 
	// http://localhost:8080/ThirdRestful/price/retrieve
	@GET
	@Path("/retrieve")
	@Produces( {MediaType.APPLICATION_XML})
	public Response retrieve(	@Context UriInfo ui,
			@DefaultValue("0") @QueryParam("start") int start, 
			@DefaultValue("10") @QueryParam("count") int count) throws URISyntaxException
	{
		String retval = "";
		String path = ui.getAbsolutePath().toString();
		URI uri = new URI(path);

		if (databasecount == 0)
			return Response.created(uri).contentLocation(uri).status(Response.Status.REQUESTED_RANGE_NOT_SATISFIABLE).build();
		else
		{
			int end = start + count - 1;
			if (end > databasecount-1)
				return Response.created(uri).contentLocation(uri).status(Response.Status.REQUESTED_RANGE_NOT_SATISFIABLE).build();
			retval = rangeAsXML(start,end);
		}
		return Response.created(uri).contentLocation(uri).status(Response.Status.OK).entity(retval).build(); 
	}

	// returns a selection of records in xml format.
	// 
	// this will return 10 items starting with the first
	// http://localhost:8080/ThirdRestful/price/fetch
	//
	// start with index one for three items
	// http://localhost:8080/ThirdRestful/price/fetch?start=1&count=3

	@GET
	@Path("/fetch")
	@Produces( {MediaType.APPLICATION_JSON})
	public Response fetch(	@Context UriInfo ui,
			@DefaultValue("0") @QueryParam("start") int start, 
			@DefaultValue("10") @QueryParam("count") int count) throws URISyntaxException
	{
		String retval = "";
		String path = ui.getAbsolutePath().toString();
		URI uri = new URI(path);

		if (databasecount == 0)
			return Response.created(uri).contentLocation(uri).status(Response.Status.REQUESTED_RANGE_NOT_SATISFIABLE).build();
		else
		{
			int end = start + count - 1;
			if (end > databasecount-1)
				return Response.created(uri).contentLocation(uri).status(Response.Status.REQUESTED_RANGE_NOT_SATISFIABLE).build();
			retval = rangeAsJSON(start,end);
		}
		return Response.created(uri).contentLocation(uri).status(Response.Status.OK).entity(retval).build(); 
	}


	// returns all records in xml format.
	// 
	// http://localhost:8080/ThirdRestful/price/retrieveall 
	@GET
	@Path("/retrieveall")
	@Produces( {MediaType.APPLICATION_XML})
	public Response showall(@Context UriInfo ui) throws URISyntaxException
	{
		String retval = "";
		String path = ui.getAbsolutePath().toString() ;
		URI uri = new URI(path);

		retval = rangeAsXML(0,databasecount-1);
		return Response.created(uri).contentLocation(uri).status(Response.Status.OK).entity(retval).build();
	}



	// returns single record in xml format.
	// 
	// http://localhost:8080/ThirdRestful/price/34.xml
	@GET
	@Path("{idx}.xml")
	@Produces( {MediaType.APPLICATION_XML})
	public Response getIndexXml(@Context UriInfo ui, @PathParam("idx") int idx)  throws URISyntaxException
	{
		MarketPrice retval = null;
		String path = ui.getAbsolutePath().toString() ;
		URI uri = new URI(path);

		if (idx >= 0 && idx < databasecount) 
                { 
                        retval = pricedatabase.get(idx); 
                        return Response.created(uri).contentLocation(uri).status(Response.Status.OK).entity(retval).build(); 
                } 
                else 
                        return Response.created(uri).contentLocation(uri).status(Response.Status.REQUESTED_RANGE_NOT_SATISFIABLE).build(); 
        } 

        // returns single record in json format. 
        // 
        // http://localhost:8080/ThirdRestful/price/34.json 
        @GET @Path("{idx}.json") 
        @Produces( {MediaType.APPLICATION_JSON}) 
        public Response getIndexJSON(@Context UriInfo ui, @PathParam("idx") int idx) throws URISyntaxException 
        { 
                MarketPrice item = null; 
                String retval = ""; 
                String path = ui.getAbsolutePath().toString() ; 
                URI uri = new URI(path); 
                if (idx >= 0 && idx < databasecount) 
                { 
                        item = pricedatabase.get(idx); 
                        retval = item.JSONString(); 
                        return Response.created(uri).contentLocation(uri).status(Response.Status.OK).entity(retval).build(); 
                } 
                else 
                        return Response.created(uri).contentLocation(uri).status(Response.Status.REQUESTED_RANGE_NOT_SATISFIABLE).build(); 
         } 

         // returns single record in json format. 
         // 
         // http://localhost:8080/ThirdRestful/price/34.text 
         @GET @Path("{idx}.text") 
         @Produces( {MediaType.TEXT_PLAIN}) 
         public Response getIndexTEXT(@Context UriInfo ui, @PathParam("idx") int idx) throws URISyntaxException 
         {
                MarketPrice item = null; 
                String retval = ""; 
                String path = ui.getAbsolutePath().toString() ; 
                URI uri = new URI(path); 
                if (idx >= 0 && idx < databasecount)
		{
			item = pricedatabase.get(idx);
			retval = item.toString();
			return Response.created(uri).contentLocation(uri).status(Response.Status.OK).entity(retval).build();
		}
		else
			return Response.created(uri).contentLocation(uri).status(Response.Status.REQUESTED_RANGE_NOT_SATISFIABLE).build();
	}


}

MarketPrice.java

package com.acmesoft;

import java.io.StringWriter;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import com.google.gson.Gson;

import javax.xml.bind.annotation.XmlAccessType;;

@XmlRootElement(name="marketPrice")
@XmlAccessorType(XmlAccessType.FIELD)
public class MarketPrice 
{
	@XmlElement
	private int id;
	@XmlElement
	private String symbol;		// EUR/USD
	@XmlElement
	private String bank;		// DBK
	@XmlElement
	private BigDecimal price;	// 1.1222
	@XmlElement
	private String exchange;	// DTB, NYSE, CBOT
	@XmlElement
	private Date quoteDateTime;	// 2015-01-01 14:32:45.032


	public MarketPrice()
	{
		id = -1;
		this.symbol= "";
		this.bank = "";
		this.price = new BigDecimal(0.0);
		this.exchange = "";
		this.quoteDateTime = new Date();
	}


	public MarketPrice(String symbol, String bank, BigDecimal price, String exchange)
	{
		DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

		this.symbol= symbol;
		this.bank = bank;
		this.price = price;
		this.exchange = exchange;
		this.quoteDateTime = new Date();
		String debugDate = dateFormat.format(quoteDateTime);

		id = -1;
	}

	public MarketPrice(String symbol, String bank, BigDecimal price, String exchange, Date quoteDateTime)
	{
		this.symbol= symbol;
		this.bank = bank;
		this.price = price;
		this.exchange = exchange;
		this.quoteDateTime = quoteDateTime;
		id = -1;
	}

	public void setId(int value)
	{
		id = value;
	}
	public int getId()
	{
		return id;
	}

	public void setSymbol(String value)
	{
		symbol = value;
	}
	public String getSymbol()
	{
		return symbol;
	}

	public void setBank(String value)
	{
		bank = value;
	}
	public String getBank()
	{
		return bank;
	}

	public void setPrice(BigDecimal value)
	{
		price = value;
	}
	public void setPrice(double value)
	{
		price = new BigDecimal(value);
	}
	public BigDecimal getPrice()
	{
		return price;
	}

	public void setExchange(String value)
	{
		exchange = value;
	}
	public String getExchange()
	{
		return exchange;
	}

	public Date getQuoteDateTime()
	{
		return quoteDateTime;
	}

	public void setQuoteDateTime(Date value)
	{
		quoteDateTime = value;
	}


	public String toString()
	{
		String retval = getCSVData();

		return retval;
	}

	public String xmlString()
	{ 
		String retval = toXmlString();
		retval = retval.substring(retval.indexOf("\n"));

		return retval;
	}

	public String JSONString()
	{ 
		Gson gson = new Gson();
		String JSONstr = gson.toJson(this);
		return JSONstr;  
	}

	public String toXmlString()
	{
		JAXBContext ctx = null;
		Marshaller maxMarshaller = null;

		try {
			ctx = JAXBContext.newInstance(MarketPrice.class);
		} catch (JAXBException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		try {
			maxMarshaller = ctx.createMarshaller();
		} catch (JAXBException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		try {
			maxMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
		} catch (PropertyException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		StringWriter writer = new StringWriter();
		try {
			maxMarshaller.marshal(this,writer);
		} catch (JAXBException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		String xmlPrice = writer.toString();

		return xmlPrice;
	}

	private String convertToTime(long milliseconds)
	{
		Date date = new Date(milliseconds);
		DateFormat dateformat = new SimpleDateFormat("HH:mm:ss.SSS");
		return dateformat.format(date);
	}
	public String getCSVHeader()
	{
		String delim = com.acmesoft.Constants.FIELDDELIM;
		String retval;

		retval = "Id" + delim + "Symbol" + delim + "Price" + delim + "Bank" + delim + "Exchange" + delim + "QuoteDate" ;

		return retval;
	}
	public String getCSVData()
	{
		String delim = com.acmesoft.Constants.FIELDDELIM;
		String retval;

		retval = id + delim + symbol + delim + price + delim + bank + delim + exchange + delim + quoteDateTime ;

		return retval;
	}
}

PriceCount.java

package com.acmesoft;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="marketPrice")
@XmlAccessorType(XmlAccessType.FIELD)
public class PriceCount
{
        @XmlElement
        private int count;

        public PriceCount()
        {

        }
        public PriceCount(int val)
        {
                count = val;
        }

        public int getCount()
        {
                return count;
        }

        public void setCount(int val)
        {
                count = val;
        }
}

Constants.java

package com.acmesoft;

public class Constants {
	final public static String FIELDDELIM = ",";
	final public static int OK = 200;
	final public static String DATETIMEFORMAT = "yyyy-MM-ddHH:mm:ss.SSS";
}
This entry was posted in programming and tagged , , . Bookmark the permalink.