/* * 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.forms.formmodel; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.excalibur.xml.sax.XMLizable; import org.apache.cocoon.forms.FormContext; import org.apache.cocoon.forms.FormsConstants; import org.apache.cocoon.forms.formmodel.AggregateFieldDefinition.SplitMapping; import org.apache.cocoon.forms.util.I18nMessage; import org.apache.cocoon.forms.validation.ValidationError; 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; /** * 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 $Id$ */ public class AggregateField extends Field implements ContainerWidget { private static final String AGGREGATEFIELD_EL = "aggregatefield"; /** * 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)getDefinition(); } public void initialize() { this.selectionList = getAggregateFieldDefinition().getSelectionList(); Iterator it = this.getChildren(); while(it.hasNext()) { ((Widget)it.next()).initialize(); } } public void addChild(Widget widget) { if (!(widget instanceof Field)) { throw new IllegalArgumentException("AggregateField can only contain fields."); } addField((Field) widget); } protected void addField(Field field) { field.setParent(this); fields.add(field); fieldsById.put(field.getId(), field); } public boolean hasChild(String id) { return this.fieldsById.containsKey(id); } public Iterator getChildren() { return fields.iterator(); } public void readFromRequest(FormContext formContext) { if (!getCombinedState().isAcceptingInputs()) { return; } String newEnteredValue = formContext.getRequest().getParameter(getRequestParameterName()); if (newEnteredValue != null) { // There is one aggregated entered value. Read it and split it. super.readFromRequest(formContext); if (this.valueState == VALUE_UNPARSED) { setFieldsValues(enteredValue); } } else { // Check if there are multiple splitted values. Read them and aggregate them. for (Iterator i = fields.iterator(); i.hasNext();) { Field field = (Field)i.next(); field.readFromRequest(formContext); if (field.valueState == VALUE_UNPARSED) { this.valueState = VALUE_UNPARSED; } } if (this.valueState == VALUE_UNPARSED) { combineFields(); } } } public void setValue(Object newValue) { super.setValue(newValue); if (this.valueState == VALUE_PARSED) { 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() { if (!getCombinedState().isValidatingValues()) { this.wasValid = true; return true; } 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() }, FormsConstants.I18N_CATALOGUE)); } valueState = VALUE_DISPLAY_VALIDATION; this.wasValid = false; return false; } // Validate ALL my child fields boolean valid = true; for (Iterator i = fields.iterator(); i.hasNext();) { Field field = (Field)i.next(); if (!field.validate()) { validationError = field.getValidationError(); valid = false; } } if (!valid) { valueState = VALUE_DISPLAY_VALIDATION; this.wasValid = false; return false; } return super.validate(); } /** * @return "aggregatefield" */ public String getXMLElementName() { return AGGREGATEFIELD_EL; } public Widget getChild(String id) { return (Widget) fieldsById.get(id); } }