| /* |
| * 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); |
| } |
| } |