package play.data.validation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sf.oval.ConstraintViolation;
import net.sf.oval.context.MethodParameterContext;
import net.sf.oval.guard.Guard;
import play.PlayPlugin;
import play.exceptions.ActionNotFoundException;
import play.exceptions.UnexpectedException;
import play.utils.Java;
import play.mvc.ActionInvoker;
import play.mvc.Http;
import play.mvc.Http.Cookie;
import play.mvc.Scope;
import play.mvc.results.Result;
public class ValidationPlugin extends PlayPlugin {
static ThreadLocal<Map<Object, String>> keys = new ThreadLocal<Map<Object, String>>();
@Override
public void beforeInvocation() {
keys.set(new HashMap<Object, String>());
Validation.current.set(new Validation());
}
@Override
public void beforeActionInvocation(Method actionMethod) {
try {
Validation.current.set(restore());
boolean verify = false;
for (Annotation[] annotations : actionMethod.getParameterAnnotations()) {
if (annotations.length > 0) {
verify = true;
break;
}
}
if (!verify) {
return;
}
List<ConstraintViolation> violations = new Validator().validateAction(actionMethod);
ArrayList<Error> errors = new ArrayList<Error>();
String[] paramNames = Java.parameterNames(actionMethod);
for (ConstraintViolation violation : violations) {
errors.add(new Error(paramNames[((MethodParameterContext) violation.getContext()).getParameterIndex()], violation.getMessage(), violation.getMessageVariables() == null ? new String[0] : violation.getMessageVariables().values().toArray(new String[0])));
}
Validation.current.get().errors.addAll(errors);
} catch (Exception e) {
throw new UnexpectedException(e);
}
}
@Override
public void onActionInvocationResult(Result result) {
save();
}
@Override
public void onInvocationException(Throwable e) {
clear();
}
@Override
public void invocationFinally() {
if (keys.get() != null) {
keys.get().clear();
}
keys.remove();
Validation.current.remove();
}
// ~~~~~~
static class Validator extends Guard {
public List<ConstraintViolation> validateAction(Method actionMethod) throws Exception {
List<ConstraintViolation> violations = new ArrayList<ConstraintViolation>();
Object instance = null;
// Patch for scala defaults
if (!Modifier.isStatic(actionMethod.getModifiers()) && actionMethod.getDeclaringClass().getSimpleName().endsWith("$")) {
try {
instance = actionMethod.getDeclaringClass().getDeclaredField("MODULE$").get(null);
} catch (Exception e) {
throw new ActionNotFoundException(Http.Request.current().action, e);
}
}
Object[] rArgs = ActionInvoker.getActionMethodArgs(actionMethod, instance);
validateMethodParameters(null, actionMethod, rArgs, violations);
validateMethodPre(null, actionMethod, rArgs, violations);
return violations;
}
}
static Pattern errorsParser = Pattern.compile("\u0000([^:]*):([^\u0000]*)\u0000");
static Validation restore() {
try {
Validation validation = new Validation();
Http.Cookie cookie = Http.Request.current().cookies.get(Scope.COOKIE_PREFIX + "_ERRORS");
if (cookie != null) {
String errorsData = URLDecoder.decode(cookie.value, "utf-8");
Matcher matcher = errorsParser.matcher(errorsData);
while (matcher.find()) {
String[] g2 = matcher.group(2).split("\u0001");
String message = g2[0];
String[] args = new String[g2.length - 1];
System.arraycopy(g2, 1, args, 0, args.length);
validation.errors.add(new Error(matcher.group(1), message, args));
}
}
return validation;
} catch (Exception e) {
return new Validation();
}
}
static void save() {
if (Http.Response.current() == null) {
// Some request like WebSocket don't have any response
return;
}
if (Validation.errors().isEmpty()) {
Http.Response.current().setCookie(Scope.COOKIE_PREFIX + "_ERRORS", "", "0s");
return;
}
try {
StringBuilder errors = new StringBuilder();
if (Validation.current() != null && Validation.current().keep) {
for (Error error : Validation.errors()) {
errors.append("\u0000");
errors.append(error.key);
errors.append(":");
errors.append(error.message);
for (String variable : error.variables) {
errors.append("\u0001");
errors.append(variable);
}
errors.append("\u0000");
}
}
String errorsData = URLEncoder.encode(errors.toString(), "utf-8");
Http.Response.current().setCookie(Scope.COOKIE_PREFIX + "_ERRORS", errorsData);
} catch (Exception e) {
throw new UnexpectedException("Errors serializationProblem", e);
}
}
static void clear() {
try {
if (Http.Response.current() != null && Http.Response.current().cookies != null) {
Cookie cookie = new Cookie();
cookie.name = Scope.COOKIE_PREFIX + "_ERRORS";
cookie.value = "";
cookie.sendOnError = true;
Http.Response.current().cookies.put(cookie.name, cookie);
}
} catch (Exception e) {
throw new UnexpectedException("Errors serializationProblem", e);
}
}
}