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

import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.List;
import java.util.ArrayList;
import org.xml.sax.SAXException;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.w3c.dom.Comment;
import org.w3c.dom.Element;
import oracle.xml.parser.v2.XMLDocument;
import oracle.xml.parser.v2.XSLStylesheet;
import oracle.xml.parser.v2.XSLProcessor;
import oracle.xml.parser.v2.XSLException;
import oracle.webdb.utils.SimpleStringBuffer;
import oracle.portal.log.LogManager;
import oracle.portal.utils.xml.v2.NodeHandlerException;
import oracle.portal.utils.xml.v2.XMLUtil;
import oracle.portal.provider.v2.PortletException;
import oracle.portal.provider.v2.Validateable;
import oracle.portal.provider.v2.render.PortletRenderRequest;
import oracle.portal.provider.v2.render.http.BaseManagedRenderer;
import oracle.portal.provider.v2.personalize.NameValuePersonalizationObject;
import oracle.portal.provider.v2.render.PortletRenderer;
import oracle.portal.provider.v2.render.PortletRendererUtil;
import oracle.portal.provider.v2.utils.ValidationError;

/**
 * <code>WebServiceRenderer</code> is the base class for web service enabled
 * renderers. A <code>WebServiceRenderer</code> is responsible for invoking a
 * web service and presenting its results as a portlet. Parameters in the
 * request to the web service can be bound to any of the following sources:
 *
 * <ul>
 *
 * <li>Arbitrary constants (see {@link WebServiceRenderer.ConstParam})</li>
 *
 * <li>Portlet render request parameters (see {@link
 * WebServiceRenderer.RequestParam})</li>
 *
 * <li>Parameters from the URL query string sent to the portlet (see {@link
 * WebServiceRenderer.URLParam})</li>
 *
 * <li>Provider session attributes (see {@link
 * WebServiceRenderer.SessionAttrib})</li>
 *
 * <li>Items of customization data from a {@link
 * NameValuePersonalizationObject} (see {@link
 * WebServiceRenderer.EditData})</li>
 *
 * </ul>
 *
 * Parameter bindings are controlled by the <code>literal</code> property, when
 * the request to the web service is a literal XML structure, or through
 * properties in a subclass (e.g. the <code>param</code> property on {@link
 * RPCWebServiceRenderer}).<p>
 *
 * The transformation from web service response to portlet response is
 * controlled by an XSL stylesheet, named by the <code>responseXSL</code>
 * property. Although, this file must be specified, it <i>need not exist</i>,
 * since a default stylesheet is generated based on the parameter bindings if
 * one does not exist.<p>
 *
 * A <code>WebServiceRenderer</code> supports an XML logging feature, where XML
 * documents corresponding to various stages in the renderering process are
 * dumped to the filesystem to help debugging. The feature is controlled by the
 * <code>logging</code> boolean property. When activated, one or more XML
 * documents are dumped to the directory named by the
 * <code>loggingDirectory</code> property if it is set, or the provider
 * 'repository path' otherwise (usually the same directory as the XML provider
 * definition). Each document has a filename that has the portlet instance name
 * as a prefix, and one of the following suffixes
 *
 * <ul>
 *
 * <li><code>_literal.xml</code>: A dump of the instantiated literal XML
 * structure which was sent to the web service. This is only written when the
 * web service arguments are defined with the <code>literal</code>
 * property.</li>
 *
 * <li><code>_request.xml</code>: A snapshot of the combined response document
 * before the web service was invoked. This helps when validating that all the
 * correct arguments are being passed.</li>
 *
 * <li><code>_response.xml</code>: A snapshot of the combined response document
 * after the web service has been invoked and its response has been added. This
 * is the XML document to which the stylesheet is applied. If this document is
 * missing, it probably means that an exception occurred when calling the web
 * service. You should check <code>application.log</code> to find a log of all
 * the exceptions that occurred.</li>
 *
 * </ul>
 *
 * The main entry point is the {@link #renderBody} method.
 */
public abstract class WebServiceRenderer extends BaseManagedRenderer
{
    private static final String NAMESPACE_PREFIX = "xmlns";
    private static final String XSL_PREFIX = "xsl";
    private static final String XSL_NAMESPACE_ATTRIBUTE = NAMESPACE_PREFIX + ":" + XSL_PREFIX;
    private static final String XSL_NAMESPACE = "http://www.w3.org/1999/XSL/Transform";
    private static final String TITLE_PARAMETER_NAME ="wsr_title";

    private boolean mIsLocked;
    private boolean mLogging;
    private String mLoggingDirectory;
    private boolean mEscapeOutput = true;
    private String mResponseXSLName;
    private File mResponseXSLFile;
    private long mResponseXSLLastModified;
    private Object mResponseXSLLock = new Object();
    private XSLStylesheet mResponseXSL;
    private XSLStylesheet mRequestXSL;
    private List mBindingList = new ArrayList(10);
    private ParameterBinding[] mBindings;
    private LiteralXML mLiteralXML;

    /**
     * Performs the extra steps required to initialize and validate a
     * <code>WebServiceRenderer</code> 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");

        super.postInitialize();

        // Convert the accumulated bindings into a more useful array form
        if (mBindingList.size() > 0)
        {
            mBindings = new ParameterBinding[mBindingList.size()];
            mBindingList.toArray(mBindings);
        }

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

    /**
     * Controls whether the XML logging debug feature is activated (see class
     * doc for more information). By default the feature is inactive.
     * @param logging String representation of boolean value. Should be
     * <code>"true"</code> if the feature should be activated,
     * <code>"false"</code> otherwise.
     */
    public final void setLogging(String logging)
    {
        // Make sure we are not in read-only 'locked down' state
        checkLocked("setLogging");

        mLogging = XMLUtil.getBooleanValue(logging);
    }

    /**
     * Determines whether the XML logging debug feature is activated (see class
     * doc for more information).
     * @return <code>true</code> if the feature is active, <code>false</code>
     * otherwise.
     */
    public final boolean getLogging()
    {
        return mLogging;
    }

    /**
     * Sets the directory where files are created by the XML logging debug
     * feature. If this directory is not set explicitly, the provider
     * 'repository path' (usually the same directory as the XML provider
     * definition) is used.
     * @param loggingDirectory the name of directory where files are created by
     * the XML logging debug feature
     * @exception PortletException if a directory with the given name does not
     * exist
     */
    public final void setLoggingDirectory(String loggingDirectory)
        throws PortletException
    {
        // Make sure we are not in read-only 'locked down' state
        checkLocked("setLoggingDirectory");

        File loggingFile = new File(loggingDirectory);
        if (!loggingFile.isDirectory())
        {
            throw new PortletException(new SimpleStringBuffer(100)
                .append("Directory \"")
                .append(loggingDirectory)
                .append("\" does not exist").toString());
        }
        mLoggingDirectory = loggingDirectory;
    }

    /**
     * Gets the directory where files are created by the XML logging debug
     * feature. If this directory has not been set explicitly with {@link
     * #setLoggingDirectory}, the provider 'repository path' (usually the same
     * directory as the XML provider definition) is returned.
     * @param wrr an object exposing the relevant properties of the current
     * render request
     * @return the directory where files are created by the XML logging debug
     * feature
     */
    public String getLoggingDirectory(WebServiceRenderRequest wrr)
    {
        return mLoggingDirectory == null ?
            wrr.getRepositoryPath() : mLoggingDirectory;
    }

    /**
     * Controls whether control characters in the response from the web service
     * (such as '&lt;' and '&amp;') are escaped in the portlet response so that
     * they display in HTML. By default, control characters are
     * escaped. Escaping should be disabled, e.g. if the web service returns a
     * string containing HTML tags, which should be copied to the portlet
     * response verbatim.
     * @param escapeOutput String representation of boolean value. Should be
     * <code>"true"</code> if control character escaping is to be enabled,
     * <code>"false"</code> otherwise.
     */
    public final void setEscapeOutput(String escapeOutput)
    {
        // Make sure we are not in read-only 'locked down' state
        checkLocked("setEscapeOutput");

        mEscapeOutput = XMLUtil.getBooleanValue(escapeOutput);
    }

    /**
     * Determines whether control characters in the response from the web service
     * (such as '&lt;' and '&amp;') are to be escaped in the portlet response so that
     * they display in HTML.
     *
     * @return <code>true</code> if control character escaping is enabled,
     * <code>false</code> otherwise.
     */
    public final boolean getEscapeOutput()
    {
        return mEscapeOutput;
    }

    /**
     * Sets the filename of the XSL stylesheet used to transform the XML result
     * document into a portlet response. The file is assumed to be stored in
     * the provider 'repository' directory (usually the same directory as the
     * XML provider definition).
     * @param responseXSL filename of the XSL stylesheet used to transform the
     * XML result document into a portlet reponse
     */
    public void setResponseXSL(String responseXSL)
    {
        // Make sure we are not in read-only 'locked down' state
        checkLocked("setResponseXSL");

        mResponseXSLName = responseXSL;
    }

    /**
     * Defines the literal XML structure that should be sent in the request to the web service.
     * @param literal object representing the literal XML structure that should
     * be sent in the request to the web service. Should be an instance of
     * {@link LiteralXML}.
     * @exception PortletException if the given object is not a valid {@link
     * LiteralXML}
     */
    public void addLiteral(Object literal)
        throws PortletException
    {
        // Make sure we are not in read-only 'locked down' state
        checkLocked("addLiteral");

        if (!(literal instanceof LiteralXML))
        {
            throw new PortletException(new SimpleStringBuffer(100)
                .append("literal object must be of class ")
                .append(LiteralXML.class.getName()).toString());
        }
        mLiteralXML = (LiteralXML)literal;
    }

    /**
     * Gets an object representing the literal XML structure that should be
     * sent in the request to the web service (if applicable).
     * @return object representing the literal XML structure that should be
     * sent in the request to the web service or <code>null</code> if not
     * applicable.
     */
    public LiteralXML getLiteral()
    {
        return mLiteralXML;
    }

    /**
     * Determines the values of parameter bindings using the given
     * <code>WebServiceRenderRequest</code>, invokes the web service, and adds
     * an XML representation of its response to the response XML
     * document. Subclasses should implement.
     * @param wrr an object exposing the relevant properties of the current
     * render request
     * @param docEl the document element of the XML document to which an XML
     * representation of the response should be added
     * @exception PortletException if an error occurs.
     */
    public abstract void invokeService (WebServiceRenderRequest wrr, Element docEl)
        throws PortletException;

    /**
     * Gets an array of all the <code>ParameterBinding</code>s declared for the
     * request to the web service. These are set up either indirectly through
     * {@link #addLiteral} or directly through a subclass method.
     * @return an array of all the <code>ParameterBinding</code>s declared for
     * the request to the web service
     */
    public final ParameterBinding[] getBindings()
    {
        return mBindings;
    }

    /**
     * The main rendering method, responsible for calling the respective mode
     * depending on PortletRenderRequest mode. The method proceeds as follows:
     * <ol>
     * <li>Call {@link #renderShow} to render the show mode.</li>
     * <li>Call {@link #renderEdit to render the edit mode.</li>
     * <li>Call {@link #renderEditDefaults} to render the edit mode.</li>
     * <li>Call {@link #renderLink} to render the link mode.</li>
     * </ol>
     * @param pr a <code>PortletRenderRequest</code>
     * @exception PortletException if an error occurs while renderering
     */
    public void renderBody(PortletRenderRequest pr)  throws PortletException
    {

        switch (pr.getMode())
        {

             case PortletRenderer.MODE_SHOW:
                  renderShow(pr);
                  break;

             case PortletRenderer.MODE_EDIT:
                  renderEdit(pr);
                  break;

             case PortletRenderer.MODE_EDIT_DEFAULTS:
                  renderEditDefaults(pr);
                  break;

             case PortletRenderer.MODE_PREVIEW:
                  renderShow(pr);
                  break;

             case PortletRenderer.MODE_LINK:
                  renderLink(pr);
                  break;

             default:
                   throw new PortletException( new SimpleStringBuffer(50)
                                           .append("WebServiceRenderer render failed: Invalid ")
                                           .append("render mode ")
                                           .append(pr.getMode()).toString());
        }
    }

    /**
     * The show mode method, responsible for calling a web service and
     * presenting its results as a portlet. The method proceeds as follows:
     * <ol>
     * <li>Construct a {@link WebServiceRenderRequest} from the given
     * <code>PortletRenderRequest</code>.</li>
     * <li>'Serialize' it to the output XML document.</li>
     * <li>Call {@link #invokeService} to send the request to the web service
     * and add the response to the output XML document.</li>
     * <li>If the XSL file specified in <code>responseXSL</code> does not yet
     * exist, generate a default one (that includes HTML form input fields for
     * all declared {@link WebServiceRenderer.URLParam}s).</li>
     * <li>Parse the XSL if it has been updated since it was last read.</li>
     * <li>Apply the XSL transformation to the XML document to produce portlet
     * HTML.</li>
     * </ol>
     * @param pr a <code>PortletRenderRequest</code>
     * @exception PortletException if an error occurs while renderering
     */
    public void renderShow(PortletRenderRequest pr)
        throws PortletException
    {
        try
        {
            WebServiceRenderRequest wr = new WebServiceRenderRequest(pr);
            String loggingDirectory = getLoggingDirectory(wr);
            XMLDocument responseDoc = new XMLDocument();
            Element docEl = responseDoc.createElement("doc");
            responseDoc.appendChild(docEl);

            wr.addToXml(docEl);

            // Create the parent element for the XML record of the argument list
            Element bindingsEl = responseDoc.createElement("bindings");
            docEl.appendChild(bindingsEl);
            boolean allParametersPresent = true;
            // Get our parameter bindings
            ParameterBinding[] bindings = getBindings();
            if (bindings != null)
            {
                Element paramEl;
                Element attribName;
                String attribValue;
                for (int i=0; i<bindings.length; i++)
                {
                    // Record the names and values of the parameter bindings in
                    // the XML request representation
                    paramEl = responseDoc.createElement("param");
                    bindingsEl.appendChild(paramEl);
                    attribName = responseDoc.createElement("name");
                    paramEl.appendChild(attribName);
                    attribValue = bindings[i].getName();
                    attribName.appendChild(responseDoc.createTextNode(attribValue));

                    attribValue = bindings[i].getValue(wr);
                    if (attribValue == null)
                    {
                        // If a mandatory parameter is missing, stop the web
                        // service from being invoked
                        if (bindings[i].isMandatory())
                        {
                            allParametersPresent = false;
                        }
                    }
                    // Only add the value if it is non-null
                    else
                    {
                        attribName = responseDoc.createElement("value");
                        paramEl.appendChild(attribName);
                        attribName.appendChild(responseDoc.createTextNode(attribValue));
                    }
                }
            }

            if (mLogging)
            {
                File requestLog = new File (loggingDirectory,
                                            new SimpleStringBuffer(50)
                                                .append(wr.getPortletInstance().getInstanceName())
                                                .append("_request.xml").toString());
                OutputStream requestOs = new FileOutputStream(requestLog);
                responseDoc.print(requestOs);
                requestOs.close();
            }

            // Only invoke the web service if it has all the parameters it
            // needs. Otherwise, the stylesheet should take the strain
            if (allParametersPresent)
            {
                invokeService(wr, docEl);
            }

            if (mLogging)
            {
                File responseLog = new File (loggingDirectory,
                                             new SimpleStringBuffer(50)
                                                 .append(wr.getPortletInstance().getInstanceName())
                                                 .append("_response.xml").toString());
                OutputStream responseOs = new FileOutputStream(responseLog);
                responseDoc.print(responseOs);
                responseOs.close();
            }

            // Transform the response document into the render response
            XSLProcessor xp = new XSLProcessor();
            xp.processXSL(getResponseXSL(wr), responseDoc, pr.getWriter());
        }
        catch (RuntimeException re)
        {
            throw re;
        }
        catch (PortletException pe)
        {
            throw pe;
        }
        catch (Exception e)
        {
            throw new PortletException(e);
        }
    }

    /**
     * This method is responsible for displaying a screen that allows customization
     * of the portlet title and parameters bound to edit data.
     * @param pr a <code>PortletRenderRequest</code>
     * @exception PortletException if an error occurs while renderering
     */
    public void renderEdit(PortletRenderRequest pr)  throws PortletException
    {
      try
      {
            // Retrieve the name of the parameter used to represent the user
            // action taken on the edit form.
            String actionParam = PortletRendererUtil.getEditFormParameter(pr);

            // Now retrieve the value of that action parameter.
            String action = pr.getParameter(actionParam);

            WebServiceRenderRequest wr = new WebServiceRenderRequest(pr);
            NameValuePersonalizationObject data = wr.getEditData();

            // retrieve the bind values
            ParameterBinding pb[] = getBindings();

            if (action != null)
            {

                // Write the personalized values into the data object.
                data.setPortletTitle(wr.getURLParameter(WebServiceRenderer.TITLE_PARAMETER_NAME));

                for( int i=0; i<pb.length; i++)
                {
                  if (!(pb[i] instanceof EditData))
                  {
                      continue;
                  }

                   data.putString(pb[i].getName(),pr.getParameter(pb[i].getName()));
                }

              // storing of customization data
              PortletRendererUtil.submitEditData(pr, data);
            }
            else
            {

              PrintWriter out = pr.getWriter();

              out.print("<TABLE BORDER=0 CELLSPACING=0>");
              out.println("<TR>");
              out.println("<TD>Title:</TD>");
              out.print("<TD><INPUT TYPE=\"text\" NAME=\"");
              out.print(TITLE_PARAMETER_NAME);
              out.print("\" SIZE=\"50\" VALUE=\"");
              out.print(XMLUtil.escapeXMLAttribute(data.getPortletTitle()));
              out.println("\" </TD>");
              out.println("</TR>");

              for (int i=0; i<pb.length; i++)
              {
                  if (!(pb[i] instanceof EditData))
                  {
                      continue;
                  }

                  out.println("<TR>");
                  out.print("<TD>");
                  out.print(XMLUtil.escapeXMLText(((EditData)pb[i]).getPrompt()));
                  out.println(": </TD>");
                  out.print("<TD><INPUT TYPE=\"text\" NAME=\"");
                  out.print(XMLUtil.escapeXMLAttribute(pb[i].getName()));
                  out.print("\"  SIZE=\"50\" VALUE=\"");
                  out.print(XMLUtil.escapeXMLAttribute( pb[i].getValue(wr)));
                  out.println("\" </TD>");
                  out.println("</TR>");
              }
              out.println("</TABLE>");
             }
      }
      catch (IOException ioe)
      {
            throw new PortletException(ioe);
      }
    }

    /**
     * This method is responsible for displaying the portlet in link mode.
     * @param pr a <code>PortletRenderRequest</code>
     * @exception PortletException if an error occurs while renderering
     */
    public void renderLink(PortletRenderRequest pr)
        throws PortletException
    {

        try
        {
            PrintWriter out = pr.getWriter();

            // Get the customized greeting and title
            NameValuePersonalizationObject data;
            data = (NameValuePersonalizationObject)PortletRendererUtil.getEditData(pr);

            // determine whether an HTML render or an mobile render
            if (this.getContentType().startsWith(PortletRenderer.TYPE_HTML))
            {
                // Writes container -- handles rendering the border if necessary
                out.print("<a href=\"");
                out.print(XMLUtil.escapeXMLAttribute(pr.getRenderContext().getPageURL()));
                out.print("\">");
                out.print(XMLUtil.escapeXMLText(data.getPortletTitle()));
                out.println("</a>");
            }
            else if (this.getContentType().startsWith(PortletRenderer.TYPE_MOBILE))
            {
                String title = data.getPortletShortTitle();

                // shortTitles may be empty -- if it is use the title
                if (title == null || title.length() == 0)
                    title = data.getPortletTitle();

                // now output the response
                out.print("<SimpleHref target=\"");
                out.print(XMLUtil.escapeXMLAttribute(pr.getRenderContext().getPageURL()));
                out.print("\">");
                out.print(XMLUtil.escapeXMLText(title));
                out.println("</SimpleHref>");
            }
            else
            {
                throw new PortletException(new SimpleStringBuffer(50)
                                          .append("Unsupported content-type: ")
                                          .append(this.getContentType()).toString());
            }
        }
        catch (java.io.IOException ioe)
        {
            throw new PortletException(ioe);
        }
    }

    /**
     * This method is responsible for displaying a screen that allows default (system)
     * customization of the portlet title and parameters to edit data.
     * @param pr a <code>PortletRenderRequest</code>
     * @exception PortletException if an error occurs while renderering
     */
    public void renderEditDefaults(PortletRenderRequest pr) throws PortletException {
        renderEdit(pr);
    }

    /**
     * Validates the internal state of this <code>WebServiceRenderer</code>
     * instance. Errors found are reported using the supplied {@link
     * oracle.portal.provider.v2.utils.ValidationError} instance.
     *
     * @param context The context of the calling instance.
     * @param errors The current list of errors.
     */
    public void validate(String context, ValidationError errors)            
    {                                                                       
        String ctx = ValidationError.newContext(context, "WebServiceRenderer",
                                                null);    
                            
        // call super type
        super.validate(ctx, errors);
                                                                            
        // Check the validity of the member variables                       
        if (mResponseXSLName == null)
        {                                                                   
            errors.addError(ctx, "responseXSL must not be null", null);
        }  
        
        if (mLiteralXML != null && mLiteralXML instanceof Validateable)
        {                                                                   
            ((Validateable)mLiteralXML).validate(ctx, errors);
        }  

        for (int i=0; mBindings != null && i < mBindings.length; i++)
        {
            if(mBindings[i] instanceof Validateable)
            {
                ((Validateable)mBindings[i]).validate(ctx, errors);
            }
        }
    }

    protected void addBinding(ParameterBinding binding)
    {
        // Make sure we are not in read-only 'locked down' state
        checkLocked("addBinding");
        mBindingList.add(binding);
    }

    private XSLStylesheet getResponseXSL(WebServiceRenderRequest wrr)
        throws PortletException, IOException, SAXException, XSLException
    {
        synchronized (mResponseXSLLock)
        {
            if (mResponseXSLFile == null)
            {
                String repositoryPath = wrr.getRepositoryPath();
                mResponseXSLFile = new File(repositoryPath, mResponseXSLName);
            }
            if (mResponseXSL == null || mResponseXSLFile.lastModified() != mResponseXSLLastModified)
            {
                if (mResponseXSLFile.exists())
                {
                    mResponseXSL = parseXSL(mResponseXSLFile);
                }
                // If the file does not yet exist, we generate it on the user's
                // behalf, based on the parameter information
                else
                {
                    mResponseXSL = deriveResponseXSL(mResponseXSLFile);
                }
                mResponseXSLLastModified = mResponseXSLFile.lastModified();
            }
            return mResponseXSL;
        }
    }

    // Derives a default response XSL file according to the parameter bindings
    private XSLStylesheet deriveResponseXSL(File xslFile)
        throws PortletException, IOException, SAXException, XSLException
    {
        XMLDocument xslDoc = new XMLDocument();
        Element docEl = xslDoc.createElementNS(XSL_NAMESPACE,
                                               XSL_PREFIX + ":stylesheet");
        xslDoc.appendChild(docEl);
        docEl.setAttribute("version", "1.0");
        docEl.setAttribute(XSL_NAMESPACE_ATTRIBUTE, XSL_NAMESPACE);

        Element instructEl = xslDoc.createElementNS(XSL_NAMESPACE,
                                                    XSL_PREFIX + ":output");
        docEl.appendChild(instructEl);
        instructEl.setAttribute("encoding", "UTF-8");
        instructEl.setAttribute("method", "html");
        instructEl.setAttribute("indent", "no");

        instructEl = xslDoc.createElementNS(XSL_NAMESPACE,
                                            XSL_PREFIX + ":strip-space");
        docEl.appendChild(instructEl);
        instructEl.setAttribute("elements", "*");

        instructEl = xslDoc.createElementNS(XSL_NAMESPACE,
                                            XSL_PREFIX + ":template");
        docEl.appendChild(instructEl);
        instructEl.setAttribute("match", "/doc");

        // Generate the form for any URL parameters, if necessary
        ParameterBinding[] bindings = getBindings();
        int i;
        Element tableEl = null, rowEl, subEl, subSubEl;
        Comment comment;
        Text text;
        for (i=0; i<bindings.length; i++)
        {
            if (bindings[i] instanceof URLParam)
            {
                if (tableEl == null)
                {
                    Element formEl = xslDoc.createElement("form");
                    instructEl.appendChild(formEl);
                    formEl.setAttribute("method", "get");
                    formEl.setAttribute("action", "{request/formActionLink}");

                    comment = xslDoc.createComment("Generate the hidden fields");
                    formEl.appendChild(comment);

                    subEl = xslDoc.createElementNS(XSL_NAMESPACE,
                                                   XSL_PREFIX + ":apply-templates");
                    formEl.appendChild(subEl);
                    subEl.setAttribute("select", "pageParams");


                    comment = xslDoc.createComment("Generate the input fields");
                    formEl.appendChild(comment);

                    tableEl = xslDoc.createElement("table");
                    formEl.appendChild(tableEl);

                    comment = xslDoc.createComment("Generate the submit button");
                    formEl.appendChild(comment);

                    subEl = xslDoc.createElement("input");
                    formEl.appendChild(subEl);
                    subEl.setAttribute("type", "submit");
                    subEl.setAttribute("name", "{request/parameterPrefix}submit");
                    subEl.setAttribute("value", "Submit");

                    // Generate the rule that expands out the hidden fields
                    comment = xslDoc.createComment("Expand out the hidden fields");
                    docEl.appendChild(comment);
                    Element instructEl2 = xslDoc.createElementNS(XSL_NAMESPACE,
                                                                 XSL_PREFIX + ":template");
                    docEl.appendChild(instructEl2);
                    instructEl2.setAttribute("match", "pageParams/param");

                    subEl = xslDoc.createElement("input");
                    instructEl2.appendChild(subEl);
                    subEl.setAttribute("type", "hidden");
                    subEl.setAttribute("name", "{name}");
                    subEl.setAttribute("value", "{value}");

                }
                // Generate a form field for each URL parameter
                rowEl = xslDoc.createElement("tr");
                tableEl.appendChild(rowEl);
                subEl = xslDoc.createElement("td");
                rowEl.appendChild(subEl);
                subEl.setAttribute("class", "PortletText1");

                subSubEl = xslDoc.createElementNS(XSL_NAMESPACE,
                                                  XSL_PREFIX + ":text");
                subEl.appendChild(subSubEl);
                text = xslDoc.createTextNode(new SimpleStringBuffer(100)
                    .append(((URLParam)bindings[i]).getPrompt())
                    .append(": ").toString());
                subSubEl.appendChild(text);

                subEl = xslDoc.createElement("td");
                rowEl.appendChild(subEl);
                subSubEl = xslDoc.createElement("input");
                subEl.appendChild(subSubEl);
                subSubEl.setAttribute("type", "text");
                subSubEl.setAttribute("name",
                                      new SimpleStringBuffer(100)
                                          .append("{request/parameterPrefix}")
                                          .append(bindings[i].getName()).toString());
                subSubEl.setAttribute("value",
                                      new SimpleStringBuffer(100)
                                          .append("{bindings/param[")
                                          .append(i+1)
                                          .append("]/value}").toString());
            } // End if instanceof URLParam
        } // end for each binding

        comment = xslDoc.createComment("Output the response if it is present");
        instructEl.appendChild(comment);

        // Make the rendering of the response conditional on whether it is
        // actually present or not
        Element ifEl = xslDoc.createElementNS(XSL_NAMESPACE,
                                              XSL_PREFIX + ":if");
        instructEl.appendChild(ifEl);
        ifEl.setAttribute("test", "response");


        subEl = xslDoc.createElement("font");
        ifEl.appendChild(subEl);
        subEl.setAttribute("class", "PortletText1");
        subSubEl = xslDoc.createElementNS(XSL_NAMESPACE,
                                       XSL_PREFIX + ":text");
        subEl.appendChild(subSubEl);
        text = xslDoc.createTextNode("Output from webservice");
        subSubEl.appendChild(text);

        subSubEl = xslDoc.createElement("hr");
        subEl.appendChild(subSubEl);
        subSubEl.setAttribute("width", "100%");

        subSubEl = xslDoc.createElementNS(XSL_NAMESPACE,
                                          XSL_PREFIX + ":value-of");
        subEl.appendChild(subSubEl);

        // For pre-formatted HTML output, don't escape the tag information
        if (!getEscapeOutput())
        {
            subSubEl.setAttribute("disable-output-escaping", "yes");
        }
        subSubEl.setAttribute("select", "response");

        // Write out the file, so that it is available for later
        OutputStream os = new FileOutputStream(xslFile);
        xslDoc.print(os);
        os.close();

        // Instantiate a parsed stylesheet from it
        return new XSLStylesheet(xslDoc, xslFile.getParentFile().toURL());
    }

    protected XSLStylesheet parseXSL(File xslFile)
        throws IOException, SAXException, XSLException
    {
        InputStream xslStream = new FileInputStream(xslFile);
        XMLDocument xslDoc = XMLUtil.parseDocument(xslStream, null);
        return new XSLStylesheet(xslDoc, xslFile.getParentFile().toURL());
    }

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


    /**
     * Base class for objects representing a parameter in the request to the
     * web service to that can be 'bound' to a variety of sources.
     */
    public static abstract class ParameterBinding
    {
        private String mName;

        public ParameterBinding()
        {
        }

        public ParameterBinding(String name)
        {
            setName(name);
        }

        /**
         * Sets the binding name, which identifies the particular item of data
         * it should be bound to.
         * @param name the binding name
         */
        public final void setName(String name)
        {
            mName = name;
        }

        /**
         * Gets the binding name, which identifies the particular item of data
         * it is bound to.
         * @return the binding name
         */
        public final String getName()
        {
            return mName;
        }

        /**
         * Evaluates the binding in the context of the given
         * <code>WebServiceRenderRequest</code>.
         * @return the value of the binding for the given
         * <code>WebServiceRenderRequest</code>.
         * @exception PortletException if an exception occurs while evaluating
         * the binding.
         */
        public abstract String getValue(WebServiceRenderRequest wrr)
            throws PortletException;

        /**
         * Determines whether this <code>ParameterBinding</code> is
         * mandatory. If a <code>WebServiceRenderer</code> has any mandatory
         * parameter bindings, its web service will not be invoked until all
         * its bindings have a value which is non-null and not equal to the
         * empty string. By default, all parameters are non-mandatory.
         */
        public boolean isMandatory()
        {
            return false;
        }
    }

    /**
     * Superclass for <code>ParameterBinding</code>s that can have a default
     * value.
     */
    public static abstract class DefaultableParameterBinding extends ParameterBinding
    {
        private String mDefault;
        private boolean mIsMandatory;

        public DefaultableParameterBinding()
        {
        }

        /**
         * Constructs a <code>DefaultableParameterBinding</code> using the
         * specified properties.
         * @param name the binding name
         * @param deflt default value to use when actual binding value is null
         * or the empty string
         * @param isMandatory is this a mandatory parameter?
         */
        public DefaultableParameterBinding(String name, String deflt, boolean isMandatory)
        {
            super(name);
            setDefault(deflt);
            setMandatory(isMandatory);
        }

        /**
         * Sets the default value for this binding, returned by {@link
         * #getValue} when the binding value is null or the empty string.
         * @param deflt the default value for this binding
         */
        public final void setDefault(String deflt)
        {
            if (deflt == null || deflt.length() == 0)
            {
                mDefault = null;
            }
            else
            {
                mDefault = deflt;
            }
        }

        /**
         * Evaluates this binding in the context of the given
         * <code>WebServiceRenderRequest</code>, taking into account any
         * defaults.
         * @return the value of this binding for the given
         * <code>WebServiceRenderRequest</code>.
         * @exception PortletException if an exception occurs while evaluating
         * the binding.
         */
        public final String getValue(WebServiceRenderRequest wrr)
            throws PortletException
        {
            String value = getActualValue(wrr);
            if (value == null || value.length() == 0)
            {
                return mDefault;
            }
            else
            {
                return value;
            }
        }

        /**
         * Evaluates this binding in the context of the given
         * <code>WebServiceRenderRequest</code>, <i>without</i> taking into
         * account any defaults. Called by {@link #getValue} to determine
         * whether the default should be returned. Subclasses should implement.
         * @return the value of this binding for the given
         * <code>WebServiceRenderRequest</code> or <code>null</code> if one
         * does not yet exist.
         * @exception PortletException if an exception occurs while evaluating
         * the binding.
         */
        public abstract String getActualValue(WebServiceRenderRequest wrr)
            throws PortletException;

        /**
         * Controls whether this binding is mandatory. If a
         * <code>WebServiceRenderer</code> has any mandatory parameter
         * bindings, its web service will not be invoked until all its bindings
         * have a value which is non-null and not equal to the empty string. By
         * default, all parameters are non-mandatory.
         * @param isMandatory <code>true</code> if this binding is mandatory,
         * <code>false</code> otherwise.
         */
        public final void setMandatory(boolean isMandatory)
        {
            mIsMandatory = isMandatory;
        }

        /**
         * Controls whether this binding is mandatory. If a
         * <code>WebServiceRenderer</code> has any mandatory parameter
         * bindings, its web service will not be invoked until all its bindings
         * have a value which is non-null and not equal to the empty string. By
         * default, all parameters are non-mandatory.
         * @param isMandatory String representation of boolean value. Should be
         * <code>"true"</code> if this binding is mandatory,
         * <code>"false"</code> otherwise.
         */
        public final void setMandatory(String isMandatory)
        {
            setMandatory(XMLUtil.getBooleanValue(isMandatory));
        }

        /**
         * Determines whether this <code>ParameterBinding</code> is
         * mandatory. If a <code>WebServiceRenderer</code> has any mandatory
         * parameter bindings, its web service will not be invoked until all
         * its bindings have a value which is non-null and not equal to the
         * empty string. By default, all parameters are non-mandatory.
         */
        public boolean isMandatory()
        {
            return mIsMandatory;
        }
    }

    /**
     * Class representing a parameter bound to a constant value
     */
    public static class ConstParam extends ParameterBinding
    {
        public ConstParam()
        {
        }

        public ConstParam(String name)
        {
            super(name);
        }

        /**
         * Evaluates the binding in the context of the given
         * <code>WebServiceRenderRequest</code>.
         * @return the value of the binding for the given
         * <code>WebServiceRenderRequest</code>. In this case, the value is
         * always the same as the name of this constant binding.
         */
        public String getValue(WebServiceRenderRequest wrr)
        {
            return getName();
        }
    }

    /**
     * Class representing a parameter bound to an item of customization data
     * from a {@link NameValuePersonalizationObject}.
     */
    public static class EditData extends PromptedBinding
    {
        public EditData()
        {
        }

        /**
         * Constructs an <code>EditData</code> using the specified properties.
         * @param name the binding name
         * @param deflt default value to use when actual binding value is null
         * or the empty string
         * @param isMandatory is this a mandatory parameter?
         * @param prompt is for user prompt message
         */
         public EditData(String name, String deflt, boolean isMandatory, String prompt)
         {
            super(name, deflt, isMandatory, prompt);
         }

        /**
         * Evaluates this binding in the context of the given
         * <code>WebServiceRenderRequest</code>, <i>without</i> taking into
         * account any defaults. In this case, the name of the binding is
         * assumed to correspond to the name of an item of persisted edit data
         * in the {@link NameValuePersonalizationObject} for the current
         * portlet instance, if one exists. Note that only values of type
         * <code>String</code> will be returned.
         * @return the value of this binding for the given
         * <code>WebServiceRenderRequest</code> or <code>null</code> if one
         * does not yet exist.
         * @exception PortletException if an exception occurs while evaluating
         * the binding.
         */
        public String getActualValue(WebServiceRenderRequest wrr)
            throws PortletException
        {
            NameValuePersonalizationObject data = wrr.getEditData();
            if (data != null)
            {
                try
                {
                    return data.getString(getName());
                }
                catch (ClassCastException cce)
                {
                    SimpleStringBuffer msg = new SimpleStringBuffer(100)
                        .append(getClass().getName())
                        .append(" unable to get value of non-String ")
                        .append("NameValuePersonalizationObject attribute \"")
                        .append(getName())
                        .append("\"");
                    LogManager.warning(msg.toString());
                }
            }
            // Just return non string-typed data as null
            return null;
        }
    }

    /**
     * Class representing a parameter bound to an item of session data
     * in the {@link oracle.portal.provider.v2.ProviderSession ProviderSession}.
     */
    public static class SessionAttrib extends DefaultableParameterBinding
    {
        public SessionAttrib()
        {
        }

        /**
         * Constructs a <code>SessionAttrib</code> using the specified
         * properties.
         * @param name the binding name
         * @param deflt default value to use when actual binding value is null
         * or the empty string
         * @param isMandatory is this a mandatory parameter?
         */
        public SessionAttrib(String name, String deflt, boolean isMandatory)
        {
            super(name, deflt, isMandatory);
        }

        /**
         * Evaluates this binding in the context of the given
         * <code>WebServiceRenderRequest</code>, <i>without</i> taking into
         * account any defaults. In this case, the name of the binding is
         * assumed to the name of an attribute of the {@link
         * oracle.portal.provider.v2.ProviderSession ProviderSession} if one
         * exists. Note that the value is converted to a String.
         * @return the value of this binding for the given
         * <code>WebServiceRenderRequest</code> or <code>null</code> if one
         * does not yet exist.
         */
        public String getActualValue(WebServiceRenderRequest wrr)
        {
            Object value = wrr.getSession().getAttribute(getName());
            return value == null ? null : value.toString();
        }
    }

        /**
         * Class representing a parameter bound to an item of data in the current
         * {@link WebServiceRenderRequest}.
         */
        public static class RequestParam extends DefaultableParameterBinding
        {

        public RequestParam()
        {
        }

        /**
         * Constructs a <code>RequestParam</code> using the specified
         * properties.
         * @param name the binding name
         * @param deflt default value to use when actual binding value is null
         * or the empty string
         * @param isMandatory is this a mandatory parameter?
         */
        public RequestParam(String name, String deflt, boolean isMandatory)
        {
            super(name, deflt, isMandatory);
        }

        /**
         * Evaluates this binding in the context of the given
         * <code>WebServiceRenderRequest</code>, <i>without</i> taking into
         * account any defaults. In this case, the result of passing the
         * binding name to {@link WebServiceRenderRequest#getRequestParameter}
         * is returned.
         * @return the value of this binding for the given
         * <code>WebServiceRenderRequest</code> or <code>null</code> if one
         * does not yet exist.
         * @see WebServiceRenderRequest#getRequestParameter
         */
        public String getActualValue(WebServiceRenderRequest wrr)
        {
            return wrr.getRequestParameter(getName());
        }

    }

    /**
     * Class representing a parameter bound to the value of a parmeter in the
     * query string of the request to the provider. The parameter may have been
     * posted by a form generated by the portlet itself, an external
     * parameter-passing portlet, or may be one of the parameters sent by
     * Portal. Note that HTML form fields are automatically generated for each
     * <code>URLParam</code> in the default XSL stylesheet.
     */
    public static class URLParam extends PromptedBinding
    {
        private String mPrompt;

        public URLParam()
        {
        }

        /**
         * Constructs a <code>URLParam</code> using the specified properties.
         * @param name the binding name
         * @param deflt default value to use when actual binding value is null
         * or the empty string
         * @param isMandatory is this a mandatory parameter?
         * @param prompt default prompt to use for item in generated HTML form.
         */
        public URLParam(String name, String deflt, boolean isMandatory, 
        String prompt)
        {
            super(name, deflt, isMandatory,prompt);
        }

        /**
         * Evaluates this binding in the context of the given
         * <code>WebServiceRenderRequest</code>, <i>without</i> taking into
         * account any defaults. In this case, the result of passing the
         * binding name to {@link WebServiceRenderRequest#getURLParameter}
         * is returned.
         * @return the value of this binding for the given
         * <code>WebServiceRenderRequest</code> or <code>null</code> if one
         * does not yet exist.
         * @see WebServiceRenderRequest#getURLParameter
         */
        public String getActualValue(WebServiceRenderRequest wrr)
        {
            return wrr.getURLParameter(getName());
        }

    }
    /**
     * Base class for objects representing a prompt in the request to the
     * web service to that is used show prompt in edit mode.
     */
     public static abstract class PromptedBinding extends DefaultableParameterBinding
     {

        private String mPrompt;

        public PromptedBinding()
        {
        }

        public PromptedBinding(String name, String deflt, boolean isMandatory, 
        String prompt)
        {
            super(name, deflt, isMandatory);
            setPrompt(prompt);
        }

        public abstract String getActualValue(WebServiceRenderRequest wrr)
            throws PortletException;

        /**
         * Sets the default prompt to use for the input field corresponding to
         * this item in the generated HTML form.
         * @param prompt the default prompt to use for the input field
         * corresponding to this item in the generated HTML form.
         */

        public final void setPrompt(String prompt)
        {
            mPrompt = prompt;
        }


        /**
         * Gets the default prompt to use for the input field corresponding to
         * this item in the generated HTML form.
         * @return the default prompt to use for the input field corresponding
         * to this item in the generated HTML form.
         */
        public final String getPrompt()
        {
            return mPrompt;
        }
    }

}
