001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.forms.formmodel;
018:
019: import java.util.ArrayList;
020: import java.util.HashMap;
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.Map;
024:
025: import org.apache.excalibur.xml.sax.XMLizable;
026:
027: import org.apache.cocoon.forms.FormContext;
028: import org.apache.cocoon.forms.FormsConstants;
029: import org.apache.cocoon.forms.formmodel.AggregateFieldDefinition.SplitMapping;
030: import org.apache.cocoon.forms.util.I18nMessage;
031: import org.apache.cocoon.forms.validation.ValidationError;
032:
033: import org.apache.oro.text.regex.MatchResult;
034: import org.apache.oro.text.regex.PatternMatcher;
035: import org.apache.oro.text.regex.Perl5Matcher;
036: import org.outerj.expression.ExpressionException;
037:
038: /**
039: * An aggregated field allows to represent one value as multiple input fields, or several values
040: * as one field. Hence this widget is a field and a container widget simultaneously.
041: *
042: * <p>Upon submit, it first attempts to read own value from the request, and splits over nested
043: * field widgets using a regular expression. If split fails, this will simply give a validation error.
044: * If own value was not submitted, it attempts to read values for nested field widgets, and combines
045: * theirs values using combine expression.
046: *
047: * <p>To validate this widget, both the validation rules of the nested widgets are
048: * checked, and those of the aggregated field themselves. The validation rules of the aggregated
049: * field can perform checks on the string as entered by the user (e.g. check its total length).
050: *
051: * <p>This field and nested fields can be of any supported type, as long as combine expression
052: * gives result of the correct type, and split regular expression can split string representation
053: * into parts which can be converted to the values of nested fields.
054: *
055: * @version $Id: AggregateField.java 462520 2006-10-10 19:39:14Z vgritsenko $
056: */
057: public class AggregateField extends Field implements ContainerWidget {
058:
059: private static final String AGGREGATEFIELD_EL = "aggregatefield";
060:
061: /**
062: * List of nested fields
063: */
064: private List fields = new ArrayList();
065:
066: /**
067: * Map of nested fields
068: */
069: private Map fieldsById = new HashMap();
070:
071: public AggregateField(AggregateFieldDefinition definition) {
072: super (definition);
073: }
074:
075: public final AggregateFieldDefinition getAggregateFieldDefinition() {
076: return (AggregateFieldDefinition) getDefinition();
077: }
078:
079: public void initialize() {
080: this .selectionList = getAggregateFieldDefinition()
081: .getSelectionList();
082:
083: Iterator it = this .getChildren();
084: while (it.hasNext()) {
085: ((Widget) it.next()).initialize();
086: }
087: }
088:
089: public void addChild(Widget widget) {
090: if (!(widget instanceof Field)) {
091: throw new IllegalArgumentException(
092: "AggregateField can only contain fields.");
093: }
094: addField((Field) widget);
095: }
096:
097: protected void addField(Field field) {
098: field.setParent(this );
099: fields.add(field);
100: fieldsById.put(field.getId(), field);
101: }
102:
103: public boolean hasChild(String id) {
104: return this .fieldsById.containsKey(id);
105: }
106:
107: public Iterator getChildren() {
108: return fields.iterator();
109: }
110:
111: public void readFromRequest(FormContext formContext) {
112: if (!getCombinedState().isAcceptingInputs()) {
113: return;
114: }
115:
116: String newEnteredValue = formContext.getRequest().getParameter(
117: getRequestParameterName());
118: if (newEnteredValue != null) {
119: // There is one aggregated entered value. Read it and split it.
120: super .readFromRequest(formContext);
121: if (this .valueState == VALUE_UNPARSED) {
122: setFieldsValues(enteredValue);
123: }
124: } else {
125: // Check if there are multiple splitted values. Read them and aggregate them.
126: for (Iterator i = fields.iterator(); i.hasNext();) {
127: Field field = (Field) i.next();
128: field.readFromRequest(formContext);
129: if (field.valueState == VALUE_UNPARSED) {
130: this .valueState = VALUE_UNPARSED;
131: }
132: }
133: if (this .valueState == VALUE_UNPARSED) {
134: combineFields();
135: }
136: }
137: }
138:
139: public void setValue(Object newValue) {
140: super .setValue(newValue);
141: if (this .valueState == VALUE_PARSED) {
142: setFieldsValues(enteredValue);
143: }
144: }
145:
146: /**
147: * Returns false if all fields have no value.
148: */
149: private boolean fieldsHaveValues() {
150: for (Iterator i = fields.iterator(); i.hasNext();) {
151: Field field = (Field) i.next();
152: if (field.getValue() != null) {
153: return true;
154: }
155: }
156: return false;
157: }
158:
159: /**
160: * Splits passed value and sets values of all nested fields.
161: * If split fails, resets all fields.
162: */
163: private void setFieldsValues(String value) {
164: if (value == null) {
165: resetFieldsValues();
166: } else {
167: PatternMatcher matcher = new Perl5Matcher();
168: if (matcher.matches(value, getAggregateFieldDefinition()
169: .getSplitPattern())) {
170: MatchResult matchResult = matcher.getMatch();
171: Iterator iterator = getAggregateFieldDefinition()
172: .getSplitMappingsIterator();
173: while (iterator.hasNext()) {
174: SplitMapping splitMapping = (SplitMapping) iterator
175: .next();
176: String result = matchResult.group(splitMapping
177: .getGroup());
178:
179: // Fields can have a non-string datatype, going to the readFromRequest
180: Field field = (Field) fieldsById.get(splitMapping
181: .getFieldId());
182: field.readFromRequest(result);
183: }
184: } else {
185: resetFieldsValues();
186: }
187: }
188: }
189:
190: public void combineFields() {
191: try {
192: Object value = getAggregateFieldDefinition()
193: .getCombineExpression().evaluate(
194: new ExpressionContextImpl(this , true));
195: super .setValue(value);
196: } catch (CannotYetResolveWarning e) {
197: super .setValue(null);
198: } catch (ExpressionException e) {
199: super .setValue(null);
200: } catch (ClassCastException e) {
201: super .setValue(null);
202: }
203: }
204:
205: /**
206: * Sets values of all nested fields to null
207: */
208: private void resetFieldsValues() {
209: for (Iterator i = fields.iterator(); i.hasNext();) {
210: Field field = (Field) i.next();
211: field.setValue(null);
212: }
213: }
214:
215: public boolean validate() {
216: if (!getCombinedState().isValidatingValues()) {
217: this .wasValid = true;
218: return true;
219: }
220:
221: if (enteredValue != null && !fieldsHaveValues()) {
222: XMLizable failMessage = getAggregateFieldDefinition()
223: .getSplitFailMessage();
224: if (failMessage != null) {
225: validationError = new ValidationError(failMessage);
226: } else {
227: validationError = new ValidationError(new I18nMessage(
228: "aggregatedfield.split-failed",
229: new String[] { getAggregateFieldDefinition()
230: .getSplitRegexp() },
231: FormsConstants.I18N_CATALOGUE));
232: }
233: valueState = VALUE_DISPLAY_VALIDATION;
234: this .wasValid = false;
235: return false;
236: }
237:
238: // Validate ALL my child fields
239: boolean valid = true;
240: for (Iterator i = fields.iterator(); i.hasNext();) {
241: Field field = (Field) i.next();
242: if (!field.validate()) {
243: validationError = field.getValidationError();
244: valid = false;
245: }
246: }
247: if (!valid) {
248: valueState = VALUE_DISPLAY_VALIDATION;
249: this .wasValid = false;
250: return false;
251: }
252:
253: return super .validate();
254: }
255:
256: /**
257: * @return "aggregatefield"
258: */
259: public String getXMLElementName() {
260: return AGGREGATEFIELD_EL;
261: }
262:
263: public Widget getChild(String id) {
264: return (Widget) fieldsById.get(id);
265: }
266: }
|