You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2006/12/22 21:13:17 UTC

svn commit: r489746 - in /tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry: ValidationTracker.java ValidationTrackerImpl.java ValidationTrackerImplTest.java

Author: hlship
Date: Fri Dec 22 12:13:16 2006
New Revision: 489746

URL: http://svn.apache.org/viewvc?view=rev&rev=489746
Log:
Base implementation of the ValidationTracker

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ValidationTracker.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ValidationTrackerImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ValidationTrackerImplTest.java

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ValidationTracker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ValidationTracker.java?view=auto&rev=489746
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ValidationTracker.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ValidationTracker.java Fri Dec 22 12:13:16 2006
@@ -0,0 +1,86 @@
+package org.apache.tapestry;
+
+import java.util.List;
+
+import org.apache.tapestry.corelib.components.Loop;
+
+/**
+ * Tracks information related to user input validations. This information is:
+ * <ul>
+ * <li>The input values provided by the user.
+ * <li>Any validation exceptions associated with input fields.
+ * </ul>
+ * <p>
+ * The tracker must differentiate between components (which will implement the {@link Field}
+ * interfaces) and fields. It is a one to many relationship, because each field may be called upon
+ * to render itself multiple times within a request, because of {@link Loop} or other similar
+ * components.
+ * <p>
+ * Internally, the tracker indexes its information in terms of the {@link Field#getElementName()}
+ * for each rendering of the component (the mechanics of Tapestry ensures that this is unique within
+ * the form).
+ * <p>
+ * Validation trackers must be serializable, as they will almost always be stored into the
+ * HttpSession.
+ * <p>
+ * Trackers are used by only a single form within a single page; they are not threadsafe.
+ */
+public interface ValidationTracker
+{
+    /**
+     * Called by a field to record the exact input from the user, prior to any validation. If the
+     * form is redisplayed (to present errors), the input value will be sent back to the user for
+     * correction.
+     * 
+     * @param field
+     *            the field recording the input
+     * @param input
+     *            the value obtained from the forms submission
+     */
+    void recordInput(Field field, String input);
+
+    /** Returns a previously recorded input value. */
+    String getInput(Field field);
+
+    /**
+     * Records an error message for a field. The error message is primarily derived from a
+     * {@link ValidationException} thrown by a {@link Validator} or {@link Translator}.
+     * 
+     * @param field
+     * @param errorMessage
+     */
+    void recordError(Field field, String errorMessage);
+
+    /**
+     * Records an error message that is not associated with any specific field. This often reflects
+     * some amount of cross-form validation.
+     * 
+     * @param errorMessage
+     */
+    void recordError(String errorMessage);
+
+    /**
+     * For a given field, determines if the field is "in error", meaning that an error message has
+     * been previously recorded for the field.
+     * 
+     * @param field
+     * @return true if an error message is present
+     */
+    boolean inError(Field field);
+
+    /** Returns a previously recorded error message. */
+    String getError(Field field);
+
+    /** Returns true if any field contains an error. */
+    boolean getHasErrors();
+
+    /**
+     * Returns a list of all error messages. The messages are stored in the order that they were
+     * added to the tracker, except that unassociated errors (unassociated with any field) are
+     * listed first.
+     */
+    List<String> getErrors();
+
+    /** Clears all information stored by the tracker. */
+    void clear();
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ValidationTrackerImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ValidationTrackerImpl.java?view=auto&rev=489746
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ValidationTrackerImpl.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ValidationTrackerImpl.java Fri Dec 22 12:13:16 2006
@@ -0,0 +1,186 @@
+package org.apache.tapestry;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry.ioc.internal.util.InternalUtils;
+
+/**
+ * Standard implmentation of {@link ValidationTracker}. Works pretty hard to ensure a minimum
+ * amount of data is stored in the HttpSession.
+ */
+public final class ValidationTrackerImpl implements ValidationTracker, Serializable
+{
+    private static final long serialVersionUID = -8029192726659275677L;
+
+    private static class FieldTracker implements Serializable
+    {
+        private static final long serialVersionUID = -3653306147088451811L;
+
+        private final String _fieldName;
+
+        private String _input;
+
+        private String _errorMessage;
+
+        FieldTracker(String fieldName)
+        {
+            _fieldName = fieldName;
+        }
+
+        public String getFieldName()
+        {
+            return _fieldName;
+        }
+
+        public void setErrorMessage(String errorMessage)
+        {
+            _errorMessage = errorMessage;
+        }
+
+        public String getErrorMessage()
+        {
+            return _errorMessage;
+        }
+
+        public String getInput()
+        {
+            return _input;
+        }
+
+        public void setInput(String input)
+        {
+            _input = input;
+        }
+
+    }
+
+    private List<String> _extraErrors;
+
+    private List<FieldTracker> _fieldTrackers;
+
+    // Rebuilt on-demand
+
+    private transient Map<String, FieldTracker> _fieldToTracker;
+
+    private void refreshFieldToTracker()
+    {
+        if (_fieldToTracker != null)
+            return;
+
+        if (_fieldTrackers == null)
+            return;
+
+        _fieldToTracker = CollectionFactory.newMap();
+
+        for (FieldTracker ft : _fieldTrackers)
+            _fieldToTracker.put(ft.getFieldName(), ft);
+    }
+
+    private FieldTracker get(Field field)
+    {
+        String key = field.getElementName();
+
+        refreshFieldToTracker();
+
+        FieldTracker result = InternalUtils.get(_fieldToTracker, key);
+
+        if (result == null)
+            result = new FieldTracker(key);
+
+        return result;
+    }
+
+    private void store(FieldTracker fieldTracker)
+    {
+        if (_fieldTrackers == null)
+            _fieldTrackers = CollectionFactory.newList();
+
+        refreshFieldToTracker();
+
+        String key = fieldTracker.getFieldName();
+
+        if (!_fieldToTracker.containsKey(key))
+        {
+            _fieldTrackers.add(fieldTracker);
+            _fieldToTracker.put(key, fieldTracker);
+        }
+    }
+
+    public void clear()
+    {
+        _extraErrors = null;
+        _fieldTrackers = null;
+        _fieldToTracker = null;
+    }
+
+    public String getError(Field field)
+    {
+        return get(field).getErrorMessage();
+    }
+
+    public List<String> getErrors()
+    {
+        List<String> result = CollectionFactory.newList();
+
+        if (_extraErrors != null)
+            result.addAll(_extraErrors);
+
+        if (_fieldTrackers != null)
+        {
+            for (FieldTracker ft : _fieldTrackers)
+            {
+                String errorMessage = ft.getErrorMessage();
+
+                if (errorMessage != null)
+                    result.add(errorMessage);
+            }
+        }
+
+        return result;
+    }
+
+    public boolean getHasErrors()
+    {
+        return !getErrors().isEmpty();
+    }
+
+    public String getInput(Field field)
+    {
+        return get(field).getInput();
+    }
+
+    public boolean inError(Field field)
+    {
+        return InternalUtils.isNonBlank(get(field).getErrorMessage());
+    }
+
+    public void recordError(Field field, String errorMessage)
+    {
+        FieldTracker ft = get(field);
+
+        ft.setErrorMessage(errorMessage);
+
+        store(ft);
+    }
+
+    public void recordError(String errorMessage)
+    {
+        if (_extraErrors == null)
+            _extraErrors = CollectionFactory.newList();
+
+        _extraErrors.add(errorMessage);
+    }
+
+    public void recordInput(Field field, String input)
+    {
+        FieldTracker ft = get(field);
+
+        ft.setInput(input);
+
+        store(ft);
+    }
+
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ValidationTrackerImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ValidationTrackerImplTest.java?view=auto&rev=489746
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ValidationTrackerImplTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ValidationTrackerImplTest.java Fri Dec 22 12:13:16 2006
@@ -0,0 +1,205 @@
+package org.apache.tapestry;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Arrays;
+
+import org.apache.tapestry.test.TapestryTestCase;
+import org.testng.annotations.Test;
+
+public class ValidationTrackerImplTest extends TapestryTestCase
+{
+    @Test
+    public void empty_tracker_has_no_errors()
+    {
+        ValidationTracker tracker = new ValidationTrackerImpl();
+
+        assertTrue(tracker.getErrors().isEmpty());
+        assertFalse(tracker.getHasErrors());
+    }
+
+    @Test
+    public void order_added_is_maintained()
+    {
+        Field fielda = newField("fieldA");
+        Field fieldb = newField("fieldB");
+
+        replay();
+
+        ValidationTracker tracker = new ValidationTrackerImpl();
+
+        tracker.recordError("one");
+        tracker.recordError(fieldb, "fieldb: two");
+        tracker.recordError("three");
+        tracker.recordError(fielda, "fielda: four");
+
+        assertEquals(tracker.getErrors(), Arrays.asList(
+                "one",
+                "three",
+                "fieldb: two",
+                "fielda: four"));
+
+        verify();
+    }
+
+    @Test
+    public void record_input()
+    {
+        Field field = newField("field");
+
+        replay();
+
+        ValidationTracker tracker = new ValidationTrackerImpl();
+
+        assertNull(tracker.getInput(field));
+
+        tracker.recordInput(field, "one");
+
+        assertEquals(tracker.getInput(field), "one");
+
+        tracker.recordInput(field, "two");
+
+        assertEquals(tracker.getInput(field), "two");
+
+        verify();
+    }
+
+    @Test
+    public void record_error_for_field()
+    {
+        Field field = newField("field");
+
+        replay();
+
+        ValidationTracker tracker = new ValidationTrackerImpl();
+
+        assertFalse(tracker.getHasErrors());
+        assertFalse(tracker.inError(field));
+        assertNull(tracker.getError(field));
+
+        tracker.recordError(field, "one");
+
+        assertTrue(tracker.getHasErrors());
+        assertTrue(tracker.inError(field));
+        assertEquals(tracker.getError(field), "one");
+
+        tracker.recordError(field, "two");
+        assertEquals(tracker.getError(field), "two");
+
+        verify();
+    }
+
+    @Test
+    public void record_error_for_form()
+    {
+        ValidationTracker tracker = new ValidationTrackerImpl();
+
+        assertFalse(tracker.getHasErrors());
+
+        assertTrue(tracker.getErrors().isEmpty());
+
+        tracker.recordError("one");
+
+        assertEquals(tracker.getErrors(), Arrays.asList("one"));
+
+        tracker.recordError("two");
+
+        assertEquals(tracker.getErrors(), Arrays.asList("one", "two"));
+    }
+
+    @Test
+    public void data_survives_serialization() throws Exception
+    {
+        Field fielda = newField("fieldA");
+        Field fieldb = newField("fieldB");
+        Field fieldc = newField("fieldC");
+
+        replay();
+
+        ValidationTracker tracker = new ValidationTrackerImpl();
+
+        tracker.recordError("one");
+        tracker.recordError(fieldb, "fieldb: two");
+        tracker.recordError("three");
+        tracker.recordError(fielda, "fielda: four");
+
+        ValidationTracker copy = cloneBySerialiation(tracker);
+
+        copy.recordError(fieldc, "fieldc: five");
+
+        assertEquals(copy.getErrors(), Arrays.asList(
+                "one",
+                "three",
+                "fieldb: two",
+                "fielda: four",
+                "fieldc: five"));
+
+        verify();
+    }
+
+    @Test
+    public void clear_removes_all()
+    {
+        Field fielda = newField("fieldA");
+        Field fieldb = newField("fieldB");
+
+        replay();
+
+        ValidationTracker tracker = new ValidationTrackerImpl();
+
+        tracker.recordError("one");
+        tracker.recordInput(fieldb, "input b");
+        tracker.recordError(fieldb, "fieldb: two");
+        tracker.recordError("three");
+        tracker.recordInput(fielda, "input a");
+        tracker.recordError(fielda, "fielda: four");
+
+        tracker.clear();
+
+        assertFalse(tracker.getHasErrors());
+        assertTrue(tracker.getErrors().isEmpty());
+        assertNull(tracker.getInput(fielda));
+        assertNull(tracker.getInput(fieldb));
+
+        verify();
+    }
+
+    private final Field newField(String elementName)
+    {
+        Field field = newField();
+
+        // Fields generated this way, for the purposes of this test, do not
+        // ever change their elementName. In real life, elementNames can change.
+
+        expect(field.getElementName()).andReturn(elementName).atLeastOnce();
+
+        return field;
+    }
+
+    protected final Field newField()
+    {
+        return newMock(Field.class);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected final <T> T cloneBySerialiation(T input) throws Exception
+    {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        ObjectOutputStream oos = new ObjectOutputStream(bos);
+
+        oos.writeObject(input);
+
+        oos.close();
+
+        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
+        ObjectInputStream ois = new ObjectInputStream(bis);
+
+        T result = (T) ois.readObject();
+
+        ois.close();
+
+        return result;
+    }
+}