Perl Cd Bookshelf [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Perl Cd Bookshelf [Electronic resources] - نسخه متنی

Mark V. Scardina, Ben ChangandJinyu Wang

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید






Creating the XML Messaging Gateway

Since the whole point of this application is to extend the widget company’s business to the Internet, you must set up Internet access to its database schema. This can be done in a number of ways. The traditional approach is to use the middle tier to container-manage the database through object-relational applications such as Oracle Toplink, EJBs, etc. However, these applications can be difficult to set up and maintain. Therefore, instead, you will use Oracle Database 10g’s built-in messaging infrastructure, Oracle Streams Advance Queuing (AQ), and its AQ servlet to send and receive SOAP messages. This robust system can be extended to perform application processing upon the messages in the manner of workflow. To use AQ Streams, you need to set up the servlet as well as create the messaging schema for storing accepted and rejected POs.


Creating the Messaging Schema


Since this portion of your database will have exposure to the Internet, you need to create a separate user and restrict access. This can be done by logging in as SYSDBA and creating POUSER, as follows:

CREATE USER pouser IDENTIFIED BY pouser
DEFAULT tablespace USERS;
GRANT CONNECT, RESOURCE TO pouser;
-- Grant AQ privileges
GRANT execute ON dbms_aq TO pouser;
GRANT execute ON dbms_aqin TO pouser;
ALTER USER pouser GRANT CONNECT through pouser;
/

As you did for POADMIN earlier in the chapter, the preceding code grants to POUSER a number of AQ privileges as well as the ability to connect to the database.

You also need to set up the tables that will be needed by the AQ messaging queue to receive the POs and store copies of accepted and rejected POs. You can set up these tables with the following script:

CREATE TABLE po_tbl(
msgid RAW(16),
consumer VARCHAR2(100),
sender VARCHAR2(200),
podoc XMLTYPE
);
CREATE TABLE po_rejected_tbl(
msgid RAW(16),
consumer VARCHAR2(100),
sender VARCHAR2(200),
reason VARCHAR2(500),
podoc XMLTYPE
);
CREATE TABLE po_backup_tbl(
msgid RAW(16),
consumer VARCHAR2(100),
sender VARCHAR2(200),
podoc XMLTYPE
);
grant SELECT,DELETE on po_tbl to poadmin;
grant SELECT,DELETE on po_rejected_tbl to poadmin;
grant SELECT,DELETE on po_backup_tbl to poadmin;
/

Note that you need to grant specific privileges on these tables to the administrator in order to review and maintain them.

Finally, you need to set up specific user accounts in order to accept a PO. This permits you to set up a user ID and password for each company you wish to do business with. The following SQL script creates a PO Users table and seeds it with sample data:

CREATE TABLE POUSER(
user_id VARCHAR(10) PRIMARY KEY,
name VARCHAR2(50),
street VARCHAR2(100),
city VARCHAR2(200),
state VARCHAR2(20),
zip VARCHAR2(20),
country VARCHAR2(30)
);
INSERT INTO POUSER values('bob', 'Bob Smith','400 Oracle parkway',
'Redwood shores', 'CA', '94065','US');
INSERT INTO POUSER values('scott', 'Scott Tiger','400 Oracle parkway',
'Redwood shores', 'CA', '94065','US');
/

As you can see, there is no entry in the table for a password. This is because you will be using the JAZN support provided by the OC4J J2EE container in which you are going to deploy this application. JAZN is the Java Authentication and Authorization Service (JAAS) provider, which can be configured either with the Oracle Internet Directory (OID) LDAP repository or with an XML repository file. For simplicity, you will use the latter configuration, because it only requires adding a jazn-data.xml file to the deployment. To set this up, you need to create an AQ agent for each user and give the user the necessary execution privileges for database access, as in the following SQL script:

BEGIN
DBMS_AQADM.create_aq_agent(agent_name=>'"jazn.com/bob"',
enable_http =>true);
END;
/
EXECUTE dbms_aqadm.enable_db_access('JAZN.COM/BOB', 'pouser');

Note that the agent name refers to “jazn.com/bob”. This is the JAZN realm set for your J2EE application as defined in the myjazn-data.xml file that gets deployed with your application. This XML file defines not only the set of users and their passwords but also the policies and permissions for the realm.


Creating the AQ Agent and Queue


While AQ is built into the Oracle database, it still must be set up to supply messages to a predefined queue and then to your previously created tables. This is done with special functions that are part of the included DBMS_ADMIN package. The queue is nothing more than special tables for the messages, and since these POs are XML documents, you can use an XMLType table, as follows:

BEGIN
dbms_aqadm.create_queue_table(queue_table=>'poxml_qt',
queue_payload_type=>'SYS.XMLTYPE',
COMMENT=>'Purchase Order Queue Table',
multiple_consumers=>TRUE,
primary_instance=>1,
secondary_instance=>2);
END;
/
BEGIN
dbms_aqadm.create_queue(queue_name => 'poxml_q',
queue_table =>'poxml_qt');
END;
/
BEGIN
dbms_aqadm.start_queue (queue_name => 'poxml_q');
END;
/

POs as SOAP messages come into the database from the AQ servlet. Therefore, you need to extend the AQ servlet to handle these messages. This can be done by creating the following MyAQServlet class:

package oracle.xml.sample.XMLIntegration;
import java.io.PrintStream;
import java.io.FileOutputStream;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import oracle.AQ.xml.AQxmlException;
import oracle.AQ.xml.AQxmlDebug;
import oracle.AQ.xml.AQxmlDataSource;
import oracle.AQ.xml.AQxmlCallback;
public class MyAQServlet extends oracle.AQ.xml.AQxmlServlet {
public void init(ServletConfig p_config) throws ServletException {
// Database Variables
String username="pouser";
String password="pouser";
String sid="ORCL";
String host="localhost";
String port="1521";
AQxmlDataSource db_drv = null;
try {
// Set the log file
PrintStream debugFile = new PrintStream (new
FileOutputStream("aqdebug.log"));
debugFile.println("Function Called");
debugFile.flush();
super.init(p_config);
AQxmlDebug.setTraceLevel(5);
AQxmlDebug.setDebug(true);
AQxmlDebug.setLogStream(new FileOutputStream("aqlogfile"));
// Get and Set DataSource
db_drv = new AQxmlDataSource(username, password, sid, host, port);
this.setAQDataSource(db_drv);
}
catch (AQxmlException aq_ex) {
aq_ex.printStackTrace();
aq_ex.getNextException().printStackTrace();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}

There are several things you should note about the preceding code:



You need to use the javax.servlet classes that are part of the servlet.jar file included with OC4J to configure this class as an extension.



To aid in debugging, the AQxmlDebug functionality is set up and initialized to create a log file. The database communication is configured by using AqxmlDataSource to pass in the necessary login and database parameters.



An option to include a stylesheet is provided. The reason is that once a submission is made, the AQ Servlet provides a response in XML, as in the following example, and you may want to format it for better presentation if the response will be viewed by a person. The classes of the package ORACLE.AQ.XML are delivered with the database distribution, in a file called aqxml.jar.

<?xml version="1.0" ?>
<Envelope xmlns="http://ns.oracle.com/AQ/schemas/envelope">
<Body>
<AQXmlPublishResponse xmlns="http://ns.oracle.com/AQ/schemas/access">
<status_response>
<status_code>0</status_code>
</status_response>
<publish_result>
<destination>POADMIN.poxml_q</destination>
<message_id>21238FFE4B5D41E89E670D8CC147CD75</message_id>
</publish_result>
</AQXmlPublishResponse>
</Body>
</Envelope>



Every AQ submission has an associated ID that is generated by the database and that uniquely identifies the message. Therefore, this number can be used by customers to trace the disposition of their POs, regardless of the PO numbers they might have assigned.


Creating the AQ PO Process Procedures


Now that your messaging queue is configured to be able to accept POs, you can finally create the procedures to actually process them. You are going to do this by using a combination of PL/SQL and Java. The reason that you are going to use PL/SQL is that this processing is being done within the database itself. Had you chosen to implement this design with a midtier SOAP service and a messaging protocol such as JMS, everything could have been done in Java, with the data inserted using JDBC. In this case, the data is already in the database and you will use Java within a stored procedure only to validate and analyze the PO using the new XDK validator class.

Once a message is submitted, it is considered to be in the queue and needs to be dequeued to be processed. Then, the PO must be extracted from the SOAP envelope and validated against its schema. Finally, the PO must be inserted into the PO database schema to be fulfilled. This is done in a single PL/SQL procedure called dequeueXMLAnalysis(), which is listed in this section and then explained in its component parts because it is quite long.

First, you must load some subprocedures. As mentioned earlier, you are going to use Java to perform the validation and analysis; the following Java stored procedure will set this up:

CREATE OR REPLACE FUNCTION XMLSemanticProcessing(xml IN CLOB,
xsd IN CLOB,
result IN OUT CLOB)
RETURN VARCHAR2
IS LANGUAGE JAVA NAME
'oracle.xml.sample.semantics.XMLAnalysis.analyze(oracle.sql.CLOB,
oracle.sql.CLOB,oracle.sql.CLOB[]) returns java.lang.String';
/

The PO and the XSD schema are passed in as CLOBs. Even though these are XML documents, there is no need to use an XMLType here because the Java class will parse them; thus, they are simply character strings at input.

The other subprocedure performs the actual XML data insert and uses the PL/SQL versions of the XML SQL Utility that have the DBMS_ prefix. These are recommended over the older, unprefixed packages because they are written over C (versus calling Java in JServer), thus improving performance significantly.

CREATE OR REPLACE PROCEDURE insProc(xmlDoc IN CLOB,
tableName IN VARCHAR2) IS
insCtx DBMS_XMLSave.ctxType;
rows NUMBER;
BEGIN
insCtx := DBMS_XMLSave.newContext(tableName);
DBMS_XMLSave.setRowtag(insCtx,'purchaseorder');
rows := DBMS_XMLSave.insertXML(insCtx,xmlDoc);
DBMS_OUTPUT.put_line(rows||' rows inserted');
DBMS_XMLSave.closeContext(insCtx);
END;
/

Note that you are inserting into <purchaseorder> instead of the <PO> table of your original schema, because the POUSER schema spans multiple tables and you need to use the object view that you defined earlier. Upon insert, the INSTEAD OF INSERT trigger will fire, properly inserting the data and creating the appropriate keys to link the rows.

Now you get to the main procedure. Because this has many parts and calls other procedures, you need to have a set of variables declared as follows:

CREATE OR REPLACE PROCEDURE dequeueXMLAnalysis(p_consumer IN VARCHAR2,
p_queue IN VARCHAR2)
AS
v_msgid RAW(16);
v_dopt dbms_aq.dequeue_options_t;
v_mprop dbms_aq.message_properties_t;
v_payload XMLTYPE;
no_messages EXCEPTION;
PRAGMA EXCEPTION_INIT(no_messages, -25228);
v_xml CLOB;
v_xsd CLOB;
v_result CLOB;
v_insert CLOB;
v_out VARCHAR2(32767);
v_temp XMLType;
v_sendby VARCHAR2(32767);
v_id VARCHAR2(10);
v_i NUMBER;
v_name VARCHAR2(100);
v_doc XMLTYPE;

Note that v_dopt and v_mprop are simply aliases for longer function names. Now you begin the body by retrieving the XML document:

BEGIN
-- Setup Dequeue Options
v_dopt.consumer_name := p_consumer;
v_dopt.dequeue_mode := DBMS_AQ.REMOVE;
v_dopt.wait := DBMS_AQ.NO_WAIT;
v_dopt.navigation := DBMS_AQ.FIRST_MESSAGE;
LOOP
DBMS_AQ.dequeue(queue_name => p_queue,
dequeue_options => v_dopt,
message_properties => v_mprop,
payload => v_payload,
msgid => v_msgid);
v_xml := v_payload.getClobVal();

In the preceding code you start a loop to process each PO message. Once you have a PO, you need to get the schema to validate:

SELECT xsd INTO v_xsd FROM xsd_tbl WHERE id=1;

Then, perform the validation and analysis:

DBMS_LOB.createtemporary(v_result,TRUE,DBMS_LOB.SESSION);
DBMS_LOB.createtemporary(v_insert,TRUE,DBMS_LOB.SESSION);
v_out := XMLSemanticProcessing(v_xml,v_xsd,v_result);
IF v_out = 'OK' THEN
INSERT INTO po_tbl(msgid, consumer, podoc)
VALUES(v_msgid,p_consumer, XMLTYPE(v_result));
INSERT INTO po_backup_tbl(msgid, consumer, podoc)
VALUES(v_msgid,p_consumer, XMLTYPE(v_xml));

Note that based upon a successful validation and analysis returning OK, you store the results in two tables—one for further processing and one for archiving. Finally, you insert the data into the tables with XSU and end the loop:

 DBMS_LOB.OPEN(v_insert, DBMS_LOB.LOB_READWRITE);
DBMS_LOB.writeappend(v_insert,length('<ROWSET>'),'<ROWSET>');
DBMS_LOB.append(v_insert,v_result);
DBMS_LOB.writeappend(v_insert,length('</ROWSET>'),'</ROWSET>');
DBMS_LOB.CLOSE(v_insert);
dbms_output.put_line(v_msgid);
INSERT INTO poid values(v_msgid);
insProc(v_insert,'purchaseorder');
DBMS_LOB.freetemporary(v_insert);
DBMS_LOB.freetemporary(v_result);
-- Commit the transaction
DELETE FROM poid;
ELSE
DBMS_OUTPUT.put_line(v_out);
INSERT INTO po_rejected_tbl(msgid, consumer, podoc,reason)
VALUES(v_msgid,p_consumer, XMLTYPE(v_xml),v_out);
END IF;
COMMIT;
v_dopt.navigation := DBMS_AQ.NEXT_MESSAGE;
END LOOP

Note that the XML was encapsulated in <ROWSET> elements. This is done to make the XML data acceptable by XSU because the document listed at the end would not have a single root. Also, rejected POs are stored in a separate po_rejected_tbl. The procedure then ends by handling possible errors:

EXCEPTION
WHEN no_messages THEN
DBMS_OUTPUT.put_line( 'Error:'||'No more messages in queue.');
END;
/

Now that you have walked through the process, you can see the transformation that took place between the original PO and the XML that was inserted by XSU. The following is the original PO:

<purchaseOrder orderDate="2003-10-20"
xmlns="http://ns.oracle.com/AQ/schemas/access">
<ShipTo country="US">
<name>California Whirligig</name>
<street>123 Maple Street</street>
<city>Mill Valley</city>
<state>CA</state>
<zip>90952</zip>
</ShipTo>
<BillTo country="US">
<name>U.S. Whirligig</name>
<street>8 Oak Avenue</street>
<city>Old Town</city>
<state>PA</state>
<zip>95819</zip>
</BillTo>
<LineItems>
<LineItem partNum="872-AA">
<ProductName>Micro Widget</ProductName>
<Quantity>1</Quantity>
<UnitofPrice>148.95</UnitofPrice>
</LineItem>
<LineItem partNum="926-AA">
<ProductName>Multi-Widget</ProductName>
<Quantity>1</Quantity>
<UnitofPrice>39.98</UnitofPrice>
</LineItem>
</LineItems>
<comment>Hurry, I can't stop this thing!</comment>
</purchaseOrder>

The original PO was transformed into the following:

<ROWSET>
<ROW num="1">
<PO_ID>7F2F2FED620F4400A2F598B23965492F</PO_ID>
<SHIPPING_ADDR>
<CUSTOMER_NAME>California Whirligig</CUSTOMER_NAME>
<STREET>123 Maple Street</STREET>
<CITY>Mill Valley</CITY>
<STATE>CA</STATE>
<ZIP>90952</ZIP>
<COUNTRY>US</COUNTRY>
</SHIPPING_ADDR>
<BILLING_ADDR>
<CUSTOMER_NAME>U.S. Whirligig</CUSTOMER_NAME>
<STREET>8 Oak Avenue</STREET>
<CITY>Old Town</CITY>
<STATE>PA</STATE>
<ZIP>95819</ZIP>
<COUNTRY>US</COUNTRY>
</BILLING_ADDR>
<LINEITEMS>
<LINEITEMS_ITEM>
<PRODUCT_ID>872-AA</PRODUCT_ID>
<PRODUCT_NAME>Micro Widget</PRODUCT_NAME>
<PRODUCT_QUANTITY>1</PRODUCT_QUANTITY>
<PRODUCT_PRICE>148.95</PRODUCT_PRICE>
</LINEITEMS_ITEM>
<LINEITEMS_ITEM>
<PRODUCT_ID>926-AA</PRODUCT_ID>
<PRODUCT_NAME>Multi-Widget</PRODUCT_NAME>
<PRODUCT_QUANTITY>1</PRODUCT_QUANTITY>
<PRODUCT_PRICE>39.98</PRODUCT_PRICE>
</LINEITEMS_ITEM>
</LINEITEMS>
</ROW>
</ROWSET>

Note that the <comment> element was ignored and the element names were transformed. This process occurred during the analysis phase in Java. The next section discusses this phase in detail.


Extending the Framework


Although your company is a successful one, it is not in position to dictate that all of your customers use your PO XML schema. Therefore, when companies want to do business with your company, they will provide their own PO schemas. You could store each of them and load them individually each time you get a PO, to validate it. This is expensive, because you not only need to retrieve it but also need to parse it and build a compiled schema object that will be thrown away each time. Instead of using these separately for each respective PO instance, you can extract the types you are interested in and add them to your base schema. The following is an example of a schema that semantically meets your requirements but uses different syntax:

<xsd:schema targetNamespace="http://ns.oracle.com/AQ/schemas/access"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xdk="http://xmlns.oracle.com/xdk"
xmlns="http://ns.oracle.com/AQ/schemas/access"
elementFormDefault="qualified" xdk:TableName="ORDERS">
<!-- Address Type -->
<xsd:complexType name="AddressType">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"
xdk:SQLName="CUSTOMER_NAME"/>
<xsd:element name="street" type="xsd:string"
xdk:SQLName="STREET"/>
<xsd:element name="city" type="xsd:string"
xdk:SQLName="CITY"/>
<xsd:element name="state" type="xsd:string"
xdk:SQLName="STATE"/>
<xsd:element name="zip" type="xsd:string"
xdk:SQLName="ZIP"/>
</xsd:sequence>
<xsd:attribute name="country" type="xsd:string"
xdk:SQLName="COUNTRY"/>
<xsd:anyAttribute processContents="skip"/>
</xsd:complexType>
<!--Sendby Address Element-->
<xsd:element name="sendby" type="AddressType"
xdk:SQLName="SENDBY_ADDR"/>
<!--Shipping Address Element-->
<xsd:element name="DeliverTo" type="AddressType"
xdk:SQLName="SHIPPING_ADDR"/>
<xsd:element name="ShipTo" type="AddressType"
xdk:SQLName="SHIPPING_ADDR"/>
<!--Billing Address Element-->
<xsd:element name="BillTo" type="AddressType"
xdk:SQLName="BILLING_ADDR"/>
<xsd:element name="InvoiceTo" type="AddressType"
xdk:SQLName="BILLING_ADDR"/>
<!--Line Item Type-->
<xsd:complexType name="LineItemType">
<xsd:all>
<xsd:element name="ProductName" type="xsd:string"
xdk:SQLName="PRODUCT_NAME"/>
<xsd:element name="Quantity" type="xsd:string"
xdk:SQLName="PRODUCT_QUANTITY"/>
<xsd:element name="UnitofPrice" type="xsd:string"
xdk:SQLName="PRODUCT_PRICE"/>
</xsd:all>
<xsd:attribute name="partNum" type="xsd:string"
xdk:SQLName="PRODUCT_ID"/>
</xsd:complexType>
<xsd:complexType name="POItemType">
<xsd:all>
<xsd:element name="ProductID" type="xsd:string"
xdk:SQLName="PRODUCT_ID"/>
<xsd:element name="Quantity" type="xsd:string"
xdk:SQLName="PRODUCT_QUANTITY"/>
<xsd:element name="UnitofPrice" type="xsd:string"
xdk:SQLName="PRODUCT_PRICE"/>
</xsd:all>
<xsd:attribute name="name" type="xsd:string"
xdk:SQLName="PRODUCT_NAME"/>
</xsd:complexType>
<!-- Line Items Element -->
<xsd:element name="LineItems" xdk:SQLName="LINEITEMS">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="LineItem" type="LineItemType"
maxOccurs="unbounded"
xdk:SQLName="LINEITEM"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="POLines" xdk:SQLName="LINEITEMS">
<xsd:complexType>
<xsd:choice>
<xsd:element name="POItem" type="POItemType"
maxOccurs="unbounded"
xdk:SQLName="LINEITEM"/>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>

There are a few unexpected aspects to this schema that allow you to use it for more than simple validation. First, each named element and attribute has an additional xdk:SQLName=" annotation that corresponds to the column name in your database schema. These annotations can be retrieved by your application by using the new XSDValidator class in the xmlparserv2.jar file. These annotations conform to the extension mechanisms provided for in the XML Schema 1.0 specification and provide a similar function to the xdb:SQLType annotations used to direct the XML DB upon schema registration.

Additionally, note that there are multiple elements that map to the same xdk:SQLName, such as “ShipTo” and “DeliverTo”. These reflect the addition of company schemas into your master schema, thus allowing you to only load one schema regardless of the PO being received. In fact, your application can keep its compiled schema object in memory for all validations. Because you extracted these types from the original company PO schemas, any changes to them that do not change the specific types you need will not cause you to reject the PO. This is an important feature that promotes the reliability of your application.

Now you are ready to analyze the Java code that will use this XML schema to deliver a validated and transformed PO that can be successfully inserted into your PO database schema. This code is organized into two source files, XMLAnalysis.java and XMLAnalysisHandler.java, the latter of which is called by the former to perform most of the work. The following are code fragments from XMLAnalysis.java:

public class XMLAnalysis {
public XMLAnalysis() {
}
public static String analyze(CLOB xml_doc, CLOB xsd_doc,
CLOB[] res_doc) {
Reader rd;
Writer wt = null;
// Check the Result CLOB
if(res_doc[0] == null) {
return "CLOB to write can not be null.";
}
try {
wt =res_doc[0].getCharacterOutputStream();
//Build up XML Schema Object
rd = xsd_doc.getCharacterStream();
XSDBuilder builder = new XSDBuilder();
XMLSchema schemadoc = (XMLSchema)builder.build(rd,null);
// Create Content Handlers
XMLAnalysisHandler POHandler =
new XMLAnalysisHandler(new PrintWriter(wt));
// Setup for SAX XML Schema Validation
SAXParser parser = new SAXParser();
parser.setContentHandler(POHandler);
POHandler.setXMLProperty(XSDConstantValues.FIXED_SCHEMA,schemadoc);
POHandler.setXMLProperty(XSDConstantValues.VALIDATION_MODE,
XSDConstantValues.LAX_VALIDATION);
// Parse the XML document
rd = xml_doc.getCharacterStream();
parser.parse(rd);
} catch(Exception e) {
e.printStackTrace();
return e.getMessage();
}
return "OK";
}

This is the method that gets the XML document and performs a SAX-based validation by using LAX mode and registering the XMLAnalysisHandler to receive the SAX events. The reason for using LAX mode is that the instance document may contain content that you are not interested in or that your application is not set up to consume, such as the <comment> element included earlier. In LAX mode the schema processor skips unknown content but performs STRICT mode validation on known content.

All of the actual work is done in the XMLAnalysisHandler. The following listing includes the declarations and constructor of this handler:

public class XMLAnalysisHandler extends XSDValidator {
private PrintWriter out;
private boolean isSelect;
private StringBuffer buf;
private int level;
private Hashtable po_tbl;
private Hashtable name_tbl;
private DMElement cur_elem;
private XMLError err;
private XSDElement elementNode;
public XMLAnalysisHandler(PrintWriter out)
throws XSDException,SAXException
{
po_tbl = new Hashtable();
name_tbl = new Hashtable();
buf = new StringBuffer();
err = new XMLError();
setError(err);
isSelect= false;
level = 0;
this.out= out;
write("<PURCHASEORDER>\n");
}

Note that the java.util.Hashtable class is used to store the elements of the PO and that an initial <purchaseOrder> element is the root tag of the transformed output.

Since this is a SAX parse, you need to register event handlers for each type of SAX event you use. In this case, you need startElement(), endElement(), and endDocument(). The following lists their respective code fragments:

 public void startElement(String namespaceURI, String localName,
String qName, Attributes atts) throws SAXException
{
String schemaName = null;
super.startElement(namespaceURI,localName,qName,atts);
// Create the DMElement at the start of Strict validation
if(getCurrentMode()==_strict && level==0)
{
isSelect=true;
cur_elem= new DMElement();
//level=curState;
level=0;
}
if (isSelect == true) {
level++;
//Get mapped name based upon localName
String elementName = getElementName(localName);
if(elementName != null)
{
//store in hashtable with localName as key
name_tbl.put(localName, elementName);
//write into buffer new mapped tag name and its attributes
writeElement("<"+elementName+">");
printAttributes(atts);
}
}
}

This handler is using the getCurrentMode method in the XSDValidator class to query the state of its schema validation. If the mode returned is _strict, the content will be buffered for transformation in DMElement, as this is a known element, and subsequently written to the name_tbl hash table. On the other hand, any _lax results indicate an unrecognized element that will be ignored. Note also the use of the level variable as a way to handle the nesting of XML elements. Once a strict validation starts, it is used as a stack to indicate the process is continuing. Finally, this handler calls getElementName(), which is an internal method to retrieve the mapped name based upon the local name. The following is the code:

String getElementName(String localName) {
String elementName=";
//Call new interface to get schema node associated with localname
elementNode = getElementDeclaration();
// Find the XDK Annotation from the attributes
if (elementNode != null) {
XSDAnnotation ann = elementNode.getAnnotation();
if ( ann != null) {
Vector attrs = ann.getAttributes();
int attrs_size = attrs.size();
for (int i =0; i< attrs_size; i++) {
XMLAttr xsd_attr = (XMLAttr)attrs.get(i);
String name = xsd_attr.getName();
if (name.equals("xdk:SQLName")) {
elementName= xsd_attr.getValue();
break;
}
}
}
//if no annotation let elementName=localName;
if (elementName.equals(")) return null;
}
return elementName;
}
}

This method uses getElementDeclaration, which is another of the XSDValidator methods, and returns the node from the master XML schema in order to retrieve the xdk:SQLName annotation. This is done by populating a vector with all the annotations by calling the getAnnontation() method from the XSDAnnotation class. Once populated, the attrs vector can be traversed to find the matching name. If no SQLName annotation is found, then the element name is returned unchanged.

While XML documents have the concept of attributes that may contain data, relational databases do not. Therefore, any data-conveying attributes must be transformed, or canonicalized, into XML elements in order for them to be stored relationally. This is the purpose of the printAttributes() method in the startElement() handler. The following is the associated code:

void printAttributes(Attributes atts) {
String attr_name;
int len = atts.getLength();
if (len <1) return;
for (int i=0; i< len; i++) {
attr_name = getAttributeName(atts.getLocalName(i));
if(attr_name != null)
{
writeElement("<"+attr_name+">");
writeElement(atts.getValue(i));
writeElement("</"+attr_name+">");
}
}
}

Note that it needs to walk through the list of attributes for the XML element, and when a mapping is found by a string returned from getAttributeName(), write out its value as a mapped XML element. The code to check for this match is a derivation of the getElementName() method, as follows:

String getAttributeName(String localName) {
String attrName=";
if(elementNode == null) return localName;
elementNode = getElementDeclaration();
XSDAttribute[] attrNode = elementNode.getAttributeDeclarations();
for (int j=0;j<attrNode.length; j++) {
if (attrNode[j].getName().equals(localName)) {
XSDAnnotation ann = attrNode[j].getAnnotation();
if ( ann != null) {
Vector attrs = ann.getAttributes();
int attrs_size = attrs.size();
for (int i =0; i< attrs_size; i++) {
XMLAttr xsd_attr = (XMLAttr)attrs.get(i);
String name = xsd_attr.getName();
if (name.equals("xdk:SQLName")) {
attrName= xsd_attr.getValue();
break;
}
}
}
}
}
if (attrName.equals(")) return null; //attrName = localName;
return attrName;
}

Similar to getElementDeclaration(), there is a corresponding attribute version called getAttributeDeclarations() that returns all the attribute nodes associated with an element. However, since there can be many nodes, it returns them in an array, and thus your code must walk through the attrNode array of attrs vectors.

Turning now to the endElement() code, you can see how the complete element is processed in the following code:

public void endElement(String namespaceURI, String localName,String qName)
throws SAXException
{
XSDAnnotation ann;
String elementName;
super.endElement(namespaceURI, localName,qName);
if (isSelect == true) {
level--;
flushChar();
// Output the endTag of the Element
elementName = (String)name_tbl.get(localName);
writeElement("</"+elementName+">");
// If validation errors exists, exit the process
int i = err.getFirstError();
if (i!=-1)
throw new
SAXException("(Line,Column)("+err.getLineNumber(i)+","+
err.getColumnNumber(i)+"):"+err.getMessage(i));
// Save the Validated Element
cur_elem.level = level;
cur_elem.isValid=true;
po_tbl.put(elementName, cur_elem);
if(level==0){
isSelect=false;
}
} else {
flushChar();
}
}

First, note that although the validation is namespace aware, your database schema is not; therefore, you are only interested in the local name of the element for processing. Since this is the end tag and you have already done the mapping for the start tag, it can be written by using a simple hash table lookup based upon the localName. If the element correctly validates, it is sent to the po_tbl hash table, from which it will be evaluated.

Once all the elements are processed, the endDocument() event is received. This event triggers several actions, as illustrated in the following handler code:

 public void endDocument() throws SAXException
{
String[] ship_elems = new String[]{"SHIPPING_ADDR"};
String[] bill_elems = new String[]{"BILLING_ADDR"};
String[] line_elems = new String[]{"LINEITEMS"};
DMElement ck_elem = null;
//Check Required Shipping Address
ck_elem = (DMElement) po_tbl.get(ship_elems[i]);
if (ck_elem != null)
write(ck_elem.content);
if (ck_elem == null)
throw new SAXException("Missing required Shipping Address!");
else if (ck_elem.content == null)
throw new SAXException("Shipping Address doesn't have content!");
//Check Required Billing Address
ck_elem = null;
ck_elem = (DMElement) po_tbl.get(bill_elems[i]);
if (ck_elem != null)
write(ck_elem.content);
if (ck_elem == null)
throw new SAXException("Missing required Billing Address!");
else if (ck_elem.content == null)
throw new SAXException("Billing Address doesn't have content!");
//Check Required LineItems
ck_elem = null;
ck_elem = (DMElement) po_tbl.get(line_elems[i]);
if (ck_elem != null)
write(ck_elem.content);
if (ck_elem == null)
throw new SAXException("Missing required LineItems!");
else if (ck_elem.content == null)
throw new SAXException("LineItems don't have content!");
//Write new PO closing tag
write("</PURCHASEORDER>");
out.flush();
}

First, the essential types that you need to have a complete PO are initialized as strings.





Note

Instead of being hard-coded, these strings could be read from an external configuration file or the DB itself, thus making the code more generic and extensible.


Then, you use these strings to look up the elements from the po_tbl hash table and check to see not only whether the element is there but also whether it contains content as is done in each ck_elem() function. If either of these conditions fails, then the appropriate error message is returned. If both conditions pass, then the closing element tag is printed and the validated and transformed document is returned for submission by XSU into the database.

/ 218