/* * 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.woody.formmodel; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.StringTokenizer; import org.apache.cocoon.woody.Constants; import org.apache.cocoon.woody.FormContext; import org.apache.cocoon.woody.event.FormHandler; import org.apache.cocoon.woody.event.ProcessingPhase; import org.apache.cocoon.woody.event.ProcessingPhaseEvent; import org.apache.cocoon.woody.event.ProcessingPhaseListener; import org.apache.cocoon.woody.event.WidgetEvent; import org.apache.cocoon.woody.event.WidgetEventMulticaster; import org.apache.cocoon.xml.AttributesImpl; import org.apache.commons.collections.list.CursorableLinkedList; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; /** * A widget that serves as a container for other widgets, the top-level widget in * a form description file. * * @author Bruno Dumon * @author <a href="http://www.apache.org/~sylvain/">Sylvain Wallez</a> * @version CVS $Id$ */ public class Form extends AbstractContainerWidget { private Boolean endProcessing; private Locale locale = Locale.getDefault(); private CursorableLinkedList events; // private FormDefinition definition; private FormHandler formHandler; private Widget submitWidget; private ProcessingPhase phase = ProcessingPhase.LOAD_MODEL; private boolean isValid = false; private ProcessingPhaseListener listener; private Map attributes; public Form(FormDefinition definition) { super(definition); setLocation(definition.getLocation()); } /** * Events produced by child widgets should not be fired immediately, but queued in order to ensure * an overall consistency of the widget tree before being handled. * * @param event the event to queue */ public void addWidgetEvent(WidgetEvent event) { if (this.events == null) { this.events = new CursorableLinkedList(); } // FIXME: limit the number of events to detect recursive event loops ? this.events.add(event); } /** * Fire the widget events that have been queued. Note that event handling can fire new * events. */ private void fireWidgetEvents() { if (this.events != null) { CursorableLinkedList.Cursor cursor = this.events.cursor(); while(cursor.hasNext()) { WidgetEvent event = (WidgetEvent)cursor.next(); event.getSourceWidget().broadcastEvent(event); if (formHandler != null) formHandler.handleEvent(event); } cursor.close(); this.events.clear(); } } /** * Get the locale to be used to process this form. * * @return the form's locale. */ public Locale getLocale() { return this.locale; } /** * Get the widget that triggered the current processing. Note that it can be any widget, and * not necessarily an action or a submit. * * @return the widget that submitted this form. */ public Widget getSubmitWidget() { return this.submitWidget; } /** * Set the widget that triggered the current form processing. * * @param widget the widget */ public void setSubmitWidget(Widget widget) { if (this.submitWidget != null && this.submitWidget != widget) { throw new IllegalStateException("SubmitWidget can only be set once."); } if (!(widget instanceof Action)) { endProcessing(true); } this.submitWidget = widget; } public void setFormHandler(FormHandler formHandler) { this.formHandler = formHandler; } // TODO: going through the form for load and save ensures state consistency. To we add this or // keep the binding strictly separate ? // public void load(Object data, Binding binding) { // if (this.phase != ProcessingPhase.LOAD_MODEL) { // throw new IllegalStateException("Cannot load form in phase " + this.phase); // } // binding.loadFormFromModel(this, data); // } // // public void save(Object data, Binding binding) throws BindingException { // if (this.phase != ProcessingPhase.VALIDATE) { // throw new IllegalStateException("Cannot save model in phase " + this.phase); // } // // if (!isValid()) { // throw new IllegalStateException("Cannot save an invalid form."); // } // this.phase = ProcessingPhase.SAVE_MODEL; // binding.saveFormToModel(this, data); // } public void addProcessingPhaseListener(ProcessingPhaseListener listener) { this.listener = WidgetEventMulticaster.add(this.listener, listener); } public void removeProcessingPhaseListener(ProcessingPhaseListener listener) { this.listener = WidgetEventMulticaster.remove(this.listener, listener); } /** * Processes a form submit. If the form is finished, i.e. the form should not be redisplayed to the user, * then this method returns true, otherwise it returns false. To know if the form was sucessfully * validated, use the {@link #isValid()} method. * <p> * Form processing consists in multiple steps: * <ul> * <li>all widgets read their value from the request (i.e. * {@link #readFromRequest(FormContext)} is called recursively on * the whole widget tree) * <li>if there is an action event, call the FormHandler * <li>perform validation. * </ul> * This processing can be interrupted by the widgets (or their event listeners) by calling * {@link #endProcessing(boolean)}. */ public boolean process(FormContext formContext) { // Fire the binding phase events fireWidgetEvents(); // setup processing this.submitWidget = null; this.locale = formContext.getLocale(); this.endProcessing = null; this.isValid = false; // Notify the end of the current phase if (this.listener != null) { this.listener.phaseEnded(new ProcessingPhaseEvent(this, this.phase)); } this.phase = ProcessingPhase.READ_FROM_REQUEST; // Find the submit widget, if not an action this.submitWidget = null; String submitId = formContext.getRequest().getParameter("woody_submit_id"); if (submitId != null && submitId.length() > 0) { StringTokenizer stok = new StringTokenizer(submitId, "."); Widget submit = this; while (stok.hasMoreTokens()) { submit = submit.getWidget(stok.nextToken()); if (submit == null) { throw new IllegalArgumentException("Invalid submit id (no such widget): " + submitId); } } setSubmitWidget(submit); } doReadFromRequest(formContext); fireWidgetEvents(); // Notify the end of the current phase if (this.listener != null) { this.listener.phaseEnded(new ProcessingPhaseEvent(this, this.phase)); } if (this.endProcessing != null) { return this.endProcessing.booleanValue(); } // Validate the form this.phase = ProcessingPhase.VALIDATE; this.isValid = doValidate(formContext); if (this.endProcessing != null) { return this.endProcessing.booleanValue(); } // Notify the end of the current phase if (this.listener != null) { this.listener.phaseEnded(new ProcessingPhaseEvent(this, this.phase)); } if (this.endProcessing != null) { // De-validate the form if one of the listeners asked to end the processing // This allows for additional application-level validation. this.isValid = false; return this.endProcessing.booleanValue(); } return this.isValid; } /** * End the current form processing after the current phase. * * @param redisplayForm indicates if the form should be redisplayed to the user. */ public void endProcessing(boolean redisplayForm) { this.endProcessing = new Boolean(!redisplayForm); } /** * Was form validation successful ? * * @return <code>true</code> if the form was successfully validated. */ public boolean isValid() { return this.isValid; } public void readFromRequest(FormContext formContext) { throw new UnsupportedOperationException("Please use Form.process()"); } private void doReadFromRequest(FormContext formContext) { // let all individual widgets read their value from the request object super.readFromRequest(formContext); } public boolean validate(FormContext formContext) { throw new UnsupportedOperationException("Please use Form.process()"); } public boolean doValidate(FormContext formContext) { return super.validate(formContext); } public Object getAttribute(String name) { return this.attributes == null ? null : this.attributes.get(name); } public void setAttribute(String name, Object value) { if (this.attributes == null) { this.attributes = new HashMap(); } this.attributes.put(name, value); } public void removeAttribute(String name) { if (this.attributes != null) { this.attributes.remove(name); } } private static final String FORM_EL = "form"; private static final String CHILDREN_EL = "children"; public void generateSaxFragment(ContentHandler contentHandler, Locale locale) throws SAXException { AttributesImpl formAttrs = new AttributesImpl(); formAttrs.addCDATAAttribute("id", definition.getId()); contentHandler.startElement(Constants.WI_NS, FORM_EL, Constants.WI_PREFIX_COLON + FORM_EL, Constants.EMPTY_ATTRS); definition.generateLabel(contentHandler); contentHandler.startElement(Constants.WI_NS, CHILDREN_EL, Constants.WI_PREFIX_COLON + CHILDREN_EL, Constants.EMPTY_ATTRS); Iterator widgetIt = widgets.iterator(); while (widgetIt.hasNext()) { Widget widget = (Widget)widgetIt.next(); widget.generateSaxFragment(contentHandler, locale); } contentHandler.endElement(Constants.WI_NS, CHILDREN_EL, Constants.WI_PREFIX_COLON + CHILDREN_EL); contentHandler.endElement(Constants.WI_NS, FORM_EL, Constants.WI_PREFIX_COLON + FORM_EL); } public void generateLabel(ContentHandler contentHandler) throws SAXException { definition.generateLabel(contentHandler); } }