Package KFM.XML

Source Code of KFM.XML.XmlWithSchemaMapper

/*
*  This software and supporting documentation were developed by
*
*    Siemens Corporate Technology
*    Competence Center Knowledge Management and Business Transformation
*    D-81730 Munich, Germany
*
*    Authors (representing a really great team ;-) )
*            Stefan B. Augustin, Thorbj�rn Hansen, Manfred Langen
*
*  This software is Open Source under GNU General Public License (GPL).
*  Read the text of this license in LICENSE.TXT
*  or look at www.opensource.org/licenses/
*
*  Once more we emphasize, that:
*  THIS SOFTWARE IS MADE AVAILABLE,  AS IS,  WITHOUT ANY WARRANTY
*  REGARDING  THE  SOFTWARE,  ITS  PERFORMANCE OR
*  FITNESS FOR ANY PARTICULAR USE, FREEDOM FROM ANY COMPUTER DISEASES OR
*  ITS CONFORMITY TO ANY SPECIFICATION. THE ENTIRE RISK AS TO QUALITY AND
*  PERFORMANCE OF THE SOFTWARE IS WITH THE USER.
*
*/


// XmlWithSchemaMapper

// ************ package ****************************************************
package KFM.XML;

// ************ imports ****************************************************
import org.w3c.dom.*;
import org.w3c.dom.traversal.*;
import javax.xml.parsers.*;

import java.io.*;
import java.util.*;

import KFM.Exceptions.*;
import KFM.GUI.RequestUrl;
import KFM.GUI.HttpParams;
import KFM.Converter;
import KFM.log.*;


/**
* Builds of a RequestUrl containing parameters in dotted style a XML Document
* driven by the structure of a XML schema.
*
*/
public class XmlWithSchemaMapper
{
    // ************************************************************
    // Variables
    // ************************************************************

    /** The XML Schema Document */
    Document mSchema = null;

    /** Location of the XML Schema file. */
    String mSchemaLocation = null;

    /** The result DOM Object to generate. */
    Document mGeneratedDoc = null;

    /** The RequestUrl containing parameters in dotted style ("context.elem$2.elem"). */
    RequestUrl mParams = null;

    /** Vector of Strings holding *all possible/allowed* RequestUrl parameters
     * in the correct order. This Vector is built by traversing the XML schema. */
    Vector mKeyOrder = new Vector();

    /** Prefix of all params, e.g. "KB.community" */
    String mKeyPrefix = null;

    /** Build empty elements for empty parameter values. */
    boolean mComplete;

    TreeWalker mXSCompositorWalker = null;
    TreeWalker mXSContentModelWalker = null;


    // ************************************************************
    // Inner classes
    // ************************************************************

    /**
     * DOM Traversal NodeFilter instance for focussing only on compositor elements when traversing
     * a XML schema. A compositor is one of the following elements:
     * - xs:sequence
     * - xs:all
     * - xs:choice
     */
    class XSCompositor implements NodeFilter
    {
        public short acceptNode(Node aNode)
        {
            String tNodeName = aNode.getNodeName();
            if (tNodeName.equals("xs:sequence") || tNodeName.equals("xs:all") || tNodeName.equals("xs:choice"))
                return FILTER_ACCEPT;
            else
                return FILTER_SKIP;
        }
    }


    /**
     * DOM Traversal NodeFilter instance for focussing only on content model elements when
     * traversing a XML schema. Content model elements are all XML schema elements relevant for
     * describing the schema structure:
     * - xs:sequence, xs:all, xs:choice (compositors)
     * - xs:complexType, xs:simpleType
     * - xs:element
     *
     * Todo: Support for further relevant elements (xs:group, ...). Maybe for the fullest possible
     * xml schema implementation in XmlWithSchemaBuilder only "xs:annotation" and "xs:documentation"
     * schould be filtered.
     */
    class XSContentModel implements NodeFilter
    {
        public short acceptNode(Node aNode)
        {
            String tNodeName = aNode.getNodeName();
            if (tNodeName.equals("xs:sequence") || tNodeName.equals("xs:all") || tNodeName.equals("xs:choice")
                || tNodeName.equals("xs:element")
                || tNodeName.equals("xs:complexType") || tNodeName.equals("xs:simpleType"))
                return FILTER_ACCEPT;
            else
                return FILTER_SKIP;
        }
    }



    // ************************************************************
    // Constructor
    // ************************************************************

    /**
     * Construct an instance of XmlWithSchemaBuilder.
     */
    public XmlWithSchemaMapper(String aSchemaLocation, String aKeyPrefix)
        throws KFMException
    {
        mSchemaLocation = aSchemaLocation;
        mKeyPrefix = aKeyPrefix;

        // Parse the XML Schema to a DOM object
        mSchema = DOM.parseToDOM(new File(mSchemaLocation), false /* Xerces can't validate XML schema */);

        // Traverse the Schema and find all possible Keys
        buildKeyOrder();
    }



    // ************************************************************
    // Methods
    // ************************************************************

    /**
     *
     * @param aParams
     * @param aComplete Build empty elements for empty parameter values
     * @param aCdataAsText Build CDATA sections as normal text nodes (hack for Bookmarks import)
     * @return
     * @throws KFMException Wraps ParserConfigurationException
     * @throws ProgrammerException If DOM Level 2 Traversal is not supported
     */
    public Document buildXmlWithSchema(RequestUrl aParams, boolean aComplete, boolean aCdataAsText)
        throws KFMException
    {
        mParams = aParams;
        mComplete = aComplete;

        // Prepare (empty) DOM that will be built up later in this class.
        try {
            mGeneratedDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        } catch (ParserConfigurationException e) {
            // should never throw an exception in a correct xerces environment
            throw new KFMException(e);
        }
        // The name of mGeneratedDoc's root element is defined in the XML schema.
        // Assumption: It is exactly the the first (but maybe not the only one) "xs:element"
        // under the schema root.
        Element tSchemaDocElement = (Element)mSchema.getDocumentElement().getElementsByTagName("xs:element").item(0);
        String tRootElemName = tSchemaDocElement.getAttribute("name");
        Element tGenRootElem = mGeneratedDoc.createElement(tRootElemName);
        tGenRootElem.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
        tGenRootElem.setAttribute("xsi:noNamespaceSchemaLocation", mSchemaLocation);
        mGeneratedDoc.appendChild(tGenRootElem);

        buildTree(tGenRootElem, aCdataAsText);

        return mGeneratedDoc;
    }


    /**
     * Traverse the XML schema and find all possible RequestUrls in the right order.
     * The result goes to member variable mKeyOrder.
     * @param aKeyPrefix
     * @throws KFMException
     */
    private void buildKeyOrder()
        throws KFMException
    {
        // The schema DOM must support DOM Level 2 Traversal
        if (!mSchema.getImplementation().hasFeature("Traversal", "2.0")) {
            throw new ProgrammerException("XmlWithSchemaBuilder.buildKeyOrder: DOM Level 2 Traversal not supported.");
        }

        // Prepare Traversal TreeWalkers for navigating through the XML schema.
        DocumentTraversal tTraversal = (DocumentTraversal)mSchema;
        mXSCompositorWalker = tTraversal.createTreeWalker(mSchema.getDocumentElement(),
            NodeFilter.SHOW_ELEMENT, (NodeFilter)new XSCompositor(), true);
        mXSContentModelWalker = tTraversal.createTreeWalker(mSchema.getDocumentElement(),
            NodeFilter.SHOW_ELEMENT, (NodeFilter)new XSContentModel(), true);

        // The entry point for traversing recursively the XML schema is the very first
        // occurence of a compositor, i.e. the compositor of the root element.
        Element tFirstCompositor=(Element)mXSCompositorWalker.nextNode();
        traverse(tFirstCompositor, mKeyPrefix);
    }


    /**
     * Public accessor to the possible RequestUrls of a Schema (result of buildKeyOrder).
     */
    public Vector getKeyOrder()
    {
        return mKeyOrder;
    }


    /**
     * Traverse recursively the XML schema and find all possible RequestUrls in the right order.
     * The argument aXSCompositor is a Element passing the XSCompositor NodeFilter. A compositor
     * is a container with an amount of "xs:element"s (children). Two cases can occur:
     * 1) An Element is a simpleType. Then it defines a leaf in the tree structure and will
     *    result in a new parameter entry in mKeyOrder.
     * 2) An Element is a complexType. Then it will contain another compositor with child
     *    elements of this Element. We will start the next recursion with the new compositor.
     *
     * @param aXSCompositor Compositor Element in the XML schema
     * @param aContextPath
     */
     private void traverse(Element aXSCompositor, String aContextPath)
     {
         // For each child Element of the compositor...
         mXSContentModelWalker.setCurrentNode(aXSCompositor);
         for (Element tXSCurrentElement = (Element)mXSContentModelWalker.firstChild();
              tXSCurrentElement != null;
              tXSCurrentElement = (Element)mXSContentModelWalker.nextSibling()) {

             String tXSCurrentElemName = tXSCurrentElement.getAttribute("name"); // name of element to generate
             int tMaxOccurs = getMaxOccours(tXSCurrentElement);

             //System.out.println("*** " + tXSCurrentElement.getNodeName() + " - "
             //                   + tXSCurrentElemName + " - "
             //                   + tMaxOccurs + " - " + aContextPath);

             // Find out the content model. Each Element has exactly one (can be NULL if simpleType):
             NodeList tXSContentModelNL = ((Element)tXSCurrentElement).getElementsByTagName("xs:complexType");
             if (tXSContentModelNL.getLength() > 0) {
                 // We have a complexType, i.e. a nested structure.
                 // Find the new child compositor Element for next recursion.
                 mXSCompositorWalker.setCurrentNode(tXSContentModelNL.item(0));
                 Element tNewXSCompositor = (Element)mXSCompositorWalker.nextNode();

                 // For each possible occurence of the current Element step with tNewXSCompositor
                 // deeper in the tree. Grow the ContextPath with tXSCurrentElemName.
                 String tNewContextPath;
                 for (int i = 1; i <= tMaxOccurs; i++) {
                     if (tMaxOccurs == 1) {
                         tNewContextPath = aContextPath + "." + tXSCurrentElemName;
                     } else {
                         tNewContextPath = aContextPath + "." + tXSCurrentElemName + "$" + i;
                     }
                     // Next recursion with the new content model
                     traverse(tNewXSCompositor, tNewContextPath);
                     // Each iteration (context$1, context$2, ...) must start at the same point.
                     mXSContentModelWalker.setCurrentNode(tXSCurrentElement);
                 }
             } else {
                 // We have a simpleType (or no simpleType element, which means the same).
                 // Add parameter(s) to the vector.
                 for (int i = 1; i <= tMaxOccurs; i++) {
                     if (tMaxOccurs == 1) {
                         mKeyOrder.add(aContextPath + "." + tXSCurrentElemName);
                     } else {
                         mKeyOrder.add(aContextPath + "." + tXSCurrentElemName + "$" + i);
                     }
                 }
             }
         } /*for*/
     }


     /**
      * Builds a complete DOM of the given request parameters using the previously computed
      * Vector mKeyOrder.
      *
      * @param aRoot Root Element of the tree ("KnowledgeBean")
      * @param aCdataAsText Build CDATA sections as normal text nodes (hack for Bookmarks import)
      */
     private void buildTree(Element aRoot, boolean aCdataAsText)
     {
         Stack tElementStack = new Stack(); //of DOM Elements
         Element tParentElement = aRoot;
         StringBuffer tParentPath = new StringBuffer(mKeyPrefix);

         // The currently worked parameter (called path because we use here the parameter's
         // dot-syntax that represents the path in the tree from the root down to the leaf.
         String tCurrentPath;
         // The value (text) of the corresponding tCurrentPath.
         String tCurrentValue;

         // Work all possible RequestUrl parameters exactly in the given order
         int i = 0;
         while (i < mKeyOrder.size()) {
             tCurrentPath = (String)mKeyOrder.get(i);
             tCurrentValue = mParams.getParam(tCurrentPath);
             //System.out.println("i = " + i + ", ParentPath:" + tParentPath +
             //                   ",  CurrentPath: " + tCurrentPath + " = " + tCurrentValue);

             if (! mComplete && tCurrentValue.equals("")) {
                 // Only if no empty elements for empty parameter values should be built:
                 // The XML element is empty, do not add it. Continue with next key.
                 // Due to the representation in mKeyOrder, this will automatically work for
                 // elements that only contain empty elements, i.e. "<a><b></b></a>" will yield "".
                 i++;
             } else if (isDescendantOf(tCurrentPath, tParentPath.toString())) {
                 // We have a child element, the tree is growing one Element
                 String tChildName = getChildName(tCurrentPath, tParentPath.toString());
                 tParentPath.append("." + tChildName);
                 // Create the child Element, without the possible $ and following number
                 if (tChildName.indexOf("$") != -1)
                     tChildName = tChildName.substring(0, tChildName.indexOf("$"));
                 Element tChild = mGeneratedDoc.createElement(tChildName);
                 tParentElement.appendChild(tChild);
                 // We memorize the parent Element in the stack and set the child Element as new parent Element.
                 // Then continue with the same i, we only have a new parent Element (deeper)
                 tElementStack.push(tParentElement);
                 tParentElement = tChild;
             } else if (isEqual(tCurrentPath, tParentPath.toString())) {
                 // We have created all Elements that tCurrentPath requires.
                 // Just append the value as text or as CDATA section.
                 // If the text is empty (can only happen, if mComplete is true), add a "xsi:nil" attribute to
                 // indicate that there's an empty element. This is needed for correct validation error handling.
                 if (tCurrentValue.equals("")) {
                     tParentElement.setAttribute("xsi:nil", "true");
                 }

                 if (tCurrentValue.startsWith("<![CDATA[")) {
                     tCurrentValue = tCurrentValue.substring(9, tCurrentValue.length() - 3);
                     if (aCdataAsText) {
                         // Hack for Bookmarks import to prevent nested CDATA sections
                         tParentElement.appendChild(mGeneratedDoc.createTextNode(tCurrentValue));
                     } else {
                         tParentElement.appendChild(mGeneratedDoc.createCDATASection(tCurrentValue));
                     }
                 } else {
                     tParentElement.appendChild(mGeneratedDoc.createTextNode(tCurrentValue));
                 }

                 // Continue with next key.
                 i++;
             } else if (isNotDescendantOf(tCurrentPath, tParentPath.toString())) {
                 // We have a tCurrentPath that points to a leaf out of tParentPath.
                 // Step one Element up in the tree by setting the "grandpa" Element as new parent Element
                 tParentElement = (Element)tElementStack.pop();
                 // Shorten the tParentPath of one Element
                 int tStart = tParentPath.toString().lastIndexOf(".");
                 tParentPath.delete(tStart, Integer.MAX_VALUE);
             } else {
                 throw new ProgrammerException("XmlWithSchemaBuilder.buildTree: Unreachable code reached.");
             }
         } /*while*/
     }

     /**
      * Compute the child name of aParentPath. This is the path segment of aCurrentPath,
      * that follows aParentPath up to the next dot.
      * e.g. if aCurrentPath="a.b.c.d.e" and aParent="a.b.c", then the name is "d".
      */
     private String getChildName(String aCurrentPath, String aParentPath) {
         int tStart = aParentPath.length() + 1;
         int tEnd = aCurrentPath.indexOf(".", tStart);
         if (tEnd == -1)
             tEnd = aCurrentPath.length();
         return aCurrentPath.substring(tStart, tEnd);
     }


     /**
      * aCurrent is an descendant of aParent, if aCurrent starts with the same path as aParent.
      * e.g. if aCurrent="a.b.c.d" and aParent="a.b.c", then aCurrent is a descendant of aParent.
      */
     private boolean isDescendantOf(String aCurrent, String aParent)
     {
         return aCurrent.startsWith(aParent + ".");
     }

     /**
      * aCurrent is equal to aParent, if the path of aCurrent is exactly the same as the path of aParent.
      * e.g. if aCurrent="a.b.c.d" and aParent="a.b.c.d", then they are equal.
      */
     private boolean isEqual(String aCurrent, String aParent)
     {
         return aCurrent.equals(aParent);
     }

     /**
      * aCurrent is not an descendant of aParent, if aCurrent dosn't start with the same path as aParent.
      * e.g. if aCurrent="a.e.f.g" and aParent="a.b.c", then aCurrent is no descendant of aParent.
      */
     private boolean isNotDescendantOf(String aCurrent, String aParent)
     {
         return ! aCurrent.startsWith(aParent);
     }


     /**
      * Determine the value of Attribute maxOccurs of a given Element.
      * If the Attribute is missing, it will default to 1.
      * We have forbidden maxOccurs="unbounded" because else we couldn't build a (finite)
      * vector of all possible RequestUrl parameters.
      *
      * @param aElement DOM Element (of XML schema)
      * @return Maximum number of occurences of this Element
      */
     private int getMaxOccours(Element aElement)
     {
         String tMaxOccursAttr = aElement.getAttribute("maxOccurs");
         if (tMaxOccursAttr.equals("")) {
             // if no attribute was given, this means maxOccurs="1"
             return 1;
         } else if (tMaxOccursAttr.equals("unbounded")) {
             throw new ProgrammerException("XmlWithSchemaBuilder.getMaxOccours: Element "
                     + aElement.getNodeName() + ": unbounded not allowed.");
         } else {
             try {
                 return Integer.parseInt(tMaxOccursAttr);
             } catch (NumberFormatException e) {
                 throw new ProgrammerException("XmlWithSchemaBuilder.getMaxOccours: Element "
                         + aElement.getNodeName() + "has invalid value " + tMaxOccursAttr);
             }
         }
     }


     /**
      *
      * @param aDocument DOM to serialize
      * @param aDocPrefix Container XML Element, where the data is in the document
      * @return
      * @throws KFMException
      */
     public RequestUrl serializeToHttpParams(
             Document aDocument,
             String aDocPrefix)
             throws KFMException
     {
         // The currently worked parameter (called path because we use here the parameter's
         // dot-syntax that represents the path in the tree from the root down to the leaf.
         String tCurrentPath;

         RequestUrl tParams = new HttpParams();

         String tElementValue = null;
         String tNewPath = null;
         String tXPathExpression = null;
         for (int i = 0; i < mKeyOrder.size(); i++) {
             tCurrentPath = (String)mKeyOrder.get(i);

             // map mKeyPrefix to aDocPrefix in aCurrentPath
             // e.g. "KB.community.sponsor$2.url" -> "KnowledgeBean.sponsor$2.url"
             tNewPath = aDocPrefix + "." + tCurrentPath.substring(mKeyPrefix.length() + 1);
             tXPathExpression = constructXPathFromParam(tNewPath);
             tElementValue = DOM.applyXPath(aDocument, tXPathExpression);

             // Value exists corresponding to the possible Key.
             // (null means: element is missing, "" means: empty element)
             if (! (tElementValue == null || "".equals(tElementValue))) {
                 tParams.addParam(tCurrentPath, tElementValue);
                 // System.out.println("* " + tCurrentPath + " = " + tElementValue);
             }
         }

         return tParams;
     }


     /**
      * Construct a XPath expression from a given param in dot-style.
      * e.g. "KnowledgeBean.sponsor$2.url" -> "/KnowledgeBean/sponsor[position()=2]/url/text()"
      *
      * @param aCurrentPath
      * @return XPath expression string
      */
     private String constructXPathFromParam(String aCurrentPath)
     {
         StringBuffer tXpath = new StringBuffer();
         String[] tLocationSteps = Converter.stringToArray(aCurrentPath.toString(), ".", false);

         for (int i = 0; i < tLocationSteps.length; i++) {
             int tIndex = tLocationSteps[i].indexOf("$");
             String tStep = (tIndex != -1) ? tLocationSteps[i].substring(0, tIndex) : tLocationSteps[i];

             // add a location step
             tXpath.append("/" + tStep);

             // when there are multiple element occurances,
             // add an additional predicate to indicate its position.
             try {
                 int tOccurance = Integer.parseInt(tLocationSteps[i].substring(tIndex + 1));
                 tXpath.append("[position()=" + tOccurance + "]");
             } catch (NumberFormatException e) {
                 // there was no number to parse, this is OK.
             }
         }

         // We are interested in the element text, and not in the whole node
         tXpath.append("/text()");

         //System.out.println("\n" + aCurrentPath + " => " + tXpath);
         return tXpath.toString();
     }



     // for testing
     public static void main(String[] args)
             throws Exception
     {
         String tSchemaLocation = "O:/KFM/www-docs/public/Portal/SieMap/DTD/KB.community.xsd";
         XmlWithSchemaMapper tMapper = new XmlWithSchemaMapper(tSchemaLocation, "KB.community");

         // Test 1: buildXmlWithSchema
         System.out.println("buildXmlWithSchema:\n-------------------");

         RequestUrl tParams = new HttpParams();
         tParams.addParam("KB.community.membershipPolicy", "restricted");
         tParams.addParam("KB.community.communication", "FaceToFace");
         tParams.addParam("KB.community.contact.name", "Hans");
         //tParams.addParam("KB.community.contact.email", "a@siemens.com");
         tParams.addParam("KB.community.contact.email", "a@siemens.com");
         tParams.addParam("KB.community.sponsor$1.name", "ICM");
         tParams.addParam("KB.community.sponsor$1.url", "http://www.icm.de");
         tParams.addParam("KB.community.sponsor$2.name", "CIO");
         tParams.addParam("KB.community.sponsor$2.url", "http://www.cio.com");
         tParams.addParam("KB.community.hasSubCoPs$1", "subCoP1");
         tParams.addParam("KB.community.hasSubCoPs$2", "subCoP2");
         tParams.addParam("KB.community.hasSubCoPs$3", "subCoP3");
         tParams.addParam("KB.community.since", "20x02-12-02");

         Document tGenDoc = tMapper.buildXmlWithSchema(tParams, true /*complete*/, false /*CDATA as text*/);
         System.out.println(DOM.serializeToString(tGenDoc, "ISO-8859-1"));

         KFMSystem.log.setLogLevel(KFMLog.DEBUG_LEVEL);
         XmlValidator tValidator = new XmlValidator(KFMSystem.log);
         tValidator.reparseDom(tGenDoc, "KB.community");


         // Test 2: serializeToHttpParams
         System.out.println("\n\nserializeToHttpParams:\n----------------------");

         String tXmlFile = "O:/KFM/www-docs/public/Portal/SieMap/DTD/KB_Community.xml";
         Document tXmlDoc = DOM.parseToDOM(new File(tXmlFile), true);
         HttpParams tParamsGen = (HttpParams)tMapper.serializeToHttpParams(
                 tXmlDoc,
                 "KnowledgeBean");

         Enumeration tParamNames = tParamsGen.getParameterNames();
         String tName = null;
         while (tParamNames.hasMoreElements()) {
             tName = (String)tParamNames.nextElement();
             System.out.println(tName + " = " + tParamsGen.getParam(tName));
         }
     }

     private static void addParamsToHashMap(HashMap aHashMap, String aName, String[] aParams)
     {
         Vector tV = new Vector();
         for (int i = 0; i < aParams.length; i++) {
             tV.add(aParams[i]);
         }
         aHashMap.put(aName, tV);
     }
}
TOP

Related Classes of KFM.XML.XmlWithSchemaMapper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.