FileDocCategorySizeDatePackage
XMLViewHandler.javaAPI DocExample14523Tue Jun 08 11:26:42 BST 2004com.mycompany.jsf.pl

XMLViewHandler.java

package com.mycompany.jsf.pl;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Iterator;
import java.util.Stack;

import javax.faces.application.Application;
import javax.faces.application.ViewHandler;
import javax.faces.component.ActionSource;
import javax.faces.component.UIComponent;
import javax.faces.component.UISelectMany;
import javax.faces.component.UISelectOne;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.el.PropertyNotFoundException;
import javax.faces.el.ValueBinding;
import javax.faces.el.MethodBinding;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;

/**
 * This class is a JSF ViewHandler that works with views defined by
 * a combination of a view specification file and a template file.
 *
 * @author Hans Bergsten, Gefion Software <hans@gefionsoftware.com>
 * @version 1.0
 */
public class XMLViewHandler extends ClassViewHandler {

    /**
     * Creates an instance and saves a reference to the
     * previously registered ViewHandler.
     */
    public XMLViewHandler(ViewHandler origViewHandler) {
        super(origViewHandler);
    }

    /**
     * Returns the UIViewRoot for the specified view, by parsing
     * the view specification file and processing the elements in
     * the specification with an instance of the ViewSpecHandler
     * class.
     */
    protected UIViewRoot createViewRoot(FacesContext context, String viewId) {

        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser saxParser = null;
        try {
            saxParser = factory.newSAXParser();
        }
        catch (SAXException e) {
            throw new IllegalArgumentException(e.getMessage());
        }
        catch (ParserConfigurationException e) {
            throw new IllegalArgumentException(e.getMessage());
        }

        UIViewRoot viewRoot = new UIViewRoot();
        viewRoot.setViewId(viewId);
        ExternalContext ec = context.getExternalContext();
        InputStream viewSpecIS = null;

        DefaultHandler handler = 
            new ViewSpecHandler(context.getApplication(), viewRoot);
        try {
            viewSpecIS = context.getExternalContext().
                getResourceAsStream(viewId + ".view");
            saxParser.parse(viewSpecIS, handler);

        } catch (SAXParseException e) {
            String msg = "View spec parsing error: " + e.getMessage() +
                " at line=" + e.getLineNumber() +
                " col=" + e.getColumnNumber();
            throw new IllegalArgumentException(msg);
        } catch (Exception e) {
            String msg = "View spec parsing error: " + e.getMessage();
            throw new IllegalArgumentException(msg);
        } finally {
            try {
                if (viewSpecIS != null) {
                    viewSpecIS.close();
                }
            } catch (IOException e) {}
        }
        return viewRoot;
    }

    /**
     * Renders the view represented by the provided root component by
     * parsing the template file for the view and processing the elements in
     * the template with an instance of the TemplateHandler class.
     */
    protected void renderResponse(FacesContext context, UIComponent component)
        throws IOException {

        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser saxParser = null;
        try {
            saxParser = factory.newSAXParser();
        }
        catch (SAXException e) {
            throw new IllegalArgumentException(e.getMessage());
        }
        catch (ParserConfigurationException e) {
            throw new IllegalArgumentException(e.getMessage());
        }

        UIViewRoot root = (UIViewRoot) component;
        String viewId = root.getViewId();
        ExternalContext ec = context.getExternalContext();
        InputStream templIS = null;

        DefaultHandler handler = new TemplateHandler(context, root);
        try {
            templIS = context.getExternalContext().
                getResourceAsStream(viewId + ".html");
            saxParser.parse(templIS, handler);

        } catch (SAXParseException e) {
            String msg = "Template parsing error: " + e.getMessage() +
                " at line=" + e.getLineNumber() +
                " col=" + e.getColumnNumber();
            throw new IllegalArgumentException(msg);
        } catch (Exception e) {
            String msg = "Template parsing error: " + e.getMessage();
            throw new RuntimeException(msg, e);
        } finally {
            try {
                if (templIS != null) {
                    templIS.close();
                }
            } catch (IOException e) {}
        }
    }

    /**
     * This class is a SAX DefaultHandler for processing the 
     * view specification file.
     */
    private static class ViewSpecHandler extends DefaultHandler {
        private Stack stack;
        private Application application;

	/**
	 * Creates an instance and pushes the root component onto a stack.
	 */
        public ViewSpecHandler(Application application, UIComponent root) {
            this.application = application;
            stack = new Stack();
            stack.push(root);
        }

	/**
	 * If the element is a "component" element, calls createComponent()
	 * to create the corresponding component, adds it as a child of
	 * the component at the top of the stack, and pushes the new
	 * component onto the stack.
	 */
        public void startElement(String namespaceURI, String lName, 
            String qName, Attributes attrs) throws SAXException {

            if ("component".equals(qName)) {
                UIComponent component = createComponent(application, attrs);
		((UIComponent) stack.peek()).getChildren().add(component);
		stack.push(component);
            }
        }

	/**
	 * Pops the top component off the stach.
	 */
        public void endElement(String namespaceURI, String lName,
            String qName) throws SAXException {

            if ("component".equals(qName)) {
                stack.pop();
            }
        }

	/**
	 * Creates and returns a component of the type specified by
	 * the "type" attribute, configured based on all the other
	 * attributes.
	 */
        private UIComponent createComponent(Application application,
            Attributes attrs) {
            if (attrs == null || attrs.getValue("type") == null) {
                String msg = 
                    "'component' element without 'type' attribute found";
                throw new IllegalArgumentException(msg);
            }

            String type = attrs.getValue("type");
            UIComponent component = application.createComponent(type);
            if (component == null) {
                String msg = "No component class registered for 'type' " +
                    type;
                throw new IllegalArgumentException(msg);
            }

            for (int i = 0; i < attrs.getLength(); i++) {
                String name = attrs.getLocalName(i);
                if ("".equals(name)) {
                    name = attrs.getQName(i);
                }
                if ("type".equals(name)) {
                    continue;
                }
                String value = attrs.getValue(i);
                if (value.startsWith("#{")) {
		    if ("action".equals(name)) {
			MethodBinding mb = 
			    application.createMethodBinding(value, null);
			((ActionSource) component).setAction(mb);
		    }
		    else {
			ValueBinding vb = 
                            application.createValueBinding(value);
			component.setValueBinding(name, vb);
		    }
                }
                else {
                    component.getAttributes().put(name, value);
                }
            }
            return component;
        }
    }

    /**
     * This class is a SAX DefaultHandler for processing the 
     * template file.
     */
    private static class TemplateHandler extends DefaultHandler {
        private StringBuffer textBuff = null;
        private FacesContext context;
        private ResponseWriter out;
        private UIViewRoot root;
        private Stack stack;
        private Object suppressTemplate;

	/**
	 * Creates an instance and pushes the root component onto a stack.
	 */
        public TemplateHandler(FacesContext context, UIViewRoot root) {

            this.context = context;
            this.root = root;
            out = context.getResponseWriter();
            stack = new Stack();
            stack.push(root);
        }

	/**
	 * Writes buffered text, if any, and tries to locate a component
	 * with the ID defined by the "id" attribute by calling 
	 * the findComponent() method on the root component. This means
	 * that the "id" attribute value must contain all naming
	 * container parent IDs. If there's a matching component in
	 * the view, it's configured based on the template element
	 * attributes, and its encodeBegin() method is invoked, and
	 * it's pushed onto the stack. Template text suppression is
	 * enabled if the suppressTemplate() method returns "true".
	 * If no component matches the "id" attribute, the element
	 * name is pushed onto the stack and the start tag is written
	 * as-is.
	 */
        public void startElement(String namespaceURI, String lName, 
            String qName, Attributes attrs) throws SAXException {

            handleTextIfNeeded();

            String id = attrs.getValue("id");
            if (id != null && root.findComponent(id) != null) {
                UIComponent comp = findAndConfigure(id, attrs);
                stack.push(comp);
                try {
                    comp.encodeBegin(context);
                }
                catch (IOException ioe) {}
                if (suppressTemplate(comp)) {
		    suppressTemplate = comp;
		}
            }
            else {
                stack.push(qName);
                if (suppressTemplate == null) {
                    try {
                        out.startElement(qName, null);
                        for (int i = 0; i < attrs.getLength(); i++) {
                            out.writeAttribute(attrs.getQName(i), 
                                 attrs.getValue(i), null);
                        }
                    }
                    catch (IOException ioe) {}
                }
            }
        }

	/**
	 * Writes buffered text, if any. If the top object on the stack
	 * is a component, calls its encodeChildren() (if it's
	 * a type that renders its children) and its encodeEnd() methods,
	 * and disables suppressing template text if it was enabled
	 * by this component. If the top object is an element name and
	 * template text isn't suppressed, the end tag is written as-is.
	 */
        public void endElement(String namespaceURI, String lName,
            String qName) throws SAXException {

            handleTextIfNeeded();

            Object o = stack.pop();
            if (o instanceof String) {
		if (suppressTemplate == null) {
		    try {
			out.endElement(qName);
			out.writeText("\n", null);
		    }
		    catch (IOException ioe) {}
		}
            }
            else {
                UIComponent comp = (UIComponent) o;
                try {
                    if (comp.getRendersChildren()) {
                        comp.encodeChildren(context);
                    }
                    comp.encodeEnd(context);
                    out.writeText("\n", null);
                }
                catch (IOException ioe) {}
                if (suppressTemplate == comp) {
                    suppressTemplate = null;
                }
            }
        }

	/**
	 * Returns the component matching the ID, configured based on
	 * the attributes.
	 */
        private UIComponent findAndConfigure(String id, Attributes attrs) {
            UIComponent comp = root.findComponent(id);
            for (int i = 0; i < attrs.getLength(); i++) {
                // Don't overwrite "id"
                if ("id".equals(attrs.getQName(i))) {
                    continue;
                }
                comp.getAttributes().put(attrs.getQName(i), attrs.getValue(i));
            }
            return comp;
        }

         private boolean suppressTemplate(UIComponent comp) {
             return comp.getRendersChildren() || 
                 comp instanceof UISelectMany || comp instanceof UISelectOne;
        } 

        /**
         * Buffer the characters until an end or start element is encountered.
         */
        public void characters(char buf[], int offset, int len)
            throws SAXException {

            if (suppressTemplate != null) {
                return;
            }
            
            if (textBuff == null) {
                textBuff = new StringBuffer(len * 2);
            }
            textBuff.append(buf, offset, len);
        }

        /**
         * Creates a String containing the buffered characters and another
         * String representing the element structure the characters belong to
         * and lets the subclass handle it. Note that this method must be
         * be called in startElement() and endElement() before updating
         * the element list, since the buffered characters belong to the
         * previous element.
         */
        private void handleTextIfNeeded() {
            if (textBuff != null) {
                String value = textBuff.toString().trim();
                textBuff = null;
                if (value.length() == 0) {
                    return;
                }
                try {
                    out.writeText(value, null);
                }
                catch (IOException ioe) {}
            }
        }
    }
}