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 org.apache.cocoon.woody.datatype.SelectionList;
020: import org.apache.cocoon.woody.validation.ValidationError;
021: import org.apache.cocoon.woody.validation.ValidationErrorAware;
022: import org.apache.cocoon.woody.event.WidgetEvent;
023: import org.apache.cocoon.woody.event.ValueChangedEvent;
024: import org.apache.cocoon.woody.Constants;
025: import org.apache.cocoon.woody.FormContext;
026: import org.apache.cocoon.woody.util.I18nMessage;
027: import org.apache.cocoon.xml.AttributesImpl;
028: import org.xml.sax.ContentHandler;
029: import org.xml.sax.SAXException;
030:
031: import java.util.Locale;
032:
033: /**
034: * A MultiValueField is mostly the same as a normal {@link Field}, but can
035: * hold multiple values. A MultiValueField should have a Datatype which
036: * has a SelectionList, because the user will always select the values
037: * from a list. A MultiValueField has no concept of "required", you should
038: * instead use the ValueCountValidationRule to check how many items the user
039: * has selected.
040: *
041: * <p>A MultiValueField also has a {@link org.apache.cocoon.woody.datatype.Datatype Datatype}
042: * associated with it. In case of MultiValueFields, this Datatype will always be an array
043: * type, thus {@link org.apache.cocoon.woody.datatype.Datatype#isArrayType()} will
044: * always return true, and this in return has an influence on the kind of validation rules that
045: * can be used with the Datatype (see {@link org.apache.cocoon.woody.datatype.Datatype Datatype}
046: * description for more information).
047: *
048: * @version $Id: MultiValueField.java 433543 2006-08-22 06:22:54Z crossley $
049: */
050: public class MultiValueField extends AbstractWidget implements
051: ValidationErrorAware, SelectableWidget {
052: private SelectionList selectionList;
053: private MultiValueFieldDefinition fieldDefinition;
054: private String[] enteredValues;
055: private Object[] values;
056: private ValidationError validationError;
057:
058: public MultiValueField(MultiValueFieldDefinition definition) {
059: super .setDefinition(definition);
060: this .fieldDefinition = definition;
061: setLocation(definition.getLocation());
062: }
063:
064: public String getId() {
065: return definition.getId();
066: }
067:
068: public void readFromRequest(FormContext formContext) {
069: enteredValues = formContext.getRequest().getParameterValues(
070: getFullyQualifiedId());
071: validationError = null;
072: values = null;
073:
074: boolean conversionFailed = false;
075: if (enteredValues != null) {
076: // Normally, for MultiValueFields, the user selects the values from
077: // a SelectionList, and the values in a SelectionList are garanteed to
078: // be valid, so the conversion from String to native datatype should
079: // never fail. But it could fail if users start messing around with
080: // request parameters.
081: Object[] tempValues = new Object[enteredValues.length];
082: for (int i = 0; i < enteredValues.length; i++) {
083: String param = enteredValues[i];
084: tempValues[i] = fieldDefinition.getDatatype()
085: .convertFromString(param,
086: formContext.getLocale());
087: if (tempValues[i] == null) {
088: conversionFailed = true;
089: break;
090: }
091: }
092:
093: if (!conversionFailed)
094: values = tempValues;
095: else
096: values = null;
097: } else {
098: values = new Object[0];
099: }
100: }
101:
102: public boolean validate(FormContext formContext) {
103: if (values != null)
104: validationError = fieldDefinition.getDatatype().validate(
105: values, new ExpressionContextImpl(this ));
106: else
107: validationError = new ValidationError(new I18nMessage(
108: "multivaluefield.conversionfailed",
109: Constants.I18N_CATALOGUE));
110:
111: return validationError == null ? super .validate(formContext)
112: : false;
113: }
114:
115: private static final String MULTIVALUEFIELD_EL = "multivaluefield";
116: private static final String VALUES_EL = "values";
117: private static final String VALUE_EL = "value";
118: private static final String VALIDATION_MSG_EL = "validation-message";
119:
120: public void generateSaxFragment(ContentHandler contentHandler,
121: Locale locale) throws SAXException {
122: AttributesImpl attrs = new AttributesImpl();
123: attrs.addCDATAAttribute("id", getFullyQualifiedId());
124: contentHandler.startElement(Constants.WI_NS,
125: MULTIVALUEFIELD_EL, Constants.WI_PREFIX_COLON
126: + MULTIVALUEFIELD_EL, attrs);
127:
128: contentHandler.startElement(Constants.WI_NS, VALUES_EL,
129: Constants.WI_PREFIX_COLON + VALUES_EL,
130: Constants.EMPTY_ATTRS);
131: if (values != null) {
132: for (int i = 0; i < values.length; i++) {
133: contentHandler.startElement(Constants.WI_NS, VALUE_EL,
134: Constants.WI_PREFIX_COLON + VALUE_EL,
135: Constants.EMPTY_ATTRS);
136: String value = fieldDefinition.getDatatype()
137: .getPlainConvertor().convertToString(values[i],
138: locale, null);
139: contentHandler.characters(value.toCharArray(), 0, value
140: .length());
141: contentHandler.endElement(Constants.WI_NS, VALUE_EL,
142: Constants.WI_PREFIX_COLON + VALUE_EL);
143: }
144: } else if (enteredValues != null) {
145: for (int i = 0; i < enteredValues.length; i++) {
146: contentHandler.startElement(Constants.WI_NS, VALUE_EL,
147: Constants.WI_PREFIX_COLON + VALUE_EL,
148: Constants.EMPTY_ATTRS);
149: String value = fieldDefinition.getDatatype()
150: .getPlainConvertor().convertToString(
151: enteredValues[i], locale, null);
152: contentHandler.characters(value.toCharArray(), 0, value
153: .length());
154: contentHandler.endElement(Constants.WI_NS, VALUE_EL,
155: Constants.WI_PREFIX_COLON + VALUE_EL);
156: }
157: }
158: contentHandler.endElement(Constants.WI_NS, VALUES_EL,
159: Constants.WI_PREFIX_COLON + VALUES_EL);
160:
161: // generate label, help, hint, etc.
162: definition.generateDisplayData(contentHandler);
163:
164: // the selection list (a MultiValueField has per definition always a SelectionList)
165: if (this .selectionList != null) {
166: this .selectionList.generateSaxFragment(contentHandler,
167: locale);
168: } else {
169: fieldDefinition.getSelectionList().generateSaxFragment(
170: contentHandler, locale);
171: }
172:
173: // validation message element
174: if (validationError != null) {
175: contentHandler.startElement(Constants.WI_NS,
176: VALIDATION_MSG_EL, Constants.WI_PREFIX_COLON
177: + VALIDATION_MSG_EL, Constants.EMPTY_ATTRS);
178: validationError.generateSaxFragment(contentHandler);
179: contentHandler.endElement(Constants.WI_NS,
180: VALIDATION_MSG_EL, Constants.WI_PREFIX_COLON
181: + VALIDATION_MSG_EL);
182: }
183:
184: contentHandler.endElement(Constants.WI_NS, MULTIVALUEFIELD_EL,
185: Constants.WI_PREFIX_COLON + MULTIVALUEFIELD_EL);
186: }
187:
188: public void generateLabel(ContentHandler contentHandler)
189: throws SAXException {
190: definition.generateLabel(contentHandler);
191: }
192:
193: public Object getValue() {
194: return values;
195: }
196:
197: public void setValue(Object value) {
198: if (value == null) {
199: setValues(new Object[0]);
200: } else if (value.getClass().isArray()) {
201: setValues((Object[]) value);
202: } else {
203: throw new RuntimeException("Cannot set value of field \""
204: + getFullyQualifiedId()
205: + "\" with an object of type "
206: + value.getClass().getName());
207: }
208: }
209:
210: public void setValues(Object[] values) {
211: // check that all the objects in the array correspond to the datatype
212: for (int i = 0; i < values.length; i++) {
213: if (!fieldDefinition.getDatatype().getTypeClass()
214: .isAssignableFrom(values[i].getClass()))
215: throw new RuntimeException(
216: "Cannot set value of field \""
217: + getFullyQualifiedId()
218: + "\" with an object of type "
219: + values[i].getClass().getName());
220: }
221: this .values = values;
222: }
223:
224: /**
225: * Set this field's selection list.
226: * @param selectionList The new selection list.
227: */
228: public void setSelectionList(SelectionList selectionList) {
229: if (selectionList != null
230: && selectionList.getDatatype() != null
231: && selectionList.getDatatype() != fieldDefinition
232: .getDatatype()) {
233:
234: throw new RuntimeException(
235: "Tried to assign a SelectionList that is not associated with this widget's datatype.");
236: }
237: this .selectionList = selectionList;
238: }
239:
240: /**
241: * Read this field's selection list from an external source.
242: * All Cocoon-supported protocols can be used.
243: * The format of the XML produced by the source should be the
244: * same as in case of inline specification of the selection list,
245: * thus the root element should be a <code>wd:selection-list</code>
246: * element.
247: * @param uri The URI of the source.
248: */
249: public void setSelectionList(String uri) {
250: setSelectionList(this .fieldDefinition.buildSelectionList(uri));
251: }
252:
253: /**
254: * Set this field's selection list using values from an in-memory
255: * object. The <code>object</code> parameter should point to a collection
256: * (Java collection or array, or Javascript array) of objects. Each object
257: * belonging to the collection should have a <em>value</em> property and a
258: * <em>label</em> property, whose values are used to specify the <code>value</code>
259: * attribute and the contents of the <code>wd:label</code> child element
260: * of every <code>wd:item</code> in the list.
261: * <p>Access to the values of the above mentioned properties is done
262: * via <a href="http://jakarta.apache.org/commons/jxpath/users-guide.html">XPath</a> expressions.
263: * @param model The collection used as a model for the selection list.
264: * @param valuePath An XPath expression referring to the attribute used
265: * to populate the values of the list's items.
266: * @param labelPath An XPath expression referring to the attribute used
267: * to populate the labels of the list's items.
268: */
269: public void setSelectionList(Object model, String valuePath,
270: String labelPath) {
271: setSelectionList(this .fieldDefinition
272: .buildSelectionListFromModel(model, valuePath,
273: labelPath));
274: }
275:
276: public void broadcastEvent(WidgetEvent event) {
277: this .fieldDefinition
278: .fireValueChangedEvent((ValueChangedEvent) event);
279: }
280:
281: public ValidationError getValidationError() {
282: return this .validationError;
283: }
284:
285: public void setValidationError(ValidationError error) {
286: this.validationError = error;
287: }
288: }
|