package com.spun.util.shipping;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.spun.util.ObjectUtils;
import com.spun.util.logger.SimpleLogger;
import com.spun.util.ups.UPSPackage;
public class USPSDomesticRateCalculator
{
public static final String SERVERNAME = "testing.shippingAPIs.com";
public static final String USERID = "484ABAKA5143";
/************************************************************************/
public enum Container {
FLAT_RATE_ENVELOPE("Flat Rate Envelope"), FLAT_RATE_BOX("Flat Rate Box");
private final String label;
Container(String label)
{
this.label = label;
}
public String toString()
{
return label;
}
}
/************************************************************************/
public enum Service {
EXPRESS("Express"), FIRST_CLASS("First Class"), PRIORITY("Priority"), PARCEL("Parcel"),
BPM("BPM"), LIBRARY("Library"), MEDIA("Media"), ALL("All");
private final String label;
Service(String label)
{
this.label = label;
}
public String toString()
{
return label;
}
}
/************************************************************************/
public enum Size {
REGULAR, LARGE, OVERSIZE
}
/************************************************************************/
public Collection<Rate> getRates(Service service, String zipFrom, String zipTo, int pounds, int ounces,
Container container, Size size, boolean machinable)
{
String uri = String.format(
"<?xml version=\"1.0\" ?><RateV2Request USERID=\"%s\"><Package ID=\"0\"><Service>%s</Service><ZipOrigination>%s</ZipOrigination><ZipDestination>%s</ZipDestination><Pounds>%d</Pounds><Ounces>%d</Ounces><Container>%s</Container><Size>%s</Size><Machinable>%s</Machinable></Package></RateV2Request>",
USERID, service, zipFrom, zipTo, pounds, ounces, container == null ? "" : container, size,
machinable == true ? "TRUE" : "");
SimpleLogger.variable(uri);
return sendRequest(uri);
}
/************************************************************************/
private Collection<Rate> sendRequest(String uri)
{
try
{
uri = String.format("http://%s/ShippingAPITest.dll?API=RateV2&XML=%s", SERVERNAME,
URLEncoder.encode(uri, "UTF-8"));
}
catch (UnsupportedEncodingException uee)
{
throw new RuntimeException("Unable to encode URL", uee);
}
HttpClient client = new HttpClient();
HttpMethod method = null;
method = new GetMethod(uri);
String responseBody = null;
try
{
client.executeMethod(method);
responseBody = method.getResponseBodyAsString();
}
catch (HttpException he)
{
throw new RuntimeException("Http error connecting to '" + uri + "'", he);
}
catch (IOException ioe)
{
throw new RuntimeException("Unable to connect to '" + uri + "'", ioe);
}
SimpleLogger.variable(responseBody);
ResponseParser handler = new ResponseParser();
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser;
try
{
saxParser = factory.newSAXParser();
saxParser.parse(method.getResponseBodyAsStream(), handler);
}
catch (ParserConfigurationException e)
{
throw new RuntimeException("Sax parser error");
}
catch (SAXException e)
{
throw new RuntimeException("Sax parser error");
}
catch (IOException e)
{
throw new RuntimeException("Sax parser error");
}
return Collections.unmodifiableCollection(handler.getRates());
}
/************************************************************************/
public Rate[] getRates(UPSPackage[] boxes)
{
String opening = "<?xml version=\"1.0\" ?><RateV2Request USERID=" + USERID + ">";
String closing = "</RateV2Request>";
String packages = "";
for (int i = 0; i < boxes.length; i++)
{
Size size = getEquivalentSize(boxes[i]);
double rawPounds = boxes[i].getPackageWeightInPounds();
double pounds = Math.floor(rawPounds);
double rawOunces = rawPounds - pounds;
double ounces = Math.ceil(16.00 * rawOunces);
String zipOrig = boxes[i].getOriginatingZipCode();
String zipDest = boxes[i].getToZipCode();
if (!ObjectUtils.isEqual(boxes[i].getToCountryCode(),
"United States")) { throw new RuntimeException("Not yet handling international shipments via USPS"); }
packages += String.format(
"<Package ID=\"%d\"><Service>%s</Service><ZipOrigination>%s</ZipOrigination><ZipDestination>%s</ZipDestination><Pounds>%d</Pounds><Ounces>%d</Ounces><Size>%s</Size></Package>",
i, Service.ALL, zipOrig, zipDest, pounds, ounces, size);
}
Collection<Rate> rates = sendRequest(opening + packages + closing);
return Rate.toArray(rates);
}
/************************************************************************/
private static Size getEquivalentSize(UPSPackage box)
{
int size = box.getPackageLength() + 2 * (box.getPackageWidth() + box.getPackageHeight());
if (size < 84)
{
return Size.REGULAR;
}
else if (84 < size && size < 108)
{
return Size.LARGE;
}
else if (108 < size && size < 130)
{
return Size.OVERSIZE;
}
else
{
// return something to indicate not valid for USPS
}
return null;
}
/************************************************************************/
/************* INNER CLASSES *************/
/************************************************************************/
public class ResponseParser extends DefaultHandler
{
private Collection<Rate> rates = new ArrayList<Rate>();
private String mailService;
private double rate;
private boolean isMailService;
private boolean isRate;
public Collection<Rate> getRates()
{
return rates;
}
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
{
if (qName.equals("MailService"))
{
isMailService = true;
}
else if (qName.equals("Rate"))
{
isRate = true;
}
else if (qName.equals("Postage"))
{
mailService = null;
rate = 0;
isMailService = false;
isRate = false;
}
}
/************************************************************************/
public void characters(char buf[], int offset, int len) throws SAXException
{
String s = new String(buf, offset, len);
if (isMailService)
mailService = s;
else if (isRate)
rate = Double.valueOf(s);
}
/************************************************************************/
public void endElement(String uri, String localName, String qName) throws SAXException
{
if (qName.equals("MailService"))
{
isMailService = false;
}
else if (qName.equals("Rate"))
{
isRate = false;
}
else if (qName.equals("Postage"))
{
rates.add(new Rate(mailService, rate));
}
}
}
/************************************************************************/
/************************************************************************/
}