001 // Copyright 2004, 2005 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.valid; 016 017 import java.text.MessageFormat; 018 import java.util.HashMap; 019 import java.util.Locale; 020 import java.util.Map; 021 import java.util.ResourceBundle; 022 023 import org.apache.hivemind.HiveMind; 024 import org.apache.hivemind.Resource; 025 import org.apache.hivemind.impl.DefaultClassResolver; 026 import org.apache.hivemind.util.ClasspathResource; 027 import org.apache.hivemind.util.PropertyUtils; 028 import org.apache.tapestry.IForm; 029 import org.apache.tapestry.IMarkupWriter; 030 import org.apache.tapestry.IRequestCycle; 031 import org.apache.tapestry.IScript; 032 import org.apache.tapestry.PageRenderSupport; 033 import org.apache.tapestry.TapestryUtils; 034 import org.apache.tapestry.engine.IScriptSource; 035 import org.apache.tapestry.form.IFormComponent; 036 037 /** 038 * Abstract base class for {@link IValidator}. Supports a required and locale property. 039 * 040 * @author Howard Lewis Ship 041 * @since 1.0.8 042 */ 043 044 public abstract class BaseValidator implements IValidator 045 { 046 /** 047 * Input Symbol used to represent the field being validated. 048 * 049 * @see #processValidatorScript(String, IRequestCycle, IFormComponent, Map) 050 * @since 2.2 051 */ 052 053 public static final String FIELD_SYMBOL = "field"; 054 055 /** 056 * Input symbol used to represent the validator itself to the script. 057 * 058 * @see #processValidatorScript(String, IRequestCycle, IFormComponent, Map) 059 * @since 2.2 060 */ 061 062 public static final String VALIDATOR_SYMBOL = "validator"; 063 064 /** 065 * Input symbol used to represent the {@link IForm}containing the field to the script. 066 * 067 * @see #processValidatorScript(String, IRequestCycle, IFormComponent, Map) 068 * @since 2.2 069 */ 070 071 public static final String FORM_SYMBOL = "form"; 072 073 /** 074 * Output symbol set by the script asthe name of the validator JavaScript function. The function 075 * implemented must return true or false (true if the field is valid, false otherwise). After 076 * the script is executed, the function is added to the {@link IForm}as a 077 * {@link org.apache.tapestry.form.FormEventType#SUBMIT}. 078 * 079 * @see #processValidatorScript(String, IRequestCycle, IFormComponent, Map) 080 * @since 2.2 081 */ 082 083 public static final String FUNCTION_SYMBOL = "function"; 084 085 private boolean _required; 086 087 /** @since 3.0 */ 088 089 private String _requiredMessage; 090 091 /** 092 * @since 2.2 093 */ 094 095 private boolean _clientScriptingEnabled = false; 096 097 /** 098 * @since 4.1 099 */ 100 private IScriptSource _scriptSource; 101 102 /** 103 * Standard constructor. Leaves locale as system default and required as false. 104 */ 105 106 public BaseValidator() 107 { 108 } 109 110 /** 111 * Allow the validator to be initialized with a property initialization string. 112 * 113 * @since 4.0 114 */ 115 public BaseValidator(String initializer) 116 { 117 PropertyUtils.configureProperties(this, initializer); 118 } 119 120 protected BaseValidator(boolean required) 121 { 122 _required = required; 123 } 124 125 public boolean isRequired() 126 { 127 return _required; 128 } 129 130 public void setRequired(boolean required) 131 { 132 _required = required; 133 } 134 135 public void setScriptSource(IScriptSource scriptSource) 136 { 137 _scriptSource = scriptSource; 138 } 139 140 /** 141 * Gets a pattern, either as the default value, or as a localized key. If override is null, then 142 * the key from the <code>org.apache.tapestry.valid.ValidationStrings</code> 143 * {@link ResourceBundle}(in the specified locale) is used. The pattern can then be used with 144 * {@link #formatString(String, Object[])}. 145 * <p> 146 * Why do we not just lump these strings into TapestryStrings.properties? because 147 * TapestryStrings.properties is localized to the server's locale, which is fine for the 148 * logging, debugging and error messages it contains. For field validation, whose errors are 149 * visible to the end user normally, we want to localize to the page's locale. 150 * 151 * @param override 152 * The override value for the localized string from the bundle. 153 * @param key 154 * used to lookup pattern from bundle, if override is null. 155 * @param locale 156 * used to get right localization of bundle. 157 * @since 3.0 158 */ 159 160 protected String getPattern(String override, String key, Locale locale) 161 { 162 if (override != null) 163 return override; 164 165 ResourceBundle strings = ResourceBundle.getBundle( 166 "org.apache.tapestry.valid.ValidationStrings", 167 locale); 168 169 return strings.getString(key); 170 } 171 172 /** 173 * Gets a string from the standard resource bundle. The string in the bundle is treated as a 174 * pattern for {@link MessageFormat#format(java.lang.String, java.lang.Object[])}. 175 * 176 * @param pattern 177 * string the input pattern to be used with 178 * {@link MessageFormat#format(java.lang.String, java.lang.Object[])}. It may 179 * contain replaceable parameters, {0}, {1}, etc. 180 * @param args 181 * the arguments used to fill replaceable parameters {0}, {1}, etc. 182 * @since 3.0 183 */ 184 185 protected String formatString(String pattern, Object[] args) 186 { 187 return MessageFormat.format(pattern, args); 188 } 189 190 /** 191 * Convienience method for invoking {@link #formatString(String, Object[])}. 192 * 193 * @since 3.0 194 */ 195 196 protected String formatString(String pattern, Object arg) 197 { 198 return formatString(pattern, new Object[] 199 { arg }); 200 } 201 202 /** 203 * Convienience method for invoking {@link #formatString(String, Object[])}. 204 * 205 * @since 3.0 206 */ 207 208 protected String formatString(String pattern, Object arg1, Object arg2) 209 { 210 return formatString(pattern, new Object[] 211 { arg1, arg2 }); 212 } 213 214 /** 215 * Invoked to check if the value is null. If the value is null (or empty), but the required flag 216 * is set, then this method throws a {@link ValidatorException}. Otherwise, returns true if the 217 * value is null. 218 */ 219 220 protected boolean checkRequired(IFormComponent field, String value) throws ValidatorException 221 { 222 boolean isEmpty = HiveMind.isBlank(value); 223 224 if (_required && isEmpty) 225 throw new ValidatorException(buildRequiredMessage(field), ValidationConstraint.REQUIRED); 226 227 return isEmpty; 228 } 229 230 /** 231 * Builds an error message indicating a value for a required field was not supplied. 232 * 233 * @since 3.0 234 */ 235 236 protected String buildRequiredMessage(IFormComponent field) 237 { 238 String pattern = getPattern(_requiredMessage, "field-is-required", field.getPage() 239 .getLocale()); 240 241 return formatString(pattern, field.getDisplayName()); 242 } 243 244 /** 245 * This implementation does nothing. Subclasses may supply their own implementation. 246 * 247 * @since 2.2 248 */ 249 250 public void renderValidatorContribution(IFormComponent field, IMarkupWriter writer, 251 IRequestCycle cycle) 252 { 253 } 254 255 /** 256 * Invoked (from sub-class implementations of 257 * {@link #renderValidatorContribution(IFormComponent, IMarkupWriter, IRequestCycle)}to process 258 * a standard validation script. This expects that: 259 * <ul> 260 * <li>The {@link IFormComponent}is (ultimately) wrapped by a {@link Body} 261 * <li>The script generates a symbol named "function" (as per {@link #FUNCTION_SYMBOL}) 262 * </ul> 263 * 264 * @param scriptPath 265 * the resource path of the script to execute 266 * @param cycle 267 * The active request cycle 268 * @param field 269 * The field to be validated 270 * @param symbols 271 * a set of input symbols needed by the script. These symbols are augmented with 272 * symbols for the field, form and validator. symbols may be null, but will be 273 * modified if not null. 274 * @throws ApplicationRuntimeException 275 * if there's an error processing the script. 276 * @since 2.2 277 */ 278 279 protected void processValidatorScript(String scriptPath, IRequestCycle cycle, 280 IFormComponent field, Map symbols) 281 { 282 IForm form = field.getForm(); 283 284 Map finalSymbols = (symbols == null) ? new HashMap() : symbols; 285 286 finalSymbols.put(FIELD_SYMBOL, field); 287 finalSymbols.put(FORM_SYMBOL, form); 288 finalSymbols.put(VALIDATOR_SYMBOL, this); 289 290 Resource location = new ClasspathResource(new DefaultClassResolver(), scriptPath); 291 292 IScript script = _scriptSource.getScript(location); 293 294 // If there's an error, report it against the field (this validator object doesn't 295 // have a location). 296 297 PageRenderSupport pageRenderSupport = TapestryUtils.getPageRenderSupport(cycle, field); 298 299 script.execute(field, cycle, pageRenderSupport, finalSymbols); 300 } 301 302 /** 303 * Returns true if client scripting is enabled. Some validators are capable of generating 304 * client-side scripting to perform validation when the form is submitted. By default, this flag 305 * is false and subclasses should check it (in 306 * {@link #renderValidatorContribution(IFormComponent, IMarkupWriter, IRequestCycle)}) before 307 * generating client side script. 308 * 309 * @since 2.2 310 */ 311 312 public boolean isClientScriptingEnabled() 313 { 314 return _clientScriptingEnabled; 315 } 316 317 public void setClientScriptingEnabled(boolean clientScriptingEnabled) 318 { 319 _clientScriptingEnabled = clientScriptingEnabled; 320 } 321 322 public String getRequiredMessage() 323 { 324 return _requiredMessage; 325 } 326 327 /** 328 * Overrides the <code>field-is-required</code> bundle key. Parameter {0} is the display name 329 * of the field. 330 * 331 * @since 3.0 332 */ 333 334 public void setRequiredMessage(String string) 335 { 336 _requiredMessage = string; 337 } 338 339 }