| // Copyright 2006, 2007, 2008 The Apache Software Foundation |
| // |
| // 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.apache.tapestry5.corelib.components; |
| |
| import org.apache.tapestry5.*; |
| import org.apache.tapestry5.annotations.*; |
| import org.apache.tapestry5.ioc.annotations.Inject; |
| import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newList; |
| import org.apache.tapestry5.services.ComponentDefaultProvider; |
| import org.apache.tapestry5.services.FormSupport; |
| import org.apache.tapestry5.services.Heartbeat; |
| |
| import java.io.Serializable; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| /** |
| * Basic looping class; loops over a number of items (provided by its source parameter), rendering 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 the encoder parameter is not bound, or as client-side objects when there is |
| * an encoder). |
| */ |
| @SupportsInformalParameters |
| 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 Object storedValue; |
| |
| public RestoreState(final Object storedValue) |
| { |
| this.storedValue = storedValue; |
| } |
| |
| public void execute(Loop component) |
| { |
| component.restoreState(storedValue); |
| } |
| } |
| |
| /** |
| * Restores the value using a stored primary key via {@link PrimaryKeyEncoder#toValue(Serializable)}. |
| */ |
| static class RestoreStateViaEncodedPrimaryKey implements ComponentAction<Loop> |
| { |
| private static final long serialVersionUID = -2422790241589517336L; |
| |
| private final Serializable primaryKey; |
| |
| public RestoreStateViaEncodedPrimaryKey(final Serializable primaryKey) |
| { |
| this.primaryKey = primaryKey; |
| } |
| |
| public void execute(Loop component) |
| { |
| component.restoreStateViaEncodedPrimaryKey(primaryKey); |
| } |
| } |
| |
| /** |
| * Stores a list of keys to be passed to {@link 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) |
| { |
| this.keys = keys; |
| } |
| |
| public void execute(Loop component) |
| { |
| component.prepareForKeys(keys); |
| } |
| } |
| |
| /** |
| * Defines the collection of values for the loop to iterate over. If not specified, defaults to a property of the |
| * container whose name |
| */ |
| @Parameter(required = true, principal = true) |
| private Iterable<?> source; |
| |
| /** |
| * Optional primary key converter; if provided and inside a form and not volatile, then each iterated value is |
| * converted and stored into the form. |
| */ |
| @Parameter |
| private PrimaryKeyEncoder<Serializable, Object> encoder; |
| |
| /** |
| * If true and the Loop is enclosed by a Form, then the normal state saving logic is turned off. Defaults to false, |
| * enabling state saving logic within Forms. |
| */ |
| @Parameter(name = "volatile") |
| private boolean volatileState; |
| |
| @Environmental(false) |
| private FormSupport formSupport; |
| |
| /** |
| * The element to render. If not null, then the loop will render the indicated element around its body (on each pass |
| * through the loop). The default is derived from the component template. |
| */ |
| @Parameter(value = "prop:componentResources.elementName", defaultPrefix = BindingConstants.LITERAL) |
| private String element; |
| |
| /** |
| * The current value, set before the component renders its body. |
| */ |
| @Parameter |
| private Object value; |
| |
| /** |
| * The index into the source items. |
| */ |
| @Parameter |
| private int index; |
| |
| private Iterator<?> iterator; |
| |
| @Environmental |
| private Heartbeat heartbeat; |
| |
| private boolean storeRenderStateInForm; |
| |
| @Inject |
| private ComponentResources resources; |
| |
| @Inject |
| private ComponentDefaultProvider componentDefaultProvider; |
| |
| Binding defaultSource() |
| { |
| return componentDefaultProvider.defaultBinding("source", resources); |
| } |
| |
| @SetupRender |
| boolean setup() |
| { |
| index = 0; |
| |
| if (source == null) return false; |
| |
| iterator = source.iterator(); |
| |
| storeRenderStateInForm = formSupport != null && !volatileState; |
| |
| // Only render the body if there is something to iterate over |
| |
| boolean result = iterator.hasNext(); |
| |
| if (formSupport != null && result) |
| { |
| |
| formSupport.store(this, volatileState ? SETUP_FOR_VOLATILE : RESET_INDEX); |
| |
| if (encoder != null) |
| { |
| List<Serializable> 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. |
| */ |
| @BeginRender |
| void begin() |
| { |
| value = iterator.next(); |
| |
| if (storeRenderStateInForm) |
| { |
| if (encoder == null) |
| { |
| formSupport.store(this, new RestoreState(value)); |
| } |
| else |
| { |
| Serializable primaryKey = encoder.toKey(value); |
| formSupport.store(this, new RestoreStateViaEncodedPrimaryKey(primaryKey)); |
| } |
| } |
| |
| if (formSupport != null && volatileState) formSupport.store(this, ADVANCE_VOLATILE); |
| |
| startHeartbeat(); |
| } |
| |
| private void startHeartbeat() |
| { |
| heartbeat.begin(); |
| } |
| |
| void beforeRenderBody(MarkupWriter writer) |
| { |
| if (element != null) |
| { |
| writer.element(element); |
| resources.renderInformalParameters(writer); |
| } |
| } |
| |
| void afterRenderBody(MarkupWriter writer) |
| { |
| if (element != null) writer.end(); |
| } |
| |
| /** |
| * Ends the current heartbeat. |
| */ |
| @AfterRender |
| boolean after() |
| { |
| endHeartbeat(); |
| |
| if (formSupport != null) formSupport.store(this, END_HEARTBEAT); |
| |
| return !iterator.hasNext(); |
| } |
| |
| private void endHeartbeat() |
| { |
| heartbeat.end(); |
| |
| index++; |
| } |
| |
| private void resetIndex() |
| { |
| index = 0; |
| } |
| |
| /** |
| * Restores state previously stored by the Loop into a Form. |
| */ |
| private void restoreState(Object storedValue) |
| { |
| value = storedValue; |
| |
| startHeartbeat(); |
| } |
| |
| /** |
| * Restores state previously encoded by the Loop and stored into the Form. |
| */ |
| private void restoreStateViaEncodedPrimaryKey(Serializable primaryKey) |
| { |
| // 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.toValue(primaryKey); |
| |
| restoreState(restoredValue); |
| } |
| |
| // For testing: |
| |
| int getIndex() |
| { |
| return index; |
| } |
| |
| Object getValue() |
| { |
| return value; |
| } |
| |
| void setSource(Iterable<?> source) |
| { |
| this.source = source; |
| } |
| |
| void setHeartbeat(Heartbeat heartbeat) |
| { |
| this.heartbeat = heartbeat; |
| } |
| } |