blob: f84ecd634de6b751333361b5f2bf603e4eb21290 [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*******************************************************************************/
package org.apache.ofbiz.shipment.thirdparty.dhl;
import java.io.IOException;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.ofbiz.base.util.Base64;
import org.apache.ofbiz.base.util.Debug;
import org.apache.ofbiz.base.util.GeneralException;
import org.apache.ofbiz.base.util.HttpClient;
import org.apache.ofbiz.base.util.HttpClientException;
import org.apache.ofbiz.base.util.StringUtil;
import org.apache.ofbiz.base.util.UtilDateTime;
import org.apache.ofbiz.base.util.UtilMisc;
import org.apache.ofbiz.base.util.UtilProperties;
import org.apache.ofbiz.base.util.UtilValidate;
import org.apache.ofbiz.base.util.UtilXml;
import org.apache.ofbiz.content.content.ContentWorker;
import org.apache.ofbiz.entity.Delegator;
import org.apache.ofbiz.entity.GenericEntityException;
import org.apache.ofbiz.entity.GenericValue;
import org.apache.ofbiz.entity.util.EntityQuery;
import org.apache.ofbiz.entity.util.EntityUtilProperties;
import org.apache.ofbiz.service.DispatchContext;
import org.apache.ofbiz.service.GenericServiceException;
import org.apache.ofbiz.service.LocalDispatcher;
import org.apache.ofbiz.service.ModelService;
import org.apache.ofbiz.service.ServiceUtil;
import org.apache.ofbiz.shipment.shipment.ShipmentServices;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
/**
* DHL ShipmentServices
*
* <p>Implementation of DHL US domestic shipment interface using DHL ShipIT XML APi.</p>
*
* Shipment services not supported in DHL ShipIT 1.1
* <ul>
* <li>Multiple Piece shipping (Shipment must not have more than one
* ShipmentPackage)</li>
* <li>Dynamic editing of previously submitted shipment (void first then
* resubmit instead)</li>
* <li>Label size 4"x6"</li>
* <li>Out of origin shipping</li>
* </ul>
*
* TODO: International
*/
public class DhlServices {
public final static String module = DhlServices.class.getName();
public final static String shipmentPropertiesFile = "shipment.properties";
public final static String DHL_WEIGHT_UOM_ID = "WT_lb"; // weight Uom used by DHL
public static final String resourceError = "ProductUiLabels";
/**
* Opens a URL to DHL and makes a request.
*
* @param xmlString Name of the DHL service to invoke
* @param delegator the delegator
* @param shipmentGatewayConfigId the shipment gateway config id
* @param resource the resource file (i.e. shipment.properties)
* @param locale locale in use
* @return XML string response from DHL
* @throws DhlConnectException
*/
public static String sendDhlRequest(String xmlString, Delegator delegator, String shipmentGatewayConfigId,
String resource, Locale locale) throws DhlConnectException {
String conStr = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "connectUrl", resource, "shipment.dhl.connect.url");
if (conStr == null) {
throw new DhlConnectException(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlConnectUrlIncomplete", locale));
}
// xmlString should contain the auth document at the beginning
// all documents require an <?xml version="1.0"?> header
if (xmlString == null) {
throw new DhlConnectException(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlXmlCannotBeNull", locale));
}
// prepare the connect string
conStr = conStr.trim();
String timeOutStr = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "connectTimeout",
resource, "shipment.dhl.connect.timeout", "60");
int timeout = 60;
try {
timeout = Integer.parseInt(timeOutStr);
} catch (NumberFormatException e) {
Debug.logError(e, "Unable to set timeout to " + timeOutStr + " using default " + timeout);
}
if (Debug.verboseOn()) {
Debug.logVerbose("DHL Connect URL : " + conStr, module);
Debug.logVerbose("DHL XML String : " + xmlString, module);
}
HttpClient http = new HttpClient(conStr);
http.setTimeout(timeout * 1000);
String response = null;
try {
response = http.post(xmlString);
} catch (HttpClientException e) {
Debug.logError(e, "Problem connecting with DHL server", module);
throw new DhlConnectException(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlConnectUrlProblem", UtilMisc.toMap("errorString", e), locale), e);
}
if (response == null) {
throw new DhlConnectException(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlReceivedNullResponse", locale));
}
if (Debug.verboseOn()) {
Debug.logVerbose("DHL Response : " + response, module);
}
return response;
}
/*
* Service to obtain a rate estimate from DHL for a shipment. Notes: Only one package per shipment currently supported by DHL ShipIT.
* If this service returns a null shippingEstimateAmount, then the shipment has not been processed
*/
public static Map<String, Object> dhlRateEstimate(DispatchContext dctx, Map<String, ? extends Object> context) {
Delegator delegator = dctx.getDelegator();
LocalDispatcher dispatcher = dctx.getDispatcher();
Locale locale = (Locale) context.get("locale");
// some of these can be refactored
String carrierPartyId = (String) context.get("carrierPartyId");
String shipmentMethodTypeId = (String) context.get("shipmentMethodTypeId");
String shippingContactMechId = (String) context.get("shippingContactMechId");
BigDecimal shippableWeight = (BigDecimal) context.get("shippableWeight");
if (shipmentMethodTypeId.equals("NO_SHIPPING")) {
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("shippingEstimateAmount", null);
return result;
}
// translate shipmentMethodTypeId to DHL service code
String dhlShipmentDetailCode = null;
try {
GenericValue carrierShipmentMethod = EntityQuery.use(delegator).from("CarrierShipmentMethod")
.where("shipmentMethodTypeId", shipmentMethodTypeId, "partyId", carrierPartyId, "roleTypeId", "CARRIER")
.queryOne();
if (carrierShipmentMethod == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlNoCarrierShipmentMethod",
UtilMisc.toMap("carrierPartyId", carrierPartyId, "shipmentMethodTypeId", shipmentMethodTypeId), locale));
}
dhlShipmentDetailCode = carrierShipmentMethod.getString("carrierServiceCode");
} catch (GenericEntityException e) {
Debug.logError(e, "Failed to get rate estimate: " + e.getMessage(), module);
}
String resource = (String) context.get("serviceConfigProps");
String shipmentGatewayConfigId = (String) context.get("shipmentGatewayConfigId");
// shipping credentials (configured in properties)
String userid = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "accessUserId", resource, "shipment.dhl.access.userid");
String password = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "accessPassword", resource, "shipment.dhl.access.password");
String shippingKey = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "accessShippingKey", resource, "shipment.dhl.access.shippingKey");
String accountNbr = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "accessAccountNbr", resource, "shipment.dhl.access.accountNbr");
if ((shippingKey == null) || (accountNbr == null) || (shippingKey.length() == 0) || (accountNbr.length() == 0)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlGatewayNotAvailable", locale));
}
// obtain the ship-to address
GenericValue shipToAddress = null;
if (shippingContactMechId != null) {
try {
shipToAddress = EntityQuery.use(delegator).from("PostalAddress").where("contactMechId", shippingContactMechId).queryOne();
if (shipToAddress == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentUnableFoundShipToAddresss", locale));
}
}
catch (GenericEntityException e) {
Debug.logError(e, module);
}
}
if ((shippableWeight == null) || (shippableWeight.compareTo(BigDecimal.ZERO) <= 0)) {
String tmpValue = EntityUtilProperties.getPropertyValue(shipmentPropertiesFile, "shipment.default.weight.value", delegator);
if (tmpValue != null) {
try {
shippableWeight = new BigDecimal(tmpValue);
} catch (Exception e) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlDefaultShippableWeightNotConfigured", locale));
}
}
}
// TODO: if a weight UOM is passed in, use convertUom service to convert it here
if (shippableWeight.compareTo(BigDecimal.ONE) < 0) {
Debug.logWarning("DHL Estimate: Weight is less than 1 lb, submitting DHL minimum of 1 lb for estimate.", module);
shippableWeight = BigDecimal.ONE;
}
if ((dhlShipmentDetailCode.equals("G") && shippableWeight.compareTo(new BigDecimal("999")) > 0) || (shippableWeight.compareTo(new BigDecimal("150")) > 0)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlShippableWeightExceed", locale));
}
String weight = (Integer.valueOf((int) shippableWeight.longValue())).toString();
// create AccessRequest XML doc using FreeMarker template
String templateName = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "rateEstimateTemplate", resource, "shipment.dhl.template.rate.estimate");
if ((templateName == null) || (templateName.trim().length() == 0)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlShipmentTemplateLocationNotFound", locale));
}
StringWriter outWriter = new StringWriter();
Map<String, Object> inContext = new HashMap<String, Object>();
inContext.put("action", "RateEstimate");
inContext.put("userid", userid);
inContext.put("password", password);
inContext.put("accountNbr", accountNbr);
inContext.put("shippingKey", shippingKey);
inContext.put("shipDate", UtilDateTime.nowTimestamp());
inContext.put("dhlShipmentDetailCode", dhlShipmentDetailCode);
inContext.put("weight", weight);
inContext.put("state", shipToAddress.getString("stateProvinceGeoId"));
// DHL ShipIT API does not accept ZIP+4
if ((shipToAddress.getString("postalCode") != null) && (shipToAddress.getString("postalCode").length() > 5)) {
inContext.put("postalCode", shipToAddress.getString("postalCode").substring(0,5));
} else {
inContext.put("postalCode", shipToAddress.getString("postalCode"));
}
try {
ContentWorker.renderContentAsText(dispatcher, delegator, templateName, outWriter, inContext, locale, "text/plain", null, null, false);
} catch (Exception e) {
Debug.logError(e, "Cannot get DHL Estimate: Failed to render DHL XML Request.", module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlShipmentTemplateError", locale));
}
String requestString = outWriter.toString();
if (Debug.verboseOn()) {
Debug.logVerbose(requestString, module);
}
// send the request
String rateResponseString = null;
try {
rateResponseString = sendDhlRequest(requestString, delegator, shipmentGatewayConfigId, resource, locale);
if (Debug.verboseOn()) {
Debug.logVerbose(rateResponseString, module);
}
}
catch (DhlConnectException e) {
String uceErrMsg = "Error sending DHL request for DHL Service Rate: " + e.toString();
Debug.logError(e, uceErrMsg, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlShipmentTemplateSendingError",
UtilMisc.toMap("errorString", e.toString()), locale));
}
Document rateResponseDocument = null;
try {
rateResponseDocument = UtilXml.readXmlDocument(rateResponseString, false);
return handleDhlRateResponse(rateResponseDocument, locale);
}
catch (SAXException e2) {
String excErrMsg = "Error parsing the RatingServiceResponse: " + e2.toString();
Debug.logError(e2, excErrMsg, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexShipmentTemplateParsingError",
UtilMisc.toMap("errorString", e2.toString()), locale));
}
catch (ParserConfigurationException e2) {
String excErrMsg = "Error parsing the RatingServiceResponse: " + e2.toString();
Debug.logError(e2, excErrMsg, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexShipmentTemplateParsingError",
UtilMisc.toMap("errorString", e2.toString()), locale));
}
catch (IOException e2) {
String excErrMsg = "Error parsing the RatingServiceResponse: " + e2.toString();
Debug.logError(e2, excErrMsg, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexShipmentTemplateParsingError",
UtilMisc.toMap("errorString", e2.toString()), locale));
}
}
/*
* Parses an XML document from DHL to get the rate estimate
*/
public static Map<String, Object> handleDhlRateResponse(Document rateResponseDocument, Locale locale) {
List<Object> errorList = new LinkedList<Object>();
Map<String, Object> dhlRateCodeMap = new HashMap<String, Object>();
// process RateResponse
Element rateResponseElement = rateResponseDocument.getDocumentElement();
DhlServices.handleErrors(rateResponseElement, errorList, locale);
if (UtilValidate.isNotEmpty(errorList)) {
return ServiceUtil.returnError(errorList);
}
// handle Response element info
Element responseElement = UtilXml.firstChildElement(rateResponseElement, "Shipment");
Element responseEstimateDetailElement = UtilXml.firstChildElement(responseElement, "EstimateDetail");
DhlServices.handleErrors(responseElement, errorList, locale);
if (UtilValidate.isNotEmpty(errorList)) {
return ServiceUtil.returnError(errorList);
}
String dateGenerated = UtilXml.childElementValue(
responseEstimateDetailElement, "DateGenerated");
Element responseServiceLevelCommitmentElement = UtilXml
.firstChildElement(responseEstimateDetailElement,
"ServiceLevelCommitment");
String responseServiceLevelCommitmentDescription = UtilXml
.childElementValue(responseServiceLevelCommitmentElement,
"Desc");
Element responseRateEstimateElement = UtilXml.firstChildElement(
responseEstimateDetailElement, "RateEstimate");
String responseTotalChargeEstimate = UtilXml.childElementValue(
responseRateEstimateElement, "TotalChargeEstimate");
Element responseChargesElement = UtilXml.firstChildElement(
responseRateEstimateElement, "Charges");
List<? extends Element> chargeNodeList = UtilXml.childElementList(responseChargesElement,
"Charge");
List<Map<String, String>> chargeList = new LinkedList<Map<String,String>>();
if (UtilValidate.isNotEmpty(chargeNodeList)) {
for (Element responseChargeElement: chargeNodeList) {
Map<String, String> charge = new HashMap<String, String>();
Element responseChargeTypeElement = UtilXml.firstChildElement(
responseChargeElement, "Type");
String responseChargeTypeCode = UtilXml.childElementValue(
responseChargeTypeElement, "Code");
String responseChargeTypeDesc = UtilXml.childElementValue(
responseChargeTypeElement, "Desc");
String responseChargeValue = UtilXml.childElementValue(
responseChargeElement, "Value");
charge.put("chargeTypeCode", responseChargeTypeCode);
charge.put("chargeTypeDesc", responseChargeTypeDesc);
charge.put("chargeValue", responseChargeValue);
chargeList.add(charge);
}
}
BigDecimal shippingEstimateAmount = new BigDecimal(responseTotalChargeEstimate);
dhlRateCodeMap.put("dateGenerated", dateGenerated);
dhlRateCodeMap.put("serviceLevelCommitment",
responseServiceLevelCommitmentDescription);
dhlRateCodeMap.put("totalChargeEstimate", responseTotalChargeEstimate);
dhlRateCodeMap.put("chargeList", chargeList);
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("shippingEstimateAmount", shippingEstimateAmount);
result.put("dhlRateCodeMap", dhlRateCodeMap);
return result;
}
/*
* Register a DHL account for shipping by obtaining the DHL shipping key
*/
public static Map<String, Object> dhlRegisterInquire(DispatchContext dctx, Map<String, ? extends Object> context) {
Delegator delegator = dctx.getDelegator();
String resource = (String) context.get("serviceConfigProps");
String shipmentGatewayConfigId = (String) context.get("shipmentGatewayConfigId");
Locale locale = (Locale) context.get("locale");
Map<String, Object> result = new HashMap<String, Object>();
String postalCode = (String) context.get("postalCode");
String accountNbr = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "accessAccountNbr",
resource, "shipment.dhl.access.accountNbr");
if (accountNbr == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlAccessAccountNbrMandotoryForRegisterAccount", locale));
}
// create AccessRequest XML doc
Document requestDocument = createAccessRequestDocument(delegator, shipmentGatewayConfigId, resource);
String requestString = null;
Element requesElement = requestDocument.getDocumentElement();
Element registerRequestElement = UtilXml.addChildElement(requesElement, "Register", requestDocument);
registerRequestElement.setAttribute("version", "1.0");
registerRequestElement.setAttribute("action", "ShippingKey");
UtilXml.addChildElementValue(registerRequestElement, "AccountNbr", accountNbr, requestDocument);
UtilXml.addChildElementValue(registerRequestElement, "PostalCode", postalCode, requestDocument);
try {
requestString = UtilXml.writeXmlDocument(requestDocument);
Debug.logInfo("AccessRequest XML Document:" + requestString, module);
} catch (IOException e) {
String ioeErrMsg = "Error writing the AccessRequest XML Document to a String: " + e.toString();
Debug.logError(e, ioeErrMsg, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlErrorAccessRequestXmlToString",
UtilMisc.toMap("errorString", e.toString()), locale));
}
// send the request
String registerResponseString = null;
try {
registerResponseString = sendDhlRequest(requestString, delegator, shipmentGatewayConfigId, resource, locale);
Debug.logInfo("DHL request for DHL Register Account:" + registerResponseString, module);
} catch (DhlConnectException e) {
String uceErrMsg = "Error sending DHL request for DHL Register Account: " + e.toString();
Debug.logError(e, uceErrMsg, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlErrorSendingRequestRegisterAccount",
UtilMisc.toMap("errorString", e.toString()), locale));
}
Document registerResponseDocument = null;
try {
registerResponseDocument = UtilXml.readXmlDocument(registerResponseString, false);
result = handleDhlRegisterResponse(registerResponseDocument, locale);
Debug.logInfo("DHL response for DHL Register Account:" + registerResponseString, module);
} catch (SAXException e2) {
String excErrMsg = "Error parsing the RegisterAccountServiceSelectionResponse: " + e2.toString();
Debug.logError(e2, excErrMsg, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlErrorParsingRegisterAccountResponse",
UtilMisc.toMap("errorString", e2.toString()), locale));
} catch (ParserConfigurationException e2) {
String excErrMsg = "Error parsing the RegisterAccountServiceSelectionResponse: " + e2.toString();
Debug.logError(e2, excErrMsg, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlErrorParsingRegisterAccountResponse",
UtilMisc.toMap("errorString", e2.toString()), locale));
} catch (IOException e2) {
String excErrMsg = "Error parsing the RegisterAccountServiceSelectionResponse: " + e2.toString();
Debug.logError(e2, excErrMsg, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlErrorParsingRegisterAccountResponse",
UtilMisc.toMap("errorString", e2.toString()), locale));
}
return result;
}
/*
* Parse response from DHL registration request to get shipping key
*/
public static Map<String, Object> handleDhlRegisterResponse(Document registerResponseDocument, Locale locale) {
List<Object> errorList = new LinkedList<Object>();
// process RegisterResponse
Element registerResponseElement = registerResponseDocument.getDocumentElement();
DhlServices.handleErrors(registerResponseElement, errorList, locale);
if (UtilValidate.isNotEmpty(errorList)) {
return ServiceUtil.returnError(errorList);
}
// handle Response element info
Element responseElement = UtilXml.firstChildElement(registerResponseElement, "Register");
DhlServices.handleErrors(responseElement, errorList, locale);
if (UtilValidate.isNotEmpty(errorList)) {
return ServiceUtil.returnError(errorList);
}
String responseShippingKey = UtilXml.childElementValue(responseElement,"ShippingKey");
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("shippingKey", responseShippingKey);
return result;
}
/*
* Pass a shipment request to DHL via ShipIT and get a tracking number and a label back, among other things
*/
public static Map<String, Object> dhlShipmentConfirm(DispatchContext dctx, Map<String, ? extends Object> context) {
Delegator delegator = dctx.getDelegator();
LocalDispatcher dispatcher = dctx.getDispatcher();
Locale locale = (Locale) context.get("locale");
GenericValue userLogin = (GenericValue) context.get("userLogin");
String shipmentId = (String) context.get("shipmentId");
String shipmentRouteSegmentId = (String) context.get("shipmentRouteSegmentId");
Map<String, Object> shipmentGatewayConfig = ShipmentServices.getShipmentGatewayConfigFromShipment(delegator, shipmentId, locale);
String shipmentGatewayConfigId = (String) shipmentGatewayConfig.get("shipmentGatewayConfigId");
String resource = (String) shipmentGatewayConfig.get("configProps");
if (UtilValidate.isEmpty(shipmentGatewayConfigId) && UtilValidate.isEmpty(resource)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlGatewayNotAvailable", locale));
}
try {
GenericValue shipment = EntityQuery.use(delegator).from("Shipment").where("shipmentId", shipmentId).queryOne();
if (shipment == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"ProductShipmentNotFoundId", locale) + shipmentId);
}
GenericValue shipmentRouteSegment = EntityQuery.use(delegator).from("ShipmentRouteSegment").where("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId).queryOne();
if (shipmentRouteSegment == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"ProductShipmentRouteSegmentNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
if (!"DHL".equals(shipmentRouteSegment.getString("carrierPartyId"))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlNotRouteSegmentCarrier",
UtilMisc.toMap("shipmentRouteSegmentId", shipmentRouteSegmentId, "shipmentId", shipmentId), locale));
}
// add ShipmentRouteSegment carrierServiceStatusId, check before all DHL services
if (UtilValidate.isNotEmpty(shipmentRouteSegment.getString("carrierServiceStatusId")) && !"SHRSCS_NOT_STARTED".equals(shipmentRouteSegment.getString("carrierServiceStatusId"))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlRouteSegmentStatusNotStarted",
UtilMisc.toMap("shipmentRouteSegmentId", shipmentRouteSegmentId, "shipmentId", shipmentId,
"shipmentRouteSegmentStatus", shipmentRouteSegment.getString("carrierServiceStatusId")), locale));
}
// Get Origin Info
GenericValue originPostalAddress = shipmentRouteSegment.getRelatedOne("OriginPostalAddress", false);
if (originPostalAddress == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentRouteSegmentOriginPostalAddressNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
GenericValue originTelecomNumber = shipmentRouteSegment.getRelatedOne("OriginTelecomNumber", false);
if (originTelecomNumber == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentRouteSegmentOriginTelecomNumberNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
String originPhoneNumber = originTelecomNumber.getString("areaCode") + originTelecomNumber.getString("contactNumber");
// don't put on country code if not specified or is the US country code (UPS wants it this way and assuming DHL will accept this)
if (UtilValidate.isNotEmpty(originTelecomNumber.getString("countryCode")) && !"001".equals(originTelecomNumber.getString("countryCode"))) {
originPhoneNumber = originTelecomNumber.getString("countryCode") + originPhoneNumber;
}
originPhoneNumber = StringUtil.replaceString(originPhoneNumber, "-", "");
originPhoneNumber = StringUtil.replaceString(originPhoneNumber, " ", "");
// lookup the two letter country code (in the geoCode field)
GenericValue originCountryGeo = originPostalAddress.getRelatedOne("CountryGeo", false);
if (originCountryGeo == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentRouteSegmentOriginCountryGeoNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
// Get Dest Info
GenericValue destPostalAddress = shipmentRouteSegment.getRelatedOne("DestPostalAddress", false);
if (destPostalAddress == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentRouteSegmentDestPostalAddressNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
// DHL requires destination phone number, default to sender # if no customer number
String destPhoneNumber = originPhoneNumber;
GenericValue destTelecomNumber = shipmentRouteSegment.getRelatedOne("DestTelecomNumber", false);
if (destTelecomNumber != null) {
destPhoneNumber = destTelecomNumber.getString("areaCode") + destTelecomNumber.getString("contactNumber");
// don't put on country code if not specified or is the US country code (UPS wants it this way)
if (UtilValidate.isNotEmpty(destTelecomNumber.getString("countryCode")) && !"001".equals(destTelecomNumber.getString("countryCode"))) {
destPhoneNumber = destTelecomNumber.getString("countryCode") + destPhoneNumber;
}
destPhoneNumber = StringUtil.replaceString(destPhoneNumber, "-", "");
destPhoneNumber = StringUtil.replaceString(destPhoneNumber, " ", "");
}
String recipientEmail = null;
Map<String, Object> results = dispatcher.runSync("getPartyEmail", UtilMisc.toMap("partyId", shipment.get("partyIdTo"), "userLogin", userLogin));
if (results.get("emailAddress") != null) {
recipientEmail = (String) results.get("emailAddress");
}
// lookup the two letter country code (in the geoCode field)
GenericValue destCountryGeo = destPostalAddress.getRelatedOne("CountryGeo", false);
if (destCountryGeo == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentRouteSegmentDestCountryGeoNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
List<GenericValue> shipmentPackageRouteSegs = shipmentRouteSegment.getRelated("ShipmentPackageRouteSeg", null, UtilMisc.toList("+shipmentPackageSeqId"), false);
if (UtilValidate.isEmpty(shipmentPackageRouteSegs)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentPackageRouteSegsNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
if (shipmentPackageRouteSegs.size() != 1) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlMultiplePackagesNotSupported", locale));
}
// get the weight from the ShipmentRouteSegment first, which overrides all later weight computations
boolean hasBillingWeight = false; // for later overrides
BigDecimal billingWeight = shipmentRouteSegment.getBigDecimal("billingWeight");
String billingWeightUomId = shipmentRouteSegment.getString("billingWeightUomId");
if ((billingWeight != null) && (billingWeight.compareTo(BigDecimal.ZERO) > 0)) {
hasBillingWeight = true;
if (billingWeightUomId == null) {
Debug.logWarning("Shipment Route Segment missing billingWeightUomId in shipmentId " + shipmentId, module);
billingWeightUomId = "WT_lb"; // TODO: this should be specified in a properties file
}
// convert
results = dispatcher.runSync("convertUom", UtilMisc.<String, Object>toMap("uomId", billingWeightUomId, "uomIdTo", DHL_WEIGHT_UOM_ID, "originalValue", billingWeight));
if (ServiceUtil.isError(results) || (results.get("convertedValue") == null)) {
Debug.logWarning("Unable to convert billing weights for shipmentId " + shipmentId , module);
// try getting the weight from package instead
hasBillingWeight = false;
} else {
billingWeight = (BigDecimal) results.get("convertedValue");
}
}
// loop through Shipment segments (NOTE: only one supported, loop is here for future refactoring reference)
BigDecimal packageWeight = null;
for (GenericValue shipmentPackageRouteSeg: shipmentPackageRouteSegs) {
GenericValue shipmentPackage = shipmentPackageRouteSeg.getRelatedOne("ShipmentPackage", false);
GenericValue shipmentBoxType = shipmentPackage.getRelatedOne("ShipmentBoxType", false);
if (shipmentBoxType != null) {
// TODO: determine what default UoM is (assuming inches) - there should be a defaultDimensionUomId in Facility
}
// next step is weight determination, so skip if we have a billing weight
if (hasBillingWeight) continue;
// compute total packageWeight (for now, just one package)
if (shipmentPackage.getString("weight") != null) {
packageWeight = new BigDecimal(shipmentPackage.getString("weight"));
} else {
// use default weight if available
try {
packageWeight = EntityUtilProperties.getPropertyAsBigDecimal(shipmentPropertiesFile, "shipment.default.weight.value", BigDecimal.ZERO);
} catch (NumberFormatException ne) {
Debug.logWarning("Default shippable weight not configured (shipment.default.weight.value)", module);
packageWeight = BigDecimal.ONE;
}
}
// convert weight
String weightUomId = (String) shipmentPackage.get("weightUomId");
if (weightUomId == null) {
Debug.logWarning("Shipment Route Segment missing weightUomId in shipmentId " + shipmentId, module);
weightUomId = "WT_lb"; // TODO: this should be specified in a properties file
}
results = dispatcher.runSync("convertUom", UtilMisc.<String, Object>toMap("uomId", weightUomId, "uomIdTo", DHL_WEIGHT_UOM_ID, "originalValue", packageWeight));
if ((results == null) || (results.get(ModelService.RESPONSE_MESSAGE).equals(ModelService.RESPOND_ERROR)) || (results.get("convertedValue") == null)) {
Debug.logWarning("Unable to convert weights for shipmentId " + shipmentId , module);
packageWeight = BigDecimal.ONE;
} else {
packageWeight = (BigDecimal) results.get("convertedValue");
}
}
// pick which weight to use and round it
BigDecimal weight = null;
if (hasBillingWeight) {
weight = billingWeight;
} else {
weight = packageWeight;
}
// want the rounded weight as a string, so we use the "" + int shortcut
String roundedWeight = weight.setScale(0, BigDecimal.ROUND_HALF_UP).toPlainString();
// translate shipmentMethodTypeId to DHL service code
String shipmentMethodTypeId = shipmentRouteSegment.getString("shipmentMethodTypeId");
String dhlShipmentDetailCode = null;
GenericValue carrierShipmentMethod = EntityQuery.use(delegator).from("CarrierShipmentMethod")
.where("shipmentMethodTypeId", shipmentMethodTypeId, "partyId", "DHL", "roleTypeId", "CARRIER")
.queryOne();
if (carrierShipmentMethod == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlNoCarrierShipmentMethod",
UtilMisc.toMap("carrierPartyId", "DHL", "shipmentMethodTypeId", shipmentMethodTypeId), locale));
}
dhlShipmentDetailCode = carrierShipmentMethod.getString("carrierServiceCode");
// shipping credentials (configured in properties)
String userid = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "accessUserId", resource, "shipment.dhl.access.userid");
String password = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "accessPassword", resource, "shipment.dhl.access.password");
String shippingKey = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "accessShippingKey", resource, "shipment.dhl.access.shippingKey");
String accountNbr = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "accessAccountNbr", resource, "shipment.dhl.access.accountNbr");
if ((shippingKey == null) || (accountNbr == null) || (shippingKey.length() == 0) || (accountNbr.length() == 0)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlGatewayNotAvailable", locale));
}
// label image preference (PNG or GIF)
String labelImagePreference = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "labelImageFormat", resource, "shipment.dhl.label.image.format");
if (labelImagePreference == null) {
Debug.logInfo("shipment.dhl.label.image.format not specified, assuming PNG", module);
labelImagePreference="PNG";
} else if (!(labelImagePreference.equals("PNG") || labelImagePreference.equals("GIF"))) {
Debug.logError("Illegal shipment.dhl.label.image.format: " + labelImagePreference, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlUnknownLabelImageFormat",
UtilMisc.toMap("labelImagePreference", labelImagePreference), locale));
}
// create AccessRequest XML doc using FreeMarker template
String templateName = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "rateEstimateTemplate", resource, "shipment.dhl.template.rate.estimate");
if ((templateName == null) || (templateName.trim().length() == 0)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlRateEstimateTemplateNotConfigured", locale));
}
StringWriter outWriter = new StringWriter();
Map<String, Object> inContext = new HashMap<String, Object>();
inContext.put("action", "GenerateLabel");
inContext.put("userid", userid);
inContext.put("password", password);
inContext.put("accountNbr", accountNbr);
inContext.put("shippingKey", shippingKey);
inContext.put("shipDate", UtilDateTime.nowTimestamp());
inContext.put("dhlShipmentDetailCode", dhlShipmentDetailCode);
inContext.put("weight", roundedWeight);
inContext.put("senderPhoneNbr", originPhoneNumber);
inContext.put("companyName", destPostalAddress.getString("toName"));
inContext.put("attnTo", destPostalAddress.getString("attnName"));
inContext.put("street", destPostalAddress.getString("address1"));
inContext.put("streetLine2", destPostalAddress.getString("address2"));
inContext.put("city", destPostalAddress.getString("city"));
inContext.put("state", destPostalAddress.getString("stateProvinceGeoId"));
// DHL ShipIT API does not accept ZIP+4
if ((destPostalAddress.getString("postalCode") != null) && (destPostalAddress.getString("postalCode").length() > 5)) {
inContext.put("postalCode", destPostalAddress.getString("postalCode").substring(0,5));
} else {
inContext.put("postalCode", destPostalAddress.getString("postalCode"));
}
inContext.put("phoneNbr", destPhoneNumber);
inContext.put("labelImageType", labelImagePreference);
inContext.put("shipperReference", shipment.getString("primaryOrderId") + "-" + shipment.getString("primaryShipGroupSeqId"));
inContext.put("notifyEmailAddress", recipientEmail);
try {
ContentWorker.renderContentAsText(dispatcher, delegator, templateName, outWriter, inContext, locale, "text/plain", null, null, false);
} catch (Exception e) {
Debug.logError(e, "Cannot confirm DHL shipment: Failed to render DHL XML Request.", module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexRateTemplateRenderingError", locale));
}
String requestString = outWriter.toString();
if (Debug.verboseOn()) {
Debug.logVerbose(requestString, module);
}
// send the request
String responseString = null;
try {
responseString = sendDhlRequest(requestString, delegator, shipmentGatewayConfigId, resource, locale);
if (Debug.verboseOn()) {
Debug.logVerbose(responseString, module);
}
} catch (DhlConnectException e) {
String uceErrMsg = "Error sending DHL request for DHL Service Rate: " + e.toString();
Debug.logError(e, uceErrMsg, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexRateTemplateSendingError",
UtilMisc.toMap("errorString", e.toString()), locale));
}
// pass to handler method
return handleDhlShipmentConfirmResponse(responseString, shipmentRouteSegment, shipmentPackageRouteSegs, locale);
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexRateTemplateReadingError",
UtilMisc.toMap("errorString", e.toString()), locale));
} catch (GenericServiceException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexRateTemplateReadingError",
UtilMisc.toMap("errorString", e.toString()), locale));
}
}
// NOTE: Must VOID shipments on errors
public static Map<String, Object> handleDhlShipmentConfirmResponse(String rateResponseString, GenericValue shipmentRouteSegment,
List<GenericValue> shipmentPackageRouteSegs, Locale locale) throws GenericEntityException {
GenericValue shipmentPackageRouteSeg = shipmentPackageRouteSegs.get(0);
// TODO: figure out how to handle validation on return XML, which can be mangled
// Ideas: try again right away, let user try again, etc.
Document rateResponseDocument = null;
try {
rateResponseDocument = UtilXml.readXmlDocument(rateResponseString, false);
} catch (SAXException e2) {
String excErrMsg = "Error parsing the RatingServiceSelectionResponse: " + e2.toString();
Debug.logError(e2, excErrMsg, module);
// TODO: VOID
} catch (ParserConfigurationException e2) {
String excErrMsg = "Error parsing the RatingServiceSelectionResponse: " + e2.toString();
Debug.logError(e2, excErrMsg, module);
// TODO VOID
} catch (IOException e2) {
String excErrMsg = "Error parsing the RatingServiceSelectionResponse: " + e2.toString();
Debug.logError(e2, excErrMsg, module);
// TODO VOID
}
// tracking number: Shipment/ShipmentDetail/AirbillNbr
Element rootElement = rateResponseDocument.getDocumentElement();
Element shipmentElement = UtilXml.firstChildElement(rootElement, "Shipment");
Element shipmentDetailElement = UtilXml.firstChildElement(shipmentElement, "ShipmentDetail");
String trackingNumber = UtilXml.childElementValue(shipmentDetailElement, "AirbillNbr");
// label: Shipment/Label/Image
Element labelElement = UtilXml.firstChildElement(shipmentElement, "Label");
String encodedImageString = UtilXml.childElementValue(labelElement, "Image");
if (encodedImageString == null) {
Debug.logError("Cannot find response DHL shipment label. Rate response document is: " + rateResponseString, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlShipmentLabelError",
UtilMisc.toMap("shipmentPackageRouteSeg", shipmentPackageRouteSeg,
"rateResponseString", rateResponseString), locale));
}
// TODO: this is a temporary hack to replace the newlines so that Base64 likes the input This is NOT platform independent
int size = encodedImageString.length();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size; i++) {
if (encodedImageString.charAt(i) == '\n')
continue;
sb.append(encodedImageString.charAt(i));
}
byte[] labelBytes = Base64.base64Decode(sb.toString().getBytes());
if (labelBytes != null) {
// store in db blob
shipmentPackageRouteSeg.setBytes("labelImage", labelBytes);
} else {
Debug.logInfo("Failed to either decode returned DHL label or no data found in eCommerce/Shipment/Label/Image.", module);
// TODO: VOID
}
shipmentPackageRouteSeg.set("trackingCode", trackingNumber);
shipmentPackageRouteSeg.set("labelHtml", sb.toString());
shipmentPackageRouteSeg.store();
shipmentRouteSegment.set("trackingIdNumber", trackingNumber);
shipmentRouteSegment.put("carrierServiceStatusId", "SHRSCS_CONFIRMED");
shipmentRouteSegment.store();
return ServiceUtil.returnSuccess(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlShipmentConfirmed", locale));
}
public static Document createAccessRequestDocument(Delegator delegator, String shipmentGatewayConfigId, String resource) {
Document eCommerceRequestDocument = UtilXml.makeEmptyXmlDocument("eCommerce");
Element eCommerceRequesElement = eCommerceRequestDocument.getDocumentElement();
eCommerceRequesElement.setAttribute("version", getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "headVersion", resource, "shipment.dhl.head.version"));
eCommerceRequesElement.setAttribute("action", getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "headAction", resource, "shipment.dhl.head.action"));
Element requestorRequestElement = UtilXml.addChildElement(eCommerceRequesElement, "Requestor", eCommerceRequestDocument);
UtilXml.addChildElementValue(requestorRequestElement, "ID", getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "accessUserId", resource, "shipment.dhl.access.userid"),
eCommerceRequestDocument);
UtilXml.addChildElementValue(requestorRequestElement, "Password", getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "accessPassword", resource, "shipment.dhl.access.password"),
eCommerceRequestDocument);
return eCommerceRequestDocument;
}
public static void handleErrors(Element responseElement, List<Object> errorList, Locale locale) {
Element faultsElement = UtilXml.firstChildElement(responseElement,
"Faults");
List<? extends Element> faultElements = UtilXml.childElementList(faultsElement, "Fault");
if (UtilValidate.isNotEmpty(faultElements)) {
for (Element errorElement: faultElements) {
StringBuilder errorMessageBuf = new StringBuilder();
String errorCode = UtilXml.childElementValue(errorElement, "Code");
String errorDescription = UtilXml.childElementValue(errorElement, "Desc");
String errorSource = UtilXml.childElementValue(errorElement, "Source");
if (UtilValidate.isEmpty(errorSource)) {
errorSource = UtilXml.childElementValue(errorElement, "Context");
}
errorMessageBuf.append(UtilProperties.getMessage(resourceError, "FacilityShipmentDhlErrorMessage",
UtilMisc.toMap("errorCode", errorCode, "errorDescription", errorDescription), locale));
if (UtilValidate.isNotEmpty(errorSource)) {
errorMessageBuf.append(UtilProperties.getMessage(resourceError,
"FacilityShipmentDhlErrorMessageElement",
UtilMisc.toMap("errorSource", errorSource), locale));
}
errorList.add(errorMessageBuf.toString());
}
}
}
private static String getShipmentGatewayConfigValue(Delegator delegator, String shipmentGatewayConfigId, String shipmentGatewayConfigParameterName,
String resource, String parameterName) {
String returnValue = "";
if (UtilValidate.isNotEmpty(shipmentGatewayConfigId)) {
try {
GenericValue dhl = EntityQuery.use(delegator).from("ShipmentGatewayDhl").where("shipmentGatewayConfigId", shipmentGatewayConfigId).queryOne();
if (UtilValidate.isNotEmpty(dhl)) {
Object dhlField = dhl.get(shipmentGatewayConfigParameterName);
if (dhlField != null) {
returnValue = dhlField.toString().trim();
}
}
} catch (GenericEntityException e) {
Debug.logError(e, module);
}
} else {
String value = EntityUtilProperties.getPropertyValue(resource, parameterName, delegator);
if (value != null) {
returnValue = value.trim();
}
}
return returnValue;
}
private static String getShipmentGatewayConfigValue(Delegator delegator, String shipmentGatewayConfigId, String shipmentGatewayConfigParameterName,
String resource, String parameterName, String defaultValue) {
String returnValue = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, shipmentGatewayConfigParameterName, resource, parameterName);
if (UtilValidate.isEmpty(returnValue)) {
returnValue = defaultValue;
}
return returnValue;
}
}
@SuppressWarnings("serial")
class DhlConnectException extends GeneralException {
DhlConnectException() {
super();
}
DhlConnectException(String msg) {
super(msg);
}
DhlConnectException(Throwable t) {
super(t);
}
DhlConnectException(String msg, Throwable t) {
super(msg, t);
}
}