001: package org.andromda.cartridges.jsf.component;
002:
003: import java.io.IOException;
004: import java.util.ArrayList;
005: import java.util.Collection;
006: import java.util.Iterator;
007: import java.util.LinkedHashMap;
008: import java.util.Locale;
009: import java.util.Map;
010:
011: import javax.faces.component.EditableValueHolder;
012: import javax.faces.component.UIComponent;
013: import javax.faces.component.UIComponentBase;
014: import javax.faces.component.UIForm;
015: import javax.faces.context.FacesContext;
016: import javax.faces.context.ResponseWriter;
017: import javax.faces.validator.Validator;
018: import javax.servlet.ServletContext;
019: import javax.servlet.http.HttpServletRequest;
020:
021: import org.andromda.cartridges.jsf.validator.JSFValidator;
022: import org.andromda.cartridges.jsf.validator.JSFValidatorException;
023: import org.andromda.cartridges.jsf.validator.ValidatorMessages;
024: import org.andromda.utils.StringUtilsHelper;
025: import org.apache.commons.lang.StringUtils;
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028: import org.apache.commons.validator.Arg;
029: import org.apache.commons.validator.Field;
030: import org.apache.commons.validator.Form;
031: import org.apache.commons.validator.ValidatorAction;
032: import org.apache.commons.validator.ValidatorResources;
033:
034: /**
035: * A JSF component that enabled the commons-validator server side validation, as well
036: * as encodes JavaScript for all client-side validations
037: * specified in the same JSP page (with <code>jsf:validator</code>.
038: */
039: public class JSFValidatorComponent extends UIComponentBase {
040: private static final Log logger = LogFactory
041: .getLog(JSFValidatorComponent.class);
042:
043: /**
044: * A map of validators, representing all of the Commons Validators attached
045: * to components in the current component hierarchy. The keys of the map are
046: * validator type names. The values are maps from IDs to JSFValidator
047: * objects.
048: */
049: private Map validators = new LinkedHashMap();
050:
051: /**
052: * The component renders itself; therefore, this method returns null.
053: */
054: public String getRendererType() {
055: return null;
056: }
057:
058: /**
059: * Returns the component's family. In this case, the component is not
060: * associated with a family, so this method returns null.
061: */
062: public String getFamily() {
063: return null;
064: }
065:
066: /**
067: * Registers a validator according to type and id.
068: *
069: * @param type The type of the validator
070: * @param id The validator's identifier
071: * @param validator The JSF validator associated with the id and type
072: */
073: private void addValidator(final String type, final String id,
074: final JSFValidator validator) {
075: Map map = (Map) this .validators.get(type);
076: if (map == null) {
077: map = new LinkedHashMap();
078: this .validators.put(type, map);
079: }
080: if (id != null) {
081: map.put(id, validator);
082: }
083: }
084:
085: private HttpServletRequest getRequest() {
086: return (HttpServletRequest) FacesContext.getCurrentInstance()
087: .getExternalContext().getRequest();
088: }
089:
090: /**
091: * <p>
092: * Recursively finds all Commons validators for the all of the components in
093: * a component hierarchy and adds them to a map.
094: * </p>
095: * If a validator's type is required, this method sets the associated
096: * component's required property to true. This is necessary because JSF does
097: * not validate empty fields unless a component's required property is true.
098: *
099: * @param component The component at the root of the component tree
100: * @param context The FacesContext for this request
101: */
102: private void findValidators(final UIComponent component,
103: final FacesContext context) {
104: if (component instanceof EditableValueHolder) {
105: final EditableValueHolder valueHolder = (EditableValueHolder) component;
106: final UIForm form = JSFValidator.findForm(component);
107: if (form != null) {
108: final String componentId = component.getId();
109: final ValidatorResources resources = JSFValidator
110: .getValidatorResources();
111: if (resources != null) {
112: final Form validatorForm = resources.getForm(Locale
113: .getDefault(), form.getId());
114: if (validatorForm != null) {
115: final java.util.List validatorFields = validatorForm
116: .getFields();
117: for (final Iterator iterator = validatorFields
118: .iterator(); iterator.hasNext();) {
119: final Field field = (Field) iterator.next();
120:
121: // we need to make it match the name of the id on the jsf components (if its nested).
122: final String fieldProperty = StringUtilsHelper
123: .lowerCamelCaseName(field
124: .getProperty());
125: if (componentId.equals(fieldProperty)) {
126: for (final Iterator dependencyIterator = field
127: .getDependencyList().iterator(); dependencyIterator
128: .hasNext();) {
129: final String dependency = (String) dependencyIterator
130: .next();
131: final ValidatorAction action = JSFValidator
132: .getValidatorAction(dependency);
133: if (action != null) {
134: final JSFValidator validator = new JSFValidator(
135: action);
136: final Arg[] args = field
137: .getArgs(dependency);
138: if (args != null) {
139: for (final Iterator varIterator = field
140: .getVars().keySet()
141: .iterator(); varIterator
142: .hasNext();) {
143: final String name = (String) varIterator
144: .next();
145: validator
146: .addParameter(
147: name,
148: field
149: .getVarValue(name));
150: }
151: validator
152: .setArgs(ValidatorMessages
153: .getArgs(
154: dependency,
155: field));
156: this
157: .addValidator(
158: dependency,
159: component
160: .getClientId(context),
161: validator);
162: if (!this .validatorPresent(
163: valueHolder,
164: validator)) {
165: valueHolder
166: .addValidator(validator);
167: }
168: }
169: } else {
170: logger
171: .error("No validator action with name '"
172: + dependency
173: + "' registered in rules files '"
174: + JSFValidator.RULES_LOCATION
175: + "'");
176: }
177: }
178: }
179: }
180: }
181: }
182: }
183: }
184: for (final Iterator iterator = component.getFacetsAndChildren(); iterator
185: .hasNext();) {
186: final UIComponent childComponent = (UIComponent) iterator
187: .next();
188: this .findValidators(childComponent, context);
189: }
190: }
191:
192: /**
193: * Indicates whether or not the JSFValidator instance is present and if so returns true.
194: *
195: * @param valueHolder the value holder on which to check if its present.
196: * @return true/false
197: */
198: private boolean validatorPresent(EditableValueHolder valueHolder,
199: final Validator validator) {
200: boolean present = false;
201: if (validator != null) {
202: final Validator[] validators = valueHolder.getValidators();
203: if (validators != null) {
204: for (int ctr = 0; ctr < validators.length; ctr++) {
205: final Validator test = validators[ctr];
206: if (test instanceof JSFValidator) {
207: present = test.toString().equals(
208: validator.toString());
209: if (present) {
210: break;
211: }
212: }
213: }
214: }
215: }
216: return present;
217: }
218:
219: private static final String JAVASCRIPT_UTILITIES = "javascriptUtilities";
220:
221: /**
222: * Write the start of the script for client-side validation.
223: *
224: * @param writer A response writer
225: */
226: private final void writeScriptStart(final ResponseWriter writer)
227: throws IOException {
228: writer.startElement("script", this );
229: writer.writeAttribute("type", "text/javascript", null);
230: writer.writeAttribute("language", "Javascript1.1", null);
231: writer.write("\n<!--\n");
232: }
233:
234: /**
235: * Write the end of the script for client-side validation.
236: *
237: * @param writer A response writer
238: */
239: private void writeScriptEnd(ResponseWriter writer)
240: throws IOException {
241: writer.write("\n-->\n");
242: writer.endElement("script");
243: }
244:
245: /**
246: * Returns the name of the JavaScript function, specified in the JSP page that validates this JSP page's form.
247: *
248: * @param action the validation action from which to retrieve the function name.
249: */
250: private String getJavaScriptFunctionName(
251: final ValidatorAction action) {
252: String functionName = null;
253: final String javascript = action.getJavascript();
254: if (StringUtils.isNotBlank(javascript)) {
255: final String function = "function ";
256: int functionIndex = javascript.indexOf(function);
257: functionName = javascript.substring(functionIndex + 9);
258: functionName = functionName.substring(0,
259: functionName.indexOf('('))
260: .replaceAll("[\\s]+", " ");
261: }
262: return functionName;
263: }
264:
265: /**
266: * The attribute storing whether or not client-side validation
267: * shall performed.
268: */
269: public static final String CLIENT = "client";
270:
271: /**
272: * Sets whether or not client-side validation shall be performed.
273: *
274: * @param true/false
275: */
276: public void setClient(final String functionName) {
277: this .getAttributes().put(CLIENT, functionName);
278: }
279:
280: /**
281: * Gets whether or not client side validation shall be performed.
282: *
283: * @return true/false
284: */
285: private boolean isClient() {
286: String client = (String) this .getAttributes().get(CLIENT);
287: return StringUtils.isBlank(client) ? true : Boolean.valueOf(
288: client).booleanValue();
289: }
290:
291: /**
292: * writes the javascript functions to the response.
293: *
294: * @param writer A response writer
295: * @param context The FacesContext for this request
296: */
297: private final void writeValidationFunctions(final UIForm form,
298: final ResponseWriter writer, final FacesContext context)
299: throws IOException {
300: writer.write("var bCancel = false;\n");
301: writer.write("function ");
302: writer.write("validate" + StringUtils.capitalize(form.getId()));
303: writer.write("(form) { return bCancel || true\n");
304:
305: // - for each validator type, write "&& fun(form);
306: final Collection validatorTypes = new ArrayList(this .validators
307: .keySet());
308:
309: // - remove any validators that don't have javascript functions defined.
310: for (final Iterator iterator = validatorTypes.iterator(); iterator
311: .hasNext();) {
312: final String type = (String) iterator.next();
313: final ValidatorAction action = JSFValidator
314: .getValidatorAction(type);
315: final String functionName = this
316: .getJavaScriptFunctionName(action);
317: if (StringUtils.isBlank(functionName)) {
318: iterator.remove();
319: }
320: }
321:
322: for (final Iterator iterator = validatorTypes.iterator(); iterator
323: .hasNext();) {
324: final String type = (String) iterator.next();
325: final ValidatorAction action = JSFValidator
326: .getValidatorAction(type);
327: if (!JAVASCRIPT_UTILITIES.equals(type)) {
328: writer.write("&& ");
329: writer.write(this .getJavaScriptFunctionName(action));
330: writer.write("(form)\n");
331: }
332: }
333: writer.write(";}\n");
334:
335: // - for each validator type, write callback
336: for (final Iterator iterator = validatorTypes.iterator(); iterator
337: .hasNext();) {
338: final String type = (String) iterator.next();
339: final ValidatorAction action = JSFValidator
340: .getValidatorAction(type);
341: String callback = action.getJsFunctionName();
342: if (StringUtils.isBlank(callback)) {
343: callback = type;
344: }
345: writer.write("function ");
346: writer.write(form.getId() + "_" + callback);
347: writer.write("() { \n");
348:
349: // for each field validated by this type, add configuration object
350: final Map map = (Map) this .validators.get(type);
351: int ctr = 0;
352: for (final Iterator idIterator = map.keySet().iterator(); idIterator
353: .hasNext(); ctr++) {
354: final String id = (String) idIterator.next();
355: final JSFValidator validator = (JSFValidator) map
356: .get(id);
357: writer.write("this[" + ctr + "] = ");
358: this .writeJavaScriptParams(writer, context, id,
359: validator);
360: writer.write(";\n");
361: }
362: writer.write("}\n");
363: }
364:
365: // - for each validator type, write code
366: for (final Iterator iterator = validatorTypes.iterator(); iterator
367: .hasNext();) {
368: final String type = (String) iterator.next();
369: final ValidatorAction action = JSFValidator
370: .getValidatorAction(type);
371: writer.write(action.getJavascript());
372: writer.write("\n");
373: }
374: }
375:
376: /**
377: * Writes the JavaScript parameters for the client-side validation code.
378: *
379: * @param writer A response writer
380: * @param context The FacesContext for this request
381: * @param validator The Commons validator
382: */
383: private void writeJavaScriptParams(final ResponseWriter writer,
384: final FacesContext context, final String id,
385: final JSFValidator validator) throws IOException {
386: writer.write("new Array(\"");
387: writer.write(id);
388: writer.write("\", \"");
389: writer.write(validator.getErrorMessage(context));
390: writer.write("\", new Function(\"x\", \"return {");
391: final Map parameters = validator.getParameters();
392: for (final Iterator iterator = parameters.keySet().iterator(); iterator
393: .hasNext();) {
394: final String name = (String) iterator.next();
395: writer.write(name);
396: writer.write(":");
397: boolean mask = name.equals("mask");
398:
399: // - mask validator does not construct regular expression
400: if (mask) {
401: writer.write("/");
402: } else {
403: writer.write("'");
404: }
405: final Object parameter = parameters.get(name);
406: writer.write(parameter.toString());
407: if (mask) {
408: writer.write("/");
409: } else {
410: writer.write("'");
411: }
412: if (iterator.hasNext()) {
413: writer.write(",");
414: }
415: }
416: writer.write("}[x];\"))");
417: }
418:
419: /**
420: * Stores all forms found within this view.
421: */
422: private final Collection forms = new ArrayList();
423:
424: private UIForm findForm(final String id) {
425: UIForm form = null;
426: UIComponent validator = null;
427: try {
428: validator = this .findComponent(id);
429: } catch (NullPointerException exception) {
430: // ignore - means we couldn't find the component
431: }
432: if (validator instanceof JSFValidatorComponent) {
433: final UIComponent parent = validator.getParent();
434: if (parent instanceof UIForm) {
435: form = (UIForm) parent;
436: }
437: }
438: return form;
439: }
440:
441: /**
442: * Used to keep track of whether or not the validation rules are present or not.
443: */
444: private static final String RULES_NOT_PRESENT = "validationRulesNotPresent";
445:
446: /**
447: * Begin encoding for this component. This method finds all Commons
448: * validators attached to components in the current component hierarchy and
449: * writes out JavaScript code to invoke those validators, in turn.
450: *
451: * @param context The FacesContext for this request
452: */
453: public void encodeBegin(final FacesContext context)
454: throws IOException {
455: final ServletContext servletContext = this .getRequest()
456: .getSession().getServletContext();
457: boolean validationResourcesPresent = servletContext
458: .getAttribute(RULES_NOT_PRESENT) == null;
459: if (validationResourcesPresent
460: && JSFValidator.getValidatorResources() == null) {
461: servletContext.setAttribute(RULES_NOT_PRESENT, "true");
462: validationResourcesPresent = false;
463: }
464: if (validationResourcesPresent) {
465: try {
466: this .validators.clear();
467: this .forms.clear();
468: // - add the javascript utilities each time
469: this .addValidator(JAVASCRIPT_UTILITIES, null, null);
470: final UIForm form = this .findForm(this .getId());
471: if (form != null) {
472: this .findValidators(form, context);
473: if (this .isClient()) {
474: final ResponseWriter writer = context
475: .getResponseWriter();
476: this .writeScriptStart(writer);
477: this .writeValidationFunctions(form, writer,
478: context);
479: this .writeScriptEnd(writer);
480: }
481: }
482: } catch (final JSFValidatorException exception) {
483: logger.error(exception);
484: }
485: }
486: }
487: }
|