Order File Processing

This cookbook page shows how to use a Camel Processor for enriching XML messages.

Situation

We want to do a workflow which polls order files (as xml) from a given folder, routing them to a Camel service which evaluates the order items and fills in the missing information to the order items. After that we send the enriched order to a file sender writing it back to a given folder.

This is a very simple example showing how to route messages from some JBI endpoint to a Camel service and enriching the data of that message before routing it back to another JBI endpoint.

The order format

Request

The request has the following very simple format to keep this example easy to overview:

order.xml
<order id="2425332123456">
   <item id="atg112x777" />
   <item id="xcx333c231" />
</order>

There is just an order id and the item ids. With the help of the item ids we are now able to find out the amount of available pieces of that item in our storage.

Response

After enriching the available pieces the order looks like this:

order.xml
<order id="2425332123456">
   <item id="atg112x777" pieces="3"/>
   <item id="xcx333c231" pieces="7"/>
</order>

You can see the pieces attribute containing the amount of available pieces.

Requirements

For this example we need 3 endpoints configured in ServiceMix.

  • a file poller endpoint
  • a camel endpoint
  • a file sender endpoint

You may put the file poller and sender together into a single service unit. We divided them here for a better overview.

The file poller

The file poller will poll a specific folder for new order files and routes them to the camel service.

xbean.xml
<?xml version="1.0"?>
<beans xmlns:f="http://servicemix.apache.org/file/1.0"
       xmlns:ex="http://servicemix.apache.org/example"
       xmlns:sm="http://servicemix.apache.org/config/1.0">

  <f:poller service="ex:filePoller"
            endpoint="pollerEndpoint"
            file="file:///home/lhein/orders/input/"
            targetService="ex:orderProcessing" />

</beans>

The camel service

This service is responsible for enriching the orders received from the file poller service and sending them back to the file sender.

The Camel Context

camel-context.xml
<?xml version="1.0"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://activemq.apache.org/camel/schema/spring http://activemq.apache.org/camel/schema/spring/camel-spring.xsd">
    
  <camelContext id="camelContext" useJmx="true" xmlns="http://activemq.apache.org/camel/schema/spring">
    <package>org.apache.servicemix.orderprocessing</package> 
  </camelContext> 

</beans>

The processor

OrderProcessor.java
/**
 * 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.servicemix.orderprocessing;

import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * A Camel Router
 */
public class OrderProcessing extends RouteBuilder {

    private static final transient Log LOG = LogFactory.getLog(OrderProcessing.class);

    /*
     * (non-Javadoc)
     * @see org.apache.camel.builder.RouteBuilder#configure()
     */
    public void configure() {
        // from our camelService which is called by the file poller
        from("jbi:service:http://servicemix.apache.org/example/orderProcessing")
        // we process the received order xml
        .process(new OrderProcessor())
        // and send the result xml back to the file sender
        .to("jbi:service:http://servicemix.apache.org/example/fileSender");     
    }
    
    /**
     * the order processor
     */
    class OrderProcessor implements Processor {
        /*
         * (non-Javadoc)
         * @see org.apache.camel.Processor#process(org.apache.camel.Exchange)
         */
        public void process(Exchange exchange) throws Exception {
            // get the document from the in message
            Document doc = exchange.getIn().getBody(Document.class);
            
            // get the order element
            Element order = doc.getDocumentElement();
            
            // determine the order id
            String orderID = order.getAttribute("id");
            
            LOG.info("Received order with id " + orderID);
            
            // now grab all items of the order and process them
            NodeList items = order.getElementsByTagName("item");
            for (int cnt = 0; cnt < items.getLength(); cnt++) {
                // grab the item from the list of items
                Element item = (Element)items.item(cnt);
                // and ask for the amount of available pieces
                processItemRequest(orderID, item);
            }
            
            // finally set the result to the out message body
            exchange.getOut().setBody(doc);
        }
        
        /**
         * processes the determining of the amount of pieces
         * 
         * @param orderID   the order id
         * @param item      the item to check available pieces
         */
        private void processItemRequest(String orderID, Element item) {
            // first get the item id
            String itemId = item.getAttribute("id");
            
            LOG.info("[Order " + orderID + "]: Requesting available pieces for item-id: " + itemId);

            // ask for the amount of pieces available for that item
            int pieces = getAvailablePieces(itemId);
            
            // put the amount of available pieces to the item element
            item.setAttribute("pieces", "" + pieces);
            
            LOG.info("[Order " + orderID + "]: There are " + pieces + " pieces available...");
        }
        
        /**
         * a dummy method to calculate the available pieces of a requested item
         * 
         * @param itemId    the item id
         * @return          the number of available pieces in our storage
         */
        private int getAvailablePieces(String itemId) {
            // we just use random values for now
            return (int) (Math.random() * 10.0);
        }
    }
}

Configuring the route

The following snippet configures the routing behaviour of the camel service.

The route
/*
 * (non-Javadoc)
 * @see org.apache.camel.builder.RouteBuilder#configure()
 */
public void configure() {
   // from our camelService which is called by the file poller
   from("jbi:service:http://servicemix.apache.org/example/orderProcessing")
   // we process the received order xml
   .process(new OrderProcessor())
   // and send the result xml back to the file sender
   .to("jbi:service:http://servicemix.apache.org/example/fileSender");     
}
FROM

The from statement defines where the message comes from. You remember we set the targetService in the file poller configuration to "ex:orderProcessing" where "ex" is "http://servicemix.apache.org/example".
Looking at the from statement you will find exactly this service name right after the prefix jbi:service which is responsible for accessing JBI endpoints from inside Camel.
You may wonder that we didn't defined an endpoint for that service inside some xbean.xml file. Don't worry. The endpoint will be created automatically if it is not existing in the JBI container.

PROCESS

Here we create a processor which is responsible for evaluating the message exchange and preparing the answer to that message. In this case we create an instance of a inner class called OrderProcessor. This processor has to implement the Processor interface.

TO

The to statement defines the service / endpoint to send the result of the processing to. In this case we define a service called "http://servicemix.apache.org/example/fileSender". You will find it defined in the file sender service unit (service="ex:fileSender").

Retrieving the request content

Take a look at the following snippet:

Getting the order
/*
 * (non-Javadoc)
 * @see org.apache.camel.Processor#process(org.apache.camel.Exchange)
 */
 public void process(Exchange exchange) throws Exception {

    // get the document from the in message
    Document doc = exchange.getIn().getBody(Document.class);

    ...
 }

The received message exchange is handed over to the process method. This exchange is not a ServiceMix message exchange object but a Camel Exchange. In the above code snippet we get the body of the IN message and automatically try to convert it to a DOM document. Camel has some nice converter logic which tries to convert to the given class. (here Document.class) For more details on that have a look at the Apache Camel project.

Processing

Well, this is very simple. We iterate all items of the received order and determine how many pieces are currently available. That information will be added as pieces attribute of the item. To have it as simple as possible we just calculate a random number for the pieces number.

Preparing the response

Take a look at the following snippet:

Setting the response
/*
 * (non-Javadoc)
 * @see org.apache.camel.Processor#process(org.apache.camel.Exchange)
 */
 public void process(Exchange exchange) throws Exception {

    ...

    // finally set the result to the out message body
    exchange.getOut().setBody(doc);
 }

You can see that we just get the OUT message of the given exchange and fill the body with the DOM document. Again the converter magic of Camel plays a role here. It converts the document automatically into a suitable object for the body of the exchange.

Maybe you are wondering now because you know that the file poller sends a InOnly exchange for the polled file and that this exchange can't have an Out-Message...yes, you are right. But under the hood the camel creates another InOnly exchange and uses this for the response. So you do not need to care about MEP's here at all. Just use the getIn() to get the request and use getOut() to set the response.

The file sender

The file sender will write the enriched orders received from the Camel order processor to the given folder.

xbean.xml
<?xml version="1.0"?>
<beans xmlns:f="http://servicemix.apache.org/file/1.0"
       xmlns:ex="http://servicemix.apache.org/example"
       xmlns:sm="http://servicemix.apache.org/config/1.0">

  <f:sender service="ex:fileSender"
            endpoint="senderEndpoint"
            directory="file:///home/lhein/orders/output/" />

</beans>

Conclusion

You should be able now to drop an order file to the poll folder and see the processing at the servicemix console output. The result should appear in your send folder very soon.

  • No labels