package org.springmodules.validation.bean.annotation.javascript.taglib; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import javax.servlet.jsp.JspWriter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.support.MessageSourceAccessor; import org.springmodules.validation.valang.javascript.AbstractValangJavaScriptTranslator; public class CommandObjectToValangConverter extends AbstractValangJavaScriptTranslator { public enum AnnotationLocation { METHOD, FIELD, CLASS } public static final String CLASS_RULENAME = "valang-global-rules"; private static final String FIRST_LINE_PREFIX = "\n"; private static final String LINE_PREFIX = ",\n"; String prefix; private final static Log logger = LogFactory.getLog(CommandObjectToValangConverter.class); Collection<Handler> supportedFieldMethodAnnotations = new ArrayList<Handler>(); Collection<Handler> supportedClassAnnotations = new ArrayList<Handler>(); public CommandObjectToValangConverter() { super(); // class level registerClassAnnotationHandler(new ExpressionHandler()); registerClassAnnotationHandler(new ExpressionsHandler()); // fields / methods registerFieldMethodAnnotationHandler(new EmailHandler()); registerFieldMethodAnnotationHandler(new ExpressionHandler()); registerFieldMethodAnnotationHandler(new ExpressionsHandler()); registerFieldMethodAnnotationHandler(new InThePastFutureHandler()); registerFieldMethodAnnotationHandler(new LengthSizeHandler()); registerFieldMethodAnnotationHandler(new MaxHandler()); registerFieldMethodAnnotationHandler(new MaxLengthSizeHandler()); registerFieldMethodAnnotationHandler(new MinHandler()); registerFieldMethodAnnotationHandler(new MinLengthSizeHandler()); registerFieldMethodAnnotationHandler(new NotBlankEmptyHandler()); registerFieldMethodAnnotationHandler(new NotNullHandler()); registerFieldMethodAnnotationHandler(new RangeHandler()); registerFieldMethodAnnotationHandler(new RegExpHandler()); } public void writeJS(String commandName, Object commandObj, String globalVar, boolean validateOnSubmit, JspWriter out, MessageSourceAccessor messages) throws IOException { try { setWriter(out); prefix = FIRST_LINE_PREFIX; // No comma for first line if (globalVar != null) { out.write("var " + globalVar + " = "); } out.write("new ValangValidator("); appendJsString(commandName); append(','); append(Boolean.toString(true)); // install to the form append(", new Array("); Class<? extends Object> commandObjClass = commandObj.getClass(); // class level Annotation[] annotations = commandObjClass.getAnnotations(); extractAnnotations(commandObj, messages, CLASS_RULENAME, annotations, AnnotationLocation.CLASS); // field level: traverse superclasses, as getDeclaredFields() doesn't, and getFields() excludes private fields Class<? extends Object> commandOrSuper = commandObjClass; do { for (Field field : commandOrSuper.getDeclaredFields()) { annotations = field.getAnnotations(); extractAnnotations(commandObj, messages, field.getName(), annotations, AnnotationLocation.FIELD); } commandOrSuper = commandOrSuper.getSuperclass(); } while (commandOrSuper != null); // method level for (Method m : commandObjClass.getMethods()) { annotations = m.getAnnotations(); extractAnnotations(commandObj, messages, m.getName(), annotations, AnnotationLocation.METHOD); } append("\n),"); append(Boolean.toString(validateOnSubmit) + ");"); } finally { clearWriter(); } } /** * Extracts valang from the annotations supplied. * * @param commandObj * @param messages * @param name * The field/method the annotations are from. If null * @param annotations * @throws IOException */ private void extractAnnotations(Object commandObj, MessageSourceAccessor messages, String name, Annotation[] annotations, AnnotationLocation aLoc) throws IOException { for (Annotation annotation : annotations) { Handler hndl = (aLoc == AnnotationLocation.CLASS) ? getClassHandler(annotation) : getFieldMethodHandler(annotation); if (hndl != null) { if (hndl.isDelegateAnnotations()) { Annotation[] delegateAnnotations = hndl.getDelegateAnnotations(annotation, name); if (delegateAnnotations != null) { extractAnnotations(commandObj, messages, name, delegateAnnotations, aLoc); } continue; } String propName = (aLoc == AnnotationLocation.METHOD) ? convertMethodNameToProperty(name) : name; String valangJS = hndl.convertToValang(propName, annotation, messages); if (valangJS != null) { append(prefix + valangJS); prefix = LINE_PREFIX; } if (logger.isDebugEnabled()) { logger.debug("Annotation " + annotation.getClass() + " on command object " + commandObj.getClass() + " generated: " + valangJS); } } else if (logger.isWarnEnabled()) { logger.warn("Unsupported annotation " + annotation.getClass() + " on command object " + commandObj.getClass()); } } } private Handler getClassHandler(Annotation annotation) { return getHandler(annotation, supportedClassAnnotations); } private Handler getFieldMethodHandler(Annotation annotation) { return getHandler(annotation, supportedFieldMethodAnnotations); } private Handler getHandler(Annotation annotation, Collection<Handler> handlers) { for (Handler h : handlers) { if (h.supports(annotation)) { return h; } } if (logger.isDebugEnabled()) { logger.debug("Annotation not supported: " + annotation); } return null; } public void registerFieldMethodAnnotationHandler(Handler handler) { if (handler == null) { return; } supportedFieldMethodAnnotations.add(handler); } public void registerClassAnnotationHandler(Handler handler) { if (handler == null) { return; } supportedClassAnnotations.add(handler); } public void setRegisterFieldMethodHandlers(Collection<Handler> handlers) { for (Handler h : handlers) { registerFieldMethodAnnotationHandler(h); } } public void setRegisterClassHandlers(Collection<Handler> handlers) { for (Handler h : handlers) { registerClassAnnotationHandler(h); } } public String convertMethodNameToProperty(String in) { if (in == null) { return null; } // TODO find elegant spring way String out = in.replaceFirst("^is", "").replaceFirst("^get", "").replaceFirst("^set", ""); if (out.length() == 0) { return null; } out = out.substring(0, 1).toLowerCase() + out.substring(1); // drop first letter case return out; } }