Sorting out the FedEx offerings can at first be a bit confusing. There are a variety of technologies, and it can be hard to understand which one is best to use. Indeed, it can be hard, when sorting through the FedEx web site, to figure out which package you should even download.
Start by visiting http://www.fedex.com/us/solutions/wis/index.html/. As shown in Figure 5-3, there are a variety of offerings, including the FedEx Ship Manager API, FedEx Ship Manager Direct, and XML Tools.
Go to the download page for the FedEx APIs. As of this writing, the URL is https://www.fedex.com/cgi-bin/shipapiDownload.cgi?link=4&first=y, but it may change. As shown in Figure 5-4, use FSM API for connections and XML Tools to actually send and retrieve data.
After registering and filling out the appropriate forms, you will be emailed instructions on how to download and install the SDK files. Of key interest is the main JAR file; if you downloaded and installed the SDK in the default location, you'll find this at C:\Program Files\FedEx\FedEx Ship Manager API\java\lib\FedExAPI.JAR. Make sure that the JAR file is on your class path.
The code shown in Example 5-4 shows how to actually connect to the FedEx server and retrieve the data. A utility main( ) method is provided to test this functionality.
package com.cascadetg.ch05; import org.jdom.Document; import org.jdom.Namespace; import org.jdom.Element; import org.jdom.input.SAXBuilder; import org.jdom.output.XMLOutputter; import com.fedex.api.*; public class FedExShipping { // We'll keep track of the meter id here. String meter = null; /** * Unfortunately, the FedExAPI sometimes returns garbage 0 bytes, * which need to be trimmed before the returned XML can be parsed. * * Therefore, we have this utility method that trims 0-value bytes * from an array of bytes. */ public String trim(byte[] inbytes) { byte[] trimmed = null; for (int i = 0; i < inbytes.length; i++) { if (inbytes[i] == 0) { return new String(inbytes, 0, i); } } return new String(inbytes); } /** * @param carrierCode * valid values are ALL, FDXE, FDXG, or null to not set. * @return */ public Element getRequestHeaderElement(String carrierCode) { // Next, we create and add the RequestHeader element org.jdom.Element requestHeader = new org.jdom.Element("RequestHeader"); // We'll be using this currentElement object to create // and set the various tags as needed. Element currentElement = new Element("CustomerTransactionIdentifier").addContent( new java.util.Date( ).toString( )); requestHeader.addContent((Element)currentElement.clone( )); currentElement.setName("AccountNumber"); currentElement.setText(FedExTokens.fedExAccountNumber); requestHeader.addContent((Element)currentElement.clone( )); if (meter != null) { currentElement.setName("MeterNumber"); currentElement.setText(meter); requestHeader.addContent((Element)currentElement.clone( )); } if (carrierCode != null) { currentElement.setName("CarrierCode"); currentElement.setText(carrierCode); requestHeader.addContent((Element)currentElement.clone( )); } return requestHeader; } public Element getContactElement( ) { // After setting the RequestHeader elements, we next need to // set the Contact element Element contactElement = new Element("Contact"); Element currentElement = new Element("PersonName"); currentElement.setText(FedExTokens.fedExPersonName); contactElement.addContent((Element)currentElement.clone( )); currentElement.setName("CompanyName"); currentElement.setText(FedExTokens.fedExCompanyName); contactElement.addContent((Element)currentElement.clone( )); currentElement.setName("Department"); currentElement.setText(FedExTokens.fedExDepartment); contactElement.addContent((Element)currentElement.clone( )); currentElement.setName("PhoneNumber"); currentElement.setText(FedExTokens.fedExPhoneNumber); contactElement.addContent((Element)currentElement.clone( )); currentElement.setName("E-MailAddress"); currentElement.setText(FedExTokens.fedExEmail); contactElement.addContent((Element)currentElement.clone( )); return contactElement; } public Element getAddressElement(String base) { // Next, we set the Address element. Element addressElement = new Element(base); Element currentElement = new Element("Line1"); currentElement.setText(FedExTokens.fedExAddressLine1); addressElement.addContent((Element)currentElement.clone( )); currentElement.setName("Line2"); currentElement.setText(FedExTokens.fedExAddressLine2); addressElement.addContent((Element)currentElement.clone( )); currentElement.setName("City"); currentElement.setText(FedExTokens.fedExAddressCity); addressElement.addContent((Element)currentElement.clone( )); currentElement.setName("StateOrProvinceCode"); currentElement.setText(FedExTokens.fedExAddressState); addressElement.addContent((Element)currentElement.clone( )); currentElement.setName("PostalCode"); currentElement.setText(FedExTokens.fedExAddressZIP); addressElement.addContent((Element)currentElement.clone( )); currentElement.setName("CountryCode"); currentElement.setText(FedExTokens.fedExCountry); addressElement.addContent((Element)currentElement.clone( )); return addressElement; } public Document getFedExRequest(String requestType) { // First, we create a new XML document and set the // namespaces as requested by FedEx. Document message = new org.jdom.Document( ); message.setRootElement(new org.jdom.Element(requestType)); Namespace api = Namespace.getNamespace( "api", "http://www.fedex.com/fsmapi"); Namespace xsi = Namespace.getNamespace( "xsi", "http://www.w3.org/2001/XMLSchema-instance"); message.getRootElement( ).addNamespaceDeclaration(api); message.getRootElement( ).addNamespaceDeclaration(xsi); message.getRootElement( ).setAttribute( "noNamespaceSchemaLocation", requestType + ".xsd", Namespace.getNamespace( "xsi", "http://www.w3.org/2001/XMLSchema-instance")); return message; } public String pad(int in) { if (in > 9) return new String(Integer.toString(in)); return new String("0" + in); } String weightLBS; String postalCode; String state; String country = "US"; /** * Used to retrieve a rate request from the FedEx server. Note that * this takes on average about two seconds by my highly informal * estimate, plus an additional three seconds the first time you * look up your meter number (so the first time you make this call * it could easily take over five seconds to return!) */ public String getRateRequest( ) { if (meter == null) if (debug) { System.out.println("Meter #: " + getMeterNumber( )); } else { getMeterNumber( ); } Document myMessage = getFedExRequest("FDXRateRequest"); myMessage.getRootElement( ).addContent( getRequestHeaderElement("FDXG")); Element currentElement = new Element("ShipDate"); java.util.Date today = new java.util.Date( ); currentElement.setText( (today.getYear( ) + 1900) + "-" + pad(today.getMonth( )) + "-" + pad(today.getDate( ))); myMessage.getRootElement( ).addContent( (Element)currentElement.clone( )); currentElement.setName("DropoffType"); currentElement.setText("REGULARPICKUP"); myMessage.getRootElement( ).addContent( (Element)currentElement.clone( )); currentElement.setName("Service"); currentElement.setText("FEDEXGROUND"); myMessage.getRootElement( ).addContent( (Element)currentElement.clone( )); currentElement.setName("Packaging"); currentElement.setText("FEDEXBOX"); myMessage.getRootElement( ).addContent( (Element)currentElement.clone( )); currentElement.setName("WeightUnits"); currentElement.setText("LBS"); myMessage.getRootElement( ).addContent( (Element)currentElement.clone( )); currentElement.setName("Weight"); currentElement.setText(weightLBS); myMessage.getRootElement( ).addContent( (Element)currentElement.clone( )); Element origin = new Element("OriginAddress"); currentElement.setName("StateOrProvinceCode"); currentElement.setText(FedExTokens.fedExAddressState); origin.addContent((Element)currentElement.clone( )); currentElement.setName("PostalCode"); currentElement.setText(FedExTokens.fedExAddressZIP); origin.addContent((Element)currentElement.clone( )); currentElement.setName("CountryCode"); currentElement.setText(FedExTokens.fedExCountry); origin.addContent((Element)currentElement.clone( )); myMessage.getRootElement( ).addContent(origin); Element destination = new Element("DestinationAddress"); currentElement.setName("StateOrProvinceCode"); currentElement.setText(state); destination.addContent((Element)currentElement.clone( )); currentElement.setName("PostalCode"); currentElement.setText(postalCode); destination.addContent((Element)currentElement.clone( )); currentElement.setName("CountryCode"); currentElement.setText(country); destination.addContent((Element)currentElement.clone( )); myMessage.getRootElement( ).addContent(destination); Element payment = new Element("Payment"); currentElement.setName("PayorType"); currentElement.setText("SENDER"); payment.addContent((Element)currentElement.clone( )); myMessage.getRootElement( ).addContent(payment); currentElement.setName("PackageCount"); currentElement.setText("1"); myMessage.getRootElement( ).addContent( (Element)currentElement.clone( )); try { if (debug) new XMLOutputter( ).output(myMessage, System.out); org.jdom.Document reply = sendRequest(myMessage); if (debug) new XMLOutputter( ).output(reply, System.out); return reply .getRootElement( ) .getChild("EstimatedCharges") .getChild("ListCharges") .getChildText("NetCharge"); } catch (Exception e) { e.printStackTrace( ); } return null; } /** * This class relies heavily on the values supplied in the * FedExTokens class for the data to be sent. Note that FedEx * performs validation on the data sent to the server, so you need * to make sure that you're sending over valid information (e.g. a * valid ZIP code). * * Note that there is no reason not to cache the meter number - * indeed, if you intend to use the FedEx API heavily, you ought to * just enter the meter number as a static value in your * FedExTokens equivalent. */ public String getMeterNumber( ) { String finalResponse = null; Document myMessage = getFedExRequest("FDXSubscriptionRequest"); myMessage.getRootElement( ).addContent( getRequestHeaderElement(null)); myMessage.getRootElement( ).addContent(getContactElement( )); myMessage.getRootElement( ).addContent( getAddressElement("Address")); org.jdom.Document myReply = sendRequest(myMessage); try { if (myReply.getRootElement( ).getChild("MeterNumber") == null) { System.out.println("Unable to find meter number:"); new XMLOutputter( ).output(myReply, System.out); } finalResponse = myReply.getRootElement( ).getChildText("MeterNumber"); meter = finalResponse; return finalResponse; } catch (Exception e) { e.printStackTrace( ); try { new XMLOutputter( ).output(myReply, System.out); } catch (Exception e1) { e1.printStackTrace( ); } } return null; } public boolean debug = false; public int getPort(org.jdom.Document inDocument) { String rootElement = inDocument.getRootElement( ).getName( ); if (rootElement.compareTo("FDXRateRequest") == 0) return 2518; if (rootElement.compareTo("FDXSubscriptionRequest") == 0) return 2523; return -1; } public org.jdom.Document sendRequest(org.jdom.Document in) { try { XMLOutputter myOutputter = new XMLOutputter( ); myOutputter.setOmitDeclaration(false); if (debug) myOutputter.output(in, System.out); byte[] response = new byte[0]; byte[] request; request = myOutputter.outputString(in).toString( ).getBytes( "UTF8"); if (debug) System.out.println("Sending transaction..."); response = FedExAPI.transact( getPort(in), request, "127.0.0.1", 8190, 125); org.jdom.Document myReply = new SAXBuilder( ).build( new java.io.StringReader(trim(response))); return myReply; } catch (Exception e) { e.printStackTrace( ); } return null; } public static void main(String[] args) { boolean debug = false; if (args != null) if (args.length > 0) if (args[0].compareTo("debug") == 0) debug = true; FedExShipping myShipper = new FedExShipping( ); long timing = System.currentTimeMillis( ); long elapsed = System.currentTimeMillis( ) - timing; System.out.println("Elapsed: " + elapsed); timing = System.currentTimeMillis( ); myShipper.setWeightLBS("10.0"); myShipper.setPostalCode("10002"); myShipper.setState("NY"); System.out.println( "New York Rate request: " + myShipper.getRateRequest( )); elapsed = System.currentTimeMillis( ) - timing; System.out.println("Elapsed: " + elapsed); timing = System.currentTimeMillis( ); myShipper.setPostalCode("96813"); myShipper.setState("HI"); System.out.println( "Hawaii Rate request: " + myShipper.getRateRequest( )); elapsed = System.currentTimeMillis( ) - timing; System.out.println("Elapsed: " + elapsed); timing = System.currentTimeMillis( ); myShipper.setPostalCode("98121"); myShipper.setState("WA"); System.out.println( "Seattle, WA Rate request: " + myShipper.getRateRequest( )); elapsed = System.currentTimeMillis( ) - timing; System.out.println("Elapsed: " + elapsed); timing = System.currentTimeMillis( ); } public String getCountry( ) { return country; } public void setCountry(String country) { this.country = country; } public String getPostalCode( ) { return postalCode; } public void setPostalCode(String postalCode) { this.postalCode = postalCode; } public String getState( ) { return state; } public void setState(String state) { this.state = state; } public String getWeightLBS( ) { return weightLBS; } public void setWeightLBS(String weightLBS) { this.weightLBS = weightLBS; } public String getMeter( ) { return meter; } }
The code in Example 5-4 performs two tasks: first, given your FedEx registration, it gets your meter number. Second, given some additional specific information (weight, state, postal code, and country), it returns a rate estimate for a package. Your meter number is the FedEx number used to track your account and is required by most FedEx operations.
Note that there is a lot of XML document processing that goes on in Example 5-4. The FedEx XML format is heavily hierarchical, requiring subnodes to be attached to nodes. For readability, the code to set up a single transaction is broken into several chunks (getRequestHeaderElement( ), getContactElement( ), getAddressElement( )), but it's still a heavily complex process of processing FedEx-specific XML. A full description of the various XML commands supported by FedEx can be found at http://www.fedex.com/us/solutions/wis/pdf/xml_transguide.pdf?link=4. The complexity arises not from any particularly complex algorithm, but rather the sheer number of options and the complexity of the XML hierarchy.
What Are the Other FedEx Choices?You'll notice that there are two other choices: FSM Direct for connectivity and FedEx Tagged Transaction for the data format. FSM Direct allows you to connect directly to the FedEx systems using HTTPS, similar to the method used to connect to eBay. Fortunately, the FSM API takes care of all this connectivity automatically; the provided JAR file handles the connectivity. If for some reason you wish to manually control the connectivity via HTTPS, you may wish to investigate FSM Direct. FedEx Tagged Transactions are an alternative to the XML format that FedEx uses (called XML Tools). It's a pre-XML format, based on special ASCII formatting. Here's an example of a FedEx Tagged Transaction: 2016:0,"021"4,"Total Widget Rebuilders"5,"445 East Street"7,"Dallas"8,"TX"9,"75247"10,"123456789"498, "123567"117,"US"183,"21455587 65"50,"US"11,"ABC Widget" 12,"Bernard F. Smith"13, "322 Latta Woods"14,"Suite 26"15,"Roanoke"16,"VA"17,"24012"18,"7035551212"23, "1"1401,"4. 0"1273,"03"1274,"03"25,"SPECIAL CALL-IN"24, "20000103"51,"Y"1115,"130555"1368,"1"1369,"1"1370, "5"1116,"I"75,"LBS"3025,"FDXE "1333,"1"99,"" Using the XML format, you avoid having to worry about parsing this data format. |
Even though there is a lot of code in Example 5-4, it's the bare minimum to actually get a rate request from FedEx. There are a significant number of additional options that can be set to obtain much more precise rate quotes. For applications using this class, however, all this is abstracted into an ordinary Java class with a few set...( ) methods and a getRateRequest( ) actually retrieving the data. Similarly, the remaining FedEx XML web service methods could be bound to Java classes.
A single class, shown in Example 5-5, stores the important information about your account.
|
This example doesn't contain real values, but the length and format of the strings are correct. You'll want to enter your own information here, as provided by FedEx, instead of using the values shown.
package com.cascadetg.ch05; public class FedExTokens { public static String fedExAccountNumber = "123456789"; public static String fedExPersonName = "Will Iverson"; public static String fedExCompanyName = "Cascade Technology Group"; public static String fedExDepartment = "Shipping"; public static String fedExPhoneNumber = "5105551212"; public static String fedExEmail = "fedex@yourdomain.com"; public static String fedExAddressLine1 = "123 Anywhere Ave"; public static String fedExAddressLine2 = "Suite 101"; public static String fedExAddressCity = "Universal City"; public static String fedExAddressState = "CA"; public static String fedExAddressZIP = "95111"; public static String fedExCountry = "US"; }
In this chapter, you used two different web services to provide an easier environment for both the seller and the buyer. The seller can automate a boring task, and the buyer can get an idea of the true cost of an auction while browsing through the auction listings (avoiding rude surprises). It's easy to imagine extending this application to do many more things, such as monitor the current status of posted auctions or generate reports.