001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.woody.formmodel;
018:
019: import java.util.HashMap;
020: import java.util.Iterator;
021: import java.util.Locale;
022: import java.util.Map;
023: import java.util.StringTokenizer;
024:
025: import org.apache.cocoon.woody.Constants;
026: import org.apache.cocoon.woody.FormContext;
027: import org.apache.cocoon.woody.event.FormHandler;
028: import org.apache.cocoon.woody.event.ProcessingPhase;
029: import org.apache.cocoon.woody.event.ProcessingPhaseEvent;
030: import org.apache.cocoon.woody.event.ProcessingPhaseListener;
031: import org.apache.cocoon.woody.event.WidgetEvent;
032: import org.apache.cocoon.woody.event.WidgetEventMulticaster;
033: import org.apache.cocoon.xml.AttributesImpl;
034: import org.apache.commons.collections.list.CursorableLinkedList;
035: import org.xml.sax.ContentHandler;
036: import org.xml.sax.SAXException;
037:
038: /**
039: * A widget that serves as a container for other widgets, the top-level widget in
040: * a form description file.
041: *
042: * @author Bruno Dumon
043: * @author <a href="http://www.apache.org/~sylvain/">Sylvain Wallez</a>
044: * @version CVS $Id: Form.java 433543 2006-08-22 06:22:54Z crossley $
045: */
046: public class Form extends AbstractContainerWidget {
047:
048: private Boolean endProcessing;
049: private Locale locale = Locale.getDefault();
050: private CursorableLinkedList events;
051: // private FormDefinition definition;
052: private FormHandler formHandler;
053: private Widget submitWidget;
054: private ProcessingPhase phase = ProcessingPhase.LOAD_MODEL;
055: private boolean isValid = false;
056: private ProcessingPhaseListener listener;
057: private Map attributes;
058:
059: public Form(FormDefinition definition) {
060: super (definition);
061: setLocation(definition.getLocation());
062: }
063:
064: /**
065: * Events produced by child widgets should not be fired immediately, but queued in order to ensure
066: * an overall consistency of the widget tree before being handled.
067: *
068: * @param event the event to queue
069: */
070: public void addWidgetEvent(WidgetEvent event) {
071:
072: if (this .events == null) {
073: this .events = new CursorableLinkedList();
074: }
075:
076: // FIXME: limit the number of events to detect recursive event loops ?
077: this .events.add(event);
078: }
079:
080: /**
081: * Fire the widget events that have been queued. Note that event handling can fire new
082: * events.
083: */
084: private void fireWidgetEvents() {
085: if (this .events != null) {
086: CursorableLinkedList.Cursor cursor = this .events.cursor();
087: while (cursor.hasNext()) {
088: WidgetEvent event = (WidgetEvent) cursor.next();
089: event.getSourceWidget().broadcastEvent(event);
090: if (formHandler != null)
091: formHandler.handleEvent(event);
092: }
093: cursor.close();
094:
095: this .events.clear();
096: }
097: }
098:
099: /**
100: * Get the locale to be used to process this form.
101: *
102: * @return the form's locale.
103: */
104: public Locale getLocale() {
105: return this .locale;
106: }
107:
108: /**
109: * Get the widget that triggered the current processing. Note that it can be any widget, and
110: * not necessarily an action or a submit.
111: *
112: * @return the widget that submitted this form.
113: */
114: public Widget getSubmitWidget() {
115: return this .submitWidget;
116: }
117:
118: /**
119: * Set the widget that triggered the current form processing.
120: *
121: * @param widget the widget
122: */
123: public void setSubmitWidget(Widget widget) {
124: if (this .submitWidget != null && this .submitWidget != widget) {
125: throw new IllegalStateException(
126: "SubmitWidget can only be set once.");
127: }
128: if (!(widget instanceof Action)) {
129: endProcessing(true);
130: }
131: this .submitWidget = widget;
132: }
133:
134: public void setFormHandler(FormHandler formHandler) {
135: this .formHandler = formHandler;
136: }
137:
138: // TODO: going through the form for load and save ensures state consistency. To we add this or
139: // keep the binding strictly separate ?
140: // public void load(Object data, Binding binding) {
141: // if (this.phase != ProcessingPhase.LOAD_MODEL) {
142: // throw new IllegalStateException("Cannot load form in phase " + this.phase);
143: // }
144: // binding.loadFormFromModel(this, data);
145: // }
146: //
147: // public void save(Object data, Binding binding) throws BindingException {
148: // if (this.phase != ProcessingPhase.VALIDATE) {
149: // throw new IllegalStateException("Cannot save model in phase " + this.phase);
150: // }
151: //
152: // if (!isValid()) {
153: // throw new IllegalStateException("Cannot save an invalid form.");
154: // }
155: // this.phase = ProcessingPhase.SAVE_MODEL;
156: // binding.saveFormToModel(this, data);
157: // }
158:
159: public void addProcessingPhaseListener(
160: ProcessingPhaseListener listener) {
161: this .listener = WidgetEventMulticaster.add(this .listener,
162: listener);
163: }
164:
165: public void removeProcessingPhaseListener(
166: ProcessingPhaseListener listener) {
167: this .listener = WidgetEventMulticaster.remove(this .listener,
168: listener);
169: }
170:
171: /**
172: * Processes a form submit. If the form is finished, i.e. the form should not be redisplayed to the user,
173: * then this method returns true, otherwise it returns false. To know if the form was sucessfully
174: * validated, use the {@link #isValid()} method.
175: * <p>
176: * Form processing consists in multiple steps:
177: * <ul>
178: * <li>all widgets read their value from the request (i.e.
179: * {@link #readFromRequest(FormContext)} is called recursively on
180: * the whole widget tree)
181: * <li>if there is an action event, call the FormHandler
182: * <li>perform validation.
183: * </ul>
184: * This processing can be interrupted by the widgets (or their event listeners) by calling
185: * {@link #endProcessing(boolean)}.
186: */
187: public boolean process(FormContext formContext) {
188:
189: // Fire the binding phase events
190: fireWidgetEvents();
191:
192: // setup processing
193: this .submitWidget = null;
194: this .locale = formContext.getLocale();
195: this .endProcessing = null;
196: this .isValid = false;
197:
198: // Notify the end of the current phase
199: if (this .listener != null) {
200: this .listener.phaseEnded(new ProcessingPhaseEvent(this ,
201: this .phase));
202: }
203:
204: this .phase = ProcessingPhase.READ_FROM_REQUEST;
205: // Find the submit widget, if not an action
206: this .submitWidget = null;
207: String submitId = formContext.getRequest().getParameter(
208: "woody_submit_id");
209: if (submitId != null && submitId.length() > 0) {
210: StringTokenizer stok = new StringTokenizer(submitId, ".");
211: Widget submit = this ;
212: while (stok.hasMoreTokens()) {
213: submit = submit.getWidget(stok.nextToken());
214: if (submit == null) {
215: throw new IllegalArgumentException(
216: "Invalid submit id (no such widget): "
217: + submitId);
218: }
219: }
220:
221: setSubmitWidget(submit);
222: }
223:
224: doReadFromRequest(formContext);
225: fireWidgetEvents();
226:
227: // Notify the end of the current phase
228: if (this .listener != null) {
229: this .listener.phaseEnded(new ProcessingPhaseEvent(this ,
230: this .phase));
231: }
232:
233: if (this .endProcessing != null) {
234: return this .endProcessing.booleanValue();
235: }
236:
237: // Validate the form
238: this .phase = ProcessingPhase.VALIDATE;
239: this .isValid = doValidate(formContext);
240:
241: if (this .endProcessing != null) {
242: return this .endProcessing.booleanValue();
243: }
244:
245: // Notify the end of the current phase
246: if (this .listener != null) {
247: this .listener.phaseEnded(new ProcessingPhaseEvent(this ,
248: this .phase));
249: }
250:
251: if (this .endProcessing != null) {
252: // De-validate the form if one of the listeners asked to end the processing
253: // This allows for additional application-level validation.
254: this .isValid = false;
255: return this .endProcessing.booleanValue();
256: }
257:
258: return this .isValid;
259: }
260:
261: /**
262: * End the current form processing after the current phase.
263: *
264: * @param redisplayForm indicates if the form should be redisplayed to the user.
265: */
266: public void endProcessing(boolean redisplayForm) {
267: this .endProcessing = new Boolean(!redisplayForm);
268: }
269:
270: /**
271: * Was form validation successful ?
272: *
273: * @return <code>true</code> if the form was successfully validated.
274: */
275: public boolean isValid() {
276: return this .isValid;
277: }
278:
279: public void readFromRequest(FormContext formContext) {
280: throw new UnsupportedOperationException(
281: "Please use Form.process()");
282: }
283:
284: private void doReadFromRequest(FormContext formContext) {
285: // let all individual widgets read their value from the request object
286: super .readFromRequest(formContext);
287: }
288:
289: public boolean validate(FormContext formContext) {
290: throw new UnsupportedOperationException(
291: "Please use Form.process()");
292: }
293:
294: public boolean doValidate(FormContext formContext) {
295: return super .validate(formContext);
296: }
297:
298: public Object getAttribute(String name) {
299: return this .attributes == null ? null : this .attributes
300: .get(name);
301: }
302:
303: public void setAttribute(String name, Object value) {
304: if (this .attributes == null) {
305: this .attributes = new HashMap();
306: }
307:
308: this .attributes.put(name, value);
309: }
310:
311: public void removeAttribute(String name) {
312: if (this .attributes != null) {
313: this .attributes.remove(name);
314: }
315: }
316:
317: private static final String FORM_EL = "form";
318: private static final String CHILDREN_EL = "children";
319:
320: public void generateSaxFragment(ContentHandler contentHandler,
321: Locale locale) throws SAXException {
322: AttributesImpl formAttrs = new AttributesImpl();
323: formAttrs.addCDATAAttribute("id", definition.getId());
324: contentHandler.startElement(Constants.WI_NS, FORM_EL,
325: Constants.WI_PREFIX_COLON + FORM_EL,
326: Constants.EMPTY_ATTRS);
327: definition.generateLabel(contentHandler);
328:
329: contentHandler.startElement(Constants.WI_NS, CHILDREN_EL,
330: Constants.WI_PREFIX_COLON + CHILDREN_EL,
331: Constants.EMPTY_ATTRS);
332: Iterator widgetIt = widgets.iterator();
333: while (widgetIt.hasNext()) {
334: Widget widget = (Widget) widgetIt.next();
335: widget.generateSaxFragment(contentHandler, locale);
336: }
337: contentHandler.endElement(Constants.WI_NS, CHILDREN_EL,
338: Constants.WI_PREFIX_COLON + CHILDREN_EL);
339:
340: contentHandler.endElement(Constants.WI_NS, FORM_EL,
341: Constants.WI_PREFIX_COLON + FORM_EL);
342: }
343:
344: public void generateLabel(ContentHandler contentHandler)
345: throws SAXException {
346: definition.generateLabel(contentHandler);
347: }
348: }
|