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
015package org.apache.tapestry.valid;
016
017import java.util.HashMap;
018import java.util.Map;
019
020import org.apache.hivemind.ApplicationRuntimeException;
021import org.apache.tapestry.IMarkupWriter;
022import org.apache.tapestry.IRequestCycle;
023import org.apache.tapestry.Tapestry;
024import org.apache.tapestry.form.IFormComponent;
025import 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 */
045public 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}