/** * Copyright (C) 2015 Valkyrie RCP * * 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 org.valkyriercp.rules.reporting; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceResolvable; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.core.style.StylerUtils; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.valkyriercp.rules.constraint.*; import org.valkyriercp.rules.constraint.property.*; import org.valkyriercp.util.ReflectiveVisitorHelper; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; /** * @author Keith Donald */ public class DefaultMessageTranslator implements MessageTranslator, ObjectNameResolver { protected static final Log logger = LogFactory .getLog(DefaultMessageTranslator.class); private ReflectiveVisitorHelper visitorSupport = new ReflectiveVisitorHelper(); private List args = new ArrayList(); private MessageSource messages; private ObjectNameResolver objectNameResolver; private Locale locale; public DefaultMessageTranslator(MessageSource messages) { this(messages, null); } public DefaultMessageTranslator(MessageSource messages, ObjectNameResolver objectNameResolver) { this(messages, objectNameResolver, null); } public DefaultMessageTranslator(MessageSource messages, ObjectNameResolver objectNameResolver, Locale locale) { setMessageSource(messages); this.objectNameResolver = objectNameResolver; this.locale = locale; } public void setMessageSource(MessageSource messageSource) { Assert.notNull(messageSource, "messageSource is required"); this.messages = messageSource; } /* * (non-Javadoc) * * @see org.springframework.rules.reporting.MessageTranslator#getMessage(org.springframework.rules.constraint.Constraint) */ public String getMessage(Constraint constraint) { String objectName = null; if (constraint instanceof PropertyConstraint) { objectName = ((PropertyConstraint) constraint).getPropertyName(); } String message = buildMessage(objectName, null, constraint); return message; } public String getMessage(String objectName, Constraint constraint) { return buildMessage(objectName, null, constraint); } public String getMessage(String objectName, Object rejectedValue, Constraint constraint) { return buildMessage(objectName, rejectedValue, constraint); } public String getMessage(String objectName, ValidationResults results) { return buildMessage(objectName, results.getRejectedValue(), results .getViolatedConstraint()); } public String getMessage(PropertyResults results) { Assert.notNull(results, "No property results specified"); return buildMessage(results.getPropertyName(), results .getRejectedValue(), results.getViolatedConstraint()); } protected String buildMessage(String objectName, Object rejectedValue, Constraint constraint) { StringBuffer buf = new StringBuffer(255); MessageSourceResolvable[] args = resolveArguments(constraint); if (logger.isDebugEnabled()) { logger.debug(StylerUtils.style(args)); } if (objectName != null) { buf.append(resolveObjectName(objectName)); buf.append(' '); } for (int i = 0; i < args.length - 1; i++) { MessageSourceResolvable arg = args[i]; buf.append(messages.getMessage(arg, locale)); buf.append(' '); } buf.append(messages.getMessage(args[args.length - 1], locale)); buf.append("."); return buf.toString(); } private MessageSourceResolvable[] resolveArguments(Constraint constraint) { args.clear(); visitorSupport.invokeVisit(this, constraint); return (MessageSourceResolvable[]) args .toArray(new MessageSourceResolvable[0]); } protected void visit(CompoundPropertyConstraint rule) { visitorSupport.invokeVisit(this, rule.getPredicate()); } protected void visit(PropertiesConstraint e) { add( getMessageCode(e.getConstraint()), new Object[] { resolveObjectName(e.getOtherPropertyName()) }, e.toString()); } protected void visit(ParameterizedPropertyConstraint e) { add(getMessageCode(e.getConstraint()), new Object[] { e.getParameter() }, e.toString()); } public void add(String code, Object[] args, String defaultMessage) { MessageSourceResolvable resolvable = new DefaultMessageSourceResolvable( new String[] { code }, args, defaultMessage); if (logger.isDebugEnabled()) { logger.debug("Adding resolvable: " + resolvable); } this.args.add(resolvable); } public String resolveObjectName(String objectName) { if(objectNameResolver != null) return objectNameResolver.resolveObjectName(objectName); return messages.getMessage(objectName, null, new DefaultBeanPropertyNameRenderer() .renderShortName(objectName), locale); } protected void visit(PropertyValueConstraint valueConstraint) { visitorSupport.invokeVisit(this, valueConstraint.getConstraint()); } /** * Visit function for compound constraints like And/Or/XOr. * * <p>syntax: <code>CONSTRAINT MESSAGE{code} CONSTRAINT</code></p> * * @param compoundConstraint */ protected void visit(CompoundConstraint compoundConstraint) { Iterator it = compoundConstraint.iterator(); String compoundMessage = getMessageCode(compoundConstraint); while (it.hasNext()) { Constraint p = (Constraint) it.next(); visitorSupport.invokeVisit(this, p); if (it.hasNext()) { add(compoundMessage, null, compoundMessage); } } } protected void visit(Not not) { add("not", null, "not"); visitorSupport.invokeVisit(this, not.getConstraint()); } // @TODO - consider standard visitor here... protected void visit(StringLengthConstraint constraint) { ClosureResultConstraint c = (ClosureResultConstraint) constraint .getPredicate(); Object p = c.getPredicate(); MessageSourceResolvable resolvable; if (p instanceof ParameterizedBinaryConstraint) { resolvable = handleParameterizedBinaryPredicate((ParameterizedBinaryConstraint) p); } else { resolvable = handleRange((Range) p); } Object[] args = new Object[] { resolvable }; add(getMessageCode(constraint), args, constraint.toString()); } protected void visit(ClosureResultConstraint c) { visitorSupport.invokeVisit(this, c.getPredicate()); } private MessageSourceResolvable handleParameterizedBinaryPredicate( ParameterizedBinaryConstraint p) { MessageSourceResolvable resolvable = new DefaultMessageSourceResolvable( new String[] { getMessageCode(p.getConstraint()) }, new Object[] { p.getParameter() }, p.toString()); return resolvable; } private MessageSourceResolvable handleRange(Range r) { MessageSourceResolvable resolvable = new DefaultMessageSourceResolvable( new String[] { getMessageCode(r) }, new Object[] { r.getMin(), r.getMax() }, r.toString()); return resolvable; } protected void visit(Constraint constraint) { if (constraint instanceof Range) { this.args.add(handleRange((Range) constraint)); } else if (constraint instanceof ParameterizedBinaryConstraint) { this.args.add(handleParameterizedBinaryPredicate((ParameterizedBinaryConstraint)constraint)); } else { add(getMessageCode(constraint), null, constraint.toString()); } } /** * Determines the messageCode (key in messageSource) to look up. * If <code>TypeResolvable</code> is implemented, user can give a custom code, * otherwise the short className is used. * * @param o * @return */ protected String getMessageCode(Object o) { if (o instanceof TypeResolvable) { String type = ((TypeResolvable) o).getType(); if (type != null) { return type; } } return ClassUtils.getShortNameAsProperty(o.getClass()); } }