| /* |
| * 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); |
| } |
| } |