0001: /*******************************************************************************
0002: * Licensed to the Apache Software Foundation (ASF) under one
0003: * or more contributor license agreements. See the NOTICE file
0004: * distributed with this work for additional information
0005: * regarding copyright ownership. The ASF licenses this file
0006: * to you under the Apache License, Version 2.0 (the
0007: * "License"); you may not use this file except in compliance
0008: * with the License. You may obtain a copy of the License at
0009: *
0010: * http://www.apache.org/licenses/LICENSE-2.0
0011: *
0012: * Unless required by applicable law or agreed to in writing,
0013: * software distributed under the License is distributed on an
0014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
0015: * KIND, either express or implied. See the License for the
0016: * specific language governing permissions and limitations
0017: * under the License.
0018: *******************************************************************************/package org.ofbiz.shipment.thirdparty.usps;
0019:
0020: import java.io.ByteArrayOutputStream;
0021: import java.io.FileOutputStream;
0022: import java.io.IOException;
0023: import java.io.OutputStream;
0024: import java.text.DecimalFormat;
0025: import java.util.ArrayList;
0026: import java.util.HashMap;
0027: import java.util.Iterator;
0028: import java.util.LinkedList;
0029: import java.util.List;
0030: import java.util.ListIterator;
0031: import java.util.Map;
0032:
0033: import javax.xml.parsers.ParserConfigurationException;
0034:
0035: import org.ofbiz.base.util.*;
0036: import org.ofbiz.entity.GenericDelegator;
0037: import org.ofbiz.entity.GenericEntityException;
0038: import org.ofbiz.entity.GenericValue;
0039: import org.ofbiz.entity.util.EntityUtil;
0040: import org.ofbiz.product.store.ProductStoreWorker;
0041: import org.ofbiz.service.DispatchContext;
0042: import org.ofbiz.service.GenericServiceException;
0043: import org.ofbiz.service.LocalDispatcher;
0044: import org.ofbiz.service.ModelService;
0045: import org.ofbiz.service.ServiceUtil;
0046:
0047: import org.apache.xml.serialize.OutputFormat;
0048: import org.apache.xml.serialize.XMLSerializer;
0049: import org.w3c.dom.Document;
0050: import org.w3c.dom.Element;
0051: import org.xml.sax.SAXException;
0052:
0053: /**
0054: * USPS Webtools API Services
0055: */
0056: public class UspsServices {
0057:
0058: public final static String module = UspsServices.class.getName();
0059:
0060: public static Map uspsRateInquire(DispatchContext dctx, Map context) {
0061:
0062: GenericDelegator delegator = dctx.getDelegator();
0063:
0064: // check for 0 weight
0065: Double shippableWeight = (Double) context
0066: .get("shippableWeight");
0067: if (shippableWeight.doubleValue() == 0) {
0068: // TODO: should we return an error, or $0.00 ?
0069: return ServiceUtil
0070: .returnError("shippableWeight must be greater than 0");
0071: }
0072:
0073: // get the origination ZIP
0074: String originationZip = null;
0075: GenericValue productStore = ProductStoreWorker.getProductStore(
0076: ((String) context.get("productStoreId")), delegator);
0077: if (productStore != null
0078: && productStore.get("inventoryFacilityId") != null) {
0079: try {
0080: List shipLocs = delegator.findByAnd(
0081: "FacilityContactMechPurpose",
0082: UtilMisc.toMap("facilityId", productStore
0083: .getString("inventoryFacilityId"),
0084: "contactMechPurposeTypeId",
0085: "SHIP_ORIG_LOCATION"), UtilMisc
0086: .toList("-fromDate"));
0087: if (UtilValidate.isNotEmpty(shipLocs)) {
0088: shipLocs = EntityUtil.filterByDate(shipLocs);
0089: GenericValue purp = EntityUtil.getFirst(shipLocs);
0090: if (purp != null) {
0091: GenericValue shipFromAddress = delegator
0092: .findByPrimaryKey(
0093: "PostalAddress",
0094: UtilMisc
0095: .toMap(
0096: "contactMechId",
0097: purp
0098: .getString("contactMechId")));
0099: if (shipFromAddress != null) {
0100: originationZip = shipFromAddress
0101: .getString("postalCode");
0102: }
0103: }
0104: }
0105: } catch (GenericEntityException e) {
0106: Debug.logError(e, module);
0107: }
0108: }
0109: if (UtilValidate.isEmpty(originationZip)) {
0110: return ServiceUtil
0111: .returnError("Unable to determine the origination ZIP");
0112: }
0113:
0114: // get the destination ZIP
0115: String destinationZip = null;
0116: String shippingContactMechId = (String) context
0117: .get("shippingContactMechId");
0118: if (UtilValidate.isNotEmpty(shippingContactMechId)) {
0119: try {
0120: GenericValue shipToAddress = delegator
0121: .findByPrimaryKey("PostalAddress", UtilMisc
0122: .toMap("contactMechId",
0123: shippingContactMechId));
0124: if (shipToAddress != null) {
0125: destinationZip = shipToAddress
0126: .getString("postalCode");
0127: }
0128: } catch (GenericEntityException e) {
0129: Debug.logError(e, module);
0130: }
0131: }
0132: if (UtilValidate.isEmpty(destinationZip)) {
0133: return ServiceUtil
0134: .returnError("Unable to determine the destination ZIP");
0135: }
0136:
0137: // get the service code
0138: String serviceCode = null;
0139: try {
0140: GenericValue carrierShipmentMethod = delegator
0141: .findByPrimaryKey(
0142: "CarrierShipmentMethod",
0143: UtilMisc
0144: .toMap(
0145: "shipmentMethodTypeId",
0146: (String) context
0147: .get("shipmentMethodTypeId"),
0148: "partyId",
0149: (String) context
0150: .get("carrierPartyId"),
0151: "roleTypeId",
0152: (String) context
0153: .get("carrierRoleTypeId")));
0154: if (carrierShipmentMethod != null) {
0155: serviceCode = carrierShipmentMethod
0156: .getString("carrierServiceCode");
0157: }
0158: } catch (GenericEntityException e) {
0159: Debug.logError(e, module);
0160: }
0161: if (UtilValidate.isEmpty(serviceCode)) {
0162: return ServiceUtil
0163: .returnError("Unable to determine the service code");
0164: }
0165:
0166: // create the request document
0167: Document requestDocument = createUspsRequestDocument("RateV2Request");
0168:
0169: // TODO: 70 lb max is valid for Express, Priority and Parcel only - handle other methods
0170: double maxWeight = 70;
0171: String maxWeightStr = UtilProperties.getPropertyValue(
0172: (String) context.get("serviceConfigProps"),
0173: "shipment.usps.max.estimate.weight", "70");
0174: try {
0175: maxWeight = Double.parseDouble(maxWeightStr);
0176: } catch (NumberFormatException e) {
0177: Debug
0178: .logWarning(
0179: "Error parsing max estimate weight string ["
0180: + maxWeightStr
0181: + "], using default instead",
0182: module);
0183: maxWeight = 70;
0184: }
0185:
0186: List shippableItemInfo = (List) context
0187: .get("shippableItemInfo");
0188: List packages = getPackageSplit(dctx, shippableItemInfo,
0189: maxWeight);
0190: // TODO: Up to 25 packages can be included per request - handle more than 25
0191: for (ListIterator li = packages.listIterator(); li.hasNext();) {
0192: Map packageMap = (Map) li.next();
0193:
0194: double packageWeight = calcPackageWeight(dctx, packageMap,
0195: shippableItemInfo, 0);
0196: if (packageWeight == 0) {
0197: continue;
0198: }
0199:
0200: Element packageElement = UtilXml.addChildElement(
0201: requestDocument.getDocumentElement(), "Package",
0202: requestDocument);
0203: packageElement.setAttribute("ID", String.valueOf(li
0204: .nextIndex() - 1)); // use zero-based index (see examples)
0205:
0206: UtilXml.addChildElementValue(packageElement, "Service",
0207: serviceCode, requestDocument);
0208: UtilXml.addChildElementValue(packageElement,
0209: "ZipOrigination", originationZip.substring(0, 5),
0210: requestDocument);
0211: UtilXml.addChildElementValue(packageElement,
0212: "ZipDestination", destinationZip.substring(0, 5),
0213: requestDocument);
0214:
0215: double weightPounds = Math.floor(packageWeight);
0216: // for Parcel post, the weight must be at least 1 lb
0217: if ("PARCEL".equals(serviceCode.toUpperCase())
0218: && (weightPounds < 1.0)) {
0219: weightPounds = 1.0;
0220: packageWeight = 0.0;
0221: }
0222: double weightOunces = Math.ceil(packageWeight * 16 % 16);
0223: DecimalFormat df = new DecimalFormat("#"); // USPS only accepts whole numbers like 1 and not 1.0
0224: UtilXml.addChildElementValue(packageElement, "Pounds", df
0225: .format(weightPounds), requestDocument);
0226: UtilXml.addChildElementValue(packageElement, "Ounces", df
0227: .format(weightOunces), requestDocument);
0228:
0229: // TODO: handle other container types, package sizes, and machinable packages
0230: // IMPORTANT: Express or Priority Mail will fail if you supply a Container tag: you will get a message like
0231: // Invalid container type. Valid container types for Priority Mail are Flat Rate Envelope and Flat Rate Box.
0232: if ("Parcel".equalsIgnoreCase(serviceCode)) {
0233: UtilXml.addChildElementValue(packageElement,
0234: "Container", "None", requestDocument);
0235: }
0236: UtilXml.addChildElementValue(packageElement, "Size",
0237: "Regular", requestDocument);
0238: UtilXml.addChildElementValue(packageElement, "Machinable",
0239: "False", requestDocument);
0240: }
0241:
0242: // send the request
0243: Document responseDocument = null;
0244: try {
0245: responseDocument = sendUspsRequest("RateV2",
0246: requestDocument);
0247: } catch (UspsRequestException e) {
0248: Debug.log(e, module);
0249: return ServiceUtil
0250: .returnError("Error sending request for USPS Domestic Rate Calculation service: "
0251: + e.getMessage());
0252: }
0253:
0254: List rates = UtilXml.childElementList(responseDocument
0255: .getDocumentElement(), "Package");
0256: if (UtilValidate.isEmpty(rates)) {
0257: return ServiceUtil
0258: .returnError("No rate available at this time");
0259: }
0260:
0261: double estimateAmount = 0.00;
0262: for (Iterator i = rates.iterator(); i.hasNext();) {
0263: Element packageElement = (Element) i.next();
0264: try {
0265: Element postageElement = UtilXml.firstChildElement(
0266: packageElement, "Postage");
0267: double packageAmount = Double.parseDouble(UtilXml
0268: .childElementValue(postageElement, "Rate"));
0269: estimateAmount += packageAmount;
0270: } catch (NumberFormatException e) {
0271: Debug.log(e, module);
0272: }
0273: }
0274:
0275: Map result = ServiceUtil.returnSuccess();
0276: result
0277: .put("shippingEstimateAmount", new Double(
0278: estimateAmount));
0279: return result;
0280: }
0281:
0282: private static List getPackageSplit(DispatchContext dctx,
0283: List shippableItemInfo, double maxWeight) {
0284: // create the package list w/ the first pacakge
0285: List packages = new LinkedList();
0286:
0287: if (shippableItemInfo != null) {
0288: Iterator sii = shippableItemInfo.iterator();
0289: while (sii.hasNext()) {
0290: Map itemInfo = (Map) sii.next();
0291: long pieces = ((Long) itemInfo.get("piecesIncluded"))
0292: .longValue();
0293: double totalQuantity = ((Double) itemInfo
0294: .get("quantity")).doubleValue();
0295: double totalWeight = ((Double) itemInfo.get("weight"))
0296: .doubleValue();
0297: String productId = (String) itemInfo.get("productId");
0298:
0299: // sanity check
0300: if (pieces < 1) {
0301: pieces = 1; // can NEVER be less than one
0302: }
0303: double weight = totalWeight / pieces;
0304:
0305: for (int z = 1; z <= totalQuantity; z++) {
0306: double partialQty = pieces > 1 ? 1.000 / pieces : 1;
0307: for (long x = 0; x < pieces; x++) {
0308: if (weight >= maxWeight) {
0309: Map newPackage = new HashMap();
0310: newPackage.put(productId, new Double(
0311: partialQty));
0312: packages.add(newPackage);
0313: } else if (totalWeight > 0) {
0314: // create the first package
0315: if (packages.size() == 0) {
0316: packages.add(new HashMap());
0317: }
0318:
0319: // package loop
0320: int packageSize = packages.size();
0321: boolean addedToPackage = false;
0322: for (int pi = 0; pi < packageSize; pi++) {
0323: if (!addedToPackage) {
0324: Map packageMap = (Map) packages
0325: .get(pi);
0326: double packageWeight = calcPackageWeight(
0327: dctx, packageMap,
0328: shippableItemInfo, weight);
0329: if (packageWeight <= maxWeight) {
0330: Double qtyD = (Double) packageMap
0331: .get(productId);
0332: double qty = qtyD == null ? 0
0333: : qtyD.doubleValue();
0334: packageMap.put(productId,
0335: new Double(qty
0336: + partialQty));
0337: addedToPackage = true;
0338: }
0339: }
0340: }
0341: if (!addedToPackage) {
0342: Map packageMap = new HashMap();
0343: packageMap.put(productId, new Double(
0344: partialQty));
0345: packages.add(packageMap);
0346: }
0347: }
0348: }
0349: }
0350: }
0351: }
0352: return packages;
0353: }
0354:
0355: private static double calcPackageWeight(DispatchContext dctx,
0356: Map packageMap, List shippableItemInfo,
0357: double additionalWeight) {
0358:
0359: LocalDispatcher dispatcher = dctx.getDispatcher();
0360: double totalWeight = 0.00;
0361: String defaultWeightUomId = UtilProperties.getPropertyValue(
0362: "shipment.properties", "shipment.default.weight.uom");
0363: if (UtilValidate.isEmpty(defaultWeightUomId)) {
0364: Debug
0365: .logWarning(
0366: "No shipment.default.weight.uom set in shipment.properties, setting it to WT_oz for USPS",
0367: module);
0368: defaultWeightUomId = "WT_oz";
0369: }
0370:
0371: Iterator i = packageMap.keySet().iterator();
0372: while (i.hasNext()) {
0373: String productId = (String) i.next();
0374: Map productInfo = getProductItemInfo(shippableItemInfo,
0375: productId);
0376: double productWeight = ((Double) productInfo.get("weight"))
0377: .doubleValue();
0378: double quantity = ((Double) packageMap.get(productId))
0379: .doubleValue();
0380:
0381: // DLK - I'm not sure if this line is working. shipment_package seems to leave this value null so???
0382: String weightUomId = (String) productInfo
0383: .get("weight_uom_id");
0384:
0385: Debug.logInfo("Product Id : " + productId.toString()
0386: + " Product Weight : "
0387: + String.valueOf(productWeight)
0388: + " Product UomId : " + weightUomId + " assuming "
0389: + defaultWeightUomId + " if null. Quantity : "
0390: + String.valueOf(quantity), module);
0391:
0392: if (UtilValidate.isEmpty(weightUomId)) {
0393: weightUomId = defaultWeightUomId;
0394: // Most shipping modules assume pounds while ProductEvents.java assumes WT_oz. - Line 720 for example.
0395: }
0396: if (!"WT_lb".equals(weightUomId)) {
0397: // attempt a conversion to pounds
0398: Map result = new HashMap();
0399: try {
0400: result = dispatcher.runSync("convertUom", UtilMisc
0401: .toMap("uomId", weightUomId, "uomIdTo",
0402: "WT_lb", "originalValue",
0403: new Double(productWeight)));
0404: } catch (GenericServiceException ex) {
0405: Debug.logError(ex, module);
0406: }
0407:
0408: if (result.get(ModelService.RESPONSE_MESSAGE).equals(
0409: ModelService.RESPOND_SUCCESS)) {
0410: productWeight = ((Double) result
0411: .get("convertedValue")).doubleValue();
0412: } else {
0413: Debug
0414: .logError(
0415: "Unsupported weightUom ["
0416: + weightUomId
0417: + "] for calcPackageWeight running productId "
0418: + productId
0419: + ", could not find a conversion factor to WT_lb",
0420: module);
0421: }
0422:
0423: }
0424:
0425: totalWeight += (productWeight * quantity);
0426: }
0427: Debug.logInfo("Package Weight : " + String.valueOf(totalWeight)
0428: + " lbs.", module);
0429: return totalWeight + additionalWeight;
0430: }
0431:
0432: // lifted from UpsServices with no changes - 2004.09.06 JFE
0433: private static Map getProductItemInfo(List shippableItemInfo,
0434: String productId) {
0435: if (shippableItemInfo != null) {
0436: Iterator i = shippableItemInfo.iterator();
0437: while (i.hasNext()) {
0438: Map testMap = (Map) i.next();
0439: String id = (String) testMap.get("productId");
0440: if (productId.equals(id)) {
0441: return testMap;
0442: }
0443: }
0444: }
0445: return null;
0446: }
0447:
0448: /*
0449:
0450: Track/Confirm Samples: (API=TrackV2)
0451:
0452: Request:
0453: <TrackRequest USERID="xxxxxxxx" PASSWORD="xxxxxxxx">
0454: <TrackID ID="EJ958083578US"></TrackID>
0455: </TrackRequest>
0456:
0457: Response:
0458: <TrackResponse>
0459: <TrackInfo ID="EJ958083578US">
0460: <TrackSummary>Your item was delivered at 8:10 am on June 1 in Wilmington DE 19801.</TrackSummary>
0461: <TrackDetail>May 30 11:07 am NOTICE LEFT WILMINGTON DE 19801.</TrackDetail>
0462: <TrackDetail>May 30 10:08 am ARRIVAL AT UNIT WILMINGTON DE 19850.</TrackDetail>
0463: <TrackDetail>May 29 9:55 am ACCEPT OR PICKUP EDGEWATER NJ 07020.</TrackDetail>
0464: </TrackInfo>
0465: </TrackResponse>
0466:
0467: */
0468:
0469: public static Map uspsTrackConfirm(DispatchContext dctx, Map context) {
0470:
0471: Document requestDocument = createUspsRequestDocument("TrackRequest");
0472:
0473: Element trackingElement = UtilXml.addChildElement(
0474: requestDocument.getDocumentElement(), "TrackID",
0475: requestDocument);
0476: trackingElement.setAttribute("ID", (String) context
0477: .get("trackingId"));
0478:
0479: Document responseDocument = null;
0480: try {
0481: responseDocument = sendUspsRequest("TrackV2",
0482: requestDocument);
0483: } catch (UspsRequestException e) {
0484: Debug.log(e, module);
0485: return ServiceUtil
0486: .returnError("Error sending request for USPS Tracking service: "
0487: + e.getMessage());
0488: }
0489:
0490: Element trackInfoElement = UtilXml.firstChildElement(
0491: responseDocument.getDocumentElement(), "TrackInfo");
0492: if (trackInfoElement == null) {
0493: return ServiceUtil
0494: .returnError("Incomplete response from USPS Tracking service: no TrackInfo element found");
0495: }
0496:
0497: Map result = ServiceUtil.returnSuccess();
0498:
0499: result.put("trackingSummary", UtilXml.childElementValue(
0500: trackInfoElement, "TrackSummary"));
0501:
0502: List detailElementList = UtilXml.childElementList(
0503: trackInfoElement, "TrackDetail");
0504: if (UtilValidate.isNotEmpty(detailElementList)) {
0505: List trackingDetailList = new ArrayList();
0506: for (Iterator iter = detailElementList.iterator(); iter
0507: .hasNext();) {
0508: trackingDetailList.add(UtilXml
0509: .elementValue((Element) iter.next()));
0510: }
0511: result.put("trackingDetailList", trackingDetailList);
0512: }
0513:
0514: return result;
0515: }
0516:
0517: /*
0518:
0519: Address Standardization Samples: (API=Verify)
0520:
0521: Request:
0522: <AddressValidateRequest USERID="xxxxxxx" PASSWORD="xxxxxxx">
0523: <Address ID="0">
0524: <Address1></Address1>
0525: <Address2>6406 Ivy Lane</Address2>
0526: <City>Greenbelt</City>
0527: <State>MD</State>
0528: <Zip5></Zip5>
0529: <Zip4></Zip4>
0530: </Address>
0531: </AddressValidateRequest>
0532:
0533: Response:
0534: <AddressValidateResponse>
0535: <Address ID="0">
0536: <Address2>6406 IVY LN</Address2>
0537: <City>GREENBELT</City>
0538: <State>MD</State>
0539: <Zip5>20770</Zip5>
0540: <Zip4>1440</Zip4>
0541: </Address>
0542: </AddressValidateResponse>
0543:
0544: Note:
0545: The service parameters address1 and addess2 follow the OFBiz naming convention,
0546: and are converted to USPS conventions internally
0547: (OFBiz address1 = USPS address2, OFBiz address2 = USPS address1)
0548:
0549: */
0550:
0551: public static Map uspsAddressValidation(DispatchContext dctx,
0552: Map context) {
0553:
0554: Document requestDocument = createUspsRequestDocument("AddressValidateRequest");
0555:
0556: Element addressElement = UtilXml.addChildElement(
0557: requestDocument.getDocumentElement(), "Address",
0558: requestDocument);
0559: addressElement.setAttribute("ID", "0");
0560:
0561: // 38 chars max
0562: UtilXml.addChildElementValue(addressElement, "FirmName",
0563: (String) context.get("firmName"), requestDocument);
0564: // 38 chars max
0565: UtilXml.addChildElementValue(addressElement, "Address1",
0566: (String) context.get("address2"), requestDocument);
0567: // 38 chars max
0568: UtilXml.addChildElementValue(addressElement, "Address2",
0569: (String) context.get("address1"), requestDocument);
0570: // 15 chars max
0571: UtilXml.addChildElementValue(addressElement, "City",
0572: (String) context.get("city"), requestDocument);
0573:
0574: UtilXml.addChildElementValue(addressElement, "State",
0575: (String) context.get("state"), requestDocument);
0576: UtilXml.addChildElementValue(addressElement, "Zip5",
0577: (String) context.get("zip5"), requestDocument);
0578: UtilXml.addChildElementValue(addressElement, "Zip4",
0579: (String) context.get("zip4"), requestDocument);
0580:
0581: Document responseDocument = null;
0582: try {
0583: responseDocument = sendUspsRequest("Verify",
0584: requestDocument);
0585: } catch (UspsRequestException e) {
0586: Debug.log(e, module);
0587: return ServiceUtil
0588: .returnError("Error sending request for USPS Address Validation service: "
0589: + e.getMessage());
0590: }
0591:
0592: Element respAddressElement = UtilXml.firstChildElement(
0593: responseDocument.getDocumentElement(), "Address");
0594: if (respAddressElement == null) {
0595: return ServiceUtil
0596: .returnError("Incomplete response from USPS Address Validation service: no Address element found");
0597: }
0598:
0599: Element respErrorElement = UtilXml.firstChildElement(
0600: respAddressElement, "Error");
0601: if (respErrorElement != null) {
0602: return ServiceUtil
0603: .returnError("The following error was returned by the USPS Address Validation service: "
0604: + UtilXml.childElementValue(
0605: respErrorElement, "Description"));
0606: }
0607:
0608: Map result = ServiceUtil.returnSuccess();
0609:
0610: // Note: a FirmName element is not returned if empty
0611: String firmName = UtilXml.childElementValue(respAddressElement,
0612: "FirmName");
0613: if (UtilValidate.isNotEmpty(firmName)) {
0614: result.put("firmName", firmName);
0615: }
0616:
0617: // Note: an Address1 element is not returned if empty
0618: String address1 = UtilXml.childElementValue(respAddressElement,
0619: "Address1");
0620: if (UtilValidate.isNotEmpty(address1)) {
0621: result.put("address2", address1);
0622: }
0623:
0624: result.put("address1", UtilXml.childElementValue(
0625: respAddressElement, "Address2"));
0626: result.put("city", UtilXml.childElementValue(
0627: respAddressElement, "City"));
0628: result.put("state", UtilXml.childElementValue(
0629: respAddressElement, "State"));
0630: result.put("zip5", UtilXml.childElementValue(
0631: respAddressElement, "Zip5"));
0632: result.put("zip4", UtilXml.childElementValue(
0633: respAddressElement, "Zip4"));
0634:
0635: return result;
0636: }
0637:
0638: /*
0639:
0640: City/State Lookup Samples: (API=CityStateLookup)
0641:
0642: Request:
0643: <CityStateLookupRequest USERID="xxxxxxx" PASSWORD="xxxxxxx">
0644: <ZipCode ID="0">
0645: <Zip5>90210</Zip5>
0646: </ZipCode>
0647: </CityStateLookupRequest>
0648:
0649: Response:
0650: <CityStateLookupResponse>
0651: <ZipCode ID="0">
0652: <Zip5>90210</Zip5>
0653: <City>BEVERLY HILLS</City>
0654: <State>CA</State>
0655: </ZipCode>
0656: </CityStateLookupResponse>
0657:
0658: */
0659:
0660: public static Map uspsCityStateLookup(DispatchContext dctx,
0661: Map context) {
0662:
0663: Document requestDocument = createUspsRequestDocument("CityStateLookupRequest");
0664:
0665: Element zipCodeElement = UtilXml.addChildElement(
0666: requestDocument.getDocumentElement(), "ZipCode",
0667: requestDocument);
0668: zipCodeElement.setAttribute("ID", "0");
0669:
0670: String zipCode = ((String) context.get("zip5")).trim(); // trim leading/trailing spaces
0671:
0672: // only the first 5 digits are used, the rest are ignored
0673: UtilXml.addChildElementValue(zipCodeElement, "Zip5", zipCode,
0674: requestDocument);
0675:
0676: Document responseDocument = null;
0677: try {
0678: responseDocument = sendUspsRequest("CityStateLookup",
0679: requestDocument);
0680: } catch (UspsRequestException e) {
0681: Debug.log(e, module);
0682: return ServiceUtil
0683: .returnError("Error sending request for USPS City/State Lookup service: "
0684: + e.getMessage());
0685: }
0686:
0687: Element respAddressElement = UtilXml.firstChildElement(
0688: responseDocument.getDocumentElement(), "ZipCode");
0689: if (respAddressElement == null) {
0690: return ServiceUtil
0691: .returnError("Incomplete response from USPS City/State Lookup service: no ZipCode element found");
0692: }
0693:
0694: Element respErrorElement = UtilXml.firstChildElement(
0695: respAddressElement, "Error");
0696: if (respErrorElement != null) {
0697: return ServiceUtil
0698: .returnError("The following error was returned by the USPS City/State Lookup service: "
0699: + UtilXml.childElementValue(
0700: respErrorElement, "Description"));
0701: }
0702:
0703: Map result = ServiceUtil.returnSuccess();
0704:
0705: String city = UtilXml.childElementValue(respAddressElement,
0706: "City");
0707: if (UtilValidate.isEmpty(city)) {
0708: return ServiceUtil
0709: .returnError("Incomplete response from USPS City/State Lookup service: no City element found");
0710: }
0711: result.put("city", city);
0712:
0713: String state = UtilXml.childElementValue(respAddressElement,
0714: "State");
0715: if (UtilValidate.isEmpty(state)) {
0716: return ServiceUtil
0717: .returnError("Incomplete response from USPS City/State Lookup service: no State element found");
0718: }
0719: result.put("state", state);
0720:
0721: return result;
0722: }
0723:
0724: /*
0725:
0726: Service Standards Samples:
0727:
0728: Priority Mail: (API=PriorityMail)
0729:
0730: Request:
0731: <PriorityMailRequest USERID="xxxxxxx" PASSWORD="xxxxxxx">
0732: <OriginZip>4</OriginZip>
0733: <DestinationZip>4</DestinationZip>
0734: </PriorityMailRequest>
0735:
0736: Response:
0737: <PriorityMailResponse>
0738: <OriginZip>4</OriginZip>
0739: <DestinationZip>4</DestinationZip>
0740: <Days>1</Days>
0741: </PriorityMailResponse>
0742:
0743: Package Services: (API=StandardB)
0744:
0745: Request:
0746: <StandardBRequest USERID="xxxxxxx" PASSWORD="xxxxxxx">
0747: <OriginZip>4</OriginZip>
0748: <DestinationZip>4</DestinationZip>
0749: </StandardBRequest>
0750:
0751: Response:
0752: <StandardBResponse>
0753: <OriginZip>4</OriginZip>
0754: <DestinationZip>4</DestinationZip>
0755: <Days>2</Days>
0756: </StandardBResponse>
0757:
0758: Note:
0759: When submitting ZIP codes, only the first 3 digits are used.
0760: If a 1- or 2-digit ZIP code is entered, leading zeros are implied.
0761: If a 4- or 5-digit ZIP code is entered, the last digits will be ignored.
0762:
0763: */
0764:
0765: public static Map uspsPriorityMailStandard(DispatchContext dctx,
0766: Map context) {
0767: context.put("serviceType", "PriorityMail");
0768: return uspsServiceStandards(dctx, context);
0769: }
0770:
0771: public static Map uspsPackageServicesStandard(DispatchContext dctx,
0772: Map context) {
0773: context.put("serviceType", "StandardB");
0774: return uspsServiceStandards(dctx, context);
0775: }
0776:
0777: private static Map uspsServiceStandards(DispatchContext dctx,
0778: Map context) {
0779:
0780: String type = (String) context.get("serviceType");
0781: if (!type.matches("PriorityMail|StandardB")) {
0782: return ServiceUtil.returnError("Unsupported service type: "
0783: + type);
0784: }
0785:
0786: Document requestDocument = createUspsRequestDocument(type
0787: + "Request");
0788:
0789: UtilXml.addChildElementValue(requestDocument
0790: .getDocumentElement(), "OriginZip", (String) context
0791: .get("originZip"), requestDocument);
0792: UtilXml
0793: .addChildElementValue(requestDocument
0794: .getDocumentElement(), "DestinationZip",
0795: (String) context.get("destinationZip"),
0796: requestDocument);
0797:
0798: Document responseDocument = null;
0799: try {
0800: responseDocument = sendUspsRequest(type, requestDocument);
0801: } catch (UspsRequestException e) {
0802: Debug.log(e, module);
0803: return ServiceUtil
0804: .returnError("Error sending request for USPS "
0805: + type + " Service Standards service: "
0806: + e.getMessage());
0807: }
0808:
0809: Map result = ServiceUtil.returnSuccess();
0810:
0811: String days = UtilXml.childElementValue(responseDocument
0812: .getDocumentElement(), "Days");
0813: if (UtilValidate.isEmpty(days)) {
0814: return ServiceUtil
0815: .returnError("Incomplete response from USPS "
0816: + type + " Service Standards service: "
0817: + "no Days element found");
0818: }
0819: result.put("days", days);
0820:
0821: return result;
0822: }
0823:
0824: /*
0825:
0826: Domestic Rate Calculator Samples: (API=Rate)
0827:
0828: Request:
0829: <RateRequest USERID="xxxxxx" PASSWORD="xxxxxxx">
0830: <Package ID="0">
0831: <Service>Priority</Service>
0832: <ZipOrigination>20770</ZipOrigination>
0833: <ZipDestination>09021</ZipDestination>
0834: <Pounds>5</Pounds>
0835: <Ounces>1</Ounces>
0836: <Container>None</Container>
0837: <Size>Regular</Size>
0838: <Machinable>False</Machinable>
0839: </Package>
0840: </RateRequest>
0841:
0842: Response:
0843: <RateResponse>
0844: <Package ID="0">
0845: <Service>Priority</Service>
0846: <ZipOrigination>20770</ZipOrigination>
0847: <ZipDestination>09021</ZipDestination>
0848: <Pounds>5</Pounds>
0849: <Ounces>1</Ounces>
0850: <Container>None</Container>
0851: <Size>REGULAR</Size>
0852: <Machinable>FALSE</Machinable>
0853: <Zone>3</Zone>
0854: <Postage>7.90</Postage>
0855: <RestrictionCodes>B-B1-C-D-U</RestrictionCodes>
0856: <RestrictionDescription>
0857: B. Form 2976-A is required for all mail weighing 16 ounces or more, with exceptions noted below.
0858: In addition, mailers must properly complete required customs documentation when mailing any potentially
0859: dutiable mail addressed to an APO or FPO regardless of weight. B1. Form 2976 or 2976-A is required.
0860: Articles are liable for customs duty and/or purchase tax unless they are bona fide gifts intended for
0861: use by military personnel or their dependents. C. Cigarettes and other tobacco products are prohibited.
0862: D. Coffee is prohibited. U. Parcels must weigh less than 16 ounces when addressed to Box R.
0863: </RestrictionDescription>
0864: </Package>
0865: </RateResponse>
0866:
0867: */
0868:
0869: public static Map uspsDomesticRate(DispatchContext dctx, Map context) {
0870:
0871: Document requestDocument = createUspsRequestDocument("RateRequest");
0872:
0873: Element packageElement = UtilXml.addChildElement(
0874: requestDocument.getDocumentElement(), "Package",
0875: requestDocument);
0876: packageElement.setAttribute("ID", "0");
0877:
0878: UtilXml.addChildElementValue(packageElement, "Service",
0879: (String) context.get("service"), requestDocument);
0880: UtilXml.addChildElementValue(packageElement, "ZipOrigination",
0881: (String) context.get("originZip"), requestDocument);
0882: UtilXml
0883: .addChildElementValue(packageElement, "ZipDestination",
0884: (String) context.get("destinationZip"),
0885: requestDocument);
0886: UtilXml.addChildElementValue(packageElement, "Pounds",
0887: (String) context.get("pounds"), requestDocument);
0888: UtilXml.addChildElementValue(packageElement, "Ounces",
0889: (String) context.get("ounces"), requestDocument);
0890:
0891: String container = (String) context.get("container");
0892: if (UtilValidate.isEmpty(container)) {
0893: container = "None";
0894: }
0895: UtilXml.addChildElementValue(packageElement, "Container",
0896: container, requestDocument);
0897:
0898: String size = (String) context.get("size");
0899: if (UtilValidate.isEmpty(size)) {
0900: size = "Regular";
0901: }
0902: UtilXml.addChildElementValue(packageElement, "Size", size,
0903: requestDocument);
0904:
0905: String machinable = (String) context.get("machinable");
0906: if (UtilValidate.isEmpty(machinable)) {
0907: machinable = "False";
0908: }
0909: UtilXml.addChildElementValue(packageElement, "Machinable",
0910: machinable, requestDocument);
0911:
0912: Document responseDocument = null;
0913: try {
0914: responseDocument = sendUspsRequest("Rate", requestDocument);
0915: } catch (UspsRequestException e) {
0916: Debug.log(e, module);
0917: return ServiceUtil
0918: .returnError("Error sending request for USPS Domestic Rate Calculation service: "
0919: + e.getMessage());
0920: }
0921:
0922: Element respPackageElement = UtilXml.firstChildElement(
0923: responseDocument.getDocumentElement(), "Package");
0924: if (respPackageElement == null) {
0925: return ServiceUtil
0926: .returnError("Incomplete response from USPS Domestic Rate Calculation service: no Package element found");
0927: }
0928:
0929: Element respErrorElement = UtilXml.firstChildElement(
0930: respPackageElement, "Error");
0931: if (respErrorElement != null) {
0932: return ServiceUtil
0933: .returnError("The following error was returned by the USPS Domestic Rate Calculation service: "
0934: + UtilXml.childElementValue(
0935: respErrorElement, "Description"));
0936: }
0937:
0938: Map result = ServiceUtil.returnSuccess();
0939:
0940: String zone = UtilXml.childElementValue(respPackageElement,
0941: "Zone");
0942: if (UtilValidate.isEmpty(zone)) {
0943: return ServiceUtil
0944: .returnError("Incomplete response from USPS Domestic Rate Calculation service: no Zone element found");
0945: }
0946: result.put("zone", zone);
0947:
0948: String postage = UtilXml.childElementValue(respPackageElement,
0949: "Postage");
0950: if (UtilValidate.isEmpty(postage)) {
0951: return ServiceUtil
0952: .returnError("Incomplete response from USPS Domestic Rate Calculation service: no Postage element found");
0953: }
0954: result.put("postage", postage);
0955:
0956: String restrictionCodes = UtilXml.childElementValue(
0957: respPackageElement, "RestrictionCodes");
0958: if (UtilValidate.isNotEmpty(restrictionCodes)) {
0959: result.put("restrictionCodes", restrictionCodes);
0960: }
0961:
0962: String restrictionDesc = UtilXml.childElementValue(
0963: respPackageElement, "RestrictionDescription");
0964: if (UtilValidate.isNotEmpty(restrictionCodes)) {
0965: result.put("restrictionDesc", restrictionDesc);
0966: }
0967:
0968: return result;
0969: }
0970:
0971: // Warning: I don't think the following 2 services were completed or fully tested - 2004.09.06 JFE
0972:
0973: /* --- ShipmentRouteSegment services --------------------------------------------------------------------------- */
0974:
0975: public static Map uspsUpdateShipmentRateInfo(DispatchContext dctx,
0976: Map context) {
0977:
0978: GenericDelegator delegator = dctx.getDelegator();
0979: LocalDispatcher dispatcher = dctx.getDispatcher();
0980:
0981: String shipmentId = (String) context.get("shipmentId");
0982: String shipmentRouteSegmentId = (String) context
0983: .get("shipmentRouteSegmentId");
0984:
0985: // ShipmentRouteSegment identifier - used in error messages
0986: String srsKeyString = "[" + shipmentId + ","
0987: + shipmentRouteSegmentId + "]";
0988:
0989: try {
0990: GenericValue shipmentRouteSegment = delegator
0991: .findByPrimaryKey("ShipmentRouteSegment", UtilMisc
0992: .toMap("shipmentId", shipmentId,
0993: "shipmentRouteSegmentId",
0994: shipmentRouteSegmentId));
0995: if (shipmentRouteSegment == null) {
0996: return ServiceUtil.returnError("ShipmentRouteSegment "
0997: + srsKeyString + " not found");
0998: }
0999:
1000: // ensure the carrier is USPS
1001: if (!"USPS".equals(shipmentRouteSegment
1002: .getString("carrierPartyId"))) {
1003: return ServiceUtil
1004: .returnError("The Carrier for ShipmentRouteSegment "
1005: + srsKeyString + ", is not USPS");
1006: }
1007:
1008: // get the origin address
1009: GenericValue originAddress = shipmentRouteSegment
1010: .getRelatedOne("OriginPostalAddress");
1011: if (originAddress == null) {
1012: return ServiceUtil
1013: .returnError("OriginPostalAddress not found for ShipmentRouteSegment ["
1014: + shipmentId
1015: + ":"
1016: + shipmentRouteSegmentId + "]");
1017: }
1018: if (!"USA".equals(originAddress.getString("countryGeoId"))) {
1019: return ServiceUtil.returnError("ShipmentRouteSeqment "
1020: + srsKeyString
1021: + " does not originate from a US address");
1022: }
1023: String originZip = originAddress.getString("postalCode");
1024: if (UtilValidate.isEmpty(originZip)) {
1025: return ServiceUtil
1026: .returnError("ZIP code is missing from the origin postal address"
1027: + " (contactMechId "
1028: + originAddress
1029: .getString("contactMechId")
1030: + ")");
1031: }
1032:
1033: // get the destination address
1034: GenericValue destinationAddress = shipmentRouteSegment
1035: .getRelatedOne("DestPostalAddress");
1036: if (destinationAddress == null) {
1037: return ServiceUtil
1038: .returnError("DestPostalAddress not found for ShipmentRouteSegment "
1039: + srsKeyString);
1040: }
1041: if (!"USA".equals(destinationAddress
1042: .getString("countryGeoId"))) {
1043: return ServiceUtil.returnError("ShipmentRouteSeqment "
1044: + srsKeyString
1045: + " is not destined for a US address");
1046: }
1047: String destinationZip = destinationAddress
1048: .getString("postalCode");
1049: if (UtilValidate.isEmpty(destinationZip)) {
1050: return ServiceUtil
1051: .returnError("ZIP code is missing from the destination postal address"
1052: + " (contactMechId "
1053: + originAddress
1054: .getString("contactMechId")
1055: + ")");
1056: }
1057:
1058: // get the service type from the CarrierShipmentMethod
1059: String shipmentMethodTypeId = shipmentRouteSegment
1060: .getString("shipmentMethodTypeId");
1061: String partyId = shipmentRouteSegment
1062: .getString("carrierPartyId");
1063: String csmKeystring = "[" + shipmentMethodTypeId + ","
1064: + partyId + ",CARRIER]";
1065:
1066: GenericValue carrierShipmentMethod = delegator
1067: .findByPrimaryKey("CarrierShipmentMethod", UtilMisc
1068: .toMap("partyId", partyId, "roleTypeId",
1069: "CARRIER", "shipmentMethodTypeId",
1070: shipmentMethodTypeId));
1071: if (carrierShipmentMethod == null) {
1072: return ServiceUtil.returnError("CarrierShipmentMethod "
1073: + csmKeystring
1074: + " not found for ShipmentRouteSegment "
1075: + srsKeyString);
1076: }
1077: String serviceType = carrierShipmentMethod
1078: .getString("carrierServiceCode");
1079: if (UtilValidate.isEmpty(serviceType)) {
1080: return ServiceUtil
1081: .returnError("carrierServiceCode not found for CarrierShipmentMethod"
1082: + csmKeystring);
1083: }
1084:
1085: // get the packages for this shipment route segment
1086: List shipmentPackageRouteSegList = shipmentRouteSegment
1087: .getRelated("ShipmentPackageRouteSeg", null,
1088: UtilMisc.toList("+shipmentPackageSeqId"));
1089: if (UtilValidate.isEmpty(shipmentPackageRouteSegList)) {
1090: return ServiceUtil
1091: .returnError("No packages found for ShipmentRouteSegment "
1092: + srsKeyString);
1093: }
1094:
1095: double actualTransportCost = 0;
1096:
1097: String carrierDeliveryZone = null;
1098: String carrierRestrictionCodes = null;
1099: String carrierRestrictionDesc = null;
1100:
1101: // send a new request for each package
1102: for (Iterator i = shipmentPackageRouteSegList.iterator(); i
1103: .hasNext();) {
1104:
1105: GenericValue shipmentPackageRouteSeg = (GenericValue) i
1106: .next();
1107: String sprsKeyString = "["
1108: + shipmentPackageRouteSeg
1109: .getString("shipmentId")
1110: + ","
1111: + shipmentPackageRouteSeg
1112: .getString("shipmentPackageSeqId")
1113: + ","
1114: + shipmentPackageRouteSeg
1115: .getString("shipmentRouteSegmentId")
1116: + "]";
1117:
1118: Document requestDocument = createUspsRequestDocument("RateRequest");
1119:
1120: Element packageElement = UtilXml.addChildElement(
1121: requestDocument.getDocumentElement(),
1122: "Package", requestDocument);
1123: packageElement.setAttribute("ID", "0");
1124:
1125: UtilXml.addChildElementValue(packageElement, "Service",
1126: serviceType, requestDocument);
1127: UtilXml.addChildElementValue(packageElement,
1128: "ZipOrigination", originZip, requestDocument);
1129: UtilXml.addChildElementValue(packageElement,
1130: "ZipDestination", destinationZip,
1131: requestDocument);
1132:
1133: GenericValue shipmentPackage = null;
1134: shipmentPackage = shipmentPackageRouteSeg
1135: .getRelatedOne("ShipmentPackage");
1136:
1137: String spKeyString = "["
1138: + shipmentPackage.getString("shipmentId")
1139: + ","
1140: + shipmentPackage
1141: .getString("shipmentPackageSeqId")
1142: + "]";
1143:
1144: // weight elements - Pounds, Ounces
1145: String weightStr = shipmentPackage.getString("weight");
1146: if (UtilValidate.isEmpty(weightStr)) {
1147: return ServiceUtil
1148: .returnError("weight not found for ShipmentPackage "
1149: + spKeyString);
1150: }
1151:
1152: double weight = 0;
1153: try {
1154: weight = Double.parseDouble(weightStr);
1155: } catch (NumberFormatException nfe) {
1156: nfe.printStackTrace(); // TODO: handle exception
1157: }
1158:
1159: String weightUomId = shipmentPackage
1160: .getString("weightUomId");
1161: if (UtilValidate.isEmpty(weightUomId)) {
1162: weightUomId = "WT_lb"; // assume weight is in pounds
1163: }
1164: if (!"WT_lb".equals(weightUomId)) {
1165: // attempt a conversion to pounds
1166: Map result = new HashMap();
1167: try {
1168: result = dispatcher.runSync("convertUom",
1169: UtilMisc.toMap("uomId", weightUomId,
1170: "uomIdTo", "WT_lb",
1171: "originalValue", new Double(
1172: weight)));
1173: } catch (GenericServiceException ex) {
1174: return ServiceUtil.returnError(ex.getMessage());
1175: }
1176:
1177: if (result.get(ModelService.RESPONSE_MESSAGE)
1178: .equals(ModelService.RESPOND_SUCCESS)) {
1179: weight *= ((Double) result
1180: .get("convertedValue")).doubleValue();
1181: } else {
1182: return ServiceUtil
1183: .returnError("Unsupported weightUom ["
1184: + weightUomId
1185: + "] for ShipmentPackage "
1186: + spKeyString
1187: + ", could not find a conversion factor for WT_lb");
1188: }
1189:
1190: }
1191:
1192: double weightPounds = Math.floor(weight);
1193: double weightOunces = Math.ceil(weight * 16 % 16);
1194:
1195: DecimalFormat df = new DecimalFormat("#");
1196: UtilXml.addChildElementValue(packageElement, "Pounds",
1197: df.format(weightPounds), requestDocument);
1198: UtilXml.addChildElementValue(packageElement, "Ounces",
1199: df.format(weightOunces), requestDocument);
1200:
1201: // Container element
1202: GenericValue carrierShipmentBoxType = null;
1203: List carrierShipmentBoxTypes = null;
1204: carrierShipmentBoxTypes = shipmentPackage.getRelated(
1205: "CarrierShipmentBoxType", UtilMisc.toMap(
1206: "partyId", "USPS"), null);
1207:
1208: if (carrierShipmentBoxTypes.size() > 0) {
1209: carrierShipmentBoxType = (GenericValue) carrierShipmentBoxTypes
1210: .get(0);
1211: }
1212:
1213: if (carrierShipmentBoxType != null
1214: && UtilValidate
1215: .isNotEmpty(carrierShipmentBoxType
1216: .getString("packagingTypeCode"))) {
1217: UtilXml.addChildElementValue(packageElement,
1218: "Container", carrierShipmentBoxType
1219: .getString("packagingTypeCode"),
1220: requestDocument);
1221: } else {
1222: // default to "None", for customers using their own package
1223: UtilXml.addChildElementValue(packageElement,
1224: "Container", "None", requestDocument);
1225: }
1226:
1227: // Size element
1228: if (carrierShipmentBoxType != null
1229: && UtilValidate.isNotEmpty("oversizeCode")) {
1230: UtilXml.addChildElementValue(packageElement,
1231: "Size", carrierShipmentBoxType
1232: .getString("oversizeCode"),
1233: requestDocument);
1234: } else {
1235: // default to "Regular", length + girth measurement <= 84 inches
1236: UtilXml.addChildElementValue(packageElement,
1237: "Size", "Regular", requestDocument);
1238: }
1239:
1240: // Although only applicable for Parcel Post, this tag is required for all requests
1241: UtilXml.addChildElementValue(packageElement,
1242: "Machinable", "False", requestDocument);
1243:
1244: Document responseDocument = null;
1245: try {
1246: responseDocument = sendUspsRequest("Rate",
1247: requestDocument);
1248: } catch (UspsRequestException e) {
1249: Debug.log(e, module);
1250: return ServiceUtil
1251: .returnError("Error sending request for USPS Domestic Rate Calculation service: "
1252: + e.getMessage());
1253: }
1254:
1255: Element respPackageElement = UtilXml.firstChildElement(
1256: responseDocument.getDocumentElement(),
1257: "Package");
1258: if (respPackageElement == null) {
1259: return ServiceUtil
1260: .returnError("Incomplete response from USPS Domestic Rate Calculation service: "
1261: + "no Package element found");
1262: }
1263:
1264: Element respErrorElement = UtilXml.firstChildElement(
1265: respPackageElement, "Error");
1266: if (respErrorElement != null) {
1267: return ServiceUtil
1268: .returnError("The following error was returned by the USPS Domestic Rate Calculation "
1269: + "service for ShipmentPackage "
1270: + spKeyString
1271: + ": "
1272: + UtilXml.childElementValue(
1273: respErrorElement,
1274: "Description"));
1275: }
1276:
1277: // update the ShipmentPackageRouteSeg
1278: String postageString = UtilXml.childElementValue(
1279: respPackageElement, "Postage");
1280: if (UtilValidate.isEmpty(postageString)) {
1281: return ServiceUtil
1282: .returnError("Incomplete response from USPS Domestic Rate Calculation service: "
1283: + "missing or empty Postage element");
1284: }
1285:
1286: double postage = 0;
1287: try {
1288: postage = Double.parseDouble(postageString);
1289: } catch (NumberFormatException nfe) {
1290: nfe.printStackTrace(); // TODO: handle exception
1291: }
1292: actualTransportCost += postage;
1293:
1294: shipmentPackageRouteSeg.setString(
1295: "packageTransportCost", postageString);
1296: shipmentPackageRouteSeg.store();
1297:
1298: // if this is the last package, get the zone and APO/FPO restrictions for the ShipmentRouteSegment
1299: if (!i.hasNext()) {
1300: carrierDeliveryZone = UtilXml.childElementValue(
1301: respPackageElement, "Zone");
1302: carrierRestrictionCodes = UtilXml
1303: .childElementValue(respPackageElement,
1304: "RestrictionCodes");
1305: carrierRestrictionDesc = UtilXml.childElementValue(
1306: respPackageElement,
1307: "RestrictionDescription");
1308: }
1309: }
1310:
1311: // update the ShipmentRouteSegment
1312: shipmentRouteSegment.set("carrierDeliveryZone",
1313: carrierDeliveryZone);
1314: shipmentRouteSegment.set("carrierRestrictionCodes",
1315: carrierRestrictionCodes);
1316: shipmentRouteSegment.set("carrierRestrictionDesc",
1317: carrierRestrictionDesc);
1318: shipmentRouteSegment.setString("actualTransportCost",
1319: String.valueOf(actualTransportCost));
1320: shipmentRouteSegment.store();
1321:
1322: } catch (GenericEntityException gee) {
1323: Debug.log(gee, module);
1324: return ServiceUtil
1325: .returnError("Error reading or writing shipment data for the USPS "
1326: + "Domestic Rate Calculation service: "
1327: + gee.getMessage());
1328: }
1329:
1330: return ServiceUtil.returnSuccess();
1331: }
1332:
1333: /*
1334:
1335: Delivery Confirmation Samples:
1336:
1337: <DeliveryConfirmationV2.0Request USERID="xxxxxxx" PASSWORD="xxxxxxxx">
1338: <Option>3</Option>
1339: <ImageParameters></ImageParameters>
1340: <FromName>John Smith</FromName>
1341: <FromFirm>ABC Corp.</FromFirm>
1342: <FromAddress1>Ste 4</FromAddress1>
1343: <FromAddress2>6406 Ivy Lane</FromAddress2>
1344: <FromCity>Greenbelt</FromCity>
1345: <FromState>MD</FromState>
1346: <FromZip5>20770</FromZip5>
1347: <FromZip4>4354</FromZip4>
1348: <ToName>Jane Smith</ToName>
1349: <ToFirm>XYZ Corp.</ToFirm>
1350: <ToAddress1>Apt 303</ToAddress1>
1351: <ToAddress2>4411 Romlon Street</ToAddress2>
1352: <ToCity>Beltsville</ToCity>
1353: <ToState>MD</ToState>
1354: <ToZip5>20705</ToZip5>
1355: <ToZip4>5656</ToZip4>
1356: <WeightInOunces>22</WeightInOunces>
1357: <ServiceType>Parcel Post</ServiceType>
1358: <ImageType>TIF</ImageType>
1359: </DeliveryConfirmationV2.0Request>
1360:
1361: <DeliveryConfirmationV2.0Response>
1362: <DeliveryConfirmationNumber>02805213907052510758</DeliveryConfirmationNumber>
1363: <DeliveryConfirmationLabel>(Base64 encoded data)</DeliveryConfirmationLabel>
1364: </DeliveryConfirmationV2.0Response>
1365:
1366: */
1367:
1368: public static Map uspsDeliveryConfirmation(DispatchContext dctx,
1369: Map context) {
1370:
1371: GenericDelegator delegator = dctx.getDelegator();
1372:
1373: String shipmentId = (String) context.get("shipmentId");
1374: String shipmentRouteSegmentId = (String) context
1375: .get("shipmentRouteSegmentId");
1376:
1377: // ShipmentRouteSegment identifier - used in error messages
1378: String srsKeyString = "[" + shipmentId + ","
1379: + shipmentRouteSegmentId + "]";
1380:
1381: try {
1382: GenericValue shipment = delegator.findByPrimaryKey(
1383: "Shipment", UtilMisc
1384: .toMap("shipmentId", shipmentId));
1385: if (shipment == null) {
1386: return ServiceUtil
1387: .returnError("Shipment not found with ID "
1388: + shipmentId);
1389: }
1390:
1391: GenericValue shipmentRouteSegment = delegator
1392: .findByPrimaryKey("ShipmentRouteSegment", UtilMisc
1393: .toMap("shipmentId", shipmentId,
1394: "shipmentRouteSegmentId",
1395: shipmentRouteSegmentId));
1396: if (shipmentRouteSegment == null) {
1397: return ServiceUtil
1398: .returnError("ShipmentRouteSegment not found with shipmentId "
1399: + shipmentId
1400: + " and shipmentRouteSegmentId "
1401: + shipmentRouteSegmentId);
1402: }
1403:
1404: // ensure the carrier is USPS
1405: if (!"USPS".equals(shipmentRouteSegment
1406: .getString("carrierPartyId"))) {
1407: return ServiceUtil
1408: .returnError("The Carrier for ShipmentRouteSegment "
1409: + srsKeyString + ", is not USPS");
1410: }
1411:
1412: // get the origin address
1413: GenericValue originAddress = shipmentRouteSegment
1414: .getRelatedOne("OriginPostalAddress");
1415: if (originAddress == null) {
1416: return ServiceUtil
1417: .returnError("OriginPostalAddress not found for ShipmentRouteSegment ["
1418: + shipmentId
1419: + ":"
1420: + shipmentRouteSegmentId + "]");
1421: }
1422: if (!"USA".equals(originAddress.getString("countryGeoId"))) {
1423: return ServiceUtil.returnError("ShipmentRouteSeqment "
1424: + srsKeyString
1425: + " does not originate from a US address");
1426: }
1427:
1428: // get the destination address
1429: GenericValue destinationAddress = shipmentRouteSegment
1430: .getRelatedOne("DestPostalAddress");
1431: if (destinationAddress == null) {
1432: return ServiceUtil
1433: .returnError("DestPostalAddress not found for ShipmentRouteSegment "
1434: + srsKeyString);
1435: }
1436: if (!"USA".equals(destinationAddress
1437: .getString("countryGeoId"))) {
1438: return ServiceUtil.returnError("ShipmentRouteSeqment "
1439: + srsKeyString
1440: + " is not destined for a US address");
1441: }
1442:
1443: // get the service type from the CarrierShipmentMethod
1444: String shipmentMethodTypeId = shipmentRouteSegment
1445: .getString("shipmentMethodTypeId");
1446: String partyId = shipmentRouteSegment
1447: .getString("carrierPartyId");
1448:
1449: String csmKeystring = "[" + shipmentMethodTypeId + ","
1450: + partyId + ",CARRIER]";
1451:
1452: GenericValue carrierShipmentMethod = delegator
1453: .findByPrimaryKey("CarrierShipmentMethod", UtilMisc
1454: .toMap("partyId", partyId, "roleTypeId",
1455: "CARRIER", "shipmentMethodTypeId",
1456: shipmentMethodTypeId));
1457: if (carrierShipmentMethod == null) {
1458: return ServiceUtil.returnError("CarrierShipmentMethod "
1459: + csmKeystring
1460: + " not found for ShipmentRouteSegment "
1461: + srsKeyString);
1462: }
1463: String serviceType = carrierShipmentMethod
1464: .getString("carrierServiceCode");
1465: if (UtilValidate.isEmpty(serviceType)) {
1466: return ServiceUtil
1467: .returnError("carrierServiceCode not found for CarrierShipmentMethod"
1468: + csmKeystring);
1469: }
1470:
1471: // get the packages for this shipment route segment
1472: List shipmentPackageRouteSegList = shipmentRouteSegment
1473: .getRelated("ShipmentPackageRouteSeg", null,
1474: UtilMisc.toList("+shipmentPackageSeqId"));
1475: if (UtilValidate.isEmpty(shipmentPackageRouteSegList)) {
1476: return ServiceUtil
1477: .returnError("No packages found for ShipmentRouteSegment "
1478: + srsKeyString);
1479: }
1480:
1481: for (Iterator i = shipmentPackageRouteSegList.iterator(); i
1482: .hasNext();) {
1483:
1484: Document requestDocument = createUspsRequestDocument("DeliveryConfirmationV2.0Request");
1485: Element requestElement = requestDocument
1486: .getDocumentElement();
1487:
1488: UtilXml.addChildElementValue(requestElement, "Option",
1489: "3", requestDocument);
1490: UtilXml.addChildElement(requestElement,
1491: "ImageParameters", requestDocument);
1492:
1493: // From address
1494: if (UtilValidate.isNotEmpty(originAddress
1495: .getString("attnName"))) {
1496: UtilXml.addChildElementValue(requestElement,
1497: "FromName", originAddress
1498: .getString("attnName"),
1499: requestDocument);
1500: UtilXml.addChildElementValue(requestElement,
1501: "FromFirm", originAddress
1502: .getString("toName"),
1503: requestDocument);
1504: } else {
1505: UtilXml.addChildElementValue(requestElement,
1506: "FromName", originAddress
1507: .getString("toName"),
1508: requestDocument);
1509: }
1510: // The following 2 assignments are not typos - USPS address1 = OFBiz address2, USPS address2 = OFBiz address1
1511: UtilXml
1512: .addChildElementValue(requestElement,
1513: "FromAddress1", originAddress
1514: .getString("address2"),
1515: requestDocument);
1516: UtilXml
1517: .addChildElementValue(requestElement,
1518: "FromAddress2", originAddress
1519: .getString("address1"),
1520: requestDocument);
1521: UtilXml.addChildElementValue(requestElement,
1522: "FromCity", originAddress.getString("city"),
1523: requestDocument);
1524: UtilXml.addChildElementValue(requestElement,
1525: "FromState", originAddress
1526: .getString("stateProvinceGeoId"),
1527: requestDocument);
1528: UtilXml.addChildElementValue(requestElement,
1529: "FromZip5", originAddress
1530: .getString("postalCode"),
1531: requestDocument);
1532: UtilXml.addChildElement(requestElement, "FromZip4",
1533: requestDocument);
1534:
1535: // To address
1536: if (UtilValidate.isNotEmpty(destinationAddress
1537: .getString("attnName"))) {
1538: UtilXml.addChildElementValue(requestElement,
1539: "ToName", destinationAddress
1540: .getString("attnName"),
1541: requestDocument);
1542: UtilXml.addChildElementValue(requestElement,
1543: "ToFirm", destinationAddress
1544: .getString("toName"),
1545: requestDocument);
1546: } else {
1547: UtilXml.addChildElementValue(requestElement,
1548: "ToName", destinationAddress
1549: .getString("toName"),
1550: requestDocument);
1551: }
1552: // The following 2 assignments are not typos - USPS address1 = OFBiz address2, USPS address2 = OFBiz address1
1553: UtilXml
1554: .addChildElementValue(requestElement,
1555: "ToAddress1", destinationAddress
1556: .getString("address2"),
1557: requestDocument);
1558: UtilXml
1559: .addChildElementValue(requestElement,
1560: "ToAddress2", destinationAddress
1561: .getString("address1"),
1562: requestDocument);
1563: UtilXml.addChildElementValue(requestElement, "ToCity",
1564: destinationAddress.getString("city"),
1565: requestDocument);
1566: UtilXml.addChildElementValue(requestElement, "ToState",
1567: destinationAddress
1568: .getString("stateProvinceGeoId"),
1569: requestDocument);
1570: UtilXml.addChildElementValue(requestElement, "ToZip5",
1571: destinationAddress.getString("postalCode"),
1572: requestDocument);
1573: UtilXml.addChildElement(requestElement, "ToZip4",
1574: requestDocument);
1575:
1576: GenericValue shipmentPackageRouteSeg = (GenericValue) i
1577: .next();
1578: GenericValue shipmentPackage = shipmentPackageRouteSeg
1579: .getRelatedOne("ShipmentPackage");
1580: String spKeyString = "["
1581: + shipmentPackage.getString("shipmentId")
1582: + ","
1583: + shipmentPackage
1584: .getString("shipmentPackageSeqId")
1585: + "]";
1586:
1587: // WeightInOunces
1588: String weightStr = shipmentPackage.getString("weight");
1589: if (UtilValidate.isEmpty(weightStr)) {
1590: return ServiceUtil
1591: .returnError("weight not found for ShipmentPackage "
1592: + spKeyString);
1593: }
1594:
1595: double weight = 0;
1596: try {
1597: weight = Double.parseDouble(weightStr);
1598: } catch (NumberFormatException nfe) {
1599: nfe.printStackTrace(); // TODO: handle exception
1600: }
1601:
1602: String weightUomId = shipmentPackage
1603: .getString("weightUomId");
1604: if (UtilValidate.isEmpty(weightUomId)) {
1605: // assume weight is in pounds for consistency (this assumption is made in uspsDomesticRate also)
1606: weightUomId = "WT_lb";
1607: }
1608: if (!"WT_oz".equals(weightUomId)) {
1609: // attempt a conversion to pounds
1610: GenericValue uomConversion = delegator
1611: .findByPrimaryKey("UomConversion", UtilMisc
1612: .toMap("uomId", weightUomId,
1613: "uomIdTo", "WT_oz"));
1614: if (uomConversion == null
1615: || UtilValidate.isEmpty(uomConversion
1616: .getString("conversionFactor"))) {
1617: return ServiceUtil
1618: .returnError("Unsupported weightUom ["
1619: + weightUomId
1620: + "] for ShipmentPackage "
1621: + spKeyString
1622: + ", could not find a conversion factor for WT_oz");
1623: }
1624: weight *= uomConversion.getDouble(
1625: "conversionFactor").doubleValue();
1626: }
1627:
1628: DecimalFormat df = new DecimalFormat("#");
1629: UtilXml.addChildElementValue(requestElement,
1630: "WeightInOunces", df.format(Math.ceil(weight)),
1631: requestDocument);
1632:
1633: UtilXml.addChildElementValue(requestElement,
1634: "ServiceType", serviceType, requestDocument);
1635: UtilXml.addChildElementValue(requestElement,
1636: "ImageType", "TIF", requestDocument);
1637: UtilXml.addChildElementValue(requestElement,
1638: "AddressServiceRequested", "True",
1639: requestDocument);
1640:
1641: Document responseDocument = null;
1642: try {
1643: responseDocument = sendUspsRequest(
1644: "DeliveryConfirmationV2", requestDocument);
1645: } catch (UspsRequestException e) {
1646: Debug.log(e, module);
1647: return ServiceUtil
1648: .returnError("Error sending request for USPS Delivery Confirmation service: "
1649: + e.getMessage());
1650: }
1651: Element responseElement = responseDocument
1652: .getDocumentElement();
1653:
1654: Element respErrorElement = UtilXml.firstChildElement(
1655: responseElement, "Error");
1656: if (respErrorElement != null) {
1657: return ServiceUtil
1658: .returnError("The following error was returned by the USPS Delivery Confirmation "
1659: + "service for ShipmentPackage "
1660: + spKeyString
1661: + ": "
1662: + UtilXml.childElementValue(
1663: respErrorElement,
1664: "Description"));
1665: }
1666:
1667: String labelImageString = UtilXml.childElementValue(
1668: responseElement, "DeliveryConfirmationLabel");
1669: if (UtilValidate.isEmpty(labelImageString)) {
1670: return ServiceUtil
1671: .returnError("Incomplete response from the USPS Delivery Confirmation service: "
1672: + "missing or empty DeliveryConfirmationLabel element");
1673: }
1674: shipmentPackageRouteSeg.setBytes("labelImage", Base64
1675: .base64Decode(labelImageString.getBytes()));
1676: String trackingCode = UtilXml.childElementValue(
1677: responseElement, "DeliveryConfirmationNumber");
1678: if (UtilValidate.isEmpty(trackingCode)) {
1679: return ServiceUtil
1680: .returnError("Incomplete response from the USPS Delivery Confirmation service: "
1681: + "missing or empty DeliveryConfirmationNumber element");
1682: }
1683: shipmentPackageRouteSeg.set("trackingCode",
1684: trackingCode);
1685: shipmentPackageRouteSeg.store();
1686: }
1687:
1688: } catch (GenericEntityException gee) {
1689: Debug.log(gee, module);
1690: return ServiceUtil
1691: .returnError("Error reading or writing shipment data for the USPS "
1692: + "Delivery Confirmation service: "
1693: + gee.getMessage());
1694: }
1695:
1696: return ServiceUtil.returnSuccess();
1697: }
1698:
1699: /* ------------------------------------------------------------------------------------------------------------- */
1700:
1701: // testing utility service - remove this
1702: public static Map uspsDumpShipmentLabelImages(DispatchContext dctx,
1703: Map context) {
1704:
1705: GenericDelegator delegator = dctx.getDelegator();
1706:
1707: try {
1708:
1709: String shipmentId = (String) context.get("shipmentId");
1710: String shipmentRouteSegmentId = (String) context
1711: .get("shipmentRouteSegmentId");
1712:
1713: GenericValue shipmentRouteSegment = delegator
1714: .findByPrimaryKey("ShipmentRouteSegment", UtilMisc
1715: .toMap("shipmentId", shipmentId,
1716: "shipmentRouteSegmentId",
1717: shipmentRouteSegmentId));
1718:
1719: List shipmentPackageRouteSegList = shipmentRouteSegment
1720: .getRelated("ShipmentPackageRouteSeg", null,
1721: UtilMisc.toList("+shipmentPackageSeqId"));
1722:
1723: for (Iterator i = shipmentPackageRouteSegList.iterator(); i
1724: .hasNext();) {
1725: GenericValue shipmentPackageRouteSeg = (GenericValue) i
1726: .next();
1727:
1728: byte[] labelImageBytes = shipmentPackageRouteSeg
1729: .getBytes("labelImage");
1730:
1731: String outFileName = "UspsLabelImage"
1732: + shipmentRouteSegment.getString("shipmentId")
1733: + "_"
1734: + shipmentRouteSegment
1735: .getString("shipmentRouteSegmentId")
1736: + "_"
1737: + shipmentPackageRouteSeg
1738: .getString("shipmentPackageSeqId")
1739: + ".gif";
1740:
1741: FileOutputStream fileOut = new FileOutputStream(
1742: outFileName);
1743: fileOut.write(labelImageBytes);
1744: fileOut.flush();
1745: fileOut.close();
1746: }
1747:
1748: } catch (GenericEntityException e) {
1749: Debug.log(e, module);
1750: return ServiceUtil.returnError(e.getMessage());
1751: } catch (IOException e) {
1752: Debug.log(e, module);
1753: return ServiceUtil.returnError(e.getMessage());
1754: }
1755:
1756: return ServiceUtil.returnSuccess();
1757: }
1758:
1759: private static Document createUspsRequestDocument(String rootElement) {
1760:
1761: Document requestDocument = UtilXml
1762: .makeEmptyXmlDocument(rootElement);
1763:
1764: Element requestElement = requestDocument.getDocumentElement();
1765: requestElement.setAttribute("USERID", UtilProperties
1766: .getPropertyValue("shipment.properties",
1767: "shipment.usps.access.userid"));
1768: requestElement.setAttribute("PASSWORD", UtilProperties
1769: .getPropertyValue("shipment.properties",
1770: "shipment.usps.access.password"));
1771:
1772: return requestDocument;
1773: }
1774:
1775: private static Document sendUspsRequest(String requestType,
1776: Document requestDocument) throws UspsRequestException {
1777: String conUrl = UtilProperties.getPropertyValue(
1778: "shipment.properties", "shipment.usps.connect.url");
1779: if (UtilValidate.isEmpty(conUrl)) {
1780: throw new UspsRequestException(
1781: "Connection URL not specified; please check your configuration");
1782: }
1783:
1784: OutputStream os = new ByteArrayOutputStream();
1785:
1786: OutputFormat format = new OutputFormat(requestDocument);
1787: format.setOmitDocumentType(true);
1788: format.setOmitXMLDeclaration(true);
1789: format.setIndenting(false);
1790:
1791: XMLSerializer serializer = new XMLSerializer(os, format);
1792: try {
1793: serializer.asDOMSerializer();
1794: serializer.serialize(requestDocument.getDocumentElement());
1795: } catch (IOException e) {
1796: throw new UspsRequestException(
1797: "Error serializing requestDocument: "
1798: + e.getMessage());
1799: }
1800:
1801: String xmlString = os.toString();
1802:
1803: Debug.logInfo("USPS XML request string: " + xmlString, module);
1804:
1805: String timeOutStr = UtilProperties.getPropertyValue(
1806: "shipment.properties", "shipment.usps.connect.timeout",
1807: "60");
1808: int timeout = 60;
1809: try {
1810: timeout = Integer.parseInt(timeOutStr);
1811: } catch (NumberFormatException e) {
1812: Debug.logError(e, "Unable to set timeout to " + timeOutStr
1813: + " using default " + timeout);
1814: }
1815:
1816: HttpClient http = new HttpClient(conUrl);
1817: http.setTimeout(timeout * 1000);
1818: http.setParameter("API", requestType);
1819: http.setParameter("XML", xmlString);
1820:
1821: String responseString = null;
1822: try {
1823: responseString = http.get();
1824: } catch (HttpClientException e) {
1825: throw new UspsRequestException(
1826: "Problem connecting with USPS server", e);
1827: }
1828:
1829: Debug.logInfo("USPS response: " + responseString, module);
1830:
1831: Document responseDocument = null;
1832: try {
1833: responseDocument = UtilXml.readXmlDocument(responseString,
1834: false);
1835: } catch (SAXException se) {
1836: throw new UspsRequestException(
1837: "Error reading request Document from a String: "
1838: + se.getMessage());
1839: } catch (ParserConfigurationException pce) {
1840: throw new UspsRequestException(
1841: "Error reading request Document from a String: "
1842: + pce.getMessage());
1843: } catch (IOException xmlReadException) {
1844: throw new UspsRequestException(
1845: "Error reading request Document from a String: "
1846: + xmlReadException.getMessage());
1847: }
1848:
1849: // If a top-level error document is returned, throw exception
1850: // Other request-level errors should be handled by the caller
1851: Element responseElement = responseDocument.getDocumentElement();
1852: if ("Error".equals(responseElement.getNodeName())) {
1853: throw new UspsRequestException(UtilXml.childElementValue(
1854: responseElement, "Description"));
1855: }
1856:
1857: return responseDocument;
1858: }
1859: }
1860:
1861: class UspsRequestException extends GeneralException {
1862: UspsRequestException() {
1863: super ();
1864: }
1865:
1866: UspsRequestException(String msg) {
1867: super (msg);
1868: }
1869:
1870: UspsRequestException(Throwable t) {
1871: super (t);
1872: }
1873:
1874: UspsRequestException(String msg, Throwable t) {
1875: super(msg, t);
1876: }
1877: }
|