001    /*******************************************************************************
002     * Portions created by Sebastian Thomschke are copyright (c) 2005-2011 Sebastian
003     * Thomschke.
004     * 
005     * All Rights Reserved. This program and the accompanying materials
006     * are made available under the terms of the Eclipse Public License v1.0
007     * which accompanies this distribution, and is available at
008     * http://www.eclipse.org/legal/epl-v10.html
009     * 
010     * Contributors:
011     *     Sebastian Thomschke - initial implementation.
012     *******************************************************************************/
013    package net.sf.oval;
014    
015    import static net.sf.oval.Validator.*;
016    
017    import java.io.Serializable;
018    import java.util.Collections;
019    import java.util.Map;
020    
021    import net.sf.oval.context.OValContext;
022    import net.sf.oval.expression.ExpressionLanguage;
023    
024    /**
025     * Partial implementation of check classes.
026     * 
027     * @author Sebastian Thomschke
028     */
029    public abstract class AbstractCheck implements Check
030    {
031            private static final long serialVersionUID = 1L;
032    
033            private OValContext context;
034            private String errorCode;
035            private String message;
036            private Map<String, ? extends Serializable> messageVariables;
037            private Map<String, ? extends Serializable> messageVariablesUnmodifiable;
038            private boolean messageVariablesUpToDate = true;
039            private String[] profiles;
040            private int severity;
041            private ConstraintTarget[] appliesTo;
042            private String target;
043            private String when;
044            private transient String whenFormula;
045            private transient String whenLang;
046    
047            protected Map<String, ? extends Serializable> createMessageVariables()
048            {
049                    return null;
050            }
051    
052            /**
053             * {@inheritDoc}
054             */
055            public ConstraintTarget[] getAppliesTo()
056            {
057                    return appliesTo == null ? getAppliesToDefault() : appliesTo;
058            }
059    
060            /**
061             * 
062             * @return the default behavior when the constraint is validated for a array/map/collection reference.
063             */
064            protected ConstraintTarget[] getAppliesToDefault()
065            {
066                    // default behavior is only validate the array/map/collection reference and not the contained keys/values
067                    return new ConstraintTarget[]{ConstraintTarget.CONTAINER};
068            }
069    
070            /**
071             * {@inheritDoc}
072             */
073            public OValContext getContext()
074            {
075                    return context;
076            }
077    
078            /**
079             * {@inheritDoc}
080             */
081            public String getErrorCode()
082            {
083                    /*
084                     * if the error code has not been initialized (which might be the case when using XML configuration),
085                     * construct the string based on this class' name minus the appendix "Check"
086                     */
087                    if (errorCode == null)
088                    {
089                            final String className = getClass().getName();
090                            if (className.endsWith("Check"))
091                                    errorCode = className.substring(0, getClass().getName().length() - "Check".length());
092                            else
093                                    errorCode = className;
094                    }
095                    return errorCode;
096            }
097    
098            /**
099             * {@inheritDoc}
100             */
101            public String getMessage()
102            {
103                    /*
104                     * if the message has not been initialized (which might be the case when using XML configuration),
105                     * construct the string based on this class' name minus the appendix "Check" plus the appendix ".violated"
106                     */
107                    if (message == null)
108                    {
109                            final String className = getClass().getName();
110                            if (className.endsWith("Check"))
111                                    message = className.substring(0, getClass().getName().length() - "Check".length()) + ".violated";
112                            else
113                                    message = className + ".violated";
114                    }
115                    return message;
116            }
117    
118            /**
119             * Values that are used to fill place holders when rendering the error message.
120             * A key "min" with a value "4" will replace the place holder {min} in an error message
121             * like "Value cannot be smaller than {min}" with the string "4".
122             * 
123             * <b>Note:</b> Override {@link #createMessageVariables()} to create and fill the map
124             * 
125             * @return an unmodifiable map
126             */
127            public final Map<String, ? extends Serializable> getMessageVariables()
128            {
129                    if (!messageVariablesUpToDate)
130                    {
131                            messageVariables = createMessageVariables();
132                            if (messageVariables == null)
133                                    messageVariablesUnmodifiable = null;
134                            else
135                                    messageVariablesUnmodifiable = Collections.unmodifiableMap(messageVariables);
136                            messageVariablesUpToDate = true;
137                    }
138                    return messageVariablesUnmodifiable;
139            }
140    
141            /**
142             * {@inheritDoc}
143             */
144            public String[] getProfiles()
145            {
146                    return profiles;
147            }
148    
149            /**
150             * {@inheritDoc}
151             */
152            public int getSeverity()
153            {
154                    return severity;
155            }
156    
157            /**
158             * @return the target
159             */
160            public String getTarget()
161            {
162                    return target;
163            }
164    
165            /**
166             * {@inheritDoc}
167             */
168            public String getWhen()
169            {
170                    return when;
171            }
172    
173            /**
174             * {@inheritDoc}
175             */
176            public boolean isActive(final Object validatedObject, final Object valueToValidate, final Validator validator)
177            {
178                    if (when == null) return true;
179    
180                    // this triggers parsing of when, happens when this check instance was deserialized
181                    if (whenLang == null) setWhen(when);
182    
183                    final Map<String, Object> values = getCollectionFactory().createMap();
184                    values.put("_value", valueToValidate);
185                    values.put("_this", validatedObject);
186    
187                    final ExpressionLanguage el = validator.getExpressionLanguageRegistry().getExpressionLanguage(whenLang);
188                    return el.evaluateAsBoolean(whenFormula, values);
189            }
190    
191            /**
192             * Calling this method indicates that the {@link #createMessageVariables()} method needs to be called before the message 
193             * for the next violation of this check is rendered.
194             */
195            protected void requireMessageVariablesRecreation()
196            {
197                    messageVariablesUpToDate = false;
198            }
199    
200            /**
201             * {@inheritDoc}
202             */
203            public void setAppliesTo(final ConstraintTarget... targets)
204            {
205                    appliesTo = targets;
206            }
207    
208            /**
209             * {@inheritDoc}
210             */
211            public void setContext(final OValContext context)
212            {
213                    this.context = context;
214            }
215    
216            /**
217             * {@inheritDoc}
218             */
219            public void setErrorCode(final String failureCode)
220            {
221                    errorCode = failureCode;
222            }
223    
224            /**
225             * {@inheritDoc}
226             */
227            public void setMessage(final String message)
228            {
229                    this.message = message;
230            }
231    
232            /**
233             * {@inheritDoc}
234             */
235            public void setProfiles(final String... profiles)
236            {
237                    this.profiles = profiles;
238            }
239    
240            /**
241             * {@inheritDoc}
242             */
243            public void setSeverity(final int severity)
244            {
245                    this.severity = severity;
246            }
247    
248            /**
249             * @param target the target to set
250             */
251            public void setTarget(final String target)
252            {
253                    this.target = target;
254            }
255    
256            /**
257             * {@inheritDoc}
258             */
259            public void setWhen(final String when)
260            {
261                    synchronized (this)
262                    {
263                            if (when == null || when.length() == 0)
264                            {
265                                    this.when = null;
266                                    whenFormula = null;
267                                    whenLang = null;
268                            }
269                            else
270                            {
271                                    final String[] parts = when.split(":", 2);
272                                    if (parts.length == 0)
273                                            throw new IllegalArgumentException("[when] is missing the scripting language declaration");
274                                    this.when = when;
275                                    whenLang = parts[0];
276                                    whenFormula = parts[1];
277                            }
278                    }
279            }
280    }