When customizing Elastic Path the most common form of Java-code level customization is integration with an external fulfillment service. This integration can take many forms, depending on your architecture: SOAP or Restful Web services, JMS messages, flat files, RMI calls, etc.
Such integration is always tricky, and usually painful. It doesn’t have to be.
The Elastic Path checkout architecture has become much more modularized, making it very easy to implement a new CheckoutAction where your fulfillment integration can start, but you still have to implement the code to make the call.
This is where Apache Camel can make your life much easier, by abstracting the plumbing of such a call from your Java code.
This post will show how you can use Apache Camel and Apache CXF to call an external SOAP-based fulfillment Web service using a few simple lines of code in your CheckoutAction.
First, a couple of assumptions:
- You have a WSDL for the Web service
- You have created an extension core project to extend the Elastic Path Core project
Now, create a new Maven project that will generate the Web service code and expose it to your application via a JAR file. The following pom.xml file can be used as a starting point:
| WS Client Project pom.xml |
|---|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <!-- to compile with 1.5 --> |
This pom assumes you have placed the Web service’s WSDL file in the src/main/resources/wsdl folder under your project folder. It uses the Apache CXF plugin to generate the sources from the WSDL.
Executing “mvn install” will then create the JAR file, containing the classes and the WSDL file, and install it into your local Maven repository.
Add a dependency on this library in your core extension project.
Next, in your extension core project, add a couple other new dependencies:
| Core Extension Dependencies |
|---|
| <!—Properties for the new dependencies. --> <properties> <cxf-version>2.5.0</cxf-version> <camel-version>2.8.3</camel-version> </properties> <dependencies> … <!-- camel & cxf --> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-core</artifactId> <version>${camel-version}</version> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-spring</artifactId> <version>${camel-version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>${cxf-version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>${cxf-version}</version> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-cxf</artifactId> <version>${camel-version}</version> </dependency> … <dependencies> |
Now, implement a type converter class that will be used by Camel to convert the Elastic Path Order object to the object used by the Web service:
| Type Converter |
|---|
import org.apache.camel.Converter;
@Converter
@Converter
com.fulfillmentcompany.fulfillmentservice.orderfulfillment.Order fulfillmentOrder = new com.fulfillmentcompany.fulfillmentservice.orderfulfillment.Order();
// Copy the EP Order data into the fulfillmentOrder here.
return fulfillmentOrder; }
} |
Then add a file called TypeConverter (with no extension) to the src\main\resources\META-INF\services\org\apache\camel in your core extension project. It should contain a single line, the fully qualified class name of your converter. For example:
| TypeConverter |
|---|
com.mycompany.coreextension.camel.converter.EpOrderToFulfillmentOrderConverter |
Next implement a Camel Route builder class that will route the EP Order to the Web service:
| Route Builder |
|---|
import org.apache.camel.builder.RouteBuilder;
public class OrderFulfillmentRouteImpl extends RouteBuilder {
@Override
final String toEndpoint = "cxf://http://externalservicehost/webservice/fulfillorder"
from ("direct:startorderfulfillment") }
} |
This class defines a Camel endpoint called “direct:startorderfulfillment”. It first converts the data sent to the endpoint, which should be an EP Order object, to the Order object expected by the fulfillment Web service. Camel is smart enough to find the type converter you implemented earlier and uses that to do the conversion. Finally it sends that converted object to the Web service. It refers to the WSDL file from the class path, which is provided by the client project created earlier.
The Web service’s path, service name, port name and class name will be dependent on your situation.
Now implement your CheckoutAction to send the EP order to Camel:
| Checkout Action |
|---|
import org.apache.camel.CamelContext;
public class SendToFulfillmentCheckoutActionImpl implements FinalizeCheckoutAction {
public void execute(final FinalizeCheckoutActionContext context) throws EpSystemException {
// Get Camel context bean. CamelContext camel = (CamelContext) beanFactory.getBean("camel");
Object out = null;
try {
// Check order response and any codes here. } |
This class sends the EP Order object to the Camel endpoint called “direct:startorderfulfillment”, which we defined in the route builder class we implemented earlier.
Finally, add the following Spring bean configuration in your core extension project’s plugin.xml file:
| Spring Configuration (plugin.xml) |
|---|
<beans xmlns="http://www.springframework.org/schema/beans" ... <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring"> ... </beans> |
Make sure to add http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd to the xsi:schemaLocation attribute.
The <package> element must specify the name of the package where your Camel route builder class is implemented. Camel will automatically pick it up and configure it.
That’s it. Compile and run your storefront and watch the orders pile up.
The really great thing about this design is that you can implement any endpoint you wish in the Camel route builder and never need to modify your checkout service. Camel includes components for sending to JMS queues, RMI services, flat files, etc.
In your route, you can also include any number of endpoints, whether for logging/auditing purposes, messaging multiple services, etc.
You can also configure the routes in your Spring configuration file without having to implement a custom route builder class, but that’s a personal preference.
Enjoy the Camel ride.