A SOAP Library

   

The Stock Server

We'll start with the server. The server is connected to the warehouse database, accepts getStock RPC, and returns the latest status on product availability.

SoapEnvelope

The first class we will look at, SoapEnvelope , is not specific to the server. This class is demonstrated in Listing 9.4.

Listing 9.4 SoapEnvelope.java
 package com.psol.stockq; import org.xml.sax.*; import org.xml.sax.helpers.*; public class SoapEnvelope    extends XMLFilterImpl {    protected static final int NONE = 0,                               ENVELOPE = 1,                               HEADER = 2,                               BODY = 3,                               FAULT = 4,                               FAULT_CODE = 5,                               FAULT_STRING = 6;    protected int status = NONE;    protected static final String SOAP_URI =       "http://schemas.xmlsoap.org/soap/envelope/";    protected StringBuffer buffer = null;    protected String[] data = null;    public void startDocument()       throws SAXException    {       status = NONE;       getContentHandler().startDocument();    }    public void startElement(String namespaceURI,                             String localName,                             String rawName,                             Attributes atts)       throws SAXException    {       if(BODY == status)          if(localName.equals("Fault") &&             namespaceURI.equals(SOAP_URI))          {             status = FAULT;             data = new String[2];          }          else             getContentHandler().startElement(namespaceURI,                                              localName,                                              rawName,                                              atts);       else if(localName.equals("Envelope") && NONE == status)          if(namespaceURI.equals(SOAP_URI))             status = ENVELOPE;          else             throw new SoapException("VersionMismatch",                                     "Unknown SOAP version");       else if(localName.equals("Body") &&               namespaceURI.equals(SOAP_URI) &&               ENVELOPE == status)          status = BODY;       else if(localName.equals("Header") &&               namespaceURI.equals(SOAP_URI) &&               ENVELOPE == status)          status = HEADER;       else if(status == HEADER)       {          // IMHO it really should be in the SOAP namespace          String mu = atts.getValue("mustUnderstand");          if(mu != null && mu.equals("1"))             throw new SoapException("MustUnderstand",                                     rawName + " unknown");       }       else if(localName.equals("faultcode") &&               status == FAULT)       {          status = FAULT_CODE;          buffer = new StringBuffer();       }       else if(localName.equals("faultstring") &&               status == FAULT)       {          status = FAULT_STRING;          buffer = new StringBuffer();       }    }    public void endElement(String namespaceURI,                           String localName,                           String rawName)       throws SAXException    {       if(BODY == status)          getContentHandler().endElement(namespaceURI,                                         localName,                                         rawName);       else if(localName.equals("Envelope") &&               namespaceURI.equals(SOAP_URI) &&               ENVELOPE == status)          status = NONE;       else if(localName.equals("Body") &&               namespaceURI.equals(SOAP_URI) &&               BODY == status)          status = ENVELOPE;       else if(localName.equals("Header") &&               namespaceURI.equals(SOAP_URI) &&               HEADER == status)          status = ENVELOPE;       else if(localName.equals("Fault") &&               namespaceURI.equals(SOAP_URI) &&               status == FAULT)          throw new SoapException(data[0],data[1]);       else if(localName.equals("faultcode") &&               status == FAULT_CODE)       {          status = FAULT;          data[0] = buffer.toString();          buffer = null;       }       else if(localName.equals("faultstring") &&               status == FAULT_STRING)       {          status = FAULT;          data[1] = buffer.toString();          buffer = null;       }    }    public void characters(char[] ch,int start,int len)       throws SAXException    {       if(BODY == status)          getContentHandler().characters(ch,start,len);       else if(FAULT_CODE == status                FAULT_STRING == status)          buffer.append(ch,start,len);    }    public void skippedEntity(String name)       throws SAXException    {       if(BODY == status)          getContentHandler().skippedEntity(name);    }    public void ignorableWhitespace(char[] ch,                                    int start,                                    int len)       throws SAXException    {       if(BODY == status)          getContentHandler().ignorableWhitespace(ch,start,len);    }    public void processingInstruction(String target,String data)       throws SAXException    {       if(BODY == status)          getContentHandler().processingInstruction(target,data);    } } 

SoapEnvelope passes most events unmodified to its ContentHandler :

 public void startDocument()    throws SAXException {    status = NONE;    getContentHandler().startDocument(); } 

The main methods are startElement() and endElement() . The filter intercepts events related to SOAP elements but passes other events unmodified.

SOAP-ENV:Header requires special attention. You will remember that header elements are not defined by SOAP. However, the header might influence how the server should process the request ”for example, when a client makes a request within the context of a transaction, it might impact the server response. What happens if a server does not recognize the transaction elements?

SOAP suggests you label mandatory elements in the header with a mustUnderstand attribute. The server must either recognize the element or signal an error. The filter enforces this rule:

 else if(localName.equals("Header") &&         namespaceURI.equals(SOAP_URI) &&         ENVELOPE == status)    status = HEADER; else if(status == HEADER) {    // IMHO it really should be in the SOAP namespace    String mu = atts.getValue("mustUnderstand");    if(mu != null && mu.equals("1"))       throw new SoapException("MustUnderstand",                               rawName + " unknown"); } 

The filter also enforces version control. SOAP uses namespaces for versioning. Elements not in the SOAP namespace indicate a new, incompatible version:

 else if(localName.equals("Envelope") && NONE == status)    if(namespaceURI.equals(SOAP_URI))       status = ENVELOPE;    else       throw new SoapException("VersionMismatch",                               "Unknown SOAP version"); 

SoapService

SoapService , in Listing 9.5, inherits from a servlet to implement the SOAP protocol. Its descendants must worry about only the RPC.

Listing 9.5 SoapService.java
 package com.psol.stockq; import java.io.*; import java.sql.*; import org.xml.sax.*; import javax.servlet.*; import javax.servlet.http.*; import org.xml.sax.helpers.*; public abstract class SoapService    extends HttpServlet {    public abstract void doSoap(XMLReader reader,                                InputSource source,                                XMLWriter writer)       throws IOException, SoapException, SAXException;    // to optimize, we could manage a pool of XMLReader    public void doPost(HttpServletRequest request,                       HttpServletResponse response)       throws IOException    {       try       {          // check for SOAPAction, ignore its value          // because the spec is unclear on what the server          // should do with SOAPAction          String soapAction = request.getHeader("SOAPAction");          if(null == soapAction)             throw new SoapException("Client",                                     "Missing SOAPAction");          XMLReader xmlReader =             XMLReaderFactory.createXMLReader(                                 Constants.SAXPARSER);          xmlReader.setFeature(Constants.SAXNAMESPACES,true);          SoapEnvelope soapEnvelope = new SoapEnvelope();          soapEnvelope.setParent(xmlReader);          CharArrayWriter payload = new CharArrayWriter();          payload.write("<?xml version='1.0'?>");          payload.write("<SOAP-ENV:Envelope xmlns:SOAP-ENV='");          payload.write(Constants.SOAPENV_URI);          payload.write("'><SOAP-ENV:Body>");          InputSource source =             new InputSource(request.getReader());          doSoap(soapEnvelope,source,new XMLWriter(payload));          payload.write("</SOAP-ENV:Body></SOAP-ENV:Envelope>");          Writer writer = response.getWriter();          response.setContentType("text/xml");          payload.writeTo(writer);          writer.flush();       }       catch(SoapException e)       {          response.setStatus(             HttpServletResponse.SC_INTERNAL_SERVER_ERROR);          response.setContentType("text/xml");          e.writeTo(new XMLWriter(response.getWriter()));          response.getWriter().flush();       }       catch(SAXException e)       {          // when SAXException embeds another exception          // it does a poor job at returning the embedded          // exception message, so extract it          response.setStatus(             HttpServletResponse.SC_INTERNAL_SERVER_ERROR);          response.setContentType("text/xml");          Exception ex = e.getException() != null ?                         e.getException() : e;          new SoapException("Client",ex.getMessage()).             writeTo(new XMLWriter(response.getWriter()));          response.getWriter().flush();       }       catch(Exception e)       {          response.setStatus(             HttpServletResponse.SC_INTERNAL_SERVER_ERROR);          response.setContentType("text/xml");          new SoapException("Server",e.getMessage()).             writeTo(new XMLWriter(response.getWriter()));          response.getWriter().flush();       }    } } 

SoapService parses the envelope (through SoapEnvelope ) but delegates processing of the request to its descendants (through a call to doSoap() ). Likewise, it writes the SOAP envelope but lets its descendants write the response.

Notice how it creates a parser, turns on namespace processing, and activates the SoapEnvelope as an XML filter:

 XMLReader xmlReader =    XMLReaderFactory.createXMLReader(Constants.SAXPARSER); xmlReader.setFeature(Constants.SAXNAMESPACES,true); SoapEnvelope soapEnvelope = new SoapEnvelope(); soapEnvelope.setParent(xmlReader); 

Next , it uses SoapEnvelope as if it were the parser itself:

 doSoap(soapEnvelope,source,new XMLWriter(payload)); 

XMLWriter

XMLWriter , in Listing 9.6, should look familiar. It provides a helper method to escape reserved characters ( < , & , and more).

Listing 9.6 XMLWriter.java
 package com.psol.stockq; import java.io.*; public class XMLWriter    extends PrintWriter {    public XMLWriter(Writer writer)    {       super(writer);    }    public void escape(String s)       throws IOException    {       for(int i = 0;i < s.length();i++)       {          char c = s.charAt(i);          if(c == '<')             write("&lt;");          else if(c == '&')             write("&amp;");          else if(c == '\ '')             write("&apos;");          else if(c == '"')             write("&quot;");          else if(c > '\ u007f')          {             write("&#");             write(Integer.toString);             write(';');          }          else             write;       }    } } 

SoapException

SoapException , in Listing 9.7, stores the faultcode and faultstring . It also provides a convenient writeTo() method to write the fault in XML.

Listing 9.7 SoapException.java
 package com.psol.stockq; import java.io.*; import org.xml.sax.*; public class SoapException    extends SAXException {    protected String code;    public SoapException(String code,String string)    {       super(string != null ? string : "Unknown error");       this.code = code;    }    public String getCode()    {       return code;    }    public void writeTo(XMLWriter writer)       throws IOException    {       writer.write("<?xml version='1.0'?>");       writer.write("<SOAP-ENV:Envelope xmlns:SOAP-ENV='");       writer.write(Constants.SOAPENV_URI);       writer.write("'><SOAP-ENV:Body>");       writer.write("<SOAP-ENV:Fault><faultcode>SOAP-ENV:");       writer.escape(code);       writer.write("</faultcode><faultstring>");       writer.escape(getMessage());       writer.write("</faultstring></SOAP-ENV:Fault>");       writer.write("</SOAP-ENV:Body></SOAP-ENV:Envelope>");    } } 

Database

So far, we have looked at generic SOAP classes. To study the specifics of the stock server, we'll start with the database.

Again, because our focus is on XML, not stock management, I've kept the database simple. It contains a single table, products , which lists products and their availability (negative numbers indicate back orders). Products are identified by their manufacturer name and a product number (sku).

Warning

This chapter does not include a tool to update inventory levels. You will need to edit them through your database user interface.

However, it is probably not a good idea to let retailers remotely manipulate product availability! You want the database to reflect actual levels in the warehouse.


StockResponse

StockResponse , in Listing 9.8, implements ContentHandler . Because it comes after a SoapEnvelope filter, it never sees the SOAP elements. As far as StockResponse is concerned , the root of the document is getProduct .

Listing 9.8 StockResponse.java
 package com.psol.stockq; import java.io.*; import java.sql.*; import org.xml.sax.*; import org.xml.sax.helpers.*; public class StockResponse    extends DefaultHandler {    protected StringBuffer manufacturer = null,                           sku = null;    protected final static int NONE = 0,                               GET_STOCK = 1,                               MANUFACTURER = 2,                               SKU = 3;    protected int status = NONE;    public void startDocument()       throws SAXException    {       status = NONE;       manufacturer = null;       sku = null;    }    public void startElement(String namespaceURI,                             String localName,                             String rawName,                             Attributes atts)       throws SAXException    {       if(localName.equals("getStock") &&          namespaceURI.equals(Constants.PSOL_URI) &&          NONE == status)          status = GET_STOCK;       else if(rawName.equals("manufacturer") &&               GET_STOCK == status &&               null == manufacturer)       {          manufacturer = new StringBuffer();          status = MANUFACTURER;       }       else if(rawName.equals("sku") &&               GET_STOCK == status  &&               null == sku)       {          sku = new StringBuffer();          status = SKU;       }    }    public void endElement(String namespaceURI,                           String localName,                           String rawName)       throws SAXException    {       if(localName.equals("getStock") &&          namespaceURI.equals(Constants.PSOL_URI) &&          GET_STOCK == status)          status = NONE;       else if(rawName.equals("manufacturer") &&               MANUFACTURER == status)          status = GET_STOCK;       else if(rawName.equals("sku") && SKU == status)          status = GET_STOCK;    }    public void characters(char[] ch,int start,int len)       throws SAXException    {       if(SKU == status)          sku.append(ch,start,len);       else if(MANUFACTURER == status)          manufacturer.append(ch,start,len);    }    public void writeResponse(Connection connection,                              XMLWriter writer)       throws SQLException, IOException, SoapException    {       if(manufacturer == null  sku == null)          throw new SoapException("Client",                                  "Missing manufacturer or sku");       PreparedStatement stmt =          connection.prepareStatement("select level " +          "from products where manufacturer=? and sku=?");       try       {          stmt.setString(1,manufacturer.toString());          stmt.setString(2,sku.toString());          ResultSet rs = stmt.executeQuery();          try          {             writer.write("<psol:getStockResponse xmlns:psol='");             writer.write(Constants.PSOL_URI);             writer.write("'SOAP-ENV:encodingStyle='");             writer.write(Constants.SOAPENCODING_URI);             writer.write("'><stockq><manufacturer>");             writer.write(manufacturer.toString());             writer.write("</manufacturer><sku>");             writer.write(sku.toString());             writer.write("</sku><available>");             if(rs.next())             {                writer.write("true</available><level>");                writer.escape(rs.getString(1));             }             else                writer.write("false</available><level>0");             writer.write("</level></stockq>");             writer.write("</psol:getStockResponse>");          }          finally          {             rs.close();          }       }       finally       {          stmt.close();       }    } } 

StockResponse is also responsible for querying the database and writing the response in the writeResponse() method. Notice that in so doing, it ignores the SOAP envelope that will be added by SoapService :

 public void writeResponse(Connection connection,                           XMLWriter writer)    throws SQLException, IOException, SoapException {    if(manufacturer == null  sku == null)       throw new SoapException("Client",                               "Missing manufacturer or sku");    PreparedStatement stmt =       connection.prepareStatement("select level " +       "from products where manufacturer=? and sku=?");    try    {       stmt.setString(1,manufacturer.toString());       stmt.setString(2,sku.toString());       ResultSet rs = stmt.executeQuery();       try       {          writer.write("<psol:getStockResponse xmlns:psol='");          writer.write(Constants.PSOL_URI);          writer.write("'SOAP-ENV:encodingStyle='");          writer.write(Constants.SOAPENCODING_URI);          writer.write("'><stockq><manufacturer>");          writer.write(manufacturer.toString());          writer.write("</manufacturer><sku>");          writer.write(sku.toString());          writer.write("</sku><available>");          if(rs.next())          {             writer.write("true</available><level>");             writer.escape(rs.getString(1));          }          else             writer.write("false</available><level>0");          writer.write("</level></stockq>");          writer.write("</psol:getStockResponse>");       }       finally       {          rs.close();       }    }    finally    {       stmt.close();    } } 

StockQService

StockQService , in Listing 9.9, is the actual servlet. It parses SOAP requests and writes the response through StockResponse .

Listing 9.9 StockQService.java
 package com.psol.stockq; import java.io.*; import java.sql.*; import org.xml.sax.*; import javax.servlet.*; public class StockQService    extends SoapService {    public void init()       throws ServletException    {       try       {          Class.forName(getInitParameter("driver"));       }       catch(ClassNotFoundException e)       {          throw new ServletException(e);       }    }    public void doSoap(XMLReader reader,                       InputSource source,                       XMLWriter writer)       throws IOException, SoapException, SAXException    {       StockResponse response = new StockResponse();       reader.setContentHandler(response);       reader.parse(source);       try       {          String url = getInitParameter("url"),                 username = getInitParameter("username"),                 password = getInitParameter("password");          Connection connection =             DriverManager.getConnection(url,username,password);          try          {             response.writeResponse(connection,writer);          }          finally          {             connection.close();          }       }       catch(SQLException e)       {          throw new SoapException("Server",                                  "SQL: " + e.getMessage());       }    } } 

Warning

For SOAP, a one-to-one mapping between servlets and RPCs is not available. A servlet can accept different RPCs: It should recognize them by their names .


   


Applied XML Solutions
Applied XML Solutions
ISBN: 0672320541
EAN: 2147483647
Year: 1999
Pages: 142

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net