Public: custom serializer and other code needed to send constraint violations
from the server to the client.

Review at http://gwt-code-reviews.appspot.com/1040802

Review by: bobv@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9135 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/common.ant.xml b/samples/common.ant.xml
index aa2939f..73468b9 100755
--- a/samples/common.ant.xml
+++ b/samples/common.ant.xml
@@ -71,7 +71,8 @@
 
   <target name="compile" description="Compile all java files">
     <mkdir dir="${sample.build}/war/WEB-INF/classes" />
-    <gwt.javac destdir="${sample.build}/war/WEB-INF/classes">
+    <gwt.javac destdir="${sample.build}/war/WEB-INF/classes" 
+               excludes="**/super/**">
       <classpath>
         <pathelement location="${gwt.user.jar}" />
         <pathelement location="${gwt.dev.jar}" />
diff --git a/samples/validation/build.xml b/samples/validation/build.xml
index 5f960a7..03f7a24 100755
--- a/samples/validation/build.xml
+++ b/samples/validation/build.xml
@@ -24,5 +24,10 @@
     <include name="apache/log4j/log4j-1.2.16.jar" />
     <include name="slf4j/slf4j-api/slf4j-api-1.6.1.jar" />
     <include name="slf4j/slf4j-log4j12/slf4j-log4j12-1.6.1.jar" />
+    <!-- Needed for JDK 1.5-->
+    <include name="javax/activation/activation-1.1.jar" />
+    <include name="javax/xml/bind/jaxb-api-2.1.jar" />
+    <include name="sun/jaxb/jaxb-impl-2.1.3.jar" />
+    <include name="javax/xml/stream/stax-api-1.0-2.jar" />
   </fileset>
 </project>
diff --git a/samples/validation/src/com/google/gwt/sample/validation/Validation.gwt.xml b/samples/validation/src/com/google/gwt/sample/validation/Validation.gwt.xml
index 55dc76a..84de2f3 100644
--- a/samples/validation/src/com/google/gwt/sample/validation/Validation.gwt.xml
+++ b/samples/validation/src/com/google/gwt/sample/validation/Validation.gwt.xml
@@ -22,4 +22,5 @@
 
   <source path='client'/>
   <source path='shared'/>
+  <super-source path="super" />
 </module>
diff --git a/samples/validation/src/com/google/gwt/sample/validation/client/GreetingService.java b/samples/validation/src/com/google/gwt/sample/validation/client/GreetingService.java
index c54f455..9ed0787 100644
--- a/samples/validation/src/com/google/gwt/sample/validation/client/GreetingService.java
+++ b/samples/validation/src/com/google/gwt/sample/validation/client/GreetingService.java
@@ -16,13 +16,17 @@
 package com.google.gwt.sample.validation.client;
 
 import com.google.gwt.rpc.client.RpcService;
+import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.sample.validation.shared.Person;
 import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
 
+import javax.validation.ConstraintViolationException;
+
 /**
  * The client side stub for the RPC service.
  */
 @RemoteServiceRelativePath("greet")
 public interface GreetingService extends RpcService {
-  String greetServer(Person name) throws IllegalArgumentException;
+  SafeHtml greetServer(Person name) throws IllegalArgumentException,
+      ConstraintViolationException;
 }
diff --git a/samples/validation/src/com/google/gwt/sample/validation/client/GreetingServiceAsync.java b/samples/validation/src/com/google/gwt/sample/validation/client/GreetingServiceAsync.java
index dda64fb..1309e56 100644
--- a/samples/validation/src/com/google/gwt/sample/validation/client/GreetingServiceAsync.java
+++ b/samples/validation/src/com/google/gwt/sample/validation/client/GreetingServiceAsync.java
@@ -15,13 +15,16 @@
  */
 package com.google.gwt.sample.validation.client;
 
+import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.sample.validation.shared.Person;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 
+import javax.validation.ConstraintViolationException;
+
 /**
  * The async counterpart of <code>GreetingService</code>.
  */
 public interface GreetingServiceAsync {
-  void greetServer(Person person, AsyncCallback<String> callback)
-      throws IllegalArgumentException;
+  void greetServer(Person person, AsyncCallback<SafeHtml> callback)
+      throws IllegalArgumentException, ConstraintViolationException;
 }
diff --git a/samples/validation/src/com/google/gwt/sample/validation/client/SampleValidator.java b/samples/validation/src/com/google/gwt/sample/validation/client/SampleValidator.java
index 5f8c74c..dffe96a 100644
--- a/samples/validation/src/com/google/gwt/sample/validation/client/SampleValidator.java
+++ b/samples/validation/src/com/google/gwt/sample/validation/client/SampleValidator.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.sample.validation.client;
 
+import com.google.gwt.sample.validation.shared.ClientGroup;
 import com.google.gwt.sample.validation.shared.Person;
 import com.google.gwt.validation.client.GwtValidation;
 
@@ -27,17 +28,6 @@
  */
 @GwtValidation(value = Person.class,
  groups = {
-    Default.class, SampleValidator.ClientGroup.class})
+    Default.class, ClientGroup.class})
 public interface SampleValidator extends Validator {
-  /**
-   * Client Validation Group
-   */
-  public interface ClientGroup {
-  }
-
-  /**
-   * Server Validation Group
-   */
-  public interface ServerGroup {
-  }
 }
diff --git a/samples/validation/src/com/google/gwt/sample/validation/client/ValidationView.java b/samples/validation/src/com/google/gwt/sample/validation/client/ValidationView.java
index 21a90f7..1383761 100644
--- a/samples/validation/src/com/google/gwt/sample/validation/client/ValidationView.java
+++ b/samples/validation/src/com/google/gwt/sample/validation/client/ValidationView.java
@@ -20,6 +20,7 @@
 import com.google.gwt.event.dom.client.KeyCodes;
 import com.google.gwt.event.dom.client.KeyUpEvent;
 import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.sample.validation.shared.Person;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
@@ -36,6 +37,7 @@
 import java.util.Set;
 
 import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
 import javax.validation.Validator;
 
 /**
@@ -124,7 +126,6 @@
     person.setName(nameField.getText());
 
     Validator validator = GWT.create(SampleValidator.class);
-
     Set<ConstraintViolation<Person>> violations = validator.validate(person);
     if (!violations.isEmpty()) {
       StringBuffer errorMessage = new StringBuffer();
@@ -140,8 +141,24 @@
     sendButton.setEnabled(false);
     textToServer.setText(person.getName());
     serverResponse.setText("");
-    greetingService.greetServer(person, new AsyncCallback<String>() {
+    greetingService.greetServer(person, new AsyncCallback<SafeHtml>() {
       public void onFailure(Throwable caught) {
+        if (caught instanceof ConstraintViolationException) {
+          ConstraintViolationException violationException = (ConstraintViolationException) caught;
+          Set<ConstraintViolation<?>> violations = violationException.getConstraintViolations();
+          StringBuffer sb = new StringBuffer();
+          for (ConstraintViolation<?> constraintViolation : violations) {
+            sb.append(constraintViolation.getPropertyPath().toString()) //
+            .append(":") //
+            .append(constraintViolation.getMessage()) //
+            .append("\n");
+          }
+          errorLabel.setText(sb.toString());
+          sendButton.setEnabled(true);
+          sendButton.setFocus(true);
+          return;
+        }
+
         // Show the RPC error message to the user
         dialogBox.setText("Remote Procedure Call - Failure");
         serverResponse.addStyleName(style.error());
@@ -150,7 +167,7 @@
         closeButton.setFocus(true);
       }
 
-      public void onSuccess(String result) {
+      public void onSuccess(SafeHtml result) {
         dialogBox.setText("Remote Procedure Call");
         serverResponse.removeStyleName(style.error());
         serverResponse.setHTML(result);
diff --git a/samples/validation/src/com/google/gwt/sample/validation/server/GreetingServiceImpl.java b/samples/validation/src/com/google/gwt/sample/validation/server/GreetingServiceImpl.java
index 3ee9de7..f8712c7 100644
--- a/samples/validation/src/com/google/gwt/sample/validation/server/GreetingServiceImpl.java
+++ b/samples/validation/src/com/google/gwt/sample/validation/server/GreetingServiceImpl.java
@@ -16,8 +16,20 @@
 package com.google.gwt.sample.validation.server;
 
 import com.google.gwt.rpc.server.RpcServlet;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.sample.validation.client.GreetingService;
 import com.google.gwt.sample.validation.shared.Person;
+import com.google.gwt.sample.validation.shared.ServerGroup;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import javax.validation.groups.Default;
 
 /**
  * The server side implementation of the RPC service.
@@ -26,35 +38,33 @@
 public class GreetingServiceImpl extends RpcServlet implements
     GreetingService {
 
-  public String greetServer(Person person) throws IllegalArgumentException {
-    // Verify that the input is valid.
+  private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
 
-    // TODO(nchalko) validate
+  public SafeHtml greetServer(Person person) throws IllegalArgumentException,
+      ConstraintViolationException {
+    // Verify that the input is valid.
+    Set<ConstraintViolation<Person>> violations = validator.validate(person,
+        Default.class, ServerGroup.class);
+    if (!violations.isEmpty()) {
+      Set<ConstraintViolation<?>> temp = new HashSet<ConstraintViolation<?>>(
+          violations);
+      throw new ConstraintViolationException(temp);
+    }
 
     String serverInfo = getServletContext().getServerInfo();
     String userAgent = getThreadLocalRequest().getHeader("User-Agent");
 
     // Escape data from the client to avoid cross-site script vulnerabilities.
-    String value = escapeHtml(person.getName());
-    userAgent = escapeHtml(userAgent);
+    SafeHtmlBuilder builder = new SafeHtmlBuilder();
 
-    return "Hello, " + value + "!<br><br>I am running " + serverInfo
-        + ".<br><br>It looks like you are using:<br>" + userAgent;
+    SafeHtml safeHtml = builder//
+    .appendEscapedLines("Hello, " + person.getName() + "!")//
+    .appendHtmlConstant("<br>")//
+    .appendEscaped("I am running " + serverInfo + ".")//
+    .appendHtmlConstant("<br><br>")//
+    .appendEscaped("It looks like you are using: ")//
+    .appendEscaped(userAgent)//
+    .toSafeHtml();
+    return safeHtml;
   }
-
-  /**
-   * Escape an html string. Escaping data received from the client helps to
-   * prevent cross-site script vulnerabilities.
-   *
-   * @param html the html string to escape
-   * @return the escaped string
-   */
-  private String escapeHtml(String html) {
-    // TODO(nchalko) use SafeHtml after it's integrated.
-    if (html == null) {
-      return null;
-    }
-    return html.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(
-        ">", "&gt;");
-  }
-}
+}
\ No newline at end of file
diff --git a/samples/validation/src/com/google/gwt/sample/validation/shared/ClientGroup.java b/samples/validation/src/com/google/gwt/sample/validation/shared/ClientGroup.java
new file mode 100644
index 0000000..0acd314
--- /dev/null
+++ b/samples/validation/src/com/google/gwt/sample/validation/shared/ClientGroup.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * 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 com.google.gwt.sample.validation.shared;
+
+/**
+ * Client Validation Group
+ */
+public interface ClientGroup {
+}
\ No newline at end of file
diff --git a/samples/validation/src/com/google/gwt/sample/validation/shared/NoOp.java b/samples/validation/src/com/google/gwt/sample/validation/shared/NoOp.java
index 197a655..1459303 100644
--- a/samples/validation/src/com/google/gwt/sample/validation/shared/NoOp.java
+++ b/samples/validation/src/com/google/gwt/sample/validation/shared/NoOp.java
@@ -31,7 +31,7 @@
 import javax.validation.Payload;
 
 /**
- * Test constaint that is always valid
+ * Test constraint that is always valid
  */
 @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE})
 @Retention(RUNTIME)
diff --git a/samples/validation/src/com/google/gwt/sample/validation/shared/Person.java b/samples/validation/src/com/google/gwt/sample/validation/shared/Person.java
index 82a68eb..5bb73db 100644
--- a/samples/validation/src/com/google/gwt/sample/validation/shared/Person.java
+++ b/samples/validation/src/com/google/gwt/sample/validation/shared/Person.java
@@ -23,7 +23,7 @@
 /**
  * A sample bean to show validation on.
  */
-@NoOp
+@ServerConstraint(groups = ServerGroup.class)
 public class Person implements IsSerializable {
 
   @NotNull
diff --git a/samples/validation/src/com/google/gwt/sample/validation/shared/ServerConstraint.java b/samples/validation/src/com/google/gwt/sample/validation/shared/ServerConstraint.java
new file mode 100644
index 0000000..290062b
--- /dev/null
+++ b/samples/validation/src/com/google/gwt/sample/validation/shared/ServerConstraint.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * 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 com.google.gwt.sample.validation.shared;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+
+/**
+ * Sample constraint that is designed to only run on the server. It will fail if
+ * the Persons name is "Fail"
+ */
+@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE})
+@Retention(RUNTIME)
+@Documented
+@Constraint(validatedBy = {ServerValidator.class})
+public @interface ServerConstraint {
+  String message() default "Not valid at the server";
+
+  Class<?>[] groups() default {};
+
+  Class<? extends Payload>[] payload() default {};
+}
\ No newline at end of file
diff --git a/samples/validation/src/com/google/gwt/sample/validation/shared/ServerGroup.java b/samples/validation/src/com/google/gwt/sample/validation/shared/ServerGroup.java
new file mode 100644
index 0000000..bc28da9
--- /dev/null
+++ b/samples/validation/src/com/google/gwt/sample/validation/shared/ServerGroup.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * 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 com.google.gwt.sample.validation.shared;
+
+/**
+ * Server Validation Group
+ */
+public interface ServerGroup {
+}
\ No newline at end of file
diff --git a/samples/validation/src/com/google/gwt/sample/validation/shared/ServerValidator.java b/samples/validation/src/com/google/gwt/sample/validation/shared/ServerValidator.java
new file mode 100644
index 0000000..9e15123
--- /dev/null
+++ b/samples/validation/src/com/google/gwt/sample/validation/shared/ServerValidator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * 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 com.google.gwt.sample.validation.shared;
+
+import java.lang.reflect.Method;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+/**
+ * Fails only on the server if the persons name is "Fail"
+ */
+public class ServerValidator implements
+    ConstraintValidator<ServerConstraint, Person> {
+
+  public void initialize(ServerConstraint constraintAnnotation) {
+    // Here I do something that will not compile on GWT
+    Method[] methods = constraintAnnotation.getClass().getMethods();
+  }
+
+  public boolean isValid(Person person, ConstraintValidatorContext context) {
+    if (person == null) {
+      return true;
+    }
+    String name = person.getName();
+    return name == null || !name.equals("Fail");
+  }
+}
\ No newline at end of file
diff --git a/samples/validation/src/com/google/gwt/sample/validation/super/com/google/gwt/sample/validation/shared/ServerValidator.java b/samples/validation/src/com/google/gwt/sample/validation/super/com/google/gwt/sample/validation/shared/ServerValidator.java
new file mode 100644
index 0000000..330d8ad
--- /dev/null
+++ b/samples/validation/src/com/google/gwt/sample/validation/super/com/google/gwt/sample/validation/shared/ServerValidator.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * 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 com.google.gwt.sample.validation.shared;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+/**
+ * Always passes.
+ * <p>
+ * TODO(nchalko) change this to extend
+ * {@link com.google.gwt.validation.client.constraints.NotGwtCompatibleValidator}
+ * when groups are properly handled.
+ */
+public class ServerValidator implements
+    ConstraintValidator<ServerConstraint, Person> {
+
+  public void initialize(ServerConstraint constraintAnnotation) {
+  }
+
+  public boolean isValid(Person person, ConstraintValidatorContext context) {
+    return true;
+  }
+}
\ No newline at end of file
diff --git a/user/build.xml b/user/build.xml
index 60319ea..6c40ce0 100755
--- a/user/build.xml
+++ b/user/build.xml
@@ -146,7 +146,7 @@
         <exclude name="javax/validation/super/javax/validation/MessageInterpolator.java"/>
         <exclude name="javax/validation/super/javax/validation/Configuration.java"/>
         <exclude name="javax/validation/super/javax/validation/Validation.java"/>
-        <exclude name="org/hibernate/validator/super/org/hibernate/validator/constraints/ScriptAssert.java"/>
+        <exclude name="org/hibernate/validator/super/org/hibernate/validator/**/*.java"/>
       </fileset>
       <fileset dir="super/com/google/gwt/emul" />
       <fileset dir="super/com/google/gwt/junit/translatable" />
diff --git a/user/src/javax/validation/ConstraintViolationException_CustomFieldSerializer.java b/user/src/javax/validation/ConstraintViolationException_CustomFieldSerializer.java
new file mode 100644
index 0000000..c73aef0
--- /dev/null
+++ b/user/src/javax/validation/ConstraintViolationException_CustomFieldSerializer.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * 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 javax.validation;
+
+import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.client.rpc.SerializationStreamReader;
+import com.google.gwt.user.client.rpc.SerializationStreamWriter;
+
+import java.util.Set;
+
+/**
+ * Custom Serializer for {@link ConstraintViolationException}.
+ */
+public class ConstraintViolationException_CustomFieldSerializer {
+
+  public static void deserialize(SerializationStreamReader streamReader,
+      ConstraintViolationException instance) throws SerializationException {
+    // no fields
+  }
+
+  public static ConstraintViolationException instantiate(
+      SerializationStreamReader streamReader)
+      throws SerializationException {
+    String message = streamReader.readString();
+    @SuppressWarnings("unchecked")
+    Set<ConstraintViolation<?>> set = (Set<ConstraintViolation<?>>) streamReader.readObject();
+    return new ConstraintViolationException(message, set);
+  }
+
+  public static void serialize(SerializationStreamWriter streamWriter,
+      ConstraintViolationException instance) throws SerializationException {
+    streamWriter.writeString(instance.getMessage());
+    streamWriter.writeObject(instance.getConstraintViolations());
+  }
+}
diff --git a/user/src/org/hibernate/validator/HibernateValidator.gwt.xml b/user/src/org/hibernate/validator/HibernateValidator.gwt.xml
index b469c70..626fd9c 100644
--- a/user/src/org/hibernate/validator/HibernateValidator.gwt.xml
+++ b/user/src/org/hibernate/validator/HibernateValidator.gwt.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <module>
-<!-- 
-Import this module to use Hibernate Validator during the compilation of validation classes for 
+<!--
+Import this module to use Hibernate Validator during the compilation of validation classes for
 gwt clients.
  -->
   <inherits name='com.google.gwt.validation.Validation' />
@@ -10,7 +10,9 @@
     <exclude name="super/" />
   </source>
   <source path="engine">
+    <include name="ConstraintViolationImpl.java"/>
     <include name="NodeImpl.java"/>
+    <include name="PathImpl.java"/>
   </source>
   <super-source path="super" />
-</module>
\ No newline at end of file
+</module>
diff --git a/user/src/org/hibernate/validator/engine/ConstraintViolationImpl_CustomFieldSerializer.java b/user/src/org/hibernate/validator/engine/ConstraintViolationImpl_CustomFieldSerializer.java
new file mode 100644
index 0000000..97883fa
--- /dev/null
+++ b/user/src/org/hibernate/validator/engine/ConstraintViolationImpl_CustomFieldSerializer.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * 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.hibernate.validator.engine;
+
+import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.client.rpc.SerializationStreamReader;
+import com.google.gwt.user.client.rpc.SerializationStreamWriter;
+
+import java.lang.annotation.ElementType;
+
+import javax.validation.Path;
+import javax.validation.metadata.ConstraintDescriptor;
+
+/**
+ * Custom Serializer for {@link ConstraintViolationImpl}.
+ */
+public class ConstraintViolationImpl_CustomFieldSerializer {
+
+  public static void deserialize(SerializationStreamReader streamReader,
+      ConstraintViolationImpl instance) throws SerializationException {
+    // no fields
+  }
+
+  public static ConstraintViolationImpl<Object> instantiate(
+      SerializationStreamReader streamReader) throws SerializationException {
+
+    String messageTemplate = null;
+    String interpolatedMessage = streamReader.readString();
+    Class<Object> rootBeanClass = null;
+    Object rootBean = null;
+    Object leafBeanInstance = null;
+    Object value = null;
+    Path propertyPath = (Path) streamReader.readObject();
+    ConstraintDescriptor<?> constraintDescriptor = null;
+    ElementType elementType = null;
+    return new ConstraintViolationImpl<Object>(messageTemplate,
+        interpolatedMessage, rootBeanClass, rootBean, leafBeanInstance, value,
+        propertyPath, constraintDescriptor, elementType);
+  }
+
+  /**
+   * Only a subset of fields are actually serialized.
+   * <p/>
+   * There is no guarantee that the root bean is GWT-serializable or that it's
+   * appropriate for it to be exposed on the client. Even if the root bean could
+   * be sent back, the lack of reflection on the client makes it troublesome to
+   * interpret the path as a sequence of property accesses.
+   * <p/>
+   * The current implementation is the simplest-to-implement properties.
+   * <ol>
+   * <li>Message</li>
+   * <li>Property Path</li>
+   * </ol>
+   */
+  public static void serialize(SerializationStreamWriter streamWriter,
+      ConstraintViolationImpl instance) throws SerializationException {
+
+    // streamWriter.writeString(instance.getMessageTemplate());
+    streamWriter.writeString(instance.getMessage());
+    // streamWriter.writeObject(instance.getRootBeanClass());
+    // streamWriter.writeObject(instance.getRootBean());
+    // streamWriter.writeObject(instance.getLeafBean());
+    // streamWriter.writeObject(instance.getInvalidValue());
+    streamWriter.writeObject(instance.getPropertyPath());
+    // streamWriter.writeObject(instance.getConstraintDescriptor());
+    // ElementType
+  }
+}
diff --git a/user/src/org/hibernate/validator/engine/PathImpl_CustomFieldSerializer.java b/user/src/org/hibernate/validator/engine/PathImpl_CustomFieldSerializer.java
new file mode 100644
index 0000000..67c6c59
--- /dev/null
+++ b/user/src/org/hibernate/validator/engine/PathImpl_CustomFieldSerializer.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * 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.hibernate.validator.engine;
+
+import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.client.rpc.SerializationStreamReader;
+import com.google.gwt.user.client.rpc.SerializationStreamWriter;
+
+/**
+ * Custom Serializer for {@link PathImpl}.
+ */
+public class PathImpl_CustomFieldSerializer {
+
+  public static void deserialize(SerializationStreamReader streamReader,
+      PathImpl instance) throws SerializationException {
+    // no fields
+  }
+
+  public static PathImpl instantiate(SerializationStreamReader streamReader)
+      throws SerializationException {
+    String propertyPath = streamReader.readString();
+
+    return PathImpl.createPathFromString(propertyPath);
+  }
+
+  public static void serialize(SerializationStreamWriter streamWriter,
+      PathImpl instance) throws SerializationException {
+    streamWriter.writeString(instance.toString());
+  }
+}
diff --git a/user/src/org/hibernate/validator/super/org/hibernate/validator/engine/PathImpl.java b/user/src/org/hibernate/validator/super/org/hibernate/validator/engine/PathImpl.java
new file mode 100644
index 0000000..91bd2c3
--- /dev/null
+++ b/user/src/org/hibernate/validator/super/org/hibernate/validator/engine/PathImpl.java
@@ -0,0 +1,225 @@
+// $Id: PathImpl.java 17744 2009-10-14 14:38:57Z hardy.ferentschik $
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2009, Red Hat, Inc. and/or its affiliates, and individual contributors
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* 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.
+*/
+// Modified by Google: Replace java.util.Pattern with gwt RegExp
+package org.hibernate.validator.engine;
+
+import com.google.gwt.regexp.shared.MatchResult;
+import com.google.gwt.regexp.shared.RegExp;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.validation.Path;
+
+/**
+ * @author Hardy Ferentschik
+ */
+public class PathImpl implements Path, Serializable {
+
+  private static final long serialVersionUID = 7564511574909882392L;
+
+  /**
+   * Regular expression used to split a string path into its elements.
+   *
+   * @see <a href="http://www.regexplanet.com/simple/index.jsp">Regular expression tester</a>
+   */
+  private static final RegExp pathPattern = RegExp.compile("(\\w+)(\\[(\\w*)\\])?(\\.(.*))*");
+
+  private static final String PROPERTY_PATH_SEPERATOR = ".";
+
+  private final List<Node> nodeList;
+
+  /**
+   * Returns a {@code Path} instance representing the path described by the given string. To create a root node the empty string should be passed.
+   *
+   * @param propertyPath the path as string representation.
+   *
+   * @return a {@code Path} instance representing the path described by the given string.
+   *
+   * @throws IllegalArgumentException in case {@code property == null} or {@code property} cannot be parsed.
+   */
+  public static PathImpl createPathFromString(String propertyPath) {
+    if ( propertyPath == null ) {
+      throw new IllegalArgumentException( "null is not allowed as property path." );
+    }
+
+    if ( propertyPath.length() == 0 ) {
+      return createNewPath( null );
+    }
+
+    return parseProperty( propertyPath );
+  }
+
+  public static PathImpl createNewPath(String name) {
+    PathImpl path = new PathImpl();
+    NodeImpl node = new NodeImpl( name );
+    path.addNode( node );
+    return path;
+  }
+
+  public static PathImpl createShallowCopy(Path path) {
+    return path == null ? null : new PathImpl( path );
+  }
+
+  private PathImpl(Path path) {
+    this.nodeList = new ArrayList<Node>();
+    for ( Object aPath : path ) {
+      nodeList.add( new NodeImpl( ( Node ) aPath ) );
+    }
+  }
+
+  private PathImpl() {
+    nodeList = new ArrayList<Node>();
+  }
+
+  private PathImpl(List<Node> nodeList) {
+    this.nodeList = new ArrayList<Node>();
+    for ( Node node : nodeList ) {
+      this.nodeList.add( new NodeImpl( node ) );
+    }
+  }
+
+  public boolean isRootPath() {
+    return nodeList.size() == 1 && nodeList.get( 0 ).getName() == null;
+  }
+
+  public PathImpl getPathWithoutLeafNode() {
+    List<Node> nodes = new ArrayList<Node>( nodeList );
+    PathImpl path = null;
+    if ( nodes.size() > 1 ) {
+      nodes.remove( nodes.size() - 1 );
+      path = new PathImpl( nodes );
+    }
+    return path;
+  }
+
+  public void addNode(Node node) {
+    nodeList.add( node );
+  }
+
+  public Node removeLeafNode() {
+    if ( nodeList.size() == 0 ) {
+      throw new IllegalStateException( "No nodes in path!" );
+    }
+    if ( nodeList.size() == 1 ) {
+      throw new IllegalStateException( "Root node cannot be removed!" );
+    }
+    return nodeList.remove( nodeList.size() - 1 );
+  }
+
+  public NodeImpl getLeafNode() {
+    if ( nodeList.size() == 0 ) {
+      throw new IllegalStateException( "No nodes in path!" );
+    }
+    return ( NodeImpl ) nodeList.get( nodeList.size() - 1 );
+  }
+
+  public Iterator<Path.Node> iterator() {
+    return nodeList.iterator();
+  }
+
+  public boolean isSubPathOf(Path path) {
+    Iterator<Node> pathIter = path.iterator();
+    Iterator<Node> thisIter = iterator();
+    while ( pathIter.hasNext() ) {
+      Node pathNode = pathIter.next();
+      if ( !thisIter.hasNext() ) {
+        return false;
+      }
+      Node thisNode = thisIter.next();
+      if ( !thisNode.equals( pathNode ) ) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    Iterator<Path.Node> iter = iterator();
+    while ( iter.hasNext() ) {
+      Node node = iter.next();
+      builder.append( node.toString() );
+      if ( iter.hasNext() ) {
+        builder.append( PROPERTY_PATH_SEPERATOR );
+      }
+    }
+    return builder.toString();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if ( this == o ) {
+      return true;
+    }
+    if ( o == null || getClass() != o.getClass() ) {
+      return false;
+    }
+
+    PathImpl path = ( PathImpl ) o;
+    if ( nodeList != null && !nodeList.equals( path.nodeList ) ) {
+      return false;
+    }
+    if ( nodeList == null && path.nodeList != null ) {
+      return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return nodeList != null ? nodeList.hashCode() : 0;
+  }
+
+  private static PathImpl parseProperty(String property) {
+    PathImpl path = new PathImpl();
+    String tmp = property;
+    do {
+      MatchResult matcher = pathPattern.exec(tmp);
+      if (matcher != null) {
+        String value = matcher.getGroup(1);
+        String indexed = matcher.getGroup(2);
+        String index = matcher.getGroup(3);
+        NodeImpl node = new NodeImpl( value );
+        if ( indexed != null ) {
+          node.setInIterable( true );
+        }
+        if ( index != null && index.length() > 0 ) {
+          try {
+            Integer i = Integer.parseInt( index );
+            node.setIndex( i );
+          }
+          catch ( NumberFormatException e ) {
+            node.setKey( index );
+          }
+        }
+        path.addNode( node );
+        tmp = matcher.getGroup(5);
+      }
+      else {
+        throw new IllegalArgumentException( "Unable to parse property path " + property );
+      }
+    } while ( tmp != null );
+    return path;
+  }
+
+}