com.icesoft.faces.webapp.parser.Parser.java Source code

Java tutorial

Introduction

Here is the source code for com.icesoft.faces.webapp.parser.Parser.java

Source

/*
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * "The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations under
 * the License.
 *
 * The Original Code is ICEfaces 1.5 open source software code, released
 * November 5, 2006. The Initial Developer of the Original Code is ICEsoft
 * Technologies Canada, Corp. Portions created by ICEsoft are Copyright (C)
 * 2004-2006 ICEsoft Technologies Canada, Corp. All Rights Reserved.
 *
 * Contributor(s): _____________________.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"
 * License), in which case the provisions of the LGPL License are
 * applicable instead of those above. If you wish to allow use of your
 * version of this file only under the terms of the LGPL License and not to
 * allow others to use your version of this file under the MPL, indicate
 * your decision by deleting the provisions above and replace them with
 * the notice and other provisions required by the LGPL License. If you do
 * not delete the provisions above, a recipient may use your version of
 * this file under either the MPL or the LGPL License."
 *
 */

package com.icesoft.faces.webapp.parser;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.webapp.UIComponentBodyTag;
import javax.faces.webapp.UIComponentTag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.Tag;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.ArrayList;

/**
 * This is the JSFX parser.  It digests a JSFX file into a tag processing tree,
 * and then executes the JSP tag processing lifecycle over that tree to produce
 * the JSF component tree.
 * <p/>
 * A parser is initialized with a list of tags that it processes.  This list of
 * tags comes from a serialized version of a TagToComponentMap, which is passed
 * in as an application initialization parameter.  The parser should be created
 * and initialized by the ViewHandler implementation for the application.  Once
 * it is initialized, the parser can parse a jsf jsp and initialize the
 * component tree for the page.  The parser relies on the apache Digester, and a
 * set of rules created in the ComponentRuleSet class.
 *
 * @author Steve Maryka
 */
public class Parser {
    private static final Log log = LogFactory.getLog(Parser.class);
    private JsfJspDigester digester;

    private int indentIndex;

    public Parser(InputStream fis) throws IOException {
        // Create digester and add rules;
        digester = new JsfJspDigester();
        digester.setNamespaceAware(true);
        digester.setValidating(false);
        digester.setUseContextClassLoader(false);
        digester.setClassLoader(this.getClass().getClassLoader());

        try {
            TagToComponentMap map = TagToComponentMap.loadFrom(fis);
            digester.addRuleSet(new ComponentRuleSet(map, ""));
        } catch (ClassNotFoundException e) {
            throw new IOException(e.getMessage());
        } finally {
            fis.close();
        }
    }

    /**
     * The main parsing logic.  Creates a Digester to parse page into a tag
     * processing tree, and then executes the JSP lifecycle across that tree.
     * The end result is a JSF component rooted with a UIViewRoot component.
     *
     * @param page    The Reader for the page.
     * @param context
     * @throws java.io.IOException      If stream IO fails.
     * @throws org.xml.sax.SAXException If digester encounters invalid XML.
     */
    public void parse(Reader page, FacesContext context) throws java.io.IOException, org.xml.sax.SAXException {
        // Need a mock pageContext
        StubPageContext pageContext = new StubPageContext(context);
        Set componentIds = new HashSet();

        //placeholder tag and wire
        XhtmlTag rootTag = new XhtmlTag();
        rootTag.setTagName("ICEtag");

        rootTag.setParent(null);
        TagWire rootWire = new TagWire();
        rootWire.setTag(rootTag);

        TagWire realViewWire = null;
        String viewTagClassName = null;
        synchronized (this) {
            digester.clear();
            digester.push(rootTag);
            digester.push(rootWire);
            digester.parse(page);
            realViewWire = digester.getViewWire();
            viewTagClassName = digester.getViewTagClassName();
        }

        try {

            // #2551 We have captured the real View Tag (from wherever it was in the tree)
            // now we check to see if it's the first child of this fake root, and
            // if not, make it so.
            // We do this by taking all top level siblings of the fake root, and
            // making them the first children of the real view root, and replacing
            // the original viewRoot with its children in the hiearchy.
            //  This duplicates what the previous code was doing, except the created
            // ViewTag has the attributes set

            Tag viewTag;
            if (null != realViewWire) {
                viewTag = realViewWire.getTag();
                transmogrifyHierarchy(realViewWire, rootWire);
                viewTag.setParent(null);
            } else {
                //fallback case for pages with no view tag
                if (null == viewTagClassName)
                    throw new IllegalStateException(
                            "ICEfaces parser unable to determine JSF implementation ViewTag class.");
                viewTag = (Tag) Class.forName(viewTagClassName).newInstance();
                rootWire.setTag(viewTag);
                realViewWire = rootWire;
                viewTag.setParent(null);
            }

            executeJspLifecycle(realViewWire, pageContext, context, componentIds);

            pageContext.removeAttribute("javax.faces.webapp.COMPONENT_TAG_STACK", PageContext.REQUEST_SCOPE);
            pageContext.removeAttribute("javax.faces.webapp.GLOBAL_ID_VIEW", PageContext.REQUEST_SCOPE);
        } catch (Exception e) {
            log.error("Failed to execute JSP lifecycle.", e);
            if (log.isDebugEnabled()) {

                log.debug("Dumping Tag Hierarchy");
                if (realViewWire != null) {
                    displayHierarchy(realViewWire);
                } else {
                    displayHierarchy(rootWire);
                }
            }
            throw new FacesException("Failed to execute JSP lifecycle.", e);
        }
    }

    /**
     * This member mimicks the JSP tag processing lifecyle across the tag
     * processing tree to produce the JSF component tree.
     *
     * @param wire         The tag's wire
     * @param pageContext  The page context
     * @param facesContext The faces context
     * @param componentIds
     * @throws JspException
     */
    public void executeJspLifecycle(TagWire wire, PageContext pageContext, FacesContext facesContext,
            Set componentIds) throws JspException {
        // Start of lifecycle;
        boolean processingViewTag = false;
        Tag tag = wire.getTag();
        tag.setPageContext(pageContext);
        // Start tag processing;
        tag.doStartTag();

        UIComponent myComponent = null; // reference will be used after doEndTag
        if (tag instanceof UIComponentTag) {
            UIComponentTag compTag = (UIComponentTag) tag;
            myComponent = compTag.getComponentInstance();

            if (myComponent != null) {
                if (myComponent instanceof UIViewRoot) {
                    myComponent.setId("_view");
                    processingViewTag = true;
                }

                String componentId = myComponent.getClientId(facesContext);
                if (componentIds.contains(componentId))
                    throw new IllegalStateException("Duplicate component ID : " + componentId);
                componentIds.add(componentId);
            }
        }

        // Now process tag children;
        Iterator children = wire.getChildren().iterator();
        while (children.hasNext()) {
            TagWire childWire = (TagWire) children.next();
            executeJspLifecycle(childWire, pageContext, facesContext, componentIds);
        }
        //Do tag body processing. This is not full-fledged body processing. It only calls the doAfterBody() member
        if (!processingViewTag && tag instanceof UIComponentBodyTag) {
            UIComponentBodyTag bodyTag = (UIComponentBodyTag) tag;
            if (bodyTag.getBodyContent() == null) {
                //body content of custom tag should not be null, so create one in here to satisfy the
                //checking in jsf 1.0 impl.
                JspWriter jspWriter = new JspWriterImpl(new PrintWriter(System.out));
                BodyContentImpl imp = new BodyContentImpl(jspWriter);
                bodyTag.setBodyContent(imp);
            }
            bodyTag.doAfterBody();
        }
        // Do end tag processing;
        tag.doEndTag();

        // ICE-3443: to save memory, trim the COMPONENT_IDS list capacity
        // to its actual size
        if (null != myComponent) {
            Map attributes = myComponent.getAttributes();
            ArrayList idsList = (ArrayList) attributes.get("javax.faces.webapp.COMPONENT_IDS");
            if (null != idsList) {
                idsList.trimToSize();
            }
        }
    }

    /**
     * This method will redo the topology of the parsed nodes. Previously, the
     * viewTag was not parsed, and all attributes were lost. Now, the tag is parsed
     * but it is no longer guaranteed to be the first child of the fake root node.
     * This method fixes the hierarchy to make it match the previous result
     * The changes required are to move the viewTag to be
     * the first and only child of the fake root Tag, and to replace the viewTag
     * in the hierarchy with its children, preserving order
     *
     * @param viewWire TagWire pointing to real parsed viewTag
     * @param rootWire tagWire pointing to fake Root Tag.
     */
    private void transmogrifyHierarchy(TagWire viewWire, TagWire rootWire) {

        Tag viewTag = viewWire.getTag();
        List rootChildren = rootWire.getChildren();

        // It should be impossible for the fake root to have no rootChildren. ?
        if ((rootChildren.size() == 0) || (!((TagWire) rootChildren.get(0)).getTag().equals(viewTag))) {

            // The tag that originally had the viewTag as a child.
            TagWire viewTagParent = findViewTagInHierarchy(viewWire, rootWire);
            if (log.isDebugEnabled()) {
                log.debug("Replacing ViewTag in midst of hierarchy");
            }
            if (viewTagParent != null) {

                // Put all the viewTag children into where the viewTag was, respecting order
                viewTagParent.replaceTagWireWithChildren(viewWire);

                // reparent all viewTag children to new parent
                List viewTagParentChildren = viewTagParent.getChildren();
                for (int idx = 0; idx < viewTagParentChildren.size(); idx++) {
                    TagWire r = (TagWire) viewTagParentChildren.get(idx);
                    r.getTag().setParent(viewTagParent.getTag());
                }

                // now replace all children of viewTag with those of the fake root
                //do not add viewWire as its own child
                rootChildren.remove(viewWire);
                viewWire.getChildren().clear();
                viewWire.getChildren().addAll(rootChildren);

                // reparent
                for (int idx = 0; idx < rootChildren.size(); idx++) {
                    TagWire r = (TagWire) rootChildren.get(idx);
                    r.getTag().setParent(viewTag);
                }
            }
        } else {
            if (log.isDebugEnabled()) {
                log.debug("ViewTag is already at top of hierarchy");
            }
        }
    }

    /**
     * Recursive method to locate the original parent of a given tag
     *
     * @param toFind The tagWire to find
     * @param parent The TagWire to look in
     * @return The TagWire parent containing the TagWire
     */
    private TagWire findViewTagInHierarchy(TagWire toFind, TagWire parent) {

        List children = parent.getChildren();
        if (children.contains(toFind)) {
            return parent;
        }
        // recurse through children of 'parent'
        TagWire child;
        for (int idx = 0; idx < children.size(); idx++) {
            child = findViewTagInHierarchy(toFind, (TagWire) children.get(idx));
            if (child != null) {
                return child;
            }
        }
        return null;
    }

    /**
     * In event of parse exception, dump out the contents of the tagwire
     * tree. This is not very performant, but it doesn't need to be. Try to
     * give the author some idea as to why the exception. #ICE-2716
     *
     * @param wire The TagWire at the Root
     */
    private void displayHierarchy(TagWire wire) {

        char[] pad = new char[indentIndex];
        Arrays.fill(pad, ' ');
        log.debug(new String(pad) + "Tag: " + wire.getTag());

        indentIndex += 2;
        List children = wire.getChildren();
        for (int idx = 0; idx < children.size(); idx++) {
            displayHierarchy((TagWire) children.get(idx));
        }
        indentIndex -= 2;
    }
}