KickJava   Java API By Example, From Geeks To Geeks.

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


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 org.apache.cocoon.forms.FormsConstants;
19 import org.apache.cocoon.forms.FormContext;
20 import org.apache.cocoon.forms.datatype.Datatype;
21 import org.apache.cocoon.forms.datatype.SelectionList;
22 import org.apache.cocoon.forms.datatype.convertor.ConversionResult;
23 import org.apache.cocoon.forms.event.*;
24 import org.apache.cocoon.forms.util.I18nMessage;
25 import org.apache.cocoon.forms.validation.ValidationError;
26 import org.apache.cocoon.forms.validation.ValidationErrorAware;
27 import org.apache.cocoon.xml.AttributesImpl;
28 import org.apache.cocoon.xml.XMLUtils;
29 import org.apache.commons.lang.ObjectUtils;
30 import org.xml.sax.ContentHandler JavaDoc;
31 import org.xml.sax.SAXException JavaDoc;
32
33 import java.util.Locale JavaDoc;
34
35 /**
36  * A general-purpose Widget that can hold one value. A Field widget can be associated
37  * with a {@link org.apache.cocoon.forms.datatype.Datatype Datatype}, and thus
38  * a Field widget can be used to edit different kinds of data, such as strings,
39  * numbers and dates. A Datatype can also have an associated SelectionList, so
40  * that the value for the Field can be selected from a list, rather than being
41  * entered in a textbox. The validation of the field is delegated to its associated
42  * Datatype.
43  *
44  * @author Bruno Dumon
45  * @author <a HREF="http://www.apache.org/~sylvain/">Sylvain Wallez</a>
46  * @version $Id: Field.java 332310 2005-11-10 15:33:10Z sylvain $
47  */

48 public class Field extends AbstractWidget
49                    implements ValidationErrorAware, DataWidget, SelectableWidget, ValueChangedListenerEnabled {
50
51     private static final String JavaDoc FIELD_EL = "field";
52     private static final String JavaDoc VALUE_EL = "value";
53     private static final String JavaDoc VALIDATION_MSG_EL = "validation-message";
54
55     /** Overrides selection list defined in FieldDefinition, if any. */
56     protected SelectionList selectionList;
57     /** Additional listeners to those defined as part of the widget definition (if any). */
58     private ValueChangedListener listener;
59
60     private final FieldDefinition fieldDefinition;
61
62     protected String JavaDoc enteredValue;
63     protected Object JavaDoc value;
64
65     protected boolean required;
66     
67     /**
68      * Value state indicating that a new value has been read from the request,
69      * but has not yet been parsed.
70      */

71     protected final static int VALUE_UNPARSED = 0;
72
73     /**
74      * Value state indicating that a value has been parsed, but needs to be
75      * validated (that must occur before the value is given to the application)
76      */

77     protected final static int VALUE_PARSED = 1;
78
79     /**
80      * Value state indicating that a parse error was encountered but should not
81      * yet be displayed.
82      */

83     protected final static int VALUE_PARSE_ERROR = 2;
84
85     /**
86      * Value state indicating that validate() has been called when state was
87      * VALUE_PARSE_ERROR. This makes the error visible on output.
88      */

89     protected final static int VALUE_DISPLAY_PARSE_ERROR = 3;
90
91     /**
92      * Transient value state indicating that validation is going on.
93      *
94      * @see #validate()
95      */

96     protected final static int VALUE_VALIDATING = 4;
97
98     /**
99      * Value state indicating that validation has occured, but that any error should not
100      * yet be displayed.
101      */

102     protected final static int VALUE_VALIDATED = 5;
103
104     /**
105      * Value state indicating that value validation has occured, and the
106      * validation error, if any, should be displayed.
107      */

108     protected final static int VALUE_DISPLAY_VALIDATION = 6;
109
110     // At startup, we have no value to parse (both enteredValue and value are null),
111
// but need to validate (e.g. error if field is required)
112
/**
113      * Transient widget processing state indicating that the widget is currently validating
114      * (used to avoid endless loops when a validator calls getValue)
115      */

116     protected int valueState = VALUE_PARSED;
117
118     protected ValidationError validationError;
119
120
121     public Field(FieldDefinition fieldDefinition) {
122         super(fieldDefinition);
123         this.fieldDefinition = fieldDefinition;
124         this.listener = fieldDefinition.getValueChangedListener();
125     }
126
127     public final FieldDefinition getFieldDefinition() {
128         return this.fieldDefinition;
129     }
130
131     public WidgetDefinition getDefinition() {
132         return this.fieldDefinition;
133     }
134
135     public void initialize() {
136         Object JavaDoc value = this.fieldDefinition.getInitialValue();
137         if (value != null) {
138             setValue(value);
139         }
140         this.selectionList = this.fieldDefinition.getSelectionList();
141         this.required = this.fieldDefinition.isRequired();
142         super.initialize();
143     }
144
145     public Object JavaDoc getValue() {
146         // if getValue() is called on this field while we're validating, then it's because a validation
147
// rule called getValue(), so then we just return the parsed (but not VALUE_VALIDATED) value to avoid an endless loop
148
if (this.valueState == VALUE_VALIDATING) {
149             return this.value;
150         }
151
152         ValidationError oldError = this.validationError;
153
154         // Parse the value
155
if (this.valueState == VALUE_UNPARSED) {
156             doParse();
157         }
158
159         // Validate the value if it was successfully parsed
160
if (this.valueState == VALUE_PARSED) {
161             doValidate();
162         }
163
164         if (oldError != null && this.validationError == null) {
165             // The parsing process removed an existing validation error. This happens
166
// mainly when a required field is given a value.
167
getForm().addWidgetUpdate(this);
168         }
169
170         return this.validationError == null ? this.value : null;
171     }
172
173     public void setValue(Object JavaDoc newValue) {
174         if (newValue != null && !getDatatype().getTypeClass().isAssignableFrom(newValue.getClass())) {
175             throw new RuntimeException JavaDoc("Incorrect value type for \"" + getRequestParameterName() +
176                            "\" (expected " + getDatatype().getTypeClass() +
177                            ", got " + newValue.getClass() + ").");
178         }
179
180         // Is it a new value?
181
boolean changed;
182         if (this.valueState == VALUE_UNPARSED) {
183             // Current value was not parsed
184
changed = true;
185         } else if (this.value == null) {
186             // Is current value not null?
187
changed = (newValue != null);
188         } else {
189             // Is current value different?
190
changed = !this.value.equals(newValue);
191         }
192
193         // Do something only if value is different or null
194
// (null allows to reset validation error)
195
if (changed || newValue == null) {
196             // Do we need to call listeners? If yes, keep (and parse if needed) old value.
197
boolean callListeners = changed && (hasValueChangedListeners() || this.getForm().hasFormHandler());
198             Object JavaDoc oldValue = callListeners ? getValue() : null;
199
200             this.value = newValue;
201             this.validationError = null;
202             // Force validation, even if set by the application
203
this.valueState = VALUE_PARSED;
204             if (newValue != null) {
205                 this.enteredValue = getDatatype().convertToString(newValue, getForm().getLocale());
206             } else {
207                 this.enteredValue = null;
208             }
209
210             if (callListeners) {
211                 getForm().addWidgetEvent(new ValueChangedEvent(this, oldValue, newValue));
212             }
213             getForm().addWidgetUpdate(this);
214         }
215     }
216
217     public void readFromRequest(FormContext formContext) {
218         if (!getCombinedState().isAcceptingInputs()) {
219             return;
220         }
221
222         String JavaDoc newEnteredValue = formContext.getRequest().getParameter(getRequestParameterName());
223         // FIXME: Should we consider only non-null values?
224
// Although distinguishing an empty value (input present but blank) from a null value
225
// (input not present in the form) is possible, this distinction is not possible for
226
// several other kinds of widgets such as BooleanField or MultiValueField. So we keep
227
// it consistent with other widgets.
228
//if (newEnteredValue != null) {
229
readFromRequest(newEnteredValue);
230         //}
231
}
232
233     protected void readFromRequest(String JavaDoc newEnteredValue) {
234         // whitespace & empty field handling
235
if (newEnteredValue != null) {
236             // TODO make whitespace behaviour configurable !!
237
newEnteredValue = newEnteredValue.trim();
238             if (newEnteredValue.length() == 0) {
239                 newEnteredValue = null;
240             }
241         }
242
243         // Only convert if the text value actually changed. Otherwise, keep the old value
244
// and/or the old validation error (allows to keep errors when clicking on actions)
245
boolean changed;
246         if (enteredValue == null) {
247             changed = (newEnteredValue != null);
248         } else {
249             changed = !enteredValue.equals(newEnteredValue);
250         }
251
252         if (changed) {
253             ValidationError oldError = this.validationError;
254
255             // If we have some value-changed listeners, we must make sure the current value has been
256
// parsed, to fill the event. Otherwise, we don't need to spend that extra CPU time.
257
boolean hasListeners = hasValueChangedListeners() || this.getForm().hasFormHandler();
258             Object JavaDoc oldValue = hasListeners ? getValue() : null;
259
260             enteredValue = newEnteredValue;
261             validationError = null;
262             value = null;
263             this.valueState = VALUE_UNPARSED;
264
265             if (hasListeners) {
266                 // Throw an event that will hold the old value and
267
// will lazily compute the new value only if needed.
268
getForm().addWidgetEvent(new DeferredValueChangedEvent(this, oldValue));
269             }
270
271             if (oldError != null) {
272                 // There was a validation error, and the user entered a new value: refresh
273
// the widget, because the previous error was cleared
274
getForm().addWidgetUpdate(this);
275             }
276         }
277     }
278
279     /**
280      * @see org.apache.cocoon.forms.formmodel.Widget#validate()
281      */

282     public boolean validate() {
283         if (!getCombinedState().isValidatingValues()) {
284             this.wasValid = true;
285             return true;
286         }
287
288         if (this.valueState == VALUE_UNPARSED) {
289             doParse();
290         }
291
292         // Force validation on already validated values (but keep invalid parsings)
293
if (this.valueState >= VALUE_VALIDATED) {
294             this.valueState = VALUE_PARSED;
295         }
296
297         if (this.valueState == VALUE_PARSED) {
298             doValidate();
299             this.valueState = VALUE_DISPLAY_VALIDATION;
300             if (this.validationError != null) {
301                 getForm().addWidgetUpdate(this);
302             }
303         } else if (this.valueState == VALUE_PARSE_ERROR) {
304             this.valueState = VALUE_DISPLAY_PARSE_ERROR;
305             getForm().addWidgetUpdate(this);
306         }
307
308         this.wasValid = this.validationError == null;
309         return this.wasValid;
310     }
311
312     /**
313      * Parse the value that has been read from the request.
314      * Should be called when valueState is VALUE_UNPARSED.
315      * On exit, valueState is set to either:
316      * - VALUE_PARSED: successful parsing or null value. Value is set and ValidationError
317      * is cleared.
318      * - VALUE_PARSE_ERROR: datatype parsing error. In that case, value is null and
319      * validationError is set.
320      */

321     private void doParse() {
322         if (this.valueState != VALUE_UNPARSED) {
323             throw new IllegalStateException JavaDoc("Field is not in UNPARSED state (" + this.valueState + ")");
324         }
325
326         // Clear value, it will be recomputed
327
this.value = null;
328         this.validationError = null;
329
330         if (this.enteredValue != null) {
331             // Parse the value
332
ConversionResult conversionResult = getDatatype().convertFromString(this.enteredValue, getForm().getLocale());
333             if (conversionResult.isSuccessful()) {
334                 this.value = conversionResult.getResult();
335                 this.valueState = VALUE_PARSED;
336             } else {
337                 // Conversion failed
338
this.validationError = conversionResult.getValidationError();
339                 // No need for further validation (and need to keep the above error)
340
this.valueState = VALUE_PARSE_ERROR;
341             }
342         } else {
343             // No value: needs to be validated
344
this.valueState = VALUE_PARSED;
345         }
346     }
347
348     /**
349      * Validate the value once it has been parsed.
350      * Should be called when valueState is VALUE_PARSED.
351      * On exit, valueState is set to VALUE_VALIDATED, and validationError is set if
352      * validation failed.
353      */

354     private void doValidate() {
355         if (this.valueState != VALUE_PARSED) {
356             throw new IllegalStateException JavaDoc("Field is not in PARSED state (" + this.valueState + ")");
357         }
358
359         // Go to transient validating state
360
this.valueState = VALUE_VALIDATING;
361
362         // reset validation errot
363
this.validationError = null;
364
365         try {
366             if (this.value == null && this.required) {
367                 // Field is required
368
this.validationError = new ValidationError(new I18nMessage("general.field-required", FormsConstants.I18N_CATALOGUE));
369             } else if (!super.validate()) {
370                 // New-style validators failed.
371
} else if (this.value != null) {
372                 // Check the old-style ones.
373
this.validationError = getDatatype().validate(this.value, new ExpressionContextImpl(this));
374             }
375         } finally {
376             // Consider validation finished even in case of exception
377
this.valueState = VALUE_VALIDATED;
378         }
379     }
380
381     /**
382      * Returns the validation error, if any. There will always be a validation error in case the
383      * {@link #validate} method returned false.
384      *
385      * <br>This method does not cause parsing to take effect, use {@link #getValue} if value
386      * is not parsed yet.
387      */

388     public ValidationError getValidationError() {
389         return this.validationError;
390     }
391
392     /**
393      * Set a validation error on this field. This allows fields to be externally marked as invalid by
394      * application logic.
395      *
396      * @param error the validation error
397      */

398     public void setValidationError(ValidationError error) {
399         if (this.valueState >= VALUE_VALIDATED) {
400             this.valueState = VALUE_DISPLAY_VALIDATION;
401         }
402
403         if (!ObjectUtils.equals(this.validationError, error)) {
404             this.validationError = error;
405             getForm().addWidgetUpdate(this);
406         }
407     }
408
409     public boolean isRequired() {
410         return this.required;
411     }
412
413     public void setRequired(boolean required) {
414         this.required = required;
415     }
416     
417     /**
418      * @return "field"
419      */

420     public String JavaDoc getXMLElementName() {
421         return FIELD_EL;
422     }
423
424     /**
425      * Adds the @required attribute
426      */

427     public AttributesImpl getXMLElementAttributes() {
428         AttributesImpl attrs = super.getXMLElementAttributes();
429         attrs.addCDATAAttribute("required", String.valueOf(isRequired()));
430         return attrs;
431     }
432
433     public void generateItemSaxFragment(ContentHandler JavaDoc contentHandler, Locale JavaDoc locale) throws SAXException JavaDoc {
434         if (locale == null) {
435             locale = getForm().getLocale();
436         }
437
438         if (enteredValue != null || value != null) {
439             contentHandler.startElement(FormsConstants.INSTANCE_NS, VALUE_EL, FormsConstants.INSTANCE_PREFIX_COLON + VALUE_EL, XMLUtils.EMPTY_ATTRIBUTES);
440             String JavaDoc stringValue;
441             if (value != null) {
442                 stringValue = getDatatype().convertToString(value, locale);
443             } else {
444                 stringValue = enteredValue;
445             }
446             contentHandler.characters(stringValue.toCharArray(), 0, stringValue.length());
447             contentHandler.endElement(FormsConstants.INSTANCE_NS, VALUE_EL, FormsConstants.INSTANCE_PREFIX_COLON + VALUE_EL);
448         }
449
450         // validation message element: only present if the value is not valid
451
if (validationError != null && (this.valueState == VALUE_DISPLAY_VALIDATION || this.valueState == VALUE_DISPLAY_PARSE_ERROR)) {
452             contentHandler.startElement(FormsConstants.INSTANCE_NS, VALIDATION_MSG_EL, FormsConstants.INSTANCE_PREFIX_COLON + VALIDATION_MSG_EL, XMLUtils.EMPTY_ATTRIBUTES);
453             validationError.generateSaxFragment(contentHandler);
454             contentHandler.endElement(FormsConstants.INSTANCE_NS, VALIDATION_MSG_EL, FormsConstants.INSTANCE_PREFIX_COLON + VALIDATION_MSG_EL);
455         }
456
457         // generate selection list, if any
458
if (selectionList != null) {
459             selectionList.generateSaxFragment(contentHandler, locale);
460         }
461
462         // include some info about the datatype
463
fieldDefinition.getDatatype().generateSaxFragment(contentHandler, locale);
464     }
465
466
467     /**
468      * Set this field's selection list.
469      * @param selectionList The new selection list.
470      */

471     public void setSelectionList(SelectionList selectionList) {
472         if (selectionList != null &&
473             selectionList.getDatatype() != null &&
474             selectionList.getDatatype() != getDatatype()) {
475             throw new RuntimeException JavaDoc("Tried to assign a SelectionList that is not associated with this widget's datatype.");
476         }
477         this.selectionList = selectionList;
478         getForm().addWidgetUpdate(this);
479     }
480
481     /**
482      * Read this field's selection list from an external source.
483      * All Cocoon-supported protocols can be used.
484      * The format of the XML produced by the source should be the
485      * same as in case of inline specification of the selection list,
486      * thus the root element should be a <code>fd:selection-list</code>
487      * element.
488      * @param uri The URI of the source.
489      */

490     public void setSelectionList(String JavaDoc uri) {
491         setSelectionList(getFieldDefinition().buildSelectionList(uri));
492     }
493
494     /**
495      * Set this field's selection list using values from an in-memory
496      * object. The <code>object</code> parameter should point to a collection
497      * (Java collection or array, or Javascript array) of objects. Each object
498      * belonging to the collection should have a <em>value</em> property and a
499      * <em>label</em> property, whose values are used to specify the <code>value</code>
500      * attribute and the contents of the <code>fd:label</code> child element
501      * of every <code>fd:item</code> in the list.
502      * <p>Access to the values of the above mentioned properties is done
503      * via <a HREF="http://jakarta.apache.org/commons/jxpath/users-guide.html">XPath</a> expressions.
504      * @param model The collection used as a model for the selection list.
505      * @param valuePath An XPath expression referring to the attribute used
506      * to populate the values of the list's items.
507      * @param labelPath An XPath expression referring to the attribute used
508      * to populate the labels of the list's items.
509      */

510     public void setSelectionList(Object JavaDoc model, String JavaDoc valuePath, String JavaDoc labelPath) {
511         setSelectionList(getFieldDefinition().buildSelectionListFromModel(model, valuePath, labelPath));
512     }
513     
514     public SelectionList getSuggestionList() {
515         return getFieldDefinition().getSuggestionList();
516     }
517
518     public Datatype getDatatype() {
519         return getFieldDefinition().getDatatype();
520     }
521
522     /**
523      * Adds a ValueChangedListener to this widget instance. Listeners defined
524      * on the widget instance will be executed in addtion to any listeners
525      * that might have been defined in the widget definition.
526      */

527     public void addValueChangedListener(ValueChangedListener listener) {
528         this.listener = WidgetEventMulticaster.add(this.listener, listener);
529     }
530
531     public void removeValueChangedListener(ValueChangedListener listener) {
532         this.listener = WidgetEventMulticaster.remove(this.listener, listener);
533     }
534
535     public boolean hasValueChangedListeners() {
536         return this.listener != null;
537     }
538
539     public void broadcastEvent(WidgetEvent event) {
540         if (event instanceof ValueChangedEvent) {
541             if (this.listener != null) {
542                 this.listener.valueChanged((ValueChangedEvent)event);
543             }
544         } else {
545             // Other kinds of events
546
super.broadcastEvent(event);
547         }
548     }
549 }
550
Popular Tags