When you upload and run a pipeline with a script node that uses a
WebReference2
object to invoke a WSDL file, Salesforce B2C Commerce
automatically generates classes from the WSDL using Apache CXF.
The generated classes are automatically added to the webreferences2
package. You use the generated classes in a B2C Commerce script to call the web service
operations and process any response from the web service.
B2C Commerce implements SOAP web services through the dw.ws
package
port
, webreference2
, and WSUtil
classes.
Overview
The diagram below assumes you are using the dw.ws
package to implement web
services.
For additional information about transport-layer and application-layer security, see also Web Service Security.
B2C Commerce also maintains a legacy implementation that uses the
webreferences
object and Axis 1.4 to generate classes from the WSDL.
This legacy implementation is deprecated and will be removed in a future release.
Integrating a web service into your storefront is relatively straightforward.
HelloWorld.wsdl
, then the other files in the directory must
be named HelloWorld.wsdl.properties
, and
HelloWorld.jks
.underscoreBinding=asCharInWord
property to control code
generation. To direct Webreferences2 to use the
underscoreBinding
property, create a properties file in the
same directory as the WSDL file. The name of the property file is
<wsdl_name>.wsdl.properties
, where
<wsdl_name>
is the name of the WSDL file. Also, if
you are migrating from Webreferences to Webreferences2, you can expedite the
process by directing Webreferences2 to generate arrays instead of lists. To
specify that you want to use arrays instead of lists, set
collectionType=indexed
in the
<wsdl_name>.wsdl.properties
properties file.webreferences2
folder in your
cartridge.Add a service definition to your service registry script for the SOAP web service.
{
initServiceClient: function() {
// Storing this in a custom attribute so it can be used later
this.webReference = webreferences.LuhnChecker;
// The return here is the 'svc.serviceClient' in subsequent calls
return webReference.getDefaultService();
}
createRequest: function(svc:SOAPService, param1) {
var requestObject = new this.webReference.CheckCC();
requestObject.setCardNumber(param1);
return requestObject;
},
execute: function(svc:SOAPService, requestObject) {
return svc.serviceClient.checkCC(requestObject);
},
parseResponse: function(svc:SOAPService, responseObject) {
var responseWrapper = {};
responseWrapper.type = responseObject.checkCCResult.cardType;
responseWrapper.valid = responseObject.checkCCResult.cardValid;
return responseWrapper;
}
}
The ServiceDefinition must specify:
initServiceClient callback to get the following using the
dw.ws
package:
WebReference2
objectThis callback must also set the serviceClient property for the Service.
The webreference2 object is required to generate the classes for the WSDL, which you must use to call the web service. When you have created this callback you can call the pipeline to generate the API for the WSDL. You might want to skip to the steps where you develop the pipeline and run it, so that you have the WSDL API available, and then finish the task of creating the service definition.
dw.ws
package:CheckFraud.WSDL
service and a
pkcs12 keystore type, the keystore must be named
CheckFraud.pkcs12
.In your B2C Commerce script that invokes the web service, create a request and response security configuration. The security configuration is a HashMap, whose first element defines the actions you want B2C Commerce to take: whether to add a timestamp, encrypt the message, sign the message, or other actions. The other elements in the HashMap assign a value to constants defined for the WSUtil class.
Create a separate security configuration for the
request messages and the response messages and pass
them both into the setWSSecurityConfig(port
: Object, requestConfigMap : Map,
responseConfigMap : Map)
class to set the
request and response security configuration for the
web service.
Example:
// define a map with all the secrets
var secretsMap : Map = new HashMap();
secretsMap.put("myclientkey", "ckpass");
secretsMap.put("myservicekey", "ckpass");
secretsMap.put("username", "password");
var requestCfg : Map = new HashMap();
// define the ws actions to be performed - in this case add a username token, timestamp,
// sign and encrypt the message
requestCfg.put(WSUtil.WS_ACTION, WSUtil.WS_USERNAME_TOKEN + " " +
WSUtil.WS_TIMESTAMP + " " +
WSUtil.WS_SIGNATURE + " " +
WSUtil.WS_ENCRYPT);
requestCfg.put(WSUtil.WS_USER, "username");
requestCfg.put(WSUtil.WS_PASSWORD_TYPE, WSUtil.WS_PW_DIGEST );
requestCfg.put(WSUtil.WS_SIG_DIGEST_ALGO, "http://www.w3.org/2001/04/xmlenc#sha256" );
// define signature properties
// the keystore file has the basename of the WSDL file and the
// file extension based on the keystore type (for example, HelloWorld.jks).
// The keystore file has to be placed beside the WSDL file.
requestCfg.put(WSUtil.WS_SIG_PROP_KEYSTORE_TYPE, "jks");
requestCfg.put(WSUtil.WS_SIG_PROP_KEYSTORE_PW, "cspass");
requestCfg.put(WSUtil.WS_SIG_PROP_KEYSTORE_ALIAS, "myclientkey");
requestCfg.put(WSUtil.WS_SIGNATURE_USER, "myclientkey");
// define enrcryption properties
requestCfg.put(WSUtil.WS_ENC_PROP_KEYSTORE_TYPE, "jks");
requestCfg.put(WSUtil.WS_ENC_PROP_KEYSTORE_PW, "cspass");
requestCfg.put(WSUtil.WS_ENC_PROP_KEYSTORE_ALIAS, "myservicekey");
requestCfg.put(WSUtil.WS_ENCRYPTION_USER, "myservicekey");
requestCfg.put(WSUtil.WS_SIGNATURE_PARTS, "{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body");
requestCfg.put(WSUtil.WS_ENCRYPTION_PARTS,"{Element}{" + WSU_NS + "}Timestamp;"+"{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body");
// set the secrets for the callback
requestCfg.put(WSUtil.WS_SECRETS_MAP, secretsMap);
var responseCfg : Map = new HashMap();
// define the ws actions to be performed for the response
responseCfg.put(WSUtil.WS_ACTION, WSUtil.WS_TIMESTAMP + " " +
WSUtil.WS_SIGNATURE + " " +
WSUtil.WS_ENCRYPT);
// define signature properties
responseCfg.put(WSUtil.WS_SIG_PROP_KEYSTORE_TYPE, "jks");
responseCfg.put(WSUtil.WS_SIG_PROP_KEYSTORE_PW, "cspass");
responseCfg.put(WSUtil.WS_SIG_PROP_KEYSTORE_ALIAS, "myservicekey");
responseCfg.put(WSUtil.WS_SIGNATURE_USER, "myservicekey");
// define decryption properties
responseCfg.put(WSUtil.WS_ENC_PROP_KEYSTORE_TYPE, "jks");
responseCfg.put(WSUtil.WS_ENC_PROP_KEYSTORE_PW, "cspass");
responseCfg.put(WSUtil.WS_ENC_PROP_KEYSTORE_ALIAS, "myclientkey");
responseCfg.put(WSUtil.WS_ENCRYPTION_USER, "myclientkey");
// set the secrets for the callback
responseCfg.put(WSUtil.WS_SECRETS_MAP, secretsMap);
// set the security
WSUtil.setWSSecurityConfig(port, requestCfg, responseCfg);
webreferences2
object. When the classes are
generated, however, you can call the script using an OCAPI hook.
However, if the WSDL changes and you need to generate a new
version of the classes, you must run the pipeline again.www.mycompany.com/default/Pipeline-Start
webreferences2
object. When the classes are generated,
however, you can call the script using an OCAPI hook. However, if the WSDL
changes and you need to generate a new version of the classes, you must run the
pipeline again.WebReference2
script class generation
process uses the name of the WSDL service and port elements to create a class
representing the service or port. If the service or port name contains an
underscore character, the generated class name might or might not contain the
underscore character based on naming rules used in the code generation process.
Regardless of this, calling WebReferences2.getDefaultService()
or WebReferences2.getService(String, String)
resolves the
service and port element names to the corresponding script classes.When calling a web service from a script file, B2C Commerce generates
dw.ws.Port
and supporting classes, using the default namespace
webreferences2.<wsdl_file_name>.
If your web service WSDL has many different types with the same name, compilation errors
can occur because the type classes are all put in the same namespace package. To resolve
this issue, you can specify that you want B2C Commerce to generate a namespace-aware
Port
and supporting classes, by including a properties file in the
same location as your WSDL file. In this properties file, you specify the property as
follows:
namespace=true
For B2C Commerce to apply namespace support, the properties file name must be specified as follows:
<wsdl_file_name>.wsdl.properties
For example, if your WSDL file is HelloWorld.wsdl
, the properties file
must be HelloWorld.wsdl.properties
and it must be placed in the same
webreferences2
directory as the WSDL file itself.
In your B2C Commerce script file, create objects using the WebReference2 class. For
example, if the namespace for HelloWorld.wsdl
is
com.test.wsdl
, creating objects for the namespace requires the
qualified name to be:
var webRef : WebReferences2 = webreference2.HelloWorld;
var request : new webRef.com.test.wsdl.HelloRequest();
var svc = webRef.getDefaultService();
var response = webRef.hello(request);
Q: I am seeing an error that references security headers and is similar to the following: "com.ibm.wsspi.wssecurity.SoapSecurityException: WSEC....."
A: The request requires a username and password token in the request header. Make sure when constructing this request that the correct credentials (as provided by the web service being connected to) are included.
Q: I am seeing an error that references 'FaultMessage', however, everything looks syntactically correct. What is wrong and how can I fix it?
_Elemen
t' suffix. What this means is that, for a WSDL element
'fmt_FaultMessage
' there is a class with the name
'Fmt_FaultMessage_Element.java
' created (when executing wsdl2java).
The WSDL and code to reference this must be adjusted accordingly. To do this, modify the
WSDL to change all references to (in this example) 'fmt_FaultMessage
':
<xsd:element name="fmt_FaultMessage"> <wsdl:message name="fmt_FaultMessage">
Q: I am seeing an error that the WSDL file can't be located in B2C Commerce, but I know the WSDL file is there. The error is: "Script exception in line 42:org.mozilla.javascript.EcmaError: ReferenceError: WSDL file for webreference2 'example' doesn't exist"
A: Make sure the that WSDL file is in the cartridge’s webreferences2
directory and make sure the pipeline executing/calling it's in the same cartridge (as
the WSDL file).
Q: I am having difficulty invoking a method that expects an ArrayOf type as a parameter, how can I get this to work?
A: To pass an ArrayOf structure as a parameter to a method in a script, one should
follow the following conventions (keeping in mind that the exact implementation will
vary depending on your specific code/logic):
request.setSomething([theArray]);
or
request.setSomething(new Array(x))
;
Q: I am trying to integrate with a web service that has fields defined as dateTime and keep getting this error: "Cannot convert [Calendar id=19206138] to java.util.Calendar "
A: All variables have setter methods. Salesforce recommends always using setter methods. However, when using primitive types it isn't necessary, but by using setters you will always be safe.
Q: I am running into some data-type specific issues. Which data-types are not supported?
<element name="NewOrderResponse">
<complexType>
<sequence>
<element name="return" type="ns:NewOrderResponseElement" minOccurs="1" maxOccurs="1"/>
</sequence>
</complexType>
</element>
response = service.newOrder(newOrder);
var result = response.['_return'];
With an underscore in the name, you can access all the properties in the object.
Errors shown for names without an Underscore
Attempting to access the element causes some of the following errors:
Example 1:
response = service.newOrder(newOrder); var result = response.return;
Throws
the following
error:org.mozilla.javascript.EvaluatorException: missing
response = service.newOrder(newOrder);
Throws the following
error:var result = response.['return']; Unknown dynamic property 'return' for class