001: // Copyright 2006, 2007 The Apache Software Foundation
002: //
003: // Licensed under the Apache License, Version 2.0 (the "License");
004: // you may not use this file except in compliance with the License.
005: // You may obtain a copy of the License at
006: //
007: // http://www.apache.org/licenses/LICENSE-2.0
008: //
009: // Unless required by applicable law or agreed to in writing, software
010: // distributed under the License is distributed on an "AS IS" BASIS,
011: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: // See the License for the specific language governing permissions and
013: // limitations under the License.
014:
015: package org.apache.tapestry.internal.services;
016:
017: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
018: import static org.apache.tapestry.ioc.internal.util.Defense.cast;
019: import static org.apache.tapestry.ioc.internal.util.Defense.notBlank;
020:
021: import java.util.List;
022: import java.util.Locale;
023: import java.util.Map;
024:
025: import org.apache.tapestry.ComponentResources;
026: import org.apache.tapestry.Field;
027: import org.apache.tapestry.FieldValidator;
028: import org.apache.tapestry.PageRenderSupport;
029: import org.apache.tapestry.Validator;
030: import org.apache.tapestry.ioc.MessageFormatter;
031: import org.apache.tapestry.ioc.Messages;
032: import org.apache.tapestry.ioc.internal.util.InternalUtils;
033: import org.apache.tapestry.ioc.services.TypeCoercer;
034: import org.apache.tapestry.runtime.Component;
035: import org.apache.tapestry.services.FieldValidatorSource;
036: import org.apache.tapestry.services.ValidationMessagesSource;
037:
038: public class FieldValidatorSourceImpl implements FieldValidatorSource {
039: private final ValidationMessagesSource _messagesSource;
040:
041: private final Map<String, Validator> _validators;
042:
043: private final TypeCoercer _typeCoercer;
044:
045: private final PageRenderSupport _pageRenderSupport;
046:
047: public FieldValidatorSourceImpl(
048: ValidationMessagesSource messagesSource,
049: TypeCoercer typeCoercer,
050: PageRenderSupport pageRenderSupport,
051: Map<String, Validator> validators) {
052: _messagesSource = messagesSource;
053: _typeCoercer = typeCoercer;
054: _pageRenderSupport = pageRenderSupport;
055: _validators = validators;
056: }
057:
058: public FieldValidator createValidator(Field field,
059: String validatorType, String constraintValue) {
060: Component component = cast(field, Component.class, "field");
061: notBlank(validatorType, "validatorType");
062:
063: ComponentResources componentResources = component
064: .getComponentResources();
065: String overrideId = componentResources.getId();
066: Locale locale = componentResources.getLocale();
067:
068: // So, if you use a TextField on your EditUser page, we want to search the messages
069: // of the EditUser page (the container), not the TextField (which will always be the same).
070:
071: Messages overrideMessages = componentResources
072: .getContainerMessages();
073:
074: return createValidator(field, validatorType, constraintValue,
075: overrideId, overrideMessages, locale);
076: }
077:
078: public FieldValidator createValidator(Field field,
079: String validatorType, String constraintValue,
080: String overrideId, Messages overrideMessages, Locale locale) {
081: notBlank(validatorType, "validatorType");
082:
083: Validator validator = _validators.get(validatorType);
084:
085: if (validator == null)
086: throw new IllegalArgumentException(ServicesMessages
087: .unknownValidatorType(validatorType, InternalUtils
088: .sortedKeys(_validators)));
089:
090: // I just have this thing about always treating parameters as finals, so
091: // we introduce a second variable to treat a mutable.
092:
093: String finalConstraintValue = constraintValue;
094:
095: // If no constraint was provided, check to see if it is available via a localized message
096: // key. This is really handy for complex validations such as patterns.
097:
098: if (finalConstraintValue == null
099: && validator.getConstraintType() != null) {
100: String key = overrideId + "-" + validatorType;
101:
102: if (overrideMessages.contains(key))
103: finalConstraintValue = overrideMessages.get(key);
104: else
105: throw new IllegalArgumentException(ServicesMessages
106: .missingValidatorConstraint(validatorType,
107: validator.getConstraintType()));
108: }
109:
110: Object coercedConstraintValue = coerceConstraintValue(
111: finalConstraintValue, validator.getConstraintType());
112:
113: MessageFormatter formatter = findMessageFormatter(overrideId,
114: overrideMessages, locale, validatorType, validator);
115:
116: return new FieldValidatorImpl(field, coercedConstraintValue,
117: formatter, validator, _pageRenderSupport);
118: }
119:
120: private MessageFormatter findMessageFormatter(String overrideId,
121: Messages overrideMessages, Locale locale,
122: String validatorType, Validator validator) {
123:
124: String overrideKey = overrideId + "-" + validatorType
125: + "-message";
126:
127: if (overrideMessages.contains(overrideKey))
128: return overrideMessages.getFormatter(overrideKey);
129:
130: Messages messages = _messagesSource
131: .getValidationMessages(locale);
132:
133: String key = validator.getMessageKey();
134:
135: return messages.getFormatter(key);
136: }
137:
138: public FieldValidator createValidators(Field field,
139: String specification) {
140: List<ValidatorSpecification> specs = parse(specification);
141:
142: List<FieldValidator> fieldValidators = newList();
143:
144: for (ValidatorSpecification spec : specs) {
145: fieldValidators.add(createValidator(field, spec
146: .getValidatorType(), spec.getConstraintValue()));
147: }
148:
149: if (fieldValidators.size() == 1)
150: return fieldValidators.get(0);
151:
152: return new CompositeFieldValidator(fieldValidators);
153: }
154:
155: @SuppressWarnings("unchecked")
156: private Object coerceConstraintValue(String constraintValue,
157: Class constraintType) {
158: if (constraintType == null)
159: return null;
160:
161: return _typeCoercer.coerce(constraintValue, constraintType);
162: }
163:
164: /**
165: * A code defining what the parser is looking for.
166: */
167: enum State {
168:
169: /** The start of a validator type. */
170: TYPE_START,
171: /** The end of a validator type. */
172: TYPE_END,
173: /** Equals sign after a validator type, or a comma. */
174: EQUALS_OR_COMMA,
175: /** The start of a constraint value. */
176: VALUE_START,
177: /** The end of the constraint value. */
178: VALUE_END,
179: /** The comma after a constraint value. */
180: COMMA
181: };
182:
183: static List<ValidatorSpecification> parse(String specification) {
184: List<ValidatorSpecification> result = newList();
185:
186: char[] input = specification.toCharArray();
187:
188: int cursor = 0;
189: int start = -1;
190:
191: String type = null;
192: String value = null;
193: boolean skipWhitespace = true;
194: State state = State.TYPE_START;
195:
196: while (cursor < input.length) {
197: char ch = input[cursor];
198:
199: if (skipWhitespace && Character.isWhitespace(ch)) {
200: cursor++;
201: continue;
202: }
203:
204: skipWhitespace = false;
205:
206: switch (state) {
207:
208: case TYPE_START:
209:
210: if (Character.isLetter(ch)) {
211: start = cursor;
212: state = State.TYPE_END;
213: break;
214: }
215:
216: parseError(cursor, specification);
217:
218: case TYPE_END:
219:
220: if (Character.isLetter(ch)) {
221: break;
222: }
223:
224: type = specification.substring(start, cursor);
225:
226: skipWhitespace = true;
227: state = State.EQUALS_OR_COMMA;
228: continue;
229:
230: case EQUALS_OR_COMMA:
231:
232: if (ch == '=') {
233: skipWhitespace = true;
234: state = State.VALUE_START;
235: break;
236: }
237:
238: if (ch == ',') {
239: result.add(new ValidatorSpecification(type));
240: type = null;
241: state = State.COMMA;
242: continue;
243: }
244:
245: parseError(cursor, specification);
246:
247: case VALUE_START:
248:
249: start = cursor;
250: state = State.VALUE_END;
251: break;
252:
253: case VALUE_END:
254:
255: // The value ends when we hit whitespace or a comma
256:
257: if (Character.isWhitespace(ch) || ch == ',') {
258: value = specification.substring(start, cursor);
259:
260: result.add(new ValidatorSpecification(type, value));
261: type = null;
262: value = null;
263:
264: skipWhitespace = true;
265: state = State.COMMA;
266: continue;
267: }
268:
269: break;
270:
271: case COMMA:
272:
273: if (ch == ',') {
274: skipWhitespace = true;
275: state = State.TYPE_START;
276: break;
277:
278: }
279:
280: parseError(cursor, specification);
281: } // case
282:
283: cursor++;
284: } // while
285:
286: // cursor is now one character past end of string.
287: // Cleanup whatever state we were in the middle of.
288:
289: switch (state) {
290: case TYPE_END:
291:
292: type = specification.substring(start);
293:
294: case EQUALS_OR_COMMA:
295:
296: result.add(new ValidatorSpecification(type));
297: break;
298:
299: // Case when the specification ends with an equals sign.
300:
301: case VALUE_START:
302: result.add(new ValidatorSpecification(type, ""));
303: break;
304:
305: case VALUE_END:
306: value = specification.substring(start);
307:
308: result.add(new ValidatorSpecification(type, value));
309: break;
310:
311: // For better or worse, ending the string with a comma is valid.
312:
313: default:
314:
315: }
316:
317: return result;
318: }
319:
320: private static void parseError(int cursor, String specification) {
321: throw new RuntimeException(
322: ServicesMessages.validatorSpecificationParseError(
323: cursor, specification));
324: }
325: }
|