The third component of the distributed application built in this chapter is an “Offers XML Data Store.” This is a content-based repository of “special offers” that retailers commonly offer to their customers. In this imaginary application, the Offers Data Store holds various offers, which are retrievable from the network at runtime. Different distributed applications can access the offers for different reasons.
For example, a web-based application may want to access the offers dynamically to present offers to users based on their recent purchases or spending habits. The web application applies a stylesheet to the offer information prior to displaying on a web page. Or, it’s possible that an Intranet application may access the offers as part of management functionality in order to change or edit offers.
To that end, in this section, we construct an XML Offers Data
Store. In the following section, we construct another Python class
similar to CustomerProfile
, which
allows us to access the XML data store on the network rather
transparently.
The Offers Data Store consists of a large XML document residing on disk. The aforementioned access component developed later will take care of traversing this file and returning the correct offers back to the caller. The basic structure of an offer as empty elements is:
<offer> <id/> <internal-name/> <heading/> <description/> <discount/> <discount-type/> <expires/> <disclaimer/> </offer>
A complete starting XML store is presented in Example 10-3, with just two offers. If you feel the need to add your own, please do so.
<?xml version="1.0" encoding="UTF-8"?> <OfferXMLStore> <offer> <id>9908d093j4p3j33</id> <internal-name>DiscountOver1000</internal-name> <heading>20% Off All Orders Over $1000.00</heading> <description> As an incentive for you to purchase more, we're offering a 20% volume discount on all orders totaling $1000 or more! This amazing discount is being brought to you because you are such an important customer to us. We love you so much! </description> <discount>20</discount> <discount-type>percent</discount-type> <expires>2002-11-21</expires> <disclaimer> Discount subject to certain restrictions. Merchandise must not be under any other discounts, and discount is taken from MSRP only. Discount may not be applied in some cases if we think you are likely to buy the product without a discount. We may revoke this discount whenever we want, including after you place your order. Sorry. </disclaimer> </offer> <offer> <id>222833fgjQZ3j30</id> <internal-name>Clearance</internal-name> <heading>20% Off All Clearance Items</heading> <description> In an effort to reduce our inventory of the items that just don't seem to sell as well as our other merchandise, we're offering a 20% deduction on all items marked clearance! This is because you are such an important customer to us. We love you so much. </description> <discount>20</discount> <discount-type>percent</discount-type> <expires>2002-11-21</expires> <disclaimer> Discount subject to certain restrictions. Merchandise must be marked clearance. In some cases, some items marked clearance may have the mark removed at time of purchase. If this happens to you, the full price of the product will be charged to your card. Sorry. </disclaimer> </offer> </OfferXMLStore>
These two offers are enough to get started. The OfferXMLStore
is just a large XML file on
disk. The larger the file gets, the overhead of parsing and
manipulating the file increases. At a certain point, it is better to
migrate the OfferXMLStore
into a
database. While an XML representation of the data is critical, using
an actual database for the physical storage to disk is far more
efficient then processing a large text file. XML is at its strongest
as a document format and glue language. In other words, you don’t want
a gigabyte XML document full of information, you want a gigabyte of
XML documents inside your database. Given the flexibility of the XML
access object created in the next section, it is simple to change the
underlying storage mechanism of the XML offers without affecting the
XML information passing in and out.
In this section, we create another access component to
access data from the XML data store. This type of object comes in
handy when encapsulating a data source from a user of the data source.
This was done with the CustomerProfile
object earlier when it hid
the ODBC data source from view behind a veil of XML. The XMLOffer
class puts forth several methods
for storing, retrieving, and modifying offers maintained in the XML
data store.
The interfaces for the XMLOffer
class are very similar to the
ones for CustomerProfile
, with
the notable addition of a getAllOffers
method. These methods are
intended to allow network applications the ability to interact with
the XML data store using only XML, without needing to know or be
concerned with what type of underlying storage mechanism the store
is using. In fact, by sticking to the interface but rewriting the
implementation, you could change the XMLOffer
class to speak with a database
rather than an XML file. This change is completely transparent to
the clients who would have no visibility of it whatsoever.
getOffer(
id
)
This method takes an ID as a string, and retrieves the corresponding XML offer within the Data Store as a single XML chunk contained in a string.
getOfferAsDomElement(
id
)
As you might suspect, this method works identically to
the analogous method in CustomerProfile
, returning a DOM
Element
object instead of a
string.
getAllOffers( )
This method returns the entire OfferXMLStore
as a string.
insertOffer(
strXML
)
This method takes an XML offer chunk as an argument, and places it inside the XML data store.
updateOffer(
strXML
)
Similar to CustomerProfile.updateProfile
, this
method takes an offer as XML, deletes the corresponding offer
from the store, and adds this one.
deleteOffer(
id
)
As you may realize, this method takes an ID as an argument and removes the corresponding offer from the XML data store.
The interfaces exposed are meant to make working with the XML offers as easy as possible for applications running on the network.
This class will be hosted, alongside CustomerProfile
, within the XML Switch. Of
course, they could easily be placed behind the web server or CORBA
servers, but for brevity, in this chapter they are accessible as
loadable classes to the XML Switch.
Use of the XMLOffer
class is simple. Provided you have OfferXMLStore.xml available on disk (as
shown in Example 10-3),
you can begin using the XMLOffer
class:
from XMLOffer import XMLOffer xo = XMLOffer( ) print xo.getOffer('9908d093j4p3j33')
The result is retrieval of the offer element with a matching
ID child. Likewise, if your web site posted an offer to you, or your
GUI app returned a text area’s XML content as a string, you could
use the insertOffer
method:
xo.insertOffer(strXMLOffer)
The interfaces are all straightforward.
The XMLOffer
class
is simple, but relies heavily on use of the DOM and XPath to support
its functionality. As with CustomerProfile
, the XMLOffer
class typically returns a
1
or a 0
after each method call. The exceptions
are, of course, the methods that return XML.
To understand how the XMLOffer
class wraps the large OfferXMLStore
with convenience functions,
such as get, insert, update, or delete, is to understand many
different ways to manipulate XML. Implementing the XMLOffer
class illustrates DOM usage as
well as XPath.
When obtaining an offer, the class accepts an ID string from the caller, and uses XPath to find the offer with the corresponding ID in the store:
offerdoc = FromXmlStream("OfferXMLStore.xml") offer = Evaluate("offer[id='" + strId + "']", offerdoc.documentElement)
The XPath looks for the supplied ID string (strId
) within offer elements inside the
XML store. When it hits the target, it is returned as the offer
element. If you requested an element node (offer
), you could call getOfferAsDomElement
; however, for a
string, you call getOffer
:
if dom: return offer[0] else: strXML = StringIO.StringIO( ) PrettyPrint(offer[0], strXML) return strXML.getvalue( )
Of course, getOfferAsDomElement
works in the same
fashion as getProfile
in the
CustomerProfile
class. The
method simply calls its shorter-named cousin with an optional
parameter and indicates to return the node rather than a
string.
The getAllOffers
method
uses a simple direct approach to delivering the XML store—it just
writes the whole file back to you as a string.
# scoop up offers file fd = open("OfferXMLStore.xml", "r") doc = fd.read( ) fd.close( ) # return big string return doc
Several methods exist for modifying and managing offers
within the XML store. The insertOffer
method allows you to put new
offers in the store. The methods updateOffer
and deleteOffer
allow for additional
maintenance.
The insertOffer
method
creates a DOM instance out of the submitted XML to verify
well-formedness (and potentially validity, if you put in the
effort). It’s converted to a string and swapped out with the
OfferXMLStore
’s end element
tag. This is a quick and easy way to add the new element to the
document. You can work with strings because the submitted XML was
at first a DOM instance, and could be validated while in that
state.
The updateOffer
extracts
the ID, and then performs a delete followed by an insert. The
deleteOffer
method extracts an
ID, and then removes the node from a DOM instance:
try: targetNode = Evaluate("offer[id="" + strId + ""]", xmlstore.documentElement) except: print "Bad XPath Evaluation." return 0 # use Node.removeChild(XPathResult) try: xmlstore.documentElement.removeChild(targetNode[0]) except: # either it didn't exist, or # the XPath call turned up nothing... return 0
XPath is used to target the specific ID, and the Evaluate
call returns the actual node.
The node is then handed off to the documentElement
node’s removeChild( )
method. At this point,
the rest of the code writes the file back to disk. Example 10-4 shows
XMLOffer.py.
""" XMLOffer.py """ import StringIO from xml.dom.ext.reader.Sax2 import FromXmlStream from xml.dom.ext.reader.Sax2 import FromXml from xml.dom.ext import PrettyPrint from xml.xpath import Evaluate class XMLOffer: def getOffer(self, strId, dom=0): """ getOffer takes an ID as a parameter and returns the corresponding offer from the XML Data Store as a string of XML, or as a DOM if the third param flag has been set. """ # create document from data store offerdoc = FromXmlStream("OfferXMLStore.xml") # use XPath to target specific offer element # by child ID character data offer = Evaluate("offer[id='" + strId + "']", offerdoc.documentElement) # decide which version to return, DOM or string if dom: # return offer element return offer[0] else: # convert to string strXML = StringIO.StringIO( ) PrettyPrint(offer[0], strXML) return strXML.getvalue( ) def getOfferAsDomElement(self, strId): """ getOfferAsDomElement works the same as getOffer but returns a DOM element instance, as opposed to a string. This method just calls getOffer with the dom flag (the third parameter) set to 1. """ return self.getOffer(strId, 1) def getAllOffers(self): """ getAllOffers returns the whole store as a string. """ # scoop up offers file fd = open("OfferXMLStore.xml", "r") doc = fd.read( ) fd.close( ) # return big string return doc def insertOffer(self, strOfferXML): """ insertOffer takes a string of XML and adds it to the XML store. """ if not strOfferXML: return None # generate DOM from input data newoffer = FromXml(strOfferXML) #---- # Optional: you could validate here using # your new dom object and offer.dtd; see # chapter 7 for details on using xmlproc for # validation... #---- # Pour DOM into String newXmlOffer = StringIO.StringIO( ) PrettyPrint(newoffer.documentElement, newXmlOffer) # grab contents into buffer rd = open("OfferXMLStore.xml", "r") bf = rd.readlines( ) rd.close( ) # search and replace in buffer wd = open("OfferXMLStore.xml", "w") for lp in range(len(bf)): if (bf[lp].rfind("</OfferXMLStore>") > -1): # replace root element end tag with fresh offer # and root element end tag bf[lp] = bf[lp].replace("</OfferXMLStore>", newXmlOffer.getvalue( ) + "</OfferXMLStore>") # write new buffer to disk wd.writelines(bf) wd.close( ) return 1 def deleteOffer(self, strId): """ deleteOffer takes an ID string and deletes that offer Node from the OfferXMLStore.xml document """ # read store into DOM, close store try: xmlstore = FromXmlStream("OfferXMLStore.xml") except: print "Unable to open xmlstore." return 0 # use XPath to return the id Node # offer/[id='<id>'] try: targetNode = Evaluate("offer[id="" + strId + ""]", xmlstore.documentElement) except: print "Bad XPath Evaluation." return 0 # use Node.removeChild(XPathResult) try: xmlstore.documentElement.removeChild(targetNode[0]) except: # either it didn't exist, or # the XPath call turned up nothing... return 0 # reopen store,w # PrettyPrint the DOM in # close the store fd = open("OfferXMLStore.xml", "w") PrettyPrint(xmlstore, fd) fd.close( ) return 1 def updateOffer(self, strOfferXML): if not strOfferXML: return 0 else: try: offerId = Evaluate("id/text( )", FromXml(strOfferXML).documentElement) if (not self.deleteOffer(offerId[0].nodeValue) or not self.insertOffer(strOfferXML)): print "could not delete or insert." return 0 except: print "unable to update offer." return 0 return 1
The XMLOffer
class is
easy to use. The next component of the distributed system is the
XML Switch, which brokers the individual messages among the
different applications.