blob: 0c58902f707363ebee7589f7e3373c4de3e73b9f [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.Constants;
import org.apache.cocoon.woody.FormContext;
import org.apache.cocoon.woody.formmodel.AggregateFieldDefinition.SplitMapping;
import org.apache.cocoon.woody.util.I18nMessage;
import org.apache.cocoon.woody.validation.ValidationError;
import org.apache.cocoon.xml.AttributesImpl;
import org.apache.excalibur.xml.sax.XMLizable;
import org.apache.oro.text.regex.MatchResult;
import org.apache.oro.text.regex.PatternMatcher;
import org.apache.oro.text.regex.Perl5Matcher;
import org.outerj.expression.ExpressionException;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* An aggregated field allows to represent one value as multiple input fields, or several values
* as one field. Hence this widget is a field and a container widget simultaneously.
*
* <p>Upon submit, it first attempts to read own value from the request, and splits over nested
* field widgets using a regular expression. If split fails, this will simply give a validation error.
* If own value was not submitted, it attempts to read values for nested field widgets, and combines
* theirs values using combine expression.
*
* <p>To validate this widget, both the validation rules of the nested widgets are
* checked, and those of the aggregated field themselves. The validation rules of the aggregated
* field can perform checks on the string as entered by the user (e.g. check its total length).
*
* <p>This field and nested fields can be of any supported type, as long as combine expression
* gives result of the correct type, and split regular expression can split string representation
* into parts which can be converted to the values of nested fields.
*
* @version CVS $Id$
*/
public class AggregateField extends Field {
/**
* List of nested fields
*/
private List fields = new ArrayList();
/**
* Map of nested fields
*/
private Map fieldsById = new HashMap();
public AggregateField(AggregateFieldDefinition definition) {
super(definition);
}
public final AggregateFieldDefinition getAggregateFieldDefinition() {
return (AggregateFieldDefinition)super.definition;
}
protected void addField(Field field) {
field.setParent(this);
fields.add(field);
fieldsById.put(field.getId(), field);
}
public Iterator getChildren() {
return fields.iterator();
}
public void readFromRequest(FormContext formContext) {
String newEnteredValue = formContext.getRequest().getParameter(getFullyQualifiedId());
if (newEnteredValue != null) {
// There is one aggregated entered value. Read it and split it.
super.readFromRequest(formContext);
if (needsParse) {
setFieldsValues(enteredValue);
}
} else {
// Check if there are multiple splitted values. Read them and aggregate them.
boolean needsParse = false;
for (Iterator i = fields.iterator(); i.hasNext();) {
Field field = (Field)i.next();
field.readFromRequest(formContext);
needsParse |= field.needsParse;
}
if (needsParse) {
combineFields();
}
}
}
public void setValue(Object newValue) {
super.setValue(newValue);
if (needsValidate) {
setFieldsValues(enteredValue);
}
}
/**
* Returns false if all fields have no value.
*/
private boolean fieldsHaveValues() {
for (Iterator i = fields.iterator(); i.hasNext();) {
Field field = (Field)i.next();
if (field.getValue() != null) {
return true;
}
}
return false;
}
/**
* Splits passed value and sets values of all nested fields.
* If split fails, resets all fields.
*/
private void setFieldsValues(String value) {
if (value == null) {
resetFieldsValues();
} else {
PatternMatcher matcher = new Perl5Matcher();
if (matcher.matches(value, getAggregateFieldDefinition().getSplitPattern())) {
MatchResult matchResult = matcher.getMatch();
Iterator iterator = getAggregateFieldDefinition().getSplitMappingsIterator();
while (iterator.hasNext()) {
SplitMapping splitMapping = (SplitMapping)iterator.next();
String result = matchResult.group(splitMapping.getGroup());
// Fields can have a non-string datatype, going to the readFromRequest
Field field = (Field)fieldsById.get(splitMapping.getFieldId());
field.readFromRequest(result);
}
} else {
resetFieldsValues();
}
}
}
public void combineFields() {
try {
Object value = getAggregateFieldDefinition().getCombineExpression().evaluate(new ExpressionContextImpl(this, true));
super.setValue(value);
} catch (CannotYetResolveWarning e) {
super.setValue(null);
} catch (ExpressionException e) {
super.setValue(null);
} catch (ClassCastException e) {
super.setValue(null);
}
}
/**
* Sets values of all nested fields to null
*/
private void resetFieldsValues() {
for (Iterator i = fields.iterator(); i.hasNext();) {
Field field = (Field)i.next();
field.setValue(null);
}
}
public boolean validate(FormContext formContext) {
if ((enteredValue != null) != fieldsHaveValues()) {
XMLizable failMessage = getAggregateFieldDefinition().getSplitFailMessage();
if (failMessage != null) {
validationError = new ValidationError(failMessage);
} else {
validationError = new ValidationError(new I18nMessage("aggregatedfield.split-failed",
new String[] { getAggregateFieldDefinition().getSplitRegexp() },
Constants.I18N_CATALOGUE));
}
return false;
}
// validate my child fields
for (Iterator i = fields.iterator(); i.hasNext();) {
Field field = (Field)i.next();
if (!field.validate(formContext)) {
validationError = field.getValidationError();
return false;
}
}
return super.validate(formContext);
}
private static final String AGGREGATEFIELD_EL = "aggregatefield";
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 aggregatedFieldAttrs = new AttributesImpl();
aggregatedFieldAttrs.addCDATAAttribute("id", getFullyQualifiedId());
aggregatedFieldAttrs.addCDATAAttribute("required", String.valueOf(getAggregateFieldDefinition().isRequired()));
contentHandler.startElement(Constants.WI_NS, AGGREGATEFIELD_EL, Constants.WI_PREFIX_COLON + AGGREGATEFIELD_EL, aggregatedFieldAttrs);
if (enteredValue != null || value != null) {
contentHandler.startElement(Constants.WI_NS, VALUE_EL, Constants.WI_PREFIX_COLON + VALUE_EL, Constants.EMPTY_ATTRS);
String stringValue;
if (value != null) {
stringValue = getDatatype().convertToString(value, locale);
} else {
stringValue = enteredValue;
}
contentHandler.characters(stringValue.toCharArray(), 0, stringValue.length());
contentHandler.endElement(Constants.WI_NS, VALUE_EL, Constants.WI_PREFIX_COLON + VALUE_EL);
}
// validation message element: only present if the value is not valid
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);
}
// generate label, help, hint, etc.
definition.generateDisplayData(contentHandler);
// generate selection list, if any
if (selectionList != null) {
selectionList.generateSaxFragment(contentHandler, locale);
} else if (getFieldDefinition().getSelectionList() != null) {
getFieldDefinition().getSelectionList().generateSaxFragment(contentHandler, locale);
}
contentHandler.endElement(Constants.WI_NS, AGGREGATEFIELD_EL, Constants.WI_PREFIX_COLON + AGGREGATEFIELD_EL);
}
public void generateLabel(ContentHandler contentHandler) throws SAXException {
definition.generateLabel(contentHandler);
}
public Widget getWidget(String id) {
return (Widget)fieldsById.get(id);
}
}