Package org.apache.cocoon.forms.generation

Source Code of org.apache.cocoon.forms.generation.JXMacrosHelper$RootBufferingPipe

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.cocoon.forms.generation;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.apache.cocoon.ajax.BrowserUpdateTransformer;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.forms.FormsConstants;
import org.apache.cocoon.forms.FormsRuntimeException;
import org.apache.cocoon.forms.event.ValueChangedListenerEnabled;
import org.apache.cocoon.forms.formmodel.Form;
import org.apache.cocoon.forms.formmodel.Repeater;
import org.apache.cocoon.forms.formmodel.Widget;
import org.apache.cocoon.forms.formmodel.tree.Tree;
import org.apache.cocoon.forms.formmodel.tree.TreeWalker;
import org.apache.cocoon.forms.validation.ValidationError;
import org.apache.cocoon.i18n.I18nUtils;
import org.apache.cocoon.xml.AbstractXMLPipe;
import org.apache.cocoon.xml.AttributesImpl;
import org.apache.cocoon.xml.XMLConsumer;
import org.apache.cocoon.xml.XMLUtils;
import org.apache.commons.collections.ArrayStack;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

/**
* Helper class for the implementation of the CForms template language with JXTemplate macros.
*
* @version $Id: JXMacrosHelper.java 639553 2008-03-21 06:46:23Z antonio $
*/
public class JXMacrosHelper {

    private XMLConsumer cocoonConsumer;
    private Request request;
    private Locale locale;
    private ArrayStack widgetStack = new ArrayStack();
    private ArrayStack pipeStack = new ArrayStack();
    private ArrayStack labelStack = new ArrayStack();
    private Map classes; // lazily created
    private boolean ajaxRequest;
    private boolean ajaxTemplate;
    private Set updatedWidgets;
    private Set childUpdatedWidgets;

    /**
     * Builds and helper object, given the generator's consumer.
     *
     * @param consumer the generator's consumer
     * @return a helper object
     */
    public static JXMacrosHelper createHelper(XMLConsumer consumer, Request request, String locale) {
        return new JXMacrosHelper(consumer, request, locale);
    }

    public JXMacrosHelper(XMLConsumer consumer, Request request, String locale) {
        this.cocoonConsumer = consumer;
        this.request = request;
        this.locale = I18nUtils.parseLocale(locale);
        this.ajaxRequest = request.getParameter("cocoon-ajax") != null;
    }

    public Form getForm(Form form, String attributeName) {
        Form returnForm = form;
        // if there hasn't been passed a form object, try to find it in the request
        if (returnForm == null) {
            returnForm = (Form) this.request.getAttribute(attributeName);
        }
        if (returnForm != null) {
            return returnForm;
        }
        throw new FormsRuntimeException("The template cannot find a form object");
    }

    public void startForm(Form form, Map attributes) throws SAXException {

        this.updatedWidgets = form.getUpdatedWidgetIds();
        this.childUpdatedWidgets = form.getChildUpdatedWidgetIds();
        this.ajaxTemplate = "true".equals(attributes.get("ajax"));

        // build attributes
        AttributesImpl attrs = new AttributesImpl();
        // top-level widget-containers like forms might have their id set to ""
        // for those the @id should not be included.
        if (form.getId().length() != 0) {
            attrs.addCDATAAttribute("id", form.getRequestParameterName());
        }

        // Add the "state" attribute
        attrs.addCDATAAttribute("state", form.getCombinedState().getName());

        // Add locale attribute, useful for client-side code which needs to do stuff that
        // corresponds to the form locale (e.g. date pickers)
        attrs.addCDATAAttribute("locale", StringUtils.replace(this.locale.toString(), "_", "-"));

        // Add the "listening" attribute is the value has change listeners
        if (form instanceof ValueChangedListenerEnabled &&
            ((ValueChangedListenerEnabled)form).hasValueChangedListeners()) {
            attrs.addCDATAAttribute("listening", "true");
        }
        Iterator iter = attributes.entrySet().iterator();
        while(iter.hasNext()) {
            Map.Entry entry = (Map.Entry)iter.next();
            final String attrName = (String)entry.getKey();
            // check if the attribute has already been defined
            if (attrs.getValue(attrName) != null) {
                attrs.removeAttribute(attrName);
            }
            attrs.addCDATAAttribute(attrName, (String)entry.getValue());
        }

        // The child widgets of the form should update the label
        this.labelStack.push(BooleanUtils.toBooleanObject(this.ajaxTemplate && this.ajaxRequest));
        this.cocoonConsumer.startPrefixMapping(FormsConstants.INSTANCE_PREFIX, FormsConstants.INSTANCE_NS);
        this.cocoonConsumer.startElement(FormsConstants.INSTANCE_NS,
                                         "form-template",
                                         FormsConstants.INSTANCE_PREFIX_COLON + "form-template",
                                         attrs);
        // Push the form at the top of the stack
        this.widgetStack.push(Boolean.FALSE); // Not in an updated template
        this.widgetStack.push(form);
    }

    public void endForm() throws SAXException {
        this.widgetStack.pop();
        this.widgetStack.pop();
        this.labelStack.pop();
        this.cocoonConsumer.endElement(FormsConstants.INSTANCE_NS,
                                       "form-template",
                                       FormsConstants.INSTANCE_PREFIX_COLON + "form-template");
        this.cocoonConsumer.endPrefixMapping(FormsConstants.INSTANCE_PREFIX);

        this.ajaxTemplate = false;
        this.updatedWidgets = null;
    }

    private void startBuReplace(String id) throws SAXException {
        AttributesImpl attr = new AttributesImpl();
        attr.addCDATAAttribute("id", id);
        this.cocoonConsumer.startElement(BrowserUpdateTransformer.BU_NSURI, "replace", "bu:replace", attr);
    }

    private void endBuReplace(String id) throws SAXException {
        this.cocoonConsumer.endElement(BrowserUpdateTransformer.BU_NSURI, "replace", "bu:replace");
    }

    protected boolean pushWidget(String path, boolean unused) throws SAXException {
        Widget parent = peekWidget();
        if (path == null || path.length() == 0) {
            throw new FormsRuntimeException("Missing 'id' attribute on template instruction");
        }
        Widget widget = parent.lookupWidget(path);
        if (widget == null) {
            throw new FormsRuntimeException(parent + " has no child named '" + path + "'", parent.getLocation());
        }

        String id = widget.getFullName();
        // Is there an updated widget at a higher level in the template?
        boolean inUpdatedTemplate = ((Boolean)widgetStack.peek(1)).booleanValue();

        boolean display;

        if (ajaxRequest) {
            // An Ajax request. We will send partial updates
            if (inUpdatedTemplate) {
                // A parent widget has been updated: redisplay this one also
                display = true;
            } else if (this.updatedWidgets.contains(id)) {
                // Widget has been updated. We are now in an updated template section,
                // and widgets have to be surrounded with <bu:replace>
                inUpdatedTemplate = true;
                display = true;
            } else if (this.childUpdatedWidgets.contains(id)) {
                // A child need to be updated
                display = true;
            } else {
                // Doesn't need to be displayed
                display = false;
            }
        } else {
            // Not an ajax request
            if (ajaxTemplate) {
                // Surround all widgets with <bu:replace>, which the bu tranformer will use to check structure
                // consistency and add an id attribute to its child elements.
                inUpdatedTemplate = true;
            }
            // Display the widget
            display = true;
        }

        if (display) {
            // Widget needs to be displayed, but does it actually allows it?
            if (widget.getState().isDisplayingValues()) {
                if (inUpdatedTemplate) {
                    // Updated part of an Ajax template: surround with <bu:replace>
                    startBuReplace(id);
                }
            } else {
                if (ajaxTemplate) {
                    // Generate a placeholder, so that the page can be updated later
                    startBuReplace(id);
                    AttributesImpl attrs = new AttributesImpl();
                    attrs.addCDATAAttribute("id", widget.getRequestParameterName());
                    attrs.addCDATAAttribute("state", widget.getCombinedState().getName());
                    this.cocoonConsumer.startElement(FormsConstants.INSTANCE_NS, "placeholder", FormsConstants.INSTANCE_PREFIX_COLON + "placeholder", attrs);
                    // check if the parent wants to update the label of the child widget
                    if (ajaxRequest && ((Boolean)this.labelStack.peek()).booleanValue()) {
                        Map style = new HashMap(1);
                        style.put("update-label", "true");
                        generateStyling(style);
                    }
                    this.cocoonConsumer.endElement(FormsConstants.INSTANCE_NS, "placeholder", FormsConstants.INSTANCE_PREFIX_COLON + "placeholder");
                    endBuReplace(id);
                }
                // Production finished for this widget
                display = false;
            }
        }

        if (display) {
            this.widgetStack.push(BooleanUtils.toBooleanObject(inUpdatedTemplate));
            this.widgetStack.push(widget);
        }
        return display;
    }

    public Widget peekWidget() {
        return (Widget)this.widgetStack.peek();
    }

    public void popWidget() throws SAXException {
        Widget widget = (Widget)this.widgetStack.pop();
        boolean inUpdatedTemplate = ((Boolean)this.widgetStack.pop()).booleanValue();

        if (inUpdatedTemplate) {
            // Close the bu:replace
            endBuReplace(widget.getFullName());
        }
        if (widget instanceof Repeater) {
            this.labelStack.pop();
        }
    }

    public boolean pushWidget(String path) throws SAXException {
        return pushWidget(path, false);
    }

    public boolean pushContainer(String path) throws SAXException {
        return pushWidget(path, true);
    }

    /**
     * Enter a repeater
     *
     * @param path widget path
     * @param ajaxAware distinguishes between &lt;ft:repeater-widget&gt; and &lt;ft:repeater&gt;.
     * @return true if the repeater template is to be executed
     * @throws SAXException
     */
    public boolean pushRepeater(String path, boolean ajaxAware) throws SAXException {
        if (!ajaxAware && this.ajaxTemplate) {
            throw new IllegalStateException("Cannot use <ft:repeater-widget> in an Ajax form");
        }
        boolean result = pushWidget(path, true);
        if (result && !(peekWidget() instanceof Repeater)) {
            throw new IllegalArgumentException("Widget " + peekWidget() + " is not a repeater");
        }
        // the child widgets of the repeater never update the label
        this.labelStack.push(Boolean.FALSE);
        return result;
    }

    /**
     * Get a child widget of a given widget, throwing an exception if no such child exists.
     *
     * @param currentWidget
     * @param path
     */
    public Widget getWidget(Widget currentWidget, String path) {
        Widget result = currentWidget.lookupWidget(path);

        if (result != null) {
            return result;
        }
        throw new FormsRuntimeException(currentWidget + " has no child named '" + path + "'", currentWidget.getLocation());
    }

    private Repeater getRepeater(Widget currentWidget, String id) {
        Widget child = getWidget(currentWidget, id);
        if (child instanceof Repeater) {
            return (Repeater)child;
        }
        throw new FormsRuntimeException(child + " is not a repeater", child.getLocation());
    }

    /**
     * Generate a widget's SAX fragment, buffering the root element's <code>endElement()</code>
     * event so that the template can insert styling information in it.
     *
     * @param widget
     * @param arguments
     * @throws SAXException
     */
    public void generateWidget(Widget widget, Map arguments) throws SAXException {
        Map args = new HashMap(arguments);
        // check if the parent wants to update the label of the child widget
        if (((Boolean)this.labelStack.peek()).booleanValue()) {
            args.put("update-label", "true");
        }
        // Needs to be buffered
        RootBufferingPipe pipe = new RootBufferingPipe(this.cocoonConsumer, args);
        this.pipeStack.push(pipe);
        widget.generateSaxFragment(pipe, this.locale);
    }

    /**
     * Flush the root element name that has been stored in
     * {@link #generateWidget(Widget, Map)}.
     *
     * @throws SAXException
     */
    public void flushRootAndPop() throws SAXException {
        ((RootBufferingPipe) pipeStack.pop()).flushRoot();
        popWidget();
    }

    public void flushRoot() throws SAXException {
        ((RootBufferingPipe) pipeStack.pop()).flushRoot();
    }

    public void generateWidgetLabel(Widget widget, String id) throws SAXException {
        Widget widgetInst = getWidget(widget, id);
        if (widget instanceof Repeater) {
            widgetInst.generateLabel(this.cocoonConsumer);
        } else {
            AttributesImpl attrs = new AttributesImpl();
            attrs.addCDATAAttribute("id", widgetInst.getRequestParameterName());
            attrs.addCDATAAttribute("state", widgetInst.getCombinedState().getName());
            this.cocoonConsumer.startElement(FormsConstants.INSTANCE_NS, "field-label", FormsConstants.INSTANCE_PREFIX_COLON + "field-label", attrs);
            if (widgetInst.getCombinedState().isDisplayingValues()) {
                widgetInst.getDefinition().generateDisplayData(this.cocoonConsumer);
            }
            this.cocoonConsumer.endElement(FormsConstants.INSTANCE_NS, "field-label", FormsConstants.INSTANCE_PREFIX_COLON + "field-label");
        }
    }

    public void generateRepeaterWidgetLabel(Widget widget, String id, String widgetId) throws SAXException {
        // Widget labels are allowed either inside or outside of <ft:repeater>
        Repeater repeater = widget instanceof Repeater ? (Repeater)widget : getRepeater(widget, id);
        repeater.generateWidgetLabel(widgetId, this.cocoonConsumer);
    }

    public void generateRepeaterSize(Widget widget, String id) throws SAXException {
        getRepeater(widget, id).generateSize(this.cocoonConsumer);
    }

    private static final String VALIDATION_ERROR = "validation-error";

    public void generateValidationError(ValidationError error) throws SAXException {
        // Needs to be buffered
        RootBufferingPipe pipe = new RootBufferingPipe(this.cocoonConsumer);
        this.pipeStack.push(pipe);
        pipe.startElement(FormsConstants.INSTANCE_NS, VALIDATION_ERROR, FormsConstants.INSTANCE_PREFIX_COLON + VALIDATION_ERROR, XMLUtils.EMPTY_ATTRIBUTES);
        error.generateSaxFragment(pipe);
        pipe.endElement(FormsConstants.INSTANCE_NS, VALIDATION_ERROR, FormsConstants.INSTANCE_PREFIX_COLON + VALIDATION_ERROR);
    }

    public boolean isValidationError(Object object) {
        return object instanceof ValidationError;
    }

    public void defineClassBody(Form form, String id, Object body) {
        // TODO: check that class actually exists in the form
        if (this.classes == null) {
            this.classes = new HashMap();
        }
        // TODO: check if class doesn't already exist?
        this.classes.put(id, body);
    }

    public Object getClassBody(String id) {
        Object result = this.classes == null ? null : this.classes.get(id);

        if (result == null) {
            throw new FormsRuntimeException("No class '" + id + "' has been defined.");
        }
        return result;
    }

    public boolean isSelectedCase(Widget unionWidget, String caseValue) {
        String value = (String)unionWidget.getValue();
        return caseValue.equals(value != null ? value : "");
    }

    public TreeWalker createWalker() {
        return new TreeWalker((Tree)peekWidget());
    }

    public boolean isVisible(Widget widget) throws SAXException {
        boolean visible = widget.getCombinedState().isDisplayingValues();

        if (!visible) {
            // Generate a placeholder it not visible
            String id = widget.getRequestParameterName();
            AttributesImpl attrs = new AttributesImpl();
            attrs.addCDATAAttribute("id", id);
            this.cocoonConsumer.startElement(BrowserUpdateTransformer.BU_NSURI, "replace", "bu:replace", attrs);
            this.cocoonConsumer.startElement(FormsConstants.INSTANCE_NS, "placeholder", FormsConstants.INSTANCE_PREFIX_COLON + "placeholder", attrs);
            this.cocoonConsumer.endElement(FormsConstants.INSTANCE_NS, "placeholder", FormsConstants.INSTANCE_PREFIX_COLON + "placeholder");
            this.cocoonConsumer.endElement(BrowserUpdateTransformer.BU_NSURI, "replace", "bu:replace");
        }
        return visible;
    }

    public boolean isModified(Widget widget) {
        return this.updatedWidgets.contains(widget.getRequestParameterName());
    }

    public boolean generateStyling(Map attributes) throws SAXException {
        return generateStyling(this.cocoonConsumer, attributes);
    }

    /**
     * Generate a <code>&lt;fi:styling&gt;</code> element holding the attributes of a <code>ft:*</code>
     * element that are in the "fi:" namespace.
     *
     * @param attributes the template instruction attributes
     * @return true if a <code>&lt;fi:styling&gt;</code> was produced
     * @throws SAXException
     */
    public static boolean generateStyling(ContentHandler handler, Map attributes) throws SAXException {
        AttributesImpl attr = null;
        Iterator entries = attributes.entrySet().iterator();
        while (entries.hasNext()) {
            Map.Entry entry = (Map.Entry)entries.next();
            String key = (String)entry.getKey();

            // FIXME: JXTG only gives the local name of attributes, so we can't distinguish namespaces...
            if (!"id".equals(key) && !"widget-id".equals(key)) {
                if (attr == null)
                    attr = new AttributesImpl();
                attr.addCDATAAttribute(key, (String)entry.getValue());
            }
        }
        if (attr != null) {
            // There were some styling attributes
            handler.startElement(FormsConstants.INSTANCE_NS, "styling", FormsConstants.INSTANCE_PREFIX_COLON + "styling", attr);
            handler.endElement(FormsConstants.INSTANCE_NS, "styling", FormsConstants.INSTANCE_PREFIX_COLON + "styling");
            return true;
        } else {
            return false;
        }
    }

    /**
     * A SAX pipe that buffers the <code>endElement()</code> event of the root element.
     * This is needed by the generator version of the FormsTransformer (see jx-macros.xml).
     *
     * @version $Id: JXMacrosHelper.java 639553 2008-03-21 06:46:23Z antonio $
     */
    private static class RootBufferingPipe extends AbstractXMLPipe {
        private int depth = 0;
        private String rootUri;
        private String rootLoc;
        private String rootRaw;
        private Map arguments;
        private boolean forbidStyling = false;

        public RootBufferingPipe(XMLConsumer next) {
            this(next, Collections.EMPTY_MAP);
        }

        public RootBufferingPipe(XMLConsumer next, Map arguments) {
            this.setConsumer(next);
            this.arguments = arguments;
        }

        public void startElement(String uri, String loc, String raw, Attributes a)
        throws SAXException {
            super.startElement(uri, loc, raw, a);
            if (depth == 0) {
                // Root element: keep its description
                this.rootUri = uri;
                this.rootLoc = loc;
                this.rootRaw = raw;

                // And produce fi:styling from attributes
                this.forbidStyling = generateStyling(this.contentHandler, arguments);
            }

            if (depth == 1 && forbidStyling &&
                uri.equals(FormsConstants.INSTANCE_NS) && loc.equals("styling")) {
                throw new SAXException("Cannot use 'fi:*' attributes and <fi:styling> at the same time");
            }
            depth++;
        }

        public void endElement(String uri, String loc, String raw)
        throws SAXException {
            depth--;
            if (depth > 0) {
                // Propagate all but root element
                super.endElement(uri, loc, raw);
            }
        }

        public void flushRoot() throws SAXException {
            if (depth != 0) {
                throw new IllegalStateException("Depth is not zero");
            }
            super.endElement(this.rootUri, this.rootLoc, this.rootRaw);
        }
    }
}
TOP

Related Classes of org.apache.cocoon.forms.generation.JXMacrosHelper$RootBufferingPipe

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