/* Copyright 2005-2006 Tim Fennell * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.sourceforge.stripes.validation; import net.sourceforge.stripes.action.SimpleMessage; import net.sourceforge.stripes.action.ActionBean; import net.sourceforge.stripes.localization.LocalizationUtility; import net.sourceforge.stripes.util.Log; import java.util.Locale; /** * <p>Validation error message that allows for supplying the error message at the time of * creation - i.e. not through a resource bundle or other external mechanism. SimpleError * will still attempt to lookup the field name in the field name bundle, but as with other * errors, it will fall back to a prettified version of the field name that is used in the * input tag.</p> * * <p>Messages may contain one or more "replacement parameters". Two replacement * parameters are provided by default, they are the field name and field value, and are * indices 0 and 1 respectively. To use replacement parameters a message must contain the * replacement token {#} where # is the numeric index of the replacement parameter.</p> * * <p>For example, to construct an error message with one additional replacement parameter which is * the action the user was trying to perform, you might supply a message like:</p> * * <pre>{1} is not a valid {0} when trying to {2}</pre> * * <p>At runtime this might get replaced out to result in an error message for the user that looks * like "<em>Fixed</em> is not a valid <em>status</em> when trying to create a new * <em>bug</em>".</p> * * <p>One last point of interest is where the user friendly field name comes from. Firstly an * attempt is made to look up the localized name in the applicable resource bundle using the * String <em>beanClassFQN.fieldName</em> where beanClassFQN is the fully qualified name of the * bean class, and fieldName is the name of the field on the form. The second attempt is made with * String <em>actionPath.fieldName</em> where actionPath is the action of the form in the JSP * (or equally, the path given in the @UrlBinding annotation in the ActionBean class). Finally, * the last attempt uses fieldName by itself.</p> * * @see java.text.MessageFormat */ public class SimpleError extends SimpleMessage implements ValidationError { private static final long serialVersionUID = 1L; private static final Log log = Log.getInstance(SimpleError.class); private String fieldNameKey; private String actionPath; private Class<? extends ActionBean> beanclass; /** * Constructs a simple error message. * * @param message the String message (template) to display * @param parameter zero or more parameters for replacement into the message */ public SimpleError(String message, Object... parameter) { super(message, processReplacementParameters(parameter)); } /** * Helper method that is used to widen the replacement parameter array to make * room for the two standard parameters, the field name and field value. * * @param parameter zero or more replacement parameters */ static Object[] processReplacementParameters(Object... parameter) { if (parameter == null) { return new Object[2]; } else { Object[] out = new Object[parameter.length + 2]; System.arraycopy(parameter, 0, out, 2, parameter.length); return out; } } /** * Looks up the field name in the resource bundle (if it exists) so that it can be used * in the message, and then defers to its super class to combine the message template * with the replacement parameters provided. * * @param locale the locale of the current request * @return String the message stored under the messageKey supplied */ @Override public String getMessage(Locale locale) { resolveFieldName(locale); return super.getMessage(locale); } /** * Takes the form field name supplied and tries first to resolve a String in the locale * specific bundle for the field name, and if that fails, will try to make a semi-friendly * name by parsing the form field name. The result is stored in replacementParameters[0] * for message template parameter replacement. */ protected void resolveFieldName(Locale locale) { log.debug("Looking up localized field name with messageKey: ", this.fieldNameKey); if (this.fieldNameKey == null) { getReplacementParameters()[0] = "FIELD NAME NOT SUPPLIED IN CODE"; } else { getReplacementParameters()[0] = LocalizationUtility.getLocalizedFieldName(this.fieldNameKey, this.actionPath, this.beanclass, locale); if (getReplacementParameters()[0] == null) { getReplacementParameters()[0] = LocalizationUtility.makePseudoFriendlyName(this.fieldNameKey); } } } /** * Sets the name of the form field in error. This is the programmatic name, and hence probably * not the name that the user sees. */ public void setFieldName(String name) { this.fieldNameKey = name; } /** Provides subclasses access to the field name. */ public String getFieldName() { return this.fieldNameKey; } /** Sets the value of the field that is in error. */ public void setFieldValue(String value) { getReplacementParameters()[1] = value; } /** Provides subclasses with access to the value of the field that is in error. */ public String getFieldValue() { return (String) getReplacementParameters()[1]; } /** Sets the binding path of the ActionBean on which the errored field occurs. */ public void setActionPath(String actionPath) { this.actionPath = actionPath; } /** Provides subclasses access to the name of the form on which the errored field occurs. */ public String getActionPath() { return actionPath; } /** Returns the class of the ActionBean associated to the request. */ public Class<? extends ActionBean> getBeanclass() { return beanclass; } /** Sets the class of the ActionBean associated to the request. */ public void setBeanclass(final Class<? extends ActionBean> beanclass) { this.beanclass = beanclass; } /** Generated equals method that ensures the message, field, parameters and action path are all equal. */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; final SimpleError that = (SimpleError) o; if (actionPath != null ? !actionPath.equals(that.actionPath) : that.actionPath != null) return false; if (fieldNameKey != null ? !fieldNameKey.equals(that.fieldNameKey) : that.fieldNameKey != null) return false; return true; } /** Hash code based on the message, field name key, action path and parameters. */ @Override public int hashCode() { int result = super.hashCode(); result = 29 * result + (fieldNameKey != null ? fieldNameKey.hashCode() : 0); result = 29 * result + (actionPath != null ? actionPath.hashCode() : 0); return result; } }