Creating an XML Data Store

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.

A Large XML File

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.

Example 10-3. OfferXMLStore.xml
<?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.

Creating an XML Access Object

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

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.

Using the XMLOffer class

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.

Creating the XMLOffer class

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.

Retrieval methods

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
Modification methods

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.

Example 10-4. 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.57.172