KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > forms > formmodel > Form


1 /*
2  * Copyright 1999-2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.forms.formmodel;
17
18 import java.util.HashSet JavaDoc;
19 import java.util.Locale JavaDoc;
20 import java.util.Set JavaDoc;
21 import java.util.StringTokenizer JavaDoc;
22
23 import org.apache.cocoon.forms.FormContext;
24 import org.apache.cocoon.forms.event.FormHandler;
25 import org.apache.cocoon.forms.event.ProcessingPhase;
26 import org.apache.cocoon.forms.event.ProcessingPhaseEvent;
27 import org.apache.cocoon.forms.event.ProcessingPhaseListener;
28 import org.apache.cocoon.forms.event.WidgetEvent;
29 import org.apache.cocoon.forms.event.WidgetEventMulticaster;
30 import org.apache.cocoon.forms.validation.ValidationError;
31 import org.apache.cocoon.forms.validation.ValidationErrorAware;
32 import org.apache.commons.collections.list.CursorableLinkedList;
33 import org.apache.commons.lang.BooleanUtils;
34
35 /**
36  * A widget that serves as a container for other widgets, the top-level widget in
37  * a form description file.
38  *
39  * @author Bruno Dumon
40  * @author <a HREF="http://www.apache.org/~sylvain/">Sylvain Wallez</a>
41  * @version $Id: Form.java 326930 2005-10-20 16:03:19Z sylvain $
42  */

43 public class Form extends AbstractContainerWidget
44                   implements ValidationErrorAware {
45
46     private static final String JavaDoc FORM_EL = "form";
47
48     private final FormDefinition definition;
49
50     /**
51      * If non-null, indicates that form processing should terminate at the end of the current phase.
52      * If true, interaction with the form is finished. It doesn't imply that the form is valid though.
53      * If false, interaction isn't finished and the form should be redisplayed (processing was triggered
54      * by e.g. and action or a field with event listeners).
55      */

56     private Boolean JavaDoc endProcessing;
57     private Locale JavaDoc locale = Locale.getDefault();
58     private FormHandler formHandler;
59     private Widget submitWidget;
60     private ProcessingPhase phase = ProcessingPhase.LOAD_MODEL;
61     private boolean isValid = false;
62     private ProcessingPhaseListener listener;
63
64     //In the "readFromRequest" phase, events are buffered to ensure that all widgets had the chance
65
//to read their value before events get fired.
66
private boolean bufferEvents = false;
67     private CursorableLinkedList events;
68
69     // Widgets that need to be updated in the client when in AJAX mode
70
private Set JavaDoc updatedWidgets;
71     // Widgets that have at least one descendant that has to be updated
72
private Set JavaDoc childUpdatedWidgets;
73
74
75     public Form(FormDefinition definition) {
76         super(definition);
77         this.definition = definition;
78     }
79
80     /**
81      * Initialize the form by recursively initializing all its children. Any events occuring within the
82      * initialization phase are buffered and fired after initialization is complete, so that any action
83      * from a widget on another one occurs after that other widget has been given the opportunity to
84      * initialize itself.
85      */

86     public void initialize() {
87         try {
88             // Start buffering events
89
this.bufferEvents = true;
90             super.initialize();
91             // Fire events, still buffering them: this ensures they will be handled in the same
92
// order as they were added.
93
fireEvents();
94         } finally {
95             // Stop buffering events
96
this.bufferEvents = false;
97         }
98     }
99
100     public WidgetDefinition getDefinition() {
101         return this.definition;
102     }
103
104     /**
105      * Events produced by child widgets should not be fired immediately, but queued in order to ensure
106      * an overall consistency of the widget tree before being handled.
107      *
108      * @param event the event to queue
109      */

110     public void addWidgetEvent(WidgetEvent event) {
111
112         if (this.bufferEvents) {
113             if (this.events == null) {
114                 this.events = new CursorableLinkedList();
115             }
116
117             // FIXME: limit the number of events to detect recursive event loops ?
118
this.events.add(event);
119         } else {
120             // Send it right now
121
event.getSourceWidget().broadcastEvent(event);
122         }
123     }
124
125     /**
126      * Mark a widget as being updated. When it Ajax mode, only updated widgets will be redisplayed
127      *
128      * @param widget the updated widget
129      * @return <code>true</code> if this widget was added to the list (i.e. wasn't alredy marked for update)
130      */

131     public boolean addWidgetUpdate(Widget widget) {
132         if (this.updatedWidgets != null) {
133             if (this.updatedWidgets.add(widget.getRequestParameterName())) {
134                 // Wasn't already there: register parents
135
Widget parent = widget.getParent();
136                 addParents: while (parent != this && parent != null) {
137                     if (this.childUpdatedWidgets.add(parent.getRequestParameterName())) {
138                         parent = parent.getParent();
139                     } else {
140                         // Parent already there, and therefore its own parents.
141
break addParents;
142                     }
143                 }
144                 return true;
145             }
146         }
147         return false;
148     }
149
150     public Set JavaDoc getUpdatedWidgetIds() {
151         return this.updatedWidgets;
152     }
153
154     public Set JavaDoc getChildUpdatedWidgetIds() {
155         return this.childUpdatedWidgets;
156     }
157
158     /**
159      * Fire the events that have been queued.
160      * Note that event handling can fire new events.
161      */

162     private void fireEvents() {
163         if (this.events != null) {
164             CursorableLinkedList.Cursor cursor = this.events.cursor();
165             while(cursor.hasNext()) {
166                 WidgetEvent event = (WidgetEvent)cursor.next();
167                 event.getSourceWidget().broadcastEvent(event);
168                 if (formHandler != null)
169                     formHandler.handleEvent(event);
170             }
171             cursor.close();
172
173             this.events.clear();
174         }
175     }
176
177     /**
178      * Get the locale to be used to process this form.
179      *
180      * @return the form's locale.
181      */

182     public Locale JavaDoc getLocale() {
183         return this.locale;
184     }
185
186     /**
187      * Get the widget that triggered the current processing. Note that it can be any widget, and
188      * not necessarily an action or a submit.
189      *
190      * @return the widget that submitted this form.
191      */

192     public Widget getSubmitWidget() {
193         return this.submitWidget;
194     }
195
196     /**
197      * Set the widget that triggered the current form processing.
198      *
199      * @param widget the widget
200      */

201     public void setSubmitWidget(Widget widget) {
202         if (this.submitWidget != null && this.submitWidget != widget) {
203             throw new IllegalStateException JavaDoc("Submit widget already set to " + this.submitWidget +
204                     ". Cannot set also " + widget);
205         }
206         
207         // Check that the submit widget is active
208
if (widget.getCombinedState() != WidgetState.ACTIVE) {
209             throw new IllegalStateException JavaDoc("Widget " + widget + " that submitted the form is not active.");
210         }
211         
212         // If the submit widget is not an action (e.g. a field with an event listener),
213
// we end form processing after the current phase and redisplay the form.
214
// Actions (including submits) will call endProcessing() themselves and it's their
215
// responsibility to indicate how form processing should continue.
216
if (!(widget instanceof Action)) {
217             endProcessing(true);
218         }
219         this.submitWidget = widget;
220     }
221
222     public boolean hasFormHandler() {
223        return (this.formHandler != null);
224     }
225
226     public void setFormHandler(FormHandler formHandler) {
227         this.formHandler = formHandler;
228     }
229
230 // TODO: going through the form for load and save ensures state consistency. To we add this or
231
// keep the binding strictly separate ?
232
// public void load(Object data, Binding binding) {
233
// if (this.phase != ProcessingPhase.LOAD_MODEL) {
234
// throw new IllegalStateException("Cannot load form in phase " + this.phase);
235
// }
236
// binding.loadFormFromModel(this, data);
237
// }
238
//
239
// public void save(Object data, Binding binding) throws BindingException {
240
// if (this.phase != ProcessingPhase.VALIDATE) {
241
// throw new IllegalStateException("Cannot save model in phase " + this.phase);
242
// }
243
//
244
// if (!isValid()) {
245
// throw new IllegalStateException("Cannot save an invalid form.");
246
// }
247
// this.phase = ProcessingPhase.SAVE_MODEL;
248
// binding.saveFormToModel(this, data);
249
// }
250

251     public void addProcessingPhaseListener(ProcessingPhaseListener listener) {
252         this.listener = WidgetEventMulticaster.add(this.listener, listener);
253     }
254
255     public void removeProcessingPhaseListener(ProcessingPhaseListener listener) {
256         this.listener = WidgetEventMulticaster.remove(this.listener, listener);
257     }
258
259     /**
260      * Processes a form submit. If the form is finished, i.e. the form should not be redisplayed to the user,
261      * then this method returns true, otherwise it returns false. To know if the form was sucessfully
262      * validated, use the {@link #isValid()} method.
263      * <p>
264      * Form processing consists in multiple steps:
265      * <ul>
266      * <li>all widgets read their value from the request (i.e.
267      * {@link #readFromRequest(FormContext)} is called recursively on
268      * the whole widget tree)
269      * <li>if there is an action event, call the FormHandler
270      * <li>perform validation.
271      * </ul>
272      * This processing can be interrupted by the widgets (or their event listeners) by calling
273      * {@link #endProcessing(boolean)}.
274      * <p>
275      * Note that this method is synchronized as a Form is not thread-safe. This should not be a
276      * bottleneck as such concurrent requests can only happen for a single user.
277      */

278     public synchronized boolean process(FormContext formContext) {
279         // Is this an AJAX request?
280
if (formContext.getRequest().getParameter("cocoon-ajax") != null) {
281             this.updatedWidgets = new HashSet JavaDoc();
282             this.childUpdatedWidgets = new HashSet JavaDoc();
283         }
284
285         // Fire the binding phase events
286
fireEvents();
287
288         // setup processing
289
this.submitWidget = null;
290         this.locale = formContext.getLocale();
291         this.endProcessing = null;
292         this.isValid = false;
293
294         // Notify the end of the current phase
295
if (this.listener != null) {
296             this.listener.phaseEnded(new ProcessingPhaseEvent(this, this.phase));
297         }
298
299         this.phase = ProcessingPhase.READ_FROM_REQUEST;
300         // Find the submit widget, if not an action
301
this.submitWidget = null;
302         String JavaDoc submitId = formContext.getRequest().getParameter("forms_submit_id");
303         if (submitId != null && submitId.length() > 0) {
304             // if the form has an ID, it is used as part of the submitId too
305
// this has ID has to be cut off
306
if(this.getId() != null && !"".equals(this.getId())) {
307                 submitId = submitId.substring(submitId.indexOf('.')+1);
308             }
309             StringTokenizer JavaDoc stok = new StringTokenizer JavaDoc(submitId, ".");
310             Widget submit = this;
311             while (stok.hasMoreTokens()) {
312                 submit = submit.lookupWidget(stok.nextToken());
313                 if (submit == null) {
314                     throw new IllegalArgumentException JavaDoc("Invalid submit id (no such widget): " + submitId);
315                 }
316             }
317
318             setSubmitWidget(submit);
319         }
320
321         try {
322             // Start buffering events
323
this.bufferEvents = true;
324
325             doReadFromRequest(formContext);
326
327             // Fire events, still buffering them: this ensures they will be handled in the same
328
// order as they were added.
329
fireEvents();
330         } finally {
331             // No need for buffering in the following phases
332
this.bufferEvents = false;
333         }
334
335         // Notify the end of the current phase
336
if (this.listener != null) {
337             this.listener.phaseEnded(new ProcessingPhaseEvent(this, this.phase));
338         }
339         if (this.endProcessing != null) {
340             return this.endProcessing.booleanValue();
341         }
342
343         return validate();
344     }
345
346     /**
347      * End the current form processing after the current phase.
348      *
349      * @param redisplayForm indicates if the form should be redisplayed to the user.
350      */

351     public void endProcessing(boolean redisplayForm) {
352         // Set the indicator that terminates the form processing.
353
// If redisplayForm is true, interaction is not finished and process() must
354
// return false, hence the negation below.
355
this.endProcessing = BooleanUtils.toBooleanObject( !redisplayForm );
356     }
357
358     /**
359      * Was form validation successful ?
360      *
361      * @return <code>true</code> if the form was successfully validated.
362      */

363     public boolean isValid() {
364         return this.isValid;
365     }
366
367     public void readFromRequest(FormContext formContext) {
368         throw new UnsupportedOperationException JavaDoc("Please use Form.process()");
369     }
370
371     private void doReadFromRequest(FormContext formContext) {
372         // let all individual widgets read their value from the request object
373
super.readFromRequest(formContext);
374     }
375
376     /**
377      * Set a validation error on this field. This allows the form to be externally marked as invalid by
378      * application logic.
379      *
380      * @return the validation error
381      */

382     public ValidationError getValidationError() {
383         return this.validationError;
384     }
385
386     /**
387      * set a validation error
388      */

389     public void setValidationError(ValidationError error) {
390         this.validationError = error;
391     }
392
393     /**
394      * Performs validation phase of form processing.
395      */

396     public boolean validate() {
397         // Validate the form
398
this.phase = ProcessingPhase.VALIDATE;
399         this.isValid = super.validate();
400
401         // FIXME: Is this check needed, before invoking the listener?
402
if (this.endProcessing != null) {
403             this.wasValid = this.endProcessing.booleanValue();
404             return this.wasValid;
405         }
406
407         // Notify the end of the current phase
408
if (this.listener != null) {
409             this.listener.phaseEnded(new ProcessingPhaseEvent(this, this.phase));
410         }
411         if (this.endProcessing != null) {
412             // De-validate the form if one of the listeners asked to end the processing
413
// This allows for additional application-level validation.
414
this.isValid = false;
415             this.wasValid = this.endProcessing.booleanValue();
416             return this.wasValid;
417         }
418         this.wasValid = this.isValid && this.validationError == null;
419         return this.wasValid;
420     }
421
422     public String JavaDoc getXMLElementName() {
423         return FORM_EL;
424     }
425 }
426
Popular Tags