Leveraging Amazon Web Services for enterprise application integration

XML messaging with Amazon SQS

Discover how to leverage XML and Amazon Web Services to integrate enterprise applications, and to build cross-platform application-integration capabilities using the Microsoft® .NET (C#) and Java™ platforms.

Brian J Stewart, Principal Consultant, Aqua Data Technologies, Inc.

Photo of Brian StewartBrian J. Stewart is currently a principal consultant at Aqua Data Technologies, a company that he founded to focus on content management, XML technologies, and enterprise client-server and Web systems. He architects and develops enterprise solutions based on the Java EE and Microsoft .NET platforms. Brian maintains a blog at BrianJStewart.com.



16 June 2009

Also available in Chinese Japanese

A queue is a temporary data structure used to store messages waiting to be processed. Amazon Simple Queue Services (Amazon SQS) is a highly available and scalable message queue that is Web services-enabled. The key benefits of Amazon SQS are:

Frequently used acronyms

  • API: application programming interface
  • DOM: Document Object Model
  • HTTP: Hypertext Transfer Protocol
  • XML: Extensible Markup Language
  • Cloud-based solution. Managed by Amazon, no on-premise infrastructure or support expertise is required.
  • Internet based. The service is accessible through Web services to any client with Internet access, thus enabling business-to-business (B2B) integration.
  • Redundancy. The service stores all messages on multiple servers to provide high availability and fault tolerance.
  • Multiple concurrent reading/writing. Amazon SQS supports multiple processes reading and writing to a queue concurrently and locks messages during the processing window to prevent two clients from processing a message concurrently.
  • Configurable. With the Amazon SQS service, you can design the lock window based on the unique processing requirements of the messages stored in the queue. The lock window prevents two queue readers from processing the same queue item. A longer window is needed for queue items that take longer to process. The lock window is controlled by the visibility timeout parameter which you can configure on a queue-by-queue basis.
  • Simple-to-use API. It provides API wrappers for common languages, including the Java and Microsoft .NET platforms, to enable rapid development and seamless integration into existing applications.
  • Low-cost solution. Companies pay only for bandwidth they use and HTTP requests; Amazon SQS has no minimum fee structure.

It is helpful to know a few characteristics of Amazon SQS before you delve into Amazon SQS development. Without knowledge of these characteristics, you might find working with Amazon SQS to be frustrating and confusing initially.

First, Amazon makes no guarantee of the order in which the queue items are processed. This means that first-in-first-out (FIFO) processing is not guaranteed, as is common with many message queue implementations. Amazon only guarantees that all messages will be delivered.

The second important characteristic of Amazon SQS is the concept of eventual consistency. The key features of large database systems are consistency, high availability, and scalability. Instead of focusing on all three, Amazon chose to focus on high availability and scalability and to provide eventual consistency. This means that Amazon propagates all messages to multiple servers to achieve high availability and scalability. Amazon guarantees that eventually all messages will be delivered but makes no guarantee as to when they will be delivered. From a practical perspective, this means that if you send three messages to a queue, the next time you attempt to receive the messages, there is no guarantee that all three messages will be received. You might receive all three messages in a single Read or two messages in the first Read and the third message in a subsequent Read. If you poll the queue continually, you will receive all three messages eventually.

To get started with Amazon SQS, you must obtain the API library for Amazon SQS based on the language you are using. Amazon provides a library for all common languages, such as Perl, Microsoft Visual Basic®.NET, C#, Java, and PHP. The libraries are open source and simple to use. See Resources for links to download these libraries.

Using Amazon SQS with the Java language

You'll start with how to create a queue, send a message, and receive a message using the Java language. The first step is to create an Amazon SQS queue. The code in Listing 1 shows how to create the HTTP client for Amazon SQS, instantiate the CreateQueueRequest object, and invoke the queue creation request. The Access Key ID is the key (20-character, alphanumeric sequence) needed to request authentication or to read queue items. In order to create or manipulate queue items, you need the Secret Access Key (40-character, alphanumeric sequence). You receive these keys when you register with Amazon. (See Resources for a link to an article on cloud security that describes these keys further.)

Listing 1. Create the Amazon SQS queue
String queueName = "TestQueue";

// create http client
AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);

// instantiate create queue request
CreateQueueRequest request = new CreateQueueRequest();
request.setQueueName(queueName);
request.setDefaultVisibilityTimeout(30);

// execute create queue operation and get the server response
System.out.print("Creating Queue: " + queueName);    
CreateQueueResponse response = service.createQueue(request);
if (response.isSetCreateQueueResult()) {
	System.out.print("Create Queue Result:");    
	CreateQueueResult createQueueResult = response.getCreateQueueResult();
	if (createQueueResult.isSetQueueUrl()) {
		System.out.print("Queue Url: " + createQueueResult.getQueueUrl());
	}
}

The next step is to send a message to the newly created queue. The code in Listing 2 shows how to create the HTTP client for Amazon SQS and how to send a simple message to the queue.

Listing 2. Send a message to the queue
String queueName = "TestQueue";

// create http client
AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);

// instantiate send message request
SendMessageRequest request = new SendMessageRequest();
request.setQueueName(queueName);
request.setMessageBody("Test SQS Message");

// execute the send message operation and get the server response
SendMessageResponse response = service.sendMessage(request);
if (response.isSetSendMessageResult()) {
	System.out.print("Send Message Result: ");

	SendMessageResult sendMessageResult = response.getSendMessageResult();
	if (sendMessageResult.isSetMessageId()) {
		System.out.print("\tMessageId: " + sendMessageResult.getMessageId());
	}
}

Now, receive the message from the queue. Listing 3 shows how to create the HTTP client for Amazon SQS and how to receive a message from the queue. Message contains the message from the queue and exposes several key methods:

  • getMessageId. Returns the unique identifier for the message. You can use isSetMessageId to determine whether the message ID has been set.
  • getReceiptHandle. Returns the handle to the message. The handle is used to delete the message. You can use isSetReceiptHandle to determine whether the message handle has been set.
  • getBody. Returns the message body in a string. The message can be plain text or XML, and you can use isSetBody to determine whether the message body has been set.
Listing 3. Receive a message from the queue
String queueName = "TestQueue";

// create http client
AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);

// instantiate the receive message request
ReceiveMessageRequest request = new ReceiveMessageRequest();
request.setQueueName(queueName);
// the following two parameters are optional
request.setMaxNumberOfMessages(10); // set maximum number of messages to receive
request.setVisibilityTimeout(30); // set visibility window
		 
// execute the receive messages operation and get server response
ReceiveMessageResponse response = service.receiveMessage(request);

System.out.print("Receive Message Response:");

if (response.isSetReceiveMessageResult()) {
	ReceiveMessageResult  receiveMessageResult = response.getReceiveMessageResult();
	java.util.List<Message> messageList = receiveMessageResult.getMessage();
	for (Message message : messageList) {			
		if (message.isSetMessageId()) {
			System.out.print("MessageId: " + message.getMessageId());
		}

		if (message.isSetReceiptHandle()) {
			System.out.print("ReceiptHandle: " + message.getReceiptHandle());
		}
		if (message.isSetBody()) {
			System.out.print("Body: " + message.getBody());
		}
}

Using Amazon SQS with C#

Now, you will serialize an object to XML and send it as an Amazon SQS message using C#.

The first step is to create a business object that will be serialized; Listing 4 shows a Product object. The public properties are decorated with the attributes to control XML serialization. C# attributes, which are similar to Java annotations, define how properties are mapped to XML elements or XML attributes. In addition, the class contains the ToXml() method, which serializes the object instance to XML.

Listing 4. Create a business object for serialization
namespace Stewart.Test
{
/// <summary>
/// Product
/// </summary>
[XmlRoot(ElementName="Product")]
public class Product
{
	/// <summary>
	/// Product Name
	/// </summary>
	[XmlElement("ProductName")]
	public string ProductName;

	/// <summary>
	/// Product Price
	/// </summary>
	[XmlElement("ProductPrice")]
	public decimal ProductPrice;

	/// <summary>
	/// Quantity in stock
	/// </summary>
	[XmlElement("InStock")]
	public bool InStock;

	/// <summary>
	/// Product Id
	/// </summary>
	[XmlAttributeAttribute(AttributeName = "Id", DataType = "integer")]
	public string Id;

	/// <summary>
	/// Initializes a new instance of the <see cref="Product"/> class.
	/// </summary>
	public Product()
	{
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="Product"/> class.
	/// </summary>
	/// <param name="productName">Name of the product.</param>
	/// <param name="productPrice">The product price.</param>
	public Product(string productName, decimal productPrice)
	{
		this.ProductName = productName;
		this.ProductPrice = productPrice;
	}

	/// <summary>
	/// Converts to XML.
	/// </summary>
	/// <returns></returns>
	public String ToXml()
	{
		StringBuilder output = new StringBuilder();

		// no name space
		XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
		ns.Add("", "");

		// settings to omit xml declaration
		XmlWriterSettings settings = new XmlWriterSettings();
		settings.OmitXmlDeclaration = true;

		// finally serialize to string
		XmlWriter writer = XmlTextWriter.Create(output, settings);
		XmlSerializer serializer = new XmlSerializer(typeof(Product));            
		serializer.Serialize(writer, this, ns);

		// return string containing XML document
		return output.ToString();
	}
}

Next, send the XML message. The Amazon C# API for Amazon SQS is functionally equivalent and similar to the Java API. The code in Listing 5 shows how to send the message using C#.

Listing 5. Sending the message using C#
Product prod = new Product("Widget", 1.5M);
string accessKeyId = ConfigurationSettings.AppSettings["AmazonAccessKeyID"];
string secretAccessKey = ConfigurationSettings.AppSettings["AmazonSecretAccessKey"];

AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);
SendMessageRequest request = new SendMessageRequest();
request.MessageBody = prod.ToXml();
request.QueueName = "TestQueue";

SendMessageResponse response = service.SendMessage(request);
if (response.IsSetSendMessageResult())
{
	Console.WriteLine("Send Message Response: ");
	SendMessageResult sendMessageResult = response.SendMessageResult;
	if (sendMessageResult.IsSetMessageId())
	{
		Console.WriteLine(String.Format("MessageId {0}", 
			sendMessageResult.MessageId));
	}
	if (sendMessageResult.IsSetMD5OfMessageBody())
	{
		Console.WriteLine(String.Format("MD5OfMessageBody: {0}", 
			sendMessageResult.MD5OfMessageBody));
	}
}

Figure 1 shows the output of Listing 5.

Figure 1. Send XML Message output
Screen capture of the Send XML Message output

The final step is to receive the XML message from the queue and deserialize the instance. Listing 6 shows the code to deserialize the XML message into a Product instance.

Listing 6. Deserialize the XML message
Product prod = null;            

string accessKeyId = ConfigurationSettings.AppSettings["AmazonAccessKeyID"];
string secretAccessKey = ConfigurationSettings.AppSettings["AmazonSecretAccessKey"];

AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);

ReceiveMessageRequest request = new ReceiveMessageRequest();
request.QueueName = "TestQueue";
ReceiveMessageResponse response = service.ReceiveMessage(request);

if (response.IsSetReceiveMessageResult())
{
	Console.WriteLine("Receive Message Result:");
	ReceiveMessageResult receiveMessageResult = response.ReceiveMessageResult;
	List<Message> messageList = receiveMessageResult.Message;
	foreach (Message message in messageList)
	{                    
		if (message.IsSetMessageId())
		{
			Console.WriteLine(String.Format("MessageId: {0}",
				message.MessageId));
		}                                      
		if (message.IsSetBody())
		{
			Console.WriteLine(string.Format("Body: {0}", message.Body));
			String xml = message.Body;
			StringReader sr = new StringReader(xml);
			XmlSerializer serializer = new XmlSerializer(typeof(Product));
			prod = (Product) serializer.Deserialize(sr);
			Console.WriteLine(string.Format("Id: {0}", prod.Id));
			Console.WriteLine(string.Format("Name: {0}", prod.ProductName));
			Console.WriteLine(string.Format("Price: {0}", prod.ProductPrice));
		}
	}
}

Figure 2 shows the output of Listing 6.

Figure 2. Receive XML Message output
Screen capture of the Receive XML Message output

Although the above sample was very simple, it is quite powerful in that you can serialize an object and send the message to another application whose application boundaries are not constrained to a local physical network. There are no complex firewall constraints or security considerations. In addition, the sender and receiver of the message don't need to be written in the same language or even use the same platform.

Technical overview and design

The demonstration solution consists of a Reseller and a Manufacturer who need to integrate their business processes. The Reseller buys widgets from the Manufacturer and sells them to Customers.

When a Customer wants widgets, the Reseller uses a C# WinForm client to submit a customer order. The order submission program stores the order details in a local MySQL database. The client also enables users to browse inventory, view orders, and view the Amazon SQS message queues.

Normally, the Reseller has enough widgets in inventory to fulfill the customer order. If not, the Reseller sends a purchase order to the Manufacturer in near-real time. The Manufacturer sends back an order summary when the items have been shipped. All communication is done using Amazon SQS.

The Reseller's Order Fulfillment and Inventory Management service, which is also built using C#, polls for incoming merchandise and pending customer orders. If, when processing a customer order, the widget inventory is less than the number ordered, a new purchase order is sent to the Manufacturer using Amazon SQS. The message body for the queue item is an XML document that contains the purchase order.

The Manufacturer's Order Processing Service, which is built on the Java platform, processes the purchase order queue. It sends a message back to the reseller using Amazon SQS when the items have been shipped. The message body is an XML document that contains the order summary.

The diagram in Figure 3 shows the systems involved.

Figure 3. Solution overview
Diagram of solution overview showing Customer, Reseller, Amazon SQS, and Manufacturer.

Create the XML schemas

The first step is to define the XML schemas for the messages sent between the Reseller and the Manufacturer. You need two schemas: a purchase order and an order summary.

Listing 7 shows the schema for the purchase order.

Listing 7. Purchase order schema
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="PurchaseOrder">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Id" type="xs:string" minOccurs="0" />
        <xs:element name="OrderDate" type="xs:string" minOccurs="0" />
        <xs:element name="Company" minOccurs="0" maxOccurs="unbounded">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="CompanyName" type="xs:string" minOccurs="0" />
              <xs:element name="StreetAddress" type="xs:string" minOccurs="0" />
              <xs:element name="City" type="xs:string" minOccurs="0" />
              <xs:element name="State" type="xs:string" minOccurs="0" />
              <xs:element name="ZipCode" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Vendor" minOccurs="0" maxOccurs="unbounded">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="CompanyName" type="xs:string" minOccurs="0" />
              <xs:element name="StreetAddress" type="xs:string" minOccurs="0" />
              <xs:element name="City" type="xs:string" minOccurs="0" />
              <xs:element name="State" type="xs:string" minOccurs="0" />
              <xs:element name="ZipCode" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Items" minOccurs="0" maxOccurs="unbounded">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="Item" minOccurs="0" maxOccurs="unbounded">
                <xs:complexType>
                  <xs:attribute name="Id" type="xs:string" />
                  <xs:attribute name="Name" type="xs:string" />
                  <xs:attribute name="Quantity" type="xs:string" />
                </xs:complexType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>  
</xs:schema>

The Purchase Order XML schema consists of the following key components:

Table 1. Key elements in the purchase order schema
Key elementsDescription
IdString containing the unique identifier of the Purchase Order
OrderDateString containing the date of the Purchase Order
CompanyContains key address information regarding the Reseller including company name, street address, city, state, and zip code
VendorContains key address information regarding the Vendor (Manufacturer) including company name, street address, city, state, and zip code
ItemsContains all items ordered including item id, item name, and quantity

Listing 8 shows the schema for the order summary.

Listing 8. Order summary schema
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="OrderSummary">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="OrderId" type="xs:string" minOccurs="0" />
        <xs:element name="ReferenceId" type="xs:string" minOccurs="0" />
        <xs:element name="OrderDate" type="xs:string" minOccurs="0" />
        <xs:element name="CompanyAddress" minOccurs="0" maxOccurs="unbounded">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="CompanyName" type="xs:string" minOccurs="0" />
              <xs:element name="StreetAddress" type="xs:string" minOccurs="0" />
              <xs:element name="City" type="xs:string" minOccurs="0" />
              <xs:element name="State" type="xs:string" minOccurs="0" />
              <xs:element name="ZipCode" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="CustomerAddress" minOccurs="0" maxOccurs="unbounded">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="CompanyName" type="xs:string" minOccurs="0" />
              <xs:element name="StreetAddress" type="xs:string" minOccurs="0" />
              <xs:element name="City" type="xs:string" minOccurs="0" />
              <xs:element name="State" type="xs:string" minOccurs="0" />
              <xs:element name="ZipCode" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Items" minOccurs="0" maxOccurs="unbounded">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="Item" minOccurs="0" maxOccurs="unbounded">
                <xs:complexType>
                  <xs:attribute name="ItemId" type="xs:string" />
                  <xs:attribute name="ItemName" type="xs:string" />
                  <xs:attribute name="Quantity" type="xs:string" />
                </xs:complexType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>  
</xs:schema>

The Order Summary XML schema consists of the following key components:

Table 2. Key elements in the order summary schema
Key elementsDescription
IdString containing unique identifier of the Order Summary
ReferenceIdString containing id of the original Purchase Order
OrderDateString containing the date of the Order Summary
CustomerAddressContains key address information regarding the Reseller including company name, street address, city, state, and zip code
VendorAddressContains key address information regarding the Vendor (Manufacturer) including company name, street address, city, state, and zip code
ItemsContains all items ordered including item id, item name, and quantity

Define the database entity model

Next, define the database schema. Figure 4 shows the database entity model diagram.

Figure 4. Database entity model
Database entity model that lists attributes for customer, customerorder, customerorderdetail, inventory, and vendororder entities.

The Reseller data entities include the following:

  • Customer contains customer contact information for orders.
  • CustomerOrder contains order information for the customer order.
  • CustomerOrderDetail contains order item details for the customer order.
  • Inventory contains inventory for the Reseller.

The Manufacturer data entity is:

  • VendorOrder tracks the purchase orders processed by the Manufacturer's Order Processing Service.

Define the message queues

The last components that you need to define are the message queues. Table 3 shows the messages queues for this solution.

Table 3. Amazon SMS message queues
Queue nameVisibility timeoutPurpose
POQueue30 secondsPurchase order messages sent from the Reseller to the Manufacturer
OSQueue30 secondsOrder summary messages sent from the Manufacturer to the Reseller

The Reseller implementation

The Reseller application consists of the business entities in Table 4.

Table 4. Business entities in the Reseller application
ClassDescription
CompanyAddressEntityContains a customer or business address
CustomerEntityContains a customer
OrderEntityContains a customer order
OrderDetailEntityContains the item-level detail for a customer order
InventoryItemEntityContains an inventory item
PurchaseOrderEntityContains a purchase order
PurchaseOrderDetailEntityContains the item-level detail for a purchase order

On the Microsoft .NET side, the BaseSQSDataProvider class also provides a utility class for Amazon SQS messaging. The BaseDbProvider provides a utility class for working with MySQL databases.


The Reseller order-management system

The Reseller order-management client is not the focus of this article, but the client is fairly straightforward. It is written in C# and is an n-tier application with all business and data separated from the presentation layer. All form binding is done by using Microsoft .NET's powerful object-binding capabilities. All drop-down lists, grid views, and form fields are bound to the business objects. The code-behind for the Windows® forms is minimal, as the business logic resides in the business logic layer.

Figure 5 shows the client.

Figure 5. Reseller order-management client
Screen capture of Reseller order-management client

The reseller order-fulfillment service

The OrderFulfillmentService class is responsible for processing customer orders and does so based on the polling interval specified in the App.config file. The service retrieves a list of orders not yet processed. For each order in the list, the following actions occur in the corresponding methods:

  • Check whether the order can be shipped—that is, you have sufficient inventory (ProcessPendingOrders method).
  • Process the order if it can be shipped (CanShip method).
  • Place the order on backorder if the order cannot be shipped, and send the purchase order to the Manufacturer (ProcessBackorder method).

Listing 9 shows the ProcessPendingOrders() method.

Listing 9. The ProcessPendingOrders() method
	public int ProcessPendingOrders()
	{
		int itemsProcessed = 0;

		// get orders not yet shipped
		CustomerOrderFactory factory = new CustomerOrderFactory();
		IList<OrderEntity> orders = factory.GetOrdersNotYetShipped();

		// iterate through all orders not processed
		IEnumerator<OrderEntity> ordersEnum = orders.GetEnumerator();
		while (ordersEnum.MoveNext()) {
			// get next order
			OrderEntity curOrder = ordersEnum.Current;
			Console.WriteLine(string.Format("Processing Order '{0}'...", 
				curOrder.Id));

			// check if merchandise is available to ship
			if (this.CanShip(curOrder)) {
				// process order
				if (this.ProcessOrder(curOrder)) {
					itemsProcessed++;
				}
			}// if can ship order
			else if (!curOrder.IsBackordered){
				// set order to backordered
				if (this.ProcessBackorder(curOrder)) {
					itemsProcessed++;
				}
			} // if can't ship order (not enough merchandise)
		} // while more orders to process

		return itemsProcessed;
	}

To determine whether the order can be processed, the item quantity ordered is checked against the inventory level of each item. Listing 10 shows the CanShip() method.

Listing 10. The CanShip() method
private bool CanShip(OrderEntity order)
{
bool hasMerchandise = true;            

// get items
IEnumerator<OrderDetailEntity> detailEnum = order.GetOrderItems();

// iterate through all items
while (detailEnum.MoveNext())
{
	// get current item
	OrderDetailEntity detailEntry = detailEnum.Current;

	InventoryItemEntity inventoryItem = detailEntry.Item;
	if (detailEntry.Quantity > inventoryItem.Quantity)
	{
		Console.WriteLine(
			string.Format("Order {0} - Insufficient Inventory: {1} ({2})",
			order.Id, inventoryItem.Name, inventoryItem.Id));
		hasMerchandise = false;
	} // if inventory is sufficient
} // while more entries to process

Console.WriteLine(string.Format("Order {0} - Can Ship: {1}", 
	order.Id, hasMerchandise));
return hasMerchandise;
}

If CanShip() returns False and the order has not already been backordered, the ProcessBackorder() method is invoked, and the IsBackordered property of the order is set to True. You use the MessageQueueFactory to create the queue item and send the purchase order message. Listing 11 shows this process.

Listing 11. The ProcessBackorder() method
private bool ProcessBackorder(OrderEntity order)
{
	// set to backordered
	order.IsBackordered = true;       

	// update order
	CustomerOrderFactory factory = new CustomerOrderFactory();
	bool result = factory.UpdateOrder(order);

	if (!result) return result;

	// get purchase order xml
	string poXml = this.GetPurchaseOrderAsXml(order);            

	// create message queue
	MessageQueueFactory queueFactory = new MessageQueueFactory();
	return queueFactory.CreatePOQueueItem(poXml);
}

The PurchaseOrderEntity object is created through the GetPurchaseOrder() method. An OrderDetailEntity is added to the order for each item that has insufficient inventory levels. Listing 12 shows this process.

Listing 12. The GetPurchaseOrder() method
private PurchaseOrderEntity GetPurchaseOrder(OrderEntity order)
{
PurchaseOrderEntity po = new PurchaseOrderEntity();

po.PurchaseDate = DateTime.Now;

// set company address of the Purchase Order - the Reseller
po.CompanyAddress.CompanyName = "The Widget Factory";
po.CompanyAddress.StreetAddress = "100 Main Street";
po.CompanyAddress.CityName = "Las Vegas";
po.CompanyAddress.StateName = "NV";
po.CompanyAddress.ZipCode = "89044";

// set vendor address of the Purchase Order - the Manufacturer
po.VendorAddress.CompanyName = "Widget Supplies";
po.VendorAddress.StreetAddress = "100 Main Street";
po.VendorAddress.CityName = "Orlando";
po.VendorAddress.StateName = "FL";
po.VendorAddress.ZipCode = "32801";

// while more items to process
IEnumerator<OrderDetailEntity> orderEnum = order.GetOrderItems();
while (orderEnum.MoveNext())
{
	OrderDetailEntity orderItem = orderEnum.Current;
	InventoryItemEntity inventoryItem = orderItem.Item;

	// if insufficient inventory
	if (orderItem.Quantity > inventoryItem.Quantity)
	{
		// order the number needed plus 100
		int quantityToOrder = (orderItem.Quantity - inventoryItem.Quantity) + 100;
		PurchaseOrderDetailEntity poItem = new PurchaseOrderDetailEntity();
		poItem.ItemName = inventoryItem.Name;
		poItem.ItemId = inventoryItem.Id;
		poItem.Quantity = quantityToOrder;

		// add item to po
		po.AddItem(poItem);
	}
}

return po;            
}

The next step is to serialize the PurchaseOrderEntity to XML. The Microsoft .NET Framework provides powerful XML serialization capabilities. You create an instance of XmlSerializerNamespaces to set the XML namespace for the output document. You create an instance of XmlWriterSettings to control the XML output. You want to omit the XML declaration from the output because it is being embedded in the message body. You use the XmlTextWriter class to serialize the object to an XML text writer that writes its output to an instance of StringBuilder. Finally, you use the Serialize() method of XmlSerializer to serialize the instance of PurchaseOrderEntity to XML. Listing 13 shows this process.

Listing 13. The GetPurchaseOrderAsXml() method
private string GetPurchaseOrderAsXml(OrderEntity order)
{
// get purchase order
PurchaseOrderEntity po = this.GetPurchaseOrder(order); ;

StringBuilder output = new StringBuilder();

// no name space
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");

// settings to omit 
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;

XmlWriter writer = XmlTextWriter.Create(output, settings);
XmlSerializer serializer = new XmlSerializer(typeof(PurchaseOrderEntity));
serializer.Serialize(writer, po, ns);

Debug.WriteLine(output.ToString());

return output.ToString();}

Now, send the message containing the purchase order to the Amazon SQS queue using the code in Listing 14.

Listing 14. The CreatePOQueueItem() method
public bool CreatePOQueueItem(String poXml)
{
MessageQueueSQSDataProvider queueFactory = DW4DataContext.MessageQueueData;
MessageQueueEntity message = new MessageQueueEntity();
message.MessageBody = poXml;
message.MessageType = MessageTypes.PurchaseOrder;
return queueFactory.InsertQueueItem(message);			
}

The InsertQueueItem() method of MessageQueueSQSDataProvider creates the queue item in the correct queue based on the message type. The method calls the base method SendMessage() to send the message to the Amazon SQS queue, as in Listing 15.

Listing 15. The InsertQueueItem() method
public bool InsertQueueItem(MessageQueueEntity message)
{
String queueName = null;
if (message.MessageType == MessageTypes.OrderSummary)
{
	queueName = ConfigurationSettings.AppSettings["OrderSummaryQueue"];
	return this.SendMessage(queueName, message.MessageBody);
}
else if (message.MessageType == MessageTypes.PurchaseOrder)
{
	queueName = ConfigurationSettings.AppSettings["PurchaseOrderQueue"];
	return this.SendMessage(queueName, message.MessageBody);
}

return false;            
}

The SendMessage() method creates SendMessageRequest, which is part of the Amazon API. The required parameters are the queue name and the message body (an XML document). If the SendMessageResponse is not null, the message was successfully sent. The next few lines simply write out key debugging information for troubleshooting problems. The complete SendMessage() method is in Listing 16.

Listing 16. The SendMessage() method
public bool SendMessage(string queueName, string messageBody) {
bool result = true;
try {
	String accessKeyId = 
		ConfigurationSettings.AppSettings["AmazonAccessKeyID"]; ;
	String secretAccessKey = 
		ConfigurationSettings.AppSettings["AmazonSecretAccessKey"];
	AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);

	// build request
	SendMessageRequest request = new SendMessageRequest();
	request.QueueName = queueName;
	request.MessageBody = messageBody;
	// send message
	SendMessageResponse response = service.SendMessage(request);
	if (response.IsSetSendMessageResult()) {
		Debug.WriteLine("Send Message Result:");
		SendMessageResult sendMessageResult = response.SendMessageResult;
		if (sendMessageResult.IsSetMessageId()) {
			Debug.WriteLine(String.Format("\tMessageId: {0}", 
				sendMessageResult.MessageId));
		}
		if (sendMessageResult.IsSetMD5OfMessageBody()) {
			Debug.WriteLine("\tMD5 Of Message Body: ", 
				sendMessageResult.MD5OfMessageBody);
		}
	}
	if (response.IsSetResponseMetadata()) {
		Debug.WriteLine("Response Metadata:");
		ResponseMetadata responseMetadata = response.ResponseMetadata;
		if (responseMetadata.IsSetRequestId()) {
			Debug.WriteLine(String.Format("\tRequest Id: {0}", 
				responseMetadata.RequestId));
		}
	}
}
catch (AmazonSQSException ex) {
	Debug.WriteLine("Caught Exception: " + ex.Message);
	Debug.WriteLine("Response Status Code: " + ex.StatusCode);
	Debug.WriteLine("Error Code: " + ex.ErrorCode);
	Debug.WriteLine("Error Type: " + ex.ErrorType);
	Debug.WriteLine("Request ID: " + ex.RequestId);
	Debug.WriteLine("XML: " + ex.XML);
	result = false;
}
return result;
}

The reseller inventory-management service

InventoryManagementService is responsible for managing the inventory and processing order summaries received from the Manufacturer. Similar to OrderFulfillmentService, the service uses the polling interval specified in the App.config file. The following actions take place:

  • The ProcessIncomingMerchandise() method first retrieves a list of all order summaries (OrderSummaryEntity) received but not yet processed.
  • The InventoryManagementService method retrieves the messages from the Amazon SQS OSQueue.
  • The ProcessOrderReceipt() method is then invoked for each shipment not processed.

Listing 17 shows how the ProcessIncomingMerchandise() method retrieves a list.

Listing 17. The ProcessIncomingMerchandise() method
public int ProcessIncomingMerchandise()
{
int itemsProcessed = 0;

OrderSummaryFactory osFactory = new OrderSummaryFactory();
IList<OrderSummaryEntity> orders = osFactory.GetOrderSummariesToProcess();

// iterate through all order summaries
IEnumerator<OrderSummaryEntity> orderEnum = orders.GetEnumerator();
while (orderEnum.MoveNext())
{
	// get current order summary
	OrderSummaryEntity order = orderEnum.Current;

	// process order summary received
	if (this.ProcessOrderReceipt(order))
	{
		itemsProcessed++;
	}
}

Debug.WriteLine(String.Format("Orders Processed: {0}", itemsProcessed));
return itemsProcessed;
}

The ProcessOrderReceipt() method iterates through each item (OrderSummaryDetailEntity) in the order summary and increments the inventory quantity for each item received. Listing 18 shows this method.

Listing 18. The ProcessOrderReceipt() method
private bool ProcessOrderReceipt(OrderSummaryEntity order)
{
	if (order == null) return false;

	bool result = true;

	// add to inventory
	InventoryFactory inventoryFactory = new InventoryFactory();
	
	// iterate through all items in order summary
	IEnumerator<OrderSummaryDetailEntity> itemsEnum = order.Items.GetEnumerator();
	while (itemsEnum.MoveNext())
	{
		// get current item
		OrderSummaryDetailEntity orderItem = itemsEnum.Current;
		
		// get item
		int itemId = orderItem.ItemId;
		InventoryItemEntity item = inventoryFactory.GetInventoryItem(itemId);

		// increase inventory
		item.Quantity = item.Quantity + orderItem.Quantity;
		result = inventoryFactory.UpdateInventoryItem(item);
		if (!result) break;
	}

	if (result)
	{
		MessageQueueFactory queueFactory = new MessageQueueFactory();
		queueFactory.DeleteQueueItem(order.QueueItem);
	}

	return result;
}

The last step is to delete the order summary queue item from the message queue if the inventory shipment was successfully processed. Listing 19 shows how to delete a message from an Amazon SQS queue using C#.

Listing 19. The DeleteMessage() method
public bool DeleteMessage(string queueName, string messageHandle) {
bool result = false;
try {
  String accessKeyId = 
           ConfigurationSettings.AppSettings["AmazonAccessKeyID"];
  String secretAccessKey = 
           ConfigurationSettings.AppSettings["AmazonSecretAccessKey"];
  AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);
 
  // Create delete message request object
  DeleteMessageRequest request = new DeleteMessageRequest();
  
  // Set queue name containing item to be deleted
  request.QueueName = queueName;
  
  // Set handle of message to be deleted
  request.ReceiptHandle = messageHandle;
  
  // Delete message
  DeleteMessageResponse response = service.DeleteMessage(request);
  Debug.WriteLine("Service Response");                
 
  Debug.WriteLine("\tDelete Message Response");
  // If message response
  if (response.IsSetResponseMetadata())
  {
           Debug.WriteLine("\tResponse Metadata");
           ResponseMetadata responseMetadata = response.ResponseMetadata;
           if (responseMetadata.IsSetRequestId())
           {
                   Debug.WriteLine(String.Format("\tRequest Id: {0}", 
                            responseMetadata.RequestId));
           }
  }
}
catch (AmazonSQSException ex)
{
  Debug.WriteLine("Caught Exception: " + ex.Message);
  Debug.WriteLine("Response Status Code: " + ex.StatusCode);
  Debug.WriteLine("Error Code: " + ex.ErrorCode);
  Debug.WriteLine("Error Type: " + ex.ErrorType);
  Debug.WriteLine("Request ID: " + ex.RequestId);
  Debug.WriteLine("XML: " + ex.XML);
}
 
return result;
}

The Manufacturer implementation

The last component of the solution is the Manufacturer's Order Processing Service, which is the Java-based service that processes orders from the reseller. The Order Processing Service:

  • Polls the purchase order queue, which contains purchase orders sent by the Reseller.
  • Upon receiving a purchase order, it creates an order summary and sends it to the reseller using a different Amazon SQS queue.
  • Deletes the incoming purchase order sent by the Reseller.

The Manufacturer application consists of the business entities in Table 5.

Table 5. Business entities in the Reseller application
ClassDescription
AddresssEntityContains a customer or business address
CustomerOrderEntityContains a customer order (purchase order)
CustomerOrderDetailEntityContains the item-level detail for a customer order (purchase order)
MessageQueueEntityContains a message queue item (that is, a purchase order or order summary)
OrderSummaryEntityContains an order shipment summary
OrderSummaryDetailEntityContains the item-level detail for an order shipment summary

This solution uses the JdbcQuery helper class for all database operations. The solution also uses two additional helper classes: DateUtil for data formatting and AppConfig to read configuration parameters. Also, the SqsDataProvider class is the utility class that provides basic Amazon SQS functionality.


The manufacturer order-processing service

The OrderProcessingService class is responsible for processing purchase orders, and it does so based on the polling interval you specify in the AppConfig.properties file. The service retrieves a list of purchase orders not yet shipped and processes them. The getMessageQueueItems method, in Listing 20, demonstrates how to retrieve a list of messages from a specified Amazon SQS queue using Java code.

Listing 20. The getMessageQueueItems() method
protected ArrayList<HashMap<String, String>> getMessageQueueItems(String queueName) {
ArrayList<HashMap<String, String>> results = new ArrayList<HashMap<String, String>>();
 
try {
  String accessKeyId = AppConfig.getProperty("AmazonAccessKeyID");
  String secretAccessKey =  AppConfig.getProperty("AmazonSecretAccessKey");
 
  AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);
  
  // get messages from queue
  ReceiveMessageRequest request = new ReceiveMessageRequest();
  request.setQueueName(queueName);
  request.setVisibilityTimeout(1);
  
  ReceiveMessageResponse response = service.receiveMessage(request);
  
  // if received response
  if (response.isSetReceiveMessageResult()) {
           System.out.println("\tReceive Message Result");
           ReceiveMessageResult receiveMessageResult = 
                   response.getReceiveMessageResult();
           
           // get all messages and iterate through them
           List<Message> messageList = receiveMessageResult.getMessage();
           for (Message message : messageList) {
                   // build hashmap containing each message
                   HashMap<String, String> row = new HashMap<String, String>();
                   
                   if (message.isSetMessageId()) {
                            row.put(COLUMN_ID, message.getMessageId());
                            System.out.println("\t\tMessageId: " + 
                                     message.getMessageId());
                   }
                   if (message.isSetReceiptHandle()) {
                            row.put(COLUMN_HANDLE, message.getReceiptHandle());
                   }
                   if (message.isSetBody()) {                                   
                            row.put(COLUMN_MESSAGE, message.getBody());
                   }
                   
                   // add row
                   results.add(row);
           }
  }        
} catch (AmazonSQSException ex) {
  System.out.println("Caught Exception: " + ex.getMessage());
  System.out.println("Response Status Code: " + ex.getStatusCode());
  System.out.println("Error Code: " + ex.getErrorCode());
  System.out.println("Error Type: " + ex.getErrorType());
  System.out.println("Request ID: " + ex.getRequestId());
  System.out.println("XML: " + ex.getXML());
}
 
return results;
}

Java technology doesn't have the same built-in XML serialization capabilities that the .NET framework does, so the JDOM API is used to generate the XML document. JDOM is similar to DOM, but provides a simple Java API to parse, manipulate, and output XML. See Resources for information on how to use JDOM.

The PurchaseOrderSerializer class is responsible for deserializing the XML content from the message to a Java class. You use JDOM to parse the XML content, as in Listing 21. (As you follow this code, it will be helpful to refer to the schema for the purchase order in Listing 7.)

Listing 21. The PurchaseOrderSerializer class
public class PurchaseOrderSerializer {

public CustomerOrderEntity deserializePO(MessageQueueEntity message) {
  CustomerOrderEntity order = null;
  
  String xml = message.getMessageBody();
  System.out.println("Purchase Order:\n" + xml);
  
  try {
           // Create input source from string containing XML
           InputSource xmlIS = new InputSource();
           xmlIS.setCharacterStream(new StringReader(xml));
           
           // Initialize SAX Builder object
           SAXBuilder builder = new SAXBuilder();
 
           // Build JDOM Document object from input source
           Document doc = builder.build(xmlIS);
           Element rootElem = doc.getRootElement();
           
           // get order id
           String id = rootElem .getChildText("Id");        
           
           // create customer order 
           order = new CustomerOrderEntity(id);
           
           // set order date
           String dateStr = rootElem.getChildText("OrderDate");
           order.setOrderDate(DateUtil.getDate(dateStr));
            
           // get company (customer) address element node and parse it
           Element addrElem = rootElem.getChild("Company");
           this.setCompanyAddress(order, addrElem);

           // get vendor address element node and parse it
           addrElem = rootElem.getChild("Vendor");
           this.setVendorAddress(order, addrElem);

           // get order items list and parse it
           Element itemsElem = rootElem.getChild("Items");
           this.setOrderItems(order, itemsElem);
           
           System.out.println(order.toString());
  } catch (IOException e) {
           e.printStackTrace();
  } catch (NullPointerException e) {
           e.printStackTrace();
  } catch (JDOMException e) {
           e.printStackTrace();
  }
  return order;
}
 
private void setVendorAddress(CustomerOrderEntity order, Element addrElem) {
  // get data from xml
  String coName = addrElem.getChildText("CompanyName");
  String streetAddress = addrElem.getChildText("StreetAddress");
  String city = addrElem.getChildText("City");
  String state = addrElem.getChildText("State");
  String zipCode = addrElem.getChildText("ZipCode");
  
  AddressEntity coAddress = order.getVendorAddress();
   
  // build address entity object using data read from xml
  AddressEntity coAddress = order.getCompanyAddress();
  coAddress.setCity(city);
  coAddress.setCompanyName(coName);
  coAddress.setStreetAddress(streetAddress);
  coAddress.setState(state);
  coAddress.setZipCode(zipCode);
}
  
private void setCompanyAddress(CustomerOrderEntity order, Element addrElem) {
  // get data from xml
  String coName = addrElem.getChildText("CompanyName");
  String streetAddress = addrElem.getChildText("StreetAddress");
  String city = addrElem.getChildText("City");
  String state = addrElem.getChildText("State");
  String zipCode = addrElem.getChildText("ZipCode");
  
  // build address entity object using data read from xml
  AddressEntity coAddress = order.getCompanyAddress();
  coAddress.setCity(city);
  coAddress.setCompanyName(coName);
  coAddress.setStreetAddress(streetAddress);
  coAddress.setState(state);
  coAddress.setZipCode(zipCode);
}
 
private void setOrderItems(CustomerOrderEntity order, Element itemsElem) {
  List itemList = itemsElem.getChildren();
  if (itemList == null) return;
  
  // iterate through all items in collection
  for (int index = 0; index < itemList.size(); index++) {
           // get current element
           Element curElem = (Element) itemList.get(index);
           
           // get item values
           String itemId = curElem.getAttributeValue("Id");
           String itemName = curElem.getAttributeValue("Name");
           String quantity = curElem.getAttributeValue("Quantity");
           
           // create order item
           CustomerOrderDetailEntity detail = new CustomerOrderDetailEntity();
           detail.setItemId(itemId);
           detail.setItemName(itemName);
           detail.setQuantity(Integer.parseInt(quantity));
  
           // add order item to order summary
           order.addItem(detail);
  }
}
}

Next, build the order summary for the purchase order. Listing 22 shows the code.

Listing 22. The getOrderSummaryForCustomerOrder() method
public OrderSummaryEntity getOrderSummaryForCustomerOrder(
	CustomerOrderEntity customerOrder) {
	
	if (customerOrder == null) return null;
	
	// create order
	OrderSummaryEntity orderSummary = new OrderSummaryEntity();
	orderSummary.setOrderDate(new Date());
	orderSummary.setReferenceId(customerOrder.getId());
	
	// set addresses
	orderSummary.setCompanyAddress(customerOrder.getVendorAddress());
	orderSummary.setCustomerAddress(customerOrder.getCompanyAddress());
	
	// add items
	Iterator<CustomerOrderDetailEntity> itemIter = customerOrder.getItems();
	while(itemIter.hasNext()) {
		CustomerOrderDetailEntity item = itemIter.next();
		
		OrderSummaryDetailEntity detail = new OrderSummaryDetailEntity();
		detail.setItemId(item.getItemId());
		detail.setItemName(item.getItemName());
		detail.setQuantity(item.getQuantity());
		
		orderSummary.addItem(detail);
	}
	
	return orderSummary;
}

Then, you must serialize the OrderSummaryEntity to XML. You use JDOM to serialize the message, as in Listing 23. (It will be helpful to refer to the schema for the order summary in Listing 8.)

Listing 23. The OrderSummarySerializer class
public class OrderSummarySerializer {
	public String serializeOrder(OrderSummaryEntity order) {
		Document doc = new Document();
		
		Element rootElem = new Element("OrderSummary");
		doc.addContent(rootElem);
		
		// add id
		Element idElem = new Element("OrderId");
		idElem.setText(order.getOrderId());
		rootElem.addContent(idElem);
		
		// add reference id
		Element referenceIdElem = new Element("ReferenceId");
		referenceIdElem.setText(order.getReferenceId());
		rootElem.addContent(referenceIdElem);
		
		// add order date
		Element dateElem = new Element("OrderDate");
		String dateStr = DateUtil.getDateString(new Date());
		dateElem.setText(dateStr);		
		rootElem.addContent(dateElem);
		
		// set company address
		Element addrElem = this.getElementForAddress("CompanyAddress", 
			order.getCompanyAddress());
		rootElem.addContent(addrElem);
		
		// set customer address
		addrElem = this.getElementForAddress("CustomerAddress", 
			order.getCustomerAddress());
		rootElem.addContent(addrElem);
		
		// iterate through all items and add each item to order
		Element itemsElem = new Element("Items");
		Iterator<OrderSummaryDetailEntity> itemIter = order.getItems();
		while(itemIter.hasNext()) {
			OrderSummaryDetailEntity item = itemIter.next();
			
			Element itemElem = new Element("Item");
			itemElem.setAttribute("ItemId", item.getItemId());
			itemElem.setAttribute("ItemName", item.getItemName());
			String quantityStr = String.valueOf(item.getQuantity());
			itemElem.setAttribute("Quantity", quantityStr);
			
			itemsElem.addContent(itemElem);
		}
		rootElem.addContent(itemsElem);
		
		// convert xml document to string
		XMLOutputter outp = new XMLOutputter();
		StringWriter strWriter = new StringWriter();        
		try {
			outp.output(doc, strWriter);
		} catch (IOException e) {
			e.printStackTrace();
		}		
		return strWriter.toString();
	}

	private Element getElementForAddress(String elemName, AddressEntity address) {
		Element addrElem = new Element(elemName);
		
		String coName = address.getCompanyName();
		String streetAddress = address.getStreetAddress();
		String cityName = address.getCity();
		String stateName= address.getState();
		String zipCode = address.getZipCode();
		
		Element coElem = new Element("CompanyName");
		coElem.setText(coName);
		addrElem.addContent(coElem);
		
		Element streetElem = new Element("StreetAddress");
		streetElem.setText(streetAddress);
		addrElem.addContent(streetElem);
		
		Element cityElem = new Element("City");
		cityElem.setText(cityName);
		addrElem.addContent(cityElem);
		
		Element stateElem = new Element("State");
		stateElem.setText(stateName);
		addrElem.addContent(stateElem);
		
		Element zipCodeElem = new Element("ZipCode");
		zipCodeElem.setText(zipCode);
		addrElem.addContent(zipCodeElem);
		
		return addrElem;
	}
}

Send the XML message to the Amazon SQS queue using the Amazon Java API. Listing 24 shows how to send the message.

Listing 24. The sendMessage() method
protected boolean sendMessage(String queueName, String messageBody) {
boolean result = false;
try {
	String accessKeyId = AppConfig.getProperty("AmazonAccessKeyID");
	String secretAccessKey =  AppConfig.getProperty("AmazonSecretAccessKey");

	AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);
	
	SendMessageRequest request = new SendMessageRequest();
	request.setQueueName(queueName);
	request.setMessageBody(messageBody);
	
	SendMessageResponse response = service.sendMessage(request);
	// Send Message Response
	if (response.isSetSendMessageResult()) {
		System.out.println("\tSend Message Result");
		SendMessageResult sendMessageResult = response.getSendMessageResult();
		if (sendMessageResult.isSetMessageId()) {
			System.out.println("\t\tMessageId: " + 
				sendMessageResult.getMessageId());
			result = true;
		}
	} 		
} catch (AmazonSQSException ex) {
	System.out.println("Caught Exception: " + ex.getMessage());
	System.out.println("Response Status Code: " + ex.getStatusCode());
	System.out.println("Error Code: " + ex.getErrorCode());
	System.out.println("Error Type: " + ex.getErrorType());
	System.out.println("Request ID: " + ex.getRequestId());
	System.out.println("XML: " + ex.getXML());
}

return result;
}

Finally, you must delete the purchase order queue item from the Amazon SQS queue. Listing 25 shows how to delete the message.

Listing 25. The deleteMessage() method
protected boolean deleteMessage(String queueName, String handle) {
boolean result = false;

try {
	String accessKeyId = AppConfig.getProperty("AmazonAccessKeyID");
	String secretAccessKey =  AppConfig.getProperty("AmazonSecretAccessKey");

	AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);
	DeleteMessageRequest request = new DeleteMessageRequest();
	request.setQueueName(queueName);
	request.setReceiptHandle(handle);
	
	DeleteMessageResponse response = service.deleteMessage(request);

	// Delete Message Response
	if (response.isSetResponseMetadata()) {
		// Response Metadata
		ResponseMetadata  responseMetadata = response.getResponseMetadata();
		if (responseMetadata.isSetRequestId()) {
			System.out.print("RequestId: " + 
				responseMetadata.getRequestId());
			result = true;
		}
	} 
} catch (AmazonSQSException ex) {
	System.out.println("Caught Exception: " + ex.getMessage());
	System.out.println("Response Status Code: " + ex.getStatusCode());
	System.out.println("Error Code: " + ex.getErrorCode());
	System.out.println("Error Type: " + ex.getErrorType());
	System.out.println("Request ID: " + ex.getRequestId());
	System.out.println("XML: " + ex.getXML());
}

return result;
}

Testing the solution

Now you are ready to test the solution. To test the solution, submit an order for an item for which there is insufficient inventory. Then, you can trace the entire process:

  • Submitting the order
  • Reseller placing the order on backorder and creating the purchase order
  • Manufacturing receiving the order and sending the order summary to the reseller
  • Reseller receiving the order summary and processing the backorder

The reseller client in Figure 6 shows the inventory levels before a new order is placed.

Figure 6. Browse the inventory
Screen capture of the Inventory management window with list of inventory items and quantity of each item

Figure 7 shows the order submission window.

Figure 7. Submit the order
Screen capture of the Customer Order window. Order requests more items than are listed in the inventory.

Figure 8 shows the Browse Orders window.

Figure 8. Browsing orders
Screen capture of Browse Orders window with list of processed and unprocessd orders. Screen lists Id, Customer Name, Order Date, Ship Date, and whether items are backordered.

After the new order is placed, the window in Figure 9 shows the output of the order-fulfillment services when the order is backordered.

Figure 9. The reseller order-fulfilment and inventory-management service
Screen capture of output messages of the reseller order-fulfilment and inventory-management service

After the new order is placed, the window in Figure 10 shows contents of the POQueue message queue.

Figure 10. View the message queue
Screen capture of the View Queues window with messages.

The manufacturer receives the purchase order from the message queue and processes it. Figure 11 shows the output of the manufacturer's order-processing system.

Figure 11. The manufacturer's order-management service
Screen capture of messages out by the manufacturer's order-management service

Conclusion

This article demonstrates how to leverage XML and the Amazon SQS Web service to integrate enterprise business applications. Combining the extensibility of XML with Amazon SQS provides a highly scalable and flexible application. The key benefits of this solution are the scalability, extensibility, and minimal integration effort. The integration did not require any special firewall or security considerations. The last benefit of this solution—and a key driver for Web services—is that the solution is cross-platform and technology agnostic. The manufacturer and reseller can each keep their existing technology platforms while they integrate their business processes to drive operational efficiency and return on investment (ROI).

This solution does have a few limitations. First and foremost, the maximum size of an Amazon SQS message body is 8KB. You can work around this limitation with a couple of possible solutions:

  • Use Amazon Simple Storage Service (Amazon S3) to store the large XML document and put a handle to the Amazon S3 item in the Amazon SQS message.
  • Use Amazon SimpleDB to store the structured data and put a handle to the items in the Amazon SQS message.

The second limitation is the eventual consistency approach that Amazon takes with the queue. It is both a good and bad thing, but clearly Amazon believes the good far outweighs the bad. The last limitation of the solution is that there is no priority or FIFO approach. Again, this is intentional on Amazon's part, as it provides for a more scalable, reliable, and highly available solution.

If you use XML for data exchange in the message body, best practices recommend that you perform XML Schema (XSD) validation to ensure that the sender and receiver speak the same language. As with any XML integration project, both the sender and the receiver need to agree upon a common schema.


Downloads

DescriptionNameSize
C# projects for the reseller applicationsNETSolution.zip223KB
Java project for the manufacturer applicationsJavaSolution.zip24KB
Contains the configuration scriptsDbScripts.zip1KB

Resources

Learn

  • Amazon SQS technical documentation: Refer to information on the Amazon SQS library.
  • JDOM documentation: Get information on the JDOM library and this Java-based solution to access, manipulate, and output XML data from Java code.
  • Connecting to the cloud, Part 3: Cloud governance and security: Secure the HybridCloud application (Mark O'Neill, developerWorks, June 2009): Find a good description of the keys used with Amazon SQS and explore how to set up oversight policies that secure sensitive information and monitor access and use of your cloud applications. In Part 1 of this series, look at a hybrid example with cloud computing services and infrastructure (Mark O'Neill, developerWorks, March 2009). In Part 2, develop that hybrid application (Mark O'Neill, developerWorks, April 2009).
  • Eventual consistency: Refer to Werner Vogels' (Amazon CTO) blog entry on eventual consistency for information on the Amazon SQS design rationale.
  • Simplify XML programming with JDOM (Wes Biggs and Harry Evans, developerWorks, May 2001): JDOM is similar to DOM, but provides a simple Java API for parsing, manipulating, and outputting XML. Learn how to use this open-source API makes XML document manipulation easy for Java developers.
  • XML technical library: See the developerWorks XML Zone for a wide range of technical articles and tips, tutorials, standards, and IBM Redbooks.
  • developerWorks technical events and webcasts: Stay current with technology in these sessions.
  • developerWorks podcasts: Listen to interesting interviews and discussions for software developers.

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Web development, Cloud computing
ArticleID=396666
ArticleTitle=Leveraging Amazon Web Services for enterprise application integration
publish-date=06162009