001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.validation;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.Iterator;
023import java.util.List;
024import java.util.Map;
025import java.util.Map.Entry;
026
027import org.apache.wicket.markup.html.form.ValidationErrorFeedback;
028import org.apache.wicket.util.lang.Args;
029import org.apache.wicket.util.lang.Classes;
030import org.apache.wicket.util.string.Strings;
031
032/**
033 * A versatile implementation of {@link IValidationError} that supports message resolution from
034 * {@link IErrorMessageSource}, default message (if none of the keys matched), and variable
035 * substitution.
036 * 
037 * The final error message is constructed via the following process:
038 * <ol>
039 * <li>Try all keys added by calls to {@link #addKey(String)} via the provided
040 * <code>IErrorMessageSource</code>.</li>
041 * <li>If none of the keys yielded a message, use the message set by {@link #setMessage(String)}, if
042 * any.</li>
043 * <li>Perform variable substitution on the message, if any.</li>
044 * </ol>
045 * 
046 * @author Igor Vaynberg (ivaynberg)
047 * @since 1.2.6
048 */
049public final class ValidationError implements IValidationError
050{
051        private static final long serialVersionUID = 1L;
052
053        /** list of message keys to try against the <code>IErrorMessageSource</code> */
054        private List<String> keys;
055
056        /** variables map to use in variable substitution */
057        private Map<String, Object> vars;
058
059        /** default message used when all keys yield no message */
060        private String message;
061
062        /**
063         * Constructs an empty error
064         */
065        public ValidationError()
066        {
067
068        }
069
070        /**
071         * Constructs a validation error with the validator's standard key. Equivalent to calling
072         * {@link #addKey(IValidator)}
073         * 
074         * @param validator
075         *            validator
076         */
077        public ValidationError(IValidator<?> validator)
078        {
079                addKey(validator);
080        }
081
082        /**
083         * Constructs a validation error with a variation of validator's standard key. Equivalent to
084         * calling {@link #addKey(IValidator, String)}
085         * 
086         * @param validator
087         *            validator
088         * @param variation
089         *            key variation
090         * 
091         * 
092         */
093        public ValidationError(IValidator<?> validator, String variation)
094        {
095                addKey(validator, variation);
096        }
097
098        /**
099         * Constructs a validation error with the specified message. Equivalent to calling
100         * {@link #setMessage(String)}
101         * 
102         * @param message
103         *            message
104         */
105        public ValidationError(String message)
106        {
107                setMessage(message);
108        }
109
110        /**
111         * Adds a key to the list of keys that will be tried against <code>IErrorMessageSource</code> to
112         * locate the error message string.
113         * 
114         * @param key
115         *            a message key to be added
116         * @return this <code>ValidationError</code> for chaining purposes
117         */
118        public ValidationError addKey(String key)
119        {
120                Args.notEmpty(key, "key");
121
122                if (keys == null)
123                {
124                        keys = new ArrayList<>(1);
125                }
126                keys.add(key);
127                return this;
128        }
129
130
131        /**
132         * Shortcut for adding a standard message key which is the simple name of the validator' class
133         * 
134         * @param validator
135         *            validator
136         * @return {@code this}
137         */
138        public ValidationError addKey(IValidator<?> validator)
139        {
140                Args.notNull(validator, "validator");
141                addKey(Classes.simpleName(validator.getClass()));
142                return this;
143        }
144
145        /**
146         * Shortcut for adding a standard message key variation which is the simple name of the
147         * validator class followed by a dot and the {@literal variation}
148         * <p>
149         * If the variation is empty only the validator's simple class name is used
150         * </p>
151         * 
152         * @param validator
153         *            validator
154         * @param variation
155         *            key variation
156         * @return {@code this}
157         */
158        public ValidationError addKey(IValidator<?> validator, String variation)
159        {
160                Args.notNull(validator, "validator");
161                String key = Classes.simpleName(validator.getClass());
162                if (!Strings.isEmpty(variation))
163                {
164                        key = key + "." + variation.trim();
165                }
166                addKey(key);
167                return this;
168        }
169
170        /**
171         * Sets a key and value in the variables map for use in substitution.
172         * 
173         * @param name
174         *            a variable name
175         * @param value
176         *            a variable value
177         * @return this <code>ValidationError</code> for chaining purposes
178         */
179        public ValidationError setVariable(String name, Object value)
180        {
181                Args.notEmpty(name, "name");
182
183                getVariables().put(name, value);
184
185                return this;
186        }
187
188        /**
189         * Retrieves the variables map for this error. The caller is free to modify the contents.
190         * 
191         * @return a <code>Map</code> of variables for this error
192         */
193        public final Map<String, Object> getVariables()
194        {
195                if (vars == null)
196                {
197                        vars = new HashMap<>(2);
198                }
199                return vars;
200        }
201
202        /**
203         * Sets the variables map for this error.
204         * 
205         * @param vars
206         *            a variables map
207         * @return this <code>ValidationError</code> for chaining purposes
208         */
209        public final ValidationError setVariables(Map<String, Object> vars)
210        {
211                Args.notNull(vars, "vars");
212
213                this.vars = vars;
214                return this;
215        }
216
217        /**
218         * @see IValidationError#getErrorMessage(IErrorMessageSource)
219         */
220        @Override
221        public final Serializable getErrorMessage(IErrorMessageSource messageSource)
222        {
223                String errorMessage = null;
224
225                if (keys != null)
226                {
227                        // try any message keys ...
228                        for (String key : keys)
229                        {
230                                errorMessage = messageSource.getMessage(key, vars);
231                                if (errorMessage != null)
232                                {
233                                        break;
234                                }
235                        }
236                }
237
238                // ... if no keys matched try the default
239                if (errorMessage == null && message != null)
240                {
241                        errorMessage = message;
242                }
243
244                return new ValidationErrorFeedback(this, errorMessage);
245        }
246
247        /**
248         * Gets the default message that will be used when no message could be located via message keys.
249         * 
250         * @return message the default message used when all keys yield no message
251         */
252        public final String getMessage()
253        {
254                return message;
255        }
256
257        /**
258         * Sets message that will be used when no message could be located via message keys.
259         * <p>
260         * Note: No variable substitution is performed on the given message!
261         * 
262         * @param message
263         *            a default message to be used when all keys yield no message
264         * 
265         * @return this <code>ValidationError</code> for chaining purposes
266         */
267        public final ValidationError setMessage(String message)
268        {
269                Args.notNull(message, "message");
270
271                this.message = message;
272                return this;
273        }
274
275
276        /**
277         * Gets error keys
278         * 
279         * @return keys
280         */
281        public List<String> getKeys()
282        {
283                if (keys == null)
284                {
285                        keys = new ArrayList<>();
286                }
287                return keys;
288        }
289
290        /**
291         * Sets error keys
292         * 
293         * @param keys
294         */
295        public void setKeys(List<String> keys)
296        {
297                this.keys = keys;
298        }
299
300        /**
301         * @see java.lang.Object#toString()
302         */
303        @Override
304        public String toString()
305        {
306                StringBuilder tostring = new StringBuilder();
307                tostring.append('[').append(Classes.simpleName(getClass()));
308
309                tostring.append(" message=[").append(message);
310
311                tostring.append("], keys=[");
312                if (keys != null)
313                {
314                        Iterator<String> i = keys.iterator();
315                        while (i.hasNext())
316                        {
317                                tostring.append(i.next());
318                                if (i.hasNext())
319                                {
320                                        tostring.append(", ");
321                                }
322                        }
323                }
324                else
325                {
326                        tostring.append("null");
327                }
328                tostring.append("], variables=[");
329
330                if (vars != null)
331                {
332                        Iterator<Entry<String, Object>> i = vars.entrySet().iterator();
333                        while (i.hasNext())
334                        {
335                                final Entry<String, Object> e = i.next();
336                                tostring.append('[')
337                                        .append(e.getKey())
338                                        .append('=')
339                                        .append(e.getValue())
340                                        .append(']');
341                                if (i.hasNext())
342                                {
343                                        tostring.append(',');
344                                }
345                        }
346                }
347                else
348                {
349                        tostring.append("null");
350                }
351                tostring.append(']');
352
353                tostring.append(']');
354
355                return tostring.toString();
356        }
357
358}