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.woody.formmodel;
018:
019: import org.apache.cocoon.woody.Constants;
020: import org.apache.cocoon.woody.FormContext;
021: import org.apache.cocoon.woody.formmodel.AggregateFieldDefinition.SplitMapping;
022: import org.apache.cocoon.woody.util.I18nMessage;
023: import org.apache.cocoon.woody.validation.ValidationError;
024: import org.apache.cocoon.xml.AttributesImpl;
025: import org.apache.excalibur.xml.sax.XMLizable;
026: import org.apache.oro.text.regex.MatchResult;
027: import org.apache.oro.text.regex.PatternMatcher;
028: import org.apache.oro.text.regex.Perl5Matcher;
029:
030: import org.outerj.expression.ExpressionException;
031: import org.xml.sax.ContentHandler;
032: import org.xml.sax.SAXException;
033:
034: import java.util.ArrayList;
035: import java.util.HashMap;
036: import java.util.Iterator;
037: import java.util.List;
038: import java.util.Locale;
039: import java.util.Map;
040:
041: /**
042: * An aggregated field allows to represent one value as multiple input fields, or several values
043: * as one field. Hence this widget is a field and a container widget simultaneously.
044: *
045: * <p>Upon submit, it first attempts to read own value from the request, and splits over nested
046: * field widgets using a regular expression. If split fails, this will simply give a validation error.
047: * If own value was not submitted, it attempts to read values for nested field widgets, and combines
048: * theirs values using combine expression.
049: *
050: * <p>To validate this widget, both the validation rules of the nested widgets are
051: * checked, and those of the aggregated field themselves. The validation rules of the aggregated
052: * field can perform checks on the string as entered by the user (e.g. check its total length).
053: *
054: * <p>This field and nested fields can be of any supported type, as long as combine expression
055: * gives result of the correct type, and split regular expression can split string representation
056: * into parts which can be converted to the values of nested fields.
057: *
058: * @version CVS $Id: AggregateField.java 433543 2006-08-22 06:22:54Z crossley $
059: */
060: public class AggregateField extends Field {
061:
062: /**
063: * List of nested fields
064: */
065: private List fields = new ArrayList();
066:
067: /**
068: * Map of nested fields
069: */
070: private Map fieldsById = new HashMap();
071:
072: public AggregateField(AggregateFieldDefinition definition) {
073: super (definition);
074: }
075:
076: public final AggregateFieldDefinition getAggregateFieldDefinition() {
077: return (AggregateFieldDefinition) super .definition;
078: }
079:
080: protected void addField(Field field) {
081: field.setParent(this );
082: fields.add(field);
083: fieldsById.put(field.getId(), field);
084: }
085:
086: public Iterator getChildren() {
087: return fields.iterator();
088: }
089:
090: public void readFromRequest(FormContext formContext) {
091: String newEnteredValue = formContext.getRequest().getParameter(
092: getFullyQualifiedId());
093: if (newEnteredValue != null) {
094: // There is one aggregated entered value. Read it and split it.
095: super .readFromRequest(formContext);
096: if (needsParse) {
097: setFieldsValues(enteredValue);
098: }
099: } else {
100: // Check if there are multiple splitted values. Read them and aggregate them.
101: boolean needsParse = false;
102: for (Iterator i = fields.iterator(); i.hasNext();) {
103: Field field = (Field) i.next();
104: field.readFromRequest(formContext);
105: needsParse |= field.needsParse;
106: }
107: if (needsParse) {
108: combineFields();
109: }
110: }
111: }
112:
113: public void setValue(Object newValue) {
114: super .setValue(newValue);
115: if (needsValidate) {
116: setFieldsValues(enteredValue);
117: }
118: }
119:
120: /**
121: * Returns false if all fields have no value.
122: */
123: private boolean fieldsHaveValues() {
124: for (Iterator i = fields.iterator(); i.hasNext();) {
125: Field field = (Field) i.next();
126: if (field.getValue() != null) {
127: return true;
128: }
129: }
130: return false;
131: }
132:
133: /**
134: * Splits passed value and sets values of all nested fields.
135: * If split fails, resets all fields.
136: */
137: private void setFieldsValues(String value) {
138: if (value == null) {
139: resetFieldsValues();
140: } else {
141: PatternMatcher matcher = new Perl5Matcher();
142: if (matcher.matches(value, getAggregateFieldDefinition()
143: .getSplitPattern())) {
144: MatchResult matchResult = matcher.getMatch();
145: Iterator iterator = getAggregateFieldDefinition()
146: .getSplitMappingsIterator();
147: while (iterator.hasNext()) {
148: SplitMapping splitMapping = (SplitMapping) iterator
149: .next();
150: String result = matchResult.group(splitMapping
151: .getGroup());
152:
153: // Fields can have a non-string datatype, going to the readFromRequest
154: Field field = (Field) fieldsById.get(splitMapping
155: .getFieldId());
156: field.readFromRequest(result);
157: }
158: } else {
159: resetFieldsValues();
160: }
161: }
162: }
163:
164: public void combineFields() {
165: try {
166: Object value = getAggregateFieldDefinition()
167: .getCombineExpression().evaluate(
168: new ExpressionContextImpl(this , true));
169: super .setValue(value);
170: } catch (CannotYetResolveWarning e) {
171: super .setValue(null);
172: } catch (ExpressionException e) {
173: super .setValue(null);
174: } catch (ClassCastException e) {
175: super .setValue(null);
176: }
177: }
178:
179: /**
180: * Sets values of all nested fields to null
181: */
182: private void resetFieldsValues() {
183: for (Iterator i = fields.iterator(); i.hasNext();) {
184: Field field = (Field) i.next();
185: field.setValue(null);
186: }
187: }
188:
189: public boolean validate(FormContext formContext) {
190: if ((enteredValue != null) != fieldsHaveValues()) {
191: XMLizable failMessage = getAggregateFieldDefinition()
192: .getSplitFailMessage();
193: if (failMessage != null) {
194: validationError = new ValidationError(failMessage);
195: } else {
196: validationError = new ValidationError(new I18nMessage(
197: "aggregatedfield.split-failed",
198: new String[] { getAggregateFieldDefinition()
199: .getSplitRegexp() },
200: Constants.I18N_CATALOGUE));
201: }
202: return false;
203: }
204:
205: // validate my child fields
206: for (Iterator i = fields.iterator(); i.hasNext();) {
207: Field field = (Field) i.next();
208: if (!field.validate(formContext)) {
209: validationError = field.getValidationError();
210: return false;
211: }
212: }
213:
214: return super .validate(formContext);
215: }
216:
217: private static final String AGGREGATEFIELD_EL = "aggregatefield";
218: private static final String VALUE_EL = "value";
219: private static final String VALIDATION_MSG_EL = "validation-message";
220:
221: public void generateSaxFragment(ContentHandler contentHandler,
222: Locale locale) throws SAXException {
223: AttributesImpl aggregatedFieldAttrs = new AttributesImpl();
224: aggregatedFieldAttrs.addCDATAAttribute("id",
225: getFullyQualifiedId());
226: aggregatedFieldAttrs.addCDATAAttribute("required", String
227: .valueOf(getAggregateFieldDefinition().isRequired()));
228: contentHandler.startElement(Constants.WI_NS, AGGREGATEFIELD_EL,
229: Constants.WI_PREFIX_COLON + AGGREGATEFIELD_EL,
230: aggregatedFieldAttrs);
231:
232: if (enteredValue != null || value != null) {
233: contentHandler.startElement(Constants.WI_NS, VALUE_EL,
234: Constants.WI_PREFIX_COLON + VALUE_EL,
235: Constants.EMPTY_ATTRS);
236: String stringValue;
237: if (value != null) {
238: stringValue = getDatatype().convertToString(value,
239: locale);
240: } else {
241: stringValue = enteredValue;
242: }
243: contentHandler.characters(stringValue.toCharArray(), 0,
244: stringValue.length());
245: contentHandler.endElement(Constants.WI_NS, VALUE_EL,
246: Constants.WI_PREFIX_COLON + VALUE_EL);
247: }
248:
249: // validation message element: only present if the value is not valid
250: if (validationError != null) {
251: contentHandler.startElement(Constants.WI_NS,
252: VALIDATION_MSG_EL, Constants.WI_PREFIX_COLON
253: + VALIDATION_MSG_EL, Constants.EMPTY_ATTRS);
254: validationError.generateSaxFragment(contentHandler);
255: contentHandler.endElement(Constants.WI_NS,
256: VALIDATION_MSG_EL, Constants.WI_PREFIX_COLON
257: + VALIDATION_MSG_EL);
258: }
259:
260: // generate label, help, hint, etc.
261: definition.generateDisplayData(contentHandler);
262:
263: // generate selection list, if any
264: if (selectionList != null) {
265: selectionList.generateSaxFragment(contentHandler, locale);
266: } else if (getFieldDefinition().getSelectionList() != null) {
267: getFieldDefinition().getSelectionList()
268: .generateSaxFragment(contentHandler, locale);
269: }
270: contentHandler.endElement(Constants.WI_NS, AGGREGATEFIELD_EL,
271: Constants.WI_PREFIX_COLON + AGGREGATEFIELD_EL);
272: }
273:
274: public void generateLabel(ContentHandler contentHandler)
275: throws SAXException {
276: definition.generateLabel(contentHandler);
277: }
278:
279: public Widget getWidget(String id) {
280: return (Widget) fieldsById.get(id);
281: }
282: }
|