PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/framework/src/play/data/validation/ValidationPlugin.java

http://github.com/playframework/play
Java | 201 lines | 172 code | 18 blank | 11 comment | 29 complexity | 7619ded80c25a14bd4a65fa8ebe77568 MD5 | raw file
Possible License(s): Apache-2.0
  1. package play.data.validation;
  2. import net.sf.oval.ConstraintViolation;
  3. import net.sf.oval.context.MethodParameterContext;
  4. import net.sf.oval.guard.Guard;
  5. import play.PlayPlugin;
  6. import play.exceptions.ActionNotFoundException;
  7. import play.exceptions.UnexpectedException;
  8. import play.mvc.ActionInvoker;
  9. import play.mvc.Http;
  10. import play.mvc.Http.Cookie;
  11. import play.mvc.Scope;
  12. import play.mvc.results.Result;
  13. import play.utils.Java;
  14. import java.lang.annotation.Annotation;
  15. import java.lang.reflect.Method;
  16. import java.lang.reflect.Modifier;
  17. import java.net.URLDecoder;
  18. import java.net.URLEncoder;
  19. import java.util.ArrayList;
  20. import java.util.HashMap;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.regex.Matcher;
  24. import java.util.regex.Pattern;
  25. public class ValidationPlugin extends PlayPlugin {
  26. public static final ThreadLocal<Map<Object, String>> keys = new ThreadLocal<>();
  27. private boolean isAwakingFromAwait() {
  28. Http.Request request = Http.Request.current();
  29. if (request == null) {
  30. return false;
  31. }
  32. // if CONTINUATIONS_STORE_VALIDATIONS is present we know that
  33. // we are awaking from await()
  34. return request.args.containsKey(ActionInvoker.CONTINUATIONS_STORE_VALIDATIONS);
  35. }
  36. @Override
  37. public void beforeInvocation() {
  38. keys.set(new HashMap<Object, String>());
  39. Validation.current.set(new Validation());
  40. }
  41. @Override
  42. public void beforeActionInvocation(Method actionMethod) {
  43. // when using await, this code get called multiple times.
  44. // When recovering from await() we're going to restore (overwrite) validation.current
  45. // with the object-instance from the previous part of the execution.
  46. // If this is happening it is no point in doing anything here, since
  47. // we overwrite it later on.
  48. if (isAwakingFromAwait()) {
  49. return ;
  50. }
  51. try {
  52. Validation.current.set(restore());
  53. boolean verify = false;
  54. for (Annotation[] annotations : actionMethod.getParameterAnnotations()) {
  55. if (annotations.length > 0) {
  56. verify = true;
  57. break;
  58. }
  59. }
  60. if (!verify) {
  61. return;
  62. }
  63. List<ConstraintViolation> violations = new Validator().validateAction(actionMethod);
  64. ArrayList<Error> errors = new ArrayList<>();
  65. String[] paramNames = Java.parameterNames(actionMethod);
  66. for (ConstraintViolation violation : violations) {
  67. errors.add(new Error(
  68. paramNames[((MethodParameterContext) violation
  69. .getContext()).getParameterIndex()], violation
  70. .getMessage(),
  71. violation.getMessageVariables() == null ? new String[0]
  72. : violation.getMessageVariables().values()
  73. .toArray(new String[0]), violation
  74. .getSeverity()));
  75. }
  76. Validation.current.get().errors.addAll(errors);
  77. } catch (Exception e) {
  78. throw new UnexpectedException(e);
  79. }
  80. }
  81. @Override
  82. public void onActionInvocationResult(Result result) {
  83. save();
  84. }
  85. @Override
  86. public void onInvocationException(Throwable e) {
  87. clear();
  88. }
  89. @Override
  90. public void invocationFinally() {
  91. if (keys.get() != null) {
  92. keys.get().clear();
  93. }
  94. keys.remove();
  95. Validation.current.remove();
  96. }
  97. // ~~~~~~
  98. static class Validator extends Guard {
  99. public List<ConstraintViolation> validateAction(Method actionMethod) throws Exception {
  100. List<ConstraintViolation> violations = new ArrayList<>();
  101. Object instance = null;
  102. // Patch for scala defaults
  103. if (!Modifier.isStatic(actionMethod.getModifiers()) && actionMethod.getDeclaringClass().getSimpleName().endsWith("$")) {
  104. try {
  105. instance = actionMethod.getDeclaringClass().getDeclaredField("MODULE$").get(null);
  106. } catch (Exception e) {
  107. throw new ActionNotFoundException(Http.Request.current().action, e);
  108. }
  109. }
  110. Object[] rArgs = ActionInvoker.getActionMethodArgs(actionMethod, instance);
  111. validateMethodParameters(null, actionMethod, rArgs, violations);
  112. validateMethodPre(null, actionMethod, rArgs, violations);
  113. return violations;
  114. }
  115. }
  116. static Pattern errorsParser = Pattern.compile("\u0000([^:]*):([^\u0000]*)\u0000");
  117. static Validation restore() {
  118. try {
  119. Validation validation = new Validation();
  120. Http.Cookie cookie = Http.Request.current().cookies.get(Scope.COOKIE_PREFIX + "_ERRORS");
  121. if (cookie != null) {
  122. String errorsData = URLDecoder.decode(cookie.value, "utf-8");
  123. Matcher matcher = errorsParser.matcher(errorsData);
  124. while (matcher.find()) {
  125. String[] g2 = matcher.group(2).split("\u0001", -1);
  126. String message = g2[0];
  127. String[] args = new String[g2.length - 1];
  128. System.arraycopy(g2, 1, args, 0, args.length);
  129. validation.errors.add(new Error(matcher.group(1), message, args));
  130. }
  131. }
  132. return validation;
  133. } catch (Exception e) {
  134. return new Validation();
  135. }
  136. }
  137. static void save() {
  138. if (Http.Response.current() == null) {
  139. // Some request like WebSocket don't have any response
  140. return;
  141. }
  142. if (Validation.errors().isEmpty()) {
  143. // Only send "delete cookie" header when the cookie was present in the request
  144. if(Http.Request.current().cookies.containsKey(Scope.COOKIE_PREFIX + "_ERRORS") || !Scope.SESSION_SEND_ONLY_IF_CHANGED) {
  145. Http.Response.current().setCookie(Scope.COOKIE_PREFIX + "_ERRORS", "", null, "/", 0, Scope.COOKIE_SECURE, Scope.SESSION_HTTPONLY);
  146. }
  147. return;
  148. }
  149. try {
  150. StringBuilder errors = new StringBuilder();
  151. if (Validation.current() != null && Validation.current().keep) {
  152. for (Error error : Validation.errors()) {
  153. errors.append("\u0000");
  154. errors.append(error.key);
  155. errors.append(":");
  156. errors.append(error.message);
  157. for (String variable : error.variables) {
  158. errors.append("\u0001");
  159. errors.append(variable);
  160. }
  161. errors.append("\u0000");
  162. }
  163. }
  164. String errorsData = URLEncoder.encode(errors.toString(), "utf-8");
  165. Http.Response.current().setCookie(Scope.COOKIE_PREFIX + "_ERRORS", errorsData, null, "/", null, Scope.COOKIE_SECURE, Scope.SESSION_HTTPONLY);
  166. } catch (Exception e) {
  167. throw new UnexpectedException("Errors serializationProblem", e);
  168. }
  169. }
  170. static void clear() {
  171. try {
  172. if (Http.Response.current() != null && Http.Response.current().cookies != null) {
  173. Cookie cookie = new Cookie();
  174. cookie.name = Scope.COOKIE_PREFIX + "_ERRORS";
  175. cookie.value = "";
  176. cookie.sendOnError = true;
  177. Http.Response.current().cookies.put(cookie.name, cookie);
  178. }
  179. } catch (Exception e) {
  180. throw new UnexpectedException("Errors serializationProblem", e);
  181. }
  182. }
  183. }