Author: hlship
Date: Tue Jan 16 18:50:12 2007
New Revision: 496918
URL: http://svn.apache.org/viewvc?view=rev&rev=496918
Log:
Refine the PrimaryKeyEncoder interface and DefaultPrimaryKeyEncoder
implementation.
Refine and test the Loop component's serialization of state in to the enclosing
Form, both in volatile and non-volatile mode.
Added:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/TapestryMessages.java
tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/TapestryStrings.properties
tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/ToDoListVolatile.html
- copied, changed from r496578,
tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/ToDoList.html
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/ToDoListVolatile.java
- copied, changed from r496578,
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/ToDoList.java
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/DefaultPrimaryKeyEncoder.java
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/PrimaryKeyEncoder.java
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Form.java
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Loop.java
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/Holder.java
tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/DefaultPrimaryKeyEncoderTest.java
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabase.java
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabaseImpl.java
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/DefaultPrimaryKeyEncoder.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/DefaultPrimaryKeyEncoder.java?view=diff&rev=496918&r1=496917&r2=496918
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/DefaultPrimaryKeyEncoder.java
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/DefaultPrimaryKeyEncoder.java
Tue Jan 16 18:50:12 2007
@@ -16,24 +16,28 @@
import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
+import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newSet;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.apache.tapestry.ioc.internal.util.Defense;
/**
* A default, extensible version of [EMAIL PROTECTED] PrimaryKeyEncoder} that
is based on loading known values
- * into an internal map.
+ * into an internal map. When there's a reasonable number (hundreds, perhaps
thousands) of items to
+ * choose from, and those items are fast and cheap to read and instantiate,
this implementation is a
+ * good bet. For very large result sets, you'll need to create your own
implementation of
+ * [EMAIL PROTECTED] PrimaryKeyEncoder}.
*
* @param <K>
- * the key type
+ * the key type (which must be serializable)
* @param <V>
* the value type
*/
-public abstract class DefaultPrimaryKeyEncoder<K extends Serializable, V>
implements
- PrimaryKeyEncoder<K, V>
+public class DefaultPrimaryKeyEncoder<K extends Serializable, V> implements
PrimaryKeyEncoder<K, V>
{
private final Map<K, V> _keyToValue = newMap();
@@ -41,30 +45,67 @@
private final List<K> _orderedKeys = newList();
+ private Set<K> _deletedKeys;
+
+ private K _currentKey;
+
/** Adds a new key/value pair to the encoder. */
public final void add(K key, V value)
{
Defense.notNull(key, "key");
Defense.notNull(value, "value");
- // TODO: Ensure the the key is unique
+ V existing = _keyToValue.get(key);
+ if (existing != null)
+ throw new
IllegalArgumentException(TapestryMessages.duplicateKey(key, value, existing));
_keyToValue.put(key, value);
_orderedKeys.add(key);
- // TODO: Ensure that the value is unique
+ // TODO: Ensure that the value is unique?
_valueToKey.put(value, key);
}
/**
* Returns the values previously [EMAIL PROTECTED] #add(Serializable,
Object) added to the encoder},
- * <em>in the order in which they were added</em>.
+ * <em>in the order in which they were added</em>. Values that are deleted
are not returned.
*
* @return ordered list of values
*/
public final List<V> getValues()
{
+ return valuesNotInKeySet(_deletedKeys);
+ }
+
+ /**
+ * Returns a list of all the values <em>except</em> those values whose
keys are in the
+ * provided set. The set may be null, in which case all values are
returned.
+ *
+ * @param keySet
+ * set of keys identifying values to exclude, or null to
exclude no values
+ * @return values (not in the set) in order origionally added
+ */
+ protected final List<V> valuesNotInKeySet(Set<K> keySet)
+ {
+ if (keySet == null || keySet.isEmpty())
+ return getAllValues();
+
+ List<V> result = newList();
+
+ for (K key : _orderedKeys)
+ {
+ if (keySet.contains(key))
+ continue;
+
+ result.add(_keyToValue.get(key));
+ }
+
+ return result;
+ }
+
+ public final List<V> getAllValues()
+ {
List<V> result = newList();
for (K key : _orderedKeys)
@@ -79,27 +120,43 @@
* For a previously [EMAIL PROTECTED] #add(Serializable, Object) added
key/value pair}, returns the key
* corresponding to the given value.
*/
- public final K extractKey(V value)
+ public final K toKey(V value)
{
- return _valueToKey.get(value);
+ Defense.notNull(value, "value");
+
+ _currentKey = _valueToKey.get(value);
+
+ if (_currentKey == null)
+ throw new
IllegalArgumentException(TapestryMessages.missingValue(value, _valueToKey
+ .keySet()));
+
+ return _currentKey;
}
- public final V recoverObject(K key)
+ public final V toValue(K key)
{
V result = _keyToValue.get(key);
if (result == null)
+ {
result = provideMissingObject(key);
+ _currentKey = key;
+ }
+ else
+ {
+ _currentKey = key;
+ }
+
return result;
}
/**
- * Invoked by [EMAIL PROTECTED] #recoverObject(Serializable)} whenever a
key can not be converted to a
- * value using the internal cache. This is an opportunity to record the
fact that an error
- * occured (they key was not valuable, possibly because it points to a
deleted entity object)
- * and provide a temporary object. This method may return null, but in a
typical application,
- * that will likely case NullPointerExceptions further down the processing
chain.
+ * Invoked by [EMAIL PROTECTED] #toValue(Serializable)} whenever a key can
not be converted to a value
+ * using the internal cache. This is an opportunity to record the fact
that an error occured
+ * (they key was not valuable, possibly because it points to a deleted
entity object) and
+ * provide a temporary object. This method may return null, but in a
typical application, that
+ * will likely case NullPointerExceptions further down the processing
chain.
* <p>
* This implementation returns null, and is intended to be overriden in
subclasses.
*
@@ -112,4 +169,66 @@
return null;
}
+ public final boolean isDeleted()
+ {
+ return inKeySet(_deletedKeys);
+ }
+
+ public final void setDeleted(boolean value)
+ {
+ _deletedKeys = modifyKeySet(_deletedKeys, value);
+ }
+
+ /**
+ * Returns true if the current key is in the provided set.
+ *
+ * @param keySet
+ * the set of keys to check, or null
+ * @return true if the key is in the set, false if it is missing (or if
keySet is null)
+ */
+ protected final boolean inKeySet(Set<K> keySet)
+ {
+ return keySet != null ? keySet.contains(_currentKey) : false;
+ }
+
+ /**
+ * Modifies a keySet to add or remove the current key. If necessary, a new
Set is created.
+ * <p>
+ * Useage: <code>
+ * private Set<K> _myFlagKeys;
+ *
+ * public boolean void setMyFlag(boolean value)
+ * {
+ * _myFlagKeys = modifySet(_myFlagKeys, value);
+ * }
+ * </code>
+ *
+ * @param keySet
+ * the set of keys, or null
+ * @param value
+ * true to add the current key, false to remove
+ * @return the provided key set, or a new one
+ */
+ protected final Set<K> modifyKeySet(Set<K> keySet, boolean value)
+ {
+ if (keySet == null)
+ {
+ if (!value)
+ return null;
+
+ keySet = newSet();
+ }
+
+ if (value)
+ keySet.add(_currentKey);
+ else
+ keySet.remove(_currentKey);
+
+ return keySet;
+ }
+
+ /** Does nothing. Subclasses may override as necessary. */
+ public void prepareForKeys(List<K> keys)
+ {
+ }
}
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/PrimaryKeyEncoder.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/PrimaryKeyEncoder.java?view=diff&rev=496918&r1=496917&r2=496918
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/PrimaryKeyEncoder.java
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/PrimaryKeyEncoder.java
Tue Jan 16 18:50:12 2007
@@ -15,6 +15,7 @@
package org.apache.tapestry;
import java.io.Serializable;
+import java.util.List;
import org.apache.tapestry.corelib.components.Loop;
@@ -24,7 +25,7 @@
* side object.
*
* @param <K>
- * the type of the primary key
+ * the type of the primary key, used to identify the value (which
must be serializable)
* @param <V>
* the type of value identified by the key
*/
@@ -33,21 +34,31 @@
/**
* Given a particular value, this method extracts and returns the primary
key that identifies
* the value. The key will later be converted back into a value using
- * [EMAIL PROTECTED] #recoverObject(Serializable)}.
+ * [EMAIL PROTECTED] #toValue(Serializable)}.
*
* @param value
* whose primary key is needed
* @return the key for the value
*/
- K extractKey(V value);
+ K toKey(V value);
/**
- * For a particular primary key, previously obtained via [EMAIL PROTECTED]
#extractKey(Object)}, this
- * method returns the same or equivalent object.
+ * Invoked as part of a form submission to alert the encoder that a series
of keys may be
+ * converted back to values. This is advisory only, and the keys passed to
+ * [EMAIL PROTECTED] #toValue(Serializable)} may not include all keys in
the list, or may include keys not
+ * in the list. In general, though, the keys passed in will match the
actual keys to be
+ * converted, giving the encoder a chance to efficiently fetch the
necessary value objects as a
+ * group.
+ */
+ void prepareForKeys(List<K> keys);
+
+ /**
+ * For a particular primary key, previously obtained via [EMAIL PROTECTED]
#toKey(Object)}, this method
+ * returns the same or equivalent object.
*
* @param key
* used to identify the object
- * @return the object for the key
+ * @return the value object for the key
*/
- V recoverObject(K key);
+ V toValue(K key);
}
Added:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/TapestryMessages.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/TapestryMessages.java?view=auto&rev=496918
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/TapestryMessages.java
(added)
+++
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/TapestryMessages.java
Tue Jan 16 18:50:12 2007
@@ -0,0 +1,22 @@
+package org.apache.tapestry;
+
+import java.util.Collection;
+
+import org.apache.tapestry.ioc.Messages;
+import org.apache.tapestry.ioc.internal.util.InternalUtils;
+import org.apache.tapestry.ioc.internal.util.MessagesImpl;
+
+final class TapestryMessages
+{
+ private static final Messages MESSAGES =
MessagesImpl.forClass(TapestryMessages.class);
+
+ static String duplicateKey(Object key, Object newValue, Object
existingValue)
+ {
+ return MESSAGES.format("duplicate-key", key, newValue, existingValue);
+ }
+
+ static <V> String missingValue(V value, Collection<V> values)
+ {
+ return MESSAGES.format("missing-value", value,
InternalUtils.joinSorted(values));
+ }
+}
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Form.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Form.java?view=diff&rev=496918&r1=496917&r2=496918
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Form.java
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Form.java
Tue Jan 16 18:50:12 2007
@@ -300,7 +300,7 @@
_resources.triggerEvent(PREPARE_EVENT, context, handler);
- if (holder.get() != null)
+ if (holder.hasValue())
return holder.get();
// TODO: Ajax stuff will eventually mean there are multiple values
for this parameter
@@ -344,7 +344,7 @@
_resources.triggerEvent(VALIDATE, context, handler);
- if (holder.get() != null)
+ if (holder.hasValue())
return holder.get();
_formSupport.executeDeferred();
@@ -352,11 +352,18 @@
// Let the listeners know about overall success or failure. Most
listeners fall into
// one of those two camps.
+ // If the tracker has no errors, then clear it of any input values
+ // as well, so that the next page render will be "clean" and show
+ // true persistent data, not value from the previous form
submission.
+
+ if (!_tracker.getHasErrors())
+ _tracker.clear();
+
_resources.triggerEvent(tracker.getHasErrors() ? FAILURE :
SUCCESS, context, handler);
// Lastly, tell anyone whose interested that the form is
completely submitted.
- if (holder.get() != null)
+ if (holder.hasValue())
return holder.get();
_resources.triggerEvent(SUBMIT, context, handler);
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Loop.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Loop.java?view=diff&rev=496918&r1=496917&r2=496918
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Loop.java
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Loop.java
Tue Jan 16 18:50:12 2007
@@ -14,8 +14,11 @@
package org.apache.tapestry.corelib.components;
+import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
+
import java.io.Serializable;
import java.util.Iterator;
+import java.util.List;
import org.apache.tapestry.ComponentAction;
import org.apache.tapestry.ComponentResources;
@@ -33,48 +36,129 @@
/**
* Basic looping class; loops over a number of items (provided by its source
parameter), rendering
- * its body for each one.
+ * its body for each one. It turns out that gettting the component to
<em>not</em> store its state
+ * in the Form is very tricky and, in fact, a series of commands for starting
and ending heartbeats,
+ * and advancing through the iterator, are still stored. For a non-volatile
Loop inside the form,
+ * the Loop stores a series of commands that start and end heartbeats and
store state (either as
+ * full objects when there is not encoder, or as client-side objects when
there is an encoder).
*/
@ComponentClass
public class Loop
{
+ /** Setup command for non-volatile rendering. */
+ private static final ComponentAction<Loop> RESET_INDEX = new
ComponentAction<Loop>()
+ {
+ private static final long serialVersionUID = 6477493424977597345L;
+
+ public void execute(Loop component)
+ {
+ component.resetIndex();
+ }
+ };
+
+ /**
+ * Setup command for volatile rendering. Volatile rendering relies on
re-acquiring the Iterator
+ * and working our way through it (and hoping for the best!).
+ */
+ private static final ComponentAction<Loop> SETUP_FOR_VOLATILE = new
ComponentAction<Loop>()
+ {
+ private static final long serialVersionUID = -977168791667037377L;
+
+ public void execute(Loop component)
+ {
+ component.setupForVolatile();
+ };
+ };
+
+ /**
+ * Advances to next value in a volatile way. So, the <em>number</em> of
steps is intrinsically
+ * stored in the Form (as the number of ADVANCE_VOLATILE commands), but
the actual values are
+ * expressly stored only on the server.
+ */
+ private static final ComponentAction<Loop> ADVANCE_VOLATILE = new
ComponentAction<Loop>()
+ {
+ private static final long serialVersionUID = -4600281573714776832L;
+
+ public void execute(Loop component)
+ {
+ component.advanceVolatile();
+ }
+ };
+
+ /**
+ * Used in both volatile and non-volatile mode to end the current
heartbeat (started by either
+ * ADVANCE_VOLATILE or one of the RestoreState commands). Also increments
the index.
+ */
+ private static final ComponentAction<Loop> END_HEARTBEAT = new
ComponentAction<Loop>()
+ {
+ private static final long serialVersionUID = -977168791667037377L;
+
+ public void execute(Loop component)
+ {
+ component.endHeartbeat();
+ };
+ };
+
+ /**
+ * Restores a state value (this is the case when there is no encoder and
the complete value is
+ * stored).
+ */
static class RestoreState implements ComponentAction<Loop>
{
private static final long serialVersionUID = -3926831611368720764L;
- private final int _index;
-
private final Object _storedValue;
- public RestoreState(final int index, final Object storedValue)
+ public RestoreState(final Object storedValue)
{
- _index = index;
_storedValue = storedValue;
}
public void execute(Loop component)
{
- component.restoreState(_index, _storedValue);
+ component.restoreState(_storedValue);
}
};
- static class RestoreConvertedState implements ComponentAction<Loop>
+ /**
+ * Restores the value using a stored primary key via
+ * [EMAIL PROTECTED] PrimaryKeyEncoder#toValue(Serializable)}.
+ */
+ static class RestoreStateViaEncodedPrimaryKey implements
ComponentAction<Loop>
{
private static final long serialVersionUID = -2422790241589517336L;
- private final int _index;
-
private final Serializable _primaryKey;
- public RestoreConvertedState(final int index, final Serializable
primaryKey)
+ public RestoreStateViaEncodedPrimaryKey(final Serializable primaryKey)
{
- _index = index;
_primaryKey = primaryKey;
}
public void execute(Loop component)
{
- component.restoreConvertedState(_index, _primaryKey);
+ component.restoreStateViaEncodedPrimaryKey(_primaryKey);
+ }
+ };
+
+ /**
+ * Stores a list of keys to be passed to [EMAIL PROTECTED]
PrimaryKeyEncoder#prepareForKeys(List)}.
+ */
+ static class PrepareForKeys implements ComponentAction<Loop>
+ {
+ private static final long serialVersionUID = -6515255627142956828L;
+
+ /** The variable is final, the contents are mutable while the Loop
renders. */
+ private final List<Serializable> _keys;
+
+ public PrepareForKeys(final List<Serializable> keys)
+ {
+ _keys = keys;
+ }
+
+ public void execute(Loop component)
+ {
+ component.prepareForKeys(_keys);
}
};
@@ -126,6 +210,8 @@
@Inject
private ComponentResources _resources;
+ private List<Serializable> _keyList;
+
@SetupRender
boolean setup()
{
@@ -136,11 +222,50 @@
_iterator = _source.iterator();
- _storeRenderStateInForm = _formSupport != null & !_volatile;
+ _storeRenderStateInForm = _formSupport != null && !_volatile;
// Only render the body if there is something to iterate over
- return _iterator.hasNext();
+ boolean result = _iterator.hasNext();
+
+ if (_formSupport != null && result)
+ {
+
+ _formSupport.store(this, _volatile ? SETUP_FOR_VOLATILE :
RESET_INDEX);
+
+ if (_encoder != null)
+ {
+ _keyList = newList();
+
+ // We'll keep updating the _keyList while the Loop renders,
the values will "lock
+ // down" when the Form serializes all the data.
+
+ _formSupport.store(this, new PrepareForKeys(_keyList));
+ }
+ }
+
+ return result;
+ }
+
+ private void prepareForKeys(List<Serializable> keys)
+ {
+ // Again, the encoder existed when we rendered, we better have another
available
+ // when the enclosing Form is submitted.
+
+ _encoder.prepareForKeys(keys);
+ }
+
+ private void setupForVolatile()
+ {
+ _index = 0;
+ _iterator = _source.iterator();
+ }
+
+ private void advanceVolatile()
+ {
+ _value = _iterator.next();
+
+ startHeartbeat();
}
/** Begins a new heartbeat. */
@@ -153,17 +278,24 @@
{
if (_encoder == null)
{
- _formSupport.store(this, new RestoreState(_index, _value));
+ _formSupport.store(this, new RestoreState(_value));
}
else
{
- Serializable primaryKey = _encoder.extractKey(_value);
- _formSupport.store(this, new RestoreConvertedState(_index,
primaryKey));
+ Serializable primaryKey = _encoder.toKey(_value);
+ _formSupport.store(this, new
RestoreStateViaEncodedPrimaryKey(primaryKey));
}
}
- _heartbeat.begin();
+ if (_formSupport != null && _volatile)
+ _formSupport.store(this, ADVANCE_VOLATILE);
+
+ startHeartbeat();
+ }
+ private void startHeartbeat()
+ {
+ _heartbeat.begin();
}
void beforeRenderBody(MarkupWriter writer)
@@ -185,29 +317,43 @@
@AfterRender
boolean after()
{
+ endHeartbeat();
+
+ if (_formSupport != null)
+ _formSupport.store(this, END_HEARTBEAT);
+
+ return _iterator.hasNext();
+ }
+
+ private void endHeartbeat()
+ {
_heartbeat.end();
_index++;
+ }
- return _iterator.hasNext();
+ private void resetIndex()
+ {
+ _index = 0;
}
/** Restores state previously stored by the Loop into a Form. */
- private void restoreState(int index, Object storedValue)
+ private void restoreState(Object storedValue)
{
- _index = index;
_value = storedValue;
+
+ startHeartbeat();
}
- /** Restores state previosly converted by the Loop and stored into the
Form. */
- private void restoreConvertedState(int index, Serializable primaryKey)
+ /** Restores state previously encoded by the Loop and stored into the
Form. */
+ private void restoreStateViaEncodedPrimaryKey(Serializable primaryKey)
{
- // We assume that if a converter is available when we rendered, that
one will be available
+ // We assume that if a encoder is available when we rendered, that one
will be available
// when the form is submitted. TODO: Check for this.
- Object restoredValue = _encoder.recoverObject(primaryKey);
+ Object restoredValue = _encoder.toValue(primaryKey);
- restoreState(index, restoredValue);
+ restoreState(restoredValue);
}
// For testing:
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/Holder.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/Holder.java?view=diff&rev=496918&r1=496917&r2=496918
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/Holder.java
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/Holder.java
Tue Jan 16 18:50:12 2007
@@ -34,6 +34,11 @@
return _held;
}
+ public boolean hasValue()
+ {
+ return _held != null;
+ }
+
public static <T> Holder<T> create()
{
return new Holder<T>();
Added:
tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/TapestryStrings.properties
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/TapestryStrings.properties?view=auto&rev=496918
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/TapestryStrings.properties
(added)
+++
tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/TapestryStrings.properties
Tue Jan 16 18:50:12 2007
@@ -0,0 +1,2 @@
+duplicate-key=Key %s may not be added with value %s, as an existing value, %s,
is already present.
+missing-value=Key for value %s not found. Available values: %s
\ No newline at end of file
Copied:
tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/ToDoListVolatile.html
(from r496578,
tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/ToDoList.html)
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/ToDoListVolatile.html?view=diff&rev=496918&p1=tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/ToDoList.html&r1=496578&p2=tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/ToDoListVolatile.html&r2=496918
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/ToDoList.html
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/ToDoListVolatile.html
Tue Jan 16 18:50:12 2007
@@ -11,7 +11,7 @@
<th> Title </th>
<th> Reorder </th>
</tr>
- <tr t:type="Loop" source="database.findAll()" value="item">
+ <tr t:type="Loop" source="items" value="item" volatile="true">
<td>
<input t:type="TextField" t:id="title" value="item.title"
size="30"
validate="validate:required"/>
@@ -21,10 +21,16 @@
<tr>
<td colspan="2">
<input type="submit" value="Update ToDos"/>
- <input t:type="Submit" value="'Add new ToDo'"/>
+ <input t:type="Submit" t:id="addNew" value="'Add new
ToDo'"/>
</td>
</tr>
</table>
</form>
+
+
+ <p>
+ <a t:type="ActionLink" t:id="reset">reset the database</a>
+ </p>
+
</html>
Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html?view=diff&rev=496918&r1=496917&r2=496918
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html Tue Jan 16
18:50:12 2007
@@ -48,12 +48,13 @@
<li>
<a
href="RenderPhaseOrder.html">RenderPhaseOrder</a> -- Order of
operations when invoking render phase methods </li>
+ <li><a href="SimpleForm.html">SimpleForm</a> -- first
pass at writing Form
+ and TextField components </li>
+
</ul>
</td>
<td>
<ul>
- <li><a href="SimpleForm.html">SimpleForm</a> -- first
pass at writing Form
- and TextField components </li>
<li>
<a href="NumberSelect.html">NumberSelect</a> --
passivate/activate page
context demo </li>
@@ -80,14 +81,17 @@
<a
href="PasswordFieldDemo.html">PasswordFieldDemo</a> -- test for the
PasswordField component </li>
<li>
- <a
href="RenderComponentDemo.html">RenderComponentDemo</a> -- components that
"nominate" other components to render
- </li>
+ <a
href="RenderComponentDemo.html">RenderComponentDemo</a> -- components
+ that "nominate" other components to render </li>
+ <li>
+ <a href="BlockDemo.html">BlockDemo</a> -- use of
blocks to control
+ rendering </li>
<li>
- <a href="BlockDemo.html">BlockDemo</a> -- use of
blocks to control rendering
- </li>
+ <a href="ToDoListVolatile.html">ToDo List
(Volatile)</a> -- Loops and
+ Submit inside Form, volatile mode </li>
<li>
- <a href="ToDoList.html">ToDo list</a> -- Loops
and Submit inside Form
- </li>
+ <a href="ToDoList.html">ToDo List</a> -- Loops and
Submit inside Form
+ using a primary key encoder </li>
</ul>
</td>
</tr>
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/DefaultPrimaryKeyEncoderTest.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/DefaultPrimaryKeyEncoderTest.java?view=diff&rev=496918&r1=496917&r2=496918
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/DefaultPrimaryKeyEncoderTest.java
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/DefaultPrimaryKeyEncoderTest.java
Tue Jan 16 18:50:12 2007
@@ -47,6 +47,42 @@
}
@Test
+ public void keys_must_be_unique()
+ {
+ IntStringEncoder encoder = newEncoder();
+
+ try
+ {
+ encoder.add(FRED_ID, "NewFred");
+ unreachable();
+ }
+ catch (IllegalArgumentException ex)
+ {
+ assertEquals(
+ ex.getMessage(),
+ "Key 1 may not be added with value NewFred, as an existing
value, FRED, is already present.");
+ }
+ }
+
+ @Test
+ public void extract_key_for_missing_value()
+ {
+ IntStringEncoder encoder = newEncoder();
+
+ try
+ {
+ encoder.toKey("BETTY");
+ unreachable();
+ }
+ catch (IllegalArgumentException ex)
+ {
+ assertEquals(
+ ex.getMessage(),
+ "Key for value BETTY not found. Available values: BARNEY,
FRED, WILMA");
+ }
+ }
+
+ @Test
public void value_ordered_maintained()
{
IntStringEncoder encoder = newEncoder();
@@ -59,8 +95,8 @@
{
IntStringEncoder encoder = newEncoder();
- assertEquals(encoder.extractKey(FRED).intValue(), FRED_ID);
- assertEquals(encoder.extractKey(BARNEY).intValue(), BARNEY_ID);
+ assertEquals(encoder.toKey(FRED).intValue(), FRED_ID);
+ assertEquals(encoder.toKey(BARNEY).intValue(), BARNEY_ID);
}
@Test
@@ -68,8 +104,8 @@
{
IntStringEncoder encoder = newEncoder();
- assertEquals(encoder.recoverObject(FRED_ID), FRED);
- assertEquals(encoder.recoverObject(BARNEY_ID), BARNEY);
+ assertEquals(encoder.toValue(FRED_ID), FRED);
+ assertEquals(encoder.toValue(BARNEY_ID), BARNEY);
}
@Test
@@ -77,7 +113,7 @@
{
IntStringEncoder encoder = newEncoder();
- assertNull(encoder.recoverObject(99), null);
+ assertNull(encoder.toValue(99), null);
}
@Test
@@ -97,7 +133,50 @@
}
};
- assertSame(encoder.recoverObject(bettyId), betty);
+ assertSame(encoder.toValue(bettyId), betty);
+ }
+
+ @Test
+ public void set_delete_false_when_nothing_yet_deleted()
+ {
+ IntStringEncoder encoder = newEncoder();
+
+ assertSame(FRED, encoder.toValue(FRED_ID));
+
+ encoder.setDeleted(false);
+
+ assertEquals(encoder.getValues(), encoder.getAllValues());
+ }
+
+ @Test
+ public void difference_between_get_values_and_get_all_values()
+ {
+ IntStringEncoder encoder = newEncoder();
+
+ assertSame(FRED, encoder.toValue(FRED_ID));
+
+ assertFalse(encoder.isDeleted());
+
+ encoder.setDeleted(true);
+
+ assertTrue(encoder.isDeleted());
+
+ assertEquals(encoder.getValues(), Arrays.asList(BARNEY, WILMA));
+
+ assertEquals(encoder.getAllValues(), Arrays.asList(BARNEY, FRED,
WILMA));
+ }
+
+ @Test
+ public void undelete_a_value()
+ {
+ IntStringEncoder encoder = newEncoder();
+
+ assertSame(FRED, encoder.toValue(FRED_ID));
+
+ encoder.setDeleted(true);
+ encoder.setDeleted(false);
+
+ assertEquals(encoder.getValues(), encoder.getAllValues());
}
private IntStringEncoder newEncoder()
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java?view=diff&rev=496918&r1=496917&r2=496918
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
Tue Jan 16 18:50:12 2007
@@ -506,6 +506,51 @@
assertTextPresent("Should now show up:");
}
+ @Test
+ public void volatile_loop_inside_a_form()
+ {
+ test_loop_inside_form("ToDo List (Volatile)");
+ }
+
+ @Test
+ public void encoded_loop_inside_a_form()
+ {
+ test_loop_inside_form("ToDo List");
+ }
+
+ private void test_loop_inside_form(String linkLabel)
+ {
+ _selenium.open(BASE_URL);
+
+ clickAndWait("link=" + linkLabel);
+ clickAndWait("reset");
+
+ assertValue("title", "Ditch Struts");
+ assertValue("title_0", "Eliminate JSF");
+ assertValue("title_1", "Conquer Rife");
+
+ _selenium.type("title", "Ditch Struts - today");
+ _selenium.type("title_0", "Eliminate JSF - immediately");
+ _selenium.type("title_1", "Conquer Rife - post haste");
+
+ clickAndWait("//[EMAIL PROTECTED]'Update ToDos']");
+
+ assertValue("title", "Ditch Struts - today");
+ assertValue("title_0", "Eliminate JSF - immediately");
+ assertValue("title_1", "Conquer Rife - post haste");
+
+ clickAndWait("addNew");
+
+ _selenium.type("title_2", "Conquer World");
+
+ clickAndWait("//[EMAIL PROTECTED]'Update ToDos']");
+
+ assertValue("title", "Ditch Struts - today");
+ assertValue("title_0", "Eliminate JSF - immediately");
+ assertValue("title_1", "Conquer Rife - post haste");
+ assertValue("title_2", "Conquer World");
+ }
+
/**
* Tests the ability to inject a Block, and the ability to use the block
to control rendering.
*/
Copied:
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/ToDoListVolatile.java
(from r496578,
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/ToDoList.java)
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/ToDoListVolatile.java?view=diff&rev=496918&p1=tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/ToDoList.java&r1=496578&p2=tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/ToDoListVolatile.java&r2=496918
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/ToDoList.java
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/ToDoListVolatile.java
Tue Jan 16 18:50:12 2007
@@ -14,6 +14,8 @@
package org.apache.tapestry.integration.app1.pages;
+import java.util.List;
+
import org.apache.tapestry.annotations.Component;
import org.apache.tapestry.annotations.ComponentClass;
import org.apache.tapestry.annotations.Inject;
@@ -22,16 +24,23 @@
import org.apache.tapestry.integration.app1.services.ToDoDatabase;
@ComponentClass
-public class ToDoList
+public class ToDoListVolatile
{
@Inject
private ToDoDatabase _database;
private ToDoItem _item;
+ private List<ToDoItem> _items;
+
@Component
private Form _form;
+ public List<ToDoItem> getItems()
+ {
+ return _items;
+ }
+
public ToDoItem getItem()
{
return _item;
@@ -45,5 +54,38 @@
public ToDoDatabase getDatabase()
{
return _database;
+ }
+
+ void onPrepare()
+ {
+ _items = _database.findAll();
+ }
+
+ void onSuccess()
+ {
+ int order = 0;
+
+ for (ToDoItem item : _items)
+ {
+ item.setOrder(order++);
+ _database.update(item);
+ }
+ }
+
+ void onSelectedFromAddNew()
+ {
+ if (_form.isValid())
+ {
+ ToDoItem item = new ToDoItem();
+ item.setTitle("<New To Do>");
+ item.setOrder(_items.size());
+
+ _database.add(item);
+ }
+ }
+
+ void onActionFromReset()
+ {
+ _database.reset();
}
}
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabase.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabase.java?view=diff&rev=496918&r1=496917&r2=496918
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabase.java
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabase.java
Tue Jan 16 18:50:12 2007
@@ -34,4 +34,7 @@
* if the item does not exist
*/
void update(ToDoItem item);
+
+ /** Resets the database, clearing out all data, re-adding base data. */
+ void reset();
}
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabaseImpl.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabaseImpl.java?view=diff&rev=496918&r1=496917&r2=496918
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabaseImpl.java
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabaseImpl.java
Tue Jan 16 18:50:12 2007
@@ -39,6 +39,13 @@
{
// A couple of items to get us started:
+ reset();
+ }
+
+ public void reset()
+ {
+ _items.clear();
+
add("Ditch Struts", 1);
add("Eliminate JSF", 2);
add("Conquer Rife", 3);