/*  Copyright (c) 2002, Oracle Corporation. All Rights Reserved. */
package oracle.portal.provider.v2.webservice;

import java.io.File;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
import org.w3c.dom.Document;
import oracle.xml.parser.v2.XMLDocument;
import oracle.webdb.utils.SimpleStringBuffer;
import oracle.portal.utils.xml.v2.XMLUtil;
import oracle.portal.utils.xml.v2.InitializableXMLObject;
import oracle.portal.utils.xml.v2.DefaultNodeHandler;
import oracle.portal.utils.xml.v2.NodeHandlerException;
import oracle.portal.provider.v2.PortletException;

/**
 * A <code>LiteralXML</code> is used to describe the structure and bindings of
 * a literal XML request to a web service. It implements the {@link
 * InitializableXMLObject} interface, and is designed to be loaded from an XML
 * definition file by a {@link oracle.portal.utils.xml.v2.NodeHandler
 * NodeHandler}.<p>
 *
 * Because the XML description of a <code>LiteralXML</code> can contain nested
 * XML elements, it is important that the class's own node handler, {@link
 * LiteralXML.Handler} is used to process its XML description. This can be
 * ensured by using the <code>handler</code> attribute. For example:
 *
 * <pre>
 * &lt;literal class="oracle.portal.provider.v2.webservice.LiteralXML"
 *   handler="oracle.portal.provider.v2.webservice.LiteralXML$Handler">
 * </pre>
 *
 * Within the above style of XML description, the <code>&lt;attribute></code>
 * element can be used to describe an XML attribute whose name is specified by
 * its <code>name</code> attribute. An <code>&lt;attribute></code> element can
 * optionally contain text, corresponding to a constant value for the
 * attribute.<p>
 *
 * Similarly, the <code>&lt;element></code> element can be used to describe an
 * XML element whose name is specified by its <code>name</code> attribute. An
 * <code>&lt;element></code> can optionally contain text, corresponding to
 * constant textual content of the element, <code>&lt;attribute></code>
 * elements, corresponding to attributes set on the element and further child
 * <code>&lt;element></code>s corresponding to nested elements.<p>
 *
 * Both <code>&lt;element></code>s and <code>&lt;attribute></code>s can be
 * 'bound' to dynamic content described by a <code>bind</code> attribute. A
 * <code>bind</code> attribute should consist of a type name and a parameter
 * name separated by a '/' character. The allowed bind type names and their
 * meanings are listed below:
 *
 * <ul>
 *
 * <li><code>request</code>: The element or attribute is bound to an attribute
 * of the portlet render request, specified by the suffix of the bind
 * attribute. This creates a {@link WebServiceRenderer.RequestParam}.</li>
 *
 * <li><code>urlParams</code>: The element or attribute is bound to a parameter
 * in the query string of the Portal page URL with a name specified by the
 * suffix of the bind attribute. This creates a {@link
 * WebServiceRenderer.URLParam}.</li>
 *
 * <li><code>session</code>: The element or attribute is bound to the attribute
 * of the {@link oracle.portal.provider.v2.ProviderSession ProviderSession}
 * with name specified by the suffix of the bind attribute. This creates a
 * {@link WebServiceRenderer.SessionAttrib}.</li>
 *
 * <li><code>editData</code>: The element or attribute is bound to the string
 * value stored in a {@link
 * oracle.portal.provider.v2.personalize.NameValuePersonalizationObject
 * NameValuePersonalizationObject} against the name specified by the suffix of
 * the bind attribute. This creates a {@link WebServiceRenderer.EditData}.</li>
 *
 * </ul>
 *
 * When the <code>bind</code> attribute is set, the <code>default</code>,
 * <code>mandatory</code> and <code>prompt</code> attributes can also be used
 * to set the corresponding property on the {@link
 * WebServiceRenderer.ParameterBinding} they describe.
 */
public class LiteralXML implements InitializableXMLObject
{
    private boolean mIsLocked;
    private WebServiceRenderer mParent;
    private NodeList mNodeList;

    /**
     * Performs the steps required to initialize a <code>LiteralXML</code>
     * object after it has been constructed.
     * @param o the object that will become this object's parent
     * @exception NodeHandlerException if an error occurs while initializing
     * this object
     */
    public void preInitialize(Object parent)
        throws NodeHandlerException
    {
        // Make sure we are not in read-only 'locked down' state
        checkLocked("preInitialize");

        if (!(parent instanceof WebServiceRenderer))
        {
            throw new NodeHandlerException(new SimpleStringBuffer(100)
                .append("Parent of a ")
                .append(getClass().getName())
                .append(" must be a ")
                .append(WebServiceRenderer.class.getName()).toString());
        }
        mParent = (WebServiceRenderer)parent;
    }

    /**
     * Performs the extra steps required to initialize and validate a
     * <code>LiteralXML</code> object once its properties have been set with
     * the appropriate accessor methods.
     * @exception NodeHandlerException if not all properties have been set
     * correctly
     */
    public void postInitialize()
        throws NodeHandlerException
    {
        // Make sure we are not in read-only 'locked down' state
        checkLocked("postInitialize");

        if (mNodeList != null)
        {
            int length = mNodeList.getLength();
            Node n;
            for (int i=0; i<length; i++)
            {
                n = mNodeList.item(i);
                if (!(n instanceof Element))
                {
                    continue;
                }
                bind((Element)n);
            }
        }

        // 'Lock down' the class so that it can no longer be modified
        mIsLocked = true;
    }

    /**
     * Sets the list of top level elements in the literal XML description.
     * @param nodeList list of top level elements in the literal XML
     * description
     */
    public final void setNodeList(NodeList nodeList)
    {
        // Make sure we are not in read-only 'locked down' state
        checkLocked("setNodeList");

        mNodeList = nodeList;
    }

    /**
     * Gets the list of top level elements in the literal XML description.
     * @return list of top level elements in the literal XML
     * description
     */
    public final NodeList getNodeList()
    {
        return mNodeList;
    }

    /**
     * 'Instantiates' the XML description by translating
     * <code>&lt;element></code>s into 'real' elements,
     * <code>&lt;attribute></code>s into 'real' attributes, and expanding out
     * bindings.
     */
    public NodeList generateElements(WebServiceRenderRequest wrr)
        throws PortletException
    {
        if (mNodeList == null)
        {
            return null;
        }

        XMLDocument factory = new XMLDocument();
        Element docEl = factory.createElement("literalXML");
        factory.appendChild(docEl);

        WebServiceRenderer.ParameterBinding[] bindings = mParent.getBindings();
        Iterator binds = null;
        if (bindings != null)
        {
            binds = Arrays.asList(bindings).iterator();
        }

        int length = mNodeList.getLength();
        Node n;
        for (int i=0; i<length; i++)
        {
            n = mNodeList.item(i);
            if (!(n instanceof Element))
            {
                continue;
            }
            generate(wrr, (Element)n, binds, factory, docEl);
        }
        
        if (mParent.getLogging())
        {
            try
            {
                String loggingDirectory = mParent.getLoggingDirectory(wrr);
                File requestLog = new File (loggingDirectory,
                                            new SimpleStringBuffer(50)
                                                .append(wrr.getPortletInstance().getInstanceName())
                                                .append("_literal.xml").toString());
                OutputStream requestOs = new FileOutputStream(requestLog);
                factory.print(requestOs);
                requestOs.close();
            }
            catch (IOException ioe)
            {
                throw new PortletException(ioe);
            }
        }

        return docEl.getChildNodes();
    }

    private void bind(Element el)
        throws NodeHandlerException
    {
        if (!el.hasAttribute("name"))
        {
            return;
        }
        String name = el.getAttribute("name");
        String bindPath = el.hasAttribute("bind") ? el.getAttribute("bind") : null;

        if (el.getTagName().equals("element"))
        {
            if (bindPath != null)
            {
                addBinding(bindPath,
                           el.hasAttribute("default") ? 
                           el.getAttribute("default") :
                           null,
                           XMLUtil.getBooleanValue(el.getAttribute("mandatory")),
                           el.hasAttribute("prompt") ? 
                           el.getAttribute("prompt") :
                           null);
            }

            // Recurse on children
            for (Node n = el.getFirstChild();
                 n != null;
                 n = n.getNextSibling())
            {
                if (n instanceof Element)
                {
                    bind((Element)n);
                }
            }

        }
        else if (el.getTagName().equals("attribute"))
        {
            if (bindPath != null)
            {
                addBinding(bindPath,
                           el.hasAttribute("default") ? 
                           el.getAttribute("default") :
                           null,
                           XMLUtil.getBooleanValue(el.getAttribute("mandatory")),
                           el.hasAttribute("prompt") ? 
                           el.getAttribute("prompt") :
                           null);
            }
        }
    }

    private void generate(WebServiceRenderRequest wrr, Element el, Iterator binds, 
                          Document factory, Node parent)
        throws PortletException
    {
        if (!el.hasAttribute("name"))
        {
            return;
        }
        String name = el.getAttribute("name");
        String bindPath = el.hasAttribute("bind") ? el.getAttribute("bind") : null;

        Element newEl;
        if (el.getTagName().equals("element"))
        {
            newEl = factory.createElement(name);
            parent.appendChild(newEl);

            if (bindPath != null)
            {
                String strValue = ((WebServiceRenderer.ParameterBinding)binds.next()).getValue(wrr);
                if (strValue != null)
                {
                    Text value = factory.createTextNode(strValue);
                    newEl.appendChild(value);
                }
            }

            // Recurse on children
            for (Node n = el.getFirstChild();
                 n != null;
                 n = n.getNextSibling())
            {
                if (n instanceof Text)
                {
                    Text value = factory.createTextNode(((Text)n).getData());
                    newEl.appendChild(value);
                }
                else if (n instanceof Element)
                {
                    generate(wrr, (Element)n, binds, factory, newEl);
                }
            }

        }
        else if (el.getTagName().equals("attribute") &&
                 parent instanceof Element)
        {
            newEl = (Element)parent;
            if (bindPath == null)
            {
                for (Node n = el.getFirstChild();
                     n != null;
                     n = n.getNextSibling())
                {
                    if (n instanceof Text)
                    {
                        newEl.setAttribute(name, ((Text)n).getData());
                    }
                }
            }
            else
            {
                String strValue = ((WebServiceRenderer.ParameterBinding)binds.next()).getValue(wrr);
                if (strValue != null)
                {
                    newEl.setAttribute(name, strValue);
                }
            }
        }
    }

    private void addBinding(String bindPath, String deflt, boolean isMandatory, String prompt)
        throws NodeHandlerException
    {
        int sepIndex = bindPath.indexOf('/');
        if (sepIndex == -1)
        {
            return;
        }
        String bindType = bindPath.substring(0, sepIndex);
        String paramPath = bindPath.substring(sepIndex+1);
        if (bindType.equals("request"))
        {
            mParent.addBinding(new WebServiceRenderer.RequestParam(paramPath, deflt, isMandatory));
        }
        else if (bindType.equals("urlParams"))
        {
            mParent.addBinding(new WebServiceRenderer.URLParam(paramPath, deflt, isMandatory, prompt));
        }
        else if (bindType.equals("editData"))
        {
            mParent.addBinding(new WebServiceRenderer.EditData(paramPath, deflt, isMandatory, prompt));
        }
        else if (bindType.equals("session"))
        {
            mParent.addBinding(new WebServiceRenderer.SessionAttrib(paramPath, deflt, isMandatory));
        }
        else
        {
            throw new NodeHandlerException(new SimpleStringBuffer(100)
                .append("Unrecognized bind type: ")
                .append(bindType).toString());
        }
    }

    /**
     * <code>LiteralXML.Handler</code> is the <code>NodeHandler</code> that
     * should be used to process XML descriptions of a {@link LiteralXML}
     * object.
     * @see LiteralXML
     */
    public static class Handler extends DefaultNodeHandler
    {
        /**
         * Recursively processes all the children of a given XML node in the
         * context of a <code>LiteralXML</code> object represented by that
         * node, in order to populate the <code>LiteralXML</code> and establish
         * parameter bindings .
         * @param n current node whose children will be processed to populate the
         * context application object.
         * @param o the context application object. Should be a {@link
         * LiteralXML}.
         * @exception NodeHandlerException if given object is not a valid
         * {@link LiteralXML}.
         */
        public void processNode(Node n, Object o) throws NodeHandlerException
        {
            if (!(o instanceof LiteralXML))
            {
                throw new NodeHandlerException(new SimpleStringBuffer(100)
                    .append(getClass().getName())
                    .append(" only supports class ")
                    .append(LiteralXML.class.getName()).toString());
            }
            ((LiteralXML)o).setNodeList(n.getChildNodes());
        }

    }

    // Called by each mutating method to ensure class has not been 'locked
    // down'
    private void checkLocked(String accessor)
    {
        if (mIsLocked)
        {
            throw new IllegalStateException(new SimpleStringBuffer(100)
                .append("Illegal call to ")
                .append(accessor)
                .append(" after initialization").toString());
        }
    }

}
