blob: e0a4a2fe14410220c977204643784b4c48ccaa04 [file] [log] [blame]
/*
* 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;
}
}