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    }