001 /*
002 * Copyright 2002-2004 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016 package org.springframework.rules.reporting;
017
018 import java.util.ArrayList;
019 import java.util.Iterator;
020 import java.util.List;
021 import java.util.Locale;
022
023 import org.apache.commons.logging.Log;
024 import org.apache.commons.logging.LogFactory;
025 import org.springframework.context.MessageSource;
026 import org.springframework.context.MessageSourceResolvable;
027 import org.springframework.context.support.DefaultMessageSourceResolvable;
028 import org.springframework.core.ReflectiveVisitorHelper;
029 import org.springframework.rules.constraint.Constraint;
030 import org.springframework.core.style.StylerUtils;
031 import org.springframework.rules.constraint.*;
032 import org.springframework.rules.constraint.property.CompoundPropertyConstraint;
033 import org.springframework.rules.constraint.property.ParameterizedPropertyConstraint;
034 import org.springframework.rules.constraint.property.PropertiesConstraint;
035 import org.springframework.rules.constraint.property.PropertyConstraint;
036 import org.springframework.rules.constraint.property.PropertyValueConstraint;
037 import org.springframework.util.Assert;
038 import org.springframework.util.ClassUtils;
039
040 /**
041 * @author Keith Donald
042 */
043 public class DefaultMessageTranslator implements MessageTranslator,
044 ObjectNameResolver {
045
046 protected static final Log logger = LogFactory
047 .getLog(DefaultMessageTranslator.class);
048
049 private ReflectiveVisitorHelper visitorSupport = new ReflectiveVisitorHelper();
050
051 private List args = new ArrayList();
052
053 private MessageSource messages;
054
055 private ObjectNameResolver objectNameResolver;
056
057 private Locale locale;
058
059 public DefaultMessageTranslator(MessageSource messages) {
060 this(messages, null);
061 }
062
063 public DefaultMessageTranslator(MessageSource messages, ObjectNameResolver objectNameResolver) {
064 this(messages, objectNameResolver, null);
065 }
066
067 public DefaultMessageTranslator(MessageSource messages,
068 ObjectNameResolver objectNameResolver, Locale locale) {
069 setMessageSource(messages);
070 this.objectNameResolver = objectNameResolver;
071 this.locale = locale;
072 }
073
074 public void setMessageSource(MessageSource messageSource) {
075 Assert.notNull(messageSource, "messageSource is required");
076 this.messages = messageSource;
077 }
078
079 /*
080 * (non-Javadoc)
081 *
082 * @see org.springframework.rules.reporting.MessageTranslator#getMessage(org.springframework.rules.constraint.Constraint)
083 */
084 public String getMessage(Constraint constraint) {
085 String objectName = null;
086 if (constraint instanceof PropertyConstraint) {
087 objectName = ((PropertyConstraint) constraint).getPropertyName();
088 }
089 String message = buildMessage(objectName, null, constraint);
090 return message;
091 }
092
093 public String getMessage(String objectName, Constraint constraint) {
094 return buildMessage(objectName, null, constraint);
095 }
096
097 public String getMessage(String objectName, Object rejectedValue,
098 Constraint constraint) {
099 return buildMessage(objectName, rejectedValue, constraint);
100 }
101
102 public String getMessage(String objectName, ValidationResults results) {
103 return buildMessage(objectName, results.getRejectedValue(), results
104 .getViolatedConstraint());
105 }
106
107 public String getMessage(PropertyResults results) {
108 Assert.notNull(results, "No property results specified");
109 return buildMessage(results.getPropertyName(), results
110 .getRejectedValue(), results.getViolatedConstraint());
111 }
112
113 private String buildMessage(String objectName, Object rejectedValue,
114 Constraint constraint) {
115 StringBuffer buf = new StringBuffer(255);
116 MessageSourceResolvable[] args = resolveArguments(constraint);
117 if (logger.isDebugEnabled()) {
118 logger.debug(StylerUtils.style(args));
119 }
120 if (objectName != null) {
121 buf.append(resolveObjectName(objectName));
122 buf.append(' ');
123 }
124 for (int i = 0; i < args.length - 1; i++) {
125 MessageSourceResolvable arg = args[i];
126 buf.append(messages.getMessage(arg, locale));
127 buf.append(' ');
128 }
129 buf.append(messages.getMessage(args[args.length - 1], locale));
130 buf.append(".");
131 return buf.toString();
132 }
133
134 private MessageSourceResolvable[] resolveArguments(Constraint constraint) {
135 args.clear();
136 visitorSupport.invokeVisit(this, constraint);
137 return (MessageSourceResolvable[]) args
138 .toArray(new MessageSourceResolvable[0]);
139 }
140
141 void visit(CompoundPropertyConstraint rule) {
142 visitorSupport.invokeVisit(this, rule.getPredicate());
143 }
144
145 void visit(PropertiesConstraint e) {
146 add(
147 getMessageCode(e.getConstraint()),
148 new Object[] { resolveObjectName(e.getOtherPropertyName()) },
149 e.toString());
150 }
151
152 void visit(ParameterizedPropertyConstraint e) {
153 add(getMessageCode(e.getConstraint()),
154 new Object[] { e.getParameter() }, e.toString());
155 }
156
157 public void add(String code, Object[] args, String defaultMessage) {
158 MessageSourceResolvable resolvable = new DefaultMessageSourceResolvable(
159 new String[] { code }, args, defaultMessage);
160 if (logger.isDebugEnabled()) {
161 logger.debug("Adding resolvable: " + resolvable);
162 }
163 this.args.add(resolvable);
164 }
165
166 public String resolveObjectName(String objectName) {
167 if(objectNameResolver != null)
168 return objectNameResolver.resolveObjectName(objectName);
169 return messages.getMessage(objectName, null,
170 new DefaultBeanPropertyNameRenderer()
171 .renderShortName(objectName), locale);
172 }
173
174 void visit(PropertyValueConstraint valueConstraint) {
175 visitorSupport.invokeVisit(this, valueConstraint.getConstraint());
176 }
177
178 /**
179 * Visit function for compound constraints like And/Or/XOr.
180 *
181 * <p>syntax: <code>CONSTRAINT MESSAGE{code} CONSTRAINT</code></p>
182 *
183 * @param compoundConstraint
184 */
185 void visit(CompoundConstraint compoundConstraint) {
186 Iterator it = compoundConstraint.iterator();
187 String compoundMessage = getMessageCode(compoundConstraint);
188 while (it.hasNext()) {
189 Constraint p = (Constraint) it.next();
190 visitorSupport.invokeVisit(this, p);
191 if (it.hasNext()) {
192 add(compoundMessage, null, compoundMessage);
193 }
194 }
195 }
196
197 void visit(Not not) {
198 add("not", null, "not");
199 visitorSupport.invokeVisit(this, not.getConstraint());
200 }
201
202 // @TODO - consider standard visitor here...
203 void visit(StringLengthConstraint constraint) {
204 ClosureResultConstraint c = (ClosureResultConstraint) constraint
205 .getPredicate();
206 Object p = c.getPredicate();
207 MessageSourceResolvable resolvable;
208 if (p instanceof ParameterizedBinaryConstraint) {
209 resolvable = handleParameterizedBinaryPredicate((ParameterizedBinaryConstraint) p);
210 } else {
211 resolvable = handleRange((Range) p);
212 }
213 Object[] args = new Object[] { resolvable };
214 add(getMessageCode(constraint), args, constraint.toString());
215 }
216
217 void visit(ClosureResultConstraint c) {
218 visitorSupport.invokeVisit(this, c.getPredicate());
219 }
220
221 private MessageSourceResolvable handleParameterizedBinaryPredicate(
222 ParameterizedBinaryConstraint p) {
223 MessageSourceResolvable resolvable = new DefaultMessageSourceResolvable(
224 new String[] { getMessageCode(p.getConstraint()) },
225 new Object[] { p.getParameter() }, p.toString());
226 return resolvable;
227 }
228
229 private MessageSourceResolvable handleRange(Range r) {
230 MessageSourceResolvable resolvable = new DefaultMessageSourceResolvable(
231 new String[] { getMessageCode(r) }, new Object[] { r.getMin(),
232 r.getMax() }, r.toString());
233 return resolvable;
234 }
235
236 void visit(Constraint constraint) {
237 if (constraint instanceof Range) {
238 this.args.add(handleRange((Range) constraint));
239 } else if (constraint instanceof ParameterizedBinaryConstraint) {
240 this.args.add(handleParameterizedBinaryPredicate((ParameterizedBinaryConstraint)constraint));
241 } else {
242 add(getMessageCode(constraint), null, constraint.toString());
243 }
244 }
245
246 /**
247 * Determines the messageCode (key in messageSource) to look up.
248 * If <code>TypeResolvable</code> is implemented, user can give a custom code,
249 * otherwise the short className is used.
250 *
251 * @param o
252 * @return
253 */
254 protected String getMessageCode(Object o) {
255 if (o instanceof TypeResolvable) {
256 String type = ((TypeResolvable) o).getType();
257 if (type != null) {
258 return type;
259 }
260 }
261 return ClassUtils.getShortNameAsProperty(o.getClass());
262 }
263 }