001: package org.andromda.cartridges.jsf.validator;
002:
003: import java.io.InputStream;
004: import java.io.Serializable;
005:
006: import java.lang.reflect.Method;
007:
008: import java.util.ArrayList;
009: import java.util.Collection;
010: import java.util.HashMap;
011: import java.util.Locale;
012: import java.util.Map;
013:
014: import javax.faces.application.FacesMessage;
015: import javax.faces.component.UIComponent;
016: import javax.faces.component.UIForm;
017: import javax.faces.context.ExternalContext;
018: import javax.faces.context.FacesContext;
019: import javax.faces.validator.ValidatorException;
020:
021: import org.apache.commons.logging.Log;
022: import org.apache.commons.logging.LogFactory;
023: import org.apache.commons.validator.Field;
024: import org.apache.commons.validator.Form;
025: import org.apache.commons.validator.ValidatorAction;
026: import org.apache.commons.validator.ValidatorResources;
027:
028: /**
029: * A JSF validator that uses the apache commons-validator to perform either
030: * client or server side validation.
031: */
032: public class JSFValidator implements javax.faces.validator.Validator,
033: Serializable {
034: private static final Log logger = LogFactory
035: .getLog(JSFValidator.class);
036:
037: /**
038: * Constructs a new instance of this class with the given
039: * <code>validatorAction</code>.
040: *
041: * @param validatorAction
042: */
043: public JSFValidator(final ValidatorAction validatorAction) {
044: this .validatorAction = validatorAction;
045: }
046:
047: public JSFValidator() {
048: // - default constructor for faces-config.xml
049: }
050:
051: /**
052: * Validator type.
053: */
054: private String type;
055:
056: /**
057: * The setter method for the <code>type</code> property. This property is
058: * passed through to the commons-validator.
059: *
060: * @param type The new value for the <code>type</code> property.
061: */
062: public void setType(final String type) {
063: this .type = type;
064: }
065:
066: /**
067: * The getter method for the <code>type</code> property. This property is
068: * passed through to the commons-validator.
069: */
070: public String getType() {
071: return this .type;
072: }
073:
074: /**
075: * Parameter for the error message. This parameter is passed through to the
076: * commons-validator.
077: */
078: private String[] args;
079:
080: /**
081: * The setter method for the <code>arg</code> property. This property is
082: * passed through to the commons-validator..
083: *
084: * @param arg The new value for the <code>arg</code> property.
085: */
086: public void setArgs(final String[] args) {
087: this .args = args;
088: }
089:
090: /**
091: * The parameters for this validator.
092: */
093: private final Map parameters = new HashMap();
094:
095: /**
096: * Gets the parameters for this validator (keyed by name).
097: *
098: * @return a map containing all parameters.
099: */
100: public Map getParameters() {
101: return this .parameters;
102: }
103:
104: /**
105: * Adds the parameter with the given <code>name</code> and the given
106: * <code>value</code>.
107: *
108: * @param name the name of the parameter.
109: * @param value the parameter's value
110: */
111: public void addParameter(final String name, final Object value) {
112: this .parameters.put(name, value);
113: }
114:
115: /**
116: * Returns the commons-validator action that's appropriate for the validator
117: * with the given <code>name</code>. This method lazily configures
118: * validator resources by reading <code>/WEB-INF/validator-rules.xml</code>
119: * and <code>/WEB-INF/validation.xml</code>.
120: *
121: * @param name The name of the validator
122: */
123: public static ValidatorAction getValidatorAction(final String name) {
124: return getValidatorResources().getValidatorAction(name);
125: }
126:
127: /**
128: * The commons-validator action, that carries out the actual validation.
129: */
130: private ValidatorAction validatorAction;
131:
132: /**
133: * The location of the validator rules.
134: */
135: public static final String RULES_LOCATION = "/WEB-INF/validator-rules.xml";
136:
137: /**
138: * The key that stores the validator resources
139: */
140: private static final String VALIDATOR_RESOURCES_KEY = "org.andromda.jsf.validator.resources";
141:
142: /**
143: * Returns the commons-validator resources. This method lazily configures
144: * validator resources by reading <code>/WEB-INF/validator-rules.xml</code>
145: * and <code>/WEB-INF/validation.xml</code>.
146: *
147: * @param name The name of the validator
148: */
149: public static ValidatorResources getValidatorResources() {
150: final FacesContext context = FacesContext.getCurrentInstance();
151: final ExternalContext external = context.getExternalContext();
152: final Map applicationMap = external.getApplicationMap();
153: ValidatorResources validatorResources = (ValidatorResources) applicationMap
154: .get(VALIDATOR_RESOURCES_KEY);
155: if (validatorResources == null) {
156: final String rulesResource = RULES_LOCATION;
157: final String validationResource = "/WEB-INF/validation.xml";
158: final InputStream rulesInput = external
159: .getResourceAsStream(rulesResource);
160: if (rulesInput == null) {
161: throw new JSFValidatorException(
162: "Could not find rules file '" + rulesResource
163: + "'");
164: }
165: final InputStream validationInput = external
166: .getResourceAsStream(validationResource);
167: if (validationInput != null) {
168: final InputStream[] inputs = new InputStream[] {
169: rulesInput, validationInput };
170: try {
171: validatorResources = new ValidatorResources(inputs);
172: applicationMap.put(VALIDATOR_RESOURCES_KEY,
173: validatorResources);
174: } catch (final Throwable throwable) {
175: throw new JSFValidatorException(throwable);
176: }
177: } else {
178: logger
179: .info("No validation rules could be loaded from --> '"
180: + validationResource
181: + ", validation not configured");
182: }
183: }
184: return validatorResources;
185: }
186:
187: /**
188: * This <code>validate</code> method is called by JSF to verify the
189: * component to which the validator is attached.
190: *
191: * @param context The faces context
192: * @param component The component to validate
193: */
194: public void validate(final FacesContext context,
195: final UIComponent component, final Object value) {
196: final UIForm form = findForm(component);
197: if (form != null) {
198: try {
199: final Form validatorForm = getValidatorResources()
200: .getForm(Locale.getDefault(), form.getId());
201: if (validatorForm != null) {
202: final Field field = this .getFormField(
203: validatorForm, component.getId());
204: if (field != null) {
205: final Collection errors = new ArrayList();
206: this .getValidatorMethod().invoke(
207: this .getValidatorClass(),
208: new Object[] { context, value,
209: this .getParameters(), errors,
210: this .validatorAction, field });
211: if (!errors.isEmpty()) {
212: throw new ValidatorException(
213: new FacesMessage(
214: FacesMessage.SEVERITY_ERROR,
215: (String) errors.iterator()
216: .next(), null));
217: }
218: } else {
219: logger.error("No field with id '"
220: + component.getId()
221: + "' found on form '" + form.getId()
222: + "'");
223: }
224: } else {
225: logger
226: .error("No validator form could be found with id '"
227: + form.getId() + "'");
228: }
229: } catch (final ValidatorException exception) {
230: throw exception;
231: } catch (final Exception exception) {
232: logger.error(exception.getMessage(), exception);
233: }
234: }
235: }
236:
237: /**
238: * Gets the validator class from the underlying <code>validatorAction</code>.
239: * @return the validator class
240: * @throws ClassNotFoundException
241: */
242: private Class getValidatorClass() throws ClassNotFoundException {
243: final FacesContext context = FacesContext.getCurrentInstance();
244: final ExternalContext external = context.getExternalContext();
245: final Map applicationMap = external.getApplicationMap();
246: final String validatorClassName = this .validatorAction
247: .getClassname();
248: Class validatorClass = (Class) applicationMap
249: .get(validatorClassName);
250: if (validatorClass == null) {
251: ClassLoader classLoader = Thread.currentThread()
252: .getContextClassLoader();
253: if (classLoader == null) {
254: classLoader = getClass().getClassLoader();
255: }
256: validatorClass = classLoader.loadClass(validatorClassName);
257: applicationMap.put(validatorClassName, validatorClass);
258: }
259: return validatorClass;
260: }
261:
262: /**
263: * Gets the validator method for the underlying <code>validatorAction</code>.
264: *
265: * @return the validator method.
266: * @throws ClassNotFoundException
267: * @throws NoSuchMethodException
268: */
269: private Method getValidatorMethod() throws ClassNotFoundException,
270: NoSuchMethodException {
271: Class[] parameterTypes = new Class[] {
272: javax.faces.context.FacesContext.class,
273: java.lang.Object.class, java.util.Map.class,
274: java.util.Collection.class,
275: org.apache.commons.validator.ValidatorAction.class,
276: org.apache.commons.validator.Field.class };
277: return this .getValidatorClass().getMethod(
278: this .validatorAction.getMethod(), parameterTypes);
279: }
280:
281: /**
282: * Attempts to retrieve the form field from the form with the given <code>formName</code>
283: * and the field with the given <code>fieldName</code>. If it can't be retrieved, null
284: * is returned.
285: * @param form the form to validate.
286: * @param fieldName the name of the field.
287: * @return the found field or null if it could not be found.
288: */
289: private Field getFormField(final Form form, final String fieldName) {
290: Field field = null;
291: if (form != null) {
292: field = form.getField(fieldName);
293: }
294: return field;
295: }
296:
297: /**
298: * Retrieves an error message, using the validator's message combined with
299: * the errant value.
300: */
301: public String getErrorMessage(final FacesContext context) {
302: return ValidatorMessages.getMessage(this .validatorAction,
303: this .args, context);
304: }
305:
306: /**
307: * Recursively finds the valueHolder's form (if the valueHolder is nested within a form).
308: *
309: * @param component the valueHolder for which to find the form.
310: * @return the form or null if there is no form for the valueHolder.
311: */
312: public static UIForm findForm(final UIComponent component) {
313: UIForm form = null;
314: if (component != null) {
315: if (component instanceof UIForm) {
316: form = (UIForm) component;
317: } else {
318: form = findForm(component.getParent());
319: }
320: }
321: return form;
322: }
323:
324: /**
325: * @see java.lang.Object#toString()
326: */
327: public String toString() {
328: return super .toString() + "[" + this .validatorAction != null ? this .validatorAction
329: .getName()
330: : null + "]";
331: }
332:
333: private static final long serialVersionUID = -5627108517488240081L;
334: }
|