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.util.HashMap;
018    import java.util.Map;
019    
020    import org.apache.hivemind.ApplicationRuntimeException;
021    import org.apache.tapestry.IMarkupWriter;
022    import org.apache.tapestry.IRequestCycle;
023    import org.apache.tapestry.Tapestry;
024    import org.apache.tapestry.form.IFormComponent;
025    import org.apache.tapestry.util.RegexpMatcher;
026    
027    /**
028     * <p>The validator bean that provides a pattern validation service.
029     * 
030     * <p>The actual pattern matching algorithm is provided by the 
031     * {@link org.apache.tapestry.valid.PatternDelegate}. This enables the user to provide
032     * custom pattern matching implementations. In the event a custom implementation is not 
033     * provided, this validator will use the {@link org.apache.tapestry.util.RegexpMatcher}.
034     * 
035     * <p>This validator has the ability to provide client side validation on demand. 
036     * To enable client side validation simply set the <code>clientScriptingEnabled</code>
037     * property to <code>true</code>.
038     * The default implementation of the script will be in JavaScript and allows the user to 
039     * override this with a custom implementation by setting the path to the custom  
040     * script via {@link #setScriptPath(String)}.
041     * 
042     * @author  Harish Krishnaswamy
043     * @since   3.0
044     */
045    public class PatternValidator extends BaseValidator
046    {
047        /**
048         * The pattern that this validator will use to validate the input. The default 
049         * pattern is an empty string.
050         */
051        private String _patternString = "";
052    
053        /**
054         * A custom message in the event of a validation failure.
055         */
056        private String _patternNotMatchedMessage;
057    
058        /**
059         * The object that handles pattern matching.
060         */
061        private PatternDelegate _patternDelegate;
062    
063        /**
064         * The location of the script specification for client side validation.
065         */
066        private String _scriptPath = "/org/apache/tapestry/valid/PatternValidator.script";
067    
068        /**
069         * Returns custom validation failure message. The default message comes from 
070         * <code>ValidationStrings.properties</code> file for key 
071         * <code>pattern-not-matched</code>.
072         */
073        public String getPatternNotMatchedMessage()
074        {
075            return _patternNotMatchedMessage;
076        }
077    
078        /**
079         * Returns the pattern that this validator uses for validation.
080         */
081        public String getPatternString()
082        {
083            return _patternString;
084        }
085    
086        /**
087         * Allows for a custom message to be set typically via the bean specification.
088         */
089        public void setPatternNotMatchedMessage(String message)
090        {
091            _patternNotMatchedMessage = message;
092        }
093    
094        /**
095         * Allows the user to change the validation pattern. 
096         */
097        public void setPatternString(String pattern)
098        {
099            _patternString = pattern;
100        }
101    
102        /**
103         * Static inner class that acts as a delegate to RegexpMatcher and conforms to the 
104         * PatternDelegate contract.
105         */
106        private static class RegExpDelegate implements PatternDelegate
107        {
108            private RegexpMatcher _matcher;
109    
110            private RegexpMatcher getPatternMatcher()
111            {
112                if (_matcher == null)
113                    _matcher = new RegexpMatcher();
114    
115                return _matcher;
116            }
117    
118            public boolean contains(String patternString, String input)
119            {
120                return getPatternMatcher().contains(patternString, input);
121            }
122    
123            public String getEscapedPatternString(String patternString)
124            {
125                return getPatternMatcher().getEscapedPatternString(patternString);
126            }
127        }
128    
129        /**
130         * Allows for a custom implementation to do the pattern matching. The default pattern 
131         * matching is done with {@link org.apache.tapestry.util.RegexpMatcher}.
132         */
133        public void setPatternDelegate(PatternDelegate patternDelegate)
134        {
135            _patternDelegate = patternDelegate;
136        }
137    
138        /**
139         * Returns the custom pattern matcher if one is provided or creates and returns the 
140         * default matcher laziliy.
141         */
142        public PatternDelegate getPatternDelegate()
143        {
144            if (_patternDelegate == null)
145                _patternDelegate = new RegExpDelegate();
146    
147            return _patternDelegate;
148        }
149    
150        /**
151         * @see org.apache.tapestry.valid.IValidator#toString(org.apache.tapestry.form.IFormComponent, java.lang.Object)
152         */
153        public String toString(IFormComponent field, Object value)
154        {
155            if (value == null)
156                return null;
157    
158            return value.toString();
159        }
160    
161        private String buildPatternNotMatchedMessage(IFormComponent field, String patternString)
162        {
163            String templateMessage =
164                getPattern(
165                    _patternNotMatchedMessage,
166                    "pattern-not-matched",
167                    field.getPage().getLocale());
168    
169            return formatString(templateMessage, field.getDisplayName(), patternString);
170        }
171    
172        /**
173         * @see org.apache.tapestry.valid.IValidator#toObject(org.apache.tapestry.form.IFormComponent, java.lang.String)
174         */
175        public Object toObject(IFormComponent field, String input) throws ValidatorException
176        {
177            if (checkRequired(field, input))
178                return null;
179    
180            boolean matched = false;
181    
182            try
183            {
184                matched = getPatternDelegate().contains(_patternString, input);
185            }
186            catch (Throwable t)
187            {
188                throw new ApplicationRuntimeException(
189                    Tapestry.format(
190                        "PatternValidator.pattern-match-error",
191                        _patternString,
192                        field.getDisplayName()),
193                    field,
194                    field.getLocation(),
195                    t);
196            }
197    
198            if (!matched)
199                throw new ValidatorException(
200                    buildPatternNotMatchedMessage(field, _patternString),
201                    ValidationConstraint.PATTERN_MISMATCH);
202    
203            return input;
204        }
205    
206        /**
207         * Allows for a custom implementation of the client side validation.
208         */
209        public void setScriptPath(String scriptPath)
210        {
211            _scriptPath = scriptPath;
212        }
213    
214        /**
215         * @see org.apache.tapestry.valid.IValidator#renderValidatorContribution(org.apache.tapestry.form.IFormComponent, org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
216         */
217        public void renderValidatorContribution(
218            IFormComponent field,
219            IMarkupWriter writer,
220            IRequestCycle cycle)
221        {
222            if (!isClientScriptingEnabled())
223                return;
224    
225            Map symbols = new HashMap();
226    
227            if (isRequired())
228                symbols.put("requiredMessage", buildRequiredMessage(field));
229    
230            symbols.put(
231                "patternNotMatchedMessage",
232                buildPatternNotMatchedMessage(field, getEscapedPatternString()));
233    
234            processValidatorScript(_scriptPath, cycle, field, symbols);
235        }
236    
237        /**
238         * Returns the escaped sequence of the pattern string for rendering in the error message. 
239         */
240        public String getEscapedPatternString()
241        {
242            return getPatternDelegate().getEscapedPatternString(_patternString);
243        }
244    
245        public String toString()
246        {
247            return "Pattern: "
248                + _patternString
249                + "; Script Path: "
250                + _scriptPath
251                + "; Pattern Delegate: "
252                + _patternDelegate;
253        }
254    }