/* * 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 org.apache.cocoon.woody.datatype.SelectionList; import org.apache.cocoon.woody.validation.ValidationError; import org.apache.cocoon.woody.validation.ValidationErrorAware; import org.apache.cocoon.woody.event.WidgetEvent; import org.apache.cocoon.woody.event.ValueChangedEvent; import org.apache.cocoon.woody.Constants; import org.apache.cocoon.woody.FormContext; import org.apache.cocoon.woody.util.I18nMessage; import org.apache.cocoon.xml.AttributesImpl; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import java.util.Locale; /** * A MultiValueField is mostly the same as a normal {@link Field}, but can * hold multiple values. A MultiValueField should have a Datatype which * has a SelectionList, because the user will always select the values * from a list. A MultiValueField has no concept of "required", you should * instead use the ValueCountValidationRule to check how many items the user * has selected. * * <p>A MultiValueField also has a {@link org.apache.cocoon.woody.datatype.Datatype Datatype} * associated with it. In case of MultiValueFields, this Datatype will always be an array * type, thus {@link org.apache.cocoon.woody.datatype.Datatype#isArrayType()} will * always return true, and this in return has an influence on the kind of validation rules that * can be used with the Datatype (see {@link org.apache.cocoon.woody.datatype.Datatype Datatype} * description for more information). * * @version $Id$ */ public class MultiValueField extends AbstractWidget implements ValidationErrorAware, SelectableWidget { private SelectionList selectionList; private MultiValueFieldDefinition fieldDefinition; private String[] enteredValues; private Object[] values; private ValidationError validationError; public MultiValueField(MultiValueFieldDefinition definition) { super.setDefinition(definition); this.fieldDefinition = definition; setLocation(definition.getLocation()); } public String getId() { return definition.getId(); } public void readFromRequest(FormContext formContext) { enteredValues = formContext.getRequest().getParameterValues(getFullyQualifiedId()); validationError = null; values = null; boolean conversionFailed = false; if (enteredValues != null) { // Normally, for MultiValueFields, the user selects the values from // a SelectionList, and the values in a SelectionList are garanteed to // be valid, so the conversion from String to native datatype should // never fail. But it could fail if users start messing around with // request parameters. Object[] tempValues = new Object[enteredValues.length]; for (int i = 0; i < enteredValues.length; i++) { String param = enteredValues[i]; tempValues[i] = fieldDefinition.getDatatype().convertFromString(param, formContext.getLocale()); if (tempValues[i] == null) { conversionFailed = true; break; } } if (!conversionFailed) values = tempValues; else values = null; } else { values = new Object[0]; } } public boolean validate(FormContext formContext) { if (values != null) validationError = fieldDefinition.getDatatype().validate(values, new ExpressionContextImpl(this)); else validationError = new ValidationError(new I18nMessage("multivaluefield.conversionfailed", Constants.I18N_CATALOGUE)); return validationError == null ? super.validate(formContext) : false; } private static final String MULTIVALUEFIELD_EL = "multivaluefield"; private static final String VALUES_EL = "values"; private static final String VALUE_EL = "value"; private static final String VALIDATION_MSG_EL = "validation-message"; public void generateSaxFragment(ContentHandler contentHandler, Locale locale) throws SAXException { AttributesImpl attrs = new AttributesImpl(); attrs.addCDATAAttribute("id", getFullyQualifiedId()); contentHandler.startElement(Constants.WI_NS, MULTIVALUEFIELD_EL, Constants.WI_PREFIX_COLON + MULTIVALUEFIELD_EL, attrs); contentHandler.startElement(Constants.WI_NS, VALUES_EL, Constants.WI_PREFIX_COLON + VALUES_EL, Constants.EMPTY_ATTRS); if (values != null) { for (int i = 0; i < values.length; i++) { contentHandler.startElement(Constants.WI_NS, VALUE_EL, Constants.WI_PREFIX_COLON + VALUE_EL, Constants.EMPTY_ATTRS); String value = fieldDefinition.getDatatype().getPlainConvertor().convertToString(values[i], locale, null); contentHandler.characters(value.toCharArray(), 0, value.length()); contentHandler.endElement(Constants.WI_NS, VALUE_EL, Constants.WI_PREFIX_COLON + VALUE_EL); } } else if (enteredValues != null) { for (int i = 0; i < enteredValues.length; i++) { contentHandler.startElement(Constants.WI_NS, VALUE_EL, Constants.WI_PREFIX_COLON + VALUE_EL, Constants.EMPTY_ATTRS); String value = fieldDefinition.getDatatype().getPlainConvertor().convertToString(enteredValues[i], locale, null); contentHandler.characters(value.toCharArray(), 0, value.length()); contentHandler.endElement(Constants.WI_NS, VALUE_EL, Constants.WI_PREFIX_COLON + VALUE_EL); } } contentHandler.endElement(Constants.WI_NS, VALUES_EL, Constants.WI_PREFIX_COLON + VALUES_EL); // generate label, help, hint, etc. definition.generateDisplayData(contentHandler); // the selection list (a MultiValueField has per definition always a SelectionList) if (this.selectionList != null) { this.selectionList.generateSaxFragment(contentHandler, locale); } else { fieldDefinition.getSelectionList().generateSaxFragment(contentHandler, locale); } // validation message element if (validationError != null) { contentHandler.startElement(Constants.WI_NS, VALIDATION_MSG_EL, Constants.WI_PREFIX_COLON + VALIDATION_MSG_EL, Constants.EMPTY_ATTRS); validationError.generateSaxFragment(contentHandler); contentHandler.endElement(Constants.WI_NS, VALIDATION_MSG_EL, Constants.WI_PREFIX_COLON + VALIDATION_MSG_EL); } contentHandler.endElement(Constants.WI_NS, MULTIVALUEFIELD_EL, Constants.WI_PREFIX_COLON + MULTIVALUEFIELD_EL); } public void generateLabel(ContentHandler contentHandler) throws SAXException { definition.generateLabel(contentHandler); } public Object getValue() { return values; } public void setValue(Object value) { if (value == null) { setValues(new Object[0]); } else if (value.getClass().isArray()) { setValues((Object[])value); } else { throw new RuntimeException("Cannot set value of field \"" + getFullyQualifiedId() + "\" with an object of type " + value.getClass().getName()); } } public void setValues(Object[] values) { // check that all the objects in the array correspond to the datatype for (int i = 0; i < values.length; i++) { if (!fieldDefinition.getDatatype().getTypeClass().isAssignableFrom(values[i].getClass())) throw new RuntimeException("Cannot set value of field \"" + getFullyQualifiedId() + "\" with an object of type " + values[i].getClass().getName()); } this.values = values; } /** * Set this field's selection list. * @param selectionList The new selection list. */ public void setSelectionList(SelectionList selectionList) { if (selectionList != null && selectionList.getDatatype() != null && selectionList.getDatatype() != fieldDefinition.getDatatype()) { throw new RuntimeException("Tried to assign a SelectionList that is not associated with this widget's datatype."); } this.selectionList = selectionList; } /** * Read this field's selection list from an external source. * All Cocoon-supported protocols can be used. * The format of the XML produced by the source should be the * same as in case of inline specification of the selection list, * thus the root element should be a <code>wd:selection-list</code> * element. * @param uri The URI of the source. */ public void setSelectionList(String uri) { setSelectionList(this.fieldDefinition.buildSelectionList(uri)); } /** * Set this field's selection list using values from an in-memory * object. The <code>object</code> parameter should point to a collection * (Java collection or array, or Javascript array) of objects. Each object * belonging to the collection should have a <em>value</em> property and a * <em>label</em> property, whose values are used to specify the <code>value</code> * attribute and the contents of the <code>wd:label</code> child element * of every <code>wd:item</code> in the list. * <p>Access to the values of the above mentioned properties is done * via <a href="http://jakarta.apache.org/commons/jxpath/users-guide.html">XPath</a> expressions. * @param model The collection used as a model for the selection list. * @param valuePath An XPath expression referring to the attribute used * to populate the values of the list's items. * @param labelPath An XPath expression referring to the attribute used * to populate the labels of the list's items. */ public void setSelectionList(Object model, String valuePath, String labelPath) { setSelectionList(this.fieldDefinition.buildSelectionListFromModel(model, valuePath, labelPath)); } public void broadcastEvent(WidgetEvent event) { this.fieldDefinition.fireValueChangedEvent((ValueChangedEvent)event); } public ValidationError getValidationError() { return this.validationError; } public void setValidationError(ValidationError error) { this.validationError = error; } }