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 }